antares 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1968f47a0c8ea17a5e98fa2a3b6d4c339d2dcda58d4ae5032c7799d75ed17a6
4
- data.tar.gz: 70e780331521d82f0dcbbe1ca6aa22de7f8ff7c2736eaca03a3e458ddb0e71f7
3
+ metadata.gz: 0243ed5a88d91e34cf4654f9e3437d590e9079caea1976fde034213dbf28ab8c
4
+ data.tar.gz: 77b2751092cc3f1fb5dd9aaf7dc0615c724512d958f1bfe7a198bc7cad70b463
5
5
  SHA512:
6
- metadata.gz: 1ccfedd6fce3a1095aa81da6bd5f2285c7757a8dba9138eef211fdac309d5b5309e1b514c82e7fd593e93a6a851d9edded3d80c165176357cc7b13bfa43c7040
7
- data.tar.gz: 5c18ad134cbf9fb791f625b3354a592b80d1052374fdf8f17e5c01b7917dd0cf73e4c2be863265d6372476776f769d31a3b2090757fce5eb9629eef0fb5ea7be
6
+ metadata.gz: b24e5c58ad94c655378e327344a9404a73a5bf57185d44854f84ab831bd29857381c5117ee3de4ac00a5de044e2c23560c564cf0a16bd7aa62a369546dc805f1
7
+ data.tar.gz: b988ef69cdf0395af22403e427677ebef54b1fe84cbbeae4b4a2127e0b4c78a243ce8fde131acf6cd0fc79aa3828c078b7dcb6057cb376bde3abcd1ee646c907
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-09-15
4
+
5
+ - Add edit-aware bracket matching and depth information.
6
+ - Add fold regions for brackets, indentation, consecutive comments, and region markers.
7
+ - Add sticky contexts and expanding selection ranges.
8
+ - Add language-specific structure provider registration without a parser dependency.
9
+ - Add bounded JSON and XML plist tmLanguage loading with Rouge-compatible output.
10
+
3
11
  ## 0.1.0 — 2026-09-10
4
12
 
5
13
  - Initial release.
data/README.md CHANGED
@@ -1,8 +1,69 @@
1
- # Antares
1
+ <h1 align="center">Antares</h1>
2
2
 
3
- Incremental syntax highlighting for Rouge, with measured per-lexer compatibility.
4
- The only runtime dependency is `rouge ~> 5.0`; document storage and colors belong
5
- to the caller. Ruby 3.1 or later is supported.
3
+ <p align="center">
4
+ <strong>Incremental syntax highlighting for Rouge, with measured per-lexer compatibility</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://rubygems.org/gems/antares"><img src="https://img.shields.io/gem/v/antares.svg?colorB=319e8c" alt="Gem Version"></a>
9
+ <a href="https://rubygems.org/gems/antares"><img src="https://img.shields.io/gem/dt/antares.svg" alt="Downloads"></a>
10
+ <img src="https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg" alt="Ruby Version">
11
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License">
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="#features">Features</a> ·
16
+ <a href="#installation">Installation</a> ·
17
+ <a href="#quick-start">Quick Start</a> ·
18
+ <a href="#configuration">Configuration</a> ·
19
+ <a href="#how-it-works">How It Works</a> ·
20
+ <a href="#compatibility">Compatibility</a>
21
+ </p>
22
+
23
+ ---
24
+
25
+ Antares adds edit-aware, line-oriented syntax highlighting to [Rouge](https://github.com/rouge-ruby/rouge). It reuses lexer state where compatibility tests show that incremental highlighting is safe, and falls back to bounded re-highlighting for other lexers. Document storage and colors remain with the caller.
26
+
27
+ ## Features
28
+
29
+ <a name="features"></a>
30
+
31
+ - Lazy tokenization for individual lines or ranges
32
+ - Incremental re-highlighting with sparse state checkpoints
33
+ - Cached suffix reuse after edits converge
34
+ - Measured compatibility for every Rouge lexer
35
+ - Windowed and full-document fallback strategies
36
+ - Configurable time and memory limits
37
+ - Bracket pairs, fold regions, sticky contexts, and expanding selections
38
+ - JSON and XML plist tmLanguage grammars for languages Rouge does not cover
39
+ - Rouge and REXML are the only runtime dependencies
40
+
41
+ ## Installation
42
+
43
+ <a name="installation"></a>
44
+
45
+ Add to your Gemfile:
46
+
47
+ ```ruby
48
+ gem "antares"
49
+ ```
50
+
51
+ Then install:
52
+
53
+ ```bash
54
+ bundle install
55
+ ```
56
+
57
+ ### Requirements
58
+
59
+ <a name="requirements"></a>
60
+
61
+ - Ruby 3.1+
62
+ - Rouge 5.x
63
+
64
+ ## Quick Start
65
+
66
+ <a name="quick-start"></a>
6
67
 
7
68
  ```ruby
8
69
  require "antares"
@@ -15,97 +76,160 @@ highlighter = Antares::Highlighter.new(
15
76
  )
16
77
 
17
78
  highlighter.tokens_for(0) # [[Rouge token class, UTF-8 text], ...]
79
+
18
80
  lines[0] = "value = 2\n"
19
81
  highlighter.edit(from_line: 0, removed: 1, inserted: 1)
20
82
  highlighter.tokens_in(0..1) # one token array per line
83
+
84
+ structure = highlighter.structure # built lazily from the same Rouge tokens
85
+ structure.bracket_at(0, 7) # matching Antares::Bracket, or nil
86
+ structure.fold_regions # bracket, indentation, comment, and #region folds
87
+ structure.context_at(1) # outer-to-inner regions for sticky scroll
88
+ structure.selection_ranges(1, 3) # inner-to-outer Antares::Region values
21
89
  ```
22
90
 
23
- For a local checkout, `gem build antares.gemspec` creates an installable gem;
24
- install that file with `gem install ./antares-0.1.0.gem`. Publication is separate
25
- from building and is not performed by tests or CI.
91
+ Update the provider before calling `edit`. Line indices are zero based;
92
+ `removed` is the old line count and `inserted` is the new line count.
26
93
 
27
- ## API and strategies
94
+ The `lines` provider returns one valid UTF-8 logical line for each index,
95
+ preferably including its newline. Tokens contain text rather than offsets: sum
96
+ `text.bytesize` for byte offsets or `text.length` for character offsets.
97
+ Returned rows, token pairs, and text are frozen.
28
98
 
29
- Line indices are zero based. Providers return one valid UTF-8 logical line each,
30
- preferably including its newline. Missing separators are added to non-final
31
- lines. A trailing empty logical line returns an empty token array. Tokens contain
32
- text, not offsets: sum `text.bytesize` for byte offsets, or `text.length` for
33
- character offsets. Returned rows/pairs/text are frozen.
99
+ Bracket and selection columns are zero-based character offsets. A selection
100
+ region's `end_column` is exclusive. Fold regions use inclusive, zero-based line
101
+ indices; their column fields are `nil`. Brackets inside string and comment
102
+ tokens are ignored. `Highlighter#edit` updates the existing `Structure`
103
+ instance and reuses unchanged line data after token state converges.
34
104
 
35
- Call `edit` **after** updating the provider, with the old removed line count and
36
- new inserted line count. `frontier` is the first line not yet proven current;
37
- `advance(until_line: 200)` lets an event loop schedule incremental progress.
38
- `last_scanned_lines` reports actual lexical work, and `checkpoint_bytes` reports
105
+ `frontier` is the first line not yet proven current.
106
+ `advance(until_line: 200)` lets an event loop schedule incremental work.
107
+ `last_scanned_lines` reports lexical work and `checkpoint_bytes` reports
39
108
  normalized checkpoint payload bytes. Highlighter instances are not thread safe.
40
109
 
110
+ Language-specific structure providers can replace the generic derivation for
111
+ future highlighters with the matching Rouge lexer tag:
112
+
113
+ ```ruby
114
+ Antares::Structure.register(:ruby, RubyStructureProvider)
115
+ # RubyStructureProvider.new receives the same five callbacks as Structure.new.
116
+ Antares::Structure.unregister(:ruby)
117
+ ```
118
+
119
+ A provider returns an object implementing `fold_regions`, `brackets`,
120
+ `bracket_at`, `context_at`, `selection_ranges`, and `edit`. Registration is
121
+ thread safe, case insensitive, and affects only structures built afterwards.
122
+ Providers must reflect line changes before `Highlighter#edit`, just like the
123
+ line provider. Antares does not depend on Prism.
124
+
125
+ Load a UTF-8 JSON or XML plist tmLanguage file when Rouge has no lexer for the
126
+ language, then pass the result to `Highlighter` normally:
127
+
128
+ ```ruby
129
+ lexer = Antares::Grammar.load_tmlanguage("syntaxes/example.tmLanguage.json")
130
+ highlighter = Antares::Highlighter.new(lexer: lexer, lines: lines, line_count: count)
131
+ ```
132
+
133
+ The loader supports repository references, `$self`/`$base`, `match`, nested
134
+ patterns, `begin`/`end` (including `applyEndPatternLast`) with numeric
135
+ begin-capture references, and named captures. TextMate comment, string,
136
+ numeric, keyword, storage, name, variable,
137
+ punctuation, markup, and invalid scopes map to their nearest Rouge token types.
138
+ Files are limited to 2 MiB, 64 levels, 10,000 rules, and 16 KiB per regular
139
+ expression; lexing also has byte and time bounds. External grammar includes,
140
+ `while`, nested repositories, capture subgrammars, and injection grammars raise
141
+ `Antares::GrammarError` instead of being partially interpreted.
142
+ The fixed Apple plist 1.0 document type is accepted without external lookup;
143
+ custom document types and entity declarations are rejected.
144
+
145
+ ## Configuration
146
+
147
+ <a name="configuration"></a>
148
+
149
+ Pass options to `Antares::Highlighter.new`:
150
+
151
+ | Option | Type | Default | Description |
152
+ |--------|------|---------|-------------|
153
+ | `strategy` | Symbol | `:auto` | Highlighting strategy |
154
+ | `checkpoint_interval` | Integer | `64` | Completed lines between checkpoints |
155
+ | `window_context` | Integer | `200` | Lines of context for window mode |
156
+ | `max_lines` | Integer | `100_000` | Maximum document lines |
157
+ | `max_bytes` | Integer | `8 MiB` | Maximum source snapshot |
158
+ | `max_line_bytes` | Integer | `16 KiB` | Maximum bytes per line |
159
+ | `max_checkpoint_bytes` | Integer | `256 KiB` | Maximum checkpoint payload |
160
+ | `max_seconds` | Numeric | `0.25` | Maximum time per scan |
161
+
162
+ ### Strategies
163
+
41
164
  | Strategy | Behavior |
42
- |---|---|
43
- | `:auto` | Uses the bundled, exact-Rouge-version compatibility verdict. Unknown versions and lexers use `:window`. |
44
- | `:incremental` | Forces state snapshots and convergence. Use for certified lexers; forcing a failed lexer can return incorrect classifications. |
45
- | `:window` | Restarts from root up to 200 lines before the requested range. Explicitly approximate for long-running constructs. |
46
- | `:full` | Runs ordinary Rouge over the complete bounded document after every edit; use for exact results on small files with failed incremental lexers. |
47
-
48
- `Antares.compatible?(Rouge::Lexers::Python)` returns `:incremental` or `:window`.
49
- Every registered lexer has an entry in `COMPATIBILITY` and a reproducible result
50
- in `COMPATIBILITY_DETAILS`; see the [complete compatibility table](docs/compatibility.md).
51
- Do not infer support merely because a lexer subclasses `Rouge::RegexLexer`.
52
-
53
- ## State, limits, and correctness
54
-
55
- Antares reuses Rouge's rule interpreter and deep-copies persistent instance
56
- state, including nested arrays/hashes, their subclasses, delegate lexers, heredoc
57
- queues, sets and structs. Immutable rule templates are shared. Per-call scanners
58
- and callbacks are excluded. Dynamic rule closures compare conservatively by
59
- identity, and opaque state is rejected rather than shallow-copied.
60
-
61
- Checkpoints are placed approximately every 64 completed lines, **after entire
62
- rule callbacks**. State fingerprints between checkpoints allow early convergence;
63
- edits shift cached suffixes and invalidate stale boundaries. Calling
64
- `continue_lex` separately for each line is insufficient: Rouge regexes may consume
65
- several lines or inspect surrounding text. The driver therefore materializes a
66
- bounded source String on demand, uses a fixed-anchor scanner, and resumes lexical
67
- work at a checkpoint. It retains no array of provider lines. Source materialization
68
- is O(document bytes) after an edit and is included in reported timings.
69
-
70
- Some grammars are inherently nonlocal: for example, adding a distant `=end` makes
71
- Rouge classify an earlier unmatched Ruby `=begin` differently. These lexers fail
72
- the delayed-closure probes and select `:window`; the fallback is intentional.
73
- Other failures are classified as token mismatch (lookahead/state dependencies) or
74
- unsupported custom stream driver. The matrix is an empirical corpus guarantee,
75
- not a proof for every possible input or non-default lexer option.
76
-
77
- Default limits are 100,000 lines, an 8 MiB source snapshot, 16 KiB per line,
78
- 256 KiB per checkpoint, and 250 ms per scan. Configure them with `max_lines`,
79
- `max_bytes`, `max_line_bytes`, `max_checkpoint_bytes`, and `max_seconds`.
80
- Exceeding a source limit switches to window mode; oversized individual lines and
81
- timed-out ranges return plain-text tokens without truncating content.
82
- `fallback_reason` explains the transition. These limits also apply to `:full`.
83
-
84
- ## Development and verification
85
-
86
- ```sh
165
+ |----------|----------|
166
+ | `:auto` | Uses the verdict bundled for the exact Rouge version. Unknown versions and lexers use `:window`. |
167
+ | `:incremental` | Forces state snapshots and convergence. Use only for certified lexers. |
168
+ | `:window` | Restarts from the root state up to `window_context` lines before the requested range. |
169
+ | `:full` | Runs Rouge over the complete bounded document after every edit. |
170
+
171
+ Exceeding a source limit switches to window mode. Oversized lines and timed-out
172
+ ranges return complete plain-text tokens. `fallback_reason` explains the
173
+ transition. These limits also apply to `:full`.
174
+
175
+ ## How It Works
176
+
177
+ <a name="how-it-works"></a>
178
+
179
+ 1. Source Materialization builds a bounded source string from the line provider
180
+ 2. Checkpoint Restore resumes the nearest reusable lexer state
181
+ 3. Incremental Lexing records tokens and sparse state checkpoints
182
+ 4. State Convergence reuses the untouched cached suffix after an edit
183
+ 5. Fallback Selection uses window mode when incremental lexing is not certified
184
+
185
+ Antares copies persistent Rouge lexer state, including nested collections,
186
+ delegate lexers, and heredoc queues. Checkpoints are created after complete rule
187
+ callbacks because Rouge expressions can consume multiple lines or inspect
188
+ surrounding text.
189
+
190
+ Some grammars are inherently nonlocal. For example, adding a distant `=end` can
191
+ change how Rouge classifies an earlier unmatched Ruby `=begin`. Such lexers use
192
+ window mode instead of unsafe incremental highlighting.
193
+
194
+ ## Compatibility
195
+
196
+ <a name="compatibility"></a>
197
+
198
+ `Antares.compatible?(Rouge::Lexers::Python)` returns `:incremental` or
199
+ `:window`. Every registered lexer has a bundled verdict; see the
200
+ [complete compatibility table](docs/compatibility.md).
201
+
202
+ The matrix uses three source variants from Rouge's bundled MIT-licensed demos,
203
+ applies 50 deterministic edits to each, and compares every line against a fresh
204
+ full lex. Three distant-closure probes detect backward reclassification. The
205
+ result is an empirical corpus guarantee, not a proof for every input or
206
+ non-default lexer option.
207
+
208
+ ## Development
209
+
210
+ <a name="development"></a>
211
+
212
+ ```bash
87
213
  bundle install
88
214
  bundle exec rake test
89
215
  bundle exec ruby script/compatibility --write
90
- BUDGET=1 bundle exec ruby --yjit bench/highlighting.rb
216
+ BUDGET=1 bundle exec rake bench
91
217
  ```
92
218
 
93
- The matrix enumerates every Rouge lexer, uses three source variants based on
94
- Rouge's bundled MIT-licensed demos, applies 50 deterministic insert/delete/replace
95
- mutations to each, and compares all per-line tokens against a fresh full lex.
96
- Three extra distant-closure probes catch backward reclassification. The test suite
97
- reruns the complete matrix. `MUTATIONS=5 ruby script/compatibility python rust`
98
- is a short diagnostic run; `DETAILS=1` prints the full JSON failure details.
99
- Only full runs with 50 or more mutations may replace the bundled matrix.
100
-
101
- Scheduled CI updates Rouge, regenerates the matrix, reruns tests, and uploads the
102
- generated source/table for review. It does not silently publish a gem or modify a
103
- release. Regular CI tests Ruby 3.1–4.0 on Linux, macOS and Windows.
104
-
105
- On the development Mac with Ruby 4.0.0/YJIT, the 5,000-line Python benchmark
106
- measured 10.771 ms for the first 60 lines and 1.325 ms median for a one-line edit
107
- including source materialization and convergence. One line was rescanned, with
108
- 80 checkpoints and 10,560 normalized payload bytes. Run the benchmark for current
109
- timings and retained-object memory; budgets are 15 ms / 5 ms / 2 MiB.
110
-
111
- Released under the [MIT license](LICENSE.txt).
219
+ Use `MUTATIONS=5 ruby script/compatibility python rust` for a short compatibility
220
+ run. Only full runs with at least 50 mutations may replace the bundled matrix.
221
+ CI tests Ruby 3.1 through 4.0 on Linux, macOS, and Windows.
222
+
223
+ See the [changelog](CHANGELOG.md) for release history.
224
+
225
+ ## Contributing
226
+
227
+ <a name="contributing"></a>
228
+
229
+ Bug reports and pull requests are welcome at https://github.com/noxdea/antares.
230
+
231
+ ## License
232
+
233
+ <a name="license"></a>
234
+
235
+ Released under the [MIT License](LICENSE.txt).