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))
|
||||
.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
|
||||
// point. We need this to figure out indentation, so we do one loop
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use owo_colors::Style;
|
||||
|
||||
use crate::SpanContents;
|
||||
|
||||
use super::{Highlighter, HighlighterState};
|
||||
|
||||
/// The default syntax highlighter. It applies `Style::default()` to input text.
|
||||
|
|
@ -10,7 +12,7 @@ pub struct BlankHighlighter;
|
|||
impl Highlighter for BlankHighlighter {
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
_source: &dyn crate::SourceCode,
|
||||
_source: &dyn SpanContents<'_>,
|
||||
) -> Box<dyn super::HighlighterState + 'h> {
|
||||
Box::new(BlankHighlighterState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use crate::SourceCode;
|
||||
use crate::SpanContents;
|
||||
use owo_colors::Styled;
|
||||
|
||||
#[cfg(feature = "syntect-highlighter")]
|
||||
|
|
@ -24,22 +24,22 @@ mod blank;
|
|||
#[cfg(feature = "syntect-highlighter")]
|
||||
mod syntect;
|
||||
|
||||
/// A syntax highlighter for highlighting miette [SourceCode] snippets.
|
||||
/// A syntax highlighter for highlighting miette [`SourceCode`](crate::SourceCode) snippets.
|
||||
pub trait Highlighter {
|
||||
/// Creates a new [HighlighterState] to begin parsing and highlighting
|
||||
/// a [SourceCode] snippet.
|
||||
/// 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 [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
|
||||
/// language syntax and make other initialization decisions prior
|
||||
/// 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
|
||||
/// responsible for the actual rendering.
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
source: &dyn SourceCode,
|
||||
source: &dyn SpanContents<'_>,
|
||||
) -> Box<dyn HighlighterState + 'h>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use owo_colors::{Rgb, Style, Styled};
|
|||
|
||||
use crate::{
|
||||
highlighters::{Highlighter, HighlighterState},
|
||||
SourceCode,
|
||||
SpanContents,
|
||||
};
|
||||
|
||||
use super::BlankHighlighterState;
|
||||
|
|
@ -41,9 +41,9 @@ impl Default for SyntectHighlighter {
|
|||
impl Highlighter for SyntectHighlighter {
|
||||
fn start_highlighter_state<'h>(
|
||||
&'h self,
|
||||
source: &dyn SourceCode,
|
||||
source: &dyn SpanContents<'_>,
|
||||
) -> 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 parse_state = syntect::ParseState::new(syntax);
|
||||
let highlight_state =
|
||||
|
|
@ -81,19 +81,26 @@ impl SyntectHighlighter {
|
|||
}
|
||||
|
||||
/// Determine syntect SyntaxReference to use for given SourceCode
|
||||
fn get_syntax_from_source(&self, source: &dyn SourceCode) -> Option<&syntect::SyntaxReference> {
|
||||
if let Some(language) = source.language() {
|
||||
self.syntax_set.find_syntax_by_name(language)
|
||||
} else if let Some(name) = source.name() {
|
||||
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
|
||||
fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
|
||||
// use language if given
|
||||
if let Some(language) = contents.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(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]`
|
||||
//! field of your [`Diagnostic`].
|
||||
//!
|
||||
//! 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
|
||||
//! 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
|
||||
//! as a string. For example `"Rust"` will indicate Rust syntax highlighting.
|
||||
//! You can set the language of a [`NamedSource`] via the
|
||||
//! [`with_language`](NamedSource::with_language) method.
|
||||
//! * [name()](SourceCode::name) - In the absence of an explicitly set
|
||||
//! language, the source code's name is assumed to contain a file name or file path.
|
||||
//! 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
|
||||
//! 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.
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl NamedSource {
|
|||
&*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 {
|
||||
self.language = Some(language.into());
|
||||
self
|
||||
|
|
@ -50,27 +50,20 @@ impl SourceCode for NamedSource {
|
|||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||
let contents = self
|
||||
.inner()
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
Ok(Box::new(MietteSpanContents::new_named(
|
||||
let inner_contents =
|
||||
self.inner()
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
let mut contents = MietteSpanContents::new_named(
|
||||
self.name.clone(),
|
||||
contents.data(),
|
||||
*contents.span(),
|
||||
contents.line(),
|
||||
contents.column(),
|
||||
contents.line_count(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(&self.name)
|
||||
}
|
||||
|
||||
fn language(&self) -> Option<&str> {
|
||||
match &self.language {
|
||||
Some(language) => Some(language),
|
||||
None => self.source.language(),
|
||||
inner_contents.data(),
|
||||
*inner_contents.span(),
|
||||
inner_contents.line(),
|
||||
inner_contents.column(),
|
||||
inner_contents.line_count(),
|
||||
);
|
||||
if let Some(language) = &self.language {
|
||||
contents = contents.with_language(language);
|
||||
}
|
||||
Ok(Box::new(contents))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,20 +240,6 @@ pub trait SourceCode: Send + Sync {
|
|||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> 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`].
|
||||
|
|
@ -447,6 +433,15 @@ pub trait SpanContents<'a> {
|
|||
fn column(&self) -> usize;
|
||||
/// Total number of lines covered by this `SpanContents`.
|
||||
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,
|
||||
// Optional filename
|
||||
name: Option<String>,
|
||||
// Optional language
|
||||
language: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> MietteSpanContents<'a> {
|
||||
|
|
@ -484,6 +481,7 @@ impl<'a> MietteSpanContents<'a> {
|
|||
column,
|
||||
line_count,
|
||||
name: None,
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -503,8 +501,15 @@ impl<'a> MietteSpanContents<'a> {
|
|||
column,
|
||||
line_count,
|
||||
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> {
|
||||
|
|
@ -526,6 +531,9 @@ impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
|||
fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
fn language(&self) -> Option<&str> {
|
||||
self.language.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Span within a [`SourceCode`]
|
||||
|
|
|
|||
Loading…
Reference in New Issue