Repository: brendanzab/codespan (rust)

Issue:
RichDiagnostic should point to the source of the primary label 
https://github.com/brendanzab/codespan/blob/master/codespan-reporting/src/term/views/source_snippet/mod.rs#L60#L66

https://github.com/brendanzab/codespan/blob/master/codespan-reporting/src/term/views/source_snippet/mod.rs#L87#L107

In SourceSnippet reporting, it tries to determine the max region of the error. Perhaps in previous versions this made sense, but in the latest version, the diagnostics are a PrimaryLabel + a list of other second labels (with notes and warnings, etc)

So it feels very peculiar to not just point to the PrimaryLabel's location in the span of the error message. 

Consider this example from Rust: 

```
error[E0308]: if and else have incompatible types
   --> path/to/my_file.rs:221:9
    |
218 | /     if (true) {
219 | |         5
    | |         - expected because of this
220 | |     } else {
221 | |         false
    | |         ^^^^^ expected integer, found bool
222 | |     };
    | |_____- if and else have incompatible types
    |
```

`SourceSnippet` would put this min/maxed box of ` path/to/my_file.rs:219:14` as the location, not `path/to/my_file.rs:221:9` which is the location of the primary label 



Pull request:
Point to span of primary label in rich diagnostics
Closes #131.

Patch:
diff --git a/codespan-reporting/src/term/views/source_snippet/mod.rs b/codespan-reporting/src/term/views/source_snippet/mod.rs
index 3728f21..c115af6 100644
--- a/codespan-reporting/src/term/views/source_snippet/mod.rs
+++ b/codespan-reporting/src/term/views/source_snippet/mod.rs
@@ -56,15 +56,27 @@ impl<'a> SourceSnippet<'a> {
         self.files.name(self.file_id)
     }
 
