Skip to content

Commit c68600d

Browse files
committed
fix: Improve annotating line endings
1 parent 004e6f9 commit c68600d

File tree

4 files changed

+511
-33
lines changed

4 files changed

+511
-33
lines changed

src/renderer/display_list.rs

+68-19
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ pub(crate) enum DisplaySourceLine<'a> {
524524
Content {
525525
text: &'a str,
526526
range: (usize, usize), // meta information for annotation placement.
527+
end_line: EndLine,
527528
},
528529
/// An empty source line.
529530
Empty,
@@ -658,7 +659,8 @@ impl<'a> CursorLines<'a> {
658659
}
659660
}
660661

661-
enum EndLine {
662+
#[derive(Copy, Clone, Debug, PartialEq)]
663+
pub(crate) enum EndLine {
662664
Eof = 0,
663665
Crlf = 1,
664666
Lf = 2,
@@ -847,13 +849,20 @@ fn format_header<'a>(
847849

848850
for item in body {
849851
if let DisplayLine::Source {
850-
line: DisplaySourceLine::Content { text, range },
852+
line:
853+
DisplaySourceLine::Content {
854+
text,
855+
range,
856+
end_line,
857+
},
851858
lineno,
852859
..
853860
} = item
854861
{
855-
if main_range >= range.0 && main_range <= range.1 {
856-
let char_column = text[0..(main_range - range.0)].chars().count();
862+
if main_range >= range.0 && main_range <= range.1 + *end_line as usize {
863+
let char_column = text[0..(main_range - range.0).min(text.len())]
864+
.chars()
865+
.count();
857866
col = char_column + 1;
858867
line_offset = lineno.unwrap_or(1);
859868
break;
@@ -927,8 +936,18 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
927936
let mut unhighlighed_lines = vec![];
928937
for line in body {
929938
match &line {
930-
DisplayLine::Source { annotations, .. } => {
931-
if annotations.is_empty() {
939+
DisplayLine::Source {
940+
annotations,
941+
inline_marks,
942+
..
943+
} => {
944+
if annotations.is_empty()
945+
// A multiline start mark (`/`) needs be treated as an
946+
// annotation or the line could get folded.
947+
&& inline_marks
948+
.iter()
949+
.all(|m| m.mark_type != DisplayMarkType::AnnotationStart)
950+
{
932951
unhighlighed_lines.push(line);
933952
} else {
934953
if lines.is_empty() {
@@ -1016,12 +1035,14 @@ fn format_body(
10161035
for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() {
10171036
let line_length: usize = line.len();
10181037
let line_range = (current_index, current_index + line_length);
1038+
let end_line_size = end_line as usize;
10191039
body.push(DisplayLine::Source {
10201040
lineno: Some(current_line),
10211041
inline_marks: vec![],
10221042
line: DisplaySourceLine::Content {
10231043
text: line,
10241044
range: line_range,
1045+
end_line,
10251046
},
10261047
annotations: vec![],
10271048
});
@@ -1045,7 +1066,7 @@ fn format_body(
10451066
let line_start_index = line_range.0;
10461067
let line_end_index = line_range.1;
10471068
current_line += 1;
1048-
current_index += line_length + end_line as usize;
1069+
current_index += line_length + end_line_size;
10491070

10501071
// It would be nice to use filter_drain here once it's stable.
10511072
annotations.retain(|annotation| {
@@ -1057,18 +1078,24 @@ fn format_body(
10571078
};
10581079
let label_right = annotation.label.map_or(0, |label| label.len() + 1);
10591080
match annotation.range {
1060-
Range { start, .. } if start > line_end_index => true,
1081+
// This handles if the annotation is on the next line. We add
1082+
// the `end_line_size` to account for annotating the line end.
1083+
Range { start, .. } if start > line_end_index + end_line_size => true,
1084+
// This handles the case where an annotation is contained
1085+
// within the current line including any line-end characters.
10611086
Range { start, end }
1062-
if start >= line_start_index && end <= line_end_index
1063-
// Allow annotating eof or stripped eol
1064-
|| start == line_end_index && end - start <= 1 =>
1087+
if start >= line_start_index
1088+
// We add at least one to `line_end_index` to allow
1089+
// highlighting the end of a file
1090+
&& end <= line_end_index + max(end_line_size, 1) =>
10651091
{
10661092
if let DisplayLine::Source {
10671093
ref mut annotations,
10681094
..
10691095
} = body[body_idx]
10701096
{
1071-
let annotation_start_col = line[0..(start - line_start_index)]
1097+
let annotation_start_col = line
1098+
[0..(start - line_start_index).min(line_length)]
10721099
.chars()
10731100
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
10741101
.sum::<usize>();
@@ -1101,11 +1128,16 @@ fn format_body(
11011128
}
11021129
false
11031130
}
1131+
// This handles the case where a multiline annotation starts
1132+
// somewhere on the current line, including any line-end chars
11041133
Range { start, end }
11051134
if start >= line_start_index
1106-
&& start <= line_end_index
1135+
// The annotation can start on a line ending
1136+
&& start <= line_end_index + end_line_size.saturating_sub(1)
11071137
&& end > line_end_index =>
11081138
{
1139+
// Special case for multiline annotations that start at the
1140+
// beginning of a line, which requires a special mark (`/`)
11091141
if start - line_start_index == 0 {
11101142
if let DisplayLine::Source {
11111143
ref mut inline_marks,
@@ -1122,7 +1154,8 @@ fn format_body(
11221154
..
11231155
} = body[body_idx]
11241156
{
1125-
let annotation_start_col = line[0..(start - line_start_index)]
1157+
let annotation_start_col = line
1158+
[0..(start - line_start_index).min(line_length)]
11261159
.chars()
11271160
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
11281161
.sum::<usize>();
@@ -1147,7 +1180,11 @@ fn format_body(
11471180
}
11481181
true
11491182
}
1150-
Range { start, end } if start < line_start_index && end > line_end_index => {
1183+
// This handles the case where a multiline annotation starts
1184+
// somewhere before this line and ends after it as well
1185+
Range { start, end }
1186+
if start < line_start_index && end > line_end_index + max(end_line_size, 1) =>
1187+
{
11511188
if let DisplayLine::Source {
11521189
ref mut inline_marks,
11531190
..
@@ -1160,10 +1197,14 @@ fn format_body(
11601197
}
11611198
true
11621199
}
1200+
// This handles the case where a multiline annotation ends
1201+
// somewhere on the current line, including any line-end chars
11631202
Range { start, end }
11641203
if start < line_start_index
11651204
&& end >= line_start_index
1166-
&& end <= line_end_index =>
1205+
// We add at least one to `line_end_index` to allow
1206+
// highlighting the end of a file
1207+
&& end <= line_end_index + max(end_line_size, 1) =>
11671208
{
11681209
if let DisplayLine::Source {
11691210
ref mut inline_marks,
@@ -1175,13 +1216,21 @@ fn format_body(
11751216
mark_type: DisplayMarkType::AnnotationThrough,
11761217
annotation_type: DisplayAnnotationType::from(annotation.level),
11771218
});
1178-
let end_mark = line[0..(end - line_start_index)]
1219+
let end_mark = line[0..(end - line_start_index).min(line_length)]
11791220
.chars()
11801221
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
11811222
.sum::<usize>()
11821223
.saturating_sub(1);
1183-
1184-
let end_plus_one = end_mark + 1;
1224+
// If the annotation ends on a line-end character, we
1225+
// need to annotate one past the end of the line
1226+
let (end_mark, end_plus_one) = if end > line_end_index
1227+
// Special case for highlighting the end of a file
1228+
|| (end == line_end_index + 1 && end_line_size == 0)
1229+
{
1230+
(end_mark + 1, end_mark + 2)
1231+
} else {
1232+
(end_mark, end_mark + 1)
1233+
};
11851234

11861235
span_left_margin = min(span_left_margin, end_mark);
11871236
span_right_margin = max(span_right_margin, end_plus_one);

tests/fixtures/no-color/ann_multiline2.svg

+6-8
Loading

tests/fixtures/no-color/fold_ann_multiline.svg

+8-6
Loading

0 commit comments

Comments
 (0)