Skip to content

Commit ed5b380

Browse files
committed
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: rust-lang/annotate-snippets-rs#172
1 parent da2df3a commit ed5b380

File tree

5 files changed

+94
-17
lines changed

5 files changed

+94
-17
lines changed

crates/ruff_annotate_snippets/src/renderer/display_list.rs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ use std::fmt::Display;
3838
use std::ops::Range;
3939
use std::{cmp, fmt};
4040

41+
use unicode_width::UnicodeWidthStr;
42+
4143
use crate::renderer::styled_buffer::StyledBuffer;
4244
use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH};
4345

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

5861
impl PartialEq for DisplayList<'_> {
@@ -119,13 +122,21 @@ impl<'a> DisplayList<'a> {
119122
stylesheet: &'a Stylesheet,
120123
anonymized_line_numbers: bool,
121124
term_width: usize,
125+
cut_indicator: &'static str,
122126
) -> DisplayList<'a> {
123-
let body = format_message(message, term_width, anonymized_line_numbers, true);
127+
let body = format_message(
128+
message,
129+
term_width,
130+
anonymized_line_numbers,
131+
cut_indicator,
132+
true,
133+
);
124134

125135
Self {
126136
body,
127137
stylesheet,
128138
anonymized_line_numbers,
139+
cut_indicator,
129140
}
130141
}
131142

@@ -143,6 +154,7 @@ impl<'a> DisplayList<'a> {
143154
multiline_depth,
144155
self.stylesheet,
145156
self.anonymized_line_numbers,
157+
self.cut_indicator,
146158
buffer,
147159
)?;
148160
}
@@ -270,6 +282,7 @@ impl DisplaySet<'_> {
270282
}
271283

272284
// Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211
285+
#[allow(clippy::too_many_arguments)]
273286
#[inline]
274287
fn format_line(
275288
&self,
@@ -278,6 +291,7 @@ impl DisplaySet<'_> {
278291
multiline_depth: usize,
279292
stylesheet: &Stylesheet,
280293
anonymized_line_numbers: bool,
294+
cut_indicator: &'static str,
281295
buffer: &mut StyledBuffer,
282296
) -> fmt::Result {
283297
let line_offset = buffer.num_lines();
@@ -349,10 +363,15 @@ impl DisplaySet<'_> {
349363
buffer.puts(line_offset, code_offset, &code, Style::new());
350364
if self.margin.was_cut_left() {
351365
// We have stripped some code/whitespace from the beginning, make it clear.
352-
buffer.puts(line_offset, code_offset, "...", *lineno_color);
366+
buffer.puts(line_offset, code_offset, cut_indicator, *lineno_color);
353367
}
354368
if was_cut_right {
355-
buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color);
369+
buffer.puts(
370+
line_offset,
371+
code_offset + taken - cut_indicator.width(),
372+
cut_indicator,
373+
*lineno_color,
374+
);
356375
}
357376

358377
let left: usize = text
@@ -724,7 +743,7 @@ impl DisplaySet<'_> {
724743
Ok(())
725744
}
726745
DisplayLine::Fold { inline_marks } => {
727-
buffer.puts(line_offset, 0, "...", *stylesheet.line_no());
746+
buffer.puts(line_offset, 0, cut_indicator, *stylesheet.line_no());
728747
if !inline_marks.is_empty() || 0 < multiline_depth {
729748
format_inline_marks(
730749
line_offset,
@@ -987,12 +1006,13 @@ impl<'a> Iterator for CursorLines<'a> {
9871006
}
9881007
}
9891008

990-
fn format_message(
991-
message: snippet::Message<'_>,
1009+
fn format_message<'m>(
1010+
message: snippet::Message<'m>,
9921011
term_width: usize,
9931012
anonymized_line_numbers: bool,
1013+
cut_indicator: &'static str,
9941014
primary: bool,
995-
) -> Vec<DisplaySet<'_>> {
1015+
) -> Vec<DisplaySet<'m>> {
9961016
let snippet::Message {
9971017
level,
9981018
id,
@@ -1016,6 +1036,7 @@ fn format_message(
10161036
!footer.is_empty(),
10171037
term_width,
10181038
anonymized_line_numbers,
1039+
cut_indicator,
10191040
));
10201041
}
10211042

@@ -1035,6 +1056,7 @@ fn format_message(
10351056
annotation,
10361057
term_width,
10371058
anonymized_line_numbers,
1059+
cut_indicator,
10381060
false,
10391061
));
10401062
}
@@ -1089,13 +1111,14 @@ fn format_label(
10891111
result
10901112
}
10911113

1092-
fn format_snippet(
1093-
snippet: snippet::Snippet<'_>,
1114+
fn format_snippet<'m>(
1115+
snippet: snippet::Snippet<'m>,
10941116
is_first: bool,
10951117
has_footer: bool,
10961118
term_width: usize,
10971119
anonymized_line_numbers: bool,
1098-
) -> DisplaySet<'_> {
1120+
cut_indicator: &'static str,
1121+
) -> DisplaySet<'m> {
10991122
let main_range = snippet.annotations.first().map(|x| x.range.start);
11001123
let origin = snippet.origin;
11011124
let need_empty_header = origin.is_some() || is_first;
@@ -1105,6 +1128,7 @@ fn format_snippet(
11051128
has_footer,
11061129
term_width,
11071130
anonymized_line_numbers,
1131+
cut_indicator,
11081132
);
11091133
let header = format_header(origin, main_range, &body.display_lines, is_first);
11101134

