Skip to content

Commit f2f0066

Browse files
committed
Highlight YAML document markers (--- / ...) as document structure
`---` and `...` were painted as plain scalars (string.unquoted): the DocStart/DocEnd tokens existed but the plain-scalar catch-all (rank 8.8) out-ranked them, and they carried a punctuation scope rather than the document scope. Now they: - require trailing whitespace/EOL so `---foo` / `...bar` stay plain scalars, - scope as entity.other.document.begin / .end (the maintained-grammar convention), - out-rank the plain scalar in scopeOrder so the marker wins the `---`/`...` match. Also fixes the identToken fallback that mis-tagged `---` as the bare identifier (it classified as variable.other and got `.readwrite` appended to its scope): the fallback now requires the token's RESOLVED scope to be variable.other, so an explicitly scoped token can never be mistaken for the identifier. And the Monarch / tree-sitter generators learn the new entity.other.document scope (delimiter / @punctuation.delimiter) so all three targets keep painting the markers. YAML scope-gap doc.marker 0/149 -> 149/149; parser unchanged (the lookahead only narrows where the marker matches). Other six grammars byte-identical. Refs #12
1 parent 01b20e7 commit f2f0066

6 files changed

Lines changed: 35 additions & 16 deletions

File tree

src/gen-monarch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ function scopeToMonarch(scope: string): string {
125125
if (scope.startsWith('storage')) return 'keyword';
126126
if (scope.startsWith('keyword.operator')) return 'operator';
127127
if (scope.startsWith('keyword')) return 'keyword';
128+
if (scope.startsWith('entity.other.document')) return 'delimiter'; // YAML --- / ... markers
128129
if (scope.startsWith('entity.name.function.decorator')) return 'annotation';
129130
if (scope.startsWith('entity.name.function')) return 'identifier';
130131
if (scope.startsWith('entity.name.type')) return 'type.identifier';

src/gen-tm.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4262,8 +4262,15 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
42624262
// punctuation tokens (e.g. JSX's `/>` / `</`, which also classify as
42634263
// `variable.other` and would otherwise be mistaken for the identifier pattern,
42644264
// corrupting every ident-derived pattern).
4265+
// The fallback (a token whose PATTERN classifies as variable.other) also requires the token's
4266+
// RESOLVED scope to be variable.other — else a grammar with no identifier token (YAML) picks the
4267+
// first punctuation token whose pattern is unrecognised (e.g. `---`, classified variable.other),
4268+
// mis-tagging it as the identifier and appending `.readwrite` to its real scope. An explicitly
4269+
// scoped token (entity.other.document, variable.other.alias, …) is excluded; a true bare
4270+
// identifier (JS `Ident`, no explicit scope) still qualifies.
42654271
const identToken = grammar.tokens.find(t => t.identifier)
4266-
?? grammar.tokens.find(t => classifyToken(t, { explicitScope: false }).scope === 'variable.other');
4272+
?? grammar.tokens.find(t => classifyToken(t, { explicitScope: false }).scope === 'variable.other'
4273+
&& classifyToken(t).scope === 'variable.other');
42674274
// Widen the identifier pattern so non-ASCII names (`Ω`, Cyrillic `А`) are scoped,
42684275
// matching the parser lexer's Unicode fallback. This widened form is used only in
42694276
// TextMate (Oniguruma) output, never by the lexer.
@@ -7137,6 +7144,10 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
71377144
// scalar it overlaps, so it must be tried first. (Markup tag names live inside begin/end
71387145
// regions, never as a top-level include, so this only ranks the YAML-style key scalar.)
71397146
if (entry?.match && scope.startsWith('entity.name.tag')) return 0.9;
7147+
// A document marker (`---`/`...`, scoped entity.other.document) is a `lit`+lookahead token that
7148+
// OVERLAPS the plain scalar (`---` also matches PLAIN_HEAD), so it must out-rank the plain-scalar
7149+
// catch-all (8.8) — else `---` paints as string.unquoted. Its lookahead pins it to a real marker.
7150+
if (entry?.match && scope.startsWith('entity.other.document')) return 0.95;
71407151
// An UNQUOTED plain-scalar catch-all (`string.unquoted`) is the least-specific scalar shape
71417152
// (it has no opening delimiter and matches almost any bare run), so — unlike a quoted/regex
71427153
// string — it must rank AFTER the typed-literal scalars (constant.numeric / constant.language)

src/gen-treesitter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ function scopeToCapture(scope: string): string | null {
902902
['entity.name.function', '@function'],
903903
['entity.name.type', '@type'],
904904
['entity.name', '@constructor'],
905+
['entity.other.document', '@punctuation.delimiter'], // YAML --- / ... markers
905906
['entity.other.property', '@property'],
906907
['support.type.primitive', '@type.builtin'],
907908
['support.type', '@type.builtin'],

yaml.monarch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,14 +711,14 @@
711711
}
712712
],
713713
[
714-
"---",
714+
"---(?=[\\t ]|\\r|\\n|$)",
715715
{
716716
"token": "delimiter",
717717
"switchTo": "@value"
718718
}
719719
],
720720
[
721-
"\\.\\.\\.",
721+
"\\.\\.\\.(?=[\\t ]|\\r|\\n|$)",
722722
{
723723
"token": "delimiter",
724724
"switchTo": "@value"

yaml.tmLanguage.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
{
2828
"include": "#key"
2929
},
30+
{
31+
"include": "#docstart"
32+
},
33+
{
34+
"include": "#docend"
35+
},
3036
{
3137
"include": "#dquote"
3238
},
@@ -51,12 +57,6 @@
5157
{
5258
"include": "#plain"
5359
},
54-
{
55-
"include": "#docstart"
56-
},
57-
{
58-
"include": "#docend"
59-
},
6060
{
6161
"include": "#punctuation"
6262
},
@@ -81,12 +81,12 @@
8181
],
8282
"repository": {
8383
"docstart": {
84-
"name": "punctuation.definition.directives-end.readwrite.yaml",
85-
"match": "---"
84+
"name": "entity.other.document.begin.yaml",
85+
"match": "---(?=[\\t ]|\\r|\\n|$)"
8686
},
8787
"docend": {
88-
"name": "punctuation.definition.document-end.yaml",
89-
"match": "\\.\\.\\."
88+
"name": "entity.other.document.end.yaml",
89+
"match": "\\.\\.\\.(?=[\\t ]|\\r|\\n|$)"
9090
},
9191
"yamldirective": {
9292
"name": "keyword.other.directive.yaml",
@@ -203,7 +203,7 @@
203203
"match": "(?:[^\\t\\n\\f\\r \\-?:,\\[\\]{}#&*!|>'\"%@`]|[\\-?:](?=[^\\t\\n\\f\\r ,\\[\\]{}]))(?:[^:#\\n]|:(?=[^\\t\\n\\f\\r ])|#(?<=[^\\t\\n\\f\\r ]#))*"
204204
},
205205
"indent": {
206-
"name": "variable.other.yaml",
206+
"name": "variable.other.readwrite.yaml",
207207
"match": "(?!)"
208208
},
209209
"dedent": {

yaml.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@ const hexDigit = oneOf(digit, range('A', 'F'), range('a', 'f'));
3030
const whitespace = oneOf('\t', '\n', '\f', '\r', ' ');
3131
const nonWhitespace = noneOf(whitespace);
3232
const hashAfterNonSpace = seq('#', precededBy(seq(nonWhitespace, '#')));
33-
const DocStart = token(lit('---'), { scope: 'punctuation.definition.directives-end' });
34-
const DocEnd = token(lit('...'), { scope: 'punctuation.definition.document-end' });
33+
// Document markers: `---` (directives end / document begin) and `...` (document end). Both must be
34+
// followed by whitespace or EOL — `---foo` / `...bar` are plain scalars, not markers — so the
35+
// lookahead keeps the marker from stealing a plain scalar's leading dashes/dots. Scoped
36+
// `entity.other.document.*` (the maintained-grammar convention) so the highlighter paints them as
37+
// document structure, not as a string.
38+
const docMarkerEnd = followedBy(alt(oneOf('\t', ' '), '\r', '\n', end()));
39+
const DocStart = token(seq('---', docMarkerEnd), { scope: 'entity.other.document.begin' });
40+
const DocEnd = token(seq('...', docMarkerEnd), { scope: 'entity.other.document.end' });
3541
const Comment = token(seq('#', star(noneOf('\n'))), { skip: true, scope: 'comment.line.number-sign' });
3642
// Double-quoted scalar body. The escape set is FIXED (YAML 1.2 §5.7): a `\` may only precede one
3743
// of `0 a b t n v f r e " / \ N _ L P`, a literal space/tab, a `x`+2 / `u`+4 / `U`+8 hex escape, or

0 commit comments

Comments
 (0)