• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::path::Path;
2 
3 // all syntect imports are explicitly qualified, but their paths are shortened for convenience
4 mod syntect {
5     pub(super) use syntect::{
6         highlighting::{
7             Color, HighlightIterator, HighlightState, Highlighter, Style, Theme, ThemeSet,
8         },
9         parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet},
10     };
11 }
12 
13 use owo_colors::{Rgb, Style, Styled};
14 
15 use crate::{
16     highlighters::{Highlighter, HighlighterState},
17     SpanContents,
18 };
19 
20 use super::BlankHighlighterState;
21 
22 /// Highlights miette [SourceCode] with the [syntect](https://docs.rs/syntect/latest/syntect/) highlighting crate.
23 ///
24 /// Currently only 24-bit truecolor output is supported due to syntect themes
25 /// representing color as RGBA.
26 #[derive(Debug, Clone)]
27 pub struct SyntectHighlighter {
28     theme: syntect::Theme,
29     syntax_set: syntect::SyntaxSet,
30     use_bg_color: bool,
31 }
32 
33 impl Default for SyntectHighlighter {
default() -> Self34     fn default() -> Self {
35         let theme_set = syntect::ThemeSet::load_defaults();
36         let theme = theme_set.themes["base16-ocean.dark"].clone();
37         Self::new_themed(theme, false)
38     }
39 }
40 
41 impl Highlighter for SyntectHighlighter {
start_highlighter_state<'h>( &'h self, source: &dyn SpanContents<'_>, ) -> Box<dyn HighlighterState + 'h>42     fn start_highlighter_state<'h>(
43         &'h self,
44         source: &dyn SpanContents<'_>,
45     ) -> Box<dyn HighlighterState + 'h> {
46         if let Some(syntax) = self.detect_syntax(source) {
47             let highlighter = syntect::Highlighter::new(&self.theme);
48             let parse_state = syntect::ParseState::new(syntax);
49             let highlight_state =
50                 syntect::HighlightState::new(&highlighter, syntect::ScopeStack::new());
51             Box::new(SyntectHighlighterState {
52                 syntax_set: &self.syntax_set,
53                 highlighter,
54                 parse_state,
55                 highlight_state,
56                 use_bg_color: self.use_bg_color,
57             })
58         } else {
59             Box::new(BlankHighlighterState)
60         }
61     }
62 }
63 
64 impl SyntectHighlighter {
65     /// Create a syntect highlighter with the given theme and syntax set.
new(syntax_set: syntect::SyntaxSet, theme: syntect::Theme, use_bg_color: bool) -> Self66     pub fn new(syntax_set: syntect::SyntaxSet, theme: syntect::Theme, use_bg_color: bool) -> Self {
67         Self {
68             theme,
69             syntax_set,
70             use_bg_color,
71         }
72     }
73 
74     /// Create a syntect highlighter with the given theme and the default syntax set.
new_themed(theme: syntect::Theme, use_bg_color: bool) -> Self75     pub fn new_themed(theme: syntect::Theme, use_bg_color: bool) -> Self {
76         Self::new(
77             syntect::SyntaxSet::load_defaults_nonewlines(),
78             theme,
79             use_bg_color,
80         )
81     }
82 
83     /// Determine syntect SyntaxReference to use for given SourceCode
detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference>84     fn detect_syntax(&self, contents: &dyn SpanContents<'_>) -> Option<&syntect::SyntaxReference> {
85         // use language if given
86         if let Some(language) = contents.language() {
87             return self.syntax_set.find_syntax_by_name(language);
88         }
89         // otherwise try to use any file extension provided in the name
90         if let Some(name) = contents.name() {
91             if let Some(ext) = Path::new(name).extension() {
92                 return self
93                     .syntax_set
94                     .find_syntax_by_extension(ext.to_string_lossy().as_ref());
95             }
96         }
97         // finally, attempt to guess syntax based on first line
98         return self.syntax_set.find_syntax_by_first_line(
99             &std::str::from_utf8(contents.data())
100                 .ok()?
101                 .split('\n')
102                 .next()?,
103         );
104     }
105 }
106 
107 /// Stateful highlighting iterator for [SyntectHighlighter]
108 #[derive(Debug)]
109 pub(crate) struct SyntectHighlighterState<'h> {
110     syntax_set: &'h syntect::SyntaxSet,
111     highlighter: syntect::Highlighter<'h>,
112     parse_state: syntect::ParseState,
113     highlight_state: syntect::HighlightState,
114     use_bg_color: bool,
115 }
116 
117 impl<'h> HighlighterState for SyntectHighlighterState<'h> {
highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>>118     fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
119         if let Ok(ops) = self.parse_state.parse_line(line, &self.syntax_set) {
120             let use_bg_color = self.use_bg_color;
121             syntect::HighlightIterator::new(
122                 &mut self.highlight_state,
123                 &ops,
124                 line,
125                 &mut self.highlighter,
126             )
127             .map(|(style, str)| (convert_style(style, use_bg_color).style(str)))
128             .collect()
129         } else {
130             vec![Style::default().style(line)]
131         }
132     }
133 }
134 
135 /// Convert syntect [syntect::Style] into owo_colors [Style] */
136 #[inline]
convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style137 fn convert_style(syntect_style: syntect::Style, use_bg_color: bool) -> Style {
138     if use_bg_color {
139         let fg = blend_fg_color(syntect_style);
140         let bg = convert_color(syntect_style.background);
141         Style::new().color(fg).on_color(bg)
142     } else {
143         let fg = convert_color(syntect_style.foreground);
144         Style::new().color(fg)
145     }
146 }
147 
148 /// Blend foreground RGB into background RGB according to alpha channel
149 #[inline]
blend_fg_color(syntect_style: syntect::Style) -> Rgb150 fn blend_fg_color(syntect_style: syntect::Style) -> Rgb {
151     let fg = syntect_style.foreground;
152     if fg.a == 0xff {
153         return convert_color(fg);
154     }
155     let bg = syntect_style.background;
156     let ratio = fg.a as u32;
157     let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
158     let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
159     let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
160     Rgb(r as u8, g as u8, b as u8)
161 }
162 
163 /// Convert syntect color into owo color.
164 ///
165 /// Note: ignores alpha channel. use [`blend_fg_color`] if you need that
166 ///
167 #[inline]
convert_color(color: syntect::Color) -> Rgb168 fn convert_color(color: syntect::Color) -> Rgb {
169     Rgb(color.r, color.g, color.b)
170 }
171