Repository: zkat/miette
Pull request #396 (2024-08-06 12:05:51)

Small refactor of doc tests
Move use NamedSource a bit up, since now it looks like it is not imported.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b179baa..5641367 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,38 @@
 # `miette` Release Changelog
 
+<a name="7.3.0"></a>
+## 7.3.0 (2024-11-26)
+
+### Features
+
+* **SourceSpan:** add impl From<InclusiveRange> (#385) ([73da45b6](https://github.com/zkat/miette/commit/73da45b65c965777a00ba64aa03a247c0e5241ca))
+* **Report:** add `from_err()` method to `Report` (#403) ([93d3bd11](https://github.com/zkat/miette/commit/93d3bd118a072c35aa761f0ec74317166ec08113))
+* **Diagnostic:** Implement `Diagnostic` for `Infallible` (#402) ([f3fb4c1e](https://github.com/zkat/miette/commit/f3fb4c1ecd196ce389cbd71139bb7e3b35474add))
+
+### Performance
+
+* **handlers:** optimize string-buffer reallocations (#387) ([b8dfcda4](https://github.com/zkat/miette/commit/b8dfcda4a8c10a14116ee275250356ac991dc7be))
+
+### Bug Fixes
+
+* **graphical:** fix nested error wrapping (#358) ([3eabbceb](https://github.com/zkat/miette/commit/3eabbcebf113d1d620a6a3f98e8a455414ed3042))
+* **docs:** updated example image (fixes #111) (#270) ([7b42b12c](https://github.com/zkat/miette/commit/7b42b12c5f6316322ce79c59bcb9e99f5d49edb8))
+* **clippy:** Fix clippy lints in docs (#365) ([ea4296da](https://github.com/zkat/miette/commit/ea4296dacec3b0e4762281d9d115c1bd69ecfac3))
+* **docs:** `alt` attribut for `single-line-example.png` (#372) ([b82cc81b](https://github.com/zkat/miette/commit/b82cc81b8ea32a1cf1b4598ed5832bc8e3b0e161))
+* **color:** setting NO_COLOR should not print ansi codes for non-terminals (#381) ([813232ba](https://github.com/zkat/miette/commit/813232ba7957ae09e4fb9d9416d821f4fd9da66d))
+* **clippy:** fix Rust v1.78.0 clippy warnings (#382) ([e1026f75](https://github.com/zkat/miette/commit/e1026f75e0a5d19bbc8e468cb3f5292074543a97))
+* **perf:** mark error constructors cold (#378) ([9bbcf3c6](https://github.com/zkat/miette/commit/9bbcf3c6017fa3455a7db714879816c1cfc511fd))
+* **handlers:** Disable textwrap::smawk feature (#379) ([edfdcb52](https://github.com/zkat/miette/commit/edfdcb525ee30fc54747460ada621f13f0ed1996))
+* **graphical:** Format entire link instead of just name (#389) ([bf5aa374](https://github.com/zkat/miette/commit/bf5aa3742fd664be3c93160b9c28c145b1ed8bc9))
+* **clippy:** fix `clippy::doc_lazy_continuation` lints (#395) ([15beec43](https://github.com/zkat/miette/commit/15beec43303180b811d4c04d1a78775feb9b0905))
+* **graphical:** Handle invalid UTF-8 in source code (#393) ([d6b45585](https://github.com/zkat/miette/commit/d6b4558502e82fa74e030ccb3c8040656590d7eb))
+* **features:** Use `dep:` syntax for dependencies in features. (#394) ([789a04e3](https://github.com/zkat/miette/commit/789a04e30d041179b373b4eb8b340456534a3f0e))
+* **clippy:** Fix `needless_return` lint. (#405) ([5f441d01](https://github.com/zkat/miette/commit/5f441d011560a091fe5d6a6cdb05f09acf622d36))
+
+### Documentation
+
+* **examples:** add serde_json integration example (#407) ([2902a233](https://github.com/zkat/miette/commit/2902a2337c2e36a5d8e0e54b007d6100cca0c9ff))
+
 <a name="7.2.0"></a>
 ## 7.2.0 (2024-03-07)
 
diff --git a/Cargo.toml b/Cargo.toml
index 01b3a71..ee00a05 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "miette"
-version = "7.2.0"
+version = "7.3.0"
 authors = ["Kat Marchán <kzm@zkat.tech>"]
 description = "Fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers."
 categories = ["rust-patterns"]
@@ -14,7 +14,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
 
 [dependencies]
 thiserror = "1.0.56"
-miette-derive = { path = "miette-derive", version = "=7.2.0", optional = true }
+miette-derive = { path = "miette-derive", version = "=7.3.0", optional = true }
 unicode-width = "0.1.11"
 cfg-if = "1.0.0"
 
@@ -24,7 +24,7 @@ supports-hyperlinks = { version = "3.0.0", optional = true }
 supports-color = { version = "3.0.0", optional = true }
 supports-unicode = { version = "3.0.0", optional = true }
 backtrace = { version = "0.3.69", optional = true }
-terminal_size = { version = "0.3.0", optional = true }
+terminal_size = { version = "0.4.0", optional = true }
 backtrace-ext = { version = "0.2.1", optional = true }
 serde = { version = "1.0.196", features = ["derive"], optional = true }
 syntect = { version = "5.1.0", optional = true }
@@ -41,32 +41,37 @@ syn = { version = "2.0.48", features = ["full"] }
 regex = "1.10"
 lazy_static = "1.4"
 
+serde = { version = "1.0.196", features = ["derive"] }
 serde_json = "1.0.113"
 strip-ansi-escapes = "0.2.0"
 
 [features]
 default = ["derive"]
-derive = ["miette-derive"]
+derive = ["dep:miette-derive"]
 no-format-args-capture = []
 fancy-base = [
-    "owo-colors",
-    "textwrap",
+    "dep:owo-colors",
+    "dep:textwrap",
 ]
 fancy-no-syscall = [
     "fancy-base",
 ]
 fancy-no-backtrace = [
     "fancy-base",
-    "terminal_size",
-    "supports-hyperlinks",
-    "supports-color",
-    "supports-unicode",
+    "dep:terminal_size",
+    "dep:supports-hyperlinks",
+    "dep:supports-color",
+    "dep:supports-unicode",
 ]
-fancy = ["fancy-no-backtrace", "backtrace", "backtrace-ext"]
-syntect-highlighter = ["fancy-no-backtrace", "syntect"]
+fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"]
+syntect-highlighter = ["fancy-no-backtrace", "dep:syntect"]
 
 [workspace]
 members = ["miette-derive"]
 
 [package.metadata.docs.rs]
 all-features = true
+
+[[example]]
+name = "serde_json"
+required-features = ["fancy"]
diff --git a/README.md b/README.md
index ef351f2..646047c 100644
--- a/README.md
+++ b/README.md
@@ -28,9 +28,9 @@ diagnostic error code: ruget::api::bad_json
 " />
 
 > **NOTE: You must enable the `"fancy"` crate feature to get fancy report
-output like in the screenshots above.** You should only do this in your
-toplevel crate, as the fancy feature pulls in a number of dependencies that
-libraries and such might not want.
+> output like in the screenshots above.** You should only do this in your
+> toplevel crate, as the fancy feature pulls in a number of dependencies that
+> libraries and such might not want.
 
 ### Table of Contents <!-- omit in toc -->
 
@@ -288,9 +288,9 @@ There are also similar [bail!] and [ensure!] macros.
 automatically.
 
 > **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
-output like in the screenshots here.** You should only do this in your
-toplevel crate, as the fancy feature pulls in a number of dependencies that
-libraries and such might not want.
+> output like in the screenshots here.** You should only do this in your
+> toplevel crate, as the fancy feature pulls in a number of dependencies that
+> libraries and such might not want.
 
 ```rust
 use miette::{IntoDiagnostic, Result};
@@ -645,8 +645,8 @@ customize!
 If you...
 - ...don't know all the possible errors upfront
 - ...need to serialize/deserialize errors
-then you may want to use [`miette!`], [`diagnostic!`] macros or
-[`MietteDiagnostic`] directly to create diagnostic on the fly.
+  then you may want to use [`miette!`], [`diagnostic!`] macros or
+  [`MietteDiagnostic`] directly to create diagnostic on the fly.
 
 ```rust
 
@@ -673,12 +673,12 @@ automatically use the [`syntect`] crate to highlight the `#[source_code]`
 field of your [`Diagnostic`].
 
 Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
-* [language()](SpanContents::language) - Provides the name of the language
+* [`language()`](SpanContents::language) - Provides the name of the language
   as a string. For example `"Rust"` will indicate Rust syntax highlighting.
   You can set the language of the [`SpanContents`] produced by a
   [`NamedSource`] via the [`with_language`](NamedSource::with_language)
   method.
-* [name()](SpanContents::name) - In the absence of an explicitly set
+* [`name()`](SpanContents::name) - In the absence of an explicitly set
   language, the name is assumed to contain a file name or file path.
   The highlighter will check for a file extension at the end of the name and
   try to guess the syntax from that.
diff --git a/examples/serde_json.rs b/examples/serde_json.rs
new file mode 100644
index 0000000..d57a76a
--- /dev/null
+++ b/examples/serde_json.rs
@@ -0,0 +1,48 @@
+//! This example shows how to integrate miette with serde_json
+//! so the decoding source will be annotated with the decoding error,
+//! providing contextual information about the error.
+
+use miette::{IntoDiagnostic, SourceOffset};
+use serde_json::{self, json};
+
+#[derive(Debug, serde::Deserialize)]
+struct Library {
+    #[allow(unused)]
+    name: String,
+}
+
+#[derive(Debug, thiserror::Error, miette::Diagnostic)]
+#[error("malformed json provided")]
+struct SerdeError {
+    cause: serde_json::Error,
+    #[source_code]
+    input: String,
+    #[label("{cause}")]
+    location: SourceOffset,
+}
+
+impl SerdeError {
+    /// Takes the input and the `serde_json::Error` and returns a SerdeError
+    /// that can be rendered nicely with miette.
+    pub fn from_serde_error(input: impl Into<String>, cause: serde_json::Error) -> Self {
+        let input = input.into();
+        let location = SourceOffset::from_location(&input, cause.line(), cause.column());
+        Self {
+            cause,
+            input,
+            location,
+        }
+    }
+}
+
+fn main() -> miette::Result<()> {
+    let input = serde_json::to_string_pretty(&json!({
+        "name": 123
+    }))
+    .into_diagnostic()?;
+
+    let _library: Library =
+        serde_json::from_str(&input).map_err(|cause| SerdeError::from_serde_error(input, cause))?;
+
+    Ok(())
+}
diff --git a/miette-derive/Cargo.toml b/miette-derive/Cargo.toml
index e8082a0..fb05f89 100644
--- a/miette-derive/Cargo.toml
+++ b/miette-derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "miette-derive"
-version = "7.2.0"
+version = "7.3.0"
 authors = ["Kat Marchán <kzm@zkat.tech>"]
 edition = "2018"
 license = "Apache-2.0"
diff --git a/src/diagnostic_impls.rs b/src/diagnostic_impls.rs
new file mode 100644
index 0000000..aebe56b
--- /dev/null
+++ b/src/diagnostic_impls.rs
@@ -0,0 +1,55 @@
+/*!
+Default trait implementations for [`Diagnostic`].
+*/
+
+use std::{convert::Infallible, fmt::Display};
+
+use crate::{Diagnostic, LabeledSpan, Severity, SourceCode};
+
+impl Diagnostic for Infallible {
+    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
+        match *self {}
+    }
+
+    fn severity(&self) -> Option<Severity> {
+        match *self {}
+    }
+
+    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
+        match *self {}
+    }
+
+    fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
+        match *self {}
+    }
+
+    fn source_code(&self) -> Option<&dyn SourceCode> {
+        match *self {}
+    }
+
+    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
+        match *self {}
+    }
+
+    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
+        match *self {}
+    }
+
+    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
+        match *self {}
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::Report;
+
+    /// Test that [`Infallible`] implements [`Diagnostic`] by seeing if a function that's generic over `Diagnostic`
+    /// will accept `Infallible` as a type parameter.
+    #[test]
+    fn infallible() {
+        let _ = Report::new::<Infallible>;
+    }
+}
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
index 94e4128..cbb4aee 100644
--- a/src/eyreish/error.rs
+++ b/src/eyreish/error.rs
@@ -425,6 +425,14 @@ impl Report {
         }
         .into()
     }
+
+    /// Construct a [`Report`] directly from an error-like type
+    pub fn from_err<E>(err: E) -> Self
+    where
+        E: std::error::Error + Send + Sync + 'static,
+    {
+        super::DiagnosticError(Box::new(err)).into()
+    }
 }
 
 impl<E> From<E> for Report
diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs
index 6480013..f862ac6 100644
--- a/src/eyreish/into_diagnostic.rs
+++ b/src/eyreish/into_diagnostic.rs
@@ -6,7 +6,7 @@ use crate::{Diagnostic, Report};
 /// Errors. This is intended to be paired with [`IntoDiagnostic`].
 #[derive(Debug, Error)]
 #[error(transparent)]
-struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
+pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
 impl Diagnostic for DiagnosticError {}
 
 /**
diff --git a/src/handler.rs b/src/handler.rs
index efbee86..10d8f65 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -89,7 +89,7 @@ impl MietteHandlerOpts {
     /// Syntax highlighting is disabled by default unless the
     /// `syntect-highlighter` feature is enabled. Call this method
     /// to override the default and use a custom highlighter
-    /// implmentation instead.
+    /// implementation instead.
     ///
     /// Use
     /// [`without_syntax_highlighting()`](MietteHandlerOpts::without_syntax_highlighting())
diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs
index 87fb354..8c4a9a5 100644
--- a/src/handlers/graphical.rs
+++ b/src/handlers/graphical.rs
@@ -178,7 +178,8 @@ impl GraphicalReportHandler {
     }
 
     /// Enable syntax highlighting for source code snippets, using the given
-    /// [`Highlighter`]. See the [crate::highlighters] crate for more details.
+    /// [`Highlighter`]. See the [highlighters](crate::highlighters) crate
+    /// for more details.
     pub fn with_syntax_highlighting(
         mut self,
         highlighter: impl Highlighter + Send + Sync + 'static,
@@ -799,7 +800,7 @@ impl GraphicalReportHandler {
             return Ok(());
         }
 
-        // keeps track of how many colums wide the gutter is
+        // keeps track of how many columns wide the gutter is
         // important for ansi since simply measuring the size of the final string
         // gives the wrong result when the string contains ansi codes.
         let mut gutter_cols = 0;
@@ -1186,7 +1187,7 @@ impl GraphicalReportHandler {
         let context_data = source
             .read_span(context_span, self.context_lines, self.context_lines)
             .map_err(|_| fmt::Error)?;
-        let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
+        let context = String::from_utf8_lossy(context_data.data());
         let mut line = context_data.line();
         let mut column = context_data.column();
         let mut offset = context_data.span().offset();
diff --git a/src/highlighters/mod.rs b/src/highlighters/mod.rs
index d605c1c..0af1aa2 100644
--- a/src/highlighters/mod.rs
+++ b/src/highlighters/mod.rs
@@ -26,16 +26,16 @@ mod syntect;
 
 /// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
 pub trait Highlighter {
-    ///  Creates a new [HighlighterState] to begin parsing and highlighting
-    /// a [SpanContents].
+    ///  Creates a new [`HighlighterState`] to begin parsing and highlighting
+    /// a [`SpanContents`].
     ///
-    /// The [GraphicalReportHandler](crate::GraphicalReportHandler) will call
-    /// this method at the start of rendering a [SpanContents].
+    /// The [`GraphicalReportHandler`](crate::GraphicalReportHandler) will call
+    /// this method at the start of rendering a [`SpanContents`].
     ///
-    /// The [SpanContents] is provided as input only so that the [Highlighter]
+    /// The [`SpanContents`] is provided as input only so that the [`Highlighter`]
     /// can detect language syntax and make other initialization decisions prior
     /// to highlighting, but it is not intended that the Highlighter begin
-    /// highlighting at this point. The returned [HighlighterState] is
+    /// highlighting at this point. The returned [`HighlighterState`] is
     /// responsible for the actual rendering.
     fn start_highlighter_state<'h>(
         &'h self,
@@ -46,12 +46,12 @@ pub trait Highlighter {
 /// A stateful highlighter that incrementally highlights lines of a particular
 /// source code.
 ///
-/// The [GraphicalReportHandler](crate::GraphicalReportHandler)
+/// The [`GraphicalReportHandler`](crate::GraphicalReportHandler)
 /// will create a highlighter state by calling
-/// [start_highlighter_state](Highlighter::start_highlighter_state) at the
+/// [`start_highlighter_state`](Highlighter::start_highlighter_state) at the
 /// start of rendering, then it will iteratively call
-/// [highlight_line](HighlighterState::highlight_line) to render individual
-/// highlighted lines. This allows [Highlighter] implementations to maintain
+/// [`highlight_line`](HighlighterState::highlight_line) to render individual
+/// highlighted lines. This allows [`Highlighter`] implementations to maintain
 /// mutable parsing and highlighting state.
 pub trait HighlighterState {
     /// Highlight an individual line from the source code by returning a vector of [Styled]
@@ -59,9 +59,9 @@ pub trait HighlighterState {
     fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>;
 }
 
-/// Arcified trait object for Highlighter. Used internally by [GraphicalReportHandler]
+/// Arcified trait object for Highlighter. Used internally by [`GraphicalReportHandler`]
 ///
-/// Wrapping the trait object in this way allows us to implement Debug and Clone.
+/// Wrapping the trait object in this way allows us to implement `Debug` and `Clone`.
 #[derive(Clone)]
 #[repr(transparent)]
 pub(crate) struct MietteHighlighter(Arc<dyn Highlighter + Send + Sync>);
@@ -92,7 +92,7 @@ impl Default for MietteHighlighter {
     }
     #[cfg(not(feature = "syntect-highlighter"))]
     fn default() -> Self {
-        return MietteHighlighter::nocolor();
+        MietteHighlighter::nocolor()
     }
 }
 
diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs
index 69ed015..538124c 100644
--- a/src/highlighters/syntect.rs
+++ b/src/highlighters/syntect.rs
@@ -20,7 +20,7 @@ use crate::{
 
 use super::BlankHighlighterState;
 
-/// Highlights miette [SourceCode] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
+/// Highlights miette [`SpanContents`] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
 ///
 /// Currently only 24-bit truecolor output is supported due to syntect themes
 /// representing color as RGBA.
@@ -81,7 +81,7 @@ impl SyntectHighlighter {
         )
     }
 
-    /// Determine syntect SyntaxReference to use for given SourceCode
+    /// Determine syntect [`SyntaxReference`] to use for given [`SpanContents`].
     fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
         // use language if given
         if let Some(language) = contents.language() {
@@ -105,7 +105,7 @@ impl SyntectHighlighter {
     }
 }
 
-/// Stateful highlighting iterator for [SyntectHighlighter]
+/// Stateful highlighting iterator for [`SyntectHighlighter`].
 #[derive(Debug)]
 pub(crate) struct SyntectHighlighterState<'h> {
     syntax_set: &'h syntect::SyntaxSet,
@@ -133,7 +133,7 @@ impl<'h> HighlighterState for SyntectHighlighterState<'h> {
     }
 }
 
-/// Convert syntect [syntect::Style] into owo_colors [Style] */
+/// Convert syntect [`syntect::Style`] into `owo_colors` [`Style`]
 #[inline]
 fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
     if use_bg_color {
diff --git a/src/lib.rs b/src/lib.rs
index 8946990..175c929 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,9 +28,9 @@
 //! " />
 //!
 //! > **NOTE: You must enable the `"fancy"` crate feature to get fancy report
-//! output like in the screenshots above.** You should only do this in your
-//! toplevel crate, as the fancy feature pulls in a number of dependencies that
-//! libraries and such might not want.
+//! > output like in the screenshots above.** You should only do this in your
+//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
+//! > libraries and such might not want.
 //!
 //! ## Table of Contents <!-- omit in toc -->
 //!
@@ -100,7 +100,7 @@
 //!
 //! `thiserror` is a great way to define them, and plays nicely with `miette`!
 //! */
-//! use miette::{Diagnostic, SourceSpan};
+//! use miette::{Diagnostic, NamedSource, SourceSpan};
 //! use thiserror::Error;
 //!
 //! #[derive(Error, Debug, Diagnostic)]
@@ -127,7 +127,7 @@
 //! throughout your app (but NOT your libraries! Those should always return
 //! concrete types!).
 //! */
-//! use miette::{NamedSource, Result};
+//! use miette::Result;
 //! fn this_fails() -> Result<()> {
 //!     // You can use plain strings as a `Source`, or anything that implements
 //!     // the one-method `Source` trait.
@@ -288,9 +288,9 @@
 //! automatically.
 //!
 //! > **NOTE:** You must enable the `"fancy"` crate feature to get fancy report
-//! output like in the screenshots here.** You should only do this in your
-//! toplevel crate, as the fancy feature pulls in a number of dependencies that
-//! libraries and such might not want.
+//! > output like in the screenshots here.** You should only do this in your
+//! > toplevel crate, as the fancy feature pulls in a number of dependencies that
+//! > libraries and such might not want.
 //!
 //! ```rust
 //! use miette::{IntoDiagnostic, Result};
@@ -645,8 +645,8 @@
 //! If you...
 //! - ...don't know all the possible errors upfront
 //! - ...need to serialize/deserialize errors
-//! then you may want to use [`miette!`], [`diagnostic!`] macros or
-//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
+//!   then you may want to use [`miette!`], [`diagnostic!`] macros or
+//!   [`MietteDiagnostic`] directly to create diagnostic on the fly.
 //!
 //! ```rust,ignore
 //! # use miette::{miette, LabeledSpan, Report};
@@ -674,12 +674,12 @@
 //! field of your [`Diagnostic`].
 //!
 //! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
-//! * [language()](SpanContents::language) - Provides the name of the language
+//! * [`language()`](SpanContents::language) - Provides the name of the language
 //!   as a string. For example `"Rust"` will indicate Rust syntax highlighting.
 //!   You can set the language of the [`SpanContents`] produced by a
 //!   [`NamedSource`] via the [`with_language`](NamedSource::with_language)
 //!   method.
-//! * [name()](SpanContents::name) - In the absence of an explicitly set
+//! * [`name()`](SpanContents::name) - In the absence of an explicitly set
 //!   language, the name is assumed to contain a file name or file path.
 //!   The highlighter will check for a file extension at the end of the name and
 //!   try to guess the syntax from that.
@@ -788,6 +788,7 @@ pub use protocol::*;
 
 mod chain;
 mod diagnostic_chain;
+mod diagnostic_impls;
 mod error;
 mod eyreish;
 #[cfg(feature = "fancy-base")]
diff --git a/src/protocol.rs b/src/protocol.rs
index d3ee9ca..7f09d4d 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -523,7 +523,7 @@ impl<'a> MietteSpanContents<'a> {
         }
     }
 
-    /// Sets the [`language`](SourceCode::language) for syntax highlighting.
+    /// Sets the [`language`](SpanContents::language) for syntax highlighting.
     pub fn with_language(mut self, language: impl Into<String>) -> Self {
         self.language = Some(language.into());
         self


Test patch:
diff --git a/tests/graphical.rs b/tests/graphical.rs
index c6b5e1a..3176812 100644
--- a/tests/graphical.rs
+++ b/tests/graphical.rs
@@ -2000,11 +2000,11 @@ fn syntax_highlighter_on_real_file() {
     // SourceSpan constants for column and length
     const CO: usize = 28;
     const LEN: usize = 27;
-    let file_src = std::fs::read_to_string(&filename).unwrap();
+    let file_src = std::fs::read_to_string(filename).unwrap();
     let offset = miette::SourceOffset::from_location(&file_src, line, CO);
     let err = Test {
-        src: NamedSource::new(&filename, file_src.clone()),
-        src_span: SourceSpan::new(offset, LEN.into()),
+        src: NamedSource::new(filename, file_src.clone()),
+        src_span: SourceSpan::new(offset, LEN),
     };
 
     let mut out = String::new();
@@ -2349,3 +2349,164 @@ Error: oops::my::inner
     assert_eq!(expected, &out);
     Ok(())
 }
+
+#[test]
+fn after_unicode_width() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("pointing")]
+    #[diagnostic(code(pointing_at))]
+    struct E {
+        #[label("something of interest")]
+        src: SourceSpan,
+    }
+
+    let unicode_source = "höööt!";
+
+    // make err pointing at the t
+    let (t_index, _) = unicode_source
+        .bytes()
+        .enumerate()
+        .find(|&(_, x)| x == b't')
+        .unwrap();
+
+    let err = E {
+        src: SourceSpan::from((t_index, 1)),
+    };
+
+    let result = fmt_report(Report::new(err).with_source_code(String::from(unicode_source)));
+
+    let expected = "pointing_at
+
+  × pointing
+   ╭────
+ 1 │ höööt!
+   ·     ┬
+   ·     ╰── something of interest
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn at_unicode_width() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("pointing")]
+    #[diagnostic(code(pointing_at))]
+    struct E {
+        #[label("something of interest")]
+        src: SourceSpan,
+    }
+
+    let unicode_source = "höööt!";
+
+    // make err pointing at an ö
+    let err = E {
+        src: SourceSpan::from((1, "ö".len())),
+    };
+
+    // we want to make sure the pointer is one char wide, not 2
+    assert!(err.src.len() == 2);
+
+    let result = fmt_report(Report::new(err).with_source_code(String::from(unicode_source)));
+
+    let expected = "pointing_at
+
+  × pointing
+   ╭────
+ 1 │ höööt!
+   ·  ┬
+   ·  ╰── something of interest
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn at_invalid_unicode() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("decoding error")]
+    #[diagnostic(code(decode_err))]
+    struct E {
+        #[label("invalid data here")]
+        src: SourceSpan,
+    }
+
+    // 3 bytes here are mapped to 1 char when replaced by replacement character
+    // this tests that the line pointing from the label is the correct length when rendered
+    // - it should be 1 char wide not 3 chars
+    let invalid_source: &[u8] = b"malformed h\xf0\x93\x8aXYZ";
+
+    // invalid utf8 lint only available from 1.72
+    #[allow(unknown_lints, invalid_from_utf8)]
+    let utf8_err = std::str::from_utf8(invalid_source).unwrap_err();
+
+    // make err pointing at the invalid part
+    let err = E {
+        src: SourceSpan::from((utf8_err.valid_up_to(), utf8_err.error_len().unwrap_or(1))),
+    };
+
+    // check that we're testing the thing we think we're testing
+    assert_eq!(err.src.len(), 3);
+
+    let result = fmt_report(Report::new(err).with_source_code(Vec::from(invalid_source)));
+
+    let expected = "decode_err
+
+  × decoding error
+   ╭────
+ 1 │ malformed h�XYZ
+   ·            ┬
+   ·            ╰── invalid data here
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
+
+#[test]
+fn after_invalid_unicode() -> Result<(), MietteError> {
+    #[derive(Debug, Diagnostic, Error)]
+    #[error("decoding error")]
+    #[diagnostic(code(decode_err))]
+    struct E {
+        #[label("valid data here")]
+        src: SourceSpan,
+    }
+
+    let invalid_source: &[u8] = b"malformed h\xf0\x93\x8aXYZ";
+
+    let (x_index, _) = invalid_source
+        .iter()
+        .enumerate()
+        .find(|&(_, &x)| x == b'X')
+        .unwrap();
+
+    // make err pointing at the X
+    let err = E {
+        src: SourceSpan::from((x_index, 1)),
+    };
+
+    let result = fmt_report(Report::new(err).with_source_code(invalid_source));
+
+    let expected = "decode_err
+
+  × decoding error
+   ╭────
+ 1 │ malformed h�XYZ
+   ·             ┬
+   ·             ╰── valid data here
+   ╰────
+";
+
+    assert_eq!(expected, result);
+
+    Ok(())
+}
diff --git a/tests/test_diagnostic_source_macro.rs b/tests/test_diagnostic_source_macro.rs
index 4f15ea0..bbc2179 100644
--- a/tests/test_diagnostic_source_macro.rs
+++ b/tests/test_diagnostic_source_macro.rs
@@ -154,6 +154,7 @@ fn test_diagnostic_source_is_output() {
     assert_eq!(expected, out);
 }
 
+#[cfg(feature = "fancy-no-backtrace")]
 #[derive(Debug, miette::Diagnostic, thiserror::Error)]
 #[error("A nested error happened")]
 struct NestedError {
@@ -212,6 +213,7 @@ fn test_nested_diagnostic_source_is_output() {
     assert_eq!(expected, out);
 }
 
+#[cfg(feature = "fancy-no-backtrace")]
 #[derive(Debug, miette::Diagnostic, thiserror::Error)]
 #[error("A multi-error happened")]
 struct MultiError {
diff --git a/tests/test_fmt.rs b/tests/test_fmt.rs
index 8f18216..abf3873 100644
--- a/tests/test_fmt.rs
+++ b/tests/test_fmt.rs
@@ -80,7 +80,6 @@ fn test_altdisplay() {
 
 #[test]
 #[ignore = "not really gonna work with the current printers"]
-#[cfg_attr(track_caller, ignore)]
 fn test_debug() {
     assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
     assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));