@@ -3271,7 +3271,7 @@ function detectDeclarations(grammar: CstGrammar, tokenNames: Set<string>): DeclI
32713271 * the lookahead (same head, broadest scope) supplies the key BODY pattern.
32723272 * Returns null when the shape is absent (so non-YAML grammars are unaffected).
32733273 */
3274- function detectExplicitKey ( grammar : CstGrammar ) : { indicator : string ; keyScope : string ; keyBody : string ; prefix : string | null } | null {
3274+ function detectExplicitKey ( grammar : CstGrammar ) : { indicator : string ; keyScope : string ; keyBody : string ; prefixGroups : { scope : string ; pattern : string } [ ] } | null {
32753275 if ( ! grammar . indent ) return null ;
32763276
32773277 // Find a separator literal S that heads a nested seq sibling of a head-indicator literal I.
@@ -3322,15 +3322,13 @@ function detectExplicitKey(grammar: CstGrammar): { indicator: string; keyScope:
33223322 // placeholders are `(?!)` and excluded). Their patterns join into an optional, repeatable
33233323 // `(?:<pat>[\t ]+)*` group inserted between the indicator's whitespace and the key body — so a
33243324 // node decorated by any run of anchors/tags still resolves the trailing scalar as the key.
3325- // The prefix group is NON-CAPTURING: it CONSUMES the anchor/tag so the key (group 3) is reached,
3326- // which means an anchor/tag sitting in this rare `? <prefix> key` position is left UNSCOPED
3327- // (variable-length, order-free → no fixed capture can scope it, and a begin/end region risks
3328- // over-running the value on the next line). Those decorators are NOT a graded role (the oracle
3329- // emits only key/value scalars + comments), and the KEY — the role that matters — is scoped
3330- // correctly; outside the explicit-key indicator, anchors/tags keep their normal `#anchor`/`#tag`
3331- // scopes. Derived from the grammar; `null` when the family has no such prefix token (most grammars).
3325+ // The prefix tokens are grouped BY SCOPE so §2b can emit one CAPTURING group per scope (anchor →
3326+ // entity.name.type.anchor, tag → storage.type.tag): a `*`-repeated capture takes the LAST match,
3327+ // which scopes the common single `? &a key` / `? !!t key` correctly (a rare multi-decorator run
3328+ // scopes only its last anchor + last tag — acceptable). Derived from the grammar; empty when the
3329+ // family has no such prefix token (most grammars).
33323330 const tokenByName = new Map ( grammar . tokens . map ( t => [ t . name , t ] as const ) ) ;
3333- const prefixPats = new Set < string > ( ) ;
3331+ const prefixByScope = new Map < string , Set < string > > ( ) ;
33343332 // A NODE-prefix token is an `opt(token)` that decorates a value: it sits in a seq whose tail
33353333 // (after it) eventually reaches the VALUE ALTERNATION — i.e. the seq contains an `alt` later on.
33363334 // This selects YAML's `opt(Anchor)`/`opt(Tag)` at the head of `Node`/`InlineNode` (each followed
@@ -3352,7 +3350,9 @@ function detectExplicitKey(grammar: CstGrammar): { indicator: string; keyScope:
33523350 const it = e . items [ i ] ;
33533351 if ( it . type === 'quantifier' && it . kind === '?' && it . body . type === 'ref' && seqHasAltLater ( e . items , i + 1 ) ) {
33543352 const t = tokenByName . get ( it . body . name ) ;
3355- if ( t && ! tokenPatternIsNever ( t ) && ! t . string ) prefixPats . add ( tokenPatternSource ( t ) ) ;
3353+ if ( t && ! tokenPatternIsNever ( t ) && ! t . string && t . scope ) {
3354+ ( prefixByScope . get ( t . scope ) ?? prefixByScope . set ( t . scope , new Set ( ) ) . get ( t . scope ) ! ) . add ( tokenPatternSource ( t ) ) ;
3355+ }
33563356 }
33573357 }
33583358 e . items . forEach ( findPrefix ) ;
@@ -3361,10 +3361,8 @@ function detectExplicitKey(grammar: CstGrammar): { indicator: string; keyScope:
33613361 else if ( e . type === 'sep' ) findPrefix ( e . element ) ;
33623362 } ;
33633363 for ( const r of grammar . rules ) findPrefix ( r . body ) ;
3364- const prefix = prefixPats . size
3365- ? `(?:(?:${ [ ...prefixPats ] . join ( '|' ) } )[\\t ]+)*`
3366- : null ;
3367- return { indicator, keyScope, keyBody, prefix } ;
3364+ const prefixGroups = [ ...prefixByScope ] . map ( ( [ scope , pats ] ) => ( { scope, pattern : [ ...pats ] . join ( '|' ) } ) ) ;
3365+ return { indicator, keyScope, keyBody, prefixGroups } ;
33683366}
33693367
33703368// ── Flow-collection detection (YAML `{ … }` mapping / `[ … ]` sequence) ──
@@ -4654,14 +4652,25 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
46544652 // (the indicator literal, the key scope, the key body); null for non-indentation grammars.
46554653 const explicitKey = detectExplicitKey ( grammar ) ;
46564654 if ( explicitKey ) {
4657- // `(indicator)( whitespace )(optional node-prefix: anchors/tags)(key-scalar)` on one line —
4658- // the dominant explicit-key shape. The prefix group is NON-capturing so the key stays group 3.
4659- const prefixGroup = explicitKey . prefix ?? '' ;
4655+ // `(indicator)( whitespace )(optional node-prefix: anchors/tags)(key-scalar)` on one line — the
4656+ // dominant explicit-key shape. The node-prefix decorators (`? &a key` / `? !!t key`) get one
4657+ // CAPTURING group per scope so they are scoped (anchor/tag), not silently consumed; the key
4658+ // scalar's capture index follows them: 1=indicator, 2=ws, 3…N=prefix scopes, N+1=key.
4659+ const prefixCaps : Record < string , { name : string } > = { } ;
4660+ const prefixAlts : string [ ] = [ ] ;
4661+ let grp = 3 ;
4662+ for ( const pg of explicitKey . prefixGroups ) {
4663+ prefixAlts . push ( `(${ pg . pattern } )` ) ;
4664+ prefixCaps [ String ( grp ) ] = { name : `${ pg . scope } .${ langName } ` } ;
4665+ grp ++ ;
4666+ }
4667+ const prefixGroup = prefixAlts . length ? `(?:(?:${ prefixAlts . join ( '|' ) } )[\\t ]+)*` : '' ;
46604668 repository [ 'explicit-key' ] = {
46614669 match : `(${ escapeRegex ( explicitKey . indicator ) } )([\\t ]+)${ prefixGroup } (${ explicitKey . keyBody } )` ,
46624670 captures : {
46634671 '1' : { name : `punctuation.definition.map.key.${ langName } ` } ,
4664- '3' : { name : `${ explicitKey . keyScope } .${ langName } ` } ,
4672+ ...prefixCaps ,
4673+ [ String ( grp ) ] : { name : `${ explicitKey . keyScope } .${ langName } ` } ,
46654674 } ,
46664675 } ;
46674676 topPatterns . push ( { include : '#explicit-key' } ) ;
0 commit comments