@@ -13,14 +13,18 @@ use std::{
13
13
use anyhow:: { Context , Result } ;
14
14
use arc_swap:: { ArcSwap , Guard } ;
15
15
use config:: { Configuration , FileType , LanguageConfiguration , LanguageServerConfiguration } ;
16
+ use foldhash:: HashSet ;
16
17
use helix_loader:: grammar:: get_language;
17
18
use helix_stdx:: rope:: RopeSliceExt as _;
18
19
use once_cell:: sync:: OnceCell ;
19
20
use ropey:: RopeSlice ;
20
21
use tree_house:: {
21
22
highlighter,
22
- query_iter:: QueryIter ,
23
- tree_sitter:: { Grammar , InactiveQueryCursor , InputEdit , Node , Query , RopeInput , Tree } ,
23
+ query_iter:: { QueryIter , QueryIterEvent } ,
24
+ tree_sitter:: {
25
+ query:: { InvalidPredicateError , UserPredicate } ,
26
+ Capture , Grammar , InactiveQueryCursor , InputEdit , Node , Pattern , Query , RopeInput , Tree ,
27
+ } ,
24
28
Error , InjectionLanguageMarker , LanguageConfig as SyntaxConfig , Layer ,
25
29
} ;
26
30
@@ -37,6 +41,7 @@ pub struct LanguageData {
37
41
syntax : OnceCell < Option < SyntaxConfig > > ,
38
42
indent_query : OnceCell < Option < IndentQuery > > ,
39
43
textobject_query : OnceCell < Option < TextObjectQuery > > ,
44
+ rainbow_query : OnceCell < Option < RainbowQuery > > ,
40
45
}
41
46
42
47
impl LanguageData {
@@ -46,6 +51,7 @@ impl LanguageData {
46
51
syntax : OnceCell :: new ( ) ,
47
52
indent_query : OnceCell :: new ( ) ,
48
53
textobject_query : OnceCell :: new ( ) ,
54
+ rainbow_query : OnceCell :: new ( ) ,
49
55
}
50
56
}
51
57
@@ -154,6 +160,36 @@ impl LanguageData {
154
160
. as_ref ( )
155
161
}
156
162
163
+ /// Compiles the rainbows.scm query for a language.
164
+ /// This function should only be used by this module or the xtask crate.
165
+ pub fn compile_rainbow_query (
166
+ grammar : Grammar ,
167
+ config : & LanguageConfiguration ,
168
+ ) -> Result < Option < RainbowQuery > > {
169
+ let name = & config. language_id ;
170
+ let text = read_query ( name, "rainbows.scm" ) ;
171
+ if text. is_empty ( ) {
172
+ return Ok ( None ) ;
173
+ }
174
+ let rainbow_query = RainbowQuery :: new ( grammar, & text)
175
+ . with_context ( || format ! ( "Failed to compile rainbows.scm query for '{name}'" ) ) ?;
176
+ Ok ( Some ( rainbow_query) )
177
+ }
178
+
179
+ fn rainbow_query ( & self , loader : & Loader ) -> Option < & RainbowQuery > {
180
+ self . rainbow_query
181
+ . get_or_init ( || {
182
+ let grammar = self . syntax_config ( loader) ?. grammar ;
183
+ Self :: compile_rainbow_query ( grammar, & self . config )
184
+ . map_err ( |err| {
185
+ log:: error!( "{err}" ) ;
186
+ } )
187
+ . ok ( )
188
+ . flatten ( )
189
+ } )
190
+ . as_ref ( )
191
+ }
192
+
157
193
fn reconfigure ( & self , scopes : & [ String ] ) {
158
194
if let Some ( Some ( config) ) = self . syntax . get ( ) {
159
195
reconfigure_highlights ( config, scopes) ;
@@ -324,6 +360,10 @@ impl Loader {
324
360
self . language ( lang) . textobject_query ( self )
325
361
}
326
362
363
+ fn rainbow_query ( & self , lang : Language ) -> Option < & RainbowQuery > {
364
+ self . language ( lang) . rainbow_query ( self )
365
+ }
366
+
327
367
pub fn language_server_configs ( & self ) -> & HashMap < String , LanguageServerConfiguration > {
328
368
& self . language_server_configs
329
369
}
@@ -496,6 +536,79 @@ impl Syntax {
496
536
{
497
537
QueryIter :: new ( & self . inner , source, loader, range)
498
538
}
539
+
540
+ pub fn rainbow_highlights (
541
+ & self ,
542
+ source : RopeSlice ,
543
+ rainbow_length : usize ,
544
+ loader : & Loader ,
545
+ range : impl RangeBounds < u32 > ,
546
+ ) -> OverlayHighlights {
547
+ struct RainbowScope < ' tree > {
548
+ end : u32 ,
549
+ node : Option < Node < ' tree > > ,
550
+ highlight : Highlight ,
551
+ }
552
+
553
+ let mut scope_stack = Vec :: < RainbowScope > :: new ( ) ;
554
+ let mut highlights = Vec :: new ( ) ;
555
+ let mut query_iter = self . query_iter :: < _ , ( ) , _ > (
556
+ source,
557
+ |lang| loader. rainbow_query ( lang) . map ( |q| & q. query ) ,
558
+ range,
559
+ ) ;
560
+
561
+ while let Some ( event) = query_iter. next ( ) {
562
+ let QueryIterEvent :: Match ( mat) = event else {
563
+ continue ;
564
+ } ;
565
+
566
+ let rainbow_query = loader
567
+ . rainbow_query ( query_iter. current_language ( ) )
568
+ . expect ( "language must have a rainbow query to emit matches" ) ;
569
+
570
+ let byte_range = mat. node . byte_range ( ) ;
571
+ // Pop any scopes that end before this capture begins.
572
+ while scope_stack
573
+ . last ( )
574
+ . is_some_and ( |scope| byte_range. start >= scope. end )
575
+ {
576
+ scope_stack. pop ( ) ;
577
+ }
578
+
579
+ let capture = Some ( mat. capture ) ;
580
+ if capture == rainbow_query. scope_capture {
581
+ scope_stack. push ( RainbowScope {
582
+ end : byte_range. end ,
583
+ node : if rainbow_query
584
+ . include_children_patterns
585
+ . contains ( & mat. pattern )
586
+ {
587
+ None
588
+ } else {
589
+ Some ( mat. node . clone ( ) )
590
+ } ,
591
+ highlight : Highlight :: new ( ( scope_stack. len ( ) % rainbow_length) as u32 ) ,
592
+ } ) ;
593
+ } else if capture == rainbow_query. bracket_capture {
594
+ if let Some ( scope) = scope_stack. last ( ) {
595
+ if !scope
596
+ . node
597
+ . as_ref ( )
598
+ . is_some_and ( |node| mat. node . parent ( ) . as_ref ( ) != Some ( node) )
599
+ {
600
+ let start = source
601
+ . byte_to_char ( source. floor_char_boundary ( byte_range. start as usize ) ) ;
602
+ let end =
603
+ source. byte_to_char ( source. ceil_char_boundary ( byte_range. end as usize ) ) ;
604
+ highlights. push ( ( scope. highlight , start..end) ) ;
605
+ }
606
+ }
607
+ }
608
+ }
609
+
610
+ OverlayHighlights :: Heterogenous { highlights }
611
+ }
499
612
}
500
613
501
614
pub type Highlighter < ' a > = highlighter:: Highlighter < ' a , ' a , Loader > ;
@@ -939,6 +1052,57 @@ fn pretty_print_tree_impl<W: fmt::Write>(
939
1052
Ok ( ( ) )
940
1053
}
941
1054
1055
+ /// Finds the child of `node` which contains the given byte range.
1056
+
1057
+ pub fn child_for_byte_range < ' a > ( node : & Node < ' a > , range : ops:: Range < u32 > ) -> Option < Node < ' a > > {
1058
+ for child in node. children ( ) {
1059
+ let child_range = child. byte_range ( ) ;
1060
+
1061
+ if range. start >= child_range. start && range. end <= child_range. end {
1062
+ return Some ( child) ;
1063
+ }
1064
+ }
1065
+
1066
+ None
1067
+ }
1068
+
1069
+ #[ derive( Debug ) ]
1070
+ pub struct RainbowQuery {
1071
+ query : Query ,
1072
+ include_children_patterns : HashSet < Pattern > ,
1073
+ scope_capture : Option < Capture > ,
1074
+ bracket_capture : Option < Capture > ,
1075
+ }
1076
+
1077
+ impl RainbowQuery {
1078
+ fn new ( grammar : Grammar , source : & str ) -> Result < Self , tree_sitter:: query:: ParseError > {
1079
+ let mut include_children_patterns = HashSet :: default ( ) ;
1080
+
1081
+ let query = Query :: new ( grammar, source, |pattern, predicate| match predicate {
1082
+ UserPredicate :: SetProperty {
1083
+ key : "rainbow.include-children" ,
1084
+ val,
1085
+ } => {
1086
+ if val. is_some ( ) {
1087
+ return Err (
1088
+ "property 'rainbow.include-children' does not take an argument" . into ( ) ,
1089
+ ) ;
1090
+ }
1091
+ include_children_patterns. insert ( pattern) ;
1092
+ Ok ( ( ) )
1093
+ }
1094
+ _ => Err ( InvalidPredicateError :: unknown ( predicate) ) ,
1095
+ } ) ?;
1096
+
1097
+ Ok ( Self {
1098
+ include_children_patterns,
1099
+ scope_capture : query. get_capture ( "rainbow.scope" ) ,
1100
+ bracket_capture : query. get_capture ( "rainbow.bracket" ) ,
1101
+ query,
1102
+ } )
1103
+ }
1104
+ }
1105
+
942
1106
#[ cfg( test) ]
943
1107
mod test {
944
1108
use once_cell:: sync:: Lazy ;
0 commit comments