feat: complete TextMate Extensions rendering layer#112
Conversation
- add extension-backed TextMate tokenization and theme registry - define stable theme numbering contract and migration behavior - preserve legacy ECLI/PySH themes as compatibility themes - implement viewport-first bounded rendering with tokenizer budgets and caches - fix large-file scrolling freezes on Makefile and logs - protect multiline comments and strings across Python, JavaScript, TypeScript, HTML, and CSS - prevent log and ignore files from resolving as SQL or Transact-SQL - document TextMate, onigurumacffi, and Oniguruma packaging requirements - add real rendering, performance, PTY, config, and multiline acceptance tests - keep imported upstream extension assets unchanged
…ax-service-editor-rendering feat: stabilize TextMate syntax rendering * add extension-backed TextMate tokenization and theme registry * define stable theme numbering contract and migration behavior * preserve legacy ECLI/PySH themes as compatibility themes * implement viewport-first bounded rendering with tokenizer budgets and caches * fix large-file scrolling freezes on Makefile and logs * protect multiline comments and strings across Python, JavaScript, TypeScript, HTML, and CSS * prevent log and ignore files from resolving as SQL or Transact-SQL * document TextMate, onigurumacffi, and Oniguruma packaging requirements * add real rendering, performance, PTY, config, theme-numbering, dependency, and multiline acceptance tests * keep imported upstream extension assets unchanged
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughImplements end-to-end TextMate syntax highlighting (issue ChangesTextMate Syntax Engine, Theme System, and Config Migration
AI Provider Expansion
Sequence Diagram(s)sequenceDiagram
participant Ecli
participant SyntaxService
participant TextMateTokenizer
participant LineHighlighter
participant theme_bridge
Ecli->>SyntaxService: resolve(filename)
SyntaxService-->>Ecli: SyntaxResolution (language_id, scope_name, fallback_to_legacy)
Ecli->>SyntaxService: build_line_highlighter(filename)
SyntaxService->>TextMateTokenizer: load_tokenizer(grammar_path)
TextMateTokenizer-->>SyntaxService: TextMateTokenizer or None
SyntaxService-->>Ecli: LineHighlighter with protected ranges
Ecli->>LineHighlighter: highlight_lines(viewport)
LineHighlighter->>TextMateTokenizer: tokenize_line(line) [SIGALRM budget]
TextMateTokenizer-->>LineHighlighter: [(scope, start, end)] or None
LineHighlighter->>theme_bridge: tokens_to_spans(tokens, protected_ranges)
theme_bridge-->>LineHighlighter: [(text, category)] spans
LineHighlighter-->>Ecli: list[list[LineSpan] | None]
Note over Ecli: Lines where highlighter returns None fall back to legacy Pygments tokenization
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/ecli/core/Ecli.py`:
- Around line 1933-1937: The _is_non_sql_filename method currently returns True
when filename is None, which incorrectly suppresses SQL syntax highlighting
guessing for unnamed buffers. Modify the logic so that it only returns True when
filename is not None AND the filename does not end with .sql. This way, missing
filenames will be treated as unknown (returning False) and allow Pygments to
guess the language from content, while named non-SQL files will still be
correctly identified as non-SQL (returning True).
- Around line 1827-1884: The _apply_extension_highlighting method is too complex
(SonarCloud reports complexity 18 > 15) and needs to be refactored. Extract the
highlighter invocation logic (both the highlight_lines and individual highlight
code paths with their exception handling) into a separate helper method that
returns the highlighted_lines list. Then extract the per-line processing logic
(the for loop that validates spans, handles fallback tokenization via
_get_tokenized_line, and performs color mapping) into another helper method.
This will reduce the cyclomatic complexity of _apply_extension_highlighting
while preserving all the current behavior for exception recovery, span
validation, and fallback rendering.
In `@src/ecli/extensions/ecli_integration/syntax_service.py`:
- Around line 260-261: Remove the unused variable assignments for start_col and
end_col that are extracted from tok.end. These variables are assigned but never
referenced anywhere in the function, which is causing the Ruff linter to fail.
Simply delete both assignment statements or, if only start_row and start_col are
needed, remove the second line that unpacks tok.end into end_row and end_col.
In `@src/ecli/extensions/ecli_integration/textmate_tokenizer.py`:
- Around line 48-60: Fix the import ordering in the imports section at the
beginning of textmate_tokenizer.py by ensuring all standard library imports are
grouped together at the top. Currently, the imports io and json appear after the
from typing import statement and should be moved up to be grouped with the other
standard library imports like contextlib, logging, os, signal, threading, and
the from imports. Additionally, address the exception naming violation
referenced at line 130 by ensuring any custom exception or exception variable
naming follows Ruff's naming conventions and repository standards for exception
handling.
In `@src/ecli/extensions/ecli_integration/theme_bridge.py`:
- Around line 273-282: The span coalescing loop that processes character
sequences by category is using string concatenation with the += operator inside
the loop (at line 277 where current_text += line[index]), which causes O(n²)
performance on long same-category runs. Replace the string concatenation
approach with a list-based accumulator that collects the individual line
elements, then join them into a single string only when appending to the spans
list (when the category changes or at the end of iteration). This will reduce
the time complexity from O(n²) to O(n) by avoiding repeated string object
creation.
In `@src/ecli/extensions/ecli_integration/theme_registry.py`:
- Around line 248-280: The _strip_trailing_commas function exceeds the allowed
complexity threshold and needs refactoring through decomposition. Extract the
string mode handling logic (the in_string conditional block and the character
checking for quote characters) into a separate helper function, and extract the
comma lookahead detection logic (the lookahead while loop and the check for
closing brackets) into another helper function. This will break the main loop
into simpler, more focused operations while preserving the original
functionality of skipping trailing commas outside of string literals.
- Around line 205-245: The _strip_json_comments function has excessive
cyclomatic complexity due to multiple nested conditional branches handling
different states (string parsing, line comments, block comments). Refactor by
extracting the three main state-handling logic blocks into separate helper
functions: one to handle character logic when inside a string (checking for
escaped characters and quote termination), one to skip line comments starting
with //, and one to skip block comments between /* and */. Have the main loop in
_strip_json_comments call these helpers instead of containing the nested
conditionals, which will reduce complexity while preserving the exact current
behavior.
In `@src/ecli/integrations/AI.py`:
- Around line 919-926: The error handling block where response.status is checked
for non-200 values is logging the full response_text directly to the logger,
which can expose sensitive information like prompt fragments or secrets from the
provider's error payload. Instead of passing response_text directly to the
logger.error call, redact or truncate the response text before logging by
limiting it to a safe length or removing potentially sensitive patterns,
ensuring sensitive content is not exposed in logs while still maintaining enough
context for debugging.
In `@src/ecli/utils/utils.py`:
- Around line 541-545: The warning message in the logger.warning call for the
legacy theme table configuration contains duplicated wording that reads
awkwardly as "Set a root-level a valid root-level...". Remove the first
occurrence of "a root-level " in the message string so it correctly reads "Set a
valid root-level..." to provide clear and grammatically correct remediation
guidance to the user.
- Around line 564-567: The `toml.load()` method in the `_load_toml_file`
function does not accept `pathlib.Path` objects directly; it requires a string
path. Modify the `toml.load(path)` call to convert the path parameter to a
string using `str(path)` before passing it to the function. This ensures
compatibility with the toml package and prevents runtime TypeErrors that would
cause the function to fall back to default configuration values.
In `@tests/docs/test_textmate_dependency_contract.py`:
- Around line 34-38: The _read function currently uses pytest.skip() when a
required document file is missing, which masks contract regressions when docs
are deleted or renamed. Replace the pytest.skip() call with pytest.fail() or
raise an exception (such as FileNotFoundError) when the file does not exist, so
that the test fails fast and makes it immediately apparent that a required
document is missing rather than silently skipping the test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5fdd525f-9f2a-499d-9e86-7789d9fe1ebf
📒 Files selected for processing (52)
.codex/PIPELINE.mdAGENTS.mdCODEX.mdREADME.mdaudit-report.mdconfig.tomldocs/INSTALL.mddocs/architecture/extensions-layer.mddocs/config/config-precedence.mddocs/config/config-schema.mddocs/contributor/build-from-source.mddocs/release/packaging-flows.mddocs/release/release-checklist.mdpyproject.tomlsrc/ecli/core/Ecli.pysrc/ecli/extensions/ecli_integration/__init__.pysrc/ecli/extensions/ecli_integration/config.pysrc/ecli/extensions/ecli_integration/grammar_catalog.pysrc/ecli/extensions/ecli_integration/language_detection.pysrc/ecli/extensions/ecli_integration/manifest.pysrc/ecli/extensions/ecli_integration/registry.pysrc/ecli/extensions/ecli_integration/syntax_service.pysrc/ecli/extensions/ecli_integration/textmate_tokenizer.pysrc/ecli/extensions/ecli_integration/theme_bridge.pysrc/ecli/extensions/ecli_integration/theme_registry.pysrc/ecli/integrations/AI.pysrc/ecli/services/config_service.pysrc/ecli/utils/themes.pysrc/ecli/utils/utils.pytests/core/test_config_loading.pytests/core/test_theme_system.pytests/docs/test_textmate_dependency_contract.pytests/extensions/test_editor_syntax_adapter.pytests/extensions/test_editor_syntax_rendering.pytests/extensions/test_extension_language_detection.pytests/extensions/test_extension_layer_config.pytests/extensions/test_extension_manifest_registry.pytests/extensions/test_extension_syntax_service.pytests/extensions/test_extension_theme_registry.pytests/extensions/test_no_fake_textmate_support.pytests/extensions/test_textmate_grammar_catalog.pytests/extensions/test_textmate_multiline_protection.pytests/extensions/test_textmate_render_performance.pytests/extensions/test_textmate_render_regressions.pytests/extensions/test_textmate_scroll_regression.pytests/extensions/test_textmate_tokenization.pytests/extensions/test_textmate_tokenizer_budget.pytests/extensions/test_theme_bridge_priority.pytests/extensions/test_theme_numbering_contract.pytests/ui/test_design_system.pytests/ui/test_professional_chrome.pytests/ui/test_textmate_scroll_pty_smoke.py
|



Summary
This PR brings the completed TextMate Extensions rendering layer into main.
The original #101 PR already merged the grammar catalog and language detection baseline. After that, #102 was stacked on top of #101 and merged into the restored #101 branch. This PR brings the remaining #102 work into main.
What changed
Manual validation
Manual validation was performed in uv run ecli.
Confirmed:
Validation
Validated locally with:
Observed:
Scope boundaries
Not changed:
Follow-up
After this PR is merged, continue with the next Extensions-layer milestone:
Summary by CodeRabbit