simple errors for lang-dev
-rw-r--r--.github/example.pngbin5230 -> 4547 bytes
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md15
-rw-r--r--src/lib.rs163
5 files changed, 147 insertions, 35 deletions
diff --git a/.github/example.png b/.github/example.png
index 5ec619f..fdf6cb9 100644
--- a/.github/example.png
+++ b/.github/example.png
Binary files differ
diff --git a/Cargo.lock b/Cargo.lock
index 2129c47..9eb39a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 3
[[package]]
name = "lerr"
-version = "0.1.1"
+version = "0.1.2"
dependencies = [
"unicode-width",
]
diff --git a/Cargo.toml b/Cargo.toml
index 8331cdf..c11022f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "lerr"
-version = "0.1.1"
+version = "0.1.2"
edition = "2021"
license = "MIT"
readme = "README.md"
diff --git a/README.md b/README.md
index e8933ce..e333949 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,15 @@ feel free to add coloring with your favorite coloring crate, or just use raw ans
```rust
use lerr::Error;
-let out = Error::new("Strin::new()")
- .label((0..5, "a 'strin' you say"))
- .note("i think you meant String")
- .to_string();
-eprintln!("{out}");
+let mut e = Error::new("Strin::nouveau().i_like_tests(3.14158)");
+e.label((0..5, "you probably meant String"))
+ .label((7..16, "use new()"))
+ .label((17..18, "caps: I"))
+ .label((30..37, "your π is bad"));
+eprintln!("{e}");
+// dont mind this
+assert_eq!(e.to_string(), "\n\u{1b}[1;34;30m0 │ \u{1b}[0mStrin::nouveau().i_like_tests(3.14158)\n\u{1b}[1;34;30m ¦ \u{1b}[0m\u{1b}[1;34;31m──┬──\u{1b}[0m \u{1b}[1;34;31m────┬────\u{1b}[0m \u{1b}[1;34;31m^\u{1b}[0m caps: I \u{1b}[1;34;31m^^^^^^^\u{1b}[0m your π is bad\n\u{1b}[1;34;30m ¦ \u{1b}[0m \u{1b}[1;34;31m│\u{1b}[0m \u{1b}[1;34;31m╰\u{1b}[0m use new()\n\u{1b}[1;34;30m ¦ \u{1b}[0m \u{1b}[1;34;31m╰\u{1b}[0m you probably meant String\n");
```
-Please note that only one label per line is currently supported, and multiline labels are not yet supported.
+Please note that multiline labels are not yet supported.
If that doesnt work for you, use something like [ariadne](https://crates.io/crates/ariadne). \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index a19276c..d31e261 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@ use unicode_width::UnicodeWidthStr;
/// Span of bytes in the source
pub type Span = Range<usize>;
/// Label around a [`Span`]
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Label {
/// The span that this label will draw at
pub span: Span,
@@ -70,6 +70,7 @@ pub struct Error<'s> {
impl<'s> Error<'s> {
/// Create a new error with source code attached
+ #[must_use = "The error doesnt print itself"]
pub fn new(source: &'s str) -> Self {
Self {
labels: vec![],
@@ -104,45 +105,144 @@ impl<'s> Error<'s> {
}
}
+macro_rules! wrpeat {
+ ($to:ident, $n:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
+ for _ in 0..$n { write!($to, $fmt $(, $arg)*)? }
+ };
+}
+
impl<'s> std::fmt::Display for Error<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.message)?;
let lines = self.source.0.lines().count();
let width = lines.ilog10() as usize + 1;
let space = " ";
- let mut found = vec![];
- for (line, (code, span)) in self.source.spans().enumerate() {
- for label in &self.labels {
- if span.end >= label.span.start && span.start <= label.span.start {
- found.push(label);
+ let mut labels = self.labels.clone();
+ // label, width of message, width of ^^^
+ let mut found: Vec<(Label, usize, usize)> = vec![];
+ for (line, (code, line_span)) in self.source.spans().enumerate() {
+ let mut i = 0;
+ while i < labels.len() {
+ if line_span.end >= labels[i].span.start && line_span.start <= labels[i].span.start
+ {
+ let candidate = labels.swap_remove(i);
+
+ for (Label { span, .. }, ..) in &found {
+ if span.contains(&candidate.span.start) {
+ todo!("erorrs may not overlap")
+ }
+ }
+ // ^^^ length
+ let mut point = UnicodeWidthStr::width(
+ &self.source.0[candidate.span.start - line_span.start
+ ..candidate.span.end - line_span.start],
+ );
+ if candidate.span.end == candidate.span.start {
+ point += 1;
+ }
+ // ^^^ [<this part length>]
+ let msglen = UnicodeWidthStr::width(candidate.message.as_str());
+ found.push((candidate, msglen, point));
+ } else {
+ i += 1;
}
}
- if found.len() > 1 {
- todo!("only one error per line supported");
- }
- let Some(&label) = found.first() else {
+ if found.is_empty() {
continue;
- };
- writeln!(f, "{line:width$} | \x1b[0m{code}")?;
- let about = UnicodeWidthStr::width(
- &self.source.0[label.span.start - span.start..label.span.end - span.start],
- );
- let padding = UnicodeWidthStr::width(&self.source.0[span.start..label.span.start]);
- write!(f, "\x1b[1;34;30m{space:width$} ¦ \x1b[0m",)?;
- for _ in 0..padding {
- write!(f, " ")?;
}
- write!(f, "\x1b[1;34;31m")?;
- for _ in 0..about {
- write!(f, "^")?;
+ writeln!(f, "\x1b[1;34;30m{line:width$} │ \x1b[0m{code}")?;
+ write!(f, "\x1b[1;34;30m{space:width$} ¦ \x1b[0m")?;
+
+ // sort by width
+ found.sort_unstable_by(|(a, ..), (b, ..)| match a.span.start.cmp(&b.span.start) {
+ core::cmp::Ordering::Equal => a.span.end.cmp(&b.span.end),
+ ord => ord,
+ });
+ // keeps track of how many chars we have printed
+ let mut position = 0;
+ let mut middles = vec![];
+ for (i, (l, msglen, about)) in found.iter().map(|(v, a, b)| (v, *a, *b)).enumerate() {
+ let padding = UnicodeWidthStr::width(
+ &self.source.0[line_span.start + position..l.span.start],
+ );
+ wrpeat!(f, padding, " ");
+ position += padding;
+
+ if found
+ .iter()
+ .skip(i + 1)
+ // will this label "but into" any of the future ones if i place it here
+ .any(|(b, ..)| l.span.start + about + msglen + 1 > b.span.start)
+ {
+ let p = about.saturating_sub(1);
+ let middle = (p + 1) / 2;
+ write!(f, "\x1b[1;34;31m")?;
+ wrpeat!(f, middle, "─");
+ write!(f, "┬")?;
+ wrpeat!(f, p - middle, "─");
+ write!(f, "\x1b[0m")?;
+ middles.push((l, middle, msglen));
+ position += about;
+ continue;
+ }
+ write!(f, "\x1b[1;34;31m")?;
+ wrpeat!(f, about, "^");
+ position += about;
+ write!(f, "\x1b[0m ")?;
+ position += 1;
+ write!(f, "{}", l.message)?;
+ position += msglen;
}
- if label.span.end == label.span.start {
- write!(f, "^")?;
+ writeln!(f)?;
+ extras(self, middles, line_span, f, width)?;
+ fn extras(
+ e: &Error,
+ mut unfinished: Vec<(&Label, usize, usize)>,
+ line_span: Span,
+ f: &mut std::fmt::Formatter<'_>,
+ width: usize,
+ ) -> std::fmt::Result {
+ if unfinished.is_empty() {
+ return Ok(());
+ }
+ write!(f, "\x1b[1;34;30m{:width$} ¦ \x1b[0m", " ")?;
+ let mut position = 0;
+ let mut i = 0;
+ while i < unfinished.len() {
+ // connection is where we are expected to put our ╰
+ let (l, connection, msglen) = unfinished[i];
+
+ let padding = UnicodeWidthStr::width(
+ &e.source.0[line_span.start + position..l.span.start + connection],
+ );
+ wrpeat!(f, padding, " ");
+ position += padding;
+
+ if unfinished
+ .iter()
+ .skip(i + 1)
+ // will this label "but into" any of the future ones if i place it here
+ .any(|(b, ..)| l.span.start + connection + msglen + 2 > b.span.start)
+ {
+ // if it will, leave it for the next line (this is a recursive fn)
+ write!(f, "\x1b[1;34;31m│\x1b[0m ")?;
+ position += 2;
+ i += 1;
+ continue;
+ }
+ write!(f, "\x1b[1;34;31m╰\x1b[0m ")?;
+ position += 2;
+ write!(f, "{}", l.message)?;
+ position += msglen;
+ unfinished.remove(i);
+ }
+ writeln!(f)?;
+ extras(e, unfinished, line_span, f, width)
}
- write!(f, "\x1b[0m ")?;
- writeln!(f, "{}", label.message)?;
+
found.clear();
}
+
for note in &self.notes {
writeln!(f, "{space:width$} \x1b[1;34;30m>\x1b[0m {}", note.message)?;
}
@@ -158,5 +258,14 @@ fn display() {
.note("\x1b[1;34;34mnote\x1b[0m: maybe python would be better for you")
.to_string();
println!("{out}");
- assert_eq!(out, "\n\u{1b}[1;34;30m0 | \u{1b}[0mvoid fn x(void) -> four {\n\u{1b}[1;34;30m ¦ \u{1b}[0m \u{1b}[1;34;31m^^^^\u{1b}[0m what is 'four'?\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;32mhelp\u{1b}[0m: change it to 4\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;34mnote\u{1b}[0m: maybe python would be better for you\n");
+ assert_eq!(out, "\n\u{1b}[1;34;30m0 │ \u{1b}[0mvoid fn x(void) -> four {\n\u{1b}[1;34;30m ¦ \u{1b}[0m \u{1b}[1;34;31m^^^^\u{1b}[0m what is 'four'?\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;32mhelp\u{1b}[0m: change it to 4\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;34mnote\u{1b}[0m: maybe python would be better for you\n");
+}
+#[test]
+fn inline() {
+ let out = Error::new("im out of this worl")
+ .label((15..19, "forgot d"))
+ .label((0..2, r#"forgot '"#))
+ .to_string();
+ println!("{out}");
+ assert_eq!(out, "\n\u{1b}[1;34;30m0 │ \u{1b}[0mim out of this worl\n\u{1b}[1;34;30m ¦ \u{1b}[0m\u{1b}[1;34;31m^^\u{1b}[0m forgot ' \u{1b}[1;34;31m^^^^\u{1b}[0m forgot d\n");
}