@@ -89,6 +89,9 @@ pub struct InjectionsQuery {
8989 injection_language_capture : Option < Capture > ,
9090 injection_filename_capture : Option < Capture > ,
9191 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 ) > ,
9295 // Note that the injections query is concatenated with the locals query.
9396 pub ( crate ) local_query : Query ,
9497 // TODO: Use a Vec<bool> instead?
@@ -108,6 +111,8 @@ impl InjectionsQuery {
108111 query_source. push_str ( injection_query_text) ;
109112 query_source. push_str ( local_query_text) ;
110113
114+ let mut injection_parent_layer_langs_predicate = None ;
115+
111116 let mut injection_properties: HashMap < Pattern , InjectionProperties > = HashMap :: new ( ) ;
112117 let mut not_scope_inherits = HashSet :: new ( ) ;
113118 let injection_query = Query :: new ( grammar, injection_query_text, |pattern, predicate| {
@@ -122,6 +127,16 @@ impl InjectionsQuery {
122127 . or_default ( )
123128 . include_children = IncludedChildren :: Unnamed
124129 }
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+ }
125140 UserPredicate :: SetProperty {
126141 key : "injection.include-children" ,
127142 val : None ,
@@ -167,6 +182,7 @@ impl InjectionsQuery {
167182 local_query. disable_capture ( "local.reference" ) ;
168183
169184 Ok ( InjectionsQuery {
185+ injection_parent_layer_langs_predicate,
170186 injection_properties,
171187 injection_content_capture : injection_query. get_capture ( "injection.content" ) ,
172188 injection_language_capture : injection_query. get_capture ( "injection.language" ) ,
@@ -195,6 +211,7 @@ impl InjectionsQuery {
195211
196212 fn process_match < ' a , ' tree > (
197213 & self ,
214+ injection_parent_language : Language ,
198215 query_match : & QueryMatch < ' a , ' tree > ,
199216 node_idx : MatchedNodeIdx ,
200217 source : RopeSlice < ' a > ,
@@ -242,11 +259,41 @@ impl InjectionsQuery {
242259 last_content_node = i as u32 ;
243260 }
244261 }
245- let marker = marker. or ( properties
246- . and_then ( |p| p. language . as_deref ( ) )
247- . map ( InjectionLanguageMarker :: Name ) ) ?;
248262
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+
250297 let scope = if properties. is_some_and ( |p| p. combined ) {
251298 Some ( InjectionScope :: Pattern {
252299 pattern : query_match. pattern ( ) ,
@@ -286,6 +333,7 @@ impl InjectionsQuery {
286333 /// This case should be handled by the calling function
287334 fn execute < ' a > (
288335 & ' a self ,
336+ injection_parent_language : Language ,
289337 node : & Node < ' a > ,
290338 source : RopeSlice < ' a > ,
291339 loader : & ' a impl LanguageLoader ,
@@ -298,7 +346,14 @@ impl InjectionsQuery {
298346 if query_match. matched_node ( node_idx) . capture != injection_content_capture {
299347 continue ;
300348 }
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 {
302357 query_match. remove ( ) ;
303358 continue ;
304359 } ;
@@ -384,7 +439,18 @@ impl Syntax {
384439 let mut injections: Vec < Injection > = Vec :: with_capacity ( layer_data. injections . len ( ) ) ;
385440 let mut old_injections = take ( & mut layer_data. injections ) . into_iter ( ) . peekable ( ) ;
386441
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+ ) ;
388454
389455 let mut combined_injections: HashMap < InjectionScope , Layer > = HashMap :: with_capacity ( 32 ) ;
390456 for mat in injection_query {
@@ -713,3 +779,62 @@ fn ranges_intersect(a: &Range, b: &Range) -> bool {
713779 // Adapted from <https://github.com/helix-editor/helix/blob/8df58b2e1779dcf0046fb51ae1893c1eebf01e7c/helix-core/src/selection.rs#L156-L163>
714780 a. start == b. start || ( a. end > b. start && b. end > a. start )
715781}
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