mirror of https://github.com/zkat/miette.git
use SpanContents instead of SourceCode for language detection
This commit is contained in:
parent
c679d87bef
commit
9c019a87af
|
|
@ -482,7 +482,7 @@ impl GraphicalReportHandler {
|
||||||
.map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st))
|
.map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut highlighter_state = self.highlighter.start_highlighter_state(source);
|
let mut highlighter_state = self.highlighter.start_highlighter_state(&*contents);
|
||||||
|
|
||||||
// The max number of gutter-lines that will be active at any given
|
// 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
|
// point. We need this to figure out indentation, so we do one loop
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use owo_colors::Style;
|
use owo_colors::Style;
|
||||||
|
|
||||||
|
use crate::SpanContents;
|
||||||
|
|
||||||
use super::{Highlighter, HighlighterState};
|
use super::{Highlighter, HighlighterState};
|
||||||
|
|
||||||
/// The default syntax highlighter. It applies `Style::default()` to input text.
|
/// The default syntax highlighter. It applies `Style::default()` to input text.
|
||||||
|
|
@ -10,7 +12,7 @@ pub struct BlankHighlighter;
|
||||||
impl Highlighter for BlankHighlighter {
|
impl Highlighter for BlankHighlighter {
|
||||||
fn start_highlighter_state<'h>(
|
fn start_highlighter_state<'h>(
|
||||||
&'h self,
|
&'h self,
|
||||||
_source: &dyn crate::SourceCode,
|
_source: &dyn SpanContents<'_>,
|
||||||
) -> Box<dyn super::HighlighterState + 'h> {
|
) -> Box<dyn super::HighlighterState + 'h> {
|
||||||
Box::new(BlankHighlighterState)
|
Box::new(BlankHighlighterState)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use crate::SourceCode;
|
use crate::SpanContents;
|
||||||
use owo_colors::Styled;
|
use owo_colors::Styled;
|
||||||
|
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
#[cfg(feature = "syntect-highlighter")]
|
||||||
|
|
@ -24,22 +24,22 @@ mod blank;
|
||||||
#[cfg(feature = "syntect-highlighter")]
|
#[cfg(feature = "syntect-highlighter")]
|
||||||
mod syntect;
|
mod syntect;
|
||||||
|
|
||||||
/// A syntax highlighter for highlighting miette [SourceCode] snippets.
|
/// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
|
||||||
pub trait Highlighter {
|
pub trait Highlighter {
|
||||||
/// Creates a new [HighlighterState] to begin parsing and highlighting
|
/// Creates a new [HighlighterState] to begin parsing and highlighting
|
||||||
/// a [SourceCode] snippet.
|
/// a [SpanContents].
|
||||||
///
|
///
|
||||||
/// The [GraphicalReportHandler](crate::GraphicalReportHandler) will call
|
/// The [GraphicalReportHandler](crate::GraphicalReportHandler) will call
|
||||||
/// this method at the start of rendering a [Diagnostic](crate::Diagnostic).
|
/// this method at the start of rendering a [SpanContents].
|
||||||
///
|
///
|
||||||
/// The source is provided as input only so that the Highlighter can detect
|
/// The [SpanContents] is provided as input only so that the [Highlighter]
|
||||||
/// language syntax and make other initialization decisions prior
|
/// can detect language syntax and make other initialization decisions prior
|
||||||
/// to highlighting, but it is not intended that the Highlighter begin
|
/// 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.
|
/// responsible for the actual rendering.
|
||||||
fn start_highlighter_state<'h>(
|
fn start_highlighter_state<'h>(
|
||||||
&'h self,
|
&'h self,
|
||||||
source: &dyn SourceCode,
|
source: &dyn SpanContents<'_>,
|
||||||
) -> Box<dyn HighlighterState + 'h>;
|
) -> Box<dyn HighlighterState + 'h>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use owo_colors::{Rgb, Style, Styled};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
highlighters::{Highlighter, HighlighterState},
|
highlighters::{Highlighter, HighlighterState},
|
||||||
SourceCode,
|
SpanContents,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::BlankHighlighterState;
|
use super::BlankHighlighterState;
|
||||||
|
|
@ -41,9 +41,9 @@ impl Default for SyntectHighlighter {
|
||||||
impl Highlighter for SyntectHighlighter {
|
impl Highlighter for SyntectHighlighter {
|
||||||
fn start_highlighter_state<'h>(
|
fn start_highlighter_state<'h>(
|
||||||
&'h self,
|
&'h self,
|
||||||
source: &dyn SourceCode,
|
source: &dyn SpanContents<'_>,
|
||||||
) -> Box<dyn HighlighterState + 'h> {
|
) -> Box<dyn HighlighterState + 'h> {
|
||||||
if let Some(syntax) = self.get_syntax_from_source(source) {
|
if let Some(syntax) = self.detect_syntax(source) {
|
||||||
let highlighter = syntect::Highlighter::new(&self.theme);
|
let highlighter = syntect::Highlighter::new(&self.theme);
|
||||||
let parse_state = syntect::ParseState::new(syntax);
|
let parse_state = syntect::ParseState::new(syntax);
|
||||||
let highlight_state =
|
let highlight_state =
|
||||||
|
|
@ -81,19 +81,26 @@ impl SyntectHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine syntect SyntaxReference to use for given SourceCode
|
/// Determine syntect SyntaxReference to use for given SourceCode
|
||||||
fn get_syntax_from_source(&self, source: &dyn SourceCode) -> Option<&syntect::SyntaxReference> {
|
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
||||||
if let Some(language) = source.language() {
|
// use language if given
|
||||||
self.syntax_set.find_syntax_by_name(language)
|
if let Some(language) = contents.language() {
|
||||||
} else if let Some(name) = source.name() {
|
return self.syntax_set.find_syntax_by_name(language);
|
||||||
if let Some(ext) = Path::new(name).extension() {
|
|
||||||
self.syntax_set
|
|
||||||
.find_syntax_by_extension(ext.to_string_lossy().as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
// otherwise try to use any file extension provided in the name
|
||||||
|
if let Some(name) = contents.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()?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
src/lib.rs
13
src/lib.rs
|
|
@ -655,13 +655,14 @@
|
||||||
//! automatically use the [`syntect`] crate to highlight the `#[source_code]`
|
//! automatically use the [`syntect`] crate to highlight the `#[source_code]`
|
||||||
//! field of your [`Diagnostic`].
|
//! field of your [`Diagnostic`].
|
||||||
//!
|
//!
|
||||||
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SourceCode`] trait, in order:
|
//! Syntax detection with [`syntect`] is handled by checking 2 methods on the [`SpanContents`] trait, in order:
|
||||||
//! * [language()](SourceCode::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.
|
//! as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
||||||
//! You can set the language of a [`NamedSource`] via the
|
//! You can set the language of the [`SpanContents`] produced by a
|
||||||
//! [`with_language`](NamedSource::with_language) method.
|
//! [`NamedSource`] via the [`with_language`](NamedSource::with_language)
|
||||||
//! * [name()](SourceCode::name) - In the absence of an explicitly set
|
//! method.
|
||||||
//! language, the source code's name is assumed to contain a file name or file path.
|
//! * [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
|
//! 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.
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl NamedSource {
|
||||||
&*self.source
|
&*self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`language`](SourceCode::language) for this source code.
|
/// Sets the [`language`](SpanContents::language) for this source code.
|
||||||
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
||||||
self.language = Some(language.into());
|
self.language = Some(language.into());
|
||||||
self
|
self
|
||||||
|
|
@ -50,27 +50,20 @@ impl SourceCode for NamedSource {
|
||||||
context_lines_before: usize,
|
context_lines_before: usize,
|
||||||
context_lines_after: usize,
|
context_lines_after: usize,
|
||||||
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
let contents = self
|
let inner_contents =
|
||||||
.inner()
|
self.inner()
|
||||||
.read_span(span, context_lines_before, context_lines_after)?;
|
.read_span(span, context_lines_before, context_lines_after)?;
|
||||||
Ok(Box::new(MietteSpanContents::new_named(
|
let mut contents = MietteSpanContents::new_named(
|
||||||
self.name.clone(),
|
self.name.clone(),
|
||||||
contents.data(),
|
inner_contents.data(),
|
||||||
*contents.span(),
|
*inner_contents.span(),
|
||||||
contents.line(),
|
inner_contents.line(),
|
||||||
contents.column(),
|
inner_contents.column(),
|
||||||
contents.line_count(),
|
inner_contents.line_count(),
|
||||||
)))
|
);
|
||||||
}
|
if let Some(language) = &self.language {
|
||||||
|
contents = contents.with_language(language);
|
||||||
fn name(&self) -> Option<&str> {
|
|
||||||
Some(&self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn language(&self) -> Option<&str> {
|
|
||||||
match &self.language {
|
|
||||||
Some(language) => Some(language),
|
|
||||||
None => self.source.language(),
|
|
||||||
}
|
}
|
||||||
|
Ok(Box::new(contents))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,20 +240,6 @@ pub trait SourceCode: Send + Sync {
|
||||||
context_lines_before: usize,
|
context_lines_before: usize,
|
||||||
context_lines_after: usize,
|
context_lines_after: usize,
|
||||||
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
|
||||||
|
|
||||||
/// Optional method. The name of this source, if any
|
|
||||||
fn name(&self) -> Option<&str> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Optional method. The language name for this source code, if any.
|
|
||||||
/// This is used to drive syntax highlighting.
|
|
||||||
///
|
|
||||||
/// Examples: Rust, TOML, C
|
|
||||||
///
|
|
||||||
fn language(&self) -> Option<&str> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A labeled [`SourceSpan`].
|
/// A labeled [`SourceSpan`].
|
||||||
|
|
@ -447,6 +433,15 @@ pub trait SpanContents<'a> {
|
||||||
fn column(&self) -> usize;
|
fn column(&self) -> usize;
|
||||||
/// Total number of lines covered by this `SpanContents`.
|
/// Total number of lines covered by this `SpanContents`.
|
||||||
fn line_count(&self) -> usize;
|
fn line_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Optional method. The language name for this source code, if any.
|
||||||
|
/// This is used to drive syntax highlighting.
|
||||||
|
///
|
||||||
|
/// Examples: Rust, TOML, C
|
||||||
|
///
|
||||||
|
fn language(&self) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -466,6 +461,8 @@ pub struct MietteSpanContents<'a> {
|
||||||
line_count: usize,
|
line_count: usize,
|
||||||
// Optional filename
|
// Optional filename
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
|
// Optional language
|
||||||
|
language: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MietteSpanContents<'a> {
|
impl<'a> MietteSpanContents<'a> {
|
||||||
|
|
@ -484,6 +481,7 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
column,
|
column,
|
||||||
line_count,
|
line_count,
|
||||||
name: None,
|
name: None,
|
||||||
|
language: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -503,8 +501,15 @@ impl<'a> MietteSpanContents<'a> {
|
||||||
column,
|
column,
|
||||||
line_count,
|
line_count,
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
|
language: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`language`](SourceCode::language) for syntax highlighting.
|
||||||
|
pub fn with_language(mut self, language: impl Into<String>) -> Self {
|
||||||
|
self.language = Some(language.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
|
|
@ -526,6 +531,9 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
fn name(&self) -> Option<&str> {
|
fn name(&self) -> Option<&str> {
|
||||||
self.name.as_deref()
|
self.name.as_deref()
|
||||||
}
|
}
|
||||||
|
fn language(&self) -> Option<&str> {
|
||||||
|
self.language.as_deref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span within a [`SourceCode`]
|
/// Span within a [`SourceCode`]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue