Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
c183165
Restore cleared scopes before Pop on pop: N + set: X
stefanobaghino Apr 20, 2026
d1200b3
Clip captures: N span to rule's match bounds
stefanobaghino Apr 21, 2026
cd23cc9
Suppress cur meta_scope on pop: N + embed: match text
stefanobaghino Apr 21, 2026
e9c022a
Drain orphan scope atoms on outer escape fire
stefanobaghino Apr 22, 2026
b185a2c
Order apply_prototype external prototype before target main
stefanobaghino Apr 22, 2026
69cd4b3
Cover apply_prototype external-prototype precedence
stefanobaghino Apr 23, 2026
347ea76
Drop HAML-Rails from syntest baselines
stefanobaghino Apr 23, 2026
17e46c4
Wrap past-EOL syntest assertions to next-line scopes
stefanobaghino Apr 23, 2026
b1b5438
Dedup flushed_ops across multi-fail parse_line calls
stefanobaghino Apr 23, 2026
78da6c4
Anchor replay-born branches to their replay line
stefanobaghino Apr 23, 2026
715a68e
Inherit outer prefix in replay-born branch_points
stefanobaghino Apr 25, 2026
7706736
Refresh post-replay shadow and stack_before snapshots
stefanobaghino Apr 25, 2026
d31dbe3
Subtract bp.pop_count in branch_point retain predicate
stefanobaghino Apr 26, 2026
d4a7e05
Route handle_fail cross-line re-emit through push_meta_ops
stefanobaghino Apr 26, 2026
82703dc
Save flushed_ops before cross-line replay loop and prefer inner corre…
stefanobaghino Apr 26, 2026
f3e497a
Fall through to parent on branch_point exhaustion
stefanobaghino Apr 27, 2026
ed949e4
Restore cleared scopes before deeper pops in pop:N
stefanobaghino Apr 27, 2026
0a2139a
Skip inner replay correction when nested deeper than outer BP
stefanobaghino Apr 27, 2026
d36dc01
Treat pop:N + push:X as Set with pop_count=N
stefanobaghino Apr 27, 2026
b31b727
Skip cur mcs Pop in Set when wrapper has embed_scope_replaces
stefanobaghino Apr 27, 2026
4bd9272
Defer target Clear in single-context Set when cur has meta_scope
stefanobaghino Apr 27, 2026
96ae959
Drop extra parent mcs on trigger of multi-set + target clear+ms
stefanobaghino Apr 27, 2026
aca11f5
Restore deeper frame clear_scopes on pop:N + set:
stefanobaghino Apr 27, 2026
61a796e
Drop python_strings.py from syntest baselines
stefanobaghino Apr 27, 2026
3b44840
Skip embed_scope_replaces for fragment embeds
stefanobaghino Apr 27, 2026
d4b6a5b
Drop python.py from syntest baselines
stefanobaghino Apr 27, 2026
72f5ac0
Prefer own over inherited in multi-extends parent merge
stefanobaghino Apr 28, 2026
172a697
Skip non-pure assertion lines in the syntest harness
stefanobaghino Apr 28, 2026
230faae
Bypass search cache on truncated searches at embed-escape boundary
stefanobaghino Apr 28, 2026
02ac40d
Apply non-topmost set target's clear_scopes at trigger position
stefanobaghino Apr 28, 2026
c432a0c
Defer LRD blank-line pop to next-line baseline
stefanobaghino Apr 28, 2026
6ca4ebd
Treat shebang header tail as testtoken_end in syntest harness
stefanobaghino Apr 28, 2026
b3d9841
Stop propagating no_prototype mark across include boundaries past a Push
stefanobaghino Apr 28, 2026
6376028
Collapse empty atoms in scope and selector strings
stefanobaghino Apr 28, 2026
bf88dd9
Bump testdata/Packages to v4202
stefanobaghino Apr 29, 2026
82121e6
Mirror same-line rewind+skip on cross-line BP exhaustion
stefanobaghino Apr 29, 2026
ae78419
Bound prefer_inner_replay_corrections by depth_diff to substitute sib…
stefanobaghino Apr 29, 2026
610412f
Pop deeper popped meta_scope before pop:N+set: trigger token
stefanobaghino Apr 29, 2026
9bfe8a9
Collapse rule scope atoms duplicating popped meta_scope on match
stefanobaghino Apr 29, 2026
be11842
Refresh syntest baselines after qualified-annotation dedup
stefanobaghino Apr 29, 2026
c25204f
Allow immediately-pop tail-extension from deeper inner BP correction
stefanobaghino Apr 29, 2026
345072e
Refresh syntest baselines after annotation EOL-pop fix
stefanobaghino Apr 29, 2026
c849e38
Refresh syntest baselines after annotation EOL-pop fix
stefanobaghino Apr 29, 2026
3918258
Distinguish pop:N+push: from pop:N+set: at MatchOperation IR
stefanobaghino Apr 30, 2026
51e62e6
Route pop:N+branch: through Push lookahead and drop scope-dedup hack
stefanobaghino Apr 30, 2026
6ce6367
Route pop:N+embed: through Push lookahead
stefanobaghino Apr 30, 2026
1a83739
Un-ignore embed wrapper-mcs test; CI checks out the submodule
stefanobaghino Jul 11, 2026
79ae5fd
Merge branch '631-fragment-embed-skip-replaces' into 631-multi-extend…
stefanobaghino Jul 11, 2026
9b8b713
Merge branch '631-markdown-fence-embed-scope-token-drop' into 631-bas…
stefanobaghino Jul 11, 2026
f8f0501
Merge branch '631-bash-tilde-meta-scope-leak' into 631-php-multi-set-…
stefanobaghino Jul 11, 2026
11b84a2
Merge branch '631-php-multi-set-target-clear-extra-mcs-drop' into 631…
stefanobaghino Jul 11, 2026
1ce1d05
Merge branch '631-pop-n-set-deeper-clear-scopes-restore' into 631-fra…
stefanobaghino Jul 11, 2026
2048676
Un-ignore zsh-for-markdown shebang test; CI has the submodule
stefanobaghino Jul 11, 2026
ff67ac4
Merge branch '631-zsh-range-clear-parent-string-scope' into 631-markd…
stefanobaghino Jul 11, 2026
d8bd7d2
Merge branch '631-multi-extends-own-beats-inherited' into 631-syntest…
stefanobaghino Jul 11, 2026
f2a0dea
Merge branch '631-syntest-non-pure-assertion-gating' into 631-clip-lo…
stefanobaghino Jul 11, 2026
b872bfa
Merge branch '631-clip-lookahead-at-embed-escape' into 631-zsh-range-…
stefanobaghino Jul 11, 2026
39f3a17
Un-ignore LRD blank-line test; CI has the submodule
stefanobaghino Jul 11, 2026
d91cc52
Merge branch '631-syntest-haskell-meta-function-identifier' into 631-…
stefanobaghino Jul 11, 2026
78498a6
Merge branch '631-markdown-lrd-blank-line' into 631-syntest-shebang-t…
stefanobaghino Jul 11, 2026
8f0918c
Merge branch '631-syntest-shebang-tail-as-end-token' into 631-syntest…
stefanobaghino Jul 11, 2026
2f3fee8
Track v4202 embed_scope atom rename in embed wrapper-mcs test
stefanobaghino Jul 11, 2026
2aadad9
Merge branch '631-java-qualified-annotation-dedup' into 631-java-anno…
stefanobaghino Jul 11, 2026
bbccf81
Merge branch '631-syntest-bump-packages-submodule' into 631-java-cros…
stefanobaghino Jul 11, 2026
81fd88e
Merge branch '631-java-cross-line-bp-fall-through' into 631-java-cros…
stefanobaghino Jul 11, 2026
4a2712c
Merge branch '631-java-cross-line-allexhaust-pops' into 631-java-pop-…
stefanobaghino Jul 11, 2026
a1d6b9d
Merge branch '631-java-pop-n-set-deeper-trigger' into 631-java-qualif…
stefanobaghino Jul 11, 2026
2ad2cbe
Un-ignore annotation EOL-pop replay test; CI has the submodule
stefanobaghino Jul 11, 2026
7674762
Merge branch '631-java-annotation-eol-pop' into 631-pop-n-non-set-loo…
stefanobaghino Jul 11, 2026
02210d7
Un-ignore the 630-era submodule tests too; CI has the submodule
stefanobaghino Jul 11, 2026
6fa70b9
Merge branch '631-markdown-fence-embed-scope-token-drop' into 631-bas…
stefanobaghino Jul 11, 2026
d2efa94
Merge branch '631-bash-tilde-meta-scope-leak' into 631-php-multi-set-…
stefanobaghino Jul 11, 2026
eea01f4
Merge branch '631-php-multi-set-target-clear-extra-mcs-drop' into 631…
stefanobaghino Jul 11, 2026
5944f95
Merge branch '631-pop-n-set-deeper-clear-scopes-restore' into 631-fra…
stefanobaghino Jul 11, 2026
e298261
Merge branch '631-fragment-embed-skip-replaces' into 631-multi-extend…
stefanobaghino Jul 11, 2026
e96f99f
Merge branch '631-multi-extends-own-beats-inherited' into 631-syntest…
stefanobaghino Jul 11, 2026
f954cb1
Merge branch '631-syntest-non-pure-assertion-gating' into 631-clip-lo…
stefanobaghino Jul 11, 2026
bad01ab
Merge branch '631-clip-lookahead-at-embed-escape' into 631-zsh-range-…
stefanobaghino Jul 11, 2026
4426958
Merge branch '631-zsh-range-clear-parent-string-scope' into 631-markd…
stefanobaghino Jul 11, 2026
e72786d
Merge branch '631-markdown-lrd-blank-line' into 631-syntest-shebang-t…
stefanobaghino Jul 11, 2026
c2bcd9a
Merge branch '631-syntest-shebang-tail-as-end-token' into 631-syntest…
stefanobaghino Jul 11, 2026
53bf87a
Merge branch '631-syntest-haskell-meta-function-identifier' into 631-…
stefanobaghino Jul 11, 2026
04f88dc
Merge branch '631-syntest-bump-packages-submodule' into 631-java-cros…
stefanobaghino Jul 11, 2026
5f28c20
Merge branch '631-java-cross-line-bp-fall-through' into 631-java-cros…
stefanobaghino Jul 11, 2026
ad9cee0
Merge branch '631-java-cross-line-allexhaust-pops' into 631-java-pop-…
stefanobaghino Jul 11, 2026
35cce2c
Merge branch '631-java-pop-n-set-deeper-trigger' into 631-java-qualif…
stefanobaghino Jul 11, 2026
972dced
Merge branch '631-java-qualified-annotation-dedup' into 631-java-anno…
stefanobaghino Jul 11, 2026
c9d30cf
Merge branch '631-java-annotation-eol-pop' into 631-pop-n-non-set-loo…
stefanobaghino Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[submodule "testdata/Packages"]
path = testdata/Packages
url = https://github.com/sublimehq/Packages
branch = v4202
[submodule "testdata/InspiredGitHub.tmtheme"]
path = testdata/InspiredGitHub.tmtheme
url = https://github.com/sethlopezme/InspiredGitHub.tmtheme.git
Expand Down
Binary file modified assets/default_newlines.packdump
Binary file not shown.
Binary file modified assets/default_nonewlines.packdump
Binary file not shown.
430 changes: 339 additions & 91 deletions examples/syntest.rs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/parsing/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,9 @@ mod tests {
let r = RawMetadataEntry::load(comments_file);
assert!(r.is_ok());

let indent_file: &str = "testdata/Packages/Go/Indentation Rules.tmPreferences";
// v4202 still has the upstream "Indention" typo; renamed to
// "Indentation" by `1c86d7f9` post-v4202 (#4519).
let indent_file: &str = "testdata/Packages/Go/Indention Rules.tmPreferences";
assert!(Path::new(indent_file).exists());

let r = RawMetadataEntry::load(indent_file).unwrap();
Expand Down
3,908 changes: 3,691 additions & 217 deletions src/parsing/parser.rs

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions src/parsing/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,27 @@ mod tests {
let back_to_str = serde_json::to_string(&pattern).unwrap();
assert_eq!(back_to_str, "\"just a string\"");
}

// The parser uses `end < text.len()` to clip a search at an embed-escape
// boundary. Negative lookaheads inside the embedded context's rules must
// treat the boundary as effective end-of-input — otherwise patterns like
// `done(?!cmd_char)` fail when `done` is immediately followed by the
// closing escape glyph (e.g. `` `for...done` `` in shell).
#[test]
fn search_clips_lookahead_at_end() {
let regex = Regex::new(String::from(r"done(?!X)"));
let mut region = Region::new();
let matched = regex.search("doneX", 0, 4, Some(&mut region), true);
assert!(matched, "lookahead must not see byte at index 4");
assert_eq!(region.pos(0), Some((0, 4)));
}

// Sanity check: when not clipped, the same lookahead correctly fails.
#[test]
fn search_lookahead_fails_when_unclipped() {
let regex = Regex::new(String::from(r"done(?!X)"));
let mut region = Region::new();
let matched = regex.search("doneX", 0, 5, Some(&mut region), true);
assert!(!matched, "lookahead must see the X when end == text.len()");
}
}
43 changes: 43 additions & 0 deletions src/parsing/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,19 @@ impl ScopeRepository {
if s.is_empty() {
return Ok(Scope { a: 0, b: 0 });
}
// Filter empty atoms so consecutive dots collapse — `variable.other..haskell`
// matches the same scope as `variable.other.haskell`. ST's selector engine
// treats them as equivalent (both score 48 against
// `source.haskell variable.other.haskell` via `sublime.score_selector`);
// syntect's previous implementation packed `""` as its own atom index,
// producing a 4-atom scope that didn't prefix-match the 3-atom target.
// Symmetric: applies to scope construction (`scope: foo..bar` typo) and
// to selector parsing (`syntax_test_haskell.hs:2348` line `:: a -> Bool`
// has `-- ^ variable.other..haskell` and ST passes it).
let parts: Vec<usize> = s
.trim_end_matches('.')
.split('.')
.filter(|a| !a.is_empty())
.map(|a| self.atom_to_index(a))
.collect();
// The internal representation supports at most 8 atoms (packed into
Expand Down Expand Up @@ -631,6 +641,20 @@ mod tests {
repo.build("comment.line.").unwrap(),
repo.build("comment.line").unwrap()
);
// Empty atoms (consecutive dots) collapse, mirroring ST's selector
// engine where `score_selector('variable.other..haskell',
// 'source.haskell variable.other.haskell')` returns 48 — the same as
// for the single-dot form. Without the collapse, syntect packed `""`
// as its own atom, producing a 4-atom scope that didn't prefix-match
// the 3-atom target. Surfaced via `syntax_test_haskell.hs:2348` line
// `:: a -> Bool` whose `-- ^ variable.other..haskell`
// assertion ST passes against the same scope syntect produces.
assert_eq!(
repo.build("variable.other..haskell").unwrap(),
repo.build("variable.other.haskell").unwrap()
);
assert_eq!(repo.build("a..b").unwrap(), repo.build("a.b").unwrap());
assert_eq!(repo.build("a...b").unwrap(), repo.build("a.b").unwrap());
}

#[test]
Expand Down Expand Up @@ -725,5 +749,24 @@ mod tests {
.does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
None
);
// Selector with consecutive dots collapses to the equivalent
// single-dot form before matching: ST's `score_selector` returns the
// same MatchPower for both, and so should syntect.
assert_eq!(
ScopeStack::from_str("variable.other..haskell")
.unwrap()
.does_match(
ScopeStack::from_str("source.haskell variable.other.haskell")
.unwrap()
.as_slice()
),
ScopeStack::from_str("variable.other.haskell")
.unwrap()
.does_match(
ScopeStack::from_str("source.haskell variable.other.haskell")
.unwrap()
.as_slice()
)
);
}
}
23 changes: 18 additions & 5 deletions src/parsing/syntax_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,19 @@ pub enum ContextReference {

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum MatchOperation {
Push(Vec<ContextReference>),
/// Pushes `ctx_refs`, optionally popping `pop_count` contexts first.
/// A plain `push:` is `pop_count == 0`; `pop: N + push:` is
/// `pop_count == N`. Per ST, `pop:N + push:` is **lookahead**: the
/// trigger token does not inherit the popped frames' meta_scope.
Push {
ctx_refs: Vec<ContextReference>,
#[serde(default)]
pop_count: usize,
},
/// Pops `pop_count` contexts off the stack, then pushes `ctx_refs`.
/// A plain `set:` is `pop_count == 1`; `pop: N + set:` is `pop_count == N`.
/// Per ST, `pop:N + set:` is **stacking**: the trigger token receives
/// both the popped frames' meta_scope and the pushed meta_scope.
Set {
ctx_refs: Vec<ContextReference>,
#[serde(default = "one")]
Expand Down Expand Up @@ -274,17 +284,20 @@ impl<'a> Iterator for MatchIter<'a> {
_ => return self.next(),
};
let ctx_ptr = self.syntax_set.get_context(context_id).unwrap();
// Also include the external syntax's prototype if the context allows it
// Push target first, then external prototype on top so
// its patterns are iterated first: `MatchIter::next`
// reads the stack top, and ST's `apply_prototype` runs
// the prototype ahead of the included context — mirror
// of `ParseState::find_best_match`'s prototype chaining.
self.ctx_stack.push(ctx_ptr);
self.index_stack.push(0);
if ctx_ptr.meta_include_prototype.unwrap_or(true) {
if let Some(ref proto_id) = ctx_ptr.prototype {
let proto_ctx = self.syntax_set.get_context(proto_id).unwrap();
// Push prototype first (it will be iterated first)
self.ctx_stack.push(proto_ctx);
self.index_stack.push(0);
}
}
self.ctx_stack.push(ctx_ptr);
self.index_stack.push(0);
}
}
} else {
Expand Down
Loading