From 1493701a40c9e0223c2c10f7b5b554b9aa800dba Mon Sep 17 00:00:00 2001
From: R4 Cheng <karaburi2023@gmail.com>
Date: Wed, 21 Aug 2024 20:46:57 +0800
Subject: [PATCH 1/4] test: Add a test case for issue 57
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Running tests/formatter.rs (target/debug/deps/formatter-d39ddd6093209828)

running 40 tests
...

failures:

---- test_line_number_0 stdout ----
thread 'test_line_number_0' panicked at tests/formatter.rs:928:5:

---- expected: tests/formatter.rs:919:20
++++ actual:   In-memory
   1    1 | error: dummy
   2      -  --> file/path:0:3
   3      -   |
   4      - 0 | foo
   5      -   |   ^
   6      -   |∅
        2 + --> file/path:0:3
        3 +  |
        4 + 0 |foo
        5 +  |   ^
        6 +  |∅

failures:
    test_line_number_0

test result: FAILED. 39 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
---
 tests/formatter.rs | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/tests/formatter.rs b/tests/formatter.rs
index 7f914de9..40e5f40f 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -905,3 +905,25 @@ error: unused optional dependency
     let renderer = Renderer::plain();
     assert_data_eq!(renderer.render(input).to_string(), expected);
 }
+
+// for issue 57
+#[test]
+fn test_line_number_0() {
+    let input = Level::Error.title("dummy").snippet(
+        Snippet::source("foo")
+            .origin("file/path")
+            .line_start(0)
+            .annotation(Level::Error.span(2..3)), // bar\nbaz
+    );
+
+    let expected = str![[r#"
+error: dummy
+ --> file/path:0:3
+  |
+0 | foo
+  |   ^
+  |
+"#]];
+    let renderer = Renderer::plain();
+    assert_data_eq!(renderer.render(input).to_string(), expected);
+}

From 7ffe4b23d7890494468897d5d3773b352cd79bdb Mon Sep 17 00:00:00 2001
From: R4 Cheng <karaburi2023@gmail.com>
Date: Fri, 23 Aug 2024 11:31:41 +0800
Subject: [PATCH 2/4] fix: Add None case for finding max line number

	- Line number width is 1 if max line number is 0
	- Line number witdh is 0 if max line number is None
	- Update test_line_number_0 in tests/formatter.rs to reflect new behavior
	- Fix issue #57

Running tests/formatter.rs (target/debug/deps/formatter-d39ddd6093209828)

running 40 tests
...

test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
---
 src/renderer/display_list.rs | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index eea49718..4b3abf1f 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -72,18 +72,17 @@ impl<'a> fmt::Debug for DisplayList<'a> {
 
 impl<'a> Display for DisplayList<'a> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let lineno_width = self.body.iter().fold(0, |max, set| {
+        let lineno_width = self.body.iter().fold(None, |max, set| {
             set.display_lines.iter().fold(max, |max, line| match line {
-                DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max),
+                DisplayLine::Source { lineno, .. } => std::cmp::max(max, *lineno),
                 _ => max,
             })
         });
-        let lineno_width = if lineno_width == 0 {
-            lineno_width
-        } else if self.anonymized_line_numbers {
-            ANONYMIZED_LINE_NUM.len()
-        } else {
-            ((lineno_width as f64).log10().floor() as usize) + 1
+        let lineno_width = match lineno_width {
+            None => 0,
+            Some(_max) if self.anonymized_line_numbers => ANONYMIZED_LINE_NUM.len(),
+            Some(0) => 1,
+            Some(max) => (max as f64).log10().floor() as usize + 1,
         };
 
         let multiline_depth = self.body.iter().fold(0, |max, set| {

From 9a89193e536d0562f0bea53f2f1f68b9e8c4e6ca Mon Sep 17 00:00:00 2001
From: R4 Cheng <karaburi2023@gmail.com>
Date: Wed, 21 Aug 2024 21:10:23 +0800
Subject: [PATCH 3/4] refactor: Rename variable

		The fold chaining is to find max line number rather than line number width
---
 src/renderer/display_list.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 4b3abf1f..516adc5a 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -72,13 +72,13 @@ impl<'a> fmt::Debug for DisplayList<'a> {
 
 impl<'a> Display for DisplayList<'a> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let lineno_width = self.body.iter().fold(None, |max, set| {
+        let max_lineno = self.body.iter().fold(None, |max, set| {
             set.display_lines.iter().fold(max, |max, line| match line {
                 DisplayLine::Source { lineno, .. } => std::cmp::max(max, *lineno),
                 _ => max,
             })
         });
-        let lineno_width = match lineno_width {
+        let lineno_width = match max_lineno {
             None => 0,
             Some(_max) if self.anonymized_line_numbers => ANONYMIZED_LINE_NUM.len(),
             Some(0) => 1,

From 7863752dad6b3bd495f3cf5916527994753d1da9 Mon Sep 17 00:00:00 2001
From: R4 Cheng <karaburi2023@gmail.com>
Date: Mon, 26 Aug 2024 13:29:31 +0800
Subject: [PATCH 4/4] refactor: Extract get_max_lineno free function

---
 src/renderer/display_list.rs | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 516adc5a..6a1cbd9f 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -72,12 +72,7 @@ impl<'a> fmt::Debug for DisplayList<'a> {
 
 impl<'a> Display for DisplayList<'a> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let max_lineno = self.body.iter().fold(None, |max, set| {
-            set.display_lines.iter().fold(max, |max, line| match line {
-                DisplayLine::Source { lineno, .. } => std::cmp::max(max, *lineno),
-                _ => max,
-            })
-        });
+        let max_lineno = get_max_lineno(&self.body);
         let lineno_width = match max_lineno {
             None => 0,
             Some(_max) if self.anonymized_line_numbers => ANONYMIZED_LINE_NUM.len(),
@@ -149,6 +144,16 @@ impl<'a> DisplayList<'a> {
     }
 }
 
+fn get_max_lineno(body: &[DisplaySet<'_>]) -> Option<usize> {
+    let max_lineno = body.iter().fold(None, |max, set| {
+        set.display_lines.iter().fold(max, |max, line| match line {
+            DisplayLine::Source { lineno, .. } => cmp::max(max, *lineno),
+            _ => max,
+        })
+    });
+    max_lineno
+}
+
 #[derive(Debug, PartialEq)]
 pub(crate) struct DisplaySet<'a> {
     pub(crate) display_lines: Vec<DisplayLine<'a>>,