Skip to content

Commit

Permalink
feat: Add Renderer::cut_indicator
Browse files Browse the repository at this point in the history
This adds a new API for overriding the use of `...` to indicate a cut or
trimmed line. In the case of Ruff, we didn't want to use `...` since
`...` is valid Python code. It could be rather confusing in some cases
where `...` would be ambiguous between "line was cut here" and "this is
what the actual line read as." I think this can happen with _any_
indicator of course, but for Python specifically, it's pretty likely to
happen with `...`.

The new API here is somewhat sub-optimal in that it requires a
`&'static str`. I did this because of the constraints imposed by
a `Renderer`'s `const` constructor.
  • Loading branch information
BurntSushi committed Jan 9, 2025
1 parent 7132bf3 commit 179098f
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 15 deletions.
55 changes: 40 additions & 15 deletions 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 @@ -350,10 +364,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 self.margin.was_cut_right(line_len) {
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 @@ -725,7 +744,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 @@ -1248,7 +1272,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 @@ -1287,13 +1311,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 @@ -1626,7 +1651,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 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 tests/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,3 +955,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);
}

0 comments on commit 179098f

Please sign in to comment.