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 ff009fb

Browse files
Merge pull request #36 from botika/master
Add strip code to the left and right for long lines
2 parents 26fb6e1 + dae1a97 commit ff009fb

File tree

14 files changed

+332
-43
lines changed

14 files changed

+332
-43
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]

‎README.md

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,49 +35,47 @@ Usage
3535

3636
```rust
3737
use annotate_snippets::{
38-
display_list::DisplayList,
39-
formatter::DisplayListFormatter,
38+
display_list::{DisplayList, FormatOptions},
4039
snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
4140
};
4241

4342
fn main() {
4443
let snippet = Snippet {
4544
title: Some(Annotation {
46-
label: Some("expected type, found `22`".to_string()),
45+
label: Some("expected type, found `22`"),
4746
id: None,
4847
annotation_type: AnnotationType::Error,
4948
}),
5049
footer: vec![],
51-
slices: vec![
52-
Slice {
53-
source:r#"
54-
This is an example
55-
content of the slice
56-
which will be annotated
57-
with the list of annotations below.
58-
"#.to_string(),
59-
line_start:26,
60-
origin:Some("examples/example.txt".to_string()),
61-
fold:false,
62-
annotations:vec![
63-
SourceAnnotation {
64-
label:"Example error annotation".to_string(),
65-
annotation_type:AnnotationType::Error,
66-
range: (13, 18),
67-
},
68-
SourceAnnotation {
69-
label:"and here's a warning".to_string(),
70-
annotation_type:AnnotationType::Warning,
71-
range: (34, 50),
72-
},
73-
],
74-
},
75-
],
50+
slices: vec![Slice {
51+
source:r#" annotations: vec![SourceAnnotation {
52+
label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
53+
,
54+
range: <22, 25>,"#,
55+
line_start:26,
56+
origin:Some("examples/footer.rs"),
57+
fold:true,
58+
annotations:vec![
59+
SourceAnnotation {
60+
label:"",
61+
annotation_type:AnnotationType::Error,
62+
range: (205, 207),
63+
},
64+
SourceAnnotation {
65+
label:"while parsing this struct",
66+
annotation_type:AnnotationType::Info,
67+
range: (34, 50),
68+
},
69+
],
70+
}],
71+
opt:FormatOptions {
72+
color:true,
73+
..Default::default()
74+
},
7675
};
7776

7877
let dl = DisplayList::from(snippet);
79-
let dlf = DisplayListFormatter::new(true, false);
80-
println!("{}", dlf.format(&dl));
78+
println!("{}", dl);
8179
}
8280
```
8381

‎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: 119 additions & 2 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+
whitespace_left: usize,
51+
/// The column of the beginning of left-most span.
52+
span_left: usize,
53+
/// The column of the end of right-most span.
54+
span_right: usize,
55+
/// The beginning of the line to be displayed.
56+
computed_left: usize,
57+
/// The end of the line to be displayed.
58+
computed_right: usize,
59+
/// The current width of the terminal. 140 by default and in tests.
60+
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+
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.
@@ -162,8 +280,7 @@ pub enum DisplayMarkType {
162280

163281
/// A type of the `Annotation` which may impact the sigils, style or text displayed.
164282
///
165-
/// There are several ways in which the `DisplayListFormatter` uses this information
166-
/// when formatting the `DisplayList`:
283+
/// There are several ways to uses this information when formatting the `DisplayList`:
167284
///
168285
/// * An annotation may display the name of the type like `error` or `info`.
169286
/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`.

‎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,

‎src/formatter/style.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ pub trait Style {
3939
c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>,
4040
f: &mut fmt::Formatter<'_>,
4141
) -> fmt::Result;
42-
/// The method used by the DisplayListFormatter to display the message
43-
/// in bold font.
42+
/// The method used by the `Formatter` to display the message in bold font.
4443
fn bold(&self) -> Box<dyn Style>;
4544
}
4645

0 commit comments

Comments
(0)

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