summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--CHANGELOG.md43
-rw-r--r--Cargo.lock111
-rw-r--r--Cargo.toml2
-rw-r--r--Cargo.toml.orig2
-rw-r--r--METADATA12
-rw-r--r--src/diagnostic.rs2
-rw-r--r--src/files.rs1
-rw-r--r--src/term/config.rs80
-rw-r--r--src/term/renderer.rs569
-rw-r--r--src/term/views.rs55
-rw-r--r--tests/snapshots/term__empty_ranges__rich_color.snap17
-rw-r--r--tests/snapshots/term__empty_ranges__rich_no_color.snap11
-rw-r--r--tests/snapshots/term__empty_ranges__short_color.snap3
-rw-r--r--tests/snapshots/term__empty_ranges__short_no_color.snap3
-rw-r--r--tests/snapshots/term__fizz_buzz__rich_color.snap18
-rw-r--r--tests/snapshots/term__multifile__rich_color.snap14
-rw-r--r--tests/snapshots/term__multiline_overlapping__rich_color.snap12
-rw-r--r--tests/snapshots/term__overlapping__rich_color.snap58
-rw-r--r--tests/snapshots/term__overlapping__rich_no_color.snap58
-rw-r--r--tests/snapshots/term__overlapping__short_color.snap11
-rw-r--r--tests/snapshots/term__overlapping__short_no_color.snap11
-rw-r--r--tests/snapshots/term__same_line__rich_color.snap (renamed from tests/snapshots/term__one_line__rich_color.snap)10
-rw-r--r--tests/snapshots/term__same_line__rich_no_color.snap (renamed from tests/snapshots/term__one_line__rich_no_color.snap)8
-rw-r--r--tests/snapshots/term__same_line__short_color.snap (renamed from tests/snapshots/term__one_line__short_color.snap)0
-rw-r--r--tests/snapshots/term__same_line__short_no_color.snap (renamed from tests/snapshots/term__one_line__short_no_color.snap)0
-rw-r--r--tests/snapshots/term__same_ranges__rich_color.snap9
-rw-r--r--tests/snapshots/term__same_ranges__rich_no_color.snap7
-rw-r--r--tests/snapshots/term__tab_columns__tab_width_2_no_color.snap23
-rw-r--r--tests/snapshots/term__tab_columns__tab_width_3_no_color.snap23
-rw-r--r--tests/snapshots/term__tab_columns__tab_width_6_no_color.snap23
-rw-r--r--tests/snapshots/term__tab_columns__tab_width_default_no_color.snap23
-rw-r--r--tests/snapshots/term__tabbed__tab_width_3_no_color.snap7
-rw-r--r--tests/snapshots/term__tabbed__tab_width_6_no_color.snap7
-rw-r--r--tests/snapshots/term__tabbed__tab_width_default_no_color.snap7
-rw-r--r--tests/snapshots/term__unicode_spans__rich_no_color.snap20
-rw-r--r--tests/snapshots/term__unicode_spans__short_no_color.snap3
-rw-r--r--tests/term.rs268
38 files changed, 1113 insertions, 420 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index a7c24e4..4139f46 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "8b22d900058f407646e3c12d353abb890bc78816"
+ "sha1": "50bceff36e169673b49bfffa1809bba6be9bd07f"
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93532b5..abd9bb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.9.5] - 2020-06-24
+
+### Changed
+
+- Sections of source code that are marked with primary labels are now rendered
+ using the primary highlight color.
+
+## [0.9.4] - 2020-05-18
+
+### Changed
+
+- We have made the caret rendering easier to read when there are multiple
+ labels on the same line. We also avoid printing trailing borders on the
+ final source source snippet if no notes are present. Instead of this:
+ ```
+ ┌─ one_line.rs:3:5
+ │
+ 3 │ v.push(v.pop().unwrap());
+ │ - first borrow later used by call
+ │ ---- first mutable borrow occurs here
+ │ ^ second mutable borrow occurs here
+ │
+ ```
+ …we now render the following:
+ ```
+ ┌─ one_line.rs:3:5
+ │
+ 3 │ v.push(v.pop().unwrap());
+ │ - ---- ^ second mutable borrow occurs here
+ │ │ │
+ │ │ first mutable borrow occurs here
+ │ first borrow later used by call
+ ```
+
+### Fixed
+
+- Diagnostic rendering no longer panics if label ranges are between UTF-8
+ character boundaries.
+
## [0.9.3] - 2020-04-29
### Changed
@@ -206,7 +245,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.2.1] - 2019-02-26
## [0.2.0] - 2018-10-11
-[Unreleased]: https://github.com/brendanzab/codespan/compare/v0.9.3...HEAD
+[Unreleased]: https://github.com/brendanzab/codespan/compare/v0.9.5...HEAD
+[0.9.4]: https://github.com/brendanzab/codespan/compare/v0.9.4...v0.9.5
+[0.9.4]: https://github.com/brendanzab/codespan/compare/v0.9.3...v0.9.4
[0.9.3]: https://github.com/brendanzab/codespan/compare/v0.9.2...v0.9.3
[0.9.2]: https://github.com/brendanzab/codespan/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/brendanzab/codespan/compare/v0.9.0...v0.9.1
diff --git a/Cargo.lock b/Cargo.lock
index e7aed11..e41e6d0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,9 +11,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.28"
+version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
+checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
[[package]]
name = "arrayref"
@@ -69,9 +69,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.52"
+version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
+checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
[[package]]
name = "cfg-if"
@@ -81,9 +81,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clap"
-version = "2.33.0"
+version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
+checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
dependencies = [
"ansi_term",
"atty",
@@ -108,7 +108,7 @@ dependencies = [
[[package]]
name = "codespan-reporting"
-version = "0.9.3"
+version = "0.9.5"
dependencies = [
"anyhow",
"insta",
@@ -161,22 +161,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
-name = "dirs"
-version = "2.0.2"
+name = "dirs-next"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8"
dependencies = [
"cfg-if",
- "dirs-sys",
+ "dirs-sys-next",
]
[[package]]
-name = "dirs-sys"
-version = "0.3.4"
+name = "dirs-sys-next"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
+checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a"
dependencies = [
- "cfg-if",
"libc",
"redox_users",
"winapi",
@@ -184,9 +183,9 @@ dependencies = [
[[package]]
name = "dtoa"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "encode_unicode"
@@ -216,9 +215,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.1.12"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
+checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
dependencies = [
"libc",
]
@@ -239,9 +238,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "lazy_static"
@@ -251,15 +250,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.69"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "linked-hash-map"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "log"
@@ -344,18 +343,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.10"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
+checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
-version = "1.0.3"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
@@ -391,12 +390,12 @@ dependencies = [
[[package]]
name = "rustyline"
-version = "6.1.2"
+version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd20b28d972040c627e209eb29f19c24a71a19d661cc5a220089176e20ee202"
+checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777"
dependencies = [
"cfg-if",
- "dirs",
+ "dirs-next",
"libc",
"log",
"memchr",
@@ -410,9 +409,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
@@ -422,18 +421,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
-version = "1.0.106"
+version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
+checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.106"
+version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
+checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
dependencies = [
"proc-macro2",
"quote",
@@ -442,9 +441,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.52"
+version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd"
+checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226"
dependencies = [
"itoa",
"ryu",
@@ -453,9 +452,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
-version = "0.8.11"
+version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
+checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
dependencies = [
"dtoa",
"linked-hash-map",
@@ -471,9 +470,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
-version = "0.3.14"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef"
+checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c"
dependencies = [
"clap",
"lazy_static",
@@ -482,9 +481,9 @@ dependencies = [
[[package]]
name = "structopt-derive"
-version = "0.4.7"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a"
+checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118"
dependencies = [
"heck",
"proc-macro-error",
@@ -495,9 +494,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.18"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213"
+checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
dependencies = [
"proc-macro2",
"quote",
@@ -572,9 +571,9 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "unindent"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993"
+checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
[[package]]
name = "utf8parse"
@@ -584,15 +583,15 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "vec_map"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
-version = "0.9.1"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "void"
@@ -639,9 +638,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
+checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
dependencies = [
"linked-hash-map",
]
diff --git a/Cargo.toml b/Cargo.toml
index e55a44f..294d127 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "codespan-reporting"
-version = "0.9.3"
+version = "0.9.5"
authors = ["Brendan Zabarauskas <bjzaba@yahoo.com.au>"]
exclude = ["assets/**"]
description = "Beautiful diagnostic reporting for text-based programming languages"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index f7bb15f..427e854 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "codespan-reporting"
-version = "0.9.3"
+version = "0.9.5"
readme = "../README.md"
license = "Apache-2.0"
authors = ["Brendan Zabarauskas <bjzaba@yahoo.com.au>"]
diff --git a/METADATA b/METADATA
index 5033068..56c195b 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,5 @@
name: "codespan-reporting"
-description:
- "Beautiful diagnostic reporting for text-based programming languages."
-
+description: "Beautiful diagnostic reporting for text-based programming languages."
third_party {
url {
type: HOMEPAGE
@@ -11,7 +9,11 @@ third_party {
type: GIT
value: "https://github.com/brendanzab/codespan"
}
- version: "0.9.3"
- last_upgrade_date { year: 2020 month: 5 day: 11 }
+ version: "0.9.5"
license_type: NOTICE
+ last_upgrade_date {
+ year: 2020
+ month: 7
+ day: 10
+ }
}
diff --git a/src/diagnostic.rs b/src/diagnostic.rs
index 6655a91..559c899 100644
--- a/src/diagnostic.rs
+++ b/src/diagnostic.rs
@@ -67,7 +67,7 @@ pub struct Label<FileId> {
pub style: LabelStyle,
/// The file that we are labelling.
pub file_id: FileId,
- /// The range we are going to include in the final snippet.
+ /// The range in bytes we are going to include in the final snippet.
pub range: Range<usize>,
/// An optional message to provide some additional information for the
/// underlined code. These should not include line breaks.
diff --git a/src/files.rs b/src/files.rs
index a717555..41691b9 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -191,6 +191,7 @@ pub fn column_index(source: &str, line_range: Range<usize>, byte_index: usize) -
///
/// assert_eq!(line_index(&line_starts, 5), Some(1));
/// ```
+// NOTE: this is copied in `codespan::file::line_starts` and should be kept in sync.
pub fn line_starts<'source>(source: &'source str) -> impl 'source + Iterator<Item = usize> {
std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
}
diff --git a/src/term/config.rs b/src/term/config.rs
index a3bd03a..7b834d7 100644
--- a/src/term/config.rs
+++ b/src/term/config.rs
@@ -1,4 +1,3 @@
-use std::io;
use termcolor::{Color, ColorSpec};
use crate::diagnostic::{LabelStyle, Severity};
@@ -31,58 +30,6 @@ impl Default for Config {
}
}
-impl Config {
- /// Measure the width of a string, taking into account the tab width.
- pub fn width(&self, s: &str) -> usize {
- use unicode_width::UnicodeWidthChar;
-
- s.chars()
- .map(|ch| match ch {
- '\t' => self.tab_width,
- _ => ch.width().unwrap_or(0),
- })
- .sum()
- }
-
- /// Construct a source writer using the current config.
- pub fn source<'a, W: ?Sized>(&self, writer: &'a mut W) -> SourceWriter<&'a mut W> {
- SourceWriter {
- upstream: writer,
- tab_width: self.tab_width,
- }
- }
-}
-
-/// Writer that replaces tab characters with the configured number of spaces.
-pub struct SourceWriter<W> {
- upstream: W,
- tab_width: usize,
-}
-
-impl<W: io::Write> io::Write for SourceWriter<W> {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- let mut last_term = 0usize;
- for (i, ch) in buf.iter().enumerate() {
- if *ch == b'\t' {
- self.upstream.write_all(&buf[last_term..i])?;
- last_term = i + 1;
- write!(
- self.upstream,
- "{space: >width$}",
- space = "",
- width = self.tab_width,
- )?;
- }
- }
- self.upstream.write_all(&buf[last_term..])?;
- Ok(buf.len())
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.upstream.flush()
- }
-}
-
/// The display style to use when rendering diagnostics.
#[derive(Clone, Debug)]
pub enum DisplayStyle {
@@ -279,29 +226,10 @@ pub struct Chars {
/// The character to use for the left of a multi-line label.
/// Defaults to: `'│'`.
pub multi_left: char,
-}
-impl Chars {
- pub fn single_caret_char(&self, label_style: LabelStyle) -> char {
- match label_style {
- LabelStyle::Primary => self.single_primary_caret,
- LabelStyle::Secondary => self.single_secondary_caret,
- }
- }
-
- pub fn multi_caret_char_start(&self, label_style: LabelStyle) -> char {
- match label_style {
- LabelStyle::Primary => self.multi_primary_caret_start,
- LabelStyle::Secondary => self.multi_secondary_caret_start,
- }
- }
-
- pub fn multi_caret_char_end(&self, label_style: LabelStyle) -> char {
- match label_style {
- LabelStyle::Primary => self.multi_primary_caret_end,
- LabelStyle::Secondary => self.multi_secondary_caret_end,
- }
- }
+ /// The character to use for the left of a pointer underneath a caret.
+ /// Defaults to: `'│'`.
+ pub pointer_left: char,
}
impl Default for Chars {
@@ -326,6 +254,8 @@ impl Default for Chars {
multi_bottom_left: '╰',
multi_bottom: '─',
multi_left: '│',
+
+ pointer_left: '│',
}
}
}
diff --git a/src/term/renderer.rs b/src/term/renderer.rs
index a8b8601..ab5656e 100644
--- a/src/term/renderer.rs
+++ b/src/term/renderer.rs
@@ -30,25 +30,25 @@ pub enum MultiLabel<'diagnostic> {
/// ```text
/// ╭
/// ```
- TopLeft(LabelStyle),
+ TopLeft,
/// Multi-line label top.
///
/// ```text
/// ╭────────────^
/// ```
- Top(LabelStyle, RangeTo<usize>),
+ Top(RangeTo<usize>),
/// Left vertical labels for multi-line labels.
///
/// ```text
/// │
/// ```
- Left(LabelStyle),
+ Left,
/// Multi-line label bottom, with an optional message.
///
/// ```text
/// ╰────────────^ blah blah
/// ```
- Bottom(LabelStyle, RangeTo<usize>, &'diagnostic str),
+ Bottom(RangeTo<usize>, &'diagnostic str),
}
#[derive(Copy, Clone)]
@@ -71,7 +71,6 @@ type Underline = (LabelStyle, VerticalBound);
/// │ │ │ │ │
/// ┌────────────────────────────────────────────────────────────────────────────
/// header ── │ error[0001]: oh noes, a cupcake has occurred!
-/// empty ── │
/// snippet start ── │ ┌─ test:9:0
/// snippet empty ── │ │
/// snippet line ── │ 9 │ ╭ Cupcake ipsum dolor. Sit amet marshmallow topping cheesecake
@@ -84,8 +83,10 @@ type Underline = (LabelStyle, VerticalBound);
/// snippet break ── │ · │
/// snippet line ── │ 38 │ │ Brownie lemon drops chocolate jelly-o candy canes. Danish marzipan
/// snippet line ── │ 39 │ │ jujubes soufflé carrot cake marshmallow tiramisu caramels candy canes.
-/// │ │ │ ^^^^^^^^^^^^^^^^^^ blah blah
-/// │ │ │ -------------------- blah blah
+/// │ │ │ ^^^^^^^^^^^^^^^^^^^ -------------------- blah blah
+/// │ │ │ │
+/// │ │ │ blah blah
+/// │ │ │ note: this is a note
/// snippet line ── │ 40 │ │ Fruitcake jelly-o danish toffee. Tootsie roll pastry cheesecake
/// snippet line ── │ 41 │ │ soufflé marzipan. Chocolate bar oat cake jujubes lollipop pastry
/// snippet line ── │ 42 │ │ cupcake. Candy canes cupcake toffee gingerbread candy canes muffin
@@ -227,8 +228,12 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
severity: Severity,
single_labels: &[SingleLabel<'_>],
num_multi_labels: usize,
- multi_labels: &[(usize, MultiLabel<'_>)],
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
) -> io::Result<()> {
+ // Trim trailing newlines, linefeeds, and null chars from source, if they exist.
+ // FIXME: Use the number of trimmed placeholders when rendering single line carets
+ let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
+
// Write source line
//
// ```text
@@ -243,37 +248,272 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
let mut multi_labels_iter = multi_labels.iter().peekable();
for label_column in 0..num_multi_labels {
match multi_labels_iter.peek() {
- Some((label_index, label)) if *label_index == label_column => {
+ Some((label_index, label_style, label)) if *label_index == label_column => {
match label {
- MultiLabel::TopLeft(label_style) => {
+ MultiLabel::TopLeft => {
self.label_multi_top_left(severity, *label_style)?;
}
MultiLabel::Top(..) => self.inner_gutter_space()?,
- MultiLabel::Left(label_style) | MultiLabel::Bottom(label_style, ..) => {
+ MultiLabel::Left | MultiLabel::Bottom(..) => {
self.label_multi_left(severity, *label_style, None)?;
}
}
multi_labels_iter.next();
}
- Some((_, _)) | None => self.inner_gutter_space()?,
+ Some((_, _, _)) | None => self.inner_gutter_space()?,
}
}
- // Write source
- write!(self.config.source(self.writer), " {}", source.trim_end())?;
+ // Write source text
+ write!(self, " ")?;
+ let mut in_primary = false;
+ for (metrics, ch) in self.char_metrics(source.char_indices()) {
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+
+ // Check if we are overlapping a primary label
+ let is_primary = single_labels.iter().any(|(ls, range, _)| {
+ *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
+ }) || multi_labels.iter().any(|(_, ls, label)| {
+ *ls == LabelStyle::Primary
+ && match label {
+ MultiLabel::Top(range) => column_range.start >= range.end,
+ MultiLabel::TopLeft | MultiLabel::Left => true,
+ MultiLabel::Bottom(range, _) => column_range.end <= range.end,
+ }
+ });
+
+ // Set the source color if we are in a primary label
+ if is_primary && !in_primary {
+ self.set_color(self.styles().label(severity, LabelStyle::Primary))?;
+ in_primary = true;
+ } else if !is_primary && in_primary {
+ self.reset()?;
+ in_primary = false;
+ }
+
+ match ch {
+ '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
+ _ => write!(self, "{}", ch)?,
+ }
+ }
+ if in_primary {
+ self.reset()?;
+ }
write!(self, "\n")?;
}
// Write single labels underneath source
//
// ```text
- // │ │ │ ^^^^ oh noes
+ // │ - ---- ^^^ second mutable borrow occurs here
+ // │ │ │
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
// ```
- for (label_style, range, message) in single_labels.iter() {
+ if !single_labels.is_empty() {
+ // Our plan is as follows:
+ //
+ // 1. Do an initial scan to find:
+ // - The number of non-empty messages.
+ // - The right-most start and end positions of labels.
+ // - A candidate for a trailing label (where the label's message
+ // is printed to the left of the caret).
+ // 2. Check if the trailing label candidate overlaps another label -
+ // if so we print it underneath the carets with the other labels.
+ // 3. Print a line of carets, and (possibly) the trailing message
+ // to the left.
+ // 4. Print vertical lines pointing to the carets, and the messages
+ // for those carets.
+ //
+ // We try our best avoid introducing new dynamic allocations,
+ // instead preferring to iterate over the labels multiple times. It
+ // is unclear what the performance tradeoffs are however, so further
+ // investigation may be required.
+
+ // The number of non-empty messages to print.
+ let mut num_messages = 0;
+ // The right-most start position, eg:
+ //
+ // ```text
+ // -^^^^---- ^^^^^^^
+ // │
+ // right-most start position
+ // ```
+ let mut max_label_start = 0;
+ // The right-most end position, eg:
+ //
+ // ```text
+ // -^^^^---- ^^^^^^^
+ // │
+ // right-most end position
+ // ```
+ let mut max_label_end = 0;
+ // A trailing message, eg:
+ //
+ // ```text
+ // ^^^ second mutable borrow occurs here
+ // ```
+ let mut trailing_label = None;
+
+ for (label_index, label) in single_labels.iter().enumerate() {
+ let (_, range, message) = label;
+ if !message.is_empty() {
+ num_messages += 1;
+ }
+ max_label_start = std::cmp::max(max_label_start, range.start);
+ max_label_end = std::cmp::max(max_label_end, range.end);
+ // This is a candidate for the trailing label, so let's record it.
+ if range.end == max_label_end {
+ if message.is_empty() {
+ trailing_label = None;
+ } else {
+ trailing_label = Some((label_index, label));
+ }
+ }
+ }
+ if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
+ // Check to see if the trailing label candidate overlaps any of
+ // the other labels on the current line.
+ if single_labels
+ .iter()
+ .enumerate()
+ .filter(|(label_index, _)| *label_index != trailing_label_index)
+ .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
+ {
+ // If it does, we'll instead want to render it below the
+ // carets along with the other hanging labels.
+ trailing_label = None;
+ }
+ }
+
+ // Write a line of carets
+ //
+ // ```text
+ // │ ^^^^^^ -------^^^^^^^^^-------^^^^^----- ^^^^ trailing label message
+ // ```
self.outer_gutter(outer_padding)?;
self.border_left()?;
self.inner_gutter(severity, num_multi_labels, multi_labels)?;
- self.label_single(severity, *label_style, source, range.clone(), message)?;
+ write!(self, " ")?;
+
+ let mut previous_label_style = None;
+ let placeholder_metrics = Metrics {
+ byte_index: source.len(),
+ unicode_width: 1,
+ };
+ for (metrics, ch) in self
+ .char_metrics(source.char_indices())
+ // Add a placeholder source column at the end to allow for
+ // printing carets at the end of lines, eg:
+ //
+ // ```text
+ // 1 │ Hello world!
+ // │ ^
+ // ```
+ .chain(std::iter::once((placeholder_metrics, '\0')))
+ {
+ // Find the current label style at this column
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+ let current_label_style = single_labels
+ .iter()
+ .filter(|(_, range, _)| is_overlapping(range, &column_range))
+ .map(|(label_style, _, _)| *label_style)
+ .max_by_key(label_priority_key);
+
+ // Update writer style if necessary
+ if previous_label_style != current_label_style {
+ match current_label_style {
+ None => self.reset()?,
+ Some(label_style) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ }
+ }
+ }
+
+ let caret_ch = match current_label_style {
+ Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
+ Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
+ // Only print padding if we are before the end of the last single line caret
+ None if metrics.byte_index < max_label_end => Some(' '),
+ None => None,
+ };
+ if let Some(caret_ch) = caret_ch {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?;
+ }
+
+ previous_label_style = current_label_style;
+ }
+ // Reset style if it was previously set
+ if previous_label_style.is_some() {
+ self.reset()?;
+ }
+ // Write first trailing label message
+ if let Some((_, (label_style, _, message))) = trailing_label {
+ write!(self, " ")?;
+ self.set_color(self.styles().label(severity, *label_style))?;
+ write!(self, "{}", message)?;
+ self.reset()?;
+ }
+ write!(self, "\n")?;
+
+ // Write hanging labels pointing to carets
+ //
+ // ```text
+ // │ │ │
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
+ // ```
+ if num_messages > trailing_label.iter().count() {
+ // Write first set of vertical lines before hanging labels
+ //
+ // ```text
+ // │ │ │
+ // ```
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ write!(self, " ")?;
+ self.caret_pointers(
+ severity,
+ max_label_start,
+ single_labels,
+ trailing_label,
+ source.char_indices(),
+ )?;
+ write!(self, "\n")?;
+
+ // Write hanging labels pointing to carets
+ //
+ // ```text
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
+ // ```
+ for (label_style, range, message) in
+ hanging_labels(single_labels, trailing_label).rev()
+ {
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ write!(self, " ")?;
+ self.caret_pointers(
+ severity,
+ max_label_start,
+ single_labels,
+ trailing_label,
+ source
+ .char_indices()
+ .take_while(|(byte_index, _)| *byte_index < range.start),
+ )?;
+ self.set_color(self.styles().label(severity, *label_style))?;
+ write!(self, "{}", message)?;
+ self.reset()?;
+ write!(self, "\n")?;
+ }
+ }
}
// Write top or bottom label carets underneath source
@@ -282,11 +522,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
// │ ╰───│──────────────────^ woops
// │ ╭─│─────────^
// ```
- for (multi_label_index, (_, label)) in multi_labels.iter().enumerate() {
+ for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
let (label_style, range, bottom_message) = match label {
- MultiLabel::TopLeft(_) | MultiLabel::Left(_) => continue, // no label caret needed
- MultiLabel::Top(ls, range) => (*ls, range, None),
- MultiLabel::Bottom(ls, range, message) => (*ls, range, Some(message)),
+ MultiLabel::TopLeft | MultiLabel::Left => continue, // no label caret needed
+ MultiLabel::Top(range) => (*label_style, range, None),
+ MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
};
self.outer_gutter(outer_padding)?;
@@ -301,22 +541,22 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
for label_column in 0..num_multi_labels {
match multi_labels_iter.peek() {
- Some((i, (label_index, label))) if *label_index == label_column => {
+ Some((i, (label_index, ls, label))) if *label_index == label_column => {
match label {
- MultiLabel::TopLeft(ls) | MultiLabel::Left(ls) => {
+ MultiLabel::TopLeft | MultiLabel::Left => {
self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
}
- MultiLabel::Top(ls, ..) if multi_label_index > *i => {
+ MultiLabel::Top(..) if multi_label_index > *i => {
self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
}
- MultiLabel::Bottom(ls, ..) if multi_label_index < *i => {
+ MultiLabel::Bottom(..) if multi_label_index < *i => {
self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
}
- MultiLabel::Top(ls, ..) if multi_label_index == *i => {
+ MultiLabel::Top(..) if multi_label_index == *i => {
underline = Some((*ls, VerticalBound::Top));
self.label_multi_top_left(severity, label_style)?
}
- MultiLabel::Bottom(ls, ..) if multi_label_index == *i => {
+ MultiLabel::Bottom(..) if multi_label_index == *i => {
underline = Some((*ls, VerticalBound::Bottom));
self.label_multi_bottom_left(severity, label_style)?;
}
@@ -353,7 +593,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
outer_padding: usize,
severity: Severity,
num_multi_labels: usize,
- multi_labels: &[(usize, MultiLabel<'_>)],
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
) -> io::Result<()> {
self.outer_gutter(outer_padding)?;
self.border_left()?;
@@ -372,7 +612,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
outer_padding: usize,
severity: Severity,
num_multi_labels: usize,
- multi_labels: &[(usize, MultiLabel<'_>)],
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
) -> io::Result<()> {
self.outer_gutter(outer_padding)?;
self.border_left_break()?;
@@ -406,12 +646,39 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
Ok(())
}
+ /// Adds tab-stop aware unicode-width computations to an iterator over
+ /// character indices. Assumes that the character indices begin at the start
+ /// of the line.
+ fn char_metrics(
+ &self,
+ char_indices: impl Iterator<Item = (usize, char)>,
+ ) -> impl Iterator<Item = (Metrics, char)> {
+ use unicode_width::UnicodeWidthChar;
+
+ let tab_width = self.config.tab_width;
+ let mut unicode_column = 0;
+
+ char_indices.map(move |(byte_index, ch)| {
+ let metrics = Metrics {
+ byte_index,
+ unicode_width: match (ch, tab_width) {
+ ('\t', 0) => 0, // Guard divide-by-zero
+ ('\t', _) => tab_width - (unicode_column % tab_width),
+ (ch, _) => ch.width().unwrap_or(0),
+ },
+ };
+ unicode_column += metrics.unicode_width;
+
+ (metrics, ch)
+ })
+ }
+
/// Location focus.
fn snippet_locus(&mut self, locus: &Locus) -> io::Result<()> {
write!(
self,
- "{origin}:{line_number}:{column_number}",
- origin = locus.name,
+ "{name}:{line_number}:{column_number}",
+ name = locus.name,
line_number = locus.location.line_number,
column_number = locus.location.column_number,
)
@@ -419,7 +686,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
/// The outer gutter of a source line.
fn outer_gutter(&mut self, outer_padding: usize) -> io::Result<()> {
- write!(self, "{space: >width$}", space = "", width = outer_padding,)?;
+ write!(self, "{space: >width$}", space = "", width = outer_padding)?;
write!(self, " ")?;
Ok(())
}
@@ -454,34 +721,37 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
Ok(())
}
- /// Single-line label with a message.
- ///
- /// ```text
- /// ^^ expected `Int` but found `String`
- /// ```
- fn label_single(
+ /// Write vertical lines pointing to carets.
+ fn caret_pointers(
&mut self,
severity: Severity,
- label_style: LabelStyle,
- source: &str,
- range: Range<usize>,
- message: &str,
+ max_label_start: usize,
+ single_labels: &[SingleLabel<'_>],
+ trailing_label: Option<(usize, &SingleLabel<'_>)>,
+ char_indices: impl Iterator<Item = (usize, char)>,
) -> io::Result<()> {
- let space_source = slice_at_char_boundaries(source, 0..range.start);
- let space_len = self.config.width(space_source);
- write!(self, " {space: >width$}", space = "", width = space_len)?;
- self.set_color(self.styles().label(severity, label_style))?;
- let source = slice_at_char_boundaries(source, range);
- // We use `usize::max` here to ensure that we print at least one
- // label character - even when we have a zero-length span.
- for _ in 0..usize::max(self.config.width(source), 1) {
- write!(self, "{}", self.chars().single_caret_char(label_style))?;
- }
- if !message.is_empty() {
- write!(self, " {}", message)?;
+ for (metrics, ch) in self.char_metrics(char_indices) {
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+ let label_style = hanging_labels(single_labels, trailing_label)
+ .filter(|(_, range, _)| column_range.contains(&range.start))
+ .map(|(label_style, _, _)| *label_style)
+ .max_by_key(label_priority_key);
+
+ let mut spaces = match label_style {
+ None => 0..metrics.unicode_width,
+ Some(label_style) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().pointer_left)?;
+ self.reset()?;
+ 1..metrics.unicode_width
+ }
+ };
+ // Only print padding if we are before the end of the last single line caret
+ if metrics.byte_index <= max_label_start {
+ spaces.try_for_each(|_| write!(self, " "))?;
+ }
}
- self.reset()?;
- write!(self, "\n")?;
+
Ok(())
}
@@ -511,36 +781,6 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
Ok(())
}
- /// The top of a multi-line label.
- fn label_multi_top_line(
- &mut self,
- severity: Severity,
- label_style: LabelStyle,
- len: usize,
- ) -> io::Result<()> {
- self.set_color(self.styles().label(severity, label_style))?;
- for _ in 0..len {
- write!(self, "{}", self.config.chars.multi_top)?;
- }
- self.reset()?;
- Ok(())
- }
-
- /// The top of a multi-line label.
- fn label_multi_bottom_line(
- &mut self,
- severity: Severity,
- label_style: LabelStyle,
- len: usize,
- ) -> io::Result<()> {
- self.set_color(self.styles().label(severity, label_style))?;
- for _ in 0..len {
- write!(self, "{}", self.config.chars.multi_bottom)?;
- }
- self.reset()?;
- Ok(())
- }
-
/// The top-left of a multi-line label.
///
/// ```text
@@ -588,10 +828,21 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
range: RangeTo<usize>,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
- for _ in 0..(self.config.width(&source[range.clone()]) + 1) {
- write!(self, "{}", self.chars().multi_top)?;
+
+ for (metrics, _) in self
+ .char_metrics(source.char_indices())
+ .take_while(|(metrics, _)| metrics.byte_index < range.end + 1)
+ {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width)
+ .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
}
- write!(self, "{}", self.chars().multi_caret_char_start(label_style))?;
+
+ let caret_start = match label_style {
+ LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
+ LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
+ };
+ write!(self, "{}", caret_start)?;
self.reset()?;
write!(self, "\n")?;
Ok(())
@@ -611,10 +862,21 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
message: &str,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
- for _ in 0..self.config.width(&source[range.clone()]) {
- write!(self, "{}", self.chars().multi_bottom)?;
+
+ for (metrics, _) in self
+ .char_metrics(source.char_indices())
+ .take_while(|(metrics, _)| metrics.byte_index < range.end)
+ {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width)
+ .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
}
- write!(self, "{}", self.chars().multi_caret_char_end(label_style))?;
+
+ let caret_end = match label_style {
+ LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
+ LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
+ };
+ write!(self, "{}", caret_end)?;
if !message.is_empty() {
write!(self, " {}", message)?;
}
@@ -631,8 +893,16 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
) -> io::Result<()> {
match underline {
None => self.inner_gutter_space(),
- Some((ls, VerticalBound::Top)) => self.label_multi_top_line(severity, ls, 2),
- Some((ls, VerticalBound::Bottom)) => self.label_multi_bottom_line(severity, ls, 2),
+ Some((label_style, vertical_bound)) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ let ch = match vertical_bound {
+ VerticalBound::Top => self.config.chars.multi_top,
+ VerticalBound::Bottom => self.config.chars.multi_bottom,
+ };
+ write!(self, "{0}{0}", ch)?;
+ self.reset()?;
+ Ok(())
+ }
}
}
@@ -646,16 +916,14 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
&mut self,
severity: Severity,
num_multi_labels: usize,
- multi_labels: &[(usize, MultiLabel<'_>)],
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
) -> io::Result<()> {
let mut multi_labels_iter = multi_labels.iter().peekable();
for label_column in 0..num_multi_labels {
match multi_labels_iter.peek() {
- Some((label_index, label)) if *label_index == label_column => match label {
- MultiLabel::TopLeft(label_style)
- | MultiLabel::Left(label_style)
- | MultiLabel::Bottom(label_style, ..) => {
- self.label_multi_left(severity, *label_style, None)?;
+ Some((label_index, ls, label)) if *label_index == label_column => match label {
+ MultiLabel::TopLeft | MultiLabel::Left | MultiLabel::Bottom(..) => {
+ self.label_multi_left(severity, *ls, None)?;
multi_labels_iter.next();
}
MultiLabel::Top(..) => {
@@ -663,7 +931,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
multi_labels_iter.next();
}
},
- Some((_, _)) | None => self.inner_gutter_space()?,
+ Some((_, _, _)) | None => self.inner_gutter_space()?,
}
}
@@ -699,83 +967,36 @@ impl<'writer, 'config> WriteColor for Renderer<'writer, 'config> {
}
}
-/// Searches for character boundary from byte_index towards the end of the string.
-fn closest_char_boundary(s: &str, byte_index: usize) -> usize {
- let length = s.len();
- for index in byte_index..=length {
- if s.is_char_boundary(index) {
- return index;
- }
- }
- length
-}
-
-/// Searches for character boundary from byte_index towards the start of the string.
-fn closest_char_boundary_rev(s: &str, byte_index: usize) -> usize {
- for index in (0..=byte_index).rev() {
- if s.is_char_boundary(index) {
- return index;
- }
- }
- 0
+struct Metrics {
+ byte_index: usize,
+ unicode_width: usize,
}
-/// Finds a valid unicode boundaries looking from `range.start` towards the beginning of the string.
-/// From `range.end` towards the end of the string. Returning a `&str` of all characters
-/// that overlapping the range.
-fn slice_at_char_boundaries<'a>(s: &'a str, range: Range<usize>) -> &'a str {
- let start = closest_char_boundary_rev(s, range.start);
- let end = closest_char_boundary(s, range.end);
- &s[start..end]
+/// Check if two ranges overlap
+fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
+ let start = std::cmp::max(range0.start, range1.start);
+ let end = std::cmp::min(range0.end, range1.end);
+ start < end
}
-#[cfg(test)]
-mod test {
- use super::*;
- use std::iter::repeat;
-
- #[test]
- fn test_boundary() {
- let s = "🌞";
- assert_eq!(closest_char_boundary(s, 0), 0);
- assert_eq!(closest_char_boundary_rev(s, 0), 0);
- for i in 1..s.len() {
- assert_eq!(closest_char_boundary_rev(s, i), 0);
- assert_eq!(closest_char_boundary(s, i), s.len());
- }
+/// For prioritizing primary labels over secondary labels when rendering carets.
+fn label_priority_key(label_style: &LabelStyle) -> u8 {
+ match label_style {
+ LabelStyle::Secondary => 0,
+ LabelStyle::Primary => 1,
}
+}
- #[test]
- fn test_boundaries() {
- let s = "🌑🌒🌓🌔";
- let individually = ["🌑", "🌒", "🌓", "🌔"];
-
- let mut expect = Vec::new();
- // [(0, 0, "", ""),
- // (0, 4, "🌑", "🌑"), repeated 4 times,
- // (4, 4, "", ""), once
- // (4, 8, "🌒", "🌒"), repeated 4 times, and so on (+4, +4)
- // ...
- // (16, 16, "", ""); 21]
- expect.push((0, 0, "", ""));
- for (idx, &char) in individually.iter().enumerate() {
- let n = char.len();
- assert_eq!(n, 4);
- let expected_start = (idx % n) * n;
- let expected_end = (idx % n) * n + n;
- expect.extend(repeat((expected_start, expected_end, char, char)).take(n - 1));
- expect.push((expected_end, expected_end, "", ""));
- }
-
- // drop mut.
- let expect = expect;
- let mut found = Vec::new();
- for i in 0..=s.len() {
- let sliced = slice_at_char_boundaries(s, i..i);
- let prev = closest_char_boundary_rev(s, i);
- let next = closest_char_boundary(s, i);
- found.push((prev, next, &s[prev..next], sliced));
- }
- assert_eq!(found, expect);
- }
+/// Return an iterator that yields the labels that require hanging messages
+/// rendered underneath them.
+fn hanging_labels<'labels, 'diagnostic>(
+ single_labels: &'labels [SingleLabel<'diagnostic>],
+ trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
+) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
+ single_labels
+ .iter()
+ .enumerate()
+ .filter(|(_, (_, _, message))| !message.is_empty())
+ .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
+ .map(|(_, label)| label)
}
diff --git a/src/term/views.rs b/src/term/views.rs
index e6f6cc1..aa79914 100644
--- a/src/term/views.rs
+++ b/src/term/views.rs
@@ -68,7 +68,7 @@ where
range: std::ops::Range<usize>,
// TODO: How do we reuse these allocations?
single_labels: Vec<SingleLabel<'diagnostic>>,
- multi_labels: Vec<(usize, MultiLabel<'diagnostic>)>,
+ multi_labels: Vec<(usize, LabelStyle, MultiLabel<'diagnostic>)>,
}
// TODO: Make this data structure external, to allow for allocation reuse
@@ -143,14 +143,21 @@ where
// to piggyback off its lexicographic comparison implementation.
(range.start, range.end).cmp(&(label_start, label_end))
}) {
- // If the ranges are the same, order the labels as they
- // originally were specified in the diagnostic.
- Ok(index) => index + 1,
- Err(index) => index,
+ // If the ranges are the same, order the labels in reverse
+ // to how they were originally specified in the diagnostic.
+ // This helps with printing in the renderer.
+ Ok(index) | Err(index) => index,
};
+ // Ensure that we print at least one caret, even when we
+ // have a zero-length source range.
+ let mut label_range = label_start..label_end;
+ if label_range.len() == 0 {
+ label_range.end = label_range.start + 1;
+ }
+
line.single_labels
- .insert(index, (label.style, label_start..label_end, &label.message));
+ .insert(index, (label.style, label_range, &label.message));
} else {
// Multiple lines
//
@@ -182,7 +189,7 @@ where
// ```text
// 4 │ ╭ case (mod num 5) (mod num 3) of
// ```
- "" => (label_index, MultiLabel::TopLeft(label.style)),
+ "" => (label_index, label.style, MultiLabel::TopLeft),
// There's source code in the prefix, so run a label
// underneath it to get to the start of the range.
//
@@ -190,7 +197,7 @@ where
// 4 │ fizz₁ num = case (mod num 5) (mod num 3) of
// │ ╭─────────────^
// ```
- _ => (label_index, MultiLabel::Top(label.style, ..label_start)),
+ _ => (label_index, label.style, MultiLabel::Top(..label_start)),
});
// Marked lines
@@ -210,7 +217,7 @@ where
labeled_file
.get_or_insert_line(line_index, line_range, line_number)
.multi_labels
- .push((label_index, MultiLabel::Left(label.style)));
+ .push((label_index, label.style, MultiLabel::Left));
}
// Last labeled line
@@ -226,13 +233,12 @@ where
.multi_labels
.push((
label_index,
- MultiLabel::Bottom(label.style, ..label_end, &label.message),
+ label.style,
+ MultiLabel::Bottom(..label_end, &label.message),
));
}
}
- // TODO: Insert `None` spaces in `labeled_files`
-
// Header and message
//
// ```text
@@ -254,7 +260,8 @@ where
// │ ^^ expected `Int` but found `String`
// │
// ```
- for labeled_file in labeled_files {
+ let mut labeled_files = labeled_files.into_iter().peekable();
+ while let Some(labeled_file) = labeled_files.next() {
let source = files.source(labeled_file.file_id).unwrap();
let source = source.as_ref();
@@ -330,12 +337,22 @@ where
}
}
}
- renderer.render_snippet_empty(
- outer_padding,
- self.diagnostic.severity,
- labeled_file.num_multi_labels,
- &current_labels,
- )?;
+
+ // Check to see if we should render a trailing border after the
+ // final line of the snippet.
+ if labeled_files.peek().is_none() && self.diagnostic.notes.is_empty() {
+ // We don't render a border if we are at the final newline
+ // without trailing notes, because it would end up looking too
+ // spaced-out in combination with the final new line.
+ } else {
+ // Render the trailing snippet border.
+ renderer.render_snippet_empty(
+ outer_padding,
+ self.diagnostic.severity,
+ labeled_file.num_multi_labels,
+ &current_labels,
+ )?;
+ }
}
// Additional notes
diff --git a/tests/snapshots/term__empty_ranges__rich_color.snap b/tests/snapshots/term__empty_ranges__rich_color.snap
index d1e9a2e..89bb2c0 100644
--- a/tests/snapshots/term__empty_ranges__rich_color.snap
+++ b/tests/snapshots/term__empty_ranges__rich_color.snap
@@ -5,22 +5,25 @@ expression: TEST_DATA.emit_color(&config)
{fg:Green bold bright}note{bold bright}: middle{/}
{fg:Blue}┌─{/} hello:1:7
{fg:Blue}│{/}
-{fg:Blue}1{/} {fg:Blue}│{/} Hello world!
- {fg:Blue}│{/} {fg:Green}^ middle{/}
- {fg:Blue}│{/}
+{fg:Blue}1{/} {fg:Blue}│{/} Hello {fg:Green}w{/}orld!
+ {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}middle{/}
{fg:Green bold bright}note{bold bright}: end of line{/}
{fg:Blue}┌─{/} hello:1:13
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} Hello world!
- {fg:Blue}│{/} {fg:Green}^ end of line{/}
- {fg:Blue}│{/}
+ {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/}
-{fg:Green bold bright}note{bold bright}: end of file{/}
+{fg:Green bold bright}note{bold bright}: end of line{/}
{fg:Blue}┌─{/} hello:2:11
{fg:Blue}│{/}
{fg:Blue}2{/} {fg:Blue}│{/} Bye world!
- {fg:Blue}│{/} {fg:Green}^ end of file{/}
+ {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/}
+
+{fg:Green bold bright}note{bold bright}: end of file{/}
+ {fg:Blue}┌─{/} hello:3:4
{fg:Blue}│{/}
+{fg:Blue}3{/} {fg:Blue}│{/}
+ {fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of file{/}
diff --git a/tests/snapshots/term__empty_ranges__rich_no_color.snap b/tests/snapshots/term__empty_ranges__rich_no_color.snap
index fa28550..862605c 100644
--- a/tests/snapshots/term__empty_ranges__rich_no_color.snap
+++ b/tests/snapshots/term__empty_ranges__rich_no_color.snap
@@ -7,20 +7,23 @@ note: middle
1 │ Hello world!
│ ^ middle
- │
note: end of line
┌─ hello:1:13
1 │ Hello world!
│ ^ end of line
- │
-note: end of file
+note: end of line
┌─ hello:2:11
2 │ Bye world!
- │ ^ end of file
+ │ ^ end of line
+
+note: end of file
+ ┌─ hello:3:4
+3 │
+ │ ^ end of file
diff --git a/tests/snapshots/term__empty_ranges__short_color.snap b/tests/snapshots/term__empty_ranges__short_color.snap
index b3930e0..3713ee4 100644
--- a/tests/snapshots/term__empty_ranges__short_color.snap
+++ b/tests/snapshots/term__empty_ranges__short_color.snap
@@ -4,5 +4,6 @@ expression: TEST_DATA.emit_color(&config)
---
hello:1:7: {fg:Green bold bright}note{bold bright}: middle{/}
hello:1:13: {fg:Green bold bright}note{bold bright}: end of line{/}
-hello:2:11: {fg:Green bold bright}note{bold bright}: end of file{/}
+hello:2:11: {fg:Green bold bright}note{bold bright}: end of line{/}
+hello:3:4: {fg:Green bold bright}note{bold bright}: end of file{/}
diff --git a/tests/snapshots/term__empty_ranges__short_no_color.snap b/tests/snapshots/term__empty_ranges__short_no_color.snap
index 5e2edfc..635a830 100644
--- a/tests/snapshots/term__empty_ranges__short_no_color.snap
+++ b/tests/snapshots/term__empty_ranges__short_no_color.snap
@@ -4,5 +4,6 @@ expression: TEST_DATA.emit_no_color(&config)
---
hello:1:7: note: middle
hello:1:13: note: end of line
-hello:2:11: note: end of file
+hello:2:11: note: end of line
+hello:3:4: note: end of file
diff --git a/tests/snapshots/term__fizz_buzz__rich_color.snap b/tests/snapshots/term__fizz_buzz__rich_color.snap
index 63bfdb9..a051eef 100644
--- a/tests/snapshots/term__fizz_buzz__rich_color.snap
+++ b/tests/snapshots/term__fizz_buzz__rich_color.snap
@@ -6,14 +6,14 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} FizzBuzz.fun:3:15
{fg:Blue}│{/}
{fg:Blue}3{/} {fg:Blue}│{/} fizz₁ : Nat → String
- {fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/}
+ {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/}
{fg:Blue}4{/} {fg:Blue}│{/} fizz₁ num = case (mod num 5) (mod num 3) of
{fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}─────────────'{/}
{fg:Blue}5{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz"
{fg:Blue}6{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz"
{fg:Blue}7{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz"
-{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/}
+{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => {fg:Red}num{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────' `case` clauses have incompatible types{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `String`
@@ -23,17 +23,17 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} FizzBuzz.fun:10:15
{fg:Blue}│{/}
{fg:Blue}10{/} {fg:Blue}│{/} fizz₂ : Nat → String
- {fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/}
+ {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/}
{fg:Blue}11{/} {fg:Blue}│{/} fizz₂ num =
{fg:Blue}12{/} {fg:Blue}│{/} {fg:Blue}╭{/} case (mod num 5) (mod num 3) of
{fg:Blue}13{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz"
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------- this is found to be of type `String`{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------{/} {fg:Blue}this is found to be of type `String`{/}
{fg:Blue}14{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz"
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/}
{fg:Blue}15{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz"
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/}
-{fg:Blue}16{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/}
+{fg:Blue}16{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => {fg:Red}num{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────────' `case` clauses have incompatible types{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `String`
diff --git a/tests/snapshots/term__multifile__rich_color.snap b/tests/snapshots/term__multifile__rich_color.snap
index 5c3be31..514be49 100644
--- a/tests/snapshots/term__multifile__rich_color.snap
+++ b/tests/snapshots/term__multifile__rich_color.snap
@@ -5,29 +5,29 @@ expression: TEST_DATA.emit_color(&config)
{fg:Red bold bright}error{bold bright}: unknown builtin: `NATRAL`{/}
{fg:Blue}┌─{/} Data/Nat.fun:7:13
{fg:Blue}│{/}
-{fg:Blue}7{/} {fg:Blue}│{/} {-# BUILTIN NATRAL Nat #-}
- {fg:Blue}│{/} {fg:Red}^^^^^^ unknown builtin{/}
+{fg:Blue}7{/} {fg:Blue}│{/} {-# BUILTIN {fg:Red}NATRAL{/} Nat #-}
+ {fg:Blue}│{/} {fg:Red}^^^^^^{/} {fg:Red}unknown builtin{/}
{fg:Blue}│{/}
{fg:Blue}={/} there is a builtin with a similar name: `NATURAL`
{fg:Yellow bold bright}warning{bold bright}: unused parameter pattern: `n₂`{/}
{fg:Blue}┌─{/} Data/Nat.fun:17:16
{fg:Blue}│{/}
-{fg:Blue}17{/} {fg:Blue}│{/} zero - succ n₂ = zero
- {fg:Blue}│{/} {fg:Yellow}^^ unused parameter{/}
+{fg:Blue}17{/} {fg:Blue}│{/} zero - succ {fg:Yellow}n₂{/} = zero
+ {fg:Blue}│{/} {fg:Yellow}^^{/} {fg:Yellow}unused parameter{/}
{fg:Blue}│{/}
{fg:Blue}={/} consider using a wildcard pattern: `_`
{fg:Red bold bright}error[E0001]{bold bright}: unexpected type in application of `_+_`{/}
{fg:Blue}┌─{/} Test.fun:4:11
{fg:Blue}│{/}
-{fg:Blue} 4{/} {fg:Blue}│{/} _ = 123 + "hello"
- {fg:Blue}│{/} {fg:Red}^^^^^^^ expected `Nat`, found `String`{/}
+{fg:Blue} 4{/} {fg:Blue}│{/} _ = 123 + {fg:Red}"hello"{/}
+ {fg:Blue}│{/} {fg:Red}^^^^^^^{/} {fg:Red}expected `Nat`, found `String`{/}
{fg:Blue}│{/}
{fg:Blue}┌─{/} Data/Nat.fun:11:1
{fg:Blue}│{/}
{fg:Blue}11{/} {fg:Blue}│{/} _+_ : Nat → Nat → Nat
- {fg:Blue}│{/} {fg:Blue}--------------------- based on the definition of `_+_`{/}
+ {fg:Blue}│{/} {fg:Blue}---------------------{/} {fg:Blue}based on the definition of `_+_`{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `Nat`
found type `String`
diff --git a/tests/snapshots/term__multiline_overlapping__rich_color.snap b/tests/snapshots/term__multiline_overlapping__rich_color.snap
index 87f8bed..fee20e1 100644
--- a/tests/snapshots/term__multiline_overlapping__rich_color.snap
+++ b/tests/snapshots/term__multiline_overlapping__rich_color.snap
@@ -7,14 +7,14 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} {fg:Blue}╭{/} match line_index.compare(self.last_line_index()) {
{fg:Blue}2{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Less => Ok(self.line_starts()[line_index.to_usize()]),
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}--------------------------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
{fg:Blue}3{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Equal => Ok(self.source_span().end()),
- {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
-{fg:Blue}4{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Greater => LineIndexOutOfBoundsError {
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
+{fg:Blue}4{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Greater => {fg:Red}LineIndexOutOfBoundsError {{/}
{fg:Blue}│{/} {fg:Red}╭{/}{fg:Red}─{/}{fg:Blue}│{/}{fg:Red}──────────────────────────────────^{/}
-{fg:Blue}5{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} given: line_index,
-{fg:Blue}6{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} max: self.last_line_index(),
-{fg:Blue}7{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} },
+{fg:Blue}5{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} given: line_index,{/}
+{fg:Blue}6{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} max: self.last_line_index(),{/}
+{fg:Blue}7{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} {fg:Red} }{/},
{fg:Blue}│{/} {fg:Red}╰{/}{fg:Red}─{/}{fg:Blue}│{/}{fg:Red}─────────────^ expected enum `Result`, found struct `LineIndexOutOfBoundsError`{/}
{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} }
{fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}─────────' `match` arms have incompatible types{/}
diff --git a/tests/snapshots/term__overlapping__rich_color.snap b/tests/snapshots/term__overlapping__rich_color.snap
new file mode 100644
index 0000000..92f061f
--- /dev/null
+++ b/tests/snapshots/term__overlapping__rich_color.snap
@@ -0,0 +1,58 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_color(&config)
+---
+{fg:Red bold bright}error[E0666]{bold bright}: nested `impl Trait` is not allowed{/}
+ {fg:Blue}┌─{/} nested_impl_trait.rs:5:46
+ {fg:Blue}│{/}
+{fg:Blue}5{/} {fg:Blue}│{/} fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<{fg:Red}impl Debug{/}> { x }
+ {fg:Blue}│{/} {fg:Blue}----------{fg:Red}^^^^^^^^^^{fg:Blue}-{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}│{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}nested `impl Trait` here{/}
+ {fg:Blue}│{/} {fg:Blue}outer `impl Trait`{/}
+
+{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
+ {fg:Blue}┌─{/} typeck_type_placeholder_item.rs:1:18
+ {fg:Blue}│{/}
+{fg:Blue}1{/} {fg:Blue}│{/} fn fn_test1() -> {fg:Red}_{/} { 5 }
+ {fg:Blue}│{/} {fg:Red}^{/}
+ {fg:Blue}│{/} {fg:Red}│{/}
+ {fg:Blue}│{/} {fg:Red}not allowed in type signatures{/}
+ {fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `i32`{/}
+
+{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
+ {fg:Blue}┌─{/} typeck_type_placeholder_item.rs:2:24
+ {fg:Blue}│{/}
+{fg:Blue}2{/} {fg:Blue}│{/} fn fn_test2(x: i32) -> ({fg:Red}_{/}, {fg:Red}_{/}) { (x, x) }
+ {fg:Blue}│{/} {fg:Blue}-{fg:Red}^{fg:Blue}--{fg:Red}^{fg:Blue}-{/}
+ {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}│{/}
+ {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}not allowed in type signatures{/}
+ {fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}not allowed in type signatures{/}
+ {fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `(i32, i32)`{/}
+
+{fg:Red bold bright}error[E0277]{bold bright}: `std::rc::Rc<()>` cannot be sent between threads safely{/}
+ {fg:Blue}┌─{/} no_send_res_ports.rs:25:5
+ {fg:Blue}│{/}
+{fg:Blue}25{/} {fg:Blue}│{/} {fg:Red}thread::spawn{/}(move|| {
+ {fg:Blue}│{/} {fg:Red}^^^^^^^^^^^^^{/} {fg:Red}`std::rc::Rc<()>` cannot be sent between threads safely{/}
+ {fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}───────────────────'{/}
+{fg:Blue}26{/} {fg:Blue}│{/} {fg:Blue}│{/} let y = x;
+{fg:Blue}27{/} {fg:Blue}│{/} {fg:Blue}│{/} println!("{:?}", y);
+{fg:Blue}28{/} {fg:Blue}│{/} {fg:Blue}│{/} });
+ {fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────' within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`{/}
+ {fg:Blue}│{/}
+ {fg:Blue}┌─{/} libstd/thread/mod.rs:5:8
+ {fg:Blue}│{/}
+{fg:Blue} 5{/} {fg:Blue}│{/} F: Send + 'static,
+ {fg:Blue}│{/} {fg:Blue}----{/} {fg:Blue}required by this bound in `std::thread::spawn`{/}
+ {fg:Blue}│{/}
+ {fg:Blue}={/} help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
+ {fg:Blue}={/} note: required because it appears within the type `Port<()>`
+ {fg:Blue}={/} note: required because it appears within the type `main::Foo`
+ {fg:Blue}={/} note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`
+
+{fg:Red bold bright}error{bold bright}: aborting due 5 previous errors{/}
+ {fg:Blue}={/} Some errors have detailed explanations: E0121, E0277, E0666.
+ {fg:Blue}={/} For more information about an error, try `rustc --explain E0121`.
+
+
diff --git a/tests/snapshots/term__overlapping__rich_no_color.snap b/tests/snapshots/term__overlapping__rich_no_color.snap
new file mode 100644
index 0000000..e4a944d
--- /dev/null
+++ b/tests/snapshots/term__overlapping__rich_no_color.snap
@@ -0,0 +1,58 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+error[E0666]: nested `impl Trait` is not allowed
+ ┌─ nested_impl_trait.rs:5:46
+ │
+5 │ fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
+ │ ----------^^^^^^^^^^-
+ │ │ │
+ │ │ nested `impl Trait` here
+ │ outer `impl Trait`
+
+error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+ ┌─ typeck_type_placeholder_item.rs:1:18
+ │
+1 │ fn fn_test1() -> _ { 5 }
+ │ ^
+ │ │
+ │ not allowed in type signatures
+ │ help: replace with the correct return type: `i32`
+
+error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+ ┌─ typeck_type_placeholder_item.rs:2:24
+ │
+2 │ fn fn_test2(x: i32) -> (_, _) { (x, x) }
+ │ -^--^-
+ │ ││ │
+ │ ││ not allowed in type signatures
+ │ │not allowed in type signatures
+ │ help: replace with the correct return type: `(i32, i32)`
+
+error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
+ ┌─ no_send_res_ports.rs:25:5
+ │
+25 │ thread::spawn(move|| {
+ │ ^^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely
+ │ ╭───────────────────'
+26 │ │ let y = x;
+27 │ │ println!("{:?}", y);
+28 │ │ });
+ │ ╰──────' within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`
+ │
+ ┌─ libstd/thread/mod.rs:5:8
+ │
+ 5 │ F: Send + 'static,
+ │ ---- required by this bound in `std::thread::spawn`
+ │
+ = help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
+ = note: required because it appears within the type `Port<()>`
+ = note: required because it appears within the type `main::Foo`
+ = note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`
+
+error: aborting due 5 previous errors
+ = Some errors have detailed explanations: E0121, E0277, E0666.
+ = For more information about an error, try `rustc --explain E0121`.
+
+
diff --git a/tests/snapshots/term__overlapping__short_color.snap b/tests/snapshots/term__overlapping__short_color.snap
new file mode 100644
index 0000000..8e33cbf
--- /dev/null
+++ b/tests/snapshots/term__overlapping__short_color.snap
@@ -0,0 +1,11 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_color(&config)
+---
+nested_impl_trait.rs:5:56: {fg:Red bold bright}error[E0666]{bold bright}: nested `impl Trait` is not allowed{/}
+typeck_type_placeholder_item.rs:1:18: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
+typeck_type_placeholder_item.rs:2:25: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
+typeck_type_placeholder_item.rs:2:28: {fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
+no_send_res_ports.rs:25:5: {fg:Red bold bright}error[E0277]{bold bright}: `std::rc::Rc<()>` cannot be sent between threads safely{/}
+{fg:Red bold bright}error{bold bright}: aborting due 5 previous errors{/}
+
diff --git a/tests/snapshots/term__overlapping__short_no_color.snap b/tests/snapshots/term__overlapping__short_no_color.snap
new file mode 100644
index 0000000..47ed5db
--- /dev/null
+++ b/tests/snapshots/term__overlapping__short_no_color.snap
@@ -0,0 +1,11 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+nested_impl_trait.rs:5:56: error[E0666]: nested `impl Trait` is not allowed
+typeck_type_placeholder_item.rs:1:18: error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+typeck_type_placeholder_item.rs:2:25: error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+typeck_type_placeholder_item.rs:2:28: error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+no_send_res_ports.rs:25:5: error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
+error: aborting due 5 previous errors
+
diff --git a/tests/snapshots/term__one_line__rich_color.snap b/tests/snapshots/term__same_line__rich_color.snap
index 6f0a7f4..59bd616 100644
--- a/tests/snapshots/term__one_line__rich_color.snap
+++ b/tests/snapshots/term__same_line__rich_color.snap
@@ -5,11 +5,11 @@ expression: TEST_DATA.emit_color(&config)
{fg:Red bold bright}error[E0499]{bold bright}: cannot borrow `v` as mutable more than once at a time{/}
{fg:Blue}┌─{/} one_line.rs:3:5
{fg:Blue}│{/}
-{fg:Blue}3{/} {fg:Blue}│{/} v.push(v.pop().unwrap());
- {fg:Blue}│{/} {fg:Blue}- first borrow later used by call{/}
- {fg:Blue}│{/} {fg:Blue}---- first mutable borrow occurs here{/}
- {fg:Blue}│{/} {fg:Red}^ second mutable borrow occurs here{/}
- {fg:Blue}│{/}
+{fg:Blue}3{/} {fg:Blue}│{/} v.push({fg:Red}v{/}.pop().unwrap());
+ {fg:Blue}│{/} {fg:Blue}-{/} {fg:Blue}----{/} {fg:Red}^{/} {fg:Red}second mutable borrow occurs here{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}│{/}
+ {fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}first mutable borrow occurs here{/}
+ {fg:Blue}│{/} {fg:Blue}first borrow later used by call{/}
{fg:Red bold bright}error{bold bright}: aborting due to previous error{/}
{fg:Blue}={/} For more information about this error, try `rustc --explain E0499`.
diff --git a/tests/snapshots/term__one_line__rich_no_color.snap b/tests/snapshots/term__same_line__rich_no_color.snap
index f857bf1..7342f3f 100644
--- a/tests/snapshots/term__one_line__rich_no_color.snap
+++ b/tests/snapshots/term__same_line__rich_no_color.snap
@@ -6,10 +6,10 @@ error[E0499]: cannot borrow `v` as mutable more than once at a time
┌─ one_line.rs:3:5
3 │ v.push(v.pop().unwrap());
- │ - first borrow later used by call
- │ ---- first mutable borrow occurs here
- │ ^ second mutable borrow occurs here
- │
+ │ - ---- ^ second mutable borrow occurs here
+ │ │ │
+ │ │ first mutable borrow occurs here
+ │ first borrow later used by call
error: aborting due to previous error
= For more information about this error, try `rustc --explain E0499`.
diff --git a/tests/snapshots/term__one_line__short_color.snap b/tests/snapshots/term__same_line__short_color.snap
index c2d86c7..c2d86c7 100644
--- a/tests/snapshots/term__one_line__short_color.snap
+++ b/tests/snapshots/term__same_line__short_color.snap
diff --git a/tests/snapshots/term__one_line__short_no_color.snap b/tests/snapshots/term__same_line__short_no_color.snap
index d4f94e1..d4f94e1 100644
--- a/tests/snapshots/term__one_line__short_no_color.snap
+++ b/tests/snapshots/term__same_line__short_no_color.snap
diff --git a/tests/snapshots/term__same_ranges__rich_color.snap b/tests/snapshots/term__same_ranges__rich_color.snap
index 0860add..e6ec885 100644
--- a/tests/snapshots/term__same_ranges__rich_color.snap
+++ b/tests/snapshots/term__same_ranges__rich_color.snap
@@ -5,9 +5,10 @@ expression: TEST_DATA.emit_color(&config)
{fg:Red bold bright}error{bold bright}: Unexpected token{/}
{fg:Blue}┌─{/} same_range:1:5
{fg:Blue}│{/}
-{fg:Blue}1{/} {fg:Blue}│{/} ::S { }
- {fg:Blue}│{/} {fg:Red}^ Unexpected '{'{/}
- {fg:Blue}│{/} {fg:Blue}- Expected '('{/}
- {fg:Blue}│{/}
+{fg:Blue}1{/} {fg:Blue}│{/} ::S {fg:Red}{{/} }
+ {fg:Blue}│{/} {fg:Red}^{/}
+ {fg:Blue}│{/} {fg:Red}│{/}
+ {fg:Blue}│{/} {fg:Red}Unexpected '{'{/}
+ {fg:Blue}│{/} {fg:Blue}Expected '('{/}
diff --git a/tests/snapshots/term__same_ranges__rich_no_color.snap b/tests/snapshots/term__same_ranges__rich_no_color.snap
index 462fc89..09510e6 100644
--- a/tests/snapshots/term__same_ranges__rich_no_color.snap
+++ b/tests/snapshots/term__same_ranges__rich_no_color.snap
@@ -6,8 +6,9 @@ error: Unexpected token
┌─ same_range:1:5
1 │ ::S { }
- │ ^ Unexpected '{'
- │ - Expected '('
- │
+ │ ^
+ │ │
+ │ Unexpected '{'
+ │ Expected '('
diff --git a/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap
new file mode 100644
index 0000000..0faa8d8
--- /dev/null
+++ b/tests/snapshots/term__tab_columns__tab_width_2_no_color.snap
@@ -0,0 +1,23 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+warning: tab test
+ ┌─ tab_columns:1:2
+ │
+1 │ hello
+ │ ^^^^^
+2 │ ∙ hello
+ │ ^^^^^
+3 │ ∙∙ hello
+ │ ^^^^^
+4 │ ∙∙∙ hello
+ │ ^^^^^
+5 │ ∙∙∙∙ hello
+ │ ^^^^^
+6 │ ∙∙∙∙∙ hello
+ │ ^^^^^
+7 │ ∙∙∙∙∙∙ hello
+ │ ^^^^^
+
+
diff --git a/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap
new file mode 100644
index 0000000..40f20a6
--- /dev/null
+++ b/tests/snapshots/term__tab_columns__tab_width_3_no_color.snap
@@ -0,0 +1,23 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+warning: tab test
+ ┌─ tab_columns:1:2
+ │
+1 │ hello
+ │ ^^^^^
+2 │ ∙ hello
+ │ ^^^^^
+3 │ ∙∙ hello
+ │ ^^^^^
+4 │ ∙∙∙ hello
+ │ ^^^^^
+5 │ ∙∙∙∙ hello
+ │ ^^^^^
+6 │ ∙∙∙∙∙ hello
+ │ ^^^^^
+7 │ ∙∙∙∙∙∙ hello
+ │ ^^^^^
+
+
diff --git a/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap
new file mode 100644
index 0000000..018a402
--- /dev/null
+++ b/tests/snapshots/term__tab_columns__tab_width_6_no_color.snap
@@ -0,0 +1,23 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+warning: tab test
+ ┌─ tab_columns:1:2
+ │
+1 │ hello
+ │ ^^^^^
+2 │ ∙ hello
+ │ ^^^^^
+3 │ ∙∙ hello
+ │ ^^^^^
+4 │ ∙∙∙ hello
+ │ ^^^^^
+5 │ ∙∙∙∙ hello
+ │ ^^^^^
+6 │ ∙∙∙∙∙ hello
+ │ ^^^^^
+7 │ ∙∙∙∙∙∙ hello
+ │ ^^^^^
+
+
diff --git a/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap b/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap
new file mode 100644
index 0000000..67cd187
--- /dev/null
+++ b/tests/snapshots/term__tab_columns__tab_width_default_no_color.snap
@@ -0,0 +1,23 @@
+---
+source: codespan-reporting/tests/term.rs
+expression: TEST_DATA.emit_no_color(&config)
+---
+warning: tab test
+ ┌─ tab_columns:1:2
+ │
+1 │ hello
+ │ ^^^^^
+2 │ ∙ hello
+ │ ^^^^^
+3 │ ∙∙ hello
+ │ ^^^^^
+4 │ ∙∙∙ hello
+ │ ^^^^^
+5 │ ∙∙∙∙ hello
+ │ ^^^^^
+6 │ ∙∙∙∙∙ hello
+ │ ^^^^^
+7 │ ∙∙∙∙∙∙ hello
+ │ ^^^^^
+
+
diff --git a/tests/snapshots/term__tabbed__tab_width_3_no_color.snap b/tests/snapshots/term__tabbed__tab_width_3_no_color.snap
index e8d797c..f7328c3 100644
--- a/tests/snapshots/term__tabbed__tab_width_3_no_color.snap
+++ b/tests/snapshots/term__tabbed__tab_width_3_no_color.snap
@@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw`
3 │ Weapon: DogJaw
│ ^^^^^^ the weapon
- │
warning: unknown condition `attack-cooldown`
┌─ tabbed:4:23
-4 │ ReloadingCondition: attack-cooldown
- │ ^^^^^^^^^^^^^^^ the condition
- │
+4 │ ReloadingCondition: attack-cooldown
+ │ ^^^^^^^^^^^^^^^ the condition
warning: unknown field `Foo`
┌─ tabbed:5:2
5 │ Foo: Bar
│ ^^^ the field
- │
diff --git a/tests/snapshots/term__tabbed__tab_width_6_no_color.snap b/tests/snapshots/term__tabbed__tab_width_6_no_color.snap
index 72991cf..bcd1b2b 100644
--- a/tests/snapshots/term__tabbed__tab_width_6_no_color.snap
+++ b/tests/snapshots/term__tabbed__tab_width_6_no_color.snap
@@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw`
3 │ Weapon: DogJaw
│ ^^^^^^ the weapon
- │
warning: unknown condition `attack-cooldown`
┌─ tabbed:4:23
-4 │ ReloadingCondition: attack-cooldown
- │ ^^^^^^^^^^^^^^^ the condition
- │
+4 │ ReloadingCondition: attack-cooldown
+ │ ^^^^^^^^^^^^^^^ the condition
warning: unknown field `Foo`
┌─ tabbed:5:2
5 │ Foo: Bar
│ ^^^ the field
- │
diff --git a/tests/snapshots/term__tabbed__tab_width_default_no_color.snap b/tests/snapshots/term__tabbed__tab_width_default_no_color.snap
index d3c0ee2..d3a41a0 100644
--- a/tests/snapshots/term__tabbed__tab_width_default_no_color.snap
+++ b/tests/snapshots/term__tabbed__tab_width_default_no_color.snap
@@ -7,20 +7,17 @@ warning: unknown weapon `DogJaw`
3 │ Weapon: DogJaw
│ ^^^^^^ the weapon
- │
warning: unknown condition `attack-cooldown`
┌─ tabbed:4:23
-4 │ ReloadingCondition: attack-cooldown
- │ ^^^^^^^^^^^^^^^ the condition
- │
+4 │ ReloadingCondition: attack-cooldown
+ │ ^^^^^^^^^^^^^^^ the condition
warning: unknown field `Foo`
┌─ tabbed:5:2
5 │ Foo: Bar
│ ^^^ the field
- │
diff --git a/tests/snapshots/term__unicode_spans__rich_no_color.snap b/tests/snapshots/term__unicode_spans__rich_no_color.snap
index e76de4b..653a60e 100644
--- a/tests/snapshots/term__unicode_spans__rich_no_color.snap
+++ b/tests/snapshots/term__unicode_spans__rich_no_color.snap
@@ -6,10 +6,24 @@ error[E01]: cow may not jump during new moon.
┌─ moon_jump.rs:1:1
1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
- │ ^^ Invalid jump
- │ -- Cow range does not start at boundary.
- │ ------ Cow does not start or end at boundary.
+ │ ^^ Invalid jump
+
+note: invalid unicode range
+ ┌─ moon_jump.rs:1:1
+ │
+1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
+ │ -- Cow range does not start at boundary.
+
+note: invalid unicode range
+ ┌─ moon_jump.rs:1:3
+ │
+1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
│ -- Cow range does not end at boundary.
+
+note: invalid unicode range
+ ┌─ moon_jump.rs:1:1
+1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
+ │ ------ Cow does not start or end at boundary.
diff --git a/tests/snapshots/term__unicode_spans__short_no_color.snap b/tests/snapshots/term__unicode_spans__short_no_color.snap
index 9085082..1b19451 100644
--- a/tests/snapshots/term__unicode_spans__short_no_color.snap
+++ b/tests/snapshots/term__unicode_spans__short_no_color.snap
@@ -3,4 +3,7 @@ source: codespan-reporting/tests/term.rs
expression: TEST_DATA.emit_no_color(&config)
---
moon_jump.rs:1:1: error[E01]: cow may not jump during new moon.
+note: invalid unicode range
+note: invalid unicode range
+note: invalid unicode range
diff --git a/tests/term.rs b/tests/term.rs
index 95cc670..ee02372 100644
--- a/tests/term.rs
+++ b/tests/term.rs
@@ -87,20 +87,23 @@ mod empty {
test_emit!(short_no_color);
}
-/// Based on: https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/codemap_tests/one_line.stderr
-mod one_line {
+/// Based on:
+/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/codemap_tests/one_line.stderr
+mod same_line {
use super::*;
lazy_static::lazy_static! {
- static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, String>> = {
- let file = SimpleFile::new(
+ static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
+ let mut files = SimpleFiles::new();
+
+ let file_id1 = files.add(
"one_line.rs",
unindent::unindent(r#"
fn main() {
let mut v = vec![Some("foo"), Some("bar")];
v.push(v.pop().unwrap());
}
- "#)
+ "#),
);
let diagnostics = vec![
@@ -108,11 +111,11 @@ mod one_line {
.with_code("E0499")
.with_message("cannot borrow `v` as mutable more than once at a time")
.with_labels(vec![
- Label::primary((), 71..72)
+ Label::primary(file_id1, 71..72)
.with_message("second mutable borrow occurs here"),
- Label::secondary((), 64..65)
+ Label::secondary(file_id1, 64..65)
.with_message("first borrow later used by call"),
- Label::secondary((), 66..70)
+ Label::secondary(file_id1, 66..70)
.with_message("first mutable borrow occurs here"),
]),
Diagnostic::error()
@@ -122,7 +125,149 @@ mod one_line {
]),
];
- TestData { files: file, diagnostics }
+ TestData { files, diagnostics }
+ };
+ }
+
+ test_emit!(rich_color);
+ test_emit!(short_color);
+ test_emit!(rich_no_color);
+ test_emit!(short_no_color);
+}
+
+/// Based on:
+/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/nested_impl_trait.stderr
+/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/typeck/typeck_type_placeholder_item.stderr
+/// - https://github.com/rust-lang/rust/blob/c20d7eecbc0928b57da8fe30b2ef8528e2bdd5be/src/test/ui/no_send_res_ports.stderr
+mod overlapping {
+ use super::*;
+
+ lazy_static::lazy_static! {
+ static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
+ let mut files = SimpleFiles::new();
+
+ let file_id1 = files.add(
+ "nested_impl_trait.rs",
+ unindent::unindent(r#"
+ use std::fmt::Debug;
+
+ fn fine(x: impl Into<u32>) -> impl Into<u32> { x }
+
+ fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
+ "#),
+ );
+ let file_id2 = files.add(
+ "typeck_type_placeholder_item.rs",
+ unindent::unindent(r#"
+ fn fn_test1() -> _ { 5 }
+ fn fn_test2(x: i32) -> (_, _) { (x, x) }
+ "#),
+ );
+ let file_id3 = files.add(
+ "libstd/thread/mod.rs",
+ unindent::unindent(r#"
+ #[stable(feature = "rust1", since = "1.0.0")]
+ pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>>
+ where
+ F: FnOnce() -> T,
+ F: Send + 'static,
+ T: Send + 'static,
+ {
+ unsafe { self.spawn_unchecked(f) }
+ }
+ "#),
+ );
+ let file_id4 = files.add(
+ "no_send_res_ports.rs",
+ unindent::unindent(r#"
+ use std::thread;
+ use std::rc::Rc;
+
+ #[derive(Debug)]
+ struct Port<T>(Rc<T>);
+
+ fn main() {
+ #[derive(Debug)]
+ struct Foo {
+ _x: Port<()>,
+ }
+
+ impl Drop for Foo {
+ fn drop(&mut self) {}
+ }
+
+ fn foo(x: Port<()>) -> Foo {
+ Foo {
+ _x: x
+ }
+ }
+
+ let x = foo(Port(Rc::new(())));
+
+ thread::spawn(move|| {
+ let y = x;
+ println!("{:?}", y);
+ });
+ }
+ "#),
+ );
+
+ let diagnostics = vec![
+ Diagnostic::error()
+ .with_code("E0666")
+ .with_message("nested `impl Trait` is not allowed")
+ .with_labels(vec![
+ Label::primary(file_id1, 129..139)
+ .with_message("nested `impl Trait` here"),
+ Label::secondary(file_id1, 119..140)
+ .with_message("outer `impl Trait`"),
+ ]),
+ Diagnostic::error()
+ .with_code("E0121")
+ .with_message("the type placeholder `_` is not allowed within types on item signatures")
+ .with_labels(vec![
+ Label::primary(file_id2, 17..18)
+ .with_message("not allowed in type signatures"),
+ Label::secondary(file_id2, 17..18)
+ .with_message("help: replace with the correct return type: `i32`"),
+ ]),
+ Diagnostic::error()
+ .with_code("E0121")
+ .with_message("the type placeholder `_` is not allowed within types on item signatures")
+ .with_labels(vec![
+ Label::primary(file_id2, 49..50)
+ .with_message("not allowed in type signatures"),
+ Label::primary(file_id2, 52..53)
+ .with_message("not allowed in type signatures"),
+ Label::secondary(file_id2, 48..54)
+ .with_message("help: replace with the correct return type: `(i32, i32)`"),
+ ]),
+ Diagnostic::error()
+ .with_code("E0277")
+ .with_message("`std::rc::Rc<()>` cannot be sent between threads safely")
+ .with_labels(vec![
+ Label::primary(file_id4, 339..352)
+ .with_message("`std::rc::Rc<()>` cannot be sent between threads safely"),
+ Label::secondary(file_id4, 353..416)
+ .with_message("within this `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`"),
+ Label::secondary(file_id3, 141..145)
+ .with_message("required by this bound in `std::thread::spawn`"),
+ ])
+ .with_notes(vec![
+ "help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`".to_owned(),
+ "note: required because it appears within the type `Port<()>`".to_owned(),
+ "note: required because it appears within the type `main::Foo`".to_owned(),
+ "note: required because it appears within the type `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`".to_owned(),
+ ]),
+ Diagnostic::error()
+ .with_message("aborting due 5 previous errors")
+ .with_notes(vec![
+ "Some errors have detailed explanations: E0121, E0277, E0666.".to_owned(),
+ "For more information about an error, try `rustc --explain E0121`.".to_owned(),
+ ]),
+ ];
+
+ TestData { files, diagnostics }
};
}
@@ -185,7 +330,7 @@ mod empty_ranges {
lazy_static::lazy_static! {
static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, &'static str>> = {
- let file = SimpleFile::new("hello", "Hello world!\nBye world!");
+ let file = SimpleFile::new("hello", "Hello world!\nBye world!\n ");
let eof = file.source().len();
let diagnostics = vec![
@@ -196,6 +341,9 @@ mod empty_ranges {
.with_message("end of line")
.with_labels(vec![Label::primary((), 12..12).with_message("end of line")]),
Diagnostic::note()
+ .with_message("end of line")
+ .with_labels(vec![Label::primary((), 23..23).with_message("end of line")]),
+ Diagnostic::note()
.with_message("end of file")
.with_labels(vec![Label::primary((), eof..eof).with_message("end of file")]),
];
@@ -483,10 +631,83 @@ mod tabbed {
fn tab_width_default_no_color() {
let config = TEST_CONFIG.clone();
- insta::assert_snapshot!(
- "tab_width_default_no_color",
- TEST_DATA.emit_no_color(&config)
- );
+ insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
+ }
+
+ #[test]
+ fn tab_width_3_no_color() {
+ let config = Config {
+ tab_width: 3,
+ ..TEST_CONFIG.clone()
+ };
+
+ insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
+ }
+
+ #[test]
+ fn tab_width_6_no_color() {
+ let config = Config {
+ tab_width: 6,
+ ..TEST_CONFIG.clone()
+ };
+
+ insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
+ }
+}
+
+mod tab_columns {
+ use super::*;
+
+ lazy_static::lazy_static! {
+ static ref TEST_DATA: TestData<'static, SimpleFiles<&'static str, String>> = {
+ let mut files = SimpleFiles::new();
+
+ let source = [
+ "\thello",
+ "∙\thello",
+ "∙∙\thello",
+ "∙∙∙\thello",
+ "∙∙∙∙\thello",
+ "∙∙∙∙∙\thello",
+ "∙∙∙∙∙∙\thello",
+ ].join("\n");
+ let hello_ranges = source
+ .match_indices("hello")
+ .map(|(start, hello)| start..(start+hello.len()))
+ .collect::<Vec<_>>();
+
+ let file_id = files.add("tab_columns", source);
+
+ let diagnostics = vec![
+ Diagnostic::warning()
+ .with_message("tab test")
+ .with_labels(
+ hello_ranges
+ .into_iter()
+ .map(|range| Label::primary(file_id, range))
+ .collect(),
+ ),
+ ];
+
+ TestData { files, diagnostics }
+ };
+ }
+
+ #[test]
+ fn tab_width_default_no_color() {
+ let config = TEST_CONFIG.clone();
+
+ insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
+ }
+
+ #[test]
+ fn tab_width_2_no_color() {
+ let config = Config {
+ tab_width: 2,
+ ..TEST_CONFIG.clone()
+ };
+
+ insta::assert_snapshot!(TEST_DATA.emit_no_color(&config));
}
#[test]
@@ -510,7 +731,8 @@ mod tabbed {
}
}
-/// Based on: https://github.com/TheSamsa/rust/blob/75cf41afb468152611212271bae026948cd3ba46/src/test/ui/codemap_tests/unicode.stderr
+/// Based on:
+/// - https://github.com/TheSamsa/rust/blob/75cf41afb468152611212271bae026948cd3ba46/src/test/ui/codemap_tests/unicode.stderr
mod unicode {
use super::*;
@@ -595,14 +817,26 @@ mod unicode_spans {
.with_labels(vec![
Label::primary((), invalid_start..invalid_end)
.with_message("Invalid jump"),
+ ]),
+ Diagnostic::note()
+ .with_message("invalid unicode range")
+ .with_labels(vec![
Label::secondary((), invalid_start.."🐄".len())
.with_message("Cow range does not start at boundary."),
+ ]),
+ Diagnostic::note()
+ .with_message("invalid unicode range")
+ .with_labels(vec![
Label::secondary((), "🐄🌑".len().."🐄🌑🐄".len() - 1)
.with_message("Cow range does not end at boundary."),
+ ]),
+ Diagnostic::note()
+ .with_message("invalid unicode range")
+ .with_labels(vec![
Label::secondary((), invalid_start.."🐄🌑🐄".len() - 1)
.with_message("Cow does not start or end at boundary."),
-
- ])];
+ ]),
+ ];
TestData{files: file, diagnostics }
};
}