use SpanContents instead of SourceCode for language detection

This commit is contained in:
Adam Curtis 2023-11-11 17:21:32 -05:00
parent c679d87bef
commit 9c019a87af
7 changed files with 77 additions and 66 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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>;
}

View File

@ -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()?,
);
}
}

View File

@ -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.
//!

View File

@ -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))
}
}

View File

@ -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`]