Skip to content

Commit 5c3f22f

Browse files
committed
Sub-scope escapes inside quoted YAML keys
A double/single-quoted KEY (`"a\nb": v`) is scoped entity.name.tag, not string.*, so it skipped the value-string region and emitted as a FLAT token — its `\n` / `''` escapes were painted as part of the key name instead of constant.character.escape. Emit the quoted key as a begin/end region (name = key scope, patterns = the escape) gated on a begin lookahead that the close delim is followed by the key separator — derived from the token's own body + key-sep lookahead (minus the open delim) — so a quoted VALUE (no trailing separator) still falls through to its own string region. The entity.name.tag precedence rank now also covers begin/end regions (not just flat matches) so the keyed region out-ranks the value-string region on the shared `"`. YAML scope-gap escape 25/28 -> 28/28 (gap to official +0.3). Parser untouched (highlighter-only); other grammars byte-identical. Refs #12
1 parent 9c0f6dd commit 5c3f22f

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

src/gen-tm.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4529,6 +4529,31 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
45294529
topPatterns.push({ include: `#${key}` });
45304530
rememberLiteralKey(scope, key, tok.name);
45314531

4532+
} else if (tok.string && scope.startsWith('entity.name.tag')
4533+
&& tokenPatternPrefixBeforeTrailingLookahead(tok)
4534+
&& tokenPatternQuoteDelimAndEscape({ pattern: tokenPatternPrefixBeforeTrailingLookahead(tok)!.body })) {
4535+
// A quoted KEY (`"a\nb": v` / `'a''b': v`) — scoped entity.name.tag, NOT string.*, so it skips
4536+
// the value-string region above and would emit as a FLAT token, leaving its in-string escapes
4537+
// un-sub-scoped. Emit a begin/end region so the escapes become constant.character.escape, but
4538+
// GATE the begin on a lookahead that the close delim is followed by the key separator — the
4539+
// token's OWN body + trailing key-sep lookahead, minus the opening delim — so a quoted VALUE
4540+
// (no trailing separator) still falls through to its own string region. The delim/escape come
4541+
// from the token's body (the 3-part `delim content delim`, after the key-sep lookahead is split
4542+
// off); all derived from the token.
4543+
const { delim, escape } = tokenPatternQuoteDelimAndEscape({ pattern: tokenPatternPrefixBeforeTrailingLookahead(tok)!.body })!;
4544+
const region: TmPattern = {
4545+
name: `${scope}.${langName}`,
4546+
begin: `${escapeRegex(delim)}(?=${tokenPatternSource(tok).slice(escapeRegex(delim).length)})`,
4547+
beginCaptures: { '0': { name: `punctuation.definition.string.begin.${langName}` } },
4548+
end: escapeRegex(delim),
4549+
endCaptures: { '0': { name: `punctuation.definition.string.end.${langName}` } },
4550+
patterns: [{ match: escape, name: `constant.character.escape.${langName}` }],
4551+
};
4552+
if (escape === escapeRegex(delim) + escapeRegex(delim)) region.applyEndPatternLast = true;
4553+
repository[key] = region;
4554+
topPatterns.push({ include: `#${key}` });
4555+
rememberLiteralKey(scope, key, tok.name);
4556+
45324557
} else if (isBlock) {
45334558
const blockDelims = tokenPatternBlockDelimiters(tok);
45344559
const blockSources = tokenPatternBlockDelimiterSources(tok);
@@ -7152,7 +7177,10 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
71527177
// KEY — a scalar that is the LHS of `:`) is a NAME, more specific than any string/typed-value
71537178
// scalar it overlaps, so it must be tried first. (Markup tag names live inside begin/end
71547179
// regions, never as a top-level include, so this only ranks the YAML-style key scalar.)
7155-
if (entry?.match && scope.startsWith('entity.name.tag')) return 0.9;
7180+
// A top-level token scoped `entity.name.tag` is a NAME (an indentation grammar's mapping KEY): a
7181+
// flat `match` (plain key) OR a begin/end region (a quoted key with sub-scoped escapes). Either
7182+
// must be tried before the value scalars / value-string regions it overlaps.
7183+
if ((entry?.match || entry?.begin) && scope.startsWith('entity.name.tag')) return 0.9;
71567184
// A document marker (`---`/`...`, scoped entity.other.document) is a `lit`+lookahead token that
71577185
// OVERLAPS the plain scalar (`---` also matches PLAIN_HEAD), so it must out-rank the plain-scalar
71587186
// catch-all (8.8) — else `---` paints as string.unquoted. Its lookahead pins it to a real marker.

yaml.tmLanguage.json

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,46 @@
102102
},
103103
"dquotekey": {
104104
"name": "entity.name.tag.yaml",
105-
"match": "\"(?:\\\\(?:[0abtnvfre\"/\\\\N_LP \\t]|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})|[^\"\\\\\\r\\n])*\"(?=[ \\t]*:)"
105+
"begin": "\"(?=(?:\\\\(?:[0abtnvfre\"/\\\\N_LP \\t]|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})|[^\"\\\\\\r\\n])*\"(?=[ \\t]*:))",
106+
"beginCaptures": {
107+
"0": {
108+
"name": "punctuation.definition.string.begin.yaml"
109+
}
110+
},
111+
"end": "\"",
112+
"endCaptures": {
113+
"0": {
114+
"name": "punctuation.definition.string.end.yaml"
115+
}
116+
},
117+
"patterns": [
118+
{
119+
"match": "\\\\(?:[0abtnvfre\"/\\\\N_LP \\t]|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})",
120+
"name": "constant.character.escape.yaml"
121+
}
122+
]
106123
},
107124
"squotekey": {
108125
"name": "entity.name.tag.yaml",
109-
"match": "'(?:''|[^'\\r\\n])*'(?=[ \\t]*:)"
126+
"begin": "'(?=(?:''|[^'\\r\\n])*'(?=[ \\t]*:))",
127+
"beginCaptures": {
128+
"0": {
129+
"name": "punctuation.definition.string.begin.yaml"
130+
}
131+
},
132+
"end": "'",
133+
"endCaptures": {
134+
"0": {
135+
"name": "punctuation.definition.string.end.yaml"
136+
}
137+
},
138+
"patterns": [
139+
{
140+
"match": "''",
141+
"name": "constant.character.escape.yaml"
142+
}
143+
],
144+
"applyEndPatternLast": true
110145
},
111146
"dquote": {
112147
"name": "string.quoted.double.yaml",

0 commit comments

Comments
 (0)