@@ -1241,7 +1265,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
12411265
match unhighlighed_lines.len() {
12421266
0 => {}
12431267
n if n <= INNER_UNFOLD_SIZE => {
1244-
// Rather than render `...`, don't fold
1268+
// Rather than render our cut indicator, don't fold
12451269
lines.append(&mut unhighlighed_lines);
12461270
}
12471271
_ => {
@@ -1280,13 +1304,14 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
12801304
lines
12811305
}
12821306

1283-
fn format_body(
1284-
snippet: snippet::Snippet<'_>,
1307+
fn format_body<'m>(
1308+
snippet: snippet::Snippet<'m>,
12851309
need_empty_header: bool,
12861310
has_footer: bool,
12871311
term_width: usize,
12881312
anonymized_line_numbers: bool,
1289-
) -> DisplaySet<'_> {
1313+
cut_indicator: &'static str,
1314+
) -> DisplaySet<'m> {
12901315
let source_len = snippet.source.len();
12911316
if let Some(bigger) = snippet.annotations.iter().find_map(|x| {
12921317
// Allow highlighting one past the last character in the source.
@@ -1617,7 +1642,7 @@ fn format_body(
16171642
current_line.to_string().len()
16181643
};
16191644

1620-
let width_offset = 3 + max_line_num_len;
1645+
let width_offset = cut_indicator.len() + max_line_num_len;
16211646

16221647
if span_left_margin == usize::MAX {
16231648
span_left_margin = 0;

crates/ruff_annotate_snippets/src/renderer/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//!
1010
//! let renderer = Renderer::styled();
1111
//! println!("{}", renderer.render(snippet));
12+
//! ```
1213
1314
mod display_list;
1415
mod margin;
@@ -30,6 +31,7 @@ pub struct Renderer {
3031
anonymized_line_numbers: bool,
3132
term_width: usize,
3233
stylesheet: Stylesheet,
34+
cut_indicator: &'static str,
3335
}
3436

3537
impl Renderer {
@@ -39,6 +41,7 @@ impl Renderer {
3941
anonymized_line_numbers: false,
4042
term_width: DEFAULT_TERM_WIDTH,
4143
stylesheet: Stylesheet::plain(),
44+
cut_indicator: "...",
4245
}
4346
}
4447

@@ -151,13 +154,22 @@ impl Renderer {
151154
self
152155
}
153156

157+
/// Set the string used for when a long line is cut.
158+
///
159+
/// The default is `...` (three `U+002E` characters).
160+
pub const fn cut_indicator(mut self, string: &'static str) -> Self {
161+
self.cut_indicator = string;
162+
self
163+
}
164+
154165
/// Render a snippet into a `Display`able object
155166
pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
156167
DisplayList::new(
157168
msg,
158169
&self.stylesheet,
159170
self.anonymized_line_numbers,
160171
self.term_width,
172+
self.cut_indicator,
161173
)
162174
}
163175
}

crates/ruff_annotate_snippets/tests/formatter.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,3 +990,42 @@ error: title
990990
let renderer = Renderer::plain();
991991
assert_data_eq!(renderer.render(input).to_string(), expected);
992992
}
993+
994+
#[test]
995+
fn long_line_cut() {
996+
let source = "abcd abcd abcd abcd abcd abcd abcd";
997+
let input = Level::Error.title("").snippet(
998+
Snippet::source(source)
999+
.line_start(1)
1000+
.annotation(Level::Error.span(0..4)),
1001+
);
1002+
let expected = str![[r#"
1003+
error
1004+
|
1005+
1 | abcd abcd a...
1006+
| ^^^^
1007+
|
1008+
"#]];
1009+
let renderer = Renderer::plain().term_width(18);
1010+
assert_data_eq!(renderer.render(input).to_string(), expected);
1011+
}
1012+
1013+
#[test]
1014+
fn long_line_cut_custom() {
1015+
let source = "abcd abcd abcd abcd abcd abcd abcd";
1016+
let input = Level::Error.title("").snippet(
1017+
Snippet::source(source)
1018+
.line_start(1)
1019+
.annotation(Level::Error.span(0..4)),
1020+
);
1021+
// This trims a little less because `…` is visually smaller than `...`.
1022+
let expected = str![[r#"
1023+
error
1024+
|
1025+
1 | abcd abcd abc…
1026+
| ^^^^
1027+
|
1028+
"#]];
1029+
let renderer = Renderer::plain().term_width(18).cut_indicator("…");
1030+
assert_data_eq!(renderer.render(input).to_string(), expected);
1031+
}

crates/ruff_linter/src/message/text.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ impl Display for MessageCodeFrame<'_> {
277277
Renderer::styled()
278278
} else {
279279
Renderer::plain()
280-
};
280+
}
281+
.cut_indicator("…");
281282
let rendered = renderer.render(message);
282283
writeln!(f, "{rendered}")
283284
}

crates/ruff_python_parser/tests/fixtures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl std::fmt::Display for CodeFrame<'_> {
210210
.annotation(annotation)
211211
.fold(false);
212212
let message = Level::None.title("").snippet(snippet);
213-
let renderer = Renderer::plain();
213+
let renderer = Renderer::plain().cut_indicator("…");
214214
let rendered = renderer.render(message);
215215
writeln!(f, "{rendered}")
216216
}

0 commit comments

Comments
 (0)