Repository: rust-lang/annotate-snippets-rs
Pull request #107 (2024-03-21 16:59:27)

fix: Allow empty annotation spans
Cargo works with empty spans in some situations, especially for eol/eof.  Annotate-snippets had a test case for this but it was dependent on a hack that remained in Cargo.  This unblocks Cargo from removing that hack.  I tested my local cargo against this and it fixes the problem I had there.

In the future, we may want to consider using a special form of underlining for empty spans.


Patch:
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 53caa38..4d2836a 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -1057,6 +1057,7 @@ fn format_body(
                 Range { start, .. } if start > line_end_index => true,
                 Range { start, end }
                     if start >= line_start_index && end <= line_end_index
+                        // Allow annotating eof or stripped eol
                         || start == line_end_index && end - start <= 1 =>
                 {
                     if let DisplayLine::Source {
@@ -1068,14 +1069,15 @@ fn format_body(
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
                             .sum::<usize>();
-                        // This allows for annotations to be placed one past the
-                        // last character
-                        let safe_end = (end - line_start_index).saturating_sub(line_length);
-                        let annotation_end_col = line[0..(end - line_start_index) - safe_end]
+                        let mut annotation_end_col = line
+                            [0..(end - line_start_index).min(line_length)]
                             .chars()
                             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
-                            .sum::<usize>()
-                            + safe_end;
+                            .sum::<usize>();
+                        if annotation_start_col == annotation_end_col {
+                            // At least highlight something
+                            annotation_end_col += 1;
+                        }
 
                         span_left_margin = min(span_left_margin, annotation_start_col);
                         span_right_margin = max(span_right_margin, annotation_end_col);


Test patch:
diff --git a/tests/fixtures/no-color/one_past.svg b/tests/fixtures/no-color/ann_eof.svg
similarity index 100%
rename from tests/fixtures/no-color/one_past.svg
rename to tests/fixtures/no-color/ann_eof.svg
diff --git a/tests/fixtures/no-color/ann_eof.toml b/tests/fixtures/no-color/ann_eof.toml
new file mode 100644
index 0000000..313d220
--- /dev/null
+++ b/tests/fixtures/no-color/ann_eof.toml
@@ -0,0 +1,12 @@
+[message]
+level = "Error"
+title = "expected `.`, `=`"
+
+[[message.snippets]]
+source = "asdf"
+line_start = 1
+origin = "Cargo.toml"
+[[message.snippets.annotations]]
+label = ""
+level = "Error"
+range = [4, 4]
diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/no-color/ann_insertion.svg
new file mode 100644
index 0000000..b15b81b
--- /dev/null
+++ b/tests/fixtures/no-color/ann_insertion.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:3</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asf</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |   ^ 'd' belongs here</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>
diff --git a/tests/fixtures/no-color/ann_insertion.toml b/tests/fixtures/no-color/ann_insertion.toml
new file mode 100644
index 0000000..ffd2140
--- /dev/null
+++ b/tests/fixtures/no-color/ann_insertion.toml
@@ -0,0 +1,12 @@
+[message]
+level = "Error"
+title = "expected `.`, `=`"
+
+[[message.snippets]]
+source = "asf"
+line_start = 1
+origin = "Cargo.toml"
+[[message.snippets.annotations]]
+label = "'d' belongs here"
+level = "Error"
+range = [2, 2]
diff --git a/tests/fixtures/no-color/ann_removed_nl.svg b/tests/fixtures/no-color/ann_removed_nl.svg
new file mode 100644
index 0000000..c8900d0
--- /dev/null
+++ b/tests/fixtures/no-color/ann_removed_nl.svg
@@ -0,0 +1,33 @@
+<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
+  <style>
+    .fg { fill: #AAAAAA }
+    .bg { background: #000000 }
+    .container {
+      padding: 0 10px;
+      line-height: 18px;
+    }
+    tspan {
+      font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+      white-space: pre;
+      line-height: 18px;
+    }
+  </style>
+
+  <rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
+
+  <text xml:space="preserve" class="container fg">
+    <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan>
+</tspan>
+    <tspan x="10px" y="46px"><tspan> --&gt; Cargo.toml:1:5</tspan>
+</tspan>
+    <tspan x="10px" y="64px"><tspan>  |</tspan>
+</tspan>
+    <tspan x="10px" y="82px"><tspan>1 | asdf</tspan>
+</tspan>
+    <tspan x="10px" y="100px"><tspan>  |     ^</tspan>
+</tspan>
+    <tspan x="10px" y="118px"><tspan>  |</tspan>
+</tspan>
+  </text>
+
+</svg>