mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Preserve line breaks and links in multiline async questions (#51866)
## Why
Rendering an entire question title as one terminal line removes line endings and misaligns hyperlink source ranges with the visible text.
## What changed
Wrap and annotate each logical line of asynchronous question titles separately, preserving blank lines and handling both LF and CRLF endings while retaining complete hyperlink destinations.
## Testing
Add a rendering regression test and snapshot covering multiline titles with code fences, Unicode text, and a URL. Check LF and CRLF rendering equivalence and hyperlink destinations at narrow and wide widths.
GitOrigin-RevId: ccd7256dbaed91ce98374e52b17d3a1b87645574
(cherry picked from commit eef4719927)
This commit is contained in:
committed by
David Wiesen
parent
a028250541
commit
1a1335eefe
@@ -158,16 +158,24 @@ impl AsyncQuestions {
|
||||
pub(super) fn wrapped_question_lines(&self, width: u16) -> Vec<HyperlinkLine> {
|
||||
self.current_question()
|
||||
.map(|q| {
|
||||
let source = annotate_web_urls_in_line(q.title.clone().into());
|
||||
let wrapped = wrap_ranges_trim(&q.title, usize::from(width.max(1)))
|
||||
.into_iter()
|
||||
.map(|range| WrappedLine {
|
||||
line: (&q.title[range.clone()]).into(),
|
||||
range,
|
||||
prefix_bytes: 0,
|
||||
// Line::from removes line endings, so wrap and annotate each logical line
|
||||
// separately to keep source byte ranges aligned with the visible text.
|
||||
q.title
|
||||
.split('\n')
|
||||
.flat_map(|text| {
|
||||
let text = text.strip_suffix('\r').unwrap_or(text);
|
||||
let source = annotate_web_urls_in_line(text.to_owned().into());
|
||||
let wrapped = wrap_ranges_trim(text, usize::from(width.max(1)))
|
||||
.into_iter()
|
||||
.map(|range| WrappedLine {
|
||||
line: (&text[range.clone()]).into(),
|
||||
range,
|
||||
prefix_bytes: 0,
|
||||
})
|
||||
.collect();
|
||||
remap_source_wrapped_line(&source, wrapped)
|
||||
})
|
||||
.collect();
|
||||
remap_source_wrapped_line(&source, wrapped)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/async_questions/state_tests.rs
|
||||
expression: "rendered[0]"
|
||||
---
|
||||
|
||||
1 of 2
|
||||
|
||||
Run this command:
|
||||
|
||||
```sh
|
||||
printf '診 断 '
|
||||
```
|
||||
Then review (https://example.com/diagnostics?view=full)?
|
||||
|
||||
|
||||
Type your answer
|
||||
|
||||
enter submit ⌃] skip ⇧→ main prompt
|
||||
⇧← next question
|
||||
@@ -180,6 +180,44 @@ fn wrapped_question_url_keeps_its_complete_destination() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_question_preserves_line_breaks_and_link_destinations() {
|
||||
let url = "https://example.com/diagnostics?view=full";
|
||||
let title = format!("Run this command:\n\n```sh\nprintf '診断'\n```\nThen review ({url})?\n");
|
||||
let mut rendered = Vec::new();
|
||||
for line_ending in ["\n", "\r\n"] {
|
||||
let mut editor = editor();
|
||||
editor.state.pending[0].question.title = title.replace('\n', line_ending);
|
||||
|
||||
for width in [20, 60] {
|
||||
let height = editor.desired_height(width);
|
||||
let buffer = render_editor(&editor, width, height);
|
||||
let linked_text = buffer
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|cell| {
|
||||
let symbol = cell.symbol();
|
||||
symbol.contains("\x1b]8;;").then(|| {
|
||||
let text = crate::terminal_hyperlinks::strip_osc8(symbol);
|
||||
assert_eq!(
|
||||
symbol,
|
||||
crate::terminal_hyperlinks::osc8_hyperlink(url, &text)
|
||||
);
|
||||
text
|
||||
})
|
||||
})
|
||||
.collect::<String>();
|
||||
assert_eq!(linked_text, url);
|
||||
let text = crate::terminal_hyperlinks::strip_osc8(&buffer_text(&buffer));
|
||||
if width == 60 {
|
||||
rendered.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(rendered[0], rendered[1]);
|
||||
insta::assert_snapshot!("question_multiline", rendered[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_other_renders_as_a_dim_placeholder() {
|
||||
let mut editor = editor();
|
||||
|
||||
Reference in New Issue
Block a user