@@ -89,6 +89,8 @@ pub struct InjectionsQuery {
8989 injection_language_capture : Option < Capture > ,
9090 injection_filename_capture : Option < Capture > ,
9191 injection_shebang_capture : Option < Capture > ,
92+ injection_parent_layer_langs_predicate : Option < Vec < String > > ,
93+ injection_parent_layer_langs_predicate_negated : bool ,
9294 // Note that the injections query is concatenated with the locals query.
9395 pub ( crate ) local_query : Query ,
9496 // TODO: Use a Vec<bool> instead?
@@ -108,6 +110,9 @@ impl InjectionsQuery {
108110 query_source. push_str ( injection_query_text) ;
109111 query_source. push_str ( local_query_text) ;
110112
113+ let mut injection_parent_layer_langs_predicate = None ;
114+ let mut injection_parent_layer_langs_predicate_negated = false ;
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,17 @@ 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_negated = negated;
138+ injection_parent_layer_langs_predicate =
139+ Some ( values. into_iter ( ) . map ( ToOwned :: to_owned) . collect ( ) ) ;
140+ }
125141 UserPredicate :: SetProperty {
126142 key : "injection.include-children" ,
127143 val : None ,
@@ -167,6 +183,8 @@ impl InjectionsQuery {
167183 local_query. disable_capture ( "local.reference" ) ;
168184
169185 Ok ( InjectionsQuery {
186+ injection_parent_layer_langs_predicate,
187+ injection_parent_layer_langs_predicate_negated,
170188 injection_properties,
171189 injection_content_capture : injection_query. get_capture ( "injection.content" ) ,
172190 injection_language_capture : injection_query. get_capture ( "injection.language" ) ,
@@ -195,6 +213,7 @@ impl InjectionsQuery {
195213
196214 fn process_match < ' a , ' tree > (
197215 & self ,
216+ injection_parent_language : Language ,
198217 query_match : & QueryMatch < ' a , ' tree > ,
199218 node_idx : MatchedNodeIdx ,
200219 source : RopeSlice < ' a > ,
@@ -242,11 +261,41 @@ impl InjectionsQuery {
242261 last_content_node = i as u32 ;
243262 }
244263 }
245- let marker = marker. or ( properties
246- . and_then ( |p| p. language . as_deref ( ) )
247- . map ( InjectionLanguageMarker :: Name ) ) ?;
248264
249- let language = loader. language_for_marker ( marker) ?;
265+ let language = marker
266+ . and_then ( |m| loader. language_for_marker ( m) )
267+ . or_else ( || {
268+ properties
269+ . and_then ( |p| p. language . as_deref ( ) )
270+ . and_then ( |name| {
271+ let matches_predicate = || {
272+ self . injection_parent_layer_langs_predicate
273+ . as_ref ( )
274+ . is_none_or ( |predicate| {
275+ predicate. iter ( ) . any ( |capture| {
276+ let Some ( marker) = loader. language_for_marker (
277+ InjectionLanguageMarker :: Name ( capture) ,
278+ ) else {
279+ return false ;
280+ } ;
281+
282+ if self . injection_parent_layer_langs_predicate_negated {
283+ marker != injection_parent_language
284+ } else {
285+ marker == injection_parent_language
286+ }
287+ } )
288+ } )
289+ } ;
290+
291+ if name == INJECTION_PARENT_LAYER && matches_predicate ( ) {
292+ Some ( injection_parent_language)
293+ } else {
294+ loader. language_for_marker ( InjectionLanguageMarker :: Name ( name) )
295+ }
296+ } )
297+ } ) ?;
298+
250299 let scope = if properties. is_some_and ( |p| p. combined ) {
251300 Some ( InjectionScope :: Pattern {
252301 pattern : query_match. pattern ( ) ,
@@ -286,6 +335,7 @@ impl InjectionsQuery {
286335 /// This case should be handled by the calling function
287336 fn execute < ' a > (
288337 & ' a self ,
338+ injection_parent_language : Language ,
289339 node : & Node < ' a > ,
290340 source : RopeSlice < ' a > ,
291341 loader : & ' a impl LanguageLoader ,
@@ -298,7 +348,14 @@ impl InjectionsQuery {
298348 if query_match. matched_node ( node_idx) . capture != injection_content_capture {
299349 continue ;
300350 }
301- let Some ( mat) = self . process_match ( & query_match, node_idx, source, loader) else {
351+
352+ let Some ( mat) = self . process_match (
353+ injection_parent_language,
354+ & query_match,
355+ node_idx,
356+ source,
357+ loader,
358+ ) else {
302359 query_match. remove ( ) ;
303360 continue ;
304361 } ;
@@ -384,7 +441,18 @@ impl Syntax {
384441 let mut injections: Vec < Injection > = Vec :: with_capacity ( layer_data. injections . len ( ) ) ;
385442 let mut old_injections = take ( & mut layer_data. injections ) . into_iter ( ) . peekable ( ) ;
386443
387- let injection_query = injections_query. execute ( & parse_tree. root_node ( ) , source, loader) ;
444+ // The language to inject if `(#set! injection.language injection.parent-layer)` is set
445+ let injection_parent_language = layer_data
446+ . parent
447+ . map ( |layer| self . layer ( layer) . language )
448+ . unwrap_or_else ( || self . layer ( self . root ) . language ) ;
449+
450+ let injection_query = injections_query. execute (
451+ injection_parent_language,
452+ & parse_tree. root_node ( ) ,
453+ source,
454+ loader,
455+ ) ;
388456
389457 let mut combined_injections: HashMap < InjectionScope , Layer > = HashMap :: with_capacity ( 32 ) ;
390458 for mat in injection_query {
@@ -713,3 +781,62 @@ fn ranges_intersect(a: &Range, b: &Range) -> bool {
713781 // Adapted from <https://github.com/helix-editor/helix/blob/8df58b2e1779dcf0046fb51ae1893c1eebf01e7c/helix-core/src/selection.rs#L156-L163>
714782 a. start == b. start || ( a. end > b. start && b. end > a. start )
715783}
784+
785+ /// When the language is injected, this value will be set to the
786+ /// language of the parent layer.
787+ ///
788+ /// This is useful e.g. when injecting markdown into documentation
789+ /// comments for a language such as Rust, and we want the default
790+ /// code block without any info string to be the same as the parent layer.
791+ ///
792+ /// In the next two examples, the language injected into the inner
793+ /// code block in the documentation comments will be the same as the parent
794+ /// layer
795+ ///
796+ /// ````gleam
797+ /// /// This code block will have the "gleam" language when
798+ /// /// no info string is supplied:
799+ /// ///
800+ /// /// ```
801+ /// /// let foo: Int = example()
802+ /// /// ```
803+ /// fn example() -> Int { todo }
804+ /// ````
805+ ///
806+ /// ````rust
807+ /// /// This code block will have the "rust" language when
808+ /// /// no info string is supplied:
809+ /// ///
810+ /// /// ```
811+ /// /// let foo: i32 = example();
812+ /// /// ```
813+ /// fn example() -> i32 { todo!() }
814+ /// ````
815+ ///
816+ /// In the above example, we have two layers:
817+ ///
818+ /// ```text
819+ /// <-- rust -->
820+ /// <-- markdown -->
821+ /// ```
822+ ///
823+ /// In the `markdown` layer, by default there will be no injection for a
824+ /// code block with no `(info_string)` node.
825+ ///
826+ /// By using `injection.parent-layer`, when markdown is injected into a
827+ /// language the code block's default value will be the parent layer.
828+ ///
829+ /// # Example
830+ ///
831+ /// The following injection will have the effect described above for the
832+ /// specified languages `gleam` and `rust`. All other languages are treated
833+ /// normally.
834+ ///
835+ /// ```scheme
836+ /// (fenced_code_block
837+ /// (code_fence_content) @injection.content
838+ /// (#set! injection.include-unnamed-children)
839+ /// (#set! injection.language injection.parent-layer)
840+ /// (#any-of? injection.parent-layer "gleam" "rust"))
841+ /// ```
842+ const INJECTION_PARENT_LAYER : & str = "injection.parent-layer" ;
0 commit comments