Keep fitting URLs intact in fullscreen Markdown transcripts (#60416)

## Why

URLs in fullscreen Markdown transcripts could split across rows even when the full URL would fit on its own row.

## What changed

Mark non-code Markdown lines containing URL-like text with the `UrlAware` wrap policy so transcript layout preserves fitting URLs on a single row.

## Testing

Add a regression snapshot showing a documentation URL moving intact to the next row after introductory text. Update the conversation snapshot and copy-on-select test dimensions for the changed wrapping.

GitOrigin-RevId: 4812c83483cff412406a71ae6745977c5384c7da
This commit is contained in:
Eric Traut
2026-11-09 16:58:01 +00:00
committed by copyberry
parent 6222faa3c4
commit 83f02f706c
4 changed files with 32 additions and 4 deletions
@@ -4,14 +4,14 @@ expression: normalize_snapshot_paths(screen)
---
····································
····································
····· ·····
·····› Second prompt ·····
····· ·····
····· ·····
·····• For the complete ·····
····· instructions see the ·····
····· manual (https:// ·····
····· example.com/docs). ·····
····· manual ·····
····· (https://example.com/doc·····
····· s). ·····
····· ·····
····· ·····
·····› preserved draft ·····
+5
View File
@@ -2396,6 +2396,11 @@ impl<'a, 'policy> Writer<'a, 'policy> {
if let Some(mut line) = self.current_line_content.take() {
let style = self.current_line_style;
let mut source = crate::terminal_hyperlinks::LogicalLineSource::from_line(&line.line);
if !self.current_line_in_code_block
&& crate::wrapping::text_contains_url_like(&source.text)
{
source.wrap_policy = crate::terminal_hyperlinks::LineWrapPolicy::UrlAware;
}
source.copy = Some(std::sync::Arc::new(std::mem::take(&mut self.copy_line)));
// NB we don't wrap code in code blocks, in order to preserve whitespace for copy/paste.
if !self.current_line_in_code_block
@@ -186,7 +186,7 @@ fn copy_on_select_preserves_link_activation_and_ignores_empty_drags() {
copy_on_select: true,
..Default::default()
};
render(&mut view, &cells, /*width*/ 30, /*height*/ 1);
render(&mut view, &cells, /*width*/ 30, /*height*/ 2);
let down = mouse(MouseEventKind::Down(MouseButton::Left), /*column*/ 2);
assert!(matches!(
view.handle_mouse(down, &cells),
@@ -298,6 +298,29 @@ fn narrow_wrapping_keeps_joined_emoji_whole() {
assert_eq!(buffer[(0, 0)].symbol(), "👩‍💻");
}
#[test]
fn fullscreen_markdown_keeps_a_fitting_url_on_one_row() {
use crate::history_cell::AgentMarkdownCell;
use crate::history_cell::HistoryCell;
let cell = AgentMarkdownCell::new(
"See the documentation at https://example.com/docs/reference.".into(),
std::path::Path::new("."),
);
let layout = TextLayout::new(
cell.retained_hyperlink_lines(/*width*/ 44, /*detailed*/ false),
/*width*/ 44,
);
let rows = layout
.rows
.iter()
.map(|row| row.line.line.to_string())
.collect::<Vec<_>>();
insta::assert_snapshot!(rows.join("\n"), @"
• See the documentation at
https://example.com/docs/reference.
");
}
#[test]
fn source_backed_markdown_copy_preserves_code_indentation() {
use crate::history_cell::AgentMarkdownCell;