Code Monkey home page Code Monkey logo

annotate-snippets-rs's Introduction

annotate-snippets

annotate-snippets is a Rust library for annotation of programming code slices.

crates.io documentation build status

The library helps visualize meta information annotating source code slices. It takes a data structure called Snippet on the input and produces a String which may look like this:

Screenshot

Local Development

cargo build
cargo test

When submitting a PR please use cargo fmt (nightly).

annotate-snippets-rs's People

Contributors

atouchet avatar bash avatar digama0 avatar epage avatar godtamit avatar inky-developer avatar jim4067 avatar magurotuna avatar muscraft avatar ndmitchell avatar oli-cosmian avatar oli-obk avatar phansch avatar rcoh avatar renovate[bot] avatar yoshuawuyts avatar zbraniecki avatar zzau13 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

annotate-snippets-rs's Issues

The README usage example panics

$ cargo run
   Compiling annotate-snippets v0.9.0
   Compiling rust-test v0.1.0 (/home/mario/Documents/rust-test)
    Finished dev [unoptimized + debuginfo] target(s) in 0.92s
     Running `target/debug/rust-test`
thread 'main' panicked at 'SourceAnnotation range `(205, 207)` is bigger than source length `195`', /home/mario/.cargo/registry/src/github.com-1ecc6299db9ec823/annotate-snippets-0.9.0/src/display_list/from_snippet.rs:287:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Appears to be an issue with the index into the source, which is in fact 195 characters long. Based on the error message I guess it should be highlighting the 22, which would be (187, 189), not (205, 207).

Source code with tabs did not highlight correctly

Here some example Slice where t symbol in source code have one space behind:
image

That Slice give us that output:
image

Now i change space behind t from space to tab(which still one symbol, so range of highlight should not be changed):
image
And highlight in output is wrong now:
image

Publish new version to crates.io

I noticed that the last version of this crate published to crates.io was back in July 2019. Would it be possible to publish a new version of this crate?

Source annotate ranges should use bytes not chars

Currently, SourceAnnotation's range field is based on chars, not bytes. This can be a little confusing for users and can cause issues when dealing with spans from things like toml errors. It would make more sense to have users give us bytes, and we calculate the char spans internally. This would make the API easier to use as most people can handle going to bytes, but going from bytes -> chars can be very hard.

Support Sub-diagnostics with spans

Currently, sub-diagnostics are handled by adding Annotation's to Snippet::footer. This system is fine, but it does not support sub-diagnostics with a span. This is needed for rust adoption as Subdiag can have spans.

One thing to note is that sub-diagnostics are rendered differently depending on if they have a span or they don't have any.

error[E0000]: main error message
  --> file.rs:LL:CC
   |
LL | <code>
   | -^^^^- secondary label
   |  |
   |  primary label
   |
   = note: note without a `Span`, created with `.note`
note: sub-diagnostic message for `.span_note`
  --> file.rs:LL:CC
   |
LL | more code
   |      ^^^^

Since spans need a source, sub-diagnostics should probably need to be moved under Slice or something similar. This does bring up the question of where we should show a sub-diagnostic, at the end of a Snippet, or at the end of source the span is associated with.

Support `Path` origins

I'd like to have a function return a Snippet but origin is a &str and I have a PathBuf. I can convert it to a Cow<str> but I wouldn't be able to return the Snippet.

SourceAnnotation underline displays offset from range

I was getting #24 with v0.6.1, but not anymore. Unfortunately I got a similar problem when I tried to update to v0.7.0.

Here's with v0.6.1:

error: invalid immediate, couldn't parse number (was: 30_2)
 --> .\add.asm:1:6
  |
...
5 | .ORIG x30_2
  |       ^^^^^ invalid immediate here
  |

and with v0.7.0, removing the DisplayListFormatter and adding default FormatOptions to the snippet:

error: invalid immediate, couldn't parse number (was: 30_2)
 --> .\add.asm:1:11
  |
...
5 |   .ORIG x30_2
  |  ___________^
6 | | AND R1, R1, R1
  | |____^ invalid immediate here
  |

add.asm has 4 lines before that error, each ending in CRLF. It seems like the offset is in the other direction now.

Originally posted by @gipsond in #24 (comment)

Origin line number is not correct when using a slice with `fold: true`

Here is a test case demonstrating the issue:

#[test]
fn test_origin_line_numbers_with_fold() {
    let snippets = Snippet {
        title: Some(snippet::Annotation {
            id: None,
            label: Some("oops"),
            annotation_type: snippet::AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![snippet::Slice {
            source: "First line\r\n\r\n\r\nSecond oops line",
            line_start: 1,
            origin: Some("<current file>"),
            annotations: vec![
                snippet::SourceAnnotation {
                    range: (23, 27),
                    label: "oops",
                    annotation_type: snippet::AnnotationType::Error,
                },
                snippet::SourceAnnotation {
                    range: (0, 5),
                    label: "First",
                    annotation_type: snippet::AnnotationType::Info,
                },
            ],
            fold: true,
        }],
        opt: Default::default(),
    };
    let expected = r#"error: oops
 --> <current file>:4:8
  |
1 | First line
  | ----- info: First
...
4 | Second oops line
  |        ^^^^ oops
  |"#;

    assert_eq!(DisplayList::from(snippets).to_string(), expected);
}

--> <current file>:2:8 is printed instead of --> <current file>:4:8.

Understanding SourceAnnotation.range

Hello!

I'm testing this library as an alternative to codespan_reporting and I'm having trouble understanding the SourceAnnotation.range value. I thought it was a byte offset (as in codespan_reporting, that I can get from my lalrpop parser) but it seems to have behaviour that suggests it is not.

When the annotation is on the first line of the source the annotation is positioned as I expect:

line 1:

error: Type mismatch
 --> /Users/louis/src/gleam/gleam/test/hello_world/src/other.gleam:1:13
  |
1 | fn main() { "" + 1 }
  |             ^^
  |

However when the annotation is not on the first line of the source it seems to move back by one character per line.

line 8:

error: Type mismatch
 --> /Users/louis/src/gleam/gleam/test/hello_world/src/other.gleam:1:6
  |
...
8 | fn main() { "" + 1 }
  |      ^^
  |

SourceAnnotation.range doesn't seem to be documented- what does it accept?

Thank you

Removing duplicate Slice origins and surrounding empty lines

I'm currently trying to move the suggestions of the following diagnostic over from Rust to annotate-snippet:

error[E0382]: use of moved value: `x`
  --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5
   |
 4 |     let x = vec![1];
   |         - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
 7 |     let y = x;
   |             - value moved here
 9 |     x; //~ ERROR use of moved value
   |     ^ value used here after move

In short, from Rust I can only get a Vec that contains the three lines with the annotations. This Vec contains only the lines with annotations and no other lines around it. Something similar to this:

[
  Line {
      line_index: 4,
      annotations: [
          Annotation {
              start_col: 8,
              end_col: 9,
              label: "move occurs because ...",
          },
      ],
  },
  Line {
      line_index: 7,
      annotations: [
          Annotation {
              start_col: 11,
              end_col: 12,
              label: "value moved here",
          },
      ],
  },
  // etc..
]

I'm currently mapping this to annotate-snippets by turning each Line into a Slice and then calling DisplayList::from(snippet) at the end.

The problem is that each Slice has the origin at the top:

error[E0382]: use of moved value: `x`
  --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:8
   |
 4 |     let x = vec![1];
   |         ^ move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
   |
  ::: /code/rust/src/test/ui/annotate-snippet/suggestion.rs:7:12
   |
 7 |     let y = x;
   |             ^ value moved here
   |
  ::: /code/rust/src/test/ui/annotate-snippet/suggestion.rs:9:4
   |
 9 |     x; //~ ERROR use of moved value
   |     ^ value used here after move

If I set the origin of consecutive slices to None there will still be extra empty padding lines:

error[E0382]: use of moved value: `x`
  --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:5:8                                                             
   |
LL |     let x = vec![1];
   |         ^ move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
   |
   |
LL |     let y = x;
   |             ^ value moved here
   |
   |
LL |     x; //~ ERROR use of moved value
   |     ^ value used here after move
   |

I'm not sure what the original idea for a Slice was, but it seems like the best approach for those rustc annotations?

I can see three ways to solve this currently:

  1. annotate-snippets doesn't add empty padding lines if the origin is None
  2. annotate-snippets removes the origin and empty padding lines of consecutive Slices that have the same origin. This means annotate-snippet would have to compare the origin of each Slice to origin of the previous Slice.
  3. We implement From<Snippet> for DisplayList by ourselves in rustc.

I would love to hear your thoughts on this before I continue with the annotations =)

cc rust-lang/rust#61809

Weird formatting with `fold: true`

When rendering an annotation with fold: true the first 4 lines get rendered if the source has more than 7 lines.
If the source has 7 or less lines, the first lines do not get rendered.

Example with source = "\n\n\n\n\n\n\nFoo":

error
  |
...
8 | Foo
  | ^^^ Error here
  |

Example with source = "\n\n\n\n\n\n\n\nFoo":

error
  |
1 | 
2 | 
3 | 
4 | 
...
8 | 
9 | Foo
  | ^^^ Error here
  |

Minimal example:

use annotate_snippets::{
    display_list::{DisplayList, FormatOptions},
    snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
};

fn print_snippet(source: &str, range: (usize, usize)) {
    let s = Snippet {
        title: Some(Annotation {
            label: None,
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: Vec::new(),
        slices: vec![Slice {
            fold: true,
            line_start: 1,
            source,
            origin: None,
            annotations: vec![SourceAnnotation {
                annotation_type: AnnotationType::Error,
                label: "Error here",
                range,
            }],
        }],
        opt: FormatOptions::default(),
    };
    println!("{}", DisplayList::from(s));
}

fn main() {
    print_snippet("\n\n\n\n\n\n\nFoo", (7, 10));
    println!("\nVs\n");
    print_snippet("\n\n\n\n\n\n\n\nFoo", (8, 11));
}

I am not sure if this is intended. In either case there should probably some way to disable this behavior.

RFC: Proposal for "modern" API

In order to address concerns in #11, #12, wycats/language-reporting#6, and probably others.

I've been experimenting with merging the APIs of codespan/language-reporting/annotate-snippets, and the below API surface is what that I think makes the most sense.

NOTE: the suggested API has changed multiple times from feedback, see conversation starting at this comment for the most recent API and discussion.

Original Proposal

An experimental implementation of the API based on #12 is at CAD97/retort#1 (being pushed within 24 hours of posting, I've got one last bit to "port" but I've got to get to bed now but I wanted to get this posted first).

EDIT: I've reconsidered this API, though the linked PR does implement most of it. I'm sketching a new slightly lower-level design from this one, and the diagnostic layout of this current API will probably be a wrapper library around annotate-snippets. (I get to use the retort name!)

API
use termcolor::WriteColor;

trait Span: fmt::Debug + Copy {
    type Origin: ?Sized + fmt::Debug + Eq;

    fn start(&self) -> usize;
    fn end(&self) -> usize;
    fn new(&self, start: usize, end: usize) -> Self;
    fn origin(&self) -> &Self::Origin;
}

trait SpanResolver<Sp> {
    fn first_line_of(&mut self, span: Sp) -> Option<SpannedLine<Sp>>;
    fn next_line_of(&mut self, span: Sp, line: SpannedLine<Sp>) -> Option<SpannedLine<Sp>>;
    fn write_span(&mut self, w: &mut dyn WriteColor, span: Sp) -> io::Result<()>;
    fn write_origin(&mut self, w: &mut dyn WriteColor, origin: Sp) -> io::Result<()>;
}

#[derive(Debug, Copy, Clone)]
pub struct SpannedLine<Sp> {
    line_num: usize,
    char_count: usize,
    span: Sp,
}

impl Span for (usize, usize) {
    type Origin = ();
}

impl<Sp: Span<Origin=()>> Span for (&'_ str, Sp) {
    type Origin = str;
}

impl<Sp: Span> SpanResolver<Sp> for &str
where Sp::Origin: fmt::Display;

mod diagnostic {
    #[derive(Debug, Clone)]
    struct Diagnostic<'a, Sp: Span> {
        pub primary: Annotation<'a, Sp>,
        pub code: Option<Cow<'a, str>>,
        pub secondary: Cow<'a, [Annotation<'a, Sp>]>,
    }

    #[derive(Debug, Clone)]
    struct Annotation<'a, Sp: Span> {
        pub span: Sp,
        pub level: Level,
        pub message: Cow<'a, str>,
    }

    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
    enum Level {
        Err,
        Warn,
        Info,
        Hint,
    }

    impl<Sp: Span> Diagnostic<'_, Sp> {
        pub fn borrow(&self) -> Diagnostic<'_, Sp>;
        pub fn into_owned(self) -> Diagnostic<'static, Sp>;
    }

    impl<Sp: Span> Annotation<'_, Sp> {
        pub fn borrow(&self) -> Annotation<'_, Sp>;
        pub fn into_owned(self) -> Annotation<'static, Sp>;
    }

    impl fmt::Display for Level;
}

mod style {
    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
    enum Mark {
        None,
        Start,
        Continue,
        End,
    }

    #[non_exhaustive]
    #[derive(Debug, Copy, Clone)]
    pub enum Style {
        Base,
        Code,
        Diagnostic(Level),
        LineNum,
        TitleLine,
        OriginLine,
    }

    trait Stylesheet {
        fn set_style(&mut self, w: &mut dyn WriteColor, style: Style) -> io::Result<()>;
        fn write_marks(&mut self, w: &mut dyn WriteColor, marks: &[Mark]) -> io::Result<()>;
        fn write_divider(&mut self, w: &mut dyn WriteColor) -> io::Result<()>;
        fn write_underline(
            &mut self,
            w: &mut dyn WriteColor,
            level: Level,
            len: usize,
        ) -> io::Result<()>;
    }

    struct Rustc; impl Stylesheet for Rustc;
    // other styles in the future
}

mod renderer {
    fn render<'a, Sp: Span>(
        w: &mut dyn WriteColor,
        stylesheet: &dyn Stylesheet,
        span_resolver: &mut dyn SpanResolver<Sp>,
        diagnostic: &'a Diagnostic<'a, Sp>,
    ) -> io::Result<()>;

    fn lsp<'a, Sp: Span + 'a>(
        diagnostics: impl IntoIterator<Item = Diagnostic<'a, Sp>>,
        source: Option<&'_ str>,
        span_resolver: impl FnMut(Sp) -> lsp_types::Location,
    ) -> Vec<lsp_types::PublishDiagnosticsParams>;
}

