Vendor latest annotate-snippets (#27033)

## Summary

This is a first step towards #20411. After this, we would work to either
upstream changes or adjust things within Ruff.

Performance improvements have already been upstreamed
(https://github.com/rust-lang/annotate-snippets-rs/pull/437,
https://github.com/rust-lang/annotate-snippets-rs/pull/440)

Re-implemented the following from the fork:
- Hidden snippets
(https://github.com/rust-lang/annotate-snippets-rs/issues/448)
- Renderer-level disabling of hyperlinks
- Jupyter cell indices
(https://github.com/rust-lang/annotate-snippets-rs/issues/449)
- Fixable mark
(https://github.com/rust-lang/annotate-snippets-rs/issues/450)
- `lineno_offset` (maybe not needed if #20648 is taken care of)
- anonymized lines affects origin
(https://github.com/rust-lang/annotate-snippets-rs/issues/451)
- cut indicator (maybe not needed if we can switch to unicode renderer)

Additional hacks to minimize behavior change include
- Adjust upstream's hidden severity behavior to match Ruff's
(https://github.com/rust-lang/annotate-snippets-rs/issues/452)
- How newlines are annotated
(https://github.com/rust-lang/annotate-snippets-rs/issues/454)
- To not indent for line numbers when none will be present
(https://github.com/rust-lang/annotate-snippets-rs/issues/453)
- Enabling `simd` by default

All of the above represents work that needs to be resolved to address
#20411.

`lib.name` hack is used to simplify maintenance with the assumption that
we'll continue working towards #20411.

Behavior changes that were included
- Tweak when implicit padding lines are present
- Fix Origin's line/column is now derived from the primary annotation
- Make consistent the rendering of primary/context annotations
- Fix snippets to show up for empty source (but may degrade #24528)
- Fix handling of term width
- Fix rendering of annotations between newline and first character
(granted, maybe the caller is wrong)
- Tweak multiline annotation markers

New or upcoming features annotate-snippets features that we will be able
to leverage by moving this forward
- `AnnotationKind::Visible` to improve the quality of built-in
code-folding if Ruff were to switch to it
- `DecorStyle::Unicode`
- *(upcoming)* No-graphiscs mode for screen readers (may also be useful
for AIs)

Deferred:
- Moving related `Group`s into the same `Report`
- Using `secondary_title` for any `Group` after the first in a `Report`
- Using a `Snippet`, instead of titles, for "virtual code" like
https://github.com/astral-sh/ruff/pull/27033#discussion_r3622727236
- Switching hidden snippets to `Origin`

## Test Plan

Within cpython, I ran
```console
$ hyperfine -i "../../ruff/target-baseline/release/ruff check" "../../ruff/target-update/release/ruff check" -N -m20 --output=null
Benchmark 1: ../../ruff/target-baseline/release/ruff check
  Time (mean ± σ):      27.5 ms ±   1.1 ms    [User: 33.0 ms, System: 108.4 ms]
  Range (min … max):    25.0 ms …  31.7 ms    94 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: ../../ruff/target-update/release/ruff check
  Time (mean ± σ):      28.1 ms ±   2.0 ms    [User: 33.4 ms, System: 105.7 ms]
  Range (min … max):    24.8 ms …  38.9 ms    102 runs

  Warning: Ignoring non-zero exit code.
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use t
he '--warmup' or '--prepare' options.

Summary
  ../../ruff/target-baseline/release/ruff check ran
    1.03 ± 0.08 times faster than ../../ruff/target-update/release/ruff check
```

Before:
<img width="486" height="583" alt="image"
src="https://github.com/user-attachments/assets/2d94dabf-b28d-48c1-b17c-52aac59fd57e"
/>

After:
<img width="486" height="583" alt="image"
src="https://github.com/user-attachments/assets/b388ada3-e3d7-4ba0-b90b-1330eb81f4be"
/>
This commit is contained in:
Ed Page authored and GitHub committed 2026-07-28 11:57:09 -05:00
1 parent 154d1c10ad
commit 958efb1ff4
1758 files changed
+20493 -10255

No files matched your search

Generated
+1 -55
View File
@@ -948,7 +948,7 @@ checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82"
dependencies = [
"camino",
"fancy-regex",
"libtest-mimic 0.8.1",
"libtest-mimic",
"walkdir",
]
@@ -1968,18 +1968,6 @@ dependencies = [
"libc",
]
[[package]]
name = "libtest-mimic"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58"
dependencies = [
"clap",
"escape8259",
"termcolor",
"threadpool",
]
[[package]]
name = "libtest-mimic"
version = "0.8.1"
@@ -2286,16 +2274,6 @@ dependencies = [
"libm",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.3"
@@ -3146,10 +3124,7 @@ dependencies = [
"anstyle",
"memchr",
"ruff_annotate_snippets",
"serde",
"snapbox",
"toml 1.1.3+spec-1.1.0",
"tryfn",
"unicode-width",
]
@@ -4255,15 +4230,6 @@ dependencies = [
"windows-sys 0.61.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.4.3"
@@ -4383,15 +4349,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "tikv-jemalloc-sys"
version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
@@ -4608,17 +4565,6 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "tryfn"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f68b00518dd6c69ee2289900b140e55dad068cb925678603bfa8d539f61ef6c1"
dependencies = [
"ignore",
"libtest-mimic 0.7.3",
"snapbox",
]
[[package]]
name = "ty"
version = "0.0.0"
-1
View File
@@ -202,7 +202,6 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
"ansi",
"smallvec",
] }
tryfn = { version = "1.0.0" }
typed-arena = { version = "2.0.2" }
unicode-ident = { version = "1.0.12" }
unicode-normalization = { version = "0.1.23" }
-1
View File
@@ -1354,7 +1354,6 @@ mod tests {
|
1 | 1
| ^
|
panic: Panicked at <location> when checking `test.py`: `Test panic for FormatCommandError`
--> test.py:1:1
+9 -11
View File
@@ -56,14 +56,14 @@ fn default_files() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> bar.py:1:1
--> bar.py:1:7
|
- bar = "needs formatting"
1 + bar = "needs formatting"
|
unformatted: File would be reformatted
--> foo.py:1:1
--> foo.py:1:7
|
- foo = "needs formatting"
1 + foo = "needs formatting"
@@ -520,7 +520,7 @@ exclude = ["format_excluded.py"]
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> main.py:1:1
--> main.py:1:3
|
- x = 1
1 + x = 1
@@ -548,7 +548,7 @@ fn deduplicate_directory_and_explicit_file() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> main.py:1:1
--> main.py:1:3
|
- x = 1
1 + x = 1
@@ -584,7 +584,6 @@ from module import =
|
2 | from module import =
| ^
|
----- stderr -----
@@ -700,7 +699,7 @@ fn output_format_notebook() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> CRATE_ROOT/resources/test/fixtures/unformatted.ipynb:cell 1:1:1
--> CRATE_ROOT/resources/test/fixtures/unformatted.ipynb:cell 1:2:1
::: cell 1
|
1 | import numpy
@@ -835,7 +834,7 @@ fn check_quiet_mode_shows_diagnostics_only() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> main.py:1:1
--> main.py:1:5
|
- def foo():
- pass
@@ -859,7 +858,7 @@ fn check_default_mode_shows_diagnostics_and_summary() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> main.py:1:1
--> main.py:1:5
|
- def foo():
- pass
@@ -2052,7 +2051,6 @@ fn syntax_error_in_notebooks_check() -> Result<()> {
2 | # выберите случайный индекс в диапазон от 0 до len(X)-1 включительно при помощи функции random.randint
3 | j = # ваш код здесь
| ^
|
----- stderr -----
@@ -2557,7 +2555,7 @@ fn markdown_formatting() -> Result<()> {
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> CRATE_ROOT/resources/test/fixtures/unformatted.md:1:1
--> CRATE_ROOT/resources/test/fixtures/unformatted.md:4:7
|
3 | ```py
- print( "hello" )
@@ -2694,7 +2692,7 @@ print( 'hello' )
exit_code: 1
----- stdout -----
unformatted: File would be reformatted
--> test.bar:1:1
--> test.bar:5:7
|
4 | ```py
- print( 'hello' )
-2
View File
@@ -4475,7 +4475,6 @@ fn show_fixes_in_full_output_with_preview_enabled() {
|
1 | import math
| ^^^^
|
help: Remove unused import: `math`
|
- import math
@@ -5195,7 +5194,6 @@ fn ruff_toml_is_linted() -> Result<()> {
|
1 | lint.select = ["F401"]
| ^^^^
|
help: Replace rule code with `unused-import`
|
- lint.select = ["F401"]
-23
View File
@@ -120,7 +120,6 @@ fn stdin_error() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -148,7 +147,6 @@ fn stdin_filename() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -187,7 +185,6 @@ import bar # unused import
|
2 | import bar # unused import
| ^^^
|
help: Remove unused import: `bar`
|
1 |
@@ -199,7 +196,6 @@ import bar # unused import
|
2 | import foo # unused import
| ^^^
|
help: Remove unused import: `foo`
|
1 |
@@ -231,7 +227,6 @@ fn check_warn_stdin_filename_with_files() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -261,7 +256,6 @@ fn stdin_source_type_py() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -502,7 +496,6 @@ fn stdin_fix_jupyter() {
|
1 | print(x)
| ^
|
Found 3 errors (2 fixed, 1 remaining).
"#);
@@ -601,7 +594,6 @@ fn stdin_override_parser_ipynb() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
::: cell 1
|
@@ -613,7 +605,6 @@ fn stdin_override_parser_ipynb() {
|
1 | import sys
| ^^^
|
help: Remove unused import: `sys`
::: cell 3
|
@@ -647,7 +638,6 @@ fn stdin_override_parser_py() {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -685,7 +675,6 @@ extension = {ipynb="python"}
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -884,7 +873,6 @@ fn stdin_parse_error() {
|
1 | from foo import
| ^
|
Found 1 error.
@@ -914,7 +902,6 @@ fn stdin_multiple_parse_error() {
1 | from foo import
2 | bar =
| ^
|
Found 2 errors.
@@ -936,7 +923,6 @@ fn parse_error_not_included() {
|
1 | foo =
| ^
|
Found 1 error.
@@ -959,7 +945,6 @@ fn full_output_preview() {
|
1 | l = 1
| ^
|
Found 1 error.
@@ -988,7 +973,6 @@ preview = true
|
1 | l = 1
| ^
|
Found 1 error.
@@ -1013,7 +997,6 @@ fn full_output_format() {
|
1 | l = 1
| ^
|
Found 1 error.
@@ -1870,7 +1853,6 @@ fn check_input_from_argfile() -> Result<()> {
|
1 | import os
| ^^
|
help: Remove unused import: `os`
|
- import os
@@ -2502,7 +2484,6 @@ select = ["RUF017"]
2 | y = [4, 5, 6]
3 | sum([x, y], [])
| ^^^^^^^^^^^^^^^
|
help: Replace with `functools.reduce`
Found 1 error.
@@ -2543,7 +2524,6 @@ unfixable = ["RUF"]
2 | y = [4, 5, 6]
3 | sum([x, y], [])
| ^^^^^^^^^^^^^^^
|
help: Replace with `functools.reduce`
Found 1 error.
@@ -2571,7 +2551,6 @@ fn pyproject_toml_stdin_syntax_error() {
|
1 | [project
| ^
|
Found 1 error.
@@ -2598,7 +2577,6 @@ fn pyproject_toml_stdin_schema_error() {
1 | [project]
2 | name = 1
| ^
|
Found 1 error.
@@ -2691,7 +2669,6 @@ fn pyproject_toml_stdin_schema_error_fix() {
1 | [project]
2 | name = 1
| ^
|
Found 1 error.
"
+75 -12
View File
@@ -12,28 +12,91 @@ license = "MIT OR Apache-2.0"
[dependencies]
anstyle = { workspace = true }
memchr = { workspace = true }
memchr = { workspace = true, optional = true }
unicode-width = { workspace = true }
[dev-dependencies]
ruff_annotate_snippets = { path = ".", features = ["testing-colors"] }
anstream = { workspace = true }
serde = { workspace = true, features = ["derive"] }
snapbox = { workspace = true, features = ["diff", "term-svg", "cmd", "examples"] }
toml = { workspace = true }
tryfn = { workspace = true }
snapbox = { workspace = true }
[features]
default = []
default = ["std", "simd"]
std = ["anstyle/std", "memchr?/std"]
simd = ["dep:memchr"]
testing-colors = []
[[test]]
name = "fixtures"
harness = false
# Using upstream lints
[lints.rust]
rust_2018_idioms = { level = "warn", priority = -1 }
unnameable_types = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
[lints]
workspace = true
[lints.clippy]
disallowed_methods = "allow" # HACK: minimize changes from upstream
bool_assert_comparison = "allow"
branches_sharing_code = "allow"
checked_conversions = "warn"
collapsible_else_if = "allow"
create_dir = "warn"
dbg_macro = "warn"
debug_assert_with_mut_call = "warn"
doc_markdown = "warn"
empty_enums = "warn"
enum_glob_use = "warn"
expl_impl_clone_on_copy = "warn"
explicit_deref_methods = "warn"
explicit_into_iter_loop = "warn"
fallible_impl_from = "warn"
filter_map_next = "warn"
flat_map_option = "warn"
float_cmp_const = "warn"
fn_params_excessive_bools = "warn"
from_iter_instead_of_collect = "warn"
if_same_then_else = "allow"
implicit_clone = "warn"
imprecise_flops = "warn"
inconsistent_struct_constructor = "warn"
inefficient_to_string = "warn"
infinite_loop = "warn"
invalid_upcast_comparisons = "warn"
large_digit_groups = "warn"
large_stack_arrays = "warn"
large_types_passed_by_value = "warn"
let_and_return = "allow" # sometimes good to name what you are returning
linkedlist = "warn"
lossy_float_literal = "warn"
macro_use_imports = "warn"
mem_forget = "warn"
mutex_integer = "warn"
needless_continue = "allow"
needless_for_each = "warn"
negative_feature_names = "warn"
path_buf_push_overwrite = "warn"
ptr_as_ptr = "warn"
rc_mutex = "warn"
redundant_feature_names = "warn"
ref_option_ref = "warn"
rest_pat_in_fully_bound_structs = "warn"
result_large_err = "allow"
same_functions_in_if_condition = "warn"
self_named_module_files = "warn"
semicolon_if_nothing_returned = "warn"
str_to_string = "warn"
string_add = "warn"
string_add_assign = "warn"
string_lit_as_bytes = "warn"
todo = "warn"
trait_duplication_in_bounds = "warn"
uninlined_format_args = "warn"
verbose_file_reads = "warn"
wildcard_imports = "warn"
zero_sized_map_values = "warn"
[lib]
name = "annotate_snippets"
test = false
@@ -0,0 +1,32 @@
use annotate_snippets::renderer::DecorStyle;
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet};
fn main() {
let source = r#"//@ compile-flags: -Ztreat-err-as-bug
//@ failure-status: 101
//@ error-pattern: aborting due to `-Z treat-err-as-bug=1`
//@ error-pattern: [eval_static_initializer] evaluating initializer of static `C`
//@ normalize-stderr: "note: .*\n\n" -> ""
//@ normalize-stderr: "thread 'rustc' panicked.*:\n.*\n" -> ""
//@ rustc-env:RUST_BACKTRACE=0
#![crate_type = "rlib"]
pub static C: u32 = 0 - 1;
//~^ ERROR could not evaluate static initializer
"#;
let report = &[Level::ERROR
.with_name(Some("error: internal compiler error"))
.primary_title("could not evaluate static initializer")
.id("E0080")
.element(
Snippet::source(source).path("$DIR/err.rs").annotation(
AnnotationKind::Primary
.span(386..391)
.label("attempt to compute `0_u32 - 1_u32`, which would overflow"),
),
)];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="751px" height="128px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,17 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error: internal compiler error[E0080]</tspan><tspan class="bold">: could not evaluate static initializer</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:LL:193</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/err.rs:11:21</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42;</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub static C: u32 = 0 - 1;</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">attempt to compute `0_u32 - 1_u32`, which would overflow</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="118px">
</tspan>
</text>
@@ -0,0 +1,64 @@
use annotate_snippets::renderer::DecorStyle;
use annotate_snippets::{AnnotationKind, Level, Patch, Renderer, Snippet};
fn main() {
let source = r#"// Regression test for issue #114529
// Tests that we do not ICE during const eval for a
// break-with-value in contexts where it is illegal
#[allow(while_true)]
fn main() {
[(); {
while true {
break 9; //~ ERROR `break` with value from a `while` loop
};
51
}];
[(); {
while let Some(v) = Some(9) {
break v; //~ ERROR `break` with value from a `while` loop
};
51
}];
while true {
break (|| { //~ ERROR `break` with value from a `while` loop
let local = 9;
});
}
}
"#;
let report = &[
Level::ERROR
.primary_title("`break` with value from a `while` loop")
.id("E0571")
.element(
Snippet::source(source)
.line_start(1)
.path("$DIR/issue-114529-illegal-break-with-value.rs")
.annotation(
AnnotationKind::Primary
.span(483..581)
.label("can only break with a value inside `loop` or breakable block"),
)
.annotation(
AnnotationKind::Context
.span(462..472)
.label("you can't `break` with a value in a `while` loop"),
),
),
Level::HELP
.with_name(Some("suggestion"))
.secondary_title("use `break` on its own without a value inside this `while` loop")
.element(
Snippet::source(source)
.line_start(1)
.path("$DIR/issue-114529-illegal-break-with-value.rs")
.patch(Patch::new(483..581, "break")),
),
];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,62 @@
<svg width="740px" height="344px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0571]</tspan><tspan class="bold">: `break` with value from a `while` loop</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/issue-114529-illegal-break-with-value.rs:22:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> while true {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">you can't `break` with a value in a `while` loop</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏</tspan><tspan> break (|| { //~ ERROR `break` with value from a `while` loop</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> let local = 9;</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> });</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┗━━━━━━━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">can only break with a value inside `loop` or breakable block</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">suggestion</tspan><tspan>: use `break` on its own without a value inside this `while` loop</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">break (|| { //~ ERROR `break` with value from a `while` loop</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- let local = 9;</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-red">- })</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> </tspan><tspan class="fg-bright-green">break</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="334px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,21 @@
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#"# Docstring followed by a newline
def foobar(door, bar={}):
"""
"""
"#;
let report = &[Group::with_level(Level::NOTE)
.element(
Snippet::source(source)
.fold(false)
.annotation(AnnotationKind::Primary.span(56..58).label("B006")),
)
.element(Level::HELP.message("Replace with `None`; initialize within function"))];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,44 @@
<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-green { fill: #55FF55 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> # Docstring followed by a newline</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> def foobar(door, bar={}):</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-green bold">━━</tspan><tspan> </tspan><tspan class="fg-bright-green bold">B006</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> """</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">5</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> """</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">help</tspan><tspan>: Replace with `None`; initialize within function</tspan>
</tspan>
<tspan x="10px" y="190px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -1,23 +1,27 @@
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#" annotations: vec![SourceAnnotation {
label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
,
range: <22, 25>,"#;
let message = Level::Error.title("expected type, found `22`").snippet(
Snippet::source(source)
.line_start(26)
.origin("examples/footer.rs")
.fold(true)
.annotation(
Level::Error
.span(193..195)
.label("expected struct `annotate_snippets::snippet::Slice`, found reference"),
)
.annotation(Level::Info.span(34..50).label("while parsing this struct")),
);
let report =
&[Level::ERROR
.primary_title("expected type, found `22`")
.element(
Snippet::source(source)
.line_start(26)
.path("examples/footer.rs")
.annotation(AnnotationKind::Primary.span(193..195).label(
"expected struct `annotate_snippets::snippet::Slice`, found reference",
))
.annotation(
AnnotationKind::Context
.span(34..50)
.label("while parsing this struct"),
),
)];
let renderer = Renderer::styled();
anstream::println!("{}", renderer.render(message));
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -1,7 +1,7 @@
<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg">
<svg width="860px" height="182px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
@@ -19,27 +19,23 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected type, found `22`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> examples/footer.rs:29:25</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>examples/footer.rs:29:25</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> annotations: vec![SourceAnnotation {</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> annotations: vec![SourceAnnotation {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">────────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">while parsing this struct</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 x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> ,</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> range: &lt;22, 25&gt;,</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: &lt;22, 25&gt;,</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </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="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </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> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="208px">
<tspan x="10px" y="172px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -1,22 +1,23 @@
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let message =
Level::Error
.title("mismatched types")
let report = &[
Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.snippet(
.element(
Snippet::source(" slices: vec![\"A\",")
.line_start(13)
.origin("src/multislice.rs")
.annotation(Level::Error.span(21..24).label(
.path("src/multislice.rs")
.annotation(AnnotationKind::Primary.span(21..24).label(
"expected struct `annotate_snippets::snippet::Slice`, found reference",
)),
)
.footer(Level::Note.title(
"expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`",
));
),
Group::with_title(Level::NOTE.secondary_title(
"expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`",
)),
];
let renderer = Renderer::styled();
anstream::println!("{}", renderer.render(message));
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -1,7 +1,7 @@
<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
@@ -20,21 +20,21 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/multislice.rs:13:22</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>src/multislice.rs:13:22</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan> slices: vec!["A",</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> slices: vec!["A",</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </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="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </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="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> found type: `__&amp;__snippet::Annotation`</tspan>
<tspan x="10px" y="154px"><tspan> found type: `__&amp;__snippet::Annotation`</tspan>
</tspan>
<tspan x="10px" y="172px">
</tspan>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,4 +1,4 @@
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#") -> Option<String> {
@@ -23,22 +23,26 @@ fn main() {
_ => continue,
}
}"#;
let message = Level::Error.title("mismatched types").id("E0308").snippet(
Snippet::source(source)
.line_start(51)
.origin("src/format.rs")
.annotation(
Level::Warning
.span(5..19)
.label("expected `Option<String>` because of return type"),
)
.annotation(
Level::Error
.span(26..724)
.label("expected enum `std::option::Option`"),
),
);
let report = &[Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.element(
Snippet::source(source)
.line_start(51)
.path("src/format.rs")
.fold(false)
.annotation(
AnnotationKind::Context
.span(5..19)
.label("expected `Option<String>` because of return type"),
)
.annotation(
AnnotationKind::Primary
.span(26..724)
.label("expected enum `std::option::Option`"),
),
)];
let renderer = Renderer::styled();
anstream::println!("{}", renderer.render(message));
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -1,10 +1,9 @@
<svg width="740px" height="542px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="524px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
@@ -20,63 +19,61 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>src/format.rs:52:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -&gt; Option&lt;String&gt; {</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> ) -&gt; Option&lt;String&gt; {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option&lt;String&gt;` because of return type</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `Option&lt;String&gt;` because of return type</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏</tspan><tspan> for ann in annotations {</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) =&gt; continue,</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> (None, None) =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> (Some(start), Some(end)) if start &gt; end_index =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> (Some(start), Some(end)) if start &gt;= start_index =&gt; {</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan>
<tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> format!(" {}", label)</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> } else {</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan>
<tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> String::from("")</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan>
<tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> };</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
<tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> return Some(format!(</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan>
<tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> "{}{}{}",</tspan>
</tspan>
<tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan>
<tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> " ".repeat(start - start_index),</tspan>
</tspan>
<tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan>
<tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> "^".repeat(end - start),</tspan>
</tspan>
<tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan>
<tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> label</tspan>
</tspan>
<tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan>
<tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> ));</tspan>
</tspan>
<tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
<tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> }</tspan>
</tspan>
<tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ =&gt; continue,</tspan>
<tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> _ =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
<tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> }</tspan>
</tspan>
<tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
<tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> }</tspan>
</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">┗━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan>
</tspan>
<tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="532px">
<tspan x="10px" y="514px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -0,0 +1,67 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use anstyle::AnsiColor;
use anstyle::Effects;
use anstyle::Style;
fn main() {
let source = r#"// Make sure "highlighted" code is colored purple
//@ compile-flags: --error-format=human --color=always
//@ edition:2018
use core::pin::Pin;
use core::future::Future;
use core::any::Any;
fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(
dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
)>>) {}
fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<(
dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static
)>> {
Box::pin(async { Err("nope".into()) })
}
fn main() {
query(wrapped_fn);
}"#;
const MAGENTA: Style = AnsiColor::Magenta.on_default().effects(Effects::BOLD);
let message = format!(
"expected fn pointer `{MAGENTA}for<'a>{MAGENTA:#} fn(Box<{MAGENTA}(dyn Any + Send + 'a){MAGENTA:#}>) -> Pin<_>`
found fn item `fn(Box<{MAGENTA}(dyn Any + Send + 'static){MAGENTA:#}>) -> Pin<_> {MAGENTA}{{wrapped_fn}}{MAGENTA:#}`",
);
let report = &[
Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.element(
Snippet::source(source)
.path("$DIR/highlighting.rs")
.annotation(
AnnotationKind::Primary
.span(553..563)
.label("one type is more general than the other"),
)
.annotation(
AnnotationKind::Context
.span(547..552)
.label("arguments to this function are incorrect"),
),
)
.element(Level::NOTE.message(&message)),
Level::NOTE
.secondary_title("function defined here")
.element(
Snippet::source(source)
.path("$DIR/highlighting.rs")
.annotation(AnnotationKind::Context.span(200..333).label(""))
.annotation(AnnotationKind::Primary.span(194..199)),
),
];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,64 @@
<svg width="768px" height="362px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.fg-magenta { fill: #AA00AA }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/highlighting.rs:21:11</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> query(wrapped_fn);</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">┬────</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">one type is more general than the other</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `</tspan><tspan class="fg-magenta bold">for&lt;'a&gt;</tspan><tspan> fn(Box&lt;</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'a)</tspan><tspan>&gt;) -&gt; Pin&lt;_&gt;`</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> found fn item `fn(Box&lt;</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>&gt;) -&gt; Pin&lt;_&gt; </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-green bold">note</tspan><tspan>: function defined here</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/highlighting.rs:10:4</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">10</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> fn query(_: fn(Box&lt;(dyn Any + Send + '_)&gt;) -&gt; Pin&lt;Box&lt;(</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">┌────</tspan><tspan class="fg-bright-green bold">━━━━━</tspan><tspan class="fg-bright-blue bold">─┘</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> dyn Future&lt;Output = Result&lt;Box&lt;(dyn Any + 'static)&gt;, String&gt;&gt; + Send + 'static</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">12</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> )&gt;&gt;) {}</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴└───┘</tspan>
</tspan>
<tspan x="10px" y="352px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,31 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#"//@ compile-flags: -Z teach
#![allow(warnings)]
const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010
//~| ERROR cannot call non-const method
fn main() {}
"#;
let report = &[Level::ERROR.primary_title("allocations are not allowed in constants")
.id("E0010")
.element(
Snippet::source(source)
.path("$DIR/E0010-teach.rs")
.annotation(
AnnotationKind::Primary
.span(72..85)
.label("allocation not allowed in constants")
.highlight_source(true),
),
)
.element(
Level::NOTE.message("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."),
)];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,40 @@
<svg width="953px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0010]</tspan><tspan class="bold">: allocations are not allowed in constants</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/E0010-teach.rs:5:23</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">5</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> const CON: Vec&lt;i32&gt; = </tspan><tspan class="fg-bright-red bold">vec![1, 2, 3]</tspan><tspan>; //~ ERROR E0010</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">note</tspan><tspan>: The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created.</tspan>
</tspan>
<tspan x="10px" y="154px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,32 @@
use annotate_snippets::renderer::DecorStyle;
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet};
fn main() {
let source = r#"//@ compile-flags: -Zterminal-urls=yes
fn main() {
let () = 4; //~ ERROR
}
"#;
let report = &[Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.id_url("https://doc.rust-lang.org/error_codes/E0308.html")
.element(
Snippet::source(source)
.line_start(1)
.path("$DIR/terminal_urls.rs")
.annotation(
AnnotationKind::Primary
.span(59..61)
.label("expected integer, found `()`"),
)
.annotation(
AnnotationKind::Context
.span(64..65)
.label("this expression has type `{integer}`"),
),
)];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,40 @@
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[</tspan><tspan class="fg-bright-red bold"><a href="https://doc.rust-lang.org/error_codes/E0308.html">E0308</a></tspan><tspan class="fg-bright-red bold">]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/terminal_urls.rs:3:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let () = 4; //~ ERROR</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┯━</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">─</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">this expression has type `{integer}`</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected integer, found `()`</tspan>
</tspan>
<tspan x="10px" y="154px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,75 @@
use annotate_snippets::{AnnotationKind, Level, Patch, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#"
#![allow(dead_code)]
struct U <T> {
wtf: Option<Box<U<T>>>,
x: T,
}
fn main() {
U {
wtf: Some(Box(U {
wtf: None,
x: (),
})),
x: ()
};
let _ = std::collections::HashMap();
let _ = std::collections::HashMap {};
let _ = Box {};
}
"#;
let report = &[
Level::ERROR
.primary_title(
"cannot construct `Box<_, _>` with struct literal syntax due to private fields",
)
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.annotation(AnnotationKind::Primary.span(295..298)),
)
.element(Level::NOTE.message("private fields `0` and `1` that were not provided")),
Level::HELP
.secondary_title(
"you might have meant to use an associated function to build this type",
)
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.patch(Patch::new(298..301, "::new(_)")),
)
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.patch(Patch::new(298..301, "::new_uninit()")),
)
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.patch(Patch::new(298..301, "::new_zeroed()")),
)
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.patch(Patch::new(298..301, "::new_in(_, _)")),
)
.element(Level::NOTE.no_name().message("and 12 other candidates")),
Level::HELP
.secondary_title("consider using the `Default` trait")
.element(
Snippet::source(source)
.path("$DIR/multi-suggestion.rs")
.patch(Patch::new(295..295, "<"))
.patch(Patch::new(
298..301,
" as std::default::Default>::default()",
)),
),
];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,82 @@
<svg width="740px" height="524px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: cannot construct `Box&lt;_, _&gt;` with struct literal syntax due to private fields</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/multi-suggestion.rs:17:13</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let _ = Box {};</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">note</tspan><tspan>: private fields `0` and `1` that were not provided</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: you might have meant to use an associated function to build this type</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-red"> {}</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-green">::new(_)</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">├╴</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-red"> {}</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-green">::new_uninit()</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">├╴</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-red"> {}</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-green">::new_zeroed()</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-bright-blue bold">├╴</tspan>
</tspan>
<tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-red"> {}</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-green">::new_in(_, _)</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="388px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="406px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan>and 12 other candidates</tspan>
</tspan>
<tspan x="10px" y="424px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: consider using the `Default` trait</tspan>
</tspan>
<tspan x="10px" y="442px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let _ = Box</tspan><tspan class="fg-bright-red"> {}</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> let _ = </tspan><tspan class="fg-bright-green">&lt;</tspan><tspan>Box</tspan><tspan class="fg-bright-green"> as std::default::Default&gt;::default()</tspan><tspan>;</tspan>
</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="514px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -1,19 +1,21 @@
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use annotate_snippets::{Annotation, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let message = Level::Error
.title("mismatched types")
.snippet(
Snippet::source("Foo")
let report = &[Level::ERROR
.primary_title("mismatched types")
.element(
Snippet::<Annotation<'_>>::source("Foo")
.line_start(51)
.origin("src/format.rs"),
.fold(false)
.path("src/format.rs"),
)
.snippet(
Snippet::source("Faa")
.element(
Snippet::<Annotation<'_>>::source("Faa")
.line_start(129)
.origin("src/display.rs"),
);
.fold(false)
.path("src/display.rs"),
)];
let renderer = Renderer::styled();
anstream::println!("{}", renderer.render(message));
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -1,7 +1,7 @@
<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
@@ -19,23 +19,23 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>src/format.rs</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> Foo</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ⸬ </tspan><tspan>src/display.rs:129</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> Faa</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="190px">
</tspan>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,27 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
fn main() {
let source = r#"struct S {
field1: usize,
field2: usize,
field3: usize,
field4: usize,
fn foo() {},
field6: usize,
}
"#;
let report = &[Level::ERROR
.primary_title("functions are not allowed in struct definitions")
.element(
Snippet::source(source)
.path("$DIR/struct_name_as_context.rs")
.annotation(AnnotationKind::Primary.span(91..102))
.annotation(AnnotationKind::Visible.span(0..8)),
)
.element(
Level::HELP
.message("unlike in C++, Java, and C#, functions are declared in `impl` blocks"),
)];
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
anstream::println!("{}", renderer.render(report));
}
@@ -0,0 +1,44 @@
<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: functions are not allowed in struct definitions</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/struct_name_as_context.rs:6:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> struct S {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">6</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> fn foo() {},</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">help</tspan><tspan>: unlike in C++, Java, and C#, functions are declared in `impl` blocks</tspan>
</tspan>
<tspan x="10px" y="190px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+235
View File
@@ -0,0 +1,235 @@
//! [`Level`] constants for easy importing
use alloc::borrow::Cow;
use anstyle::Style;
use crate::renderer::stylesheet::Stylesheet;
use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT};
use crate::{Message, OptionCow, Title};
/// Default `error:` [`Level`]
pub const ERROR: Level<'_> = Level {
name: None,
level: LevelInner::Error,
};
/// Default `warning:` [`Level`]
pub const WARNING: Level<'_> = Level {
name: None,
level: LevelInner::Warning,
};
/// Default `info:` [`Level`]
pub const INFO: Level<'_> = Level {
name: None,
level: LevelInner::Info,
};
/// Default `note:` [`Level`]
pub const NOTE: Level<'_> = Level {
name: None,
level: LevelInner::Note,
};
/// Default `help:` [`Level`]
pub const HELP: Level<'_> = Level {
name: None,
level: LevelInner::Help,
};
/// Severity level for [`Title`]s and [`Message`]s
///
/// # Example
///
/// ```rust
/// # use annotate_snippets::*;
/// let report = &[
/// Level::ERROR.primary_title("mismatched types").id("E0308")
/// .element(Level::NOTE.message("expected reference")),
/// Group::with_title(
/// Level::HELP.secondary_title("function defined here")
/// ),
/// ];
/// ```
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Level<'a> {
pub(crate) name: Option<Option<Cow<'a, str>>>,
pub(crate) level: LevelInner,
}
/// # Constructors
impl<'a> Level<'a> {
pub const ERROR: Level<'a> = ERROR;
pub const WARNING: Level<'a> = WARNING;
pub const INFO: Level<'a> = INFO;
pub const NOTE: Level<'a> = NOTE;
pub const HELP: Level<'a> = HELP;
}
impl<'a> Level<'a> {
/// For the primary, or root cause, [`Group`][crate::Group] (the first) in a [`Report`][crate::Report]
///
/// See [`Group::with_title`][crate::Group::with_title]
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn primary_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> {
Title {
level: self,
id: None,
text: text.into(),
allows_styling: false,
is_fixable: false,
}
}
/// For any secondary, or context, [`Group`][crate::Group]s (subsequent) in a [`Report`][crate::Report]
///
/// See [`Group::with_title`][crate::Group::with_title]
///
/// <div class="warning">
///
/// Text passed to this function is allowed to be styled, as such all
/// text is considered "trusted input" and has no normalizations applied to
/// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
/// used to normalize untrusted text before it is passed to this function.
///
/// </div>
pub fn secondary_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> {
Title {
level: self,
id: None,
text: text.into(),
allows_styling: true,
is_fixable: false,
}
}
/// A text [`Element`][crate::Element] in a [`Group`][crate::Group]
///
/// <div class="warning">
///
/// Text passed to this function is allowed to be styled, as such all
/// text is considered "trusted input" and has no normalizations applied to
/// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be
/// used to normalize untrusted text before it is passed to this function.
///
/// </div>
pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> {
Message {
level: self,
text: text.into(),
}
}
pub(crate) fn as_str(&'a self) -> &'a str {
match (&self.name, self.level) {
(Some(Some(name)), _) => name.as_ref(),
(Some(None), _) => "",
(None, LevelInner::Error) => ERROR_TXT,
(None, LevelInner::Warning) => WARNING_TXT,
(None, LevelInner::Info) => INFO_TXT,
(None, LevelInner::Note) => NOTE_TXT,
(None, LevelInner::Help) => HELP_TXT,
}
}
pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style {
self.level.style(stylesheet)
}
}
/// # Customize the `Level`
impl<'a> Level<'a> {
/// Replace the name describing this [`Level`]
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
///
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/custom_level.rs")]
/// ```
#[doc = include_str!("../examples/custom_level.svg")]
pub fn with_name(self, name: impl Into<OptionCow<'a>>) -> Level<'a> {
Level {
name: Some(name.into().0),
level: self.level,
}
}
/// Do not show the [`Level`]s name
///
/// Useful for:
/// - Another layer of the application will include the level (e.g. when rendering errors)
/// - [`Message`]s that are part of a previous [`Group`][crate::Group] [`Element`][crate::Element]s
///
/// # Example
///
/// ```rust
/// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level};
///let source = r#"fn main() {
/// let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types
/// let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types
/// }"#;
/// let report = &[
/// Level::ERROR.primary_title("mismatched types").id("E0308")
/// .element(
/// Snippet::source(source)
/// .path("$DIR/mismatched-types.rs")
/// .annotation(
/// AnnotationKind::Primary
/// .span(105..131)
/// .label("expected `&str`, found `&[u8; 0]`"),
/// )
/// .annotation(
/// AnnotationKind::Context
/// .span(98..102)
/// .label("expected due to this"),
/// ),
/// )
/// .element(
/// Level::NOTE
/// .no_name()
/// .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"),
/// ),
/// ];
/// ```
pub fn no_name(self) -> Level<'a> {
self.with_name(None::<&str>)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum LevelInner {
Error,
Warning,
Info,
Note,
Help,
}
impl LevelInner {
pub(crate) fn style(self, stylesheet: &Stylesheet) -> Style {
match self {
LevelInner::Error => stylesheet.error,
LevelInner::Warning => stylesheet.warning,
LevelInner::Info => stylesheet.info,
LevelInner::Note => stylesheet.note,
LevelInner::Help => stylesheet.help,
}
}
}
+97 -36
View File
@@ -1,35 +1,93 @@
//! A library for formatting of text or programming code snippets.
//!
//! It's primary purpose is to build an ASCII-graphical representation of the snippet
//! with annotations.
//! Format [diagnostic reports][Report], including highlighting snippets of text
//!
//! # Example
//!
//! ```rust
//! # #[allow(clippy::needless_doctest_main)]
#![doc = include_str!("../examples/expected_type.rs")]
//! ```
//!
#![doc = include_str!("../examples/expected_type.svg")]
//!
//! The crate uses a three stage process with two conversions between states:
//! # Visual overview
//!
//! [`Report`]
//!
#![doc = include_str!("../examples/multi_suggestion.svg")]
//!
//! ### Primary group
//!
//! [`Title`]
//! ```text
//! Message --> Renderer --> impl Display
//! error: cannot construct `Box<_, _>` with struct literal syntax due to private fields
//! ```
//!
//! The input type - [Message] is a structure designed
//! to align with likely output from any parser whose code snippet is to be
//! annotated.
//!
//! The middle structure - [Renderer] is a structure designed
//! to convert a snippet into an internal structure that is designed to store
//! the snippet data in a way that is easy to format.
//! [Renderer] also handles the user-configurable formatting
//! options, such as color, or margins.
//! [`Annotation`] on a [`Snippet`]
//! ```text
//! ╭▸ $DIR/multi-suggestion.rs:17:13
//! │
//! 17 │ let _ = Box {};
//! │ ━━━
//! │
//! ```
//!
//! Finally, `impl Display` into a final `String` output.
//! [`Message`]
//! ```text
//! ╰ note: private fields `0` and `1` that were not provided
//! ```
//!
//!
//!
//! ### Secondary group: suggested fix
//!
//! [`Title`] (proposed solution)
//! ```text
//! help: you might have meant to use an associated function to build this type
//! ```
//!
//! [`Patch`] Option 1 on a [`Snippet`]
//! ```text
//! ╭╴
//! 21 - let _ = Box {};
//! 21 + let _ = Box::new(_);
//! ├╴
//! ```
//!
//! [`Patch`] Option 2 on a [`Snippet`]
//! ```text
//! ├╴
//! 17 - let _ = Box {};
//! 17 + let _ = Box::new_uninit();
//! ├╴
//! ```
//!
//! *etc for Options 3 and 4*
//!
//! [`Message`]
//! ```text
//! ╰ and 12 other candidates
//! ```
//!
//! ### Secondary group: alternative suggested fix
//!
//! [`Title`] (proposed solution)
//! ```text
//! help: consider using the `Default` trait
//! ```
//!
//! Only [`Patch`] on a [`Snippet`]
//! ```text
//! ╭╴
//! 17 - let _ = Box {};
//! 17 + let _ = <Box as std::default::Default>::default();
//! ╰╴
//! ```
//!
//! # Cargo `features`
//!
//! - `simd` - Speeds up folding
//!
//! # features
//! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which
//! allows for easier testing when testing colored output. It should be added as
//! a feature in `[dev-dependencies]`, which can be done with the following command:
@@ -37,33 +95,36 @@
//! cargo add annotate-snippets --dev --feature testing-colors
//! ```
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
#![warn(clippy::std_instead_of_alloc)]
#![warn(clippy::std_instead_of_core)]
#![warn(missing_debug_implementations)]
// Since this is a vendored copy of `annotate-snippets`, we squash Clippy
// warnings from upstream in order to the reduce the diff. If our copy drifts
// far from upstream such that patches become impractical to apply in both
// places, then we can get rid of these suppressions and fix the lints.
#![allow(
clippy::return_self_not_must_use,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::explicit_iter_loop,
clippy::unused_self,
clippy::unnecessary_wraps,
clippy::range_plus_one,
clippy::redundant_closure_for_method_calls,
clippy::struct_field_names,
clippy::cloned_instead_of_copied,
clippy::cast_sign_loss,
clippy::needless_as_bytes,
clippy::unnecessary_map_or
)]
extern crate alloc;
use alloc::string::String;
pub mod level;
pub mod renderer;
mod snippet;
/// Normalize the string to avoid any unicode control characters.
///
/// This is important for untrusted input, as it can contain
/// invalid unicode sequences.
pub fn normalize_untrusted_str(s: &str) -> String {
renderer::normalize_whitespace(s).into_owned()
}
#[doc(inline)]
pub use level::Level;
#[doc(inline)]
pub use renderer::Renderer;
pub use snippet::*;
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
File diff suppressed because it is too large. Load diff
@@ -1,4 +1,4 @@
use std::cmp::{max, min};
use core::cmp::{max, min};
const ELLIPSIS_PASSING: usize = 6;
const LONG_WHITESPACE: usize = 20;
@@ -17,7 +17,7 @@ pub(crate) struct Margin {
/// The end of the line to be displayed.
computed_right: usize,
/// The current width of the terminal. 140 by default and in tests.
term_width: usize,
pub(crate) term_width: usize,
/// The end column of a span label, including the span. Doesn't account for labels not in the
/// same line as the span.
label_right: usize,
@@ -41,15 +41,9 @@ impl Margin {
// | ^^^^^^^^^
// ```
let whitespace_left = whitespace_left.saturating_sub(ELLIPSIS_PASSING);
let span_left = span_left.saturating_sub(ELLIPSIS_PASSING);
let mut m = Margin {
// When an annotation points at leading whitespace (e.g. an indentation error),
// `whitespace_left` can exceed `span_left`. Clamp it so that trimming whitespace
// never hides the leftmost annotation.
whitespace_left: min(whitespace_left, span_left),
span_left,
whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING),
span_left: span_left.saturating_sub(ELLIPSIS_PASSING),
span_right: span_right + ELLIPSIS_PASSING,
computed_left: 0,
computed_right: 0,
@@ -77,7 +71,12 @@ impl Margin {
if self.computed_right - self.computed_left > self.term_width {
// Trimming only whitespace isn't enough, let's get craftier.
if self.label_right - self.whitespace_left <= self.term_width {
if self.label_right.saturating_sub(self.whitespace_left) <= self.term_width
// Trimming whitespace when the right-most label is somewhrere
// within it would result in the label pointing to the wrong
// place
&& self.label_right >= self.whitespace_left
{
// Attempt to fit the code window only trimming whitespace.
self.computed_left = self.whitespace_left;
self.computed_right = self.computed_left + self.term_width;
+340 -75
View File
@@ -1,37 +1,118 @@
//! The renderer for [`Message`]s
//! The [Renderer] and its settings
//!
//! # Example
//! ```
//! use ruff_annotate_snippets::{Renderer, Snippet, Level};
//! let snippet = Level::Error.title("mismatched types")
//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
//!
//! let renderer = Renderer::styled();
//! println!("{}", renderer.render(snippet));
//! ```
//! # use annotate_snippets::*;
//! # use annotate_snippets::renderer::*;
//! # use annotate_snippets::Level;
//! let report = // ...
//! # &[Group::with_title(
//! # Level::ERROR
//! # .primary_title("unresolved import `baz::zed`")
//! # .id("E0432")
//! # )];
//!
//! let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
//! let output = renderer.render(report);
//! anstream::println!("{output}");
//! ```
mod display_list;
mod margin;
mod styled_buffer;
pub(crate) mod render;
pub(crate) mod source_map;
pub(crate) mod stylesheet;
use crate::snippet::Message;
pub use anstyle::*;
use display_list::DisplayList;
use margin::Margin;
use std::fmt::Display;
use stylesheet::Stylesheet;
mod margin;
mod styled_buffer;
use alloc::string::String;
use crate::Report;
pub(crate) use render::ElementStyle;
pub(crate) use render::UnderlineParts;
pub(crate) use render::normalize_whitespace;
pub(crate) use render::{LineAnnotation, LineAnnotationType, char_width, num_overlap};
pub(crate) use stylesheet::Stylesheet;
pub use anstyle::*;
/// See [`Renderer::term_width`]
pub const DEFAULT_TERM_WIDTH: usize = 140;
/// A renderer for [`Message`]s
const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
AnsiColor::BrightCyan.on_default()
} else {
AnsiColor::BrightBlue.on_default()
};
/// [`Renderer::error`] applied by [`Renderer::styled`]
pub const DEFAULT_ERROR_STYLE: Style = AnsiColor::BrightRed.on_default().effects(Effects::BOLD);
/// [`Renderer::warning`] applied by [`Renderer::styled`]
pub const DEFAULT_WARNING_STYLE: Style = if USE_WINDOWS_COLORS {
AnsiColor::BrightYellow.on_default()
} else {
AnsiColor::Yellow.on_default()
}
.effects(Effects::BOLD);
/// [`Renderer::info`] applied by [`Renderer::styled`]
pub const DEFAULT_INFO_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
/// [`Renderer::note`] applied by [`Renderer::styled`]
pub const DEFAULT_NOTE_STYLE: Style = AnsiColor::BrightGreen.on_default().effects(Effects::BOLD);
/// [`Renderer::help`] applied by [`Renderer::styled`]
pub const DEFAULT_HELP_STYLE: Style = AnsiColor::BrightCyan.on_default().effects(Effects::BOLD);
/// [`Renderer::line_num`] applied by [`Renderer::styled`]
pub const DEFAULT_LINE_NUM_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
/// [`Renderer::emphasis`] applied by [`Renderer::styled`]
pub const DEFAULT_EMPHASIS_STYLE: Style = if USE_WINDOWS_COLORS {
AnsiColor::BrightWhite.on_default()
} else {
Style::new()
}
.effects(Effects::BOLD);
/// [`Renderer::none`] applied by [`Renderer::styled`]
pub const DEFAULT_NONE_STYLE: Style = Style::new();
/// [`Renderer::context`] applied by [`Renderer::styled`]
pub const DEFAULT_CONTEXT_STYLE: Style = BRIGHT_BLUE.effects(Effects::BOLD);
/// [`Renderer::addition`] applied by [`Renderer::styled`]
pub const DEFAULT_ADDITION_STYLE: Style = AnsiColor::BrightGreen.on_default();
/// [`Renderer::removal`] applied by [`Renderer::styled`]
pub const DEFAULT_REMOVAL_STYLE: Style = AnsiColor::BrightRed.on_default();
/// The [Renderer] for a [`Report`]
///
/// The caller is expected to detect any relevant terminal features and configure the renderer,
/// including
/// - ANSI Escape code support (always outputted with [`Renderer::styled`])
/// - Terminal width ([`Renderer::term_width`])
/// - Unicode support ([`Renderer::decor_style`])
///
/// # Example
///
/// ```
/// # use annotate_snippets::*;
/// # use annotate_snippets::renderer::*;
/// # use annotate_snippets::Level;
/// let report = // ...
/// # &[Group::with_title(
/// # Level::ERROR
/// # .primary_title("unresolved import `baz::zed`")
/// # .id("E0432")
/// # )];
///
/// let renderer = Renderer::styled();
/// let output = renderer.render(report);
/// anstream::println!("{output}");
/// ```
#[derive(Clone, Debug)]
pub struct Renderer {
anonymized_line_numbers: bool,
term_width: usize,
decor_style: DecorStyle,
stylesheet: Stylesheet,
cut_indicator: &'static str,
hyperlink: bool,
short_message: bool,
cut_indicator: Option<&'static str>,
}
impl Renderer {
@@ -40,57 +121,71 @@ impl Renderer {
Self {
anonymized_line_numbers: false,
term_width: DEFAULT_TERM_WIDTH,
decor_style: DecorStyle::Ascii,
stylesheet: Stylesheet::plain(),
cut_indicator: "...",
hyperlink: false,
short_message: false,
cut_indicator: None,
}
}
/// Default terminal styling
///
/// If ANSI escape codes are not supported, either
/// - Call [`Renderer::plain`] instead
/// - Strip them after the fact, like with [`anstream`](https://docs.rs/anstream/latest/anstream/)
///
/// # Note
///
/// When testing styled terminal output, see the [`testing-colors` feature](crate#features)
pub const fn styled() -> Self {
const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors");
const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS {
AnsiColor::BrightCyan.on_default()
} else {
AnsiColor::BrightBlue.on_default()
};
Self {
stylesheet: Stylesheet {
error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD),
warning: if USE_WINDOWS_COLORS {
AnsiColor::BrightYellow.on_default()
} else {
AnsiColor::Yellow.on_default()
}
.effects(Effects::BOLD),
info: BRIGHT_BLUE.effects(Effects::BOLD),
note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD),
help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
line_no: BRIGHT_BLUE.effects(Effects::BOLD),
emphasis: if USE_WINDOWS_COLORS {
AnsiColor::BrightWhite.on_default()
} else {
Style::new()
}
.effects(Effects::BOLD),
none: Style::new(),
hyperlink: true,
error: DEFAULT_ERROR_STYLE,
warning: DEFAULT_WARNING_STYLE,
info: DEFAULT_INFO_STYLE,
note: DEFAULT_NOTE_STYLE,
help: DEFAULT_HELP_STYLE,
line_num: DEFAULT_LINE_NUM_STYLE,
emphasis: DEFAULT_EMPHASIS_STYLE,
none: DEFAULT_NONE_STYLE,
context: DEFAULT_CONTEXT_STYLE,
addition: DEFAULT_ADDITION_STYLE,
removal: DEFAULT_REMOVAL_STYLE,
},
hyperlink: true,
..Self::plain()
}
}
/// Abbreviate the message
pub const fn short_message(mut self, short_message: bool) -> Self {
self.short_message = short_message;
self
}
/// Set the width to render within
///
/// Affects the rendering of [`Snippet`][crate::Snippet]s
pub const fn term_width(mut self, term_width: usize) -> Self {
self.term_width = term_width;
self
}
/// Set the character set used for rendering decor
pub const fn decor_style(mut self, decor_style: DecorStyle) -> Self {
self.decor_style = decor_style;
self
}
/// Anonymize line numbers
///
/// This enables (or disables) line number anonymization. When enabled, line numbers are replaced
/// with `LL`.
/// When enabled, line numbers are replaced with `LL` which is useful for tests.
///
/// # Example
///
/// ```text
/// --> $DIR/whitespace-trimming.rs:LL:193
/// --> $DIR/whitespace-trimming.rs:4:193
/// |
/// LL | ... let _: () = 42;
/// | ^^ expected (), found integer
@@ -100,82 +195,252 @@ impl Renderer {
self.anonymized_line_numbers = anonymized_line_numbers;
self
}
}
/// Set the terminal width
pub const fn term_width(mut self, term_width: usize) -> Self {
self.term_width = term_width;
self
impl Renderer {
/// Render a diagnostic [`Report`]
pub fn render(&self, groups: Report<'_>) -> String {
render::render(self, groups)
}
}
/// Set the output style for `error`
/// Customize [`Renderer::styled`]
impl Renderer {
/// Override the output style for [error][crate::Level::ERROR]
pub const fn error(mut self, style: Style) -> Self {
self.stylesheet.error = style;
self
}
/// Set the output style for `warning`
/// Override the output style for [warnings][crate::Level::WARNING]
pub const fn warning(mut self, style: Style) -> Self {
self.stylesheet.warning = style;
self
}
/// Set the output style for `info`
/// Override the output style for [info][crate::Level::INFO]
pub const fn info(mut self, style: Style) -> Self {
self.stylesheet.info = style;
self
}
/// Set the output style for `note`
/// Override the output style for [notes][crate::Level::NOTE]
pub const fn note(mut self, style: Style) -> Self {
self.stylesheet.note = style;
self
}
/// Set the output style for `help`
/// Override the output style for [help][crate::Level::HELP]
pub const fn help(mut self, style: Style) -> Self {
self.stylesheet.help = style;
self
}
/// Set the output style for line numbers
pub const fn line_no(mut self, style: Style) -> Self {
self.stylesheet.line_no = style;
/// Override the output style for line numbers in the [`Snippet`][crate::Snippet] gutter
pub const fn line_num(mut self, style: Style) -> Self {
self.stylesheet.line_num = style;
self
}
/// Set the output style for emphasis
/// Override the output style for emphasis for the
/// [`primary_title`][crate::Level::primary_title]
pub const fn emphasis(mut self, style: Style) -> Self {
self.stylesheet.emphasis = style;
self
}
/// Set the output style for none
/// Override the output style for [`AnnotationKind::Context`][crate::AnnotationKind::Context]
pub const fn context(mut self, style: Style) -> Self {
self.stylesheet.context = style;
self
}
/// Override the output style for [`Patch`][crate::Patch] additions
pub const fn addition(mut self, style: Style) -> Self {
self.stylesheet.addition = style;
self
}
/// Override the output style for [`Patch`][crate::Patch] removals
pub const fn removal(mut self, style: Style) -> Self {
self.stylesheet.removal = style;
self
}
/// Override the output style for all other text
pub const fn none(mut self, style: Style) -> Self {
self.stylesheet.none = style;
self
}
pub const fn hyperlink(mut self, hyperlink: bool) -> Self {
self.stylesheet.hyperlink = hyperlink;
self.hyperlink = hyperlink;
self
}
/// Set the string used for when a long line is cut.
///
/// The default is `...` (three `U+002E` characters).
pub const fn cut_indicator(mut self, string: &'static str) -> Self {
self.cut_indicator = string;
/// The default for [`DecorStyle::Ascii`] is `...` (three `U+002E` characters).
pub const fn cut_indicator(mut self, cut: &'static str) -> Self {
self.cut_indicator = Some(cut);
self
}
}
/// Render a snippet into a `Display`able object
pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
DisplayList::new(
msg,
&self.stylesheet,
self.anonymized_line_numbers,
self.term_width,
self.cut_indicator,
)
/// The character set for rendering for decor
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecorStyle {
Ascii,
Unicode,
}
impl DecorStyle {
fn col_separator(&self) -> char {
match self {
DecorStyle::Ascii => '|',
DecorStyle::Unicode => '│',
}
}
fn note_separator(&self, is_cont: bool) -> &str {
match self {
DecorStyle::Ascii => "= ",
DecorStyle::Unicode if is_cont => "├ ",
DecorStyle::Unicode => "╰ ",
}
}
fn multi_suggestion_separator(&self) -> &'static str {
match self {
DecorStyle::Ascii => "|",
DecorStyle::Unicode => "├╴",
}
}
fn file_start(&self, is_first: bool, alone: bool) -> &'static str {
match self {
DecorStyle::Ascii => "--> ",
DecorStyle::Unicode if is_first && alone => " ─▸ ",
DecorStyle::Unicode if is_first => " ╭▸ ",
DecorStyle::Unicode => " ├▸ ",
}
}
fn secondary_file_start(&self) -> &'static str {
match self {
DecorStyle::Ascii => "::: ",
DecorStyle::Unicode => " ⸬ ",
}
}
fn diff(&self) -> char {
match self {
DecorStyle::Ascii => '~',
DecorStyle::Unicode => '±',
}
}
fn margin(&self) -> &'static str {
match self {
DecorStyle::Ascii => "...",
DecorStyle::Unicode => "…",
}
}
fn underline(&self, is_primary: bool) -> UnderlineParts {
// X0 Y0
// label_start > ┯━━━━ < underline
// │ < vertical_text_line
// text
// multiline_start_down ⤷ X0 Y0
// top_left > ┌───╿──┘ < top_right_flat
// top_left > ┏│━━━┙ < top_right
// multiline_vertical > ┃│
// ┃│ X1 Y1
// ┃│ X2 Y2
// ┃└────╿──┘ < multiline_end_same_line
// bottom_left > ┗━━━━━┥ < bottom_right_with_text
// multiline_horizontal ^ `X` is a good letter
// multiline_whole_line > ┏ X0 Y0
// ┃ X1 Y1
// ┗━━━━┛ < multiline_end_same_line
// multiline_whole_line > ┏ X0 Y0
// ┃ X1 Y1
// ┃ ╿ < multiline_end_up
// ┗━━┛ < bottom_right
match (self, is_primary) {
(DecorStyle::Ascii, true) => UnderlineParts {
style: ElementStyle::UnderlinePrimary,
underline: '^',
label_start: '^',
vertical_text_line: '|',
multiline_vertical: '|',
multiline_horizontal: '_',
multiline_whole_line: '/',
multiline_start_down: '^',
bottom_right: '|',
top_left: ' ',
top_right_flat: '^',
bottom_left: '|',
multiline_end_up: '^',
multiline_end_same_line: '^',
multiline_bottom_right_with_text: '|',
},
(DecorStyle::Ascii, false) => UnderlineParts {
style: ElementStyle::UnderlineSecondary,
underline: '-',
label_start: '-',
vertical_text_line: '|',
multiline_vertical: '|',
multiline_horizontal: '_',
multiline_whole_line: '/',
multiline_start_down: '-',
bottom_right: '|',
top_left: ' ',
top_right_flat: '-',
bottom_left: '|',
multiline_end_up: '-',
multiline_end_same_line: '-',
multiline_bottom_right_with_text: '|',
},
(DecorStyle::Unicode, true) => UnderlineParts {
style: ElementStyle::UnderlinePrimary,
underline: '━',
label_start: '┯',
vertical_text_line: '│',
multiline_vertical: '┃',
multiline_horizontal: '━',
multiline_whole_line: '┏',
multiline_start_down: '╿',
bottom_right: '┙',
top_left: '┏',
top_right_flat: '┛',
bottom_left: '┗',
multiline_end_up: '╿',
multiline_end_same_line: '┛',
multiline_bottom_right_with_text: '┥',
},
(DecorStyle::Unicode, false) => UnderlineParts {
style: ElementStyle::UnderlineSecondary,
underline: '─',
label_start: '┬',
vertical_text_line: '│',
multiline_vertical: '│',
multiline_horizontal: '─',
multiline_whole_line: '┌',
multiline_start_down: '│',
bottom_right: '┘',
top_left: '┌',
top_right_flat: '┘',
bottom_left: '└',
multiline_end_up: '│',
multiline_end_same_line: '┘',
multiline_bottom_right_with_text: '┤',
},
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,828 @@
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::{vec, vec::Vec};
use core::cmp::{max, min};
use core::ops::Range;
use crate::renderer::{LineAnnotation, LineAnnotationType, char_width, num_overlap};
use crate::{Annotation, AnnotationKind, Patch};
#[derive(Debug)]
pub(crate) struct SourceMap<'a> {
lines: Vec<LineInfo<'a>>,
pub(crate) source: &'a str,
}
impl<'a> SourceMap<'a> {
pub(crate) fn new(source: &'a str, line_start: usize) -> Self {
// Empty sources do have a "line", but it is empty, so we need to add
// a line with an empty string to the source map.
if source.is_empty() {
return Self {
lines: vec![LineInfo {
line: "",
line_index: line_start,
start_byte: 0,
end_byte: 0,
end_line_size: 0,
}],
source,
};
}
let mut current_index = 0;
let mut mapping = vec![];
for (idx, (line, end_line)) in CursorLines::new(source).enumerate() {
let line_length = line.len();
let line_range = current_index..current_index + line_length;
let end_line_size = end_line.len();
mapping.push(LineInfo {
line,
line_index: line_start + idx,
start_byte: line_range.start,
end_byte: line_range.end + end_line_size,
end_line_size,
});
current_index += line_length + end_line_size;
}
Self {
lines: mapping,
source,
}
}
pub(crate) fn get_line(&self, idx: usize) -> Option<&'a str> {
self.lines
.iter()
.find(|l| l.line_index == idx)
.map(|info| info.line)
}
pub(crate) fn span_to_locations(&self, span: Range<usize>) -> (Loc, Loc) {
let start_info = self
.lines
.iter()
.find(|info| span.start >= info.start_byte && span.start < info.end_byte)
.unwrap_or(self.lines.last().unwrap());
let (mut start_char_pos, start_display_pos) = start_info.line
[0..(span.start - start_info.start_byte).min(start_info.line.len())]
.chars()
.fold((0, 0), |(char_pos, byte_pos), c| {
let display = char_width(c);
(char_pos + 1, byte_pos + display)
});
// correct the char pos if we are highlighting the end of a line
if (span.start - start_info.start_byte).saturating_sub(start_info.line.len()) > 0 {
start_char_pos += 1;
}
let start = Loc {
line: start_info.line_index,
char: start_char_pos,
display: start_display_pos,
byte: span.start,
};
if span.start == span.end {
return (start, start);
}
let (end_idx, end_info, eof) = self
.lines
.iter()
.enumerate()
.find(|(_, info)| span.end >= info.start_byte && span.end < info.end_byte)
.map(|(idx, info)| (idx, info, false))
.unwrap_or((self.lines.len() - 1, self.lines.last().unwrap(), true));
let (end_char_pos, end_display_pos) = end_info.line
[0..(span.end - end_info.start_byte).min(end_info.line.len())]
.chars()
.fold((0, 0), |(char_pos, byte_pos), c| {
let display = char_width(c);
(char_pos + 1, byte_pos + display)
});
let mut end = Loc {
line: end_info.line_index,
char: end_char_pos,
display: end_display_pos,
byte: span.end,
};
if start.line < end.line && end.char == 0 && !eof {
let prev_line_info = &self.lines[end_idx - 1];
let (end_char_pos, end_display_pos) = prev_line_info.line
[0..(span.end - prev_line_info.start_byte).min(prev_line_info.line.len())]
.chars()
.fold((0, 0), |(char_pos, byte_pos), c| {
let display = char_width(c);
(char_pos + 1, byte_pos + display)
});
if prev_line_info.end_byte == start.byte {
end = Loc {
line: prev_line_info.line_index,
char: end_char_pos + 1,
display: end_display_pos + 1,
byte: span.end,
};
} else {
end = Loc {
line: prev_line_info.line_index,
char: end_char_pos,
display: end_display_pos,
byte: span.end,
};
}
}
if start.line != end.line && end.byte > end_info.end_byte - end_info.end_line_size {
end.char += 1;
end.display += 1;
}
(start, end)
}
pub(crate) fn span_to_snippet(&self, span: Range<usize>) -> Option<&str> {
self.source.get(span)
}
pub(crate) fn span_to_lines(&self, span: Range<usize>) -> Vec<&LineInfo<'a>> {
let mut lines = vec![];
let start = span.start;
let end = span.end;
for line_info in &self.lines {
if start >= line_info.end_byte {
continue;
}
if end < line_info.start_byte {
break;
}
lines.push(line_info);
}
if lines.is_empty() && !self.lines.is_empty() {
lines.push(self.lines.last().unwrap());
}
lines
}
pub(crate) fn annotated_lines(
&self,
annotations: Vec<Annotation<'a>>,
fold: bool,
) -> (usize, Vec<AnnotatedLineInfo<'a>>) {
let source_len = self.source.len();
if let Some(bigger) = annotations.iter().find_map(|x| {
// Allow highlighting one past the last character in the source.
if source_len + 1 < x.span.end {
Some(&x.span)
} else {
None
}
}) {
panic!("Annotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
}
let mut annotated_line_infos = self
.lines
.iter()
.map(|info| AnnotatedLineInfo {
line: info.line,
line_index: info.line_index,
annotations: vec![],
keep: false,
})
.collect::<Vec<_>>();
let mut multiline_annotations = vec![];
for Annotation {
span,
label,
kind,
highlight_source,
is_file_level: _,
} in annotations
{
let (lo, mut hi) = self.span_to_locations(span.clone());
if kind == AnnotationKind::Visible {
for line_idx in lo.line..=hi.line {
self.keep_line(&mut annotated_line_infos, line_idx);
}
continue;
}
// Watch out for "empty spans". If we get a span like 6..6, we
// want to just display a `^` at 6, so convert that to
// 6..7. This is degenerate input, but it's best to degrade
// gracefully -- and the parser likes to supply a span like
// that for EOF, in particular.
if lo.display == hi.display && lo.line == hi.line {
hi.display += 1;
}
if lo.line == hi.line {
let line_ann = LineAnnotation {
start: lo,
end: hi,
kind,
label,
annotation_type: LineAnnotationType::Singleline,
highlight_source,
};
self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann);
} else {
multiline_annotations.push(MultilineAnnotation {
depth: 1,
start: lo,
end: hi,
kind,
label,
overlaps_exactly: false,
highlight_source,
});
}
}
let mut primary_spans = vec![];
// Find overlapping multiline annotations, put them at different depths
multiline_annotations.sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line));
for (outer_i, ann) in multiline_annotations.clone().into_iter().enumerate() {
if ann.kind.is_primary() {
primary_spans.push((ann.start, ann.end));
}
for (inner_i, a) in &mut multiline_annotations.iter_mut().enumerate() {
// Move all other multiline annotations overlapping with this one
// one level to the right.
if !ann.same_span(a)
&& num_overlap(ann.start.line, ann.end.line, a.start.line, a.end.line, true)
{
a.increase_depth();
} else if ann.same_span(a) && outer_i != inner_i {
a.overlaps_exactly = true;
} else {
if primary_spans
.iter()
.any(|(s, e)| a.start == *s && a.end == *e)
{
a.kind = AnnotationKind::Primary;
}
break;
}
}
}
let mut max_depth = 0; // max overlapping multiline spans
for ann in &multiline_annotations {
max_depth = max(max_depth, ann.depth);
}
// Change order of multispan depth to minimize the number of overlaps in the ASCII art.
for a in &mut multiline_annotations {
a.depth = max_depth - a.depth + 1;
}
for ann in multiline_annotations {
let mut end_ann = ann.as_end();
if ann.overlaps_exactly {
end_ann.annotation_type = LineAnnotationType::Singleline;
} else {
// avoid output like
//
// | foo(
// | _____^
// | |_____|
// | || bar,
// | || );
// | || ^
// | ||______|
// | |______foo
// | baz
//
// and instead get
//
// | foo(
// | _____^
// | | bar,
// | | );
// | | ^
// | | |
// | |______foo
// | baz
self.add_annotation_to_file(
&mut annotated_line_infos,
ann.start.line,
ann.as_start(),
);
// 4 is the minimum vertical length of a multiline span when presented: two lines
// of code and two lines of underline. This is not true for the special case where
// the beginning doesn't have an underline, but the current logic seems to be
// working correctly.
let middle = min(ann.start.line + 4, ann.end.line);
// We'll show up to 4 lines past the beginning of the multispan start.
// We will *not* include the tail of lines that are only whitespace, a comment or
// a bare delimiter.
let filter = |s: &str| {
let s = s.trim();
// Consider comments as empty, but don't consider docstrings to be empty.
!(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
// Consider lines with nothing but whitespace, a single delimiter as empty.
&& !["", "{", "}", "(", ")", "[", "]"].contains(&s)
};
let until = (ann.start.line..middle)
.rev()
.filter_map(|line| self.get_line(line).map(|s| (line + 1, s)))
.find(|(_, s)| filter(s))
.map_or(ann.start.line, |(line, _)| line);
for line in ann.start.line + 1..until {
// Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
self.add_annotation_to_file(&mut annotated_line_infos, line, ann.as_line());
}
let line_end = ann.end.line - 1;
let end_is_empty = self.get_line(line_end).is_some_and(|s| !filter(s));
if middle < line_end && !end_is_empty {
self.add_annotation_to_file(&mut annotated_line_infos, line_end, ann.as_line());
}
}
self.add_annotation_to_file(&mut annotated_line_infos, end_ann.end.line, end_ann);
}
if fold {
annotated_line_infos.retain(|l| !l.annotations.is_empty() || l.keep);
}
(max_depth, annotated_line_infos)
}
fn add_annotation_to_file(
&self,
annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>,
line_index: usize,
line_ann: LineAnnotation<'a>,
) {
if let Some(line_info) = annotated_line_infos
.iter_mut()
.find(|line_info| line_info.line_index == line_index)
{
line_info.annotations.push(line_ann);
} else {
let info = self
.lines
.iter()
.find(|l| l.line_index == line_index)
.unwrap();
annotated_line_infos.push(AnnotatedLineInfo {
line: info.line,
line_index,
annotations: vec![line_ann],
keep: false,
});
annotated_line_infos.sort_by_key(|l| l.line_index);
}
}
fn keep_line(&self, annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>, line_index: usize) {
if let Some(line_info) = annotated_line_infos
.iter_mut()
.find(|line_info| line_info.line_index == line_index)
{
line_info.keep = true;
} else {
let info = self
.lines
.iter()
.find(|l| l.line_index == line_index)
.unwrap();
annotated_line_infos.push(AnnotatedLineInfo {
line: info.line,
line_index,
annotations: vec![],
keep: true,
});
annotated_line_infos.sort_by_key(|l| l.line_index);
}
}
pub(crate) fn splice_lines<'b>(
&'a self,
mut patches: Vec<Patch<'b>>,
fold: bool,
) -> Option<SplicedLines<'b>> {
fn push_trailing(buf: &mut String, line_opt: Option<&str>, lo: &Loc, hi_opt: Option<&Loc>) {
// Convert CharPos to Usize, as CharPose is character offset
// Extract low index and high index
let (lo, hi_opt) = (lo.char, hi_opt.map(|hi| hi.char));
if let Some(line) = line_opt {
if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) {
// Get high index while account for rare unicode and emoji with char_indices
let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
match hi_opt {
// If high index exist, take string from low to high index
Some(hi) if hi > lo => buf.push_str(&line[lo..hi]),
Some(_) => (),
// If high index absence, take string from low index till end string.len
None => buf.push_str(&line[lo..]),
}
}
// If high index is None
if hi_opt.is_none() {
buf.push('\n');
}
}
}
let source_len = self.source.len();
if let Some(bigger) = patches.iter().find_map(|x| {
// Allow patching one past the last character in the source.
if source_len + 1 < x.span.end {
Some(&x.span)
} else {
None
}
}) {
panic!("Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`")
}
// Assumption: all spans are in the same file, and all spans
// are disjoint. Sort in ascending order.
patches.sort_by_key(|p| p.span.start);
// Find the bounding span.
let (lo, hi) = if fold {
let lo = patches.iter().map(|p| p.span.start).min()?;
let hi = patches.iter().map(|p| p.span.end).max()?;
(lo, hi)
} else {
(0, source_len)
};
let lines = self.span_to_lines(lo..hi);
let mut highlights = vec![];
// To build up the result, we do this for each span:
// - push the line segment trailing the previous span
// (at the beginning a "phantom" span pointing at the start of the line)
// - push lines between the previous and current span (if any)
// - if the previous and current span are not on the same line
// push the line segment leading up to the current span
// - splice in the span substitution
//
// Finally push the trailing line segment of the last span
let (mut prev_hi, _) = self.span_to_locations(lo..hi);
prev_hi.char = 0;
let mut prev_line = lines.first().map(|line| line.line);
let mut buf = String::new();
let trimmed_patches = patches
.into_iter()
// If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the
// suggestion and snippet to look as if we just suggested to add
// `"b"`, which is typically much easier for the user to understand.
.map(|part| part.trim_trivial_replacements(self.source))
.collect::<Vec<_>>();
let mut line_highlight = vec![];
// We need to keep track of the difference between the existing code and the added
// or deleted code in order to point at the correct column *after* substitution.
let mut acc = 0;
for part in &trimmed_patches {
let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone());
if prev_hi.line == cur_lo.line {
push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
} else {
acc = 0;
highlights.push(core::mem::take(&mut line_highlight));
push_trailing(&mut buf, prev_line, &prev_hi, None);
// push lines between the previous and current span (if any)
for idx in prev_hi.line + 1..(cur_lo.line) {
if let Some(line) = self.get_line(idx) {
buf.push_str(line.as_ref());
buf.push('\n');
highlights.push(core::mem::take(&mut line_highlight));
}
}
if let Some(cur_line) = self.get_line(cur_lo.line) {
let end = match cur_line.char_indices().nth(cur_lo.char) {
Some((i, _)) => i,
None => cur_line.len(),
};
buf.push_str(&cur_line[..end]);
}
}
// Add a whole line highlight per line in the snippet.
let len: isize = part
.replacement
.split('\n')
.next()
.unwrap_or(&part.replacement)
.chars()
.map(|c| match c {
'\t' => 4,
_ => 1,
})
.sum();
line_highlight.push(SubstitutionHighlight {
start: (cur_lo.char as isize + acc) as usize,
end: (cur_lo.char as isize + acc + len) as usize,
});
buf.push_str(&part.replacement);
// Account for the difference between the width of the current code and the
// snippet being suggested, so that the *later* suggestions are correctly
// aligned on the screen. Note that cur_hi and cur_lo can be on different
// lines, so cur_hi.col can be smaller than cur_lo.col
acc += len - (cur_hi.char as isize - cur_lo.char as isize);
prev_hi = cur_hi;
prev_line = self.get_line(prev_hi.line);
for line in part.replacement.split('\n').skip(1) {
acc = 0;
highlights.push(core::mem::take(&mut line_highlight));
let end: usize = line
.chars()
.map(|c| match c {
'\t' => 4,
_ => 1,
})
.sum();
line_highlight.push(SubstitutionHighlight { start: 0, end });
}
}
highlights.push(core::mem::take(&mut line_highlight));
if fold {
// if the replacement already ends with a newline, don't print the next line
if !buf.ends_with('\n') {
push_trailing(&mut buf, prev_line, &prev_hi, None);
}
} else {
// Add the trailing part of the source after the last patch
if let Some(snippet) = self.span_to_snippet(prev_hi.byte..source_len) {
buf.push_str(snippet);
for _ in snippet.matches('\n') {
highlights.push(core::mem::take(&mut line_highlight));
}
}
}
// remove trailing newlines
while buf.ends_with('\n') {
buf.pop();
}
let (bounding_lo, bounding_hi) = self.span_to_locations(lo..hi);
let line_count = bounding_hi.line.saturating_sub(bounding_lo.line) + 1;
let mut replaced_highlights: Vec<Vec<SubstitutionHighlight>> = vec![Vec::new(); line_count];
for part in &trimmed_patches {
let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone());
for line in cur_lo.line..=cur_hi.line {
let start = if line == cur_lo.line { cur_lo.char } else { 0 };
let end = if line == cur_hi.line {
cur_hi.char
} else {
self.get_line(line).unwrap_or_default().chars().count()
};
replaced_highlights[line - bounding_lo.line]
.push(SubstitutionHighlight { start, end });
}
}
if highlights.iter().all(|parts| parts.is_empty()) {
None
} else {
Some((buf, trimmed_patches, highlights, replaced_highlights))
}
}
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub(crate) struct MultilineAnnotation<'a> {
pub depth: usize,
pub start: Loc,
pub end: Loc,
pub kind: AnnotationKind,
pub label: Option<Cow<'a, str>>,
pub overlaps_exactly: bool,
pub highlight_source: bool,
}
impl<'a> MultilineAnnotation<'a> {
pub(crate) fn increase_depth(&mut self) {
self.depth += 1;
}
/// Compare two `MultilineAnnotation`s considering only the `Span` they cover.
pub(crate) fn same_span(&self, other: &MultilineAnnotation<'_>) -> bool {
self.start == other.start && self.end == other.end
}
pub(crate) fn as_start(&self) -> LineAnnotation<'a> {
LineAnnotation {
start: self.start,
end: Loc {
line: self.start.line,
char: self.start.char + 1,
display: self.start.display + 1,
byte: self.start.byte + 1,
},
kind: self.kind,
label: None,
annotation_type: LineAnnotationType::MultilineStart(self.depth),
highlight_source: self.highlight_source,
}
}
pub(crate) fn as_end(&self) -> LineAnnotation<'a> {
LineAnnotation {
start: Loc {
line: self.end.line,
char: self.end.char.saturating_sub(1),
display: self.end.display.saturating_sub(1),
byte: self.end.byte.saturating_sub(1),
},
end: self.end,
kind: self.kind,
label: self.label.clone(),
annotation_type: LineAnnotationType::MultilineEnd(self.depth),
highlight_source: self.highlight_source,
}
}
pub(crate) fn as_line(&self) -> LineAnnotation<'a> {
LineAnnotation {
start: Loc::default(),
end: Loc::default(),
kind: self.kind,
label: None,
annotation_type: LineAnnotationType::MultilineLine(self.depth),
highlight_source: self.highlight_source,
}
}
}
#[derive(Debug)]
pub(crate) struct LineInfo<'a> {
pub(crate) line: &'a str,
pub(crate) line_index: usize,
pub(crate) start_byte: usize,
pub(crate) end_byte: usize,
end_line_size: usize,
}
#[derive(Debug)]
pub(crate) struct AnnotatedLineInfo<'a> {
pub(crate) line: &'a str,
pub(crate) line_index: usize,
pub(crate) annotations: Vec<LineAnnotation<'a>>,
pub(crate) keep: bool,
}
/// A source code location used for error reporting.
#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
pub(crate) struct Loc {
/// The (1-based) line number.
pub(crate) line: usize,
/// The (0-based) column offset.
pub(crate) char: usize,
/// The (0-based) column offset when displayed.
pub(crate) display: usize,
/// The (0-based) byte offset.
pub(crate) byte: usize,
}
struct CursorLines<'a>(&'a str);
impl CursorLines<'_> {
fn new(src: &str) -> CursorLines<'_> {
CursorLines(src)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum EndLine {
Eof,
Lf,
Crlf,
}
impl EndLine {
/// The number of characters this line ending occupies in bytes.
pub(crate) fn len(self) -> usize {
match self {
EndLine::Eof => 0,
EndLine::Lf => 1,
EndLine::Crlf => 2,
}
}
}
impl<'a> Iterator for CursorLines<'a> {
type Item = (&'a str, EndLine);
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
None
} else {
self.0
.find('\n')
.map(|x| {
let ret = if 0 < x {
if self.0.as_bytes()[x - 1] == b'\r' {
(&self.0[..x - 1], EndLine::Crlf)
} else {
(&self.0[..x], EndLine::Lf)
}
} else {
("", EndLine::Lf)
};
self.0 = &self.0[x + 1..];
ret
})
.or_else(|| {
let ret = Some((self.0, EndLine::Eof));
self.0 = "";
ret
})
}
}
}
pub(crate) type SplicedLines<'a> = (
String,
Vec<TrimmedPatch<'a>>,
// Char spans to highlight per line of the post-substitution output.
Vec<Vec<SubstitutionHighlight>>,
// Char spans of the replaced (original) code, per original line in the
// bounding range covered by the splice.
Vec<Vec<SubstitutionHighlight>>,
);
/// Used to translate between `Span`s and byte positions within a single output line in highlighted
/// code of structured suggestions.
#[derive(Debug, Clone, Copy)]
pub(crate) struct SubstitutionHighlight {
pub(crate) start: usize,
pub(crate) end: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct TrimmedPatch<'a> {
pub(crate) original_span: Range<usize>,
pub(crate) span: Range<usize>,
pub(crate) replacement: Cow<'a, str>,
}
impl<'a> TrimmedPatch<'a> {
pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool {
!self.replacement.is_empty() && !self.replaces_meaningful_content(sm)
}
pub(crate) fn is_deletion(&self, sm: &SourceMap<'_>) -> bool {
self.replacement.trim().is_empty() && self.replaces_meaningful_content(sm)
}
pub(crate) fn is_replacement(&self, sm: &SourceMap<'_>) -> bool {
!self.replacement.is_empty() && self.replaces_meaningful_content(sm)
}
/// Whether this is a replacement that overwrites source with a snippet
/// in a way that isn't a superset of the original string. For example,
/// replacing "abc" with "abcde" is not destructive, but replacing it
/// it with "abx" is, since the "c" character is lost.
pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool {
self.is_replacement(sm)
&& sm
.span_to_snippet(self.span.clone())
.is_none_or(|s| as_substr(s.trim(), self.replacement.trim()).is_none())
}
fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool {
sm.span_to_snippet(self.span.clone())
.map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
}
}
/// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect
/// the case where a substring of the suggestion is "sandwiched" in the original, like
/// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length
/// of the suffix.
pub(crate) fn as_substr<'a>(
original: &'a str,
suggestion: &'a str,
) -> Option<(usize, &'a str, usize)> {
if let Some(stripped) = suggestion.strip_prefix(original) {
Some((original.len(), stripped, 0))
} else if let Some(stripped) = suggestion.strip_suffix(original) {
Some((0, stripped, original.len()))
} else {
let common_prefix = original
.chars()
.zip(suggestion.chars())
.take_while(|(c1, c2)| c1 == c2)
.map(|(c, _)| c.len_utf8())
.sum();
let original = &original[common_prefix..];
let suggestion = &suggestion[common_prefix..];
if let Some(stripped) = suggestion.strip_suffix(original) {
let common_suffix = original.len();
Some((common_prefix, stripped, common_suffix))
} else {
None
}
}
}
@@ -2,10 +2,13 @@
//!
//! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
use alloc::string::String;
use alloc::{vec, vec::Vec};
use core::fmt::{self, Write};
use crate::Level;
use crate::renderer::ElementStyle;
use crate::renderer::stylesheet::Stylesheet;
use anstyle::Style;
use std::fmt;
use std::fmt::Write;
#[derive(Debug)]
pub(crate) struct StyledBuffer {
@@ -15,13 +18,13 @@ pub(crate) struct StyledBuffer {
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct StyledChar {
ch: char,
style: Style,
style: ElementStyle,
}
impl StyledChar {
pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new());
pub(crate) const SPACE: Self = StyledChar::new(' ', ElementStyle::NoStyle);
pub(crate) const fn new(ch: char, style: Style) -> StyledChar {
pub(crate) const fn new(ch: char, style: ElementStyle) -> StyledChar {
StyledChar { ch, style }
}
}
@@ -37,33 +40,40 @@ impl StyledBuffer {
}
}
pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> {
pub(crate) fn render(
&self,
level: &Level<'_>,
stylesheet: &Stylesheet,
str: &mut String,
) -> Result<(), fmt::Error> {
let capacity = self.lines.iter().map(|line| line.len()).sum();
let mut str = String::with_capacity(capacity);
str.reserve(capacity);
for (i, line) in self.lines.iter().enumerate() {
let mut current_style = stylesheet.none;
for ch in line {
if ch.style != current_style {
for StyledChar { ch, style } in line {
let ch_style = style.color_spec(level, stylesheet);
if ch_style != current_style {
if !line.is_empty() {
write!(str, "{}", current_style.render_reset())?;
write!(str, "{current_style:#}")?;
}
current_style = ch.style;
write!(str, "{}", current_style.render())?;
current_style = ch_style;
write!(str, "{current_style}")?;
}
str.push(ch.ch);
str.push(*ch);
}
write!(str, "{}", current_style.render_reset())?;
write!(str, "{current_style:#}")?;
if i != self.lines.len() - 1 {
str.push('\n');
}
}
Ok(str)
Ok(())
}
/// Sets `chr` with `style` for given `line`, `col`.
/// If `line` does not exist in our buffer, adds empty lines up to the given
/// and fills the last line with unstyled whitespace.
pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: ElementStyle) {
self.ensure_lines(line);
if col >= self.lines[line].len() {
self.lines[line].resize(col + 1, StyledChar::SPACE);
@@ -74,24 +84,29 @@ impl StyledBuffer {
/// Sets `string` with `style` for given `line`, starting from `col`.
/// If `line` does not exist in our buffer, adds empty lines up to the given
/// and fills the last line with unstyled whitespace.
pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) {
if string.is_empty() {
// don't add trailing whitespace (from column offset) for blank strings
return;
}
self.ensure_lines(line);
let char_count = string.chars().count();
let needed = col + char_count;
if needed > self.lines[line].len() {
self.lines[line].resize(needed, StyledChar::SPACE);
}
let line = &mut self.lines[line];
for (i, c) in string.chars().enumerate() {
line[col + i] = StyledChar::new(c, style);
let new_len = col + string.chars().count();
if new_len > line.len() {
line.resize(new_len, StyledChar::SPACE);
}
for (offset, chr) in string.chars().enumerate() {
let col = col + offset;
line[col] = StyledChar::new(chr, style);
}
}
/// For given `line` inserts `string` with `style` after old content of that line,
/// adding lines if needed
pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
pub(crate) fn append(&mut self, line: usize, string: &str, style: ElementStyle) {
if line >= self.lines.len() {
self.puts(line, 0, string, style);
} else {
@@ -100,7 +115,58 @@ impl StyledBuffer {
}
}
pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
if start == end {
return;
}
// If the replacement range would be out of bounds, do nothing, as we
// can't replace things that don't exist.
if start > self.lines[line].len() || end > self.lines[line].len() {
return;
};
self.lines[line].splice(
start..end,
string
.chars()
.map(|c| StyledChar::new(c, ElementStyle::LineNumber)),
);
}
pub(crate) fn num_lines(&self) -> usize {
self.lines.len()
}
/// Set `style` for `line`, `col_start..col_end` range if:
/// 1. That line and column range exist in `StyledBuffer`
/// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
pub(crate) fn set_style_range(
&mut self,
line: usize,
col_start: usize,
col_end: usize,
style: ElementStyle,
overwrite: bool,
) {
for col in col_start..col_end {
self.set_style(line, col, style, overwrite);
}
}
/// Set `style` for `line`, `col` if:
/// 1. That line and column exist in `StyledBuffer`
/// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
pub(crate) fn set_style(
&mut self,
line: usize,
col: usize,
style: ElementStyle,
overwrite: bool,
) {
if let Some(ref mut line) = self.lines.get_mut(line)
&& let Some(StyledChar { style: s, .. }) = line.get_mut(col)
&& (overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation))
{
*s = style;
}
}
}
@@ -7,10 +7,12 @@ pub(crate) struct Stylesheet {
pub(crate) info: Style,
pub(crate) note: Style,
pub(crate) help: Style,
pub(crate) line_no: Style,
pub(crate) line_num: Style,
pub(crate) emphasis: Style,
pub(crate) none: Style,
pub(crate) hyperlink: bool,
pub(crate) context: Style,
pub(crate) addition: Style,
pub(crate) removal: Style,
}
impl Default for Stylesheet {
@@ -27,44 +29,12 @@ impl Stylesheet {
info: Style::new(),
note: Style::new(),
help: Style::new(),
line_no: Style::new(),
line_num: Style::new(),
emphasis: Style::new(),
none: Style::new(),
hyperlink: false,
context: Style::new(),
addition: Style::new(),
removal: Style::new(),
}
}
}
impl Stylesheet {
pub(crate) fn error(&self) -> &Style {
&self.error
}
pub(crate) fn warning(&self) -> &Style {
&self.warning
}
pub(crate) fn info(&self) -> &Style {
&self.info
}
pub(crate) fn note(&self) -> &Style {
&self.note
}
pub(crate) fn help(&self) -> &Style {
&self.help
}
pub(crate) fn line_no(&self) -> &Style {
&self.line_no
}
pub(crate) fn emphasis(&self) -> &Style {
&self.emphasis
}
pub(crate) fn none(&self) -> &Style {
&self.none
}
}
+537 -130
View File
@@ -1,77 +1,101 @@
//! Structures used as an input for the library.
//!
//! Example:
//!
//! ```
//! use ruff_annotate_snippets::*;
//!
//! Level::Error.title("mismatched types")
//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
//! ```
use std::ops::Range;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::{vec, vec::Vec};
use core::ops::Range;
#[derive(Copy, Clone, Debug, Default, PartialEq)]
use crate::Level;
use crate::renderer::source_map::{TrimmedPatch, as_substr};
pub(crate) const ERROR_TXT: &str = "error";
pub(crate) const HELP_TXT: &str = "help";
pub(crate) const INFO_TXT: &str = "info";
pub(crate) const NOTE_TXT: &str = "note";
pub(crate) const WARNING_TXT: &str = "warning";
/// A [diagnostic message][Title] and any associated [context][Element] to help users
/// understand it
///
/// The first [`Group`] is the ["primary" group][Level::primary_title], ie it contains the diagnostic
/// message.
///
/// All subsequent [`Group`]s are for distinct pieces of [context][Level::secondary_title].
/// The primary group will be visually distinguished to help tell them apart.
pub type Report<'a> = &'a [Group<'a>];
#[derive(Clone, Debug, Default)]
pub(crate) struct Id<'a> {
pub(crate) id: &'a str,
pub(crate) url: Option<&'a str>,
pub(crate) id: Option<Cow<'a, str>>,
pub(crate) url: Option<Cow<'a, str>>,
}
/// Primary structure provided for formatting
/// A [`Title`] with supporting [context][Element] within a [`Report`]
///
/// See [`Level::title`] to create a [`Message`]
#[derive(Debug)]
pub struct Message<'a> {
pub(crate) level: Level,
pub(crate) id: Option<Id<'a>>,
pub(crate) title: &'a str,
pub(crate) snippets: Vec<Snippet<'a>>,
pub(crate) footer: Vec<Message<'a>>,
pub(crate) is_fixable: bool,
/// [Decor][crate::renderer::DecorStyle] is used to visually connect [`Element`]s of a `Group`.
///
/// Generally, you will create separate group's for:
/// - New [`Snippet`]s, especially if they need their own [`AnnotationKind::Primary`]
/// - Each logically distinct set of [suggestions][Patch`]
///
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/highlight_message.rs")]
/// ```
#[doc = include_str!("../examples/highlight_message.svg")]
#[derive(Clone, Debug)]
pub struct Group<'a> {
pub(crate) primary_level: Level<'a>,
pub(crate) title: Option<Title<'a>>,
pub(crate) elements: Vec<Element<'a>>,
pub(crate) lineno_offset: usize,
}
impl<'a> Message<'a> {
pub fn id(mut self, id: &'a str) -> Self {
self.id = Some(Id { id, url: None });
self
impl<'a> Group<'a> {
/// Create group with a [`Title`], deriving [`AnnotationKind::Primary`] from its [`Level`]
pub fn with_title(title: Title<'a>) -> Self {
let level = title.level.clone();
let mut x = Self::with_level(level);
x.title = Some(title);
x
}
pub fn id_with_url(mut self, id: &'a str, url: Option<&'a str>) -> Self {
self.id = Some(Id { id, url });
self
}
pub fn snippet(mut self, slice: Snippet<'a>) -> Self {
self.snippets.push(slice);
self
}
pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self {
self.snippets.extend(slice);
self
}
pub fn footer(mut self, footer: Message<'a>) -> Self {
self.footer.push(footer);
self
}
pub fn footers(mut self, footer: impl IntoIterator<Item = Message<'a>>) -> Self {
self.footer.extend(footer);
self
}
/// Whether or not the diagnostic for this message is fixable.
/// Create a title-less group with a primary [`Level`] for [`AnnotationKind::Primary`]
///
/// This is rendered as a `[*]` indicator after the `id` in an annotation header, if the
/// annotation also has `Level::None`.
pub fn is_fixable(mut self, yes: bool) -> Self {
self.is_fixable = yes;
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/elide_header.rs")]
/// ```
#[doc = include_str!("../examples/elide_header.svg")]
pub fn with_level(level: Level<'a>) -> Self {
Self {
primary_level: level,
title: None,
elements: vec![],
lineno_offset: 0,
}
}
/// Append an [`Element`] that adds context to the [`Title`]
pub fn element(mut self, section: impl Into<Element<'a>>) -> Self {
self.elements.push(section.into());
self
}
/// Append [`Element`]s that adds context to the [`Title`]
pub fn elements(mut self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Self {
self.elements.extend(sections.into_iter().map(Into::into));
self
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty() && self.title.is_none()
}
/// Add an offset used for aligning the header sigil (`-->`) with the line number separators.
///
/// For normal diagnostics this is computed automatically based on the lines to be rendered.
@@ -83,61 +107,193 @@ impl<'a> Message<'a> {
}
}
/// Structure containing the slice of text to be annotated and
/// basic information about the location of the slice.
/// A section of content within a [`Group`]
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Element<'a> {
Message(Message<'a>),
Cause(Snippet<'a, Annotation<'a>>),
Suggestion(Snippet<'a, Patch<'a>>),
Origin(Origin<'a>),
Padding(Padding),
}
impl<'a> From<Message<'a>> for Element<'a> {
fn from(value: Message<'a>) -> Self {
Element::Message(value)
}
}
impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> {
fn from(value: Snippet<'a, Annotation<'a>>) -> Self {
Element::Cause(value)
}
}
impl<'a> From<Snippet<'a, Patch<'a>>> for Element<'a> {
fn from(value: Snippet<'a, Patch<'a>>) -> Self {
Element::Suggestion(value)
}
}
impl<'a> From<Origin<'a>> for Element<'a> {
fn from(value: Origin<'a>) -> Self {
Element::Origin(value)
}
}
impl From<Padding> for Element<'_> {
fn from(value: Padding) -> Self {
Self::Padding(value)
}
}
/// A whitespace [`Element`] in a [`Group`]
#[derive(Clone, Debug)]
pub struct Padding;
/// A title that introduces a [`Group`], describing the main point
///
/// One `Snippet` is meant to represent a single, continuous,
/// slice of source code that you want to annotate.
#[derive(Debug)]
pub struct Snippet<'a> {
pub(crate) origin: Option<&'a str>,
pub(crate) line_start: usize,
/// To create a `Title`, see [`Level::primary_title`] or [`Level::secondary_title`].
///
/// # Example
///
/// ```rust
/// # use annotate_snippets::*;
/// let report = &[
/// Group::with_title(
/// Level::ERROR.primary_title("mismatched types").id("E0308")
/// ),
/// Group::with_title(
/// Level::HELP.secondary_title("function defined here")
/// ),
/// ];
/// ```
#[derive(Clone, Debug)]
pub struct Title<'a> {
pub(crate) level: Level<'a>,
pub(crate) id: Option<Id<'a>>,
pub(crate) text: Cow<'a, str>,
pub(crate) allows_styling: bool,
pub(crate) is_fixable: bool,
}
pub(crate) source: &'a str,
pub(crate) annotations: Vec<Annotation<'a>>,
impl<'a> Title<'a> {
/// The category for this [`Report`]
///
/// Useful for looking searching for more information to resolve the diagnostic.
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn id(mut self, id: impl Into<Cow<'a, str>>) -> Self {
self.id.get_or_insert(Id::default()).id = Some(id.into());
self
}
pub(crate) fold: bool,
/// Provide a URL for [`Title::id`] for more information on this diagnostic
///
/// <div class="warning">
///
/// This is only relevant if `id` is present
///
/// </div>
pub fn id_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
self.id.get_or_insert(Id::default()).url = Some(url.into());
self
}
/// Append an [`Element`] that adds context to the [`Title`]
pub fn element(self, section: impl Into<Element<'a>>) -> Group<'a> {
Group::with_title(self).element(section)
}
/// Append [`Element`]s that adds context to the [`Title`]
pub fn elements(self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Group<'a> {
Group::with_title(self).elements(sections)
}
/// Whether or not the diagnostic for this message is fixable.
///
/// This is rendered as a `[*]` indicator after the `id` in an annotation header, if the
/// annotation also has `Level::None`.
pub fn is_fixable(mut self, yes: bool) -> Self {
self.is_fixable = yes;
self
}
}
/// A text [`Element`] in a [`Group`]
///
/// See [`Level::message`] to create this.
#[derive(Clone, Debug)]
pub struct Message<'a> {
pub(crate) level: Level<'a>,
pub(crate) text: Cow<'a, str>,
}
/// A source view [`Element`] in a [`Group`]
///
/// If you do not have [source][Snippet::source] available, see instead [`Origin`]
///
/// `Snippet`s come in the following styles (`T`):
/// - With [`Annotation`]s, see [`Snippet::annotation`]
/// - With [`Patch`]s, see [`Snippet::patch`]
#[derive(Clone, Debug)]
pub struct Snippet<'a, T> {
pub(crate) path: Option<Cow<'a, str>>,
/// The optional cell index in a Jupyter notebook, used for reporting source locations along
/// with the ranges on `annotations`.
pub(crate) cell_index: Option<usize>,
pub(crate) line_start: usize,
pub(crate) source: Cow<'a, str>,
pub(crate) markers: Vec<T>,
pub(crate) fold: bool,
}
impl<'a> Snippet<'a> {
pub fn source(source: &'a str) -> Self {
impl<'a, T: Clone> Snippet<'a, T> {
/// The source code to be rendered
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn source(source: impl Into<Cow<'a, str>>) -> Self {
Self {
origin: None,
path: None,
line_start: 1,
source,
annotations: vec![],
fold: false,
cell_index: None,
source: source.into(),
markers: vec![],
fold: true,
}
}
/// When manually [`fold`][Self::fold]ing,
/// the [`source`][Self::source]s line offset from the original start
pub fn line_start(mut self, line_start: usize) -> Self {
self.line_start = line_start;
self
}
pub fn origin(mut self, origin: &'a str) -> Self {
self.origin = Some(origin);
self
}
pub fn annotation(mut self, annotation: Annotation<'a>) -> Self {
self.annotations.push(annotation);
self
}
pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
self.annotations.extend(annotation);
self
}
/// Hide lines without [`Annotation`]s
pub fn fold(mut self, fold: bool) -> Self {
self.fold = fold;
/// The location of the [`source`][Self::source] (e.g. a path)
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn path(mut self, path: impl Into<OptionCow<'a>>) -> Self {
self.path = path.into().0;
self
}
@@ -146,23 +302,89 @@ impl<'a> Snippet<'a> {
self.cell_index = index;
self
}
/// Control whether lines without [`Annotation`]s are shown
///
/// The default is `fold(true)`, collapsing uninteresting lines.
///
/// See [`AnnotationKind::Visible`] to force specific spans to be shown.
pub fn fold(mut self, fold: bool) -> Self {
self.fold = fold;
self
}
}
/// An annotation for a [`Snippet`].
impl<'a> Snippet<'a, Annotation<'a>> {
/// Highlight and describe a span of text within the [`source`][Self::source]
pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> {
self.markers.push(annotation);
self
}
/// Highlight and describe spans of text within the [`source`][Self::source]
pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
self.markers.extend(annotation);
self
}
}
impl<'a> Snippet<'a, Patch<'a>> {
/// Suggest to the user an edit to the [`source`][Self::source]
pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> {
self.markers.push(patch);
self
}
/// Suggest to the user edits to the [`source`][Self::source]
pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self {
self.markers.extend(patches);
self
}
}
/// Highlight and describe a span of text within a [`Snippet`]
///
/// See [`Level::span`] to create a [`Annotation`]
#[derive(Debug)]
/// See [`AnnotationKind`] to create an annotation.
///
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/expected_type.rs")]
/// ```
///
#[doc = include_str!("../examples/expected_type.svg")]
#[derive(Clone, Debug)]
pub struct Annotation<'a> {
/// The byte range of the annotation in the `source` string
pub(crate) range: Range<usize>,
pub(crate) label: Option<&'a str>,
pub(crate) level: Level,
pub(crate) span: Range<usize>,
pub(crate) label: Option<Cow<'a, str>>,
pub(crate) kind: AnnotationKind,
pub(crate) highlight_source: bool,
pub(crate) is_file_level: bool,
}
impl<'a> Annotation<'a> {
pub fn label(mut self, label: &'a str) -> Self {
self.label = Some(label);
/// Describe the reason the span is highlighted
///
/// This will be styled according to the [`AnnotationKind`]
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn label(mut self, label: impl Into<OptionCow<'a>>) -> Self {
self.label = label.into().0;
self
}
/// Style the source according to the [`AnnotationKind`]
///
/// This gives extra emphasis to this annotation
pub fn highlight_source(mut self, highlight_source: bool) -> Self {
self.highlight_source = highlight_source;
self
}
@@ -172,40 +394,225 @@ impl<'a> Annotation<'a> {
}
}
/// Types of annotations.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Level {
/// Do not attach any annotation.
None,
/// Error annotations are displayed using red color and "^" character.
Error,
/// Warning annotations are displayed using blue color and "-" character.
Warning,
Info,
Note,
Help,
/// The type of [`Annotation`] being applied to a [`Snippet`]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum AnnotationKind {
/// For showing the source that the [Group's Title][Group::with_title] references
///
/// For [`Title`]-less groups, see [`Group::with_level`]
Primary,
/// Additional context to better understand the [`Primary`][Self::Primary]
/// [`Annotation`]
///
/// See also [`Renderer::context`].
///
/// [`Renderer::context`]: crate::renderer::Renderer
Context,
/// Prevents the annotated text from getting [folded][Snippet::fold]
///
/// By default, [`Snippet`]s will [fold][`Snippet::fold`] (remove) lines
/// that do not contain any annotations. [`Visible`][Self::Visible] makes
/// it possible to selectively prevent this behavior for specific text,
/// allowing context to be preserved without adding any annotation
/// characters.
///
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/struct_name_as_context.rs")]
/// ```
///
#[doc = include_str!("../examples/struct_name_as_context.svg")]
///
Visible,
}
impl Level {
pub fn title(self, title: &str) -> Message<'_> {
Message {
level: self,
id: None,
title,
snippets: vec![],
footer: vec![],
is_fixable: false,
lineno_offset: 0,
}
}
/// Create a [`Annotation`] with the given span for a [`Snippet`]
impl AnnotationKind {
/// Annotate a byte span within [`Snippet`]
pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
Annotation {
range: span,
span,
label: None,
level: self,
kind: self,
highlight_source: false,
is_file_level: false,
}
}
pub(crate) fn is_primary(&self) -> bool {
matches!(self, AnnotationKind::Primary)
}
}
/// Suggested edit to the [`Snippet`]
///
/// See [`Snippet::patch`]
///
/// # Example
///
/// ```rust
/// # #[allow(clippy::needless_doctest_main)]
#[doc = include_str!("../examples/multi_suggestion.rs")]
/// ```
///
#[doc = include_str!("../examples/multi_suggestion.svg")]
#[derive(Clone, Debug)]
pub struct Patch<'a> {
pub(crate) span: Range<usize>,
pub(crate) replacement: Cow<'a, str>,
}
impl<'a> Patch<'a> {
/// Splice `replacement` into the [`Snippet`] at the specified byte span
///
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn new(span: Range<usize>, replacement: impl Into<Cow<'a, str>>) -> Self {
Self {
span,
replacement: replacement.into(),
}
}
/// Try to turn a replacement into an addition when the span that is being
/// overwritten matches either the prefix or suffix of the replacement.
pub(crate) fn trim_trivial_replacements(self, source: &str) -> TrimmedPatch<'a> {
let mut trimmed = TrimmedPatch {
original_span: self.span.clone(),
span: self.span,
replacement: self.replacement,
};
if trimmed.replacement.is_empty() {
return trimmed;
}
let Some(snippet) = source.get(trimmed.original_span.clone()) else {
return trimmed;
};
if let Some((prefix, substr, suffix)) = as_substr(snippet, &trimmed.replacement) {
trimmed.span = trimmed.original_span.start + prefix
..trimmed.original_span.end.saturating_sub(suffix);
trimmed.replacement = Cow::Owned(substr.to_owned());
}
trimmed
}
}
/// A source location [`Element`] in a [`Group`]
///
/// If you have source available, see instead [`Snippet`]
///
/// # Example
///
/// ```rust
/// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level, Origin};
/// let report = &[
/// Level::ERROR.primary_title("mismatched types").id("E0308")
/// .element(
/// Origin::path("$DIR/mismatched-types.rs")
/// )
/// ];
/// ```
#[derive(Clone, Debug)]
pub struct Origin<'a> {
pub(crate) path: Cow<'a, str>,
/// The optional cell index in a Jupyter notebook, used for reporting source locations along
/// with the ranges on `annotations`.
pub(crate) cell_index: Option<usize>,
pub(crate) line: Option<usize>,
pub(crate) char_column: Option<usize>,
}
impl<'a> Origin<'a> {
/// <div class="warning">
///
/// Text passed to this function is considered "untrusted input", as such
/// all text is passed through a normalization function. Pre-styled text is
/// not allowed to be passed to this function.
///
/// </div>
pub fn path(path: impl Into<Cow<'a, str>>) -> Self {
Self {
path: path.into(),
cell_index: None,
line: None,
char_column: None,
}
}
/// Attach a Jupyter notebook cell index.
pub fn cell_index(mut self, index: Option<usize>) -> Self {
self.cell_index = index;
self
}
/// Set the default line number to display
pub fn line(mut self, line: usize) -> Self {
self.line = Some(line);
self
}
/// Set the default column to display
///
/// <div class="warning">
///
/// `char_column` is only be respected if [`Origin::line`] is also set.
///
/// </div>
pub fn char_column(mut self, char_column: usize) -> Self {
self.char_column = Some(char_column);
self
}
}
impl<'a> From<Cow<'a, str>> for Origin<'a> {
fn from(origin: Cow<'a, str>) -> Self {
Self::path(origin)
}
}
#[derive(Debug)]
pub struct OptionCow<'a>(pub(crate) Option<Cow<'a, str>>);
impl<'a, T: Into<Cow<'a, str>>> From<Option<T>> for OptionCow<'a> {
fn from(value: Option<T>) -> Self {
Self(value.map(Into::into))
}
}
impl<'a> From<&'a Cow<'a, str>> for OptionCow<'a> {
fn from(value: &'a Cow<'a, str>) -> Self {
Self(Some(Cow::Borrowed(value)))
}
}
impl<'a> From<Cow<'a, str>> for OptionCow<'a> {
fn from(value: Cow<'a, str>) -> Self {
Self(Some(value))
}
}
impl<'a> From<&'a str> for OptionCow<'a> {
fn from(value: &'a str) -> Self {
Self(Some(Cow::Borrowed(value)))
}
}
impl<'a> From<String> for OptionCow<'a> {
fn from(value: String) -> Self {
Self(Some(Cow::Owned(value)))
}
}
impl<'a> From<&'a String> for OptionCow<'a> {
fn from(value: &'a String) -> Self {
Self(Some(Cow::Borrowed(value.as_str())))
}
}
@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,21 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let input = &[Level::ERROR.primary_title("expected `.`, `=`").element(
Snippet::source("asdf")
.path("Cargo.toml")
.line_start(1)
.annotation(AnnotationKind::Primary.span(4..4).label("")),
)];
let expected_ascii = file!["ann_eof.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ann_eof.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:1:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> asdf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:3</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:3</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,21 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let input = &[Level::ERROR.primary_title("expected `.`, `=`").element(
Snippet::source("asf")
.path("Cargo.toml")
.line_start(1)
.annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")),
)];
let expected_ascii = file!["ann_insertion.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ann_insertion.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:1:3</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> asf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,4 +1,4 @@
<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,23 +19,21 @@
<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 class="fg-bright-red bold">error[E0027]</tspan><tspan>: </tspan><tspan class="bold">pattern does not mention fields `lineno`, `content`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/display_list.rs:139:32</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/display_list.rs:139:32</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139 |</tspan><tspan> if let DisplayLine::Source {</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let DisplayLine::Source {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">________________________________^</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold"> ________________________________^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,34 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#" if let DisplayLine::Source {
ref mut inline_marks,
} = body[body_idx]
"#;
let input = &[Level::ERROR
.primary_title("pattern does not mention fields `lineno`, `content`")
.id("E0027")
.element(
Snippet::source(source)
.path("src/display_list.rs")
.line_start(139)
.fold(false)
.annotation(
AnnotationKind::Primary
.span(31..128)
.label("missing fields `lineno`, `content`"),
),
)];
let expected_ascii = file!["ann_multiline.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ann_multiline.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,40 @@
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>src/display_list.rs:139:32</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> if let DisplayLine::Source {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> ref mut inline_marks,</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> } = body[body_idx]</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">┗━━━━━━━━━━━━━━━━━━━━━━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -19,19 +19,19 @@
<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 class="fg-bright-red bold">error[E####]</tspan><tspan>: </tspan><tspan class="bold">spacing error found</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> foo.txt:26:12</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>foo.txt:26:12</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> This is an example</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> This is an example</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> of an edge case of an annotation overflowing</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> to exactly one character on next line.</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
@@ -0,0 +1,34 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"This is an example
of an edge case of an annotation overflowing
to exactly one character on next line.
"#;
let input = &[Level::ERROR
.primary_title("spacing error found")
.id("E####")
.element(
Snippet::source(source)
.path("foo.txt")
.line_start(26)
.fold(false)
.annotation(
AnnotationKind::Primary
.span(11..19)
.label("this should not be on separate lines"),
),
)];
let expected_ascii = file!["ann_multiline2.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ann_multiline2.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,40 @@
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>foo.txt:26:12</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> This is an example</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> of an edge case of an annotation overflowing</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> to exactly one character on next line.</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:5</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,21 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let input = &[Level::ERROR.primary_title("expected `.`, `=`").element(
Snippet::source("asdf")
.path("Cargo.toml")
.line_start(1)
.annotation(AnnotationKind::Primary.span(4..5).label("")),
)];
let expected_ascii = file!["ann_removed_nl.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ann_removed_nl.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:1:5</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> asdf</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,4 +1,4 @@
<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="1356px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> &lt;file&gt;:7:1</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>&lt;file&gt;:7:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,25 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#""haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }
"#;
let input = &[Level::ERROR.primary_title("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)")
.element(
Snippet::source(source)
.path("<file>")
.line_start(7)
.annotation(AnnotationKind::Primary.span(0..35).label(""))
)];
let expected_ascii = file!["ensure_emoji_highlight_width.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["ensure_emoji_highlight_width.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="1356px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>&lt;file&gt;:7:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,54 @@
<svg width="740px" height="290px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/file.txt:3:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> this is from a txt file</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-----------------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">the macro expands to this string</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>$DIR/mismatched-types.rs:2:12</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let b: &amp;[u8] = include_str!("file.txt");</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-----</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `&amp;[u8]`, found `&amp;str`</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected due to this</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected reference `&amp;[u8]`</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan> found reference `&amp;'static str`</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,52 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let file_txt_source = r#"this is from a txt file"#;
let rust_source = r#"fn main() {
let b: &[u8] = include_str!("file.txt");
let s: &str = include_bytes!("file.txt");
}"#;
let input = &[Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.element(
Snippet::source(file_txt_source)
.line_start(3)
.path("$DIR/file.txt")
.annotation(
AnnotationKind::Context
.span(0..23)
.label("the macro expands to this string"),
),
)
.element(
Snippet::source(rust_source)
.path("$DIR/mismatched-types.rs")
.annotation(
AnnotationKind::Context
.span(23..28)
.label("expected due to this"),
)
.annotation(
AnnotationKind::Primary
.span(31..55)
.label("expected `&[u8]`, found `&str`"),
),
)
.element(
Level::NOTE.message("expected reference `&[u8]`\n found reference `&'static str`"),
)];
let expected_ascii = file!["first_snippet_is_primary.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["first_snippet_is_primary.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,54 @@
<svg width="740px" height="290px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/file.txt:3:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> this is from a txt file</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">───────────────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">the macro expands to this string</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ⸬ </tspan><tspan>$DIR/mismatched-types.rs:2:12</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let b: &amp;[u8] = include_str!("file.txt");</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">┬────</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━━━━━━━━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `&amp;[u8]`, found `&amp;str`</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected due to this</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰ </tspan><tspan class="bold">note</tspan><tspan>: expected reference `&amp;[u8]`</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan> found reference `&amp;'static str`</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -4,7 +4,6 @@
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
@@ -20,29 +19,29 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> src/format.rs:51:6</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>src/format.rs:52:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -&gt; Option&lt;String&gt; {</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ) -&gt; Option&lt;String&gt; {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
</tspan>
</text>
@@ -0,0 +1,55 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#") -> Option<String> {
for ann in annotations {
match (ann.range.0, ann.range.1) {
(None, None) => continue,
(Some(start), Some(end)) if start > end_index || end < start_index => continue,
(Some(start), Some(end)) if start >= start_index && end <= end_index => {
let label = if let Some(ref label) = ann.label {
format!(" {}", label)
} else {
String::from("")
};
return Some(format!(
"{}{}{}",
" ".repeat(start - start_index),
"^".repeat(end - start),
label
));
}
_ => continue,
}
}
"#;
let input = &[Level::ERROR
.primary_title("mismatched types")
.id("E0308")
.element(
Snippet::source(source)
.path("src/format.rs")
.line_start(51)
.annotation(AnnotationKind::Context.span(5..19).label(
"expected `std::option::Option<std::string::String>` because of return type",
))
.annotation(
AnnotationKind::Primary
.span(22..766)
.label("expected enum `std::option::Option`, found ()"),
),
)];
let expected_ascii = file!["fold_ann_multiline.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["fold_ann_multiline.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,48 @@
<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>src/format.rs:52:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> ) -&gt; Option&lt;String&gt; {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `std::option::Option&lt;std::string::String&gt;` because of return type</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏</tspan><tspan> for ann in annotations {</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> (None, None) =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> (Some(start), Some(end)) if start &gt; end_index || end &lt; start_index =&gt; continue,</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┇</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> }</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">┗━━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -1,10 +1,9 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
@@ -20,17 +19,15 @@
<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 class="fg-bright-red bold">error</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> path/to/error.rs:3:1</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>path/to/error.rs:3:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> invalid syntax</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">error here</tspan>
</tspan>
</text>
@@ -0,0 +1,26 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"
invalid syntax
"#;
let input = &[Level::ERROR.primary_title("").element(
Snippet::source(source)
.path("path/to/error.rs")
.line_start(1)
.annotation(AnnotationKind::Context.span(2..16).label("error here")),
)];
let expected_ascii = file!["fold_bad_origin_line.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["fold_bad_origin_line.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>path/to/error.rs:3:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> invalid syntax</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴──────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">error here</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a bool</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:11:13</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:11:13</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,37 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"[workspace]
[package]
name = "hello"
version = "1.0.0"
license = "MIT"
rust-version = "1.70"
edition = "2021"
[lints]
workspace = 20
"#;
let input = &[Level::ERROR
.primary_title("invalid type: integer `20`, expected a bool")
.id("E0308")
.element(
Snippet::source(source)
.path("Cargo.toml")
.line_start(1)
.annotation(AnnotationKind::Primary.span(132..134).label("")),
)];
let expected_ascii = file!["fold_leading.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["fold_leading.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/whitespace-trimming.rs:LL:193</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:11:13</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42ñ</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> workspace = 20</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━</tspan>
</tspan>
</text>
@@ -1,4 +1,4 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -19,17 +19,15 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a lints table</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> Cargo.toml:1:9</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:1:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> lints = 20</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,36 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"lints = 20
[workspace]
[package]
name = "hello"
version = "1.0.0"
license = "MIT"
rust-version = "1.70"
edition = "2021"
"#;
let input = &[Level::ERROR
.primary_title("invalid type: integer `20`, expected a lints table")
.id("E0308")
.element(
Snippet::source(source)
.path("Cargo.toml")
.line_start(1)
.annotation(AnnotationKind::Primary.span(8..10).label("")),
)];
let expected_ascii = file!["fold_trailing.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["fold_trailing.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:1:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> lints = 20</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,54 @@
<svg width="740px" height="272px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0422]</tspan><tspan class="bold">: cannot find struct, variant or union type `哈哈哈哈` in this scope</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/highlight_diff_line_with_wide_characters.rs:3:18</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> struct 啊啊啊啊 {}</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">------------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">similarly named struct `啊啊啊啊` defined here</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">here</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: a struct with a similar name exists: `啊啊啊啊`</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>const 哦哦: 啊啊啊啊 = </tspan><tspan class="fg-bright-red">哈哈哈哈</tspan><tspan> {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan>const 哦哦: 啊啊啊啊 = </tspan><tspan class="fg-bright-green">啊啊啊啊</tspan><tspan> {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,44 @@
use annotate_snippets::{AnnotationKind, Level, Patch, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"struct 啊啊啊啊 {}
const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment"#;
let path = "$DIR/highlight_diff_line_with_wide_characters.rs";
let report = &[
Level::ERROR
.primary_title("cannot find struct, variant or union type `哈哈哈哈` in this scope")
.id("E0422")
.element(
Snippet::source(source)
.path(path)
.annotation(AnnotationKind::Primary.span(53..65).label("here"))
.annotation(
AnnotationKind::Context
.span(0..22)
.label("similarly named struct `啊啊啊啊` defined here"),
),
),
Level::HELP
.secondary_title("a struct with a similar name exists: `啊啊啊啊`")
.element(
Snippet::source(source)
.path(path)
.patch(Patch::new(53..65, "啊啊啊啊")),
),
];
let expected_ascii = file!["highlight_diff_line_with_wide_characters.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode =
file!["highlight_diff_line_with_wide_characters.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
@@ -0,0 +1,54 @@
<svg width="740px" height="272px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0422]</tspan><tspan class="bold">: cannot find struct, variant or union type `哈哈哈哈` in this scope</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/highlight_diff_line_with_wide_characters.rs:3:18</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> struct 啊啊啊啊 {}</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">similarly named struct `啊啊啊啊` defined here</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> const 哦哦: 啊啊啊啊 = 哈哈哈哈 {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">here</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: a struct with a similar name exists: `啊啊啊啊`</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>const 哦哦: 啊啊啊啊 = </tspan><tspan class="fg-bright-red">哈哈哈哈</tspan><tspan> {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan>const 哦哦: 啊啊啊啊 = </tspan><tspan class="fg-bright-green">啊啊啊啊</tspan><tspan> {}; // some comment</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,62 @@
<svg width="740px" height="344px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0061]</tspan><tspan class="bold">: this function takes 6 arguments but 7 arguments were supplied</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let foo = Thingy::new(</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> String::from(""),</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">unexpected argument #7 of type `String`</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-green bold">note</tspan><tspan>: associated function defined here</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>$DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold"> 4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> pub(crate) fn new(</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-green bold">^^^</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: remove the extra argument</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- String::from(""),</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,75 @@
use annotate_snippets::{AnnotationKind, Level, Patch, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
// https://github.com/rust-lang/rust/blob/4b94758d2ba7d0ef71ccf5fde29ce4bc5d6fe2a4/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.rs
let source = r#"struct Thingy;
impl Thingy {
pub(crate) fn new(
_a: String,
_b: String,
_c: String,
_d: String,
_e: String,
_f: String,
) -> Self {
unimplemented!()
}
}
fn main() {
let foo = Thingy::new(
String::from(""),
String::from(""),
String::from(""),
String::from(""),
String::from(""),
String::from(""),
String::from(""),
);
}"#;
let path = "$DIR/wrong-highlight-span-extra-arguments-147070.rs";
let report = &[
Level::ERROR
.primary_title("this function takes 6 arguments but 7 arguments were supplied")
.id("E0061")
.element(
Snippet::source(source)
.path(path)
.annotation(
AnnotationKind::Context
.span(426..442)
.label("unexpected argument #7 of type `String`"),
)
.annotation(AnnotationKind::Primary.span(249..260)),
),
Level::NOTE
.secondary_title("associated function defined here")
.element(
Snippet::source(source)
.path(path)
.annotation(AnnotationKind::Primary.span(48..51)),
),
Level::HELP
.secondary_title("remove the extra argument")
.element(
Snippet::source(source)
.path(path)
.patch(Patch::new(413..439, "")),
),
];
let expected_ascii = file!["highlight_duplicated_diff_lines.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode = file!["highlight_duplicated_diff_lines.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
@@ -0,0 +1,62 @@
<svg width="740px" height="344px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-cyan { fill: #55FFFF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error[E0061]</tspan><tspan class="bold">: this function takes 6 arguments but 7 arguments were supplied</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let foo = Thingy::new(</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> String::from(""),</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">────────────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">unexpected argument #7 of type `String`</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-green bold">note</tspan><tspan>: associated function defined here</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan class="fg-bright-blue bold"> 4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub(crate) fn new(</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-green bold">━━━</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: remove the extra argument</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- String::from(""),</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,41 @@
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: &lt;sample error message&gt;</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>&lt;sample path&gt;:LL:18</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">6</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> ts.into_iter()</tspan><tspan class="fg-bright-red">.map(|t| {</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-red">- (is_true, t)</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">8</tspan><tspan> </tspan><tspan class="fg-bright-red">- })</tspan><tspan>.flatten()</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">6</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> ts.into_iter().flatten()</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,21 @@
use annotate_snippets::{Level, Patch, Renderer, Snippet};
use snapbox::{assert_data_eq, file};
#[test]
fn test() {
let report = &[Level::ERROR
.primary_title("<sample error message>")
.element(
Snippet::source(
"\t\t\tts.into_iter().map(|t| {\n\t\t\t\t(is_true, t)\n\t\t\t}).flatten()\n",
)
.line_start(6)
.path("<sample path>")
.patch(Patch::new(17..50, "")),
)];
let expected_ascii = file!["highlight_first_line_tab_371.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
}
@@ -19,19 +19,19 @@
<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 class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">call-non-callable</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: all annotation kinds have a highlighted source</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/main.py:5:9</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>Cargo.toml:9:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> def f():</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> [workspace.lints.rust]</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">5 |</tspan><tspan> return (1 == '2')() # Tab indented</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unused_qualifications</tspan><tspan> = </tspan><tspan class="fg-bright-blue bold">"warn"</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">------</tspan>
</tspan>
</text>
@@ -0,0 +1,43 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"
[workspace.lints.rust]
rust_2018_idioms = { level = "warn", priority = -1 }
unnameable_types = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
"#;
let input = &[Level::ERROR
.primary_title("all annotation kinds have a highlighted source")
.element(
Snippet::source(source)
.path("Cargo.toml")
.annotation(
AnnotationKind::Primary
.span(214..235)
.highlight_source(true),
)
.annotation(
AnnotationKind::Context
.span(238..244)
.highlight_source(true),
)
.annotation(AnnotationKind::Visible.span(2..22).highlight_source(true)),
)];
let expected_ascii = file!["highlight_source.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["highlight_source.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,38 @@
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: all annotation kinds have a highlighted source</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>Cargo.toml:9:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> [workspace.lints.rust]</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unused_qualifications</tspan><tspan> = </tspan><tspan class="fg-bright-blue bold">"warn"</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">━━━━━━━━━━━━━━━━━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,30 @@
<svg width="740px" height="74px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> [lorem ipsum](</tspan><tspan class="fg-yellow bold">表意文字</tspan><tspan>)</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">^^^^^^^^</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,20 @@
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"[lorem ipsum](表意文字)"#;
let report = &[Group::with_level(Level::WARNING).element(
Snippet::source(source)
.annotation(AnnotationKind::Primary.span(14..26).highlight_source(true)),
)];
let expected_ascii = file!["highlight_source_multi_width_chars.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode = file!["highlight_source_multi_width_chars.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
@@ -0,0 +1,30 @@
<svg width="740px" height="74px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> [lorem ipsum](</tspan><tspan class="fg-yellow bold">表意文字</tspan><tspan>)</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-yellow bold">━━━━━━━━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,30 @@
<svg width="740px" height="74px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> [</tspan><tspan class="fg-yellow bold">résumé</tspan><tspan>] [</tspan><tspan class="fg-yellow bold">naïve</tspan><tspan>]</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">^^^^^^</tspan><tspan> </tspan><tspan class="fg-yellow bold">^^^^^</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,21 @@
use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"[résumé] [naïve]"#;
let report = &[Group::with_level(Level::WARNING).element(
Snippet::source(source)
.annotation(AnnotationKind::Primary.span(1..11).highlight_source(true))
.annotation(AnnotationKind::Primary.span(14..21).highlight_source(true)),
)];
let expected_ascii = file!["highlight_source_zero_width_chars.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode = file!["highlight_source_zero_width_chars.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
@@ -0,0 +1,30 @@
<svg width="740px" height="74px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> [</tspan><tspan class="fg-yellow bold">résumé</tspan><tspan>] [</tspan><tspan class="fg-yellow bold">naïve</tspan><tspan>]</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-yellow bold">━━━━━━</tspan><tspan> </tspan><tspan class="fg-yellow bold">━━━━━</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,10 +1,9 @@
<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.fg-yellow { fill: #AA5500 }
.container {
padding: 0 10px;
line-height: 18px;
@@ -20,29 +19,27 @@
<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 class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt; </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:9:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> let x = vec![1];</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">8</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
@@ -0,0 +1,33 @@
use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn case() {
let source = r#"let x = vec![1];
let y = x;
x;
"#;
let input = &[Level::ERROR.primary_title("expected one of `.`, `;`, `?`, or an operator, found `for`")
.element(
Snippet::source(source)
.path("/code/rust/src/test/ui/annotate-snippet/suggestion.rs")
.line_start(4)
.annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"))
.annotation(AnnotationKind::Context.span(27..28).label("value moved here"))
.annotation(AnnotationKind::Primary.span(31..32).label("value used here after move"))
)
];
let expected_ascii = file!["issue_9.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(input), expected_ascii);
let expected_unicode = file!["issue_9.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(input), expected_unicode);
}
@@ -0,0 +1,46 @@
<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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 class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:9:1</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let x = vec![1];</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">─</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">move occurs because `x` has type `std::vec::Vec&lt;i32&gt;`, which does not implement the `Copy` trait</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">┆</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let y = x;</tspan>
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">─</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">8</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> x;</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan class="fg-bright-red bold">━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,32 @@
mod ann_eof;
mod ann_insertion;
mod ann_multiline;
mod ann_multiline2;
mod ann_removed_nl;
mod ensure_emoji_highlight_width;
mod first_snippet_is_primary;
mod fold_ann_multiline;
mod fold_bad_origin_line;
mod fold_leading;
mod fold_trailing;
mod highlight_diff_line_with_wide_characters;
mod highlight_duplicated_diff_lines;
mod highlight_first_line_tab_371;
mod highlight_source;
mod highlight_source_multi_width_chars;
mod highlight_source_zero_width_chars;
mod issue_9;
mod multiline_removal_indent;
mod multiline_removal_last_line_tabs;
mod multiline_removal_suggestion;
mod multiple_annotations;
mod multiple_highlight_duplicated;
mod multiple_multiline_removal;
mod primary_title_second_group;
mod regression_leading_tab_label_alignment;
mod regression_leading_tab_long_line;
mod simple;
mod strip_line;
mod strip_line_char;
mod strip_line_non_ws;
mod styled_title;
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">local function f()</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-red">- print()</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-red">- end</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,19 @@
use annotate_snippets::{Group, Level, Patch, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn test() {
let report = &[Group::with_level(Level::ERROR).element(
Snippet::source("do\n local function f()\n print()\n end\nend\n")
.patch(Patch::new(5..41, "")),
)];
let expected_ascii = file!["multiline_removal_indent.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode = file!["multiline_removal_indent.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
@@ -0,0 +1,34 @@
<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">local function f()</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-red">- print()</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-red">- end</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,37 @@
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-green { fill: #55FF55 }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
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> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan>a</tspan><tspan class="fg-bright-red">.foo(|t| {</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-red">- b</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-red">- })</tspan><tspan>.bar()</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan>a.bar()</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,18 @@
use annotate_snippets::{Group, Level, Patch, Renderer, Snippet, renderer::DecorStyle};
use snapbox::{assert_data_eq, file};
#[test]
fn test() {
let report = &[Group::with_level(Level::ERROR).element(
Snippet::source("a.foo(|t| {\n\t\t\tb\n\t\t\t}).bar()\n").patch(Patch::new(1..22, "")),
)];
let expected_ascii = file!["multiline_removal_last_line_tabs.ascii.term.svg": TermSvg];
let renderer = Renderer::styled();
assert_data_eq!(renderer.render(report), expected_ascii);
let expected_unicode = file!["multiline_removal_last_line_tabs.unicode.term.svg": TermSvg];
let renderer = renderer.decor_style(DecorStyle::Unicode);
assert_data_eq!(renderer.render(report), expected_unicode);
}
Loaded 100 of 1758 files, more files were not shown because too many files have changed in this diff. Show more