-    fn span(&self) -> Span {
-        let span = self.spans.iter().fold(None::<Span>, |acc, (label, _)| {
-            Some(acc.map_or(label.span, |acc| {
-                let start = std::cmp::min(acc.start(), label.span.start());
-                let end = std::cmp::max(acc.end(), label.span.end());
-                Span::new(start, end)
-            }))
-        });
-        span.unwrap_or(Span::initial())
+    fn source_locus_spans(&self) -> (Span, Span) {
+        fn merge(span0: Span, span1: Span) -> Span {
+            let start = std::cmp::min(span0.start(), span1.start());
+            let end = std::cmp::max(span0.end(), span1.end());
+            Span::new(start, end)
+        }
+
+        let mut source_span = None;
+        let mut locus_span = None;
+
+        for (label, mark_style) in &self.spans {
+            source_span = Some(source_span.map_or(label.span, |span| merge(span, label.span)));
+            if let MarkStyle::Primary(_) = mark_style {
+                locus_span = Some(locus_span.map_or(label.span, |span| merge(span, label.span)));
+            }
+        }
+
+        let source_span = source_span.unwrap_or(Span::initial());
+        let locus_span = locus_span.unwrap_or(source_span);
+
+        (source_span, locus_span)
     }
 
     fn location(&self, byte_index: ByteIndex) -> Result<Location, impl std::error::Error> {
@@ -84,12 +96,12 @@ impl<'a> SourceSnippet<'a> {
     }
 
     pub fn emit(&self, writer: &mut (impl WriteColor + ?Sized), config: &Config) -> io::Result<()> {
-        let span = self.span();
-        let start = self.location(span.start()).expect("location_start");
-        let end = self.location(span.end()).expect("location_end");
+        let (source_span, locus_span) = self.source_locus_spans();
+        let locus_start = self.location(locus_span.start()).expect("locus_span_start");
+        let source_end = self.location(source_span.end()).expect("source_span_end");
 
         // Use the length of the last line number as the gutter padding
-        let gutter_padding = format!("{}", end.line.number()).len();
+        let gutter_padding = format!("{}", source_end.line.number()).len();
         // Cache the tabs we'll be using to pad the source strings.
         let tab = config.tab_padding();
 
@@ -104,7 +116,7 @@ impl<'a> SourceSnippet<'a> {
         BorderTop::new(2).emit(writer, config)?;
         write!(writer, " ")?;
 
-        Locus::new(self.file_name(), start).emit(writer, config)?;
+        Locus::new(self.file_name(), locus_start).emit(writer, config)?;
 
         write!(writer, " ")?;
         BorderTop::new(3).emit(writer, config)?;


Test patch:
diff --git a/codespan-reporting/tests/snapshots/fizz_buzz__rich_color.snap b/codespan-reporting/tests/snapshots/fizz_buzz__rich_color.snap
index 41ab7a3..7d4a5d8 100644
--- a/codespan-reporting/tests/snapshots/fizz_buzz__rich_color.snap
+++ b/codespan-reporting/tests/snapshots/fizz_buzz__rich_color.snap
@@ -1,12 +1,10 @@
 ---
-created: "2019-08-28T14:48:32.919789Z"
-creator: insta@0.10.1
 source: codespan-reporting/tests/term.rs
 expression: TEST_DATA.emit_color(&config)
 ---
 {fg:Red bold bright}error[E0308]{bold bright}: `case` clauses have incompatible types{/}
 
-   {fg:Blue}┌{/}{fg:Blue}──{/} FizzBuzz.fun:3:15 {fg:Blue}───{/}
+   {fg:Blue}┌{/}{fg:Blue}──{/} FizzBuzz.fun:8:12 {fg:Blue}───{/}
    {fg:Blue}│{/}
  {fg:Blue}8{/} {fg:Blue}│{/}     _ _ => {fg:Red}num{/}
    {fg:Blue}│{/}            {fg:Red}^^^ expected `String`, found `Nat`{/}
@@ -27,7 +25,7 @@ expression: TEST_DATA.emit_color(&config)
 
 {fg:Red bold bright}error[E0308]{bold bright}: `case` clauses have incompatible types{/}
 
-    {fg:Blue}┌{/}{fg:Blue}──{/} FizzBuzz.fun:11:5 {fg:Blue}───{/}
+    {fg:Blue}┌{/}{fg:Blue}──{/} FizzBuzz.fun:15:16 {fg:Blue}───{/}
     {fg:Blue}│{/}
  {fg:Blue}15{/} {fg:Blue}│{/}         _ _ => {fg:Red}num{/}
     {fg:Blue}│{/}                {fg:Red}^^^ expected `String`, found `Nat`{/}
diff --git a/codespan-reporting/tests/snapshots/fizz_buzz__rich_no_color.snap b/codespan-reporting/tests/snapshots/fizz_buzz__rich_no_color.snap
index e2dde50..3f1d4c5 100644
--- a/codespan-reporting/tests/snapshots/fizz_buzz__rich_no_color.snap
+++ b/codespan-reporting/tests/snapshots/fizz_buzz__rich_no_color.snap
@@ -1,12 +1,10 @@
 ---
-created: "2019-08-28T14:48:32.919737Z"
-creator: insta@0.10.1
 source: codespan-reporting/tests/term.rs
 expression: TEST_DATA.emit_no_color(&config)
 ---
 error[E0308]: `case` clauses have incompatible types
 
-   ┌── FizzBuzz.fun:3:15 ───
+   ┌── FizzBuzz.fun:8:12 ───
    │
  8 │     _ _ => num
    │            ^^^ expected `String`, found `Nat`
@@ -27,7 +25,7 @@ error[E0308]: `case` clauses have incompatible types
 
 error[E0308]: `case` clauses have incompatible types
 
-    ┌── FizzBuzz.fun:11:5 ───
+    ┌── FizzBuzz.fun:15:16 ───
     │
  15 │         _ _ => num
     │                ^^^ expected `String`, found `Nat`