Skip to content

On long spans, trim the middle of them to make them fit in the terminal width #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions src/renderer/margin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub(crate) struct Margin {
/// The end of the line to be displayed.
computed_right: usize,
/// The current width of the terminal. 140 by default and in tests.
term_width: usize,
pub(crate) term_width: usize,
/// The end column of a span label, including the span. Doesn't account for labels not in the
/// same line as the span.
label_right: usize,
Expand Down Expand Up @@ -58,18 +58,6 @@ impl Margin {
self.computed_left > 0
}

pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
let right =
if self.computed_right == self.span_right || self.computed_right == self.label_right {
// Account for the "..." padding given above. Otherwise we end up with code lines that
// do fit but end in "..." as if they were trimmed.
self.computed_right - ELLIPSIS_PASSING
} else {
self.computed_right
};
right < line_len && self.computed_left + self.term_width < line_len
}

fn compute(&mut self, max_line_len: usize) {
// When there's a lot of whitespace (>20), we want to trim it as it is useless.
self.computed_left = if self.whitespace_left > LONG_WHITESPACE {
Expand Down
69 changes: 50 additions & 19 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -967,21 +967,7 @@ impl Renderer {

let line_offset = buffer.num_lines();

// Left trim
let left = margin.left(str_width(&source_string));

// FIXME: This looks fishy. See #132860.
// Account for unicode characters of width !=0 that were removed.
let mut taken = 0;
source_string.chars().for_each(|ch| {
let next = char_width(ch);
if taken + next <= left {
taken += next;
}
});

let left = taken;
self.draw_line(
let left = self.draw_line(
buffer,
&source_string,
line_info.line_index,
Expand Down Expand Up @@ -1136,11 +1122,16 @@ impl Renderer {
// | x_span
// <EMPTY LINE>
//
let mut overlap = vec![false; annotations.len()];
let mut annotations_position = vec![];
let mut line_len: usize = 0;
let mut p = 0;
for (i, annotation) in annotations.iter().enumerate() {
for (j, next) in annotations.iter().enumerate() {
if overlaps(next, annotation, 0) && j > 1 {
overlap[i] = true;
overlap[j] = true;
}
if overlaps(next, annotation, 0) // This label overlaps with another one and both
&& annotation.has_label() // take space (they have text and are not
&& j > i // multiline lines).
Expand Down Expand Up @@ -1488,6 +1479,39 @@ impl Renderer {
);
}
}

// We look for individual *long* spans, and we trim the *middle*, so that we render
// LL | ...= [0, 0, 0, ..., 0, 0];
// | ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]`
for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
// Skip cases where multiple spans overlap eachother.
if overlap[i] {
continue;
};
let LineAnnotationType::Singleline = annotation.annotation_type else {
continue;
};
let width = annotation.end.display - annotation.start.display;
if width > margin.term_width * 2 && width > 10 {
// If the terminal is *too* small, we keep at least a tiny bit of the span for
// display.
let pad = max(margin.term_width / 3, 5);
// Code line
buffer.replace(
line_offset,
annotation.start.display + pad,
annotation.end.display - pad,
self.margin(),
);
// Underline line
buffer.replace(
line_offset + 1,
annotation.start.display + pad,
annotation.end.display - pad,
self.margin(),
);
}
}
annotations_position
.iter()
.filter_map(|&(_, annotation)| match annotation.annotation_type {
Expand Down Expand Up @@ -2036,12 +2060,12 @@ impl Renderer {
code_offset: usize,
max_line_num_len: usize,
margin: Margin,
) {
) -> usize {
// Tabs are assumed to have been replaced by spaces in calling code.
debug_assert!(!source_string.contains('\t'));
let line_len = str_width(source_string);
// Create the source line we will highlight.
let left = margin.left(line_len);
let mut left = margin.left(line_len);
let right = margin.right(line_len);
// FIXME: The following code looks fishy. See #132860.
// On long lines, we strip the source line, accounting for unicode.
Expand Down Expand Up @@ -2074,10 +2098,15 @@ impl Renderer {
break;
}
}

if width_taken > padding {
left -= width_taken - padding;
}

buffer.puts(
line_offset,
code_offset,
&format!("{placeholder:>width_taken$}"),
placeholder,
ElementStyle::LineNumber,
);
(width_taken, bytes_taken)
Expand All @@ -2092,7 +2121,7 @@ impl Renderer {
ElementStyle::Quotation,
);

if margin.was_cut_right(line_len) {
if line_len > right {
// We have stripped some code/whitespace from the beginning, make it clear.
let mut char_taken = 0;
let mut width_taken_inner = 0;
Expand Down Expand Up @@ -2121,6 +2150,8 @@ impl Renderer {
);

self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);

left
}

fn draw_range(
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/styled_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ impl StyledBuffer {
}
}

pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
if start == end {
return;
}
let _ = self.lines[line].drain(start..(end - string.chars().count()));
for (i, c) in string.chars().enumerate() {
self.lines[line][start + i] = StyledChar::new(c, ElementStyle::LineNumber);
}
}

/// For given `line` inserts `string` with `style` before old content of that line,
/// adding lines if needed
pub(crate) fn prepend(&mut self, line: usize, string: &str, style: ElementStyle) {
Expand Down
16 changes: 8 additions & 8 deletions tests/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2178,8 +2178,8 @@ fn unicode_cut_handling2() {
let expected_ascii = str![[r#"
error: expected item, found `?`
|
1 | ...的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
| ^ expected item
1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
| ^ expected item
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
"#]];

Expand All @@ -2189,8 +2189,8 @@ error: expected item, found `?`
let expected_unicode = str![[r#"
error: expected item, found `?`
1 │ 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
│ ━ expected item
1 │ 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?
━ expected item
╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
"#]];
let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode);
Expand All @@ -2215,8 +2215,8 @@ fn unicode_cut_handling3() {
let expected_ascii = str![[r#"
error: expected item, found `?`
|
1 | ...。这是宽的。这是宽的。这是宽的...
| ^^ expected item
1 | ... 。这是宽的。这是宽的。这是宽的...
| ^^ expected item
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
"#]];

Expand All @@ -2226,8 +2226,8 @@ error: expected item, found `?`
let expected_unicode = str![[r#"
error: expected item, found `?`
1 │ 的。这是宽的。这是宽的。这是宽的。…
│ ━━ expected item
1 │ 的。这是宽的。这是宽的。这是宽的。…
━━ expected item
╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
"#]];
let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode);
Expand Down
Loading