Repository: rust-lang/annotate-snippets-rs
Pull request #111 (2024-03-21 19:43:40)

fix(render): Fold like rustc
Before, we elided all small sets of unhighlighted lines and then partially elided large sets of unhighlighted lines (favoring the start).

In contrast, rustc shows 2 lines of context, split between the start and end of the fold.
If there are 3 unhighlighted lines, it just skips folding. This makes a lot more sense.
See https://github.com/rust-lang/rust/blob/2627e9f3012a97d3136b3e11bf6bd0853c38a534/compiler/rustc_errors/src/emitter.rs#L1878C17-L1939C18

In contrast to rustc, I made the amount of context a variable to make the intent clearer and to open the door to it being configurable.


Patch:
diff --git a/examples/expected_type.svg b/examples/expected_type.svg
index ae06504..a355dbc 100644
--- a/examples/expected_type.svg
+++ b/examples/expected_type.svg
@@ -1,4 +1,4 @@
-<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg">
+<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -29,15 +29,17 @@
 </tspan>
     <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-blue bold">                                   ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan>
 </tspan>
-    <tspan x="10px" y="118px"><tspan>...</tspan>
+    <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan>                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
+    <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan>                     ,</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
+    <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan>                 range: &lt;22, 25&gt;,</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan>
+    <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">   |</tspan><tspan class="fg-bright-red bold">                         ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan>
 </tspan>
-    <tspan x="10px" y="190px">
+    <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">   |</tspan>
+</tspan>
+    <tspan x="10px" y="208px">
 </tspan>
   </text>
 
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index fa8274c..ead3db9 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -919,105 +919,65 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_>
     snippet
 }
 
-fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
-    enum Line {
-        Fold(usize),
-        Source(usize),
-    }
+fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
+    const INNER_CONTEXT: usize = 1;
+    const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1;
 
     let mut lines = vec![];
-    let mut no_annotation_lines_counter = 0;
-    for (idx, line) in body.iter().enumerate() {
-        match line {
-            DisplayLine::Source {
-                line: DisplaySourceLine::Content { .. },
-                annotations,
-                ..
-            } => {
+    let mut unhighlighed_lines = vec![];
+    for line in body {
+        match &line {
+            DisplayLine::Source { annotations, .. } => {
                 if annotations.is_empty() {
-                    no_annotation_lines_counter += 1;
-                    continue;
+                    unhighlighed_lines.push(line);
                 } else {
-                    let fold_start = idx - no_annotation_lines_counter;
-                    if no_annotation_lines_counter >= 2 {
-                        let fold_end = idx;
-                        let pre_len = if no_annotation_lines_counter > 8 {
-                            4
-                        } else {
-                            0
-                        };
-                        let post_len = if no_annotation_lines_counter > 8 {
-                            2
-                        } else {
-                            1
-                        };
-                        for (i, _) in body
-                            .iter()
-                            .enumerate()
-                            .take(fold_start + pre_len)
-                            .skip(fold_start)
-                        {
-                            lines.push(Line::Source(i));
-                        }
-                        lines.push(Line::Fold(idx));
-                        for (i, _) in body
-                            .iter()
-                            .enumerate()
-                            .take(fold_end)
-                            .skip(fold_end + 1 - post_len)
-                        {
-                            lines.push(Line::Source(i));
+                    if lines.is_empty() {
+                        // Ignore leading unhighlighed lines
+                        unhighlighed_lines.clear();
+                    }
+                    match unhighlighed_lines.len() {
+                        0 => {}
+                        n if n <= INNER_UNFOLD_SIZE => {
+                            // Rather than render `...`, don't fold
+                            lines.append(&mut unhighlighed_lines);
                         }
-                    } else {
-                        for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
-                            lines.push(Line::Source(i));
+                        _ => {
+                            lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT));
+                            let inline_marks = lines
+                                .last()
+                                .and_then(|line| {
+                                    if let DisplayLine::Source {
+                                        ref inline_marks, ..
+                                    } = line
+                                    {
+                                        let mut inline_marks = inline_marks.clone();
+                                        for mark in &mut inline_marks {
+                                            mark.mark_type = DisplayMarkType::AnnotationThrough;
+                                        }
+                                        Some(inline_marks)
+                                    } else {
+                                        None
+                                    }
+                                })
+                                .unwrap_or_default();
+                            lines.push(DisplayLine::Fold {
+                                inline_marks: inline_marks.clone(),
+                            });
+                            unhighlighed_lines
+                                .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT));
+                            lines.append(&mut unhighlighed_lines);
                         }
                     }
-                    no_annotation_lines_counter = 0;
+                    lines.push(line);
                 }
             }
-            DisplayLine::Source { .. } => {
-                no_annotation_lines_counter += 1;
-                continue;
-            }
             _ => {
-                no_annotation_lines_counter += 1;
-            }
-        }
-        lines.push(Line::Source(idx));
-    }
-
-    let mut new_body = vec![];
-    let mut removed = 0;
-    for line in lines {
-        match line {
-            Line::Source(i) => {
-                new_body.push(body.remove(i - removed));
-                removed += 1;
-            }
-            Line::Fold(i) => {
-                if let DisplayLine::Source {
-                    line: DisplaySourceLine::Content { .. },
-                    ref inline_marks,
-                    ref annotations,
-                    ..
-                } = body.get(i - removed).unwrap()
-                {
-                    if !annotations.is_empty() {
-                        new_body.push(DisplayLine::Fold {
-                            inline_marks: inline_marks.clone(),
-                        });
-                    } else {
-                        unreachable!()
-                    }
-                } else {
-                    unreachable!()
-                }
+                unhighlighed_lines.push(line);
             }
         }
     }
 
-    new_body
+    lines
 }
 
 fn format_body(


Test patch:
diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/no-color/fold_ann_multiline.svg
index 5fa6e81..0d2d67c 100644
--- a/tests/fixtures/no-color/fold_ann_multiline.svg
+++ b/tests/fixtures/no-color/fold_ann_multiline.svg
@@ -1,4 +1,4 @@
-<svg width="869px" height="272px" xmlns="http://www.w3.org/2000/svg">
+<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -28,21 +28,15 @@
 </tspan>
     <tspan x="10px" y="118px"><tspan>52 | /     for ann in annotations {</tspan>
 </tspan>
-    <tspan x="10px" y="136px"><tspan>53 | |         match (ann.range.0, ann.range.1) {</tspan>
+    <tspan x="10px" y="136px"><tspan>...  |</tspan>
 </tspan>
-    <tspan x="10px" y="154px"><tspan>54 | |             (None, None) =&gt; continue,</tspan>
+    <tspan x="10px" y="154px"><tspan>71 | |         }</tspan>
 </tspan>
-    <tspan x="10px" y="172px"><tspan>55 | |             (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
+    <tspan x="10px" y="172px"><tspan>72 | |     }</tspan>
 </tspan>
-    <tspan x="10px" y="190px"><tspan>...  |</tspan>
+    <tspan x="10px" y="190px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
 </tspan>
-    <tspan x="10px" y="208px"><tspan>71 | |         }</tspan>
-</tspan>
-    <tspan x="10px" y="226px"><tspan>72 | |     }</tspan>
-</tspan>
-    <tspan x="10px" y="244px"><tspan>   | |_____^ expected enum `std::option::Option`, found ()</tspan>
-</tspan>
-    <tspan x="10px" y="262px"><tspan>   |</tspan>
+    <tspan x="10px" y="208px"><tspan>   |</tspan>
 </tspan>
   </text>