Skip to content

Commit

Permalink
ruff_annotate_snippets: support overriding the "cut indicator"
Browse files Browse the repository at this point in the history
We do this because `...` is valid Python, which makes it pretty likely
that some line trimming will lead to ambiguous output. So we add support
for overriding the cut indicator. This also requires changing some of
the alignment math, which was previously tightly coupled to `...`.

For Ruff, we go with `…` (`U+2026 HORIZONTAL ELLIPSIS`) for our cut
indicator.

For more details, see the patch sent to upstream:
rust-lang/annotate-snippets-rs#172
  • Loading branch information
BurntSushi committed Jan 14, 2025
1 parent 1186a3c commit 84ac650
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 17 deletions.
55 changes: 40 additions & 15 deletions crates/ruff_annotate_snippets/src/renderer/display_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ use std::fmt::Display;
use std::ops::Range;
use std::{cmp, fmt};

use unicode_width::UnicodeWidthStr;

use crate::renderer::styled_buffer::StyledBuffer;
use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};

Expand All @@ -53,6 +55,7 @@ pub(crate) struct DisplayList<'a> {
pub(crate) body: Vec<DisplaySet<'a>>,
pub(crate) stylesheet: &'a Stylesheet,
pub(crate) anonymized_line_numbers: bool,
pub(crate) cut_indicator: &'static str,
}

impl PartialEq for DisplayList<'_> {
Expand Down Expand Up @@ -119,13 +122,21 @@ impl<'a> DisplayList<'a> {
stylesheet: &'a Stylesheet,
anonymized_line_numbers: bool,
term_width: usize,
cut_indicator: &'static str,
) -> DisplayList<'a> {
let body = format_message(message, term_width, anonymized_line_numbers, true);
let body = format_message(
message,
term_width,
anonymized_line_numbers,
cut_indicator,
true,
);

Self {
body,
stylesheet,
anonymized_line_numbers,
cut_indicator,
}
}

Expand All @@ -143,6 +154,7 @@ impl<'a> DisplayList<'a> {
multiline_depth,
self.stylesheet,
self.anonymized_line_numbers,
self.cut_indicator,
buffer,
)?;
}
Expand Down Expand Up @@ -270,6 +282,7 @@ impl DisplaySet<'_> {
}

// Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211
#[allow(clippy::too_many_arguments)]
#[inline]
fn format_line(
&self,
Expand All @@ -278,6 +291,7 @@ impl DisplaySet<'_> {
multiline_depth: usize,
stylesheet: &Stylesheet,
anonymized_line_numbers: bool,
cut_indicator: &'static str,
buffer: &mut StyledBuffer,
) -> fmt::Result {
let line_offset = buffer.num_lines();
Expand Down Expand Up @@ -349,10 +363,15 @@ impl DisplaySet<'_> {
buffer.puts(line_offset, code_offset, &code, Style::new());
if self.margin.was_cut_left() {
// We have stripped some code/whitespace from the beginning, make it clear.
buffer.puts(line_offset, code_offset, "...", *lineno_color);
buffer.puts(line_offset, code_offset, cut_indicator, *lineno_color);
}
if was_cut_right {
buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
buffer.puts(
line_offset,
code_offset + taken - cut_indicator.width(),
cut_indicator,
*lineno_color,
);
}

let left: usize = text
Expand Down Expand Up @@ -724,7 +743,7 @@ impl DisplaySet<'_> {
Ok(())
}
DisplayLine::Fold { inline_marks } => {
buffer.puts(line_offset, 0, "...", *stylesheet.line_no());
buffer.puts(line_offset, 0, cut_indicator, *stylesheet.line_no());
if !inline_marks.is_empty() || 0 < multiline_depth {
format_inline_marks(
line_offset,
Expand Down Expand Up @@ -987,12 +1006,13 @@ impl<'a> Iterator for CursorLines<'a> {
}
}

fn format_message(
message: snippet::Message<'_>,
fn format_message<'m>(
message: snippet::Message<'m>,
term_width: usize,
anonymized_line_numbers: bool,
cut_indicator: &'static str,
primary: bool,
) -> Vec<DisplaySet<'_>> {
) -> Vec<DisplaySet<'m>> {
let snippet::Message {
level,
id,
Expand All @@ -1016,6 +1036,7 @@ fn format_message(
!footer.is_empty(),
term_width,
anonymized_line_numbers,
cut_indicator,
));
}

Expand All @@ -1035,6 +1056,7 @@ fn format_message(
annotation,
term_width,
anonymized_line_numbers,
cut_indicator,
false,
));
}
Expand Down Expand Up @@ -1089,13 +1111,14 @@ fn format_label(
result
}

fn format_snippet(
snippet: snippet::Snippet<'_>,
fn format_snippet<'m>(
snippet: snippet::Snippet<'m>,
is_first: bool,
has_footer: bool,
term_width: usize,
anonymized_line_numbers: bool,
) -> DisplaySet<'_> {
cut_indicator: &'static str,
) -> DisplaySet<'m> {
let main_range = snippet.annotations.first().map(|x| x.range.start);
let origin = snippet.origin;
let need_empty_header = origin.is_some() || is_first;
Expand All @@ -1105,6 +1128,7 @@ fn format_snippet(
has_footer,
term_width,
anonymized_line_numbers,
cut_indicator,
);
let header = format_header(origin, main_range, &body.display_lines, is_first);

Expand Down Expand Up @@ -1241,7 +1265,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
match unhighlighed_lines.len() {
0 => {}
n if n <= INNER_UNFOLD_SIZE => {
// Rather than render `...`, don't fold
// Rather than render our cut indicator, don't fold
lines.append(&mut unhighlighed_lines);
}
_ => {
Expand Down Expand Up @@ -1280,13 +1304,14 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
lines
}

fn format_body(
snippet: snippet::Snippet<'_>,
fn format_body<'m>(
snippet: snippet::Snippet<'m>,
need_empty_header: bool,
has_footer: bool,
term_width: usize,
anonymized_line_numbers: bool,
) -> DisplaySet<'_> {
cut_indicator: &'static str,
) -> DisplaySet<'m> {
let source_len = snippet.source.len();
if let Some(bigger) = snippet.annotations.iter().find_map(|x| {
// Allow highlighting one past the last character in the source.
Expand Down Expand Up @@ -1617,7 +1642,7 @@ fn format_body(
current_line.to_string().len()
};

let width_offset = 3 + max_line_num_len;
let width_offset = cut_indicator.len() + max_line_num_len;

if span_left_margin == usize::MAX {
span_left_margin = 0;
Expand Down
12 changes: 12 additions & 0 deletions crates/ruff_annotate_snippets/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//!
//! let renderer = Renderer::styled();
//! println!("{}", renderer.render(snippet));
//! ```
mod display_list;
mod margin;
Expand All @@ -30,6 +31,7 @@ pub struct Renderer {
anonymized_line_numbers: bool,
term_width: usize,
stylesheet: Stylesheet,
cut_indicator: &'static str,
}

impl Renderer {
Expand All @@ -39,6 +41,7 @@ impl Renderer {
anonymized_line_numbers: false,
term_width: DEFAULT_TERM_WIDTH,
stylesheet: Stylesheet::plain(),
cut_indicator: "...",
}
}

Expand Down Expand Up @@ -151,13 +154,22 @@ impl Renderer {
self
}

/// Set the string used for when a long line is cut.
///
/// The default is `...` (three `U+002E` characters).
pub const fn cut_indicator(mut self, string: &'static str) -> Self {
self.cut_indicator = string;
self
}

/// Render a snippet into a `Display`able object
pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
DisplayList::new(
msg,
&self.stylesheet,
self.anonymized_line_numbers,
self.term_width,
self.cut_indicator,
)
}
}
39 changes: 39 additions & 0 deletions crates/ruff_annotate_snippets/tests/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,3 +990,42 @@ error: title
let renderer = Renderer::plain();
assert_data_eq!(renderer.render(input).to_string(), expected);
}

#[test]
fn long_line_cut() {
let source = "abcd abcd abcd abcd abcd abcd abcd";
let input = Level::Error.title("").snippet(
Snippet::source(source)
.line_start(1)
.annotation(Level::Error.span(0..4)),
);
let expected = str![[r#"
error
|
1 | abcd abcd a...
| ^^^^
|
"#]];
let renderer = Renderer::plain().term_width(18);
assert_data_eq!(renderer.render(input).to_string(), expected);
}

#[test]
fn long_line_cut_custom() {
let source = "abcd abcd abcd abcd abcd abcd abcd";
let input = Level::Error.title("").snippet(
Snippet::source(source)
.line_start(1)
.annotation(Level::Error.span(0..4)),
);
// This trims a little less because `…` is visually smaller than `...`.
let expected = str![[r#"
error
|
1 | abcd abcd abc…
| ^^^^
|
"#]];
let renderer = Renderer::plain().term_width(18).cut_indicator("…");
assert_data_eq!(renderer.render(input).to_string(), expected);
}
3 changes: 2 additions & 1 deletion crates/ruff_linter/src/message/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ impl Display for MessageCodeFrame<'_> {
Renderer::styled()
} else {
Renderer::plain()
};
}
.cut_indicator("…");
let rendered = renderer.render(message);
writeln!(f, "{rendered}")
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_parser/tests/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl std::fmt::Display for CodeFrame<'_> {
.annotation(annotation)
.fold(false);
let message = Level::None.title("").snippet(snippet);
let renderer = Renderer::plain();
let renderer = Renderer::plain().cut_indicator("…");
let rendered = renderer.render(message);
writeln!(f, "{rendered}")
}
Expand Down

0 comments on commit 84ac650

Please sign in to comment.