Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e364000

Browse files
committed
Add strip code to the left and right for long lines
1 parent 26fb6e1 commit e364000

File tree

10 files changed

+299
-6
lines changed

10 files changed

+299
-6
lines changed

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master",
1616
maintenance = { status = "actively-developed" }
1717

1818
[dependencies]
19+
unicode-width = "0.1"
1920
yansi-term = { version = "0.1", optional = true }
2021

2122
[dev-dependencies]

‎src/display_list/from_snippet.rs‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,13 @@ fn format_slice(
107107
slice: snippet::Slice<'_>,
108108
is_first: bool,
109109
has_footer: bool,
110+
margin: Option<Margin>,
110111
) -> Vec<DisplayLine<'_>> {
111112
let main_range = slice.annotations.get(0).map(|x| x.range.0);
112113
let origin = slice.origin;
113114
let line_start = slice.line_start;
114115
let need_empty_header = origin.is_some() || is_first;
115-
let mut body = format_body(slice, need_empty_header, has_footer);
116+
let mut body = format_body(slice, need_empty_header, has_footer, margin);
116117
let header = format_header(origin, main_range, line_start, &body, is_first);
117118
let mut result = vec![];
118119

@@ -273,6 +274,7 @@ fn format_body(
273274
slice: snippet::Slice<'_>,
274275
need_empty_header: bool,
275276
has_footer: bool,
277+
margin: Option<Margin>,
276278
) -> Vec<DisplayLine<'_>> {
277279
let source_len = slice.source.chars().count();
278280
if let Some(bigger) = slice.annotations.iter().find_map(|x| {
@@ -312,6 +314,9 @@ fn format_body(
312314
let mut annotation_line_count = 0;
313315
let mut annotations = slice.annotations;
314316
for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() {
317+
let margin_left = margin
318+
.map(|m| m.left(line_end - line_start))
319+
.unwrap_or_default();
315320
// It would be nice to use filter_drain here once it's stable.
316321
annotations = annotations
317322
.into_iter()
@@ -328,7 +333,10 @@ fn format_body(
328333
if start >= line_start && end <= line_end
329334
|| start == line_end && end - start <= 1 =>
330335
{
331-
let range = (start - line_start, end - line_start);
336+
let range = (
337+
(start - line_start) - margin_left,
338+
(end - line_start) - margin_left,
339+
);
332340
body.insert(
333341
body_idx + 1,
334342
DisplayLine::Source {
@@ -419,7 +427,10 @@ fn format_body(
419427
});
420428
}
421429

422-
let range = (end - line_start, end - line_start + 1);
430+
let range = (
431+
(end - line_start) - margin_left,
432+
(end - line_start + 1) - margin_left,
433+
);
423434
body.insert(
424435
body_idx + 1,
425436
DisplayLine::Source {
@@ -499,7 +510,12 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
499510
}
500511

501512
for (idx, slice) in slices.into_iter().enumerate() {
502-
body.append(&mut format_slice(slice, idx == 0, !footer.is_empty()));
513+
body.append(&mut format_slice(
514+
slice,
515+
idx == 0,
516+
!footer.is_empty(),
517+
opt.margin,
518+
));
503519
}
504520

505521
for annotation in footer {
@@ -509,12 +525,14 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
509525
let FormatOptions {
510526
color,
511527
anonymized_line_numbers,
528+
margin,
512529
} = opt;
513530

514531
Self {
515532
body,
516533
stylesheet: get_term_style(color),
517534
anonymized_line_numbers,
535+
margin,
518536
}
519537
}
520538
}

‎src/display_list/structs.rs‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::cmp::{max, min};
12
use std::fmt;
23

34
use crate::formatter::{get_term_style, style::Stylesheet};
@@ -7,6 +8,7 @@ pub struct DisplayList<'a> {
78
pub body: Vec<DisplayLine<'a>>,
89
pub stylesheet: Box<dyn Stylesheet>,
910
pub anonymized_line_numbers: bool,
11+
pub margin: Option<Margin>,
1012
}
1113

1214
impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
@@ -15,6 +17,7 @@ impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
1517
body,
1618
anonymized_line_numbers: false,
1719
stylesheet: get_term_style(false),
20+
margin: None,
1821
}
1922
}
2023
}
@@ -38,6 +41,121 @@ impl<'a> fmt::Debug for DisplayList<'a> {
3841
pub struct FormatOptions {
3942
pub color: bool,
4043
pub anonymized_line_numbers: bool,
44+
pub margin: Option<Margin>,
45+
}
46+
47+
#[derive(Clone, Copy, Debug)]
48+
pub struct Margin {
49+
/// The available whitespace in the left that can be consumed when centering.
50+
pub whitespace_left: usize,
51+
/// The column of the beginning of left-most span.
52+
pub span_left: usize,
53+
/// The column of the end of right-most span.
54+
pub span_right: usize,
55+
/// The beginning of the line to be displayed.
56+
pub computed_left: usize,
57+
/// The end of the line to be displayed.
58+
pub computed_right: usize,
59+
/// The current width of the terminal. 140 by default and in tests.
60+
pub column_width: usize,
61+
/// The end column of a span label, including the span. Doesn't account for labels not in the
62+
/// same line as the span.
63+
pub label_right: usize,
64+
}
65+
66+
impl Margin {
67+
pub fn new(
68+
whitespace_left: usize,
69+
span_left: usize,
70+
span_right: usize,
71+
label_right: usize,
72+
column_width: usize,
73+
max_line_len: usize,
74+
) -> Self {
75+
// The 6 is padding to give a bit of room for `...` when displaying:
76+
// ```
77+
// error: message
78+
// --> file.rs:16:58
79+
// |
80+
// 16 | ... fn foo(self) -> Self::Bar {
81+
// | ^^^^^^^^^
82+
// ```
83+
84+
let mut m = Margin {
85+
whitespace_left: whitespace_left.saturating_sub(6),
86+
span_left: span_left.saturating_sub(6),
87+
span_right: span_right + 6,
88+
computed_left: 0,
89+
computed_right: 0,
90+
column_width,
91+
label_right: label_right + 6,
92+
};
93+
m.compute(max_line_len);
94+
m
95+
}
96+
97+
pub(crate) fn was_cut_left(&self) -> bool {
98+
self.computed_left > 0
99+
}
100+
101+
pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
102+
let right =
103+
if self.computed_right == self.span_right || self.computed_right == self.label_right {
104+
// Account for the "..." padding given above. Otherwise we end up with code lines that
105+
// do fit but end in "..." as if they were trimmed.
106+
self.computed_right - 6
107+
} else {
108+
self.computed_right
109+
};
110+
right < line_len && self.computed_left + self.column_width < line_len
111+
}
112+
113+
fn compute(&mut self, max_line_len: usize) {
114+
// When there's a lot of whitespace (>20), we want to trim it as it is useless.
115+
self.computed_left = if self.whitespace_left > 20 {
116+
self.whitespace_left - 16 // We want some padding.
117+
} else {
118+
0
119+
};
120+
// We want to show as much as possible, max_line_len is the right-most boundary for the
121+
// relevant code.
122+
self.computed_right = max(max_line_len, self.computed_left);
123+
124+
if self.computed_right - self.computed_left > self.column_width {
125+
// Trimming only whitespace isn't enough, let's get craftier.
126+
if self.label_right - self.whitespace_left <= self.column_width {
127+
// Attempt to fit the code window only trimming whitespace.
128+
self.computed_left = self.whitespace_left;
129+
self.computed_right = self.computed_left + self.column_width;
130+
} else if self.label_right - self.span_left <= self.column_width {
131+
// Attempt to fit the code window considering only the spans and labels.
132+
let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
133+
self.computed_left = self.span_left.saturating_sub(padding_left);
134+
self.computed_right = self.computed_left + self.column_width;
135+
} else if self.span_right - self.span_left <= self.column_width {
136+
// Attempt to fit the code window considering the spans and labels plus padding.
137+
let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
138+
self.computed_left = self.span_left.saturating_sub(padding_left);
139+
self.computed_right = self.computed_left + self.column_width;
140+
} else {
141+
// Mostly give up but still don't show the full line.
142+
self.computed_left = self.span_left;
143+
self.computed_right = self.span_right;
144+
}
145+
}
146+
}
147+
148+
pub(crate) fn left(&self, line_len: usize) -> usize {
149+
min(self.computed_left, line_len)
150+
}
151+
152+
pub(crate) fn right(&self, line_len: usize) -> usize {
153+
if line_len.saturating_sub(self.computed_left) <= self.column_width {
154+
line_len
155+
} else {
156+
min(line_len, self.computed_right)
157+
}
158+
}
41159
}
42160

43161
/// Inline annotation which can be used in either Raw or Source line.

‎src/formatter/mod.rs‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,56 @@ impl<'a> DisplayList<'a> {
198198
DisplaySourceLine::Empty => Ok(()),
199199
DisplaySourceLine::Content { text, .. } => {
200200
f.write_char(' ')?;
201-
text.fmt(f)
201+
if let Some(margin) = self.margin {
202+
let line_len = text.chars().count();
203+
let mut left = margin.left(line_len);
204+
let right = margin.right(line_len);
205+
206+
if margin.was_cut_left() {
207+
// We have stripped some code/whitespace from the beginning, make it clear.
208+
"...".fmt(f)?;
209+
left += 3;
210+
}
211+
212+
// On long lines, we strip the source line, accounting for unicode.
213+
let mut taken = 0;
214+
let cut_right = if margin.was_cut_right(line_len) {
215+
taken += 3;
216+
true
217+
} else {
218+
false
219+
};
220+
let range = text
221+
.char_indices()
222+
.skip(left)
223+
.take_while(|(_, ch)| {
224+
// Make sure that the trimming on the right will fall within the terminal width.
225+
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
226+
// For now, just accept that sometimes the code line will be longer than desired.
227+
taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
228+
if taken > right - left {
229+
return false;
230+
}
231+
true
232+
})
233+
.fold((None, 0), |acc, (i, _)| {
234+
if acc.0.is_some() {
235+
(acc.0, i)
236+
} else {
237+
(Some(i), i)
238+
}
239+
});
240+
241+
text[range.0.expect("One character at line")..=range.1].fmt(f)?;
242+
243+
if cut_right {
244+
// We have stripped some code after the right-most span end, make it clear we did so.
245+
"...".fmt(f)?;
246+
}
247+
Ok(())
248+
} else {
249+
text.fmt(f)
250+
}
202251
}
203252
DisplaySourceLine::Annotation {
204253
range,

‎tests/dl_from_snippet.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn test_format_title() {
2828
})],
2929
stylesheet: get_term_style(input.opt.color),
3030
anonymized_line_numbers: input.opt.anonymized_line_numbers,
31+
margin: None,
3132
};
3233
assert_eq!(dl::DisplayList::from(input), output);
3334
}
@@ -80,6 +81,7 @@ fn test_format_slice() {
8081
],
8182
stylesheet: get_term_style(input.opt.color),
8283
anonymized_line_numbers: input.opt.anonymized_line_numbers,
84+
margin: None,
8385
};
8486
assert_eq!(dl::DisplayList::from(input), output);
8587
}
@@ -162,6 +164,7 @@ fn test_format_slices_continuation() {
162164
],
163165
stylesheet: get_term_style(input.opt.color),
164166
anonymized_line_numbers: input.opt.anonymized_line_numbers,
167+
margin: None,
165168
};
166169
assert_eq!(dl::DisplayList::from(input), output);
167170
}
@@ -237,6 +240,7 @@ fn test_format_slice_annotation_standalone() {
237240
],
238241
stylesheet: get_term_style(input.opt.color),
239242
anonymized_line_numbers: input.opt.anonymized_line_numbers,
243+
margin: None,
240244
};
241245
assert_eq!(dl::DisplayList::from(input), output);
242246
}
@@ -278,6 +282,7 @@ fn test_format_label() {
278282
})],
279283
stylesheet: get_term_style(input.opt.color),
280284
anonymized_line_numbers: input.opt.anonymized_line_numbers,
285+
margin: None,
281286
};
282287
assert_eq!(dl::DisplayList::from(input), output);
283288
}
@@ -395,6 +400,7 @@ fn test_i_29() {
395400
],
396401
stylesheet: get_term_style(false),
397402
anonymized_line_numbers: false,
403+
margin: None,
398404
};
399405

400406
assert_eq!(DisplayList::from(snippets), expected);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[title]
2+
id = "E0308"
3+
label = "mismatched types"
4+
annotation_type = "Error"
5+
6+
[[slices]]
7+
source = " let _: () = 42;"
8+
line_start = 4
9+
origin = "$DIR/whitespace-trimming.rs"
10+
11+
[[slices.annotations]]
12+
label = "expected (), found integer"
13+
annotation_type = "Error"
14+
range = [192, 194]
15+
16+
[opt]
17+
color = false
18+
anonymized_line_numbers = true
19+
[opt.margin]
20+
whitespace_left = 180
21+
span_left = 192
22+
span_right = 194
23+
label_right = 221
24+
column_width = 140
25+
max_line_len = 195
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/whitespace-trimming.rs:4:193
3+
|
4+
LL | ... let _: () = 42;
5+
| ^^ expected (), found integer
6+
|

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /