11use crate :: db:: read:: Entry ;
22use crate :: errors:: Error ;
3- use sha2:: { Digest , Sha256 } ;
43use std:: cmp:: Ordering ;
5- use std:: sync:: LazyLock ;
64use syntect:: html:: { line_tokens_to_classed_spans, ClassStyle } ;
75use syntect:: parsing:: { ParseState , ScopeStack , SyntaxReference , SyntaxSet } ;
86use syntect:: util:: LinesWithEndings ;
@@ -23,108 +21,98 @@ pub enum Theme {
2321#[ derive( Clone ) ]
2422pub struct Html ( String ) ;
2523
26- pub static DATA : LazyLock < Data > = LazyLock :: new ( || {
27- let syntax_set = two_face:: syntax:: extra_newlines ( ) ;
28- let mut syntaxes = syntax_set. syntaxes ( ) . to_vec ( ) ;
29- syntaxes. sort_by ( |a, b| {
30- a. name
31- . to_lowercase ( )
32- . partial_cmp ( & b. name . to_lowercase ( ) )
33- . unwrap_or ( Ordering :: Less )
34- } ) ;
35-
36- Data {
37- syntax_set,
38- syntaxes,
39- }
40- } ) ;
41-
42- /// Combines content with a filename containing the hash of the content.
43- pub struct Hashed < ' a > {
44- pub name : String ,
45- pub content : & ' a str ,
46- }
47-
48- pub struct Data {
49- pub syntax_set : SyntaxSet ,
24+ #[ derive( Clone ) ]
25+ pub struct Highlighter {
26+ syntax_set : SyntaxSet ,
5027 pub syntaxes : Vec < SyntaxReference > ,
5128}
5229
53- impl < ' a > Hashed < ' a > {
54- fn new ( name : & str , ext : & str , content : & ' a str ) -> Self {
55- let name = format ! (
56- "{name}.{}.{ext}" ,
57- hex:: encode( Sha256 :: digest( content. as_bytes( ) ) )
58- . get( 0 ..16 )
59- . expect( "at least 16 characters" )
60- ) ;
61-
62- Self { name, content }
63- }
64- }
65-
66- fn highlight ( source : & str , ext : & str ) -> Result < String , Error > {
67- let syntax_ref = DATA
68- . syntax_set
69- . find_syntax_by_extension ( ext)
70- . unwrap_or_else ( || {
71- DATA . syntax_set
72- . find_syntax_by_extension ( "txt" )
73- . expect ( "finding txt syntax" )
30+ impl Default for Highlighter {
31+ fn default ( ) -> Self {
32+ let syntax_set = two_face:: syntax:: extra_newlines ( ) ;
33+ let mut syntaxes = syntax_set. syntaxes ( ) . to_vec ( ) ;
34+ syntaxes. sort_by ( |a, b| {
35+ a. name
36+ . to_lowercase ( )
37+ . partial_cmp ( & b. name . to_lowercase ( ) )
38+ . unwrap_or ( Ordering :: Less )
7439 } ) ;
7540
76- let mut parse_state = ParseState :: new ( syntax_ref) ;
77- let mut html = String :: from ( "<table><tbody>" ) ;
78- let mut scope_stack = ScopeStack :: new ( ) ;
79-
80- for ( mut line_number, line) in LinesWithEndings :: from ( source) . enumerate ( ) {
81- let ( formatted, delta) = if line. len ( ) > HIGHLIGHT_LINE_LENGTH_CUTOFF {
82- ( line. to_string ( ) , 0 )
83- } else {
84- let parsed = parse_state. parse_line ( line, & DATA . syntax_set ) ?;
85- line_tokens_to_classed_spans (
86- line,
87- parsed. as_slice ( ) ,
88- ClassStyle :: Spaced ,
89- & mut scope_stack,
90- ) ?
91- } ;
92-
93- line_number += 1 ;
94- let line_number = format ! (
95- r#"<tr><td class="line-number" id="L{line_number}"><a href=#L{line_number}>{line_number:>4}</a></td>"#
96- ) ;
97- html. push_str ( & line_number) ;
98- html. push_str ( r#"<td class="line">"# ) ;
99-
100- if delta < 0 {
101- html. push_str ( & "<span>" . repeat ( delta. abs ( ) . try_into ( ) ?) ) ;
41+ Self {
42+ syntax_set,
43+ syntaxes,
10244 }
45+ }
46+ }
10347
104- // Strip stray newlines that cause vertically stretched lines.
105- for c in formatted. chars ( ) . filter ( |c| * c != '\n' ) {
106- html. push ( c) ;
48+ impl Highlighter {
49+ fn highlight_inner ( & self , source : & str , ext : & str ) -> Result < String , Error > {
50+ let syntax_ref = self
51+ . syntax_set
52+ . find_syntax_by_extension ( ext)
53+ . unwrap_or_else ( || {
54+ self . syntax_set
55+ . find_syntax_by_extension ( "txt" )
56+ . expect ( "finding txt syntax" )
57+ } ) ;
58+
59+ let mut parse_state = ParseState :: new ( syntax_ref) ;
60+ let mut html = String :: from ( "<table><tbody>" ) ;
61+ let mut scope_stack = ScopeStack :: new ( ) ;
62+
63+ for ( mut line_number, line) in LinesWithEndings :: from ( source) . enumerate ( ) {
64+ let ( formatted, delta) = if line. len ( ) > HIGHLIGHT_LINE_LENGTH_CUTOFF {
65+ ( line. to_string ( ) , 0 )
66+ } else {
67+ let parsed = parse_state. parse_line ( line, & self . syntax_set ) ?;
68+ line_tokens_to_classed_spans (
69+ line,
70+ parsed. as_slice ( ) ,
71+ ClassStyle :: Spaced ,
72+ & mut scope_stack,
73+ ) ?
74+ } ;
75+
76+ line_number += 1 ;
77+ let line_number = format ! (
78+ r#"<tr><td class="line-number" id="L{line_number}"><a href=#L{line_number}>{line_number:>4}</a></td>"#
79+ ) ;
80+ html. push_str ( & line_number) ;
81+ html. push_str ( r#"<td class="line">"# ) ;
82+
83+ if delta < 0 {
84+ html. push_str ( & "<span>" . repeat ( delta. abs ( ) . try_into ( ) ?) ) ;
85+ }
86+
87+ // Strip stray newlines that cause vertically stretched lines.
88+ for c in formatted. chars ( ) . filter ( |c| * c != '\n' ) {
89+ html. push ( c) ;
90+ }
91+
92+ if delta > 0 {
93+ html. push_str ( & "</span>" . repeat ( delta. try_into ( ) ?) ) ;
94+ }
95+
96+ html. push_str ( "</td></tr>" ) ;
10797 }
10898
109- if delta > 0 {
110- html. push_str ( & "</span>" . repeat ( delta. try_into ( ) ?) ) ;
111- }
99+ html. push_str ( "</tbody></table>" ) ;
112100
113- html . push_str ( "</td></tr>" ) ;
101+ Ok ( html )
114102 }
115103
116- html. push_str ( "</tbody></table>" ) ;
117-
118- Ok ( html)
119- }
104+ /// Highlight `entry` with the given file extension.
105+ pub async fn highlight ( & self , entry : Entry , ext : String ) -> Result < Html , Error > {
106+ let highlighter = self . clone ( ) ;
120107
121- impl Html {
122- pub async fn from ( entry : Entry , ext : String ) -> Result < Self , Error > {
123- Ok ( Self (
124- tokio:: task:: spawn_blocking ( move || highlight ( & entry. text , & ext) ) . await ??,
108+ Ok ( Html (
109+ tokio:: task:: spawn_blocking ( move || highlighter. highlight_inner ( & entry. text , & ext) )
110+ . await ??,
125111 ) )
126112 }
113+ }
127114
115+ impl Html {
128116 pub fn into_inner ( self ) -> String {
129117 self . 0
130118 }
0 commit comments