mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:49:50 +00:00
ruff_annotate_snippets: support overriding the "cut indicator"
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: https://github.com/rust-lang/annotate-snippets-rs/pull/172
This commit is contained in:
parent
a45f4de683
commit
84ba4ecaf5
5 changed files with 94 additions and 17 deletions
|
@ -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};
|
||||
|
||||
|
@ -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<'_> {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +154,7 @@ impl<'a> DisplayList<'a> {
|
|||
multiline_depth,
|
||||
self.stylesheet,
|
||||
self.anonymized_line_numbers,
|
||||
self.cut_indicator,
|
||||
buffer,
|
||||
)?;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -1016,6 +1036,7 @@ fn format_message(
|
|||
!footer.is_empty(),
|
||||
term_width,
|
||||
anonymized_line_numbers,
|
||||
cut_indicator,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1035,6 +1056,7 @@ fn format_message(
|
|||
annotation,
|
||||
term_width,
|
||||
anonymized_line_numbers,
|
||||
cut_indicator,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
_ => {
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//!
|
||||
//! let renderer = Renderer::styled();
|
||||
//! println!("{}", renderer.render(snippet));
|
||||
//! ```
|
||||
|
||||
mod display_list;
|
||||
mod margin;
|
||||
|
@ -30,6 +31,7 @@ pub struct Renderer {
|
|||
anonymized_line_numbers: bool,
|
||||
term_width: usize,
|
||||
stylesheet: Stylesheet,
|
||||
cut_indicator: &'static str,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
|
@ -39,6 +41,7 @@ impl Renderer {
|
|||
anonymized_line_numbers: false,
|
||||
term_width: DEFAULT_TERM_WIDTH,
|
||||
stylesheet: Stylesheet::plain(),
|
||||
cut_indicator: "...",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +154,14 @@ 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(
|
||||
|
@ -158,6 +169,7 @@ impl Renderer {
|
|||
&self.stylesheet,
|
||||
self.anonymized_line_numbers,
|
||||
self.term_width,
|
||||
self.cut_indicator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -277,7 +277,8 @@ impl Display for MessageCodeFrame<'_> {
|
|||
Renderer::styled()
|
||||
} else {
|
||||
Renderer::plain()
|
||||
};
|
||||
}
|
||||
.cut_indicator("…");
|
||||
let rendered = renderer.render(message);
|
||||
writeln!(f, "{rendered}")
|
||||
}
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue