Skip to content

Commit dc46901

Browse files
committed
Run tree_sitter.parse with whole text.
This allows predicates in SCM files to work.
1 parent bcfa702 commit dc46901

File tree

33 files changed

+969
-1018
lines changed

33 files changed

+969
-1018
lines changed

pyproject.toml

+18-18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description = "Modern Text User Interface framework"
99
authors = ["Will McGugan <[email protected]>"]
1010
license = "MIT"
1111
readme = "README.md"
12-
classifiers = [
12+
classifiers = [
1313
"Development Status :: 5 - Production/Stable",
1414
"Environment :: Console",
1515
"Intended Audience :: Developers",
@@ -52,24 +52,24 @@ typing-extensions = "^4.4.0"
5252
platformdirs = ">=3.6.0,<5"
5353

5454
# start of [syntax] extras
55-
# Require tree-sitter >= 0.23.0 and python >= 3.9
55+
# Require tree-sitter >= 0.24.0 and python >= 3.10
5656
# Windows, MacOS and Linux binary wheels are available for all of the languages below.
57-
tree-sitter = { version = ">=0.23.0", optional = true, python = ">=3.9" }
58-
tree-sitter-python = { version = ">=0.23.0", optional = true, python = ">=3.9" }
59-
tree-sitter-markdown = { version = ">=0.3.0", optional = true, python = ">=3.9"}
60-
tree-sitter-json = { version = ">=0.24.0", optional = true, python = ">=3.9" }
61-
tree-sitter-toml = { version = ">=0.6.0", optional = true, python = ">=3.9" }
62-
tree-sitter-yaml = { version = ">=0.6.0", optional = true, python = ">=3.9" }
63-
tree-sitter-html = { version = ">=0.23.0", optional = true, python = ">=3.9" }
64-
tree-sitter-css = { version = ">=0.23.0", optional = true, python = ">=3.9" }
65-
tree-sitter-javascript = { version = ">=0.23.0", optional = true, python = ">=3.9" }
66-
tree-sitter-rust = { version = ">=0.23.0", optional = true, python = ">=3.9" }
67-
tree-sitter-go = { version = ">=0.23.0", optional = true, python = ">=3.9" }
68-
tree-sitter-regex = { version = ">=0.24.0", optional = true, python = ">=3.9" }
69-
tree-sitter-xml = { version = ">=0.7.0", optional = true, python = ">=3.9" }
70-
tree-sitter-sql = { version = ">=0.3.0,<0.3.8", optional = true, python = ">=3.9" }
71-
tree-sitter-java = { version = ">=0.23.0", optional = true, python = ">=3.9" }
72-
tree-sitter-bash = { version = ">=0.23.0", optional = true, python = ">=3.9" }
57+
tree-sitter = { version = ">=0.24.0", optional = true, python = ">=3.10" }
58+
tree-sitter-python = { version = ">=0.23.0", optional = true, python = ">=3.10" }
59+
tree-sitter-markdown = { version = ">=0.3.0", optional = true, python = ">=3.10"}
60+
tree-sitter-json = { version = ">=0.24.0", optional = true, python = ">=3.10" }
61+
tree-sitter-toml = { version = ">=0.6.0", optional = true, python = ">=3.10" }
62+
tree-sitter-yaml = { version = ">=0.6.0", optional = true, python = ">=3.10" }
63+
tree-sitter-html = { version = ">=0.23.0", optional = true, python = ">=3.10" }
64+
tree-sitter-css = { version = ">=0.23.0", optional = true, python = ">=3.10" }
65+
tree-sitter-javascript = { version = ">=0.23.0", optional = true, python = ">=3.10" }
66+
tree-sitter-rust = { version = ">=0.23.0", optional = true, python = ">=3.10" }
67+
tree-sitter-go = { version = ">=0.23.0", optional = true, python = ">=3.10" }
68+
tree-sitter-regex = { version = ">=0.24.0", optional = true, python = ">=3.10" }
69+
tree-sitter-xml = { version = ">=0.7.0", optional = true, python = ">=3.10" }
70+
tree-sitter-sql = { version = ">=0.3.0,<0.3.8", optional = true, python = ">=3.10" }
71+
tree-sitter-java = { version = ">=0.23.0", optional = true, python = ">=3.10" }
72+
tree-sitter-bash = { version = ">=0.23.0", optional = true, python = ">=3.10" }
7373
# end of [syntax] extras
7474

7575
[tool.poetry.extras]

src/textual/document/_syntax_aware_document.py

+9-55
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import weakref
44
from asyncio import CancelledError, Event, Task, create_task, sleep
5-
from functools import partial
65
from typing import Callable, NamedTuple
76

87
try:
@@ -61,7 +60,7 @@ def __init__(
6160
"""The tree-sitter Parser or None if tree-sitter is unavailable."""
6261

6362
self._syntax_tree: Tree = self._parser.parse(
64-
partial(self._read_callable, lines=self.lines)
63+
self.text.encode("utf-8")
6564
) # type: ignore
6665
"""The tree-sitter Tree (syntax tree) built from the document."""
6766

@@ -165,7 +164,7 @@ def replace_range(self, start: Location, end: Location, text: str) -> EditResult
165164
)
166165
return replace_result
167166

168-
def reparse(self, timeout_us: int, lines: list[str], syntax_tree=None) -> bool:
167+
def reparse(self, timeout_us: int, text: bytes) -> bool:
169168
"""Reparse the document.
170169
171170
Args:
@@ -176,13 +175,12 @@ def reparse(self, timeout_us: int, lines: list[str], syntax_tree=None) -> bool:
176175
True if parsing succeeded and False if a timeout occurred.
177176
"""
178177
assert timeout_us > 0
179-
read_source = partial(self._read_callable, lines=lines)
180178
tree = self._syntax_tree
181179
saved_timeout = self._parser.timeout_micros
182180
try:
183181
self._parser.timeout_micros = timeout_us
184182
try:
185-
tree = self._parser.parse(read_source, tree) # type: ignore[arg-type]
183+
tree = self._parser.parse(text, tree) # type: ignore[arg-type]
186184
except ValueError:
187185
# The only known cause is a timeout.
188186
return False
@@ -194,7 +192,7 @@ def set_new_tree():
194192
self._syntax_tree = tree
195193

196194
changed_ranges = self._syntax_tree.changed_ranges(tree)
197-
self._syntax_tree_update_callback(self._syntax_tree, len(lines))
195+
self._syntax_tree_update_callback(self._syntax_tree)
198196
else:
199197
self._syntax_tree = tree
200198
return True
@@ -256,50 +254,6 @@ def _location_to_point(self, location: Location) -> tuple[int, int]:
256254
bytes_on_left = 0
257255
return row, bytes_on_left
258256

259-
def _read_callable(
260-
self,
261-
byte_offset: int,
262-
point: tuple[int, int],
263-
lines: list[str],
264-
) -> bytes:
265-
"""A callable which informs tree-sitter about the document content.
266-
267-
This is passed to tree-sitter which will call it frequently to retrieve
268-
the bytes from the document.
269-
270-
Args:
271-
byte_offset: The number of (utf-8) bytes from the start of the document.
272-
point: A tuple (row index, column *byte* offset). Note that this differs
273-
from our Location tuple which is (row_index, column codepoint offset).
274-
lines: The lines of the document being parsed.
275-
276-
Returns:
277-
All the utf-8 bytes between the byte_offset/point and the end of the current
278-
line _including_ the line separator character(s). Returns None if the
279-
offset/point requested by tree-sitter doesn't correspond to a byte.
280-
"""
281-
row, column = point
282-
newline = self.newline
283-
284-
row_out_of_bounds = row >= len(lines)
285-
if row_out_of_bounds:
286-
return b""
287-
else:
288-
row_text = lines[row]
289-
290-
encoded_row = _utf8_encode(row_text)
291-
encoded_row_length = len(encoded_row)
292-
293-
if column < encoded_row_length:
294-
return encoded_row[column:] + _utf8_encode(newline)
295-
elif column == encoded_row_length:
296-
return _utf8_encode(newline[0])
297-
elif column == encoded_row_length + 1:
298-
if newline == "\r\n":
299-
return b"\n"
300-
301-
return b""
302-
303257

304258
class BackgroundSyntaxParser:
305259
"""A provider of incremental background parsing for syntax highlighting.
@@ -357,15 +311,15 @@ async def _perform_a_single_reparse(self, force_update: bool) -> None:
357311

358312
# In order to allow the user to continue editing without interruption, we reparse
359313
# a snapshot of the TextArea's document.
360-
copy_of_text_for_parsing = document.copy_of_lines()
314+
copy_of_text_for_parsing = document.text.encode("utf-8")
361315

362-
# Use tree-sitter's parser timeout mechanism, when necessary, break the
363-
# full reparse into multiple steps. Most of the time, tree-sitter is so
364-
# fast that no looping occurs.
316+
# Use tree-sitter's parser timeout mechanism to break the full reparse
317+
# into multiple steps. Most of the time, tree-sitter is so fast that no
318+
# looping occurs.
365319
parsed_ok = False
366320
while not parsed_ok:
367321
parsed_ok = document.reparse(
368-
self.PARSE_TIMEOUT_MICROSECONDS, lines=copy_of_text_for_parsing
322+
self.PARSE_TIMEOUT_MICROSECONDS, text=copy_of_text_for_parsing
369323
)
370324
if not parsed_ok:
371325
# Sleeping for zero seconds allows other tasks, I/O, *etc.* to execute,

src/textual/tree-sitter/highlights/python.scm

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
;; Identifier naming conventions
1313
((identifier) @type
14-
(#lua-match? @type "^[A-Z].*[a-z]"))
14+
(#match? @type "^[A-Z].*[a-z]"))
1515
((identifier) @constant
16-
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
16+
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
1717

1818
((attribute
1919
attribute: (identifier) @field)
@@ -59,12 +59,12 @@
5959

6060
((call
6161
function: (identifier) @constructor)
62-
(#lua-match? @constructor "^[A-Z]"))
62+
(#match? @constructor "^[A-Z]"))
6363

6464
((call
6565
function: (attribute
6666
attribute: (identifier) @constructor))
67-
(#lua-match? @constructor "^[A-Z]"))
67+
(#match? @constructor "^[A-Z]"))
6868

6969
;; Decorators
7070

src/textual/widgets/_text_area.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ def check_consume_key(self, key: str, character: str | None = None) -> bool:
12081208
# Otherwise we capture all printable keys
12091209
return character is not None and character.isprintable()
12101210

1211-
def _handle_syntax_tree_update(self, tree: Tree, line_count: int) -> None:
1211+
def _handle_syntax_tree_update(self, tree: Tree) -> None:
12121212
"""Reflect changes to the syntax tree."""
12131213
self._trigger_repaint()
12141214

@@ -1693,11 +1693,6 @@ def _prepare_for_repaint(self) -> Collection[Region]:
16931693
return self._do_prepare_for_repaint()
16941694

16951695
def _do_prepare_for_repaint(self) -> Collection[Region]:
1696-
# TODO:
1697-
# This is being used as a hook to prepare for an imminent screen
1698-
# update, which is not the intended use of this method. A proper
1699-
# 'prepare for screen update' hook.
1700-
17011696
is_syntax_aware = self.is_syntax_aware
17021697
if is_syntax_aware:
17031698
highlights = self._highlights
@@ -1849,7 +1844,7 @@ def create_select_range() -> range:
18491844
range_end = min(sel_end, text_length)
18501845
if not line_text and yy != cursor_y:
18511846
# Make sure that empty line show up as selected.
1852-
line_text = TextReprString("▌")
1847+
line_text = TextReprString.create("▌")
18531848
select_range = range(1, 0, -1)
18541849
elif sel_start < sel_end:
18551850
# The selection covers part of this line section.

0 commit comments

Comments
 (0)