Skip to content

Commit 17cd269

Browse files
committed
Emit a real begin/end region for YAML block scalars
The BlockScalar token is produced by the lexer's indentation state machine (its pattern is a never() placeholder), so gen-tm's flat token loop emitted a dead `(?!)` match and the verbatim body fell through to the comment / plain rules: `#`-lines became comments, the body ended early, tabs and punctuation were mis-scoped. Replace it with the proven textmate/yaml.tmbundle begin/end region. A top-level `while: \G` region cannot sustain itself in a flat grammar — vscode-textmate sets the while-anchor to -1 unless a parent begin captured end-of-line, which is why the maintained RedCMD grammar only survives nested many regions deep. So the body is a begin/end region whose begin captures the trailing newline (without it the `(?!\G)` end-arm fires at the start of line 2) and whose inner rule auto-detects the first content line's indent and releases at the first shallower non-blank line, so nested siblings are not swallowed. YAML scope-gap holds at 99.7% vs official 99.5%; the comment role is now genuinely correct (104 -> 106) rather than aliased through the plain-scalar fallback. Change is gated to YAML; the other six grammars are byte-identical. Addresses the block-scalar items of #12 (body detected as comment, body ended prematurely, incorrect punctuation inside the body). Refs #12
1 parent 4a26317 commit 17cd269

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

src/gen-tm.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4581,6 +4581,60 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
45814581
}
45824582
}
45834583

4584+
// ── 2a. Block scalar (`|` / `>`) — a real begin/end region replacing the dead `(?!)` token ──
4585+
// The BlockScalar token is emitted by the lexer's indentation state machine (its pattern is a
4586+
// `never()` placeholder), so the flat token loop above produced a dead `(?!)` match for it and
4587+
// the verbatim body was never scoped — it fell through to the comment / plain rules (`# x` inside
4588+
// a body became a comment, the scalar "ended early", tabs mis-scoped). In TextMate the construct
4589+
// is a REGION: the `|`/`>` introducer (+ optional indentation / chomping indicators) opens it and
4590+
// the more-indented lines below are opaque string content, NEVER re-scanned. Everything is DERIVED
4591+
// from `indent.blockScalar`: the introducer chars and the header signature, which mirrors the
4592+
// lexer's `blockScalarSig` so the highlighter opens a region at EXACTLY the positions the parser
4593+
// tokenises a BlockScalar — and never on a `|`/`>` that is plain-scalar content (`a: b | c`),
4594+
// since the lookahead requires the rest of the header line to be only indicators + an optional
4595+
// ` #comment`.
4596+
const blockScalar = grammar.indent?.blockScalar;
4597+
if (blockScalar) {
4598+
const bsTok = grammar.tokens.find(t => t.name === blockScalar.token);
4599+
const bsKey = blockScalar.token.toLowerCase();
4600+
const bsScope = bsTok?.scope ?? 'string.unquoted.block';
4601+
const introClass = `[${blockScalar.introducers.map(escapeForCharClass).join('')}]`;
4602+
// introducer + indentation/chomping indicators (a digit and a `+`/`-`, in either order, or a
4603+
// lone `+`/`-`), then a lookahead requiring the rest of the header line to be blank or a comment.
4604+
const indicators = '(?:[1-9][-+]?|[-+][1-9]?|[-+])?';
4605+
const commentIncs = commentIncludeKeys.map(k => ({ include: `#${k}` }));
4606+
// This is the proven textmate/yaml.tmbundle structure (a `begin`/`end` region, NOT `begin`/
4607+
// `while`): a flat grammar has no enclosing line-spanning rule, and a top-level `while: \G`
4608+
// region cannot sustain itself across lines — vscode-textmate sets the while-anchor to -1
4609+
// unless a PARENT begin captured the end-of-line, so the chain collapses after the header line
4610+
// (the maintained RedCMD grammar's `while: \G` only survives because it is nested many regions
4611+
// deep). Two load-bearing details:
4612+
// • the begin's trailing `(.*\n?)` group CAPTURES the newline. Without it the `(?!\G)` arm of
4613+
// the `end` (below) fires at the very start of line 2 and the body is never scoped.
4614+
// • `end: ^(?=\S)|(?!\G)` — the body ends at a line that dedents to a non-space at column 0
4615+
// (a sibling key, a new node, a `---`/`...` marker), and `(?!\G)` guards the contiguity the
4616+
// captured newline establishes.
4617+
// The inner rule auto-detects the body indentation (§8.1.1): `^([ \t]+)(?! )` captures the
4618+
// first content line's full indent as `\1`, and `end: ^(?!\1|[ \t]*$)` holds the body for every
4619+
// line that re-matches `\1` (or is blank) and releases at the first shallower non-blank line —
4620+
// so a deeper-nested scalar's siblings (`- a: |` … ` b: |`) are NOT swallowed.
4621+
repository[bsKey] = {
4622+
begin: `(${introClass}${indicators})(?=[\\t ]*(?:#|$))(.*\\n?)`,
4623+
beginCaptures: {
4624+
'1': { name: `${bsScope}.${langName}` },
4625+
'2': { patterns: commentIncs },
4626+
},
4627+
end: '^(?=\\S)|(?!\\G)',
4628+
patterns: [
4629+
{
4630+
begin: '^([ \\t]+)(?! )',
4631+
end: '^(?!\\1|[ \\t]*$)',
4632+
contentName: `${bsScope}.${langName}`,
4633+
},
4634+
],
4635+
};
4636+
}
4637+
45844638
// ── 2b. Explicit mapping key (`? key`) ──
45854639
// An indentation grammar may flag a mapping key by a PRECEDING indicator instead of a
45864640
// trailing separator (YAML `? key`). The key scalar then has no `:` and the flat token loop

yaml.tmLanguage.json

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@
4545
{
4646
"include": "#boolnull"
4747
},
48-
{
49-
"include": "#blockscalar"
50-
},
5148
{
5249
"include": "#plain"
5350
},
@@ -74,6 +71,9 @@
7471
},
7572
{
7673
"include": "#anchor"
74+
},
75+
{
76+
"include": "#blockscalar"
7777
}
7878
],
7979
"repository": {
@@ -161,8 +161,27 @@
161161
"match": "!(?:<[^>]*>|[^\\t\\n\\f\\r \\[\\]{},]*)"
162162
},
163163
"blockscalar": {
164-
"name": "string.unquoted.block.yaml",
165-
"match": "(?!)"
164+
"begin": "([|>](?:[1-9][-+]?|[-+][1-9]?|[-+])?)(?=[\\t ]*(?:#|$))(.*\\n?)",
165+
"beginCaptures": {
166+
"1": {
167+
"name": "string.unquoted.block.yaml"
168+
},
169+
"2": {
170+
"patterns": [
171+
{
172+
"include": "#comment"
173+
}
174+
]
175+
}
176+
},
177+
"end": "^(?=\\S)|(?!\\G)",
178+
"patterns": [
179+
{
180+
"begin": "^([ \\t]+)(?! )",
181+
"end": "^(?!\\1|[ \\t]*$)",
182+
"contentName": "string.unquoted.block.yaml"
183+
}
184+
]
166185
},
167186
"key": {
168187
"name": "entity.name.tag.yaml",

0 commit comments

Comments
 (0)