From 84b20a40168e469ebdbfc4e3cc23253d3955134a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 24 Jul 2020 15:13:35 -0700 Subject: [PATCH] fix highlighting hang with empty begin end rules --- babi/highlight.py | 23 ++++++++++++++++++----- tests/highlight_test.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/babi/highlight.py b/babi/highlight.py index e701e03..bcb937d 100644 --- a/babi/highlight.py +++ b/babi/highlight.py @@ -273,6 +273,7 @@ class CompiledRegsetRule(CompiledRule, Protocol): class Entry(NamedTuple): scope: Tuple[str, ...] rule: CompiledRule + start: Tuple[str, int] reg: _Reg = ERR_REG boundary: bool = False @@ -284,7 +285,7 @@ def _inner_capture_parse( scope: Scope, rule: CompiledRule, ) -> Regions: - state = State.root(Entry(scope + rule.name, rule)) + state = State.root(Entry(scope + rule.name, rule, (s, 0))) _, regions = highlight_line(compiler, state, s, first_line=False) return tuple( r._replace(start=r.start + start, end=r.end + start) for r in regions @@ -440,7 +441,8 @@ class EndRule(NamedTuple): boundary = match.end() == len(match.string) reg = make_reg(expand_escaped(match, self.end)) - state = state.push(Entry(next_scope, self, reg, boundary)) + start = (match.string, match.start()) + state = state.push(Entry(next_scope, self, start, reg, boundary)) regions = _captures(compiler, scope, match, self.begin_captures) return state, True, regions @@ -455,7 +457,16 @@ class EndRule(NamedTuple): if m.start() > pos: ret.append(Region(pos, m.start(), state.cur.scope)) ret.extend(_captures(compiler, state.cur.scope, m, self.end_captures)) - return state.pop(), m.end(), False, tuple(ret) + # this is probably a bug in the grammar, but it pushed and popped at + # the same position. + # we'll advance the highlighter by one position to get past the loop + # this appears to be what vs code does as well + if state.entries[-1].start == (m.string, m.end()): + ret.append(Region(m.end(), m.end() + 1, state.cur.scope)) + end = m.end() + 1 + else: + end = m.end() + return state.pop(), end, False, tuple(ret) def search( self, @@ -501,7 +512,9 @@ class WhileRule(NamedTuple): boundary = match.end() == len(match.string) reg = make_reg(expand_escaped(match, self.while_)) - state = state.push_while(self, Entry(next_scope, self, reg, boundary)) + start = (match.string, match.start()) + entry = Entry(next_scope, self, start, reg, boundary) + state = state.push_while(self, entry) regions = _captures(compiler, scope, match, self.begin_captures) return state, True, regions @@ -541,7 +554,7 @@ class Compiler: self._rule_to_grammar: Dict[_Rule, Grammar] = {} self._c_rules: Dict[_Rule, CompiledRule] = {} root = self._compile_root(grammar) - self.root_state = State.root(Entry(root.name, root)) + self.root_state = State.root(Entry(root.name, root, ('', 0))) def _visit_rule(self, grammar: Grammar, rule: _Rule) -> _Rule: self._rule_to_grammar[rule] = grammar diff --git a/tests/highlight_test.py b/tests/highlight_test.py index c2c3d84..8c825ef 100644 --- a/tests/highlight_test.py +++ b/tests/highlight_test.py @@ -637,3 +637,25 @@ def test_backslash_z(compiler_state): assert regions2 == ( Region(0, 6, ('test', 'comment')), ) + + +def test_buggy_begin_end_grammar(compiler_state): + # before this would result in an infinite loop of start / end + compiler, state = compiler_state({ + 'scopeName': 'test', + 'patterns': [ + { + 'begin': '(?=