@@ -89,6 +89,9 @@ pub struct InjectionsQuery {
89
89
injection_language_capture : Option < Capture > ,
90
90
injection_filename_capture : Option < Capture > ,
91
91
injection_shebang_capture : Option < Capture > ,
92
+ /// 1. The list of matches to compare the parent layer's language
93
+ /// 1. Whether it is negated: `#any-of` or `#not-any-of?`
94
+ injection_parent_layer_langs_predicate : Option < ( Vec < String > , bool ) > ,
92
95
// Note that the injections query is concatenated with the locals query.
93
96
pub ( crate ) local_query : Query ,
94
97
// TODO: Use a Vec<bool> instead?
@@ -108,6 +111,8 @@ impl InjectionsQuery {
108
111
query_source. push_str ( injection_query_text) ;
109
112
query_source. push_str ( local_query_text) ;
110
113
114
+ let mut injection_parent_layer_langs_predicate = None ;
115
+
111
116
let mut injection_properties: HashMap < Pattern , InjectionProperties > = HashMap :: new ( ) ;
112
117
let mut not_scope_inherits = HashSet :: new ( ) ;
113
118
let injection_query = Query :: new ( grammar, injection_query_text, |pattern, predicate| {
@@ -122,6 +127,16 @@ impl InjectionsQuery {
122
127
. or_default ( )
123
128
. include_children = IncludedChildren :: Unnamed
124
129
}
130
+ // Allow filtering for specific languages in
131
+ // `#set! injection.languae injection.parent-layer`
132
+ UserPredicate :: IsAnyOf {
133
+ negated,
134
+ value : INJECTION_PARENT_LAYER ,
135
+ values,
136
+ } => {
137
+ injection_parent_layer_langs_predicate =
138
+ Some ( ( values. into_iter ( ) . map ( ToOwned :: to_owned) . collect ( ) , negated) ) ;
139
+ }
125
140
UserPredicate :: SetProperty {
126
141
key : "injection.include-children" ,
127
142
val : None ,
@@ -167,6 +182,7 @@ impl InjectionsQuery {
167
182
local_query. disable_capture ( "local.reference" ) ;
168
183
169
184
Ok ( InjectionsQuery {
185
+ injection_parent_layer_langs_predicate,
170
186
injection_properties,
171
187
injection_content_capture : injection_query. get_capture ( "injection.content" ) ,
172
188
injection_language_capture : injection_query. get_capture ( "injection.language" ) ,
@@ -195,6 +211,7 @@ impl InjectionsQuery {
195
211
196
212
fn process_match < ' a , ' tree > (
197
213
& self ,
214
+ injection_parent_language : Language ,
198
215
query_match : & QueryMatch < ' a , ' tree > ,
199
216
node_idx : MatchedNodeIdx ,
200
217
source : RopeSlice < ' a > ,
@@ -242,11 +259,41 @@ impl InjectionsQuery {
242
259
last_content_node = i as u32 ;
243
260
}
244
261
}
245
- let marker = marker. or ( properties
246
- . and_then ( |p| p. language . as_deref ( ) )
247
- . map ( InjectionLanguageMarker :: Name ) ) ?;
248
262
249
- let language = loader. language_for_marker ( marker) ?;
263
+ let language = marker
264
+ . and_then ( |m| loader. language_for_marker ( m) )
265
+ . or_else ( || {
266
+ properties
267
+ . and_then ( |p| p. language . as_deref ( ) )
268
+ . and_then ( |name| {
269
+ let matches_predicate = || {
270
+ self . injection_parent_layer_langs_predicate
271
+ . as_ref ( )
272
+ . is_none_or ( |( predicate, is_negated) | {
273
+ predicate. iter ( ) . any ( |capture| {
274
+ let Some ( marker) = loader. language_for_marker (
275
+ InjectionLanguageMarker :: Name ( capture) ,
276
+ ) else {
277
+ return false ;
278
+ } ;
279
+
280
+ if * is_negated {
281
+ marker != injection_parent_language
282
+ } else {
283
+ marker == injection_parent_language
284
+ }
285
+ } )
286
+ } )
287
+ } ;
288
+
289
+ if name == INJECTION_PARENT_LAYER && matches_predicate ( ) {
290
+ Some ( injection_parent_language)
291
+ } else {
292
+ loader. language_for_marker ( InjectionLanguageMarker :: Name ( name) )
293
+ }
294
+ } )
295
+ } ) ?;
296
+
250
297
let scope = if properties. is_some_and ( |p| p. combined ) {
251
298
Some ( InjectionScope :: Pattern {
252
299
pattern : query_match. pattern ( ) ,
@@ -286,6 +333,7 @@ impl InjectionsQuery {
286
333
/// This case should be handled by the calling function
287
334
fn execute < ' a > (
288
335
& ' a self ,
336
+ injection_parent_language : Language ,
289
337
node : & Node < ' a > ,
290
338
source : RopeSlice < ' a > ,
291
339
loader : & ' a impl LanguageLoader ,
@@ -298,7 +346,14 @@ impl InjectionsQuery {
298
346
if query_match. matched_node ( node_idx) . capture != injection_content_capture {
299
347
continue ;
300
348
}
301
- let Some ( mat) = self . process_match ( & query_match, node_idx, source, loader) else {
349
+
350
+ let Some ( mat) = self . process_match (
351
+ injection_parent_language,
352
+ & query_match,
353
+ node_idx,
354
+ source,
355
+ loader,
356
+ ) else {
302
357
query_match. remove ( ) ;
303
358
continue ;
304
359
} ;
@@ -384,7 +439,18 @@ impl Syntax {
384
439
let mut injections: Vec < Injection > = Vec :: with_capacity ( layer_data. injections . len ( ) ) ;
385
440
let mut old_injections = take ( & mut layer_data. injections ) . into_iter ( ) . peekable ( ) ;
386
441
387
- let injection_query = injections_query. execute ( & parse_tree. root_node ( ) , source, loader) ;
442
+ // The language to inject if `(#set! injection.language injection.parent-layer)` is set
443
+ let injection_parent_language = layer_data. parent . map_or_else (
444
+ || self . layer ( self . root ) . language ,
445
+ |layer| self . layer ( layer) . language ,
446
+ ) ;
447
+
448
+ let injection_query = injections_query. execute (
449
+ injection_parent_language,
450
+ & parse_tree. root_node ( ) ,
451
+ source,
452
+ loader,
453
+ ) ;
388
454
389
455
let mut combined_injections: HashMap < InjectionScope , Layer > = HashMap :: with_capacity ( 32 ) ;
390
456
for mat in injection_query {
@@ -713,3 +779,62 @@ fn ranges_intersect(a: &Range, b: &Range) -> bool {
713
779
// Adapted from <https://github.com/helix-editor/helix/blob/8df58b2e1779dcf0046fb51ae1893c1eebf01e7c/helix-core/src/selection.rs#L156-L163>
714
780
a. start == b. start || ( a. end > b. start && b. end > a. start )
715
781
}
782
+
783
+ /// When the language is injected, this value will be set to the
784
+ /// language of the parent layer.
785
+ ///
786
+ /// This is useful e.g. when injecting markdown into documentation
787
+ /// comments for a language such as Rust, and we want the default
788
+ /// code block without any info string to be the same as the parent layer.
789
+ ///
790
+ /// In the next two examples, the language injected into the inner
791
+ /// code block in the documentation comments will be the same as the parent
792
+ /// layer
793
+ ///
794
+ /// ````gleam
795
+ /// /// This code block will have the "gleam" language when
796
+ /// /// no info string is supplied:
797
+ /// ///
798
+ /// /// ```
799
+ /// /// let foo: Int = example()
800
+ /// /// ```
801
+ /// fn example() -> Int { todo }
802
+ /// ````
803
+ ///
804
+ /// ````rust
805
+ /// /// This code block will have the "rust" language when
806
+ /// /// no info string is supplied:
807
+ /// ///
808
+ /// /// ```
809
+ /// /// let foo: i32 = example();
810
+ /// /// ```
811
+ /// fn example() -> i32 { todo!() }
812
+ /// ````
813
+ ///
814
+ /// In the above example, we have two layers:
815
+ ///
816
+ /// ```text
817
+ /// <-- rust -->
818
+ /// <-- markdown -->
819
+ /// ```
820
+ ///
821
+ /// In the `markdown` layer, by default there will be no injection for a
822
+ /// code block with no `(info_string)` node.
823
+ ///
824
+ /// By using `injection.parent-layer`, when markdown is injected into a
825
+ /// language the code block's default value will be the parent layer.
826
+ ///
827
+ /// # Example
828
+ ///
829
+ /// The following injection will have the effect described above for the
830
+ /// specified languages `gleam` and `rust`. All other languages are treated
831
+ /// normally.
832
+ ///
833
+ /// ```scheme
834
+ /// (fenced_code_block
835
+ /// (code_fence_content) @injection.content
836
+ /// (#set! injection.include-unnamed-children)
837
+ /// (#set! injection.language injection.parent-layer)
838
+ /// (#any-of? injection.parent-layer "gleam" "rust"))
839
+ /// ```
840
+ const INJECTION_PARENT_LAYER : & str = "injection.parent-layer" ;
0 commit comments