mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Clamp wrapped source ranges before remapping terminal hyperlinks (#52330)
## Why Wrapping can retain a cursor sentinel one byte past the end of the input. Using that range to slice source text during hyperlink remapping can panic. ## What changed Clamp wrapped range boundaries to the input length before calculating hyperlink columns and recording logical source ranges, keeping both aligned with the visible text. ## Testing Add a regression test that extends a wrapped range past the input boundary and verifies that the line, hyperlinks, and logical source are preserved. GitOrigin-RevId: c83c817df8d3ea790f7d126768092ae317ff9217
This commit is contained in:
committed by
copyberry
parent
806d2d1cc1
commit
22ebb0fb17
@@ -389,10 +389,14 @@ pub(crate) fn remap_source_wrapped_line(
|
||||
let line = line_to_static(&wrapped.line);
|
||||
let displayed = line_text(&line);
|
||||
let prefix_columns = display_width(&displayed[..wrapped.prefix_bytes]);
|
||||
source_column += display_width(&text[source_byte..wrapped.range.start]);
|
||||
// Some wrapping paths retain a one-past-the-end cursor sentinel. Span slicing already
|
||||
// clips that sentinel to the visible line; keep hyperlink and source projection on the
|
||||
// same bounded range instead of indexing past the flattened text.
|
||||
let range = wrapped.range.start.min(text.len())..wrapped.range.end.min(text.len());
|
||||
source_column += display_width(&text[source_byte..range.start]);
|
||||
let start = source_column;
|
||||
let end = start + display_width(&text[wrapped.range.clone()]);
|
||||
source_byte = wrapped.range.end;
|
||||
let end = start + display_width(&text[range.clone()]);
|
||||
source_byte = range.end;
|
||||
source_column = end;
|
||||
let hyperlinks = source
|
||||
.hyperlinks
|
||||
@@ -410,7 +414,7 @@ pub(crate) fn remap_source_wrapped_line(
|
||||
HyperlinkLine {
|
||||
line,
|
||||
hyperlinks,
|
||||
source: Some(logical.wrapped(wrapped.range, wrapped.prefix_bytes)),
|
||||
source: Some(logical.wrapped(range, wrapped.prefix_bytes)),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -65,3 +65,17 @@ fn decorated_hyperlinks_preserve_joined_emoji_columns() {
|
||||
))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_wrapping_clips_a_cursor_sentinel_at_the_input_boundary() {
|
||||
let destination = "https://example.test/pull/9410";
|
||||
let text = format!("› [configuration editor]({destination})");
|
||||
let source = annotate_web_urls_in_line(Line::from(text.clone()));
|
||||
let mut wrapped = crate::wrapping::word_wrap_line_with_source(&source.line, text.len());
|
||||
wrapped.last_mut().expect("wrapped line").range.end += 1;
|
||||
let actual = remap_source_wrapped_line(&source, wrapped);
|
||||
let mut expected = source;
|
||||
expected.source = Some(LogicalLineSource::from_line(&expected.line));
|
||||
|
||||
assert!(lines_with_sources_eq(&actual, &[expected]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user