Notes:

  • I've skipped imports and implementation bodies for clarity. All definitions are exported where I've written them.
  • I've liberally used dyn Trait, so the only monomorphization should be over the Span type.
  • I'm not particularly attached to any of the organization of exports, things can move around.
  • Span::new is only used for impl SpanResolver<impl Span> for &str; making that impl more specific can get rid of that trait method.
  • SpanResolver takes &mut for its methods primarily because it can, in order to allow use of a single-threaded DB that requires &mut access for caching as a span resolver.
  • Span resolution is passed through SpanResolver at the last moment such that a SpanResolver can supply syntax highlighting for errors.
  • SpanResolver::write_origin only gets io::Write because styling is done ahead of time by Stylesheet. Because WriteColor does not have an upcast method, this means we can't use dyn WriteColor anywhere that will end up calling SpanResolver::write_origin. This can be changed to take WriteColor if desired.
  • Diagnostic's layout is tuned to have similar layout to the language server protocol's Diagnostic.
  • Diagnostic is set up so that Diagnostic<'_, Sp> can be borrowed but also an owned Diagnostic<'static, Sp> can be produced by using Cows. This eases use with constructed diagnostics.
  • Potential style improvement: extend Style::Code to be an enum of general code token types (e.g. the list from pygments), SpanResolver::write_span just gets the ability to set the style to one of those, which goes through the StyleSheet for styling.

Unaligned display when one line source given

Result:

 |
0 | SELECT bar
 |        ^^^ unexpected token
 | ^^^^^^^^^^ while parsing statement
 |

As you can see the | is not aligned.

Code:

pub(crate) fn display_annotated_error(source: &str, labels: Vec<(Range, String)>) -> String {
    let annotations = labels
        .iter()
        .map(|label| SourceAnnotation {
            range: (label.0.start, label.0.end),
            label: label.1.as_str(),
            annotation_type: AnnotationType::Error,
        })
        .collect::<Vec<_>>();

    let snippet = Snippet {
        title: None,
        footer: vec![],
        slices: vec![Slice {
            source,
            line_start: 0,
            origin: None,
            annotations,
            fold: false,
        }],
        opt: FormatOptions {
            color: true,
            ..Default::default()
        },
    };

    format!("{}", DisplayList::from(snippet))
}

display_annotated_error("SELECT bar", labels)
// where labels = [((7, 10), unexpected token), ((0, 10), while parsing statement)],

Off-by-one error in multiline error highlights

In both cases, the highlight begins at letter e and ends at letter l. In the one-line case, it is correct:

error: oops
 --> <current file>:1:6
  |
1 | abcd efgh ijkl mnop
  |      ^^^^^^^^^ oops
  |

But in multiple lines it is off by one - the end pointer points to one past the end rather than the last character of the highlight:

error: oops
 --> <current file>:1:6
  |
1 |   abcd efgh
  |  ______^
2 | | ijkl mnop
  | |_____^ oops
  |

I could propose code but I'm not sure I'll get all the details of continuations right since I haven't used those and am not certain of the desired behavior. Here's the issue presented as a test case.

use annotate_snippets::{display_list::DisplayList, snippet};
use snippet::Snippet;

#[test]
fn with_space_works() {
    let snippets = Snippet {
        title: Some(snippet::Annotation {
            id: None,
            label: Some("oops"),
            annotation_type: snippet::AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![snippet::Slice {
            source: "abcd efgh ijkl mnop",
            line_start: 1,
            origin: Some("<current file>"),
            annotations: vec![snippet::SourceAnnotation {
                range: (5, 14),
                label: "oops",
                annotation_type: snippet::AnnotationType::Error,
            }],
            fold: true,
        }],
        opt: Default::default(),
    };
    let expected = r#"error: oops
 --> <current file>:1:6
  |
1 | abcd efgh ijkl mnop
  |      ^^^^^^^^^ oops
  |"#;
    assert_eq!(DisplayList::from(snippets).to_string(), expected);
}

#[test]
fn with_newline_fails() {
    let snippets = Snippet {
        title: Some(snippet::Annotation {
            id: None,
            label: Some("oops"),
            annotation_type: snippet::AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![snippet::Slice {
            source: "abcd efgh\nijkl mnop",
            line_start: 1,
            origin: Some("<current file>"),
            annotations: vec![snippet::SourceAnnotation {
                range: (5, 14),
                label: "oops",
                annotation_type: snippet::AnnotationType::Error,
            }],
            fold: true,
        }],
        opt: Default::default(),
    };
    let expected = r#"error: oops
 --> <current file>:1:6
  |
1 |   abcd efgh
  |  ______^
2 | | ijkl mnop
  | |____^ oops
  |"#;

    assert_eq!(DisplayList::from(snippets).to_string(), expected);
}

Formatting bug with overlapping lines

I was messing around with the basic example, and found some interesting behavior (that doesn't seem to be quite right. Below is the source code + output for a few cases.

update: as I was going through this it seems that there is a clear bug for this. If we start the annotation at either 0 or 1 it offsets. At range 2 it's before the line. But at range 3 it actually pops back to the right location (I think, I'm unsure because of /, but that might just be the right behavior)! Check out cases 4 and 5 for what's going on.

Either way, I hope this report is helpful. Excited this crate exists!

Case 1

Output

error: expected type, found `22`
  --> examples/example.txt:27:11
   |
26 |
   |   __-
   |   __-
27 |    This is an example content of the slice which will be annotated with the list of annotations below.
   |  |            ^^^^^ Example error annotation
   | ||___________________________________________________________- and here's a warning
   |  |_______________________________________- and here's a warning
28 |
   |

Code

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let snippet = Snippet {
        title: Some(Annotation {
            label: Some("expected type, found `22`".to_string()),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![Slice {
            source: r#"
This is an example content of the slice which will be annotated with the list of annotations below.
                "#
            .to_string(),
            line_start: 26,
            origin: Some("examples/example.txt".to_string()),
            fold: false,
            annotations: vec![
                SourceAnnotation {
                    label: "Example error annotation".to_string(),
                    annotation_type: AnnotationType::Error,
                    range: (13, 18),
                },
                SourceAnnotation {
                    label: "and here's a warning".to_string(),
                    annotation_type: AnnotationType::Warning,
                    range: (1, 60),
                },
                SourceAnnotation {
                    label: "and here's a warning".to_string(),
                    annotation_type: AnnotationType::Warning,
                    range: (1, 40),
                },
            ],
        }],
    };

    let dl = DisplayList::from(snippet);
    let dlf = DisplayListFormatter::new(true, false);
    println!("{}", dlf.format(&dl));
}

Case 2

Output

error: expected type, found `22`
  --> examples/example.txt:27:11
   |
26 |
   |  __-
27 |   This is an example content of the slice which will be annotated with the list of annotations below.
   | |            ^^^^^ Example error annotation
   | |___________________________________________________________- and here's a warning
28 |
   |

Code

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let snippet = Snippet {
        title: Some(Annotation {
            label: Some("expected type, found `22`".to_string()),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![Slice {
            source: r#"
This is an example content of the slice which will be annotated with the list of annotations below.
                "#
            .to_string(),
            line_start: 26,
            origin: Some("examples/example.txt".to_string()),
            fold: false,
            annotations: vec![
                SourceAnnotation {
                    label: "Example error annotation".to_string(),
                    annotation_type: AnnotationType::Error,
                    range: (13, 18),
                },
                SourceAnnotation {
                    label: "and here's a warning".to_string(),
                    annotation_type: AnnotationType::Warning,
                    range: (1, 60),
                },
            ],
        }],
    };

    let dl = DisplayList::from(snippet);
    let dlf = DisplayListFormatter::new(true, false);
    println!("{}", dlf.format(&dl));
}

Case 3

Output

error: expected type, found `22`
  --> examples/example.txt:26:1
   |
26 |
   |  __-
27 | | This is an example content of the slice which will be annotated with the list of annotations below.
   | |___________________________________________________________- and here's a warning
28 |
   |

Code

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let snippet = Snippet {
        title: Some(Annotation {
            label: Some("expected type, found `22`".to_string()),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![Slice {
            source: r#"
This is an example content of the slice which will be annotated with the list of annotations below.
                "#
            .to_string(),
            line_start: 26,
            origin: Some("examples/example.txt".to_string()),
            fold: false,
            annotations: vec![SourceAnnotation {
                label: "and here's a warning".to_string(),
                annotation_type: AnnotationType::Warning,
                range: (1, 60),
            }],
        }],
    };

    let dl = DisplayList::from(snippet);
    let dlf = DisplayListFormatter::new(true, false);
    println!("{}", dlf.format(&dl));
}

Case 4

The range now starts at index 2.

Output

error: expected type, found `22`
  --> examples/example.txt:27:0
   |
26 |
27 | / This is an example content of the slice which will be
28 | | annotated with the list of annotations below.
   | |____- and here's a warning
29 |
   |

Code

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let snippet = Snippet {
        title: Some(Annotation {
            label: Some("expected type, found `22`".to_string()),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![Slice {
            source: r#"
This is an example content of the slice which will be
annotated with the list of annotations below.
                "#
            .to_string(),
            line_start: 26,
            origin: Some("examples/example.txt".to_string()),
            fold: false,
            annotations: vec![SourceAnnotation {
                label: "and here's a warning".to_string(),
                annotation_type: AnnotationType::Warning,
                range: (2, 60),
            }],
        }],
    };

    let dl = DisplayList::from(snippet);
    let dlf = DisplayListFormatter::new(true, false);
    println!("{}", dlf.format(&dl));
}

Case 5

The range now starts at index 3.

Output

error: expected type, found `22`
  --> examples/example.txt:27:1
   |
26 |
27 |   This is an example content of the slice which will be
   |  __-
28 | | annotated with the list of annotations below.
   | |____- and here's a warning
29 |
   |

Code

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let snippet = Snippet {
        title: Some(Annotation {
            label: Some("expected type, found `22`".to_string()),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: vec![],
        slices: vec![Slice {
            source: r#"
This is an example content of the slice which will be
annotated with the list of annotations below.
                "#
            .to_string(),
            line_start: 26,
            origin: Some("examples/example.txt".to_string()),
            fold: false,
            annotations: vec![SourceAnnotation {
                label: "and here's a warning".to_string(),
                annotation_type: AnnotationType::Warning,
                range: (3, 60),
            }],
        }],
    };

    let dl = DisplayList::from(snippet);
    let dlf = DisplayListFormatter::new(true, false);
    println!("{}", dlf.format(&dl));
}

Custom box drawing characters

This is split out of #13 (comment)

FormattedSnippet is public for alternate renderers to use, such as ones that want to use box drawing characters. The layout work is done (i.e. placing lines where they need to be) and all the renderer does is translate the structured output into io::Write calls.

Refactor the code to use modern Rust

Since we now have interest from https://github.com/brendanzab/codespan and https://github.com/wycats/language-reporting to merge them into annotate-snippets, I'd like to put some effort into cleaning up the internals of this crate.

There are two levels on which we can work:

  1. We can provide some basic impl methods for our structs/enums.

The reasons here would be to make creating snippets/slices easier, and to allow for data validation during snippet/slice creation. For example, we could then validate that an annotation vector fits into slice scope.

  1. We can clean up the internal logic.

A lot of the internal code is suboptimally written and not fully performant.

I'm not sure how/if/when I'd like to tackle the (1), since I'm not sure how much value there is to duplicate the validation which must happen anyway during slice->displaylist creation. I certainly would like to avoid performing the same logic twice.
So maybe it's ok that you construct the Slice which may be bogus, and then when you convert it to display list we can error out if your slice pointers make no sense.

We can think about it more.

But I certainly would like to clean up the inner guts to reduce memory usage and boost perf. I started playing with it, and am getting a nice ~10x perf boost so I'll continue working on the cleanups. The cleanups should not affect the API except of things like zero-copy.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

cargo
Cargo.toml
  • anstyle 1.0.4
  • unicode-width 0.1.11
  • anstream 0.6.13
  • criterion 0.5.1
  • difference 2.0.0
  • glob 0.3.1
  • serde 1.0.197
  • snapbox 0.5.9
  • toml 0.5.11
github-actions
.github/workflows/audit.yml
  • actions/checkout v4
  • actions-rs/audit-check v1
  • actions/checkout v4
  • EmbarkStudios/cargo-deny-action v1
.github/workflows/ci.yml
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • github/codeql-action v3
.github/workflows/committed.yml
  • actions/checkout v4
.github/workflows/pre-commit.yml
  • actions/checkout v4
  • actions/setup-python v5
  • pre-commit/action v3.0.1
.github/workflows/rust-next.yml
  • actions/checkout v4
  • Swatinem/rust-cache v2
  • actions/checkout v4
  • Swatinem/rust-cache v2
.github/workflows/spelling.yml
  • actions/checkout v4
regex
.github/workflows/ci.yml
  • rust 1.73
  • rust 1.73
  • rust 1.73
  • rust 1.73
Cargo.toml
  • rust 1.73

  • Check this box to trigger a request for Renovate to run again on this repository

Decouple `FormatOptions` from `Snippet`

At the layer I want to be creating Snippets, I won't know whether colored formatting is appropriate or not. That seems like a concern for when converting into a DisplayList.

Column number should start at 1 and not 0

Given this code:

let snippet = Snippet {
    title: Some("failed to parse file".to_owned()),
    footer: vec![],
    slices: vec![Slice {
        source: r#"This is an example
content of the slice
which will be annotated
with the list of annotations below."#
            .to_string(),
        line_start: 26,
        origin: Some("chris.beep".to_owned()),
        fold: false,
        annotations: vec![SourceAnnotation {
            label: "hello world".to_owned(),
            annotation_type: AnnotationType::Error,
            range: (13, 20),
        }],
    }],
};

This results in the following output:

error: failed to parse file
  --> chris.beep:26:13
   |
26 | This is an example
   |              ^^^^^^^ hello world
27 | content of the slice
28 | which will be annotated
29 | with the list of annotations below.

Because the output should be for humans, shouldn't the location be chris.beep:26:14?

Support messages without a line associated with it

I am creating a tool that needs to report messages on the file itself and not a specific range within the file. I'd like these messages to be consistent with my annotate-snippets messages but the API requires having source associated with it.

Multi-line slices are not wrapping around when over by 1

Given this code:

let snippet = Snippet {
    title: Some("failed to parse file".to_owned()),
    footer: vec![],
    slices: vec![Slice {
        source: r#"This is an example
content of the slice
which will be annotated
with the list of annotations below."#
            .to_string(),
        line_start: 26,
        origin: Some("chris.beep".to_owned()),
        fold: false,
        annotations: vec![SourceAnnotation {
            label: "hello world".to_owned(),
            annotation_type: AnnotationType::Error,
            range: (13, 20),
        }],
    }],
};

This results in the following output:

error: failed to parse file
  --> chris.beep:26:13
   |
26 | This is an example
   |              ^^^^^^^ hello world
27 | content of the slice
28 | which will be annotated
29 | with the list of annotations below.

I would expect the underlining should wrap around to include the c of content on the next line. If we change the range from (13, 20) to (13, 21), that result includes the co of content being underlined.

__ gets lost from labels

The starlark-rust library uses this code, specifically at https://github.com/facebookexperimental/starlark-rust/blob/13bec61a44dd2ec268403e1d6da4401ea76a3f12/starlark/src/errors/mod.rs#L205-L219. The relevant fragment is:

    let snippet = Snippet {
        title: Some(Annotation {
            label: Some(annotation_label),
            id: None,
            annotation_type: AnnotationType::Error,
        }),
        footer: Vec::new(),
        slices: slice.map(|s| vec![s]).unwrap_or_default(),
        opt: FormatOptions {
            color,
            ..Default::default()
        },
    };

    DisplayList::from(snippet)

However, if we have a label containing __, then that text gets lost from the label. The reason is that format_label in

for (idx, element) in label.split("__").enumerate() {
splits by __ to apply italics to alternate parts of the label. That behaviour was super surprising. I can't find it documented. There seems to be no way to turn it off, and no way to apply escaping to stop it happening. Can you suggest how I might get it so that errors don't get changed as they are rendered?

Support any `Display`, not just `&str`

This is split out of #13 (comment)

Input text doesn't have to be &str; it can be anything that can implement Display. This is especially useful when the origin source #12 (comment), so the intermediate allocation doesn't have to happen. Playing with take-two I think this dyn has negligible effect on runtime, the real killer to my time is dyn indirection to the span resolvers IIUC.

Special way to underline with empty spans

The toml crate occasionally returns empty spans and #107 fixed it so we could render something but it might be useful to try to highlight the space between characters, rather than a character.

Annotate range cannot match `\t`

use std::fs;
use std::path::PathBuf;

use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{AnnotationType, Slice, Snippet, SourceAnnotation};

fn main() {
    let sql = fs::read_to_string(PathBuf::from("21.sql")).unwrap();
    let source = sql.as_str();

    println!("source: {:?}", source);
    println!("capture: {}", &source[51..52]);

    let snippet = Snippet {
        title: None,
        footer: vec![],
        slices: vec![Slice {
            source,
            line_start: 1,
            origin: None,
            annotations: vec![SourceAnnotation {
                range: (51, 52),
                label: "",
                annotation_type: AnnotationType::Error,
            }],
            fold: false,
        }],
        opt: FormatOptions {
            color: true,
            ..Default::default()
        },
    };

    println!("{}", DisplayList::from(snippet));
}

... output:

source: "-- using default substitutions\r\n\r\n\r\nselect\r\n\ts_name,\r\n\tcount(*) as numwait\r\nfrom\r\n\tsupplier,\r\n\tlineitem l1,\r\n\torders,\r\n\tnation\r\nwhere\r\n\ts_suppkey = l1.l_suppkey\r\n\tand o_orderkey = l1.l_orderkey\r\n\tand o_orderstatus = 'F'\r\n\tand l1.l_receiptdate > l1.l_commitdate\r\n\tand exists (\r\n\t\tselect\r\n\t\t\t*\r\n\t\tfrom\r\n\t\t\tlineitem l2\r\n\t\twhere\r\n\t\t\tl2.l_orderkey = l1.l_orderkey\r\n\t\t\tand l2.l_suppkey <> l1.l_suppkey\r\n\t)\r\n\tand not exists (\r\n\t\tselect\r\n\t\t\t*\r\n\t\tfrom\r\n\t\t\tlineitem l3\r\n\t\twhere\r\n\t\t\tl3.l_orderkey = l1.l_orderkey\r\n\t\t\tand l3.l_suppkey <> l1.l_suppkey\r\n\t\t\tand l3.l_receiptdate > l3.l_commitdate\r\n\t)\r\n\tand s_nationkey = n_nationkey\r\n\tand n_name = 'SAUDI ARABIA'\r\ngroup by\r\n\ts_name\r\norder by\r\n\tnumwait desc,\r\n\ts_name;\r\n"
capture: ,
   |
 1 | -- using default substitutions
 2 | 
 3 | 
 4 | select
 5 |    s_name,
   |       ^
 6 |    count(*) as numwait
 7 | from
 8 |    supplier,
 9 |    lineitem l1,
10 |    orders,
11 |    nation
12 | where
13 |    s_suppkey = l1.l_suppkey
14 |    and o_orderkey = l1.l_orderkey
15 |    and o_orderstatus = 'F'
16 |    and l1.l_receiptdate > l1.l_commitdate
17 |    and exists (
18 |            select
19 |                    *
20 |            from
21 |                    lineitem l2
22 |            where
23 |                    l2.l_orderkey = l1.l_orderkey
24 |                    and l2.l_suppkey <> l1.l_suppkey
25 |    )
26 |    and not exists (
27 |            select
28 |                    *
29 |            from
30 |                    lineitem l3
31 |            where
32 |                    l3.l_orderkey = l1.l_orderkey
33 |                    and l3.l_suppkey <> l1.l_suppkey
34 |                    and l3.l_receiptdate > l3.l_commitdate
35 |    )
36 |    and s_nationkey = n_nationkey
37 |    and n_name = 'SAUDI ARABIA'
38 | group by
39 |    s_name
40 | order by
41 |    numwait desc,
42 |    s_name;

If I change all \r\n to \n, it can correctly point at ,.

If I change all \t to spaces and correct count the span (I have a outer fn to count chars, here it becomes 54..55), it can correctly point at ,

  • Env: macOS
  • Lib Version: 0.9.1

Error from Snippet to DisplayList

I use this in yarte here.

With any error returns something similar to this:

DisplayList { body: [Raw(Annotation { annotation: Annotation { annotation_type: Error, id: None, label: [DisplayTextFragment { content: "", style: Emphasis }] }, source_aligned: false, continuation: false }), Raw(Origin { path: "templates/deep/more/card/hi.hbs", pos: Some((7, 21)), header_type: Initial }), Source { lineno: None, inline_marks: [], line: Empty }, Source { lineno: Some(7), inline_marks: [], line: Content { text: "<h1>Hi, {{ name }} {{ @lastname }}", range: (0, 35) } }, Source { lineno: None, inline_marks: [], 

// Label is empty
line: Annotation { 
    annotation: Annotation { 
       annotation_type: None, id: None, 
       label: [/* WHY IS EMPTY */] }, 

range: (21, 22), annotation_type: Error, annotation_part: MultilineStart } }, Source { lineno: None, inline_marks: [], line: Empty }] }

And as you can see the label has vanished

Support optional rendering of `AnnotationType` type prefix like "note:", "help:", "hint:", etc.

Perhaps this functionality already exists, but I couldn't find it in any of the options, I also didn't see how it could be manipulated in the resulting DisplayList.

By default, the prefix that denotes the AnnotationType is not rendered for errors and warnings, but it is for all other types of annotations. In the output of the Sway compiler we need the flexibility to show the prefix for some annotations, and for some not.

Consider the following output of the Sway compiler:

07C Constant shadowing - Alias - After 02

We want to have the help: prefix in the footer, but not the info: prefix in the code snippet. In general, we do not want to have any prefixes in the code snippet.

Is there a way already to achieve this functionality?

If not, I would be glad to contribute and implement it.

Provide a way to 'anonymize' line numbers when building a Snippet

In the rust test suite there's the option to replace line numbers in the diagnostics output with letters, so that changes in the output produce less noisy diffs.

With that flag enabled, the following

error: cannot concatenate a byte string literal
  --> $DIR/concat.rs:2:13
   |
 2 |     concat!(b'f');
   |             ^^^^

is turned into

error: cannot concatenate a byte string literal
  --> $DIR/concat.rs:2:13
   |
LL |     concat!(b'f');
   |             ^^^^

Any line number is replaced by LL, no matter the digits of that number.

AFAICT, rustc would currently have to implement its own Snippet -> DisplayList conversion, which would duplicate a lot of the conversion code just to anonymize the line numbers.

I think this would be nice to have inside annotate-snippets too, maybe as an option when constructing a Snippet? I believe other users of this library could also benefit from this option.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.