diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2423a1258..888ce59308 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added Widget.preflight_checks to perform some debug checks after a widget is instantiated, to catch common errors. https://github.com/Textualize/textual/pull/5588
+### Fixed
+
+- Fixed TextArea's syntax highlighting. Some highlighting details were not being
+ applied. For example, in CSS, the text 'padding: 10px 0;' was shown in a
+ single colour. Now the 'px' appears in a different colour to the rest of the
+ text.
+
+- Fixed a cause of slow editing for syntax highlighed TextArea widgets with
+ large documents.
+
+
## [2.1.2] - 2025-02-26
### Fixed
diff --git a/src/textual/document/_syntax_aware_document.py b/src/textual/document/_syntax_aware_document.py
index 162d3fbd54..9288e63fa2 100644
--- a/src/textual/document/_syntax_aware_document.py
+++ b/src/textual/document/_syntax_aware_document.py
@@ -1,5 +1,8 @@
from __future__ import annotations
+from contextlib import contextmanager
+from typing import ContextManager
+
try:
from tree_sitter import Language, Node, Parser, Query, Tree
@@ -12,6 +15,35 @@
from textual.document._document import Document, EditResult, Location, _utf8_encode
+@contextmanager
+def temporary_query_point_range(
+ query: Query,
+ start_point: tuple[int, int] | None,
+ end_point: tuple[int, int] | None,
+) -> ContextManager[None]:
+ """Temporarily change the start and/or end point for a tree-sitter Query.
+
+ Args:
+ query: The tree-sitter Query.
+ start_point: The (row, column byte) to start the query at.
+ end_point: The (row, column byte) to end the query at.
+ """
+ # Note: Although not documented for the tree-sitter Python API, an
+ # end-point of (0, 0) means 'end of document'.
+ default_point_range = [(0, 0), (0, 0)]
+
+ point_range = list(default_point_range)
+ if start_point is not None:
+ point_range[0] = start_point
+ if end_point is not None:
+ point_range[1] = end_point
+ query.set_point_range(point_range)
+ try:
+ yield None
+ finally:
+ query.set_point_range(default_point_range)
+
+
class SyntaxAwareDocumentError(Exception):
"""General error raised when SyntaxAwareDocument is used incorrectly."""
@@ -128,14 +160,8 @@ def query_syntax_tree(
"tree-sitter is not available on this architecture."
)
- captures_kwargs = {}
- if start_point is not None:
- captures_kwargs["start_point"] = start_point
- if end_point is not None:
- captures_kwargs["end_point"] = end_point
-
- captures = query.captures(self._syntax_tree.root_node, **captures_kwargs)
- return captures
+ with temporary_query_point_range(query, start_point, end_point):
+ return query.captures(self._syntax_tree.root_node)
def replace_range(self, start: Location, end: Location, text: str) -> EditResult:
"""Replace text at the given range.
diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py
index 687ef8107d..9f58388a14 100644
--- a/src/textual/widgets/_text_area.py
+++ b/src/textual/widgets/_text_area.py
@@ -70,6 +70,105 @@ class LanguageDoesNotExist(Exception):
"""
+class HighlightMap:
+ """Lazy evaluated pseudo dictionary mapping lines to highlight information.
+
+ This allows TextArea syntax highlighting to scale.
+
+ Args:
+ text_area_widget: The associated `TextArea` widget.
+ """
+
+ BLOCK_SIZE = 50
+
+ def __init__(self, text_area: TextArea):
+ self.text_area: TextArea = text_area
+ """The text area associated with this highlight map."""
+
+ self._highlighted_blocks: set[int] = set()
+ """The set of blocks that have been highlighted. Each block covers BLOCK_SIZE
+ lines.
+ """
+
+ self._highlights: dict[int, list[Highlight]] = defaultdict(list)
+ """A mapping from line index to a list of Highlight instances."""
+
+ def reset(self) -> None:
+ """Reset so that future lookups rebuild the highlight map."""
+ self._highlights.clear()
+ self._highlighted_blocks.clear()
+
+ @property
+ def document(self) -> DocumentBase:
+ """The text document being highlighted."""
+ return self.text_area.document
+
+ def __getitem__(self, index: int) -> list[Highlight]:
+ block_index = index // self.BLOCK_SIZE
+ if block_index not in self._highlighted_blocks:
+ self._highlighted_blocks.add(block_index)
+ self._build_part_of_highlight_map(block_index * self.BLOCK_SIZE)
+ return self._highlights[index]
+
+ def _build_part_of_highlight_map(self, start_index: int) -> None:
+ """Build part of the highlight map.
+
+ Args:
+ start_index: The start of the block of line for which to build the map.
+ """
+ highlights = self._highlights
+ start_point = (start_index, 0)
+ end_index = min(self.document.line_count, start_index + self.BLOCK_SIZE)
+ end_point = (end_index, 0)
+ captures = self.document.query_syntax_tree(
+ self.text_area._highlight_query,
+ start_point=start_point,
+ end_point=end_point,
+ )
+ for highlight_name, nodes in captures.items():
+ for node in nodes:
+ node_start_row, node_start_column = node.start_point
+ node_end_row, node_end_column = node.end_point
+ if node_start_row == node_end_row:
+ highlight = node_start_column, node_end_column, highlight_name
+ highlights[node_start_row].append(highlight)
+ else:
+ # Add the first line of the node range
+ highlights[node_start_row].append(
+ (node_start_column, None, highlight_name)
+ )
+
+ # Add the middle lines - entire row of this node is highlighted
+ middle_highlight = (0, None, highlight_name)
+ for node_row in range(node_start_row + 1, node_end_row):
+ highlights[node_row].append(middle_highlight)
+
+ # Add the last line of the node range
+ highlights[node_end_row].append(
+ (0, node_end_column, highlight_name)
+ )
+
+ # The highlights for each line need to be sorted. Each highlight is of
+ # the form:
+ #
+ # a, b, highlight-name
+ #
+ # Where a is a number and b is a number or ``None``. These highlights need
+ # to be sorted in ascending order of ``a``. When two highlights have the same
+ # value of ``a`` then the one with the larger a--b range comes first, with ``None``
+ # being considered larger than any number.
+ def sort_key(highlight: Highlight) -> tuple[int, int, int]:
+ a, b, _ = highlight
+ max_range_index = 1
+ if b is None:
+ max_range_index = 0
+ b = a
+ return a, max_range_index, a - b
+
+ for line_index in range(start_index, end_index):
+ highlights.get(line_index, []).sort(key=sort_key)
+
+
@dataclass
class TextAreaLanguage:
"""A container for a language which has been registered with the TextArea.
@@ -456,15 +555,15 @@ def __init__(
cursor is currently at. If the cursor is at a bracket, or there's no matching
bracket, this will be `None`."""
- self._highlights: dict[int, list[Highlight]] = defaultdict(list)
- """Mapping line numbers to the set of highlights for that line."""
-
self._highlight_query: "Query | None" = None
"""The query that's currently being used for highlighting."""
self.document: DocumentBase = Document(text)
"""The document this widget is currently editing."""
+ self._highlights: HighlightMap = HighlightMap(self)
+ """Mapping line numbers to the set of highlights for that line."""
+
self.wrapped_document: WrappedDocument = WrappedDocument(self.document)
"""The wrapped view of the document."""
@@ -592,36 +691,11 @@ def check_consume_key(self, key: str, character: str | None = None) -> bool:
# Otherwise we capture all printable keys
return character is not None and character.isprintable()
- def _build_highlight_map(self) -> None:
- """Query the tree for ranges to highlights, and update the internal highlights mapping."""
- highlights = self._highlights
- highlights.clear()
- if not self._highlight_query:
- return
-
- captures = self.document.query_syntax_tree(self._highlight_query)
- for highlight_name, nodes in captures.items():
- for node in nodes:
- node_start_row, node_start_column = node.start_point
- node_end_row, node_end_column = node.end_point
-
- if node_start_row == node_end_row:
- highlight = (node_start_column, node_end_column, highlight_name)
- highlights[node_start_row].append(highlight)
- else:
- # Add the first line of the node range
- highlights[node_start_row].append(
- (node_start_column, None, highlight_name)
- )
-
- # Add the middle lines - entire row of this node is highlighted
- for node_row in range(node_start_row + 1, node_end_row):
- highlights[node_row].append((0, None, highlight_name))
+ def _reset_highlights(self) -> None:
+ """Reset the lazily evaluated highlight map."""
- # Add the last line of the node range
- highlights[node_end_row].append(
- (0, node_end_column, highlight_name)
- )
+ if self._highlight_query:
+ self._highlights.reset()
def _watch_has_focus(self, focus: bool) -> None:
self._cursor_visible = focus
@@ -935,7 +1009,7 @@ def _set_document(self, text: str, language: str | None) -> None:
self.document = document
self.wrapped_document = WrappedDocument(document, tab_width=self.indent_width)
self.navigator = DocumentNavigator(self.wrapped_document)
- self._build_highlight_map()
+ self._reset_highlights()
self.move_cursor((0, 0))
self._rewrap_and_refresh_virtual_size()
@@ -1348,7 +1422,7 @@ def edit(self, edit: Edit) -> EditResult:
self._refresh_size()
edit.after(self)
- self._build_highlight_map()
+ self._reset_highlights()
self.post_message(self.Changed(self))
return result
@@ -1411,7 +1485,7 @@ def _undo_batch(self, edits: Sequence[Edit]) -> None:
self._refresh_size()
for edit in reversed(edits):
edit.after(self)
- self._build_highlight_map()
+ self._reset_highlights()
self.post_message(self.Changed(self))
def _redo_batch(self, edits: Sequence[Edit]) -> None:
@@ -1459,7 +1533,7 @@ def _redo_batch(self, edits: Sequence[Edit]) -> None:
self._refresh_size()
for edit in edits:
edit.after(self)
- self._build_highlight_map()
+ self._reset_highlights()
self.post_message(self.Changed(self))
async def _on_key(self, event: events.Key) -> None:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg
index 44ac0c0411..a49c38524e 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[css].svg
@@ -19,330 +19,330 @@
font-weight: 700;
}
- .terminal-2526263208-matrix {
+ .terminal-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-2526263208-title {
+ .terminal-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-2526263208-r1 { fill: #121212 }
-.terminal-2526263208-r2 { fill: #0178d4 }
-.terminal-2526263208-r3 { fill: #c5c8c6 }
-.terminal-2526263208-r4 { fill: #c2c2bf }
-.terminal-2526263208-r5 { fill: #272822 }
-.terminal-2526263208-r6 { fill: #75715e }
-.terminal-2526263208-r7 { fill: #f8f8f2 }
-.terminal-2526263208-r8 { fill: #90908a }
-.terminal-2526263208-r9 { fill: #a6e22e }
-.terminal-2526263208-r10 { fill: #ae81ff }
-.terminal-2526263208-r11 { fill: #e6db74 }
-.terminal-2526263208-r12 { fill: #f92672 }
+ .terminal-r1 { fill: #121212 }
+.terminal-r2 { fill: #0178d4 }
+.terminal-r3 { fill: #c5c8c6 }
+.terminal-r4 { fill: #c2c2bf }
+.terminal-r5 { fill: #272822 }
+.terminal-r6 { fill: #75715e }
+.terminal-r7 { fill: #f8f8f2 }
+.terminal-r8 { fill: #90908a }
+.terminal-r9 { fill: #a6e22e }
+.terminal-r10 { fill: #ae81ff }
+.terminal-r11 { fill: #e6db74 }
+.terminal-r12 { fill: #f92672 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- TextAreaSnapshot
+ TextAreaSnapshot
-
-
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
-▊ 1 /* This is a comment in CSS */▎
-▊ 2 ▎
-▊ 3 /* Basic selectors and properties */▎
-▊ 4 body { ▎
-▊ 5 font-family: Arial, sans-serif; ▎
-▊ 6 background-color: #f4f4f4; ▎
-▊ 7 margin: 0; ▎
-▊ 8 padding: 0; ▎
-▊ 9 } ▎
-▊10 ▎
-▊11 /* Class and ID selectors */▎
-▊12 .header { ▎
-▊13 background-color: #333; ▎
-▊14 color: #fff; ▎
-▊15 padding: 10px0; ▎
-▊16 text-align: center; ▎
-▊17 } ▎
-▊18 ▎
-▊19 #logo { ▎
-▊20 font-size: 24px; ▎
-▊21 font-weight: bold; ▎
-▊22 } ▎
-▊23 ▎
-▊24 /* Descendant and child selectors */▎
-▊25 .navul { ▎
-▊26 list-style-type: none; ▎
-▊27 padding: 0; ▎
-▊28 } ▎
-▊29 ▎
-▊30 .nav > li { ▎
-▊31 display: inline-block; ▎
-▊32 margin-right: 10px; ▎
-▊33 } ▎
-▊34 ▎
-▊35 /* Pseudo-classes */▎
-▊36 a:hover { ▎
-▊37 text-decoration: underline; ▎
-▊38 } ▎
-▊39 ▎
-▊40 input:focus { ▎
-▊41 border-color: #007BFF; ▎
-▊42 } ▎
-▊43 ▎
-▊44 /* Media query */▎
-▊45 @media (max-width: 768px) { ▎
-▊46 body { ▎
-▊47 font-size: 16px; ▎
-▊48 } ▎
-▊49 ▎
-▊50 .header { ▎
-▊51 padding: 5px0; ▎
-▊52 } ▎
-▊53 } ▎
-▊54 ▎
-▊55 /* Keyframes animation */▎
-▊56 @keyframes slideIn { ▎
-▊57 from { ▎
-▊58 transform: translateX(-100%); ▎
-▊59 } ▎
-▊60 to { ▎
-▊61 transform: translateX(0); ▎
-▊62 } ▎
-▊63 } ▎
-▊64 ▎
-▊65 .slide-in-element { ▎
-▊66 animation: slideIn0.5sforwards; ▎
-▊67 } ▎
-▊68 ▎
-▊▎
-▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
+
+
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
+▊ 1 /* This is a comment in CSS */▎
+▊ 2 ▎
+▊ 3 /* Basic selectors and properties */▎
+▊ 4 body { ▎
+▊ 5 font-family: Arial, sans-serif; ▎
+▊ 6 background-color: #f4f4f4; ▎
+▊ 7 margin: 0; ▎
+▊ 8 padding: 0; ▎
+▊ 9 } ▎
+▊10 ▎
+▊11 /* Class and ID selectors */▎
+▊12 .header { ▎
+▊13 background-color: #333; ▎
+▊14 color: #fff; ▎
+▊15 padding: 10px0; ▎
+▊16 text-align: center; ▎
+▊17 } ▎
+▊18 ▎
+▊19 #logo { ▎
+▊20 font-size: 24px; ▎
+▊21 font-weight: bold; ▎
+▊22 } ▎
+▊23 ▎
+▊24 /* Descendant and child selectors */▎
+▊25 .navul { ▎
+▊26 list-style-type: none; ▎
+▊27 padding: 0; ▎
+▊28 } ▎
+▊29 ▎
+▊30 .nav > li { ▎
+▊31 display: inline-block; ▎
+▊32 margin-right: 10px; ▎
+▊33 } ▎
+▊34 ▎
+▊35 /* Pseudo-classes */▎
+▊36 a:hover { ▎
+▊37 text-decoration: underline; ▎
+▊38 } ▎
+▊39 ▎
+▊40 input:focus { ▎
+▊41 border-color: #007BFF; ▎
+▊42 } ▎
+▊43 ▎
+▊44 /* Media query */▎
+▊45 @media (max-width: 768px) { ▎
+▊46 body { ▎
+▊47 font-size: 16px; ▎
+▊48 } ▎
+▊49 ▎
+▊50 .header { ▎
+▊51 padding: 5px0; ▎
+▊52 } ▎
+▊53 } ▎
+▊54 ▎
+▊55 /* Keyframes animation */▎
+▊56 @keyframes slideIn { ▎
+▊57 from { ▎
+▊58 transform: translateX(-100%); ▎
+▊59 } ▎
+▊60 to { ▎
+▊61 transform: translateX(0); ▎
+▊62 } ▎
+▊63 } ▎
+▊64 ▎
+▊65 .slide-in-element { ▎
+▊66 animation: slideIn0.5sforwards; ▎
+▊67 } ▎
+▊68 ▎
+▊▎
+▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg
index 645ea326fa..77ae9609f9 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[javascript].svg
@@ -19,371 +19,371 @@
font-weight: 700;
}
- .terminal-2506662657-matrix {
+ .terminal-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-2506662657-title {
+ .terminal-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-2506662657-r1 { fill: #121212 }
-.terminal-2506662657-r2 { fill: #0178d4 }
-.terminal-2506662657-r3 { fill: #c5c8c6 }
-.terminal-2506662657-r4 { fill: #c2c2bf }
-.terminal-2506662657-r5 { fill: #272822 }
-.terminal-2506662657-r6 { fill: #75715e }
-.terminal-2506662657-r7 { fill: #f8f8f2 }
-.terminal-2506662657-r8 { fill: #90908a }
-.terminal-2506662657-r9 { fill: #f92672 }
-.terminal-2506662657-r10 { fill: #e6db74 }
-.terminal-2506662657-r11 { fill: #ae81ff }
-.terminal-2506662657-r12 { fill: #66d9ef;font-style: italic; }
-.terminal-2506662657-r13 { fill: #a6e22e }
+ .terminal-r1 { fill: #121212 }
+.terminal-r2 { fill: #0178d4 }
+.terminal-r3 { fill: #c5c8c6 }
+.terminal-r4 { fill: #c2c2bf }
+.terminal-r5 { fill: #272822 }
+.terminal-r6 { fill: #75715e }
+.terminal-r7 { fill: #f8f8f2 }
+.terminal-r8 { fill: #90908a }
+.terminal-r9 { fill: #f92672 }
+.terminal-r10 { fill: #e6db74 }
+.terminal-r11 { fill: #ae81ff }
+.terminal-r12 { fill: #66d9ef;font-style: italic; }
+.terminal-r13 { fill: #a6e22e }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- TextAreaSnapshot
+ TextAreaSnapshot
-
-
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
-▊ 1 // Variable declarations▎
-▊ 2 const name = "John"; ▎
-▊ 3 let age = 30; ▎
-▊ 4 var isStudent = true; ▎
-▊ 5 ▎
-▊ 6 // Template literals▎
-▊ 7 console.log(`Hello, ${name}! You are ${age} years old.`); ▎
-▊ 8 ▎
-▊ 9 // Conditional statements▎
-▊10 if (age >= 18 && isStudent) { ▎
-▊11 console.log("You are an adult student."); ▎
-▊12 } elseif (age >= 18) { ▎
-▊13 console.log("You are an adult."); ▎
-▊14 } else { ▎
-▊15 console.log("You are a minor."); ▎
-▊16 } ▎
-▊17 ▎
-▊18 // Arrays and array methods▎
-▊19 const numbers = [1, 2, 3, 4, 5]; ▎
-▊20 const doubledNumbers = numbers.map((num) => num * 2); ▎
-▊21 console.log("Doubled numbers:", doubledNumbers); ▎
-▊22 ▎
-▊23 // Objects▎
-▊24 const person = { ▎
-▊25 firstName: "John", ▎
-▊26 lastName: "Doe", ▎
-▊27 getFullName() { ▎
-▊28 return`${this.firstName}${this.lastName}`; ▎
-▊29 }, ▎
-▊30 }; ▎
-▊31 console.log("Full name:", person.getFullName()); ▎
-▊32 ▎
-▊33 // Classes▎
-▊34 class Rectangle { ▎
-▊35 constructor(width, height) { ▎
-▊36 this.width = width; ▎
-▊37 this.height = height; ▎
-▊38 } ▎
-▊39 ▎
-▊40 getArea() { ▎
-▊41 return this.width * this.height; ▎
-▊42 } ▎
-▊43 } ▎
-▊44 const rectangle = new Rectangle(5, 3); ▎
-▊45 console.log("Rectangle area:", rectangle.getArea()); ▎
-▊46 ▎
-▊47 // Async/Await and Promises▎
-▊48 asyncfunctionfetchData() { ▎
-▊49 try { ▎
-▊50 const response = awaitfetch("https://api.example.com/data"); ▎
-▊51 const data = await response.json(); ▎
-▊52 console.log("Fetched data:", data); ▎
-▊53 } catch (error) { ▎
-▊54 console.error("Error:", error); ▎
-▊55 } ▎
-▊56 } ▎
-▊57 fetchData(); ▎
-▊58 ▎
-▊59 // Arrow functions▎
-▊60 constgreet = (name) => { ▎
-▊61 console.log(`Hello, ${name}!`); ▎
-▊62 }; ▎
-▊63 greet("Alice"); ▎
-▊64 ▎
-▊65 // Destructuring assignment▎
-▊66 const [a, b, ...rest] = [1, 2, 3, 4, 5]; ▎
-▊67 console.log(a, b, rest); ▎
-▊68 ▎
-▊69 // Spread operator▎
-▊70 const arr1 = [1, 2, 3]; ▎
-▊71 const arr2 = [4, 5, 6]; ▎
-▊72 const combinedArr = [...arr1, ...arr2]; ▎
-▊73 console.log("Combined array:", combinedArr); ▎
-▊74 ▎
-▊75 // Ternary operator▎
-▊76 const message = age >= 18 ? "You are an adult." : "You are a minor."; ▎
-▊77 console.log(message); ▎
-▊78 ▎
-▊▎
-▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
+
+
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
+▊ 1 // Variable declarations▎
+▊ 2 const name = "John"; ▎
+▊ 3 let age = 30; ▎
+▊ 4 var isStudent = true; ▎
+▊ 5 ▎
+▊ 6 // Template literals▎
+▊ 7 console.log(`Hello, ${name}! You are ${age} years old.`); ▎
+▊ 8 ▎
+▊ 9 // Conditional statements▎
+▊10 if (age >= 18 && isStudent) { ▎
+▊11 console.log("You are an adult student."); ▎
+▊12 } elseif (age >= 18) { ▎
+▊13 console.log("You are an adult."); ▎
+▊14 } else { ▎
+▊15 console.log("You are a minor."); ▎
+▊16 } ▎
+▊17 ▎
+▊18 // Arrays and array methods▎
+▊19 const numbers = [1, 2, 3, 4, 5]; ▎
+▊20 const doubledNumbers = numbers.map((num) => num * 2); ▎
+▊21 console.log("Doubled numbers:", doubledNumbers); ▎
+▊22 ▎
+▊23 // Objects▎
+▊24 const person = { ▎
+▊25 firstName: "John", ▎
+▊26 lastName: "Doe", ▎
+▊27 getFullName() { ▎
+▊28 return`${this.firstName}${this.lastName}`; ▎
+▊29 }, ▎
+▊30 }; ▎
+▊31 console.log("Full name:", person.getFullName()); ▎
+▊32 ▎
+▊33 // Classes▎
+▊34 class Rectangle { ▎
+▊35 constructor(width, height) { ▎
+▊36 this.width = width; ▎
+▊37 this.height = height; ▎
+▊38 } ▎
+▊39 ▎
+▊40 getArea() { ▎
+▊41 return this.width * this.height; ▎
+▊42 } ▎
+▊43 } ▎
+▊44 const rectangle = new Rectangle(5, 3); ▎
+▊45 console.log("Rectangle area:", rectangle.getArea()); ▎
+▊46 ▎
+▊47 // Async/Await and Promises▎
+▊48 asyncfunctionfetchData() { ▎
+▊49 try { ▎
+▊50 const response = awaitfetch("https://api.example.com/data"); ▎
+▊51 const data = await response.json(); ▎
+▊52 console.log("Fetched data:", data); ▎
+▊53 } catch (error) { ▎
+▊54 console.error("Error:", error); ▎
+▊55 } ▎
+▊56 } ▎
+▊57 fetchData(); ▎
+▊58 ▎
+▊59 // Arrow functions▎
+▊60 constgreet = (name) => { ▎
+▊61 console.log(`Hello, ${name}!`); ▎
+▊62 }; ▎
+▊63 greet("Alice"); ▎
+▊64 ▎
+▊65 // Destructuring assignment▎
+▊66 const [a, b, ...rest] = [1, 2, 3, 4, 5]; ▎
+▊67 console.log(a, b, rest); ▎
+▊68 ▎
+▊69 // Spread operator▎
+▊70 const arr1 = [1, 2, 3]; ▎
+▊71 const arr2 = [4, 5, 6]; ▎
+▊72 const combinedArr = [...arr1, ...arr2]; ▎
+▊73 console.log("Combined array:", combinedArr); ▎
+▊74 ▎
+▊75 // Ternary operator▎
+▊76 const message = age >= 18 ? "You are an adult." : "You are a minor."; ▎
+▊77 console.log(message); ▎
+▊78 ▎
+▊▎
+▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg
index 5cf7309fde..95053a32f0 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[markdown].svg
@@ -19,329 +19,328 @@
font-weight: 700;
}
- .terminal-1784849415-matrix {
+ .terminal-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-1784849415-title {
+ .terminal-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-1784849415-r1 { fill: #121212 }
-.terminal-1784849415-r2 { fill: #0178d4 }
-.terminal-1784849415-r3 { fill: #c5c8c6 }
-.terminal-1784849415-r4 { fill: #c2c2bf }
-.terminal-1784849415-r5 { fill: #272822;font-weight: bold }
-.terminal-1784849415-r6 { fill: #f92672;font-weight: bold }
-.terminal-1784849415-r7 { fill: #f8f8f2 }
-.terminal-1784849415-r8 { fill: #90908a }
-.terminal-1784849415-r9 { fill: #90908a;font-weight: bold }
-.terminal-1784849415-r10 { fill: #272822 }
-.terminal-1784849415-r11 { fill: #003054 }
+ .terminal-r1 { fill: #121212 }
+.terminal-r2 { fill: #0178d4 }
+.terminal-r3 { fill: #c5c8c6 }
+.terminal-r4 { fill: #c2c2bf }
+.terminal-r5 { fill: #272822;font-weight: bold }
+.terminal-r6 { fill: #f92672;font-weight: bold }
+.terminal-r7 { fill: #f8f8f2 }
+.terminal-r8 { fill: #90908a }
+.terminal-r9 { fill: #272822 }
+.terminal-r10 { fill: #003054 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- TextAreaSnapshot
+ TextAreaSnapshot
-
-
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
-▊ 1 Heading ▎
-▊ 2 =======▎
-▊ 3 ▎
-▊ 4 Sub-heading ▎
-▊ 5 -----------▎
-▊ 6 ▎
-▊ 7 ###Heading▎
-▊ 8 ▎
-▊ 9 ####H4 Heading▎
-▊10 ▎
-▊11 #####H5 Heading▎
-▊12 ▎
-▊13 ######H6 Heading▎
-▊14 ▎
-▊15 ▎
-▊16 Paragraphs are separated ▎
-▊17 by a blank line. ▎
-▊18 ▎
-▊19 Two spaces at the end of a line ▎
-▊20 produces a line break. ▎
-▊21 ▎
-▊22 Text attributes _italic_, ▎
-▊23 **bold**, `monospace`. ▎
-▊24 ▎
-▊25 Horizontal rule: ▎
-▊26 ▎
-▊27 --- ▎
-▊28 ▎
-▊29 Bullet list: ▎
-▊30 ▎
-▊31 * apples ▎
-▊32 * oranges ▎
-▊33 * pears ▎
-▊34 ▎
-▊35 Numbered list: ▎
-▊36 ▎
-▊37 1. lather ▎
-▊38 2. rinse ▎
-▊39 3. repeat ▎
-▊40 ▎
-▊41 An [example](http://example.com). ▎
-▊42 ▎
-▊43 > Markdown uses email-style > characters for blockquoting. ▎
-▊44 > ▎
-▊45 > Lorem ipsum ▎
-▊46 ▎
-▊47 . ▎
+▊42 ▎
+▊43 > Markdown uses email-style > characters for blockquoting. ▎
+▊44 > ▎
+▊45 > Lorem ipsum ▎
+▊46 ▎
+▊47 ▎
-▊ 5 ▎
-▊ 6 string_var = "Hello, world!"▎
-▊ 7 int_var = 42▎
-▊ 8 float_var = 3.14▎
-▊ 9 complex_var = 1 + 2j▎
-▊10 ▎
-▊11 list_var = [1, 2, 3, 4, 5] ▎
-▊12 tuple_var = (1, 2, 3, 4, 5) ▎
-▊13 set_var = {1, 2, 3, 4, 5} ▎
-▊14 dict_var = {"a": 1, "b": 2, "c": 3} ▎
-▊15 ▎
-▊16 deffunction_no_args(): ▎
-▊17 return"No arguments"▎
-▊18 ▎
-▊19 deffunction_with_args(a, b): ▎
-▊20 return a + b ▎
-▊21 ▎
-▊22 deffunction_with_default_args(a=0, b=0): ▎
-▊23 return a * b ▎
-▊24 ▎
-▊25 lambda_func = lambda x: x**2▎
-▊26 ▎
-▊27 if int_var == 42: ▎
-▊28 print("It's the answer!") ▎
-▊29 elif int_var < 42: ▎
-▊30 print("Less than the answer.") ▎
-▊31 else: ▎
-▊32 print("Greater than the answer.") ▎
-▊33 ▎
-▊34 for index, value inenumerate(list_var): ▎
-▊35 print(f"Index: {index}, Value: {value}") ▎
-▊36 ▎
-▊37 counter = 0▎
-▊38 while counter < 5: ▎
-▊39 print(f"Counter value: {counter}") ▎
-▊40 counter += 1▎
-▊41 ▎
-▊42 squared_numbers = [x**2for x inrange(10) if x % 2 == 0] ▎
-▊43 ▎
-▊44 try: ▎
-▊45 result = 10 / 0▎
-▊46 except ZeroDivisionError: ▎
-▊47 print("Cannot divide by zero!") ▎
-▊48 finally: ▎
-▊49 print("End of try-except block.") ▎
-▊50 ▎
-▊51 classAnimal: ▎
-▊52 def__init__(self, name): ▎
-▊53 self.name = name ▎
-▊54 ▎
-▊55 defspeak(self): ▎
-▊56 raiseNotImplementedError("Subclasses must implement this method▎
-▊57 ▎
-▊58 classDog(Animal): ▎
-▊59 defspeak(self): ▎
-▊60 returnf"{self.name} says Woof!"▎
-▊61 ▎
-▊62 deffibonacci(n): ▎
-▊63 a, b = 0, 1▎
-▊64 for _ inrange(n): ▎
-▊65 yield a ▎
-▊66 a, b = b, a + b ▎
-▊67 ▎
-▊68 for num infibonacci(5): ▎
-▊69 print(num) ▎
-▊70 ▎
-▊71 withopen('test.txt', 'w') as f: ▎
-▊72 f.write("Testing with statement.") ▎
-▊73 ▎
-▊74 @my_decorator ▎
-▊75 defsay_hello(): ▎
-▊76 print("Hello!") ▎
-▊77 ▎
-▊78 say_hello() ▎
-▊79 ▎
-▊▎▎
-▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
+
+
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
+▊ 1 import math ▎
+▊ 2 from os import path ▎
+▊ 3 ▎
+▊ 4 # I'm a comment :)▎
+▊ 5 ▎
+▊ 6 string_var = "Hello, world!"▎
+▊ 7 int_var = 42▎
+▊ 8 float_var = 3.14▎
+▊ 9 complex_var = 1 + 2j▎
+▊10 ▎
+▊11 list_var = [1, 2, 3, 4, 5] ▎
+▊12 tuple_var = (1, 2, 3, 4, 5) ▎
+▊13 set_var = {1, 2, 3, 4, 5} ▎
+▊14 dict_var = {"a": 1, "b": 2, "c": 3} ▎
+▊15 ▎
+▊16 deffunction_no_args(): ▎
+▊17 return"No arguments"▎
+▊18 ▎
+▊19 deffunction_with_args(a, b): ▎
+▊20 return a + b ▎
+▊21 ▎
+▊22 deffunction_with_default_args(a=0, b=0): ▎
+▊23 return a * b ▎
+▊24 ▎
+▊25 lambda_func = lambda x: x**2▎
+▊26 ▎
+▊27 if int_var == 42: ▎
+▊28 print("It's the answer!") ▎
+▊29 elif int_var < 42: ▎
+▊30 print("Less than the answer.") ▎
+▊31 else: ▎
+▊32 print("Greater than the answer.") ▎
+▊33 ▎
+▊34 for index, value inenumerate(list_var): ▎
+▊35 print(f"Index: {index}, Value: {value}") ▎
+▊36 ▎
+▊37 counter = 0▎
+▊38 while counter < 5: ▎
+▊39 print(f"Counter value: {counter}") ▎
+▊40 counter += 1▎
+▊41 ▎
+▊42 squared_numbers = [x**2for x inrange(10) if x % 2 == 0] ▎
+▊43 ▎
+▊44 try: ▎
+▊45 result = 10 / 0▎
+▊46 except ZeroDivisionError: ▎
+▊47 print("Cannot divide by zero!") ▎
+▊48 finally: ▎
+▊49 print("End of try-except block.") ▎
+▊50 ▎
+▊51 classAnimal: ▎
+▊52 def__init__(self, name): ▎
+▊53 self.name = name ▎
+▊54 ▎
+▊55 defspeak(self): ▎
+▊56 raiseNotImplementedError("Subclasses must implement this method▎
+▊57 ▎
+▊58 classDog(Animal): ▎
+▊59 defspeak(self): ▎
+▊60 returnf"{self.name} says Woof!"▎
+▊61 ▎
+▊62 deffibonacci(n): ▎
+▊63 a, b = 0, 1▎
+▊64 for _ inrange(n): ▎
+▊65 yield a ▎
+▊66 a, b = b, a + b ▎
+▊67 ▎
+▊68 for num infibonacci(5): ▎
+▊69 print(num) ▎
+▊70 ▎
+▊71 withopen('test.txt', 'w') as f: ▎
+▊72 f.write("Testing with statement.") ▎
+▊73 ▎
+▊74 @my_decorator ▎
+▊75 defsay_hello(): ▎
+▊76 print("Hello!") ▎
+▊77 ▎
+▊78 say_hello() ▎
+▊79 ▎
+▊▎▎
+▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg
index 7d9ce1aeb3..31f74b433e 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_text_area_language_rendering[xml].svg
@@ -19,130 +19,130 @@
font-weight: 700;
}
- .terminal-1843935949-matrix {
+ .terminal-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-1843935949-title {
+ .terminal-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-1843935949-r1 { fill: #121212 }
-.terminal-1843935949-r2 { fill: #0178d4 }
-.terminal-1843935949-r3 { fill: #c5c8c6 }
-.terminal-1843935949-r4 { fill: #c2c2bf }
-.terminal-1843935949-r5 { fill: #272822 }
-.terminal-1843935949-r6 { fill: #f8f8f2 }
-.terminal-1843935949-r7 { fill: #f92672 }
-.terminal-1843935949-r8 { fill: #ae81ff }
-.terminal-1843935949-r9 { fill: #90908a }
-.terminal-1843935949-r10 { fill: #75715e }
-.terminal-1843935949-r11 { fill: #e6db74 }
-.terminal-1843935949-r12 { fill: #003054 }
+ .terminal-r1 { fill: #121212 }
+.terminal-r2 { fill: #0178d4 }
+.terminal-r3 { fill: #c5c8c6 }
+.terminal-r4 { fill: #c2c2bf }
+.terminal-r5 { fill: #272822 }
+.terminal-r6 { fill: #f8f8f2 }
+.terminal-r7 { fill: #f92672 }
+.terminal-r8 { fill: #ae81ff }
+.terminal-r9 { fill: #90908a }
+.terminal-r10 { fill: #75715e }
+.terminal-r11 { fill: #e6db74 }
+.terminal-r12 { fill: #003054 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- TextAreaSnapshot
+ TextAreaSnapshot
-
-
-
- ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
-▊ 1 <?xml version="1.0" encoding="UTF-8"?> ▎
-▊ 2 <!-- This is an example XML document -->▎
-▊ 3 <library> ▎
-▊ 4 <book id="1" genre="fiction"> ▎
-▊ 5 <title>The Great Gatsby</title> ▎
-▊ 6 <author>F. Scott Fitzgerald</author> ▎
-▊ 7 <published>1925</published> ▎
-▊ 8 <description><![CDATA[This classic novel explores themes of weal▎
-▊ 9 </book> ▎
-▊10 <book id="2" genre="non-fiction"> ▎
-▊11 <title>Sapiens: A Brief History of Humankind</title> ▎
-▊12 <author>Yuval Noah Harari</author> ▎
-▊13 <published>2011</published> ▎
-▊14 <description><![CDATA[Explores the history and impact of Homo sa▎
-▊15 </book> ▎
-▊16 <!-- Another book can be added here -->▎
-▊17 </library> ▎
-▊18 ▎
-▊▌▎
-▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
+
+
+
+ ▊▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▎
+▊ 1 <?xml version="1.0" encoding="UTF-8"?> ▎
+▊ 2 <!-- This is an example XML document -->▎
+▊ 3 <library> ▎
+▊ 4 <book id="1" genre="fiction"> ▎
+▊ 5 <title>The Great Gatsby</title> ▎
+▊ 6 <author>F. Scott Fitzgerald</author> ▎
+▊ 7 <published>1925</published> ▎
+▊ 8 <description><![CDATA[This classic novel explores themes of weal▎
+▊ 9 </book> ▎
+▊10 <book id="2" genre="non-fiction"> ▎
+▊11 <title>Sapiens: A Brief History of Humankind</title> ▎
+▊12 <author>Yuval Noah Harari</author> ▎
+▊13 <published>2011</published> ▎
+▊14 <description><![CDATA[Explores the history and impact of Homo sa▎
+▊15 </book> ▎
+▊16 <!-- Another book can be added here -->▎
+▊17 </library> ▎
+▊18 ▎
+▊▌▎
+▊▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▎
diff --git a/tests/text_area/test_languages.py b/tests/text_area/test_languages.py
index f5f381354f..d1bdc54de6 100644
--- a/tests/text_area/test_languages.py
+++ b/tests/text_area/test_languages.py
@@ -86,10 +86,10 @@ async def test_update_highlight_query():
text_area = app.query_one(TextArea)
# Before registering the language, we have highlights as expected.
- assert len(text_area._highlights) > 0
+ assert len(text_area._highlights[0]) > 0
# Overwriting the highlight query for Python...
text_area.update_highlight_query("python", "")
# We've overridden the highlight query with a blank one, so there are no highlights.
- assert text_area._highlights == {}
+ assert len(text_area._highlights[0]) == 0