Fix incorrect caching in syntax highlighter
the concrete broken case was for markdown with yaml ```md --- x: y --- (this one shouldn't be yaml highlighted) --- x: y --- ```
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import curses
|
||||
from typing import Dict
|
||||
import functools
|
||||
import math
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi.color_manager import ColorManager
|
||||
@@ -36,8 +39,10 @@ class FileSyntax:
|
||||
self.regions: List[HLs] = []
|
||||
self._states: List[State] = []
|
||||
|
||||
self._hl_cache: Dict[str, Dict[State, Tuple[State, HLs]]]
|
||||
self._hl_cache = {}
|
||||
# this will be assigned a functools.lru_cache per instance for
|
||||
# better hit rate and memory usage
|
||||
self._hl: Optional[Callable[[State, str, bool], Tuple[State, HLs]]]
|
||||
self._hl = None
|
||||
|
||||
def attr(self, style: Style) -> int:
|
||||
pair = self._color_manager.color_pair(style.fg, style.bg)
|
||||
@@ -48,19 +53,14 @@ class FileSyntax:
|
||||
curses.A_UNDERLINE * style.u
|
||||
)
|
||||
|
||||
def _hl(
|
||||
def _hl_uncached(
|
||||
self,
|
||||
state: State,
|
||||
line: str,
|
||||
i: int,
|
||||
first_line: bool,
|
||||
) -> Tuple[State, HLs]:
|
||||
try:
|
||||
return self._hl_cache[line][state]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
new_state, regions = highlight_line(
|
||||
self._compiler, state, f'{line}\n', first_line=i == 0,
|
||||
self._compiler, state, f'{line}\n', first_line=first_line,
|
||||
)
|
||||
|
||||
# remove the trailing newline
|
||||
@@ -83,18 +83,22 @@ class FileSyntax:
|
||||
else:
|
||||
regs.append(HL(x=r.start, end=r.end, attr=attr))
|
||||
|
||||
dct = self._hl_cache.setdefault(line, {})
|
||||
ret = dct[state] = (new_state, tuple(regs))
|
||||
return ret
|
||||
return new_state, tuple(regs)
|
||||
|
||||
def highlight_until(self, lines: SequenceNoSlice, idx: int) -> None:
|
||||
if self._hl is None:
|
||||
# the docs claim better performance with power of two sizing
|
||||
size = max(4096, 2 ** (int(math.log(len(lines), 2)) + 2))
|
||||
self._hl = functools.lru_cache(maxsize=size)(self._hl_uncached)
|
||||
|
||||
if not self._states:
|
||||
state = self._compiler.root_state
|
||||
else:
|
||||
state = self._states[-1]
|
||||
|
||||
for i in range(len(self._states), idx):
|
||||
state, regions = self._hl(state, lines[i], i)
|
||||
# https://github.com/python/mypy/issues/8579
|
||||
state, regions = self._hl(state, lines[i], i == 0) # type: ignore
|
||||
self._states.append(state)
|
||||
self.regions.append(regions)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
|
||||
from babi.color_manager import ColorManager
|
||||
from babi.highlight import Grammars
|
||||
from babi.hl.interface import HL
|
||||
from babi.hl.syntax import Syntax
|
||||
from babi.theme import Color
|
||||
from babi.theme import Theme
|
||||
@@ -149,3 +150,20 @@ def test_style_attributes_applied(stdscr, syntax):
|
||||
style = THEME.select(('keyword.python',))
|
||||
attr = syntax.blank_file_highlighter().attr(style)
|
||||
assert attr == 2 << 8 | curses.A_BOLD
|
||||
|
||||
|
||||
def test_syntax_highlight_cache_first_line(stdscr):
|
||||
with FakeCurses.patch(n_colors=256, can_change_color=False):
|
||||
grammars = Grammars([{
|
||||
'scopeName': 'source.demo',
|
||||
'fileTypes': ['demo'],
|
||||
'patterns': [{'match': r'\Aint', 'name': 'keyword'}],
|
||||
}])
|
||||
syntax = Syntax(grammars, THEME, ColorManager.make())
|
||||
syntax._init_screen(stdscr)
|
||||
file_hl = syntax.file_highlighter('foo.demo', '')
|
||||
file_hl.highlight_until(['int', 'int'], 2)
|
||||
assert file_hl.regions == [
|
||||
(HL(0, 3, curses.A_BOLD | 2 << 8),),
|
||||
(),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user