Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(highlighting): implement syntax highlighting for unnamed source codes #425

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ You can derive a `Diagnostic` from any `std::error::Error` type.

`thiserror` is a great way to define them, and plays nicely with `miette`!
*/
use miette::{Diagnostic, NamedSource, SourceSpan};
use miette::{Diagnostic, MietteSourceCode, SourceSpan};
use thiserror::Error;

#[derive(Error, Debug, Diagnostic)]
Expand All @@ -114,7 +114,7 @@ struct MyBad {
// The Source that we're gonna be printing snippets out of.
// This can be a String if you don't have or care about file names.
#[source_code]
src: NamedSource<String>,
src: MietteSourceCode<String>,
// Snippets and highlights can be included in the diagnostic!
#[label("This bit here")]
bad_bit: SourceSpan,
Expand All @@ -134,7 +134,7 @@ fn this_fails() -> Result<()> {
let src = "source\n text\n here".to_string();

Err(MyBad {
src: NamedSource::new("bad_file.rs", src),
src: MietteSourceCode::new(src).with_name("bad_file.rs"),
bad_bit: (9, 4).into(),
})?;

Expand Down Expand Up @@ -649,7 +649,6 @@ If you...
[`MietteDiagnostic`] directly to create diagnostic on the fly.

```rust

let source = "2 + 2 * 2 = 8".to_string();
let report = miette!(
labels = vec![
Expand All @@ -665,23 +664,32 @@ println!("{:?}", report)

`miette` can be configured to highlight syntax in source code snippets.

<!-- TODO: screenshot goes here once default Theme is decided -->
<img src="https://raw.githubusercontent.com/zkat/miette/main/images/syntax_highlight.png"/>

To use the built-in highlighting functionality, you must enable the
`syntect-highlighter` crate feature. When this feature is enabled, `miette` will
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
Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SourceCode`] trait, in order:
* [`language()`](SourceCode::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)
You can set the language of a [`SourceCode`] by using a
[`MietteSourceCode`], via the [`with_language`](MietteSourceCode::with_language)
method.
* [`name()`](SpanContents::name) - In the absence of an explicitly set
* [`name()`](SourceCode::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.
try to guess the syntax from that. Can also be set via the
[`with_name`](MietteSourceCod::with_name) method.

```rust
let src = MietteSourceCode::new("fn hello(oops) -> &str { \"hello!\" }").with_language("Rust");
let report = miette!(
labels = vec![LabeledSpan::at((9, 4), "this is wrong")],
"invalid syntax!",
).with_source_code(src);
```

If you want to use a custom highlighter, you can provide a custom
implementation of the [`Highlighter`](highlighters::Highlighter)
Expand Down
Binary file added images/syntax_highlight.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ impl GraphicalReportHandler {
.map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st))
.collect::<Vec<_>>();

let mut highlighter_state = self.highlighter.start_highlighter_state(&*contents);
let mut highlighter_state = self.highlighter.start_highlighter_state(source, &*contents);

// The max number of gutter-lines that will be active at any given
// point. We need this to figure out indentation, so we do one loop
Expand Down Expand Up @@ -663,7 +663,7 @@ impl GraphicalReportHandler {
None => contents,
};

if let Some(source_name) = primary_contents.name() {
if let Some(source_name) = source.name() {
writeln!(
f,
"[{}]",
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ impl JSONReportHandler {
) -> fmt::Result {
if let Some(mut labels) = diagnostic.labels() {
if let Some(label) = labels.next() {
if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
let filename = span_content.name().unwrap_or_default();
if source.read_span(label.inner(), 0, 0).is_ok() {
let filename = source.name().unwrap_or_default();
return write!(f, r#""filename": "{}","#, escape(filename));
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/narratable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl NarratableReportHandler {
) -> fmt::Result {
let (contents, lines) = self.get_lines(source, context.inner())?;
write!(f, "Begin snippet")?;
if let Some(filename) = contents.name() {
if let Some(filename) = source.name() {
write!(f, " for {}", filename,)?;
}
writeln!(
Expand Down
5 changes: 3 additions & 2 deletions src/highlighters/blank.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use owo_colors::Style;

use crate::SpanContents;
use crate::{SourceCode, SpanContents};

use super::{Highlighter, HighlighterState};

Expand All @@ -12,7 +12,8 @@ pub struct BlankHighlighter;
impl Highlighter for BlankHighlighter {
fn start_highlighter_state<'h>(
&'h self,
_source: &dyn SpanContents<'_>,
_source: &dyn SourceCode,
_span: &dyn SpanContents<'_>,
) -> Box<dyn super::HighlighterState + 'h> {
Box::new(BlankHighlighterState)
}
Expand Down
5 changes: 3 additions & 2 deletions src/highlighters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use std::{ops::Deref, sync::Arc};

use crate::SpanContents;
use crate::{SourceCode, SpanContents};
use owo_colors::Styled;

#[cfg(feature = "syntect-highlighter")]
Expand All @@ -39,7 +39,8 @@ pub trait Highlighter {
/// responsible for the actual rendering.
fn start_highlighter_state<'h>(
&'h self,
source: &dyn SpanContents<'_>,
source: &dyn SourceCode,
span: &dyn SpanContents<'_>,
) -> Box<dyn HighlighterState + 'h>;
}

Expand Down
25 changes: 13 additions & 12 deletions src/highlighters/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use owo_colors::{Rgb, Style, Styled};

use crate::{
highlighters::{Highlighter, HighlighterState},
SpanContents,
SourceCode, SpanContents,
};

use super::BlankHighlighterState;
Expand All @@ -42,9 +42,10 @@ impl Default for SyntectHighlighter {
impl Highlighter for SyntectHighlighter {
fn start_highlighter_state<'h>(
&'h self,
source: &dyn SpanContents<'_>,
source: &dyn SourceCode,
span: &dyn SpanContents<'_>,
) -> Box<dyn HighlighterState + 'h> {
if let Some(syntax) = self.detect_syntax(source) {
if let Some(syntax) = self.detect_syntax(source, span) {
let highlighter = syntect::Highlighter::new(&self.theme);
let parse_state = syntect::ParseState::new(syntax);
let highlight_state =
Expand Down Expand Up @@ -82,26 +83,26 @@ impl SyntectHighlighter {
}

/// Determine syntect [`SyntaxReference`] to use for given [`SpanContents`].
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
fn detect_syntax(
&self,
source: &dyn SourceCode,
span: &dyn SpanContents<'_>,
) -> Option<&syntect::SyntaxReference> {
// use language if given
if let Some(language) = contents.language() {
if let Some(language) = source.language() {
return self.syntax_set.find_syntax_by_name(language);
}
// otherwise try to use any file extension provided in the name
if let Some(name) = contents.name() {
if let Some(name) = source.name() {
if let Some(ext) = Path::new(name).extension() {
return self
.syntax_set
.find_syntax_by_extension(ext.to_string_lossy().as_ref());
}
}
// finally, attempt to guess syntax based on first line
return self.syntax_set.find_syntax_by_first_line(
std::str::from_utf8(contents.data())
.ok()?
.split('\n')
.next()?,
);
self.syntax_set
.find_syntax_by_first_line(std::str::from_utf8(span.data()).ok()?.split('\n').next()?)
}
}

Expand Down
36 changes: 23 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
//!
//! `thiserror` is a great way to define them, and plays nicely with `miette`!
//! */
//! use miette::{Diagnostic, NamedSource, SourceSpan};
//! use miette::{Diagnostic, MietteSourceCode, SourceSpan};
//! use thiserror::Error;
//!
//! #[derive(Error, Debug, Diagnostic)]
Expand All @@ -114,7 +114,7 @@
//! // The Source that we're gonna be printing snippets out of.
//! // This can be a String if you don't have or care about file names.
//! #[source_code]
//! src: NamedSource<String>,
//! src: MietteSourceCode<String>,
//! // Snippets and highlights can be included in the diagnostic!
//! #[label("This bit here")]
//! bad_bit: SourceSpan,
Expand All @@ -134,7 +134,7 @@
//! let src = "source\n text\n here".to_string();
//!
//! Err(MyBad {
//! src: NamedSource::new("bad_file.rs", src),
//! src: MietteSourceCode::new(src).with_name("bad_file.rs"),
//! bad_bit: (9, 4).into(),
//! })?;
//!
Expand Down Expand Up @@ -650,7 +650,6 @@
//!
//! ```rust,ignore
//! # use miette::{miette, LabeledSpan, Report};
//!
//! let source = "2 + 2 * 2 = 8".to_string();
//! let report = miette!(
//! labels = vec![
Expand All @@ -666,23 +665,34 @@
//!
//! `miette` can be configured to highlight syntax in source code snippets.
//!
//! <!-- TODO: screenshot goes here once default Theme is decided -->
//! <img src="https://raw.githubusercontent.com/zkat/miette/main/images/syntax_highlight.png"/>
//!
//! To use the built-in highlighting functionality, you must enable the
//! `syntect-highlighter` crate feature. When this feature is enabled, `miette` will
//! 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
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SourceCode`] trait, in order:
//! * [`language()`](SourceCode::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)
//! You can set the language of a [`SourceCode`] by using a
//! [`MietteSourceCode`], via the [`with_language`](MietteSourceCode::with_language)
//! method.
//! * [`name()`](SpanContents::name) - In the absence of an explicitly set
//! * [`name()`](SourceCode::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.
//! try to guess the syntax from that. Can also be set via the
//! [`with_name`](MietteSourceCod::with_name) method.
//!
//! ```rust
//! # use miette::{miette, LabeledSpan, MietteSourceCode};
//! # use miette::Result;
//! let src = MietteSourceCode::new("fn hello(oops) -> &str { \"hello!\" }").with_language("Rust");
//! let report = miette!(
//! labels = vec![LabeledSpan::at((9, 4), "this is wrong")],
//! "invalid syntax!",
//! ).with_source_code(src);
//! ```
//!
//! If you want to use a custom highlighter, you can provide a custom
//! implementation of the [`Highlighter`](highlighters::Highlighter)
Expand Down Expand Up @@ -781,10 +791,10 @@ pub use eyreish::*;
pub use handler::*;
pub use handlers::*;
pub use miette_diagnostic::*;
pub use named_source::*;
#[cfg(feature = "fancy")]
pub use panic::*;
pub use protocol::*;
pub use source_code::*;

mod chain;
mod diagnostic_chain;
Expand All @@ -799,8 +809,8 @@ pub mod highlighters;
#[doc(hidden)]
pub mod macro_helpers;
mod miette_diagnostic;
mod named_source;
#[cfg(feature = "fancy")]
mod panic;
mod protocol;
mod source_code;
mod source_impls;
78 changes: 0 additions & 78 deletions src/named_source.rs

This file was deleted.

Loading
Loading