antares 0.1.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.
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module Antares
6
+ class Highlighter
7
+ STRATEGIES = %i[auto incremental window full].freeze
8
+ attr_reader :strategy, :requested_strategy, :frontier, :checkpoints, :last_scanned_lines, :fallback_reason
9
+
10
+ def initialize(lexer:, lines:, line_count:, strategy: :auto, checkpoint_interval: 64,
11
+ window_context: 200, max_bytes: 8 * 1024 * 1024, max_lines: 100_000,
12
+ max_line_bytes: 16_384, max_checkpoint_bytes: 256 * 1024, max_seconds: 0.25)
13
+ raise ArgumentError, "unknown strategy #{strategy.inspect}" unless STRATEGIES.include?(strategy)
14
+ raise ArgumentError, "lines and line_count must be callable" unless lines.respond_to?(:call) && line_count.respond_to?(:call)
15
+ {checkpoint_interval: checkpoint_interval, max_bytes: max_bytes, max_lines: max_lines,
16
+ max_line_bytes: max_line_bytes, max_checkpoint_bytes: max_checkpoint_bytes}.each do |name, value|
17
+ raise ArgumentError, "#{name} must be a positive integer" unless value.is_a?(Integer) && value.positive?
18
+ end
19
+ raise ArgumentError, "window_context must be nonnegative" unless window_context.is_a?(Integer) && window_context >= 0
20
+ raise ArgumentError, "max_seconds must be positive" unless max_seconds.is_a?(Numeric) && max_seconds.positive?
21
+ @template = lexer
22
+ @lines, @line_count = lines, line_count
23
+ @requested_strategy = strategy
24
+ @strategy = strategy == :auto ? Antares.compatible?(lexer) : strategy
25
+ @checkpoint_interval, @window_context = checkpoint_interval, window_context
26
+ @max_bytes, @max_lines, @max_line_bytes = max_bytes, max_lines, max_line_bytes
27
+ @max_checkpoint_bytes, @max_seconds = max_checkpoint_bytes, max_seconds
28
+ @tokens, @fingerprints, @checkpoints = {}, {}, {}
29
+ @frontier = @last_scanned_lines = 0
30
+ @count = count
31
+ @dirty_until = 0
32
+ initialize_lexer
33
+ rescue UnsupportedLexerError => error
34
+ raise unless strategy == :auto || strategy == :window || strategy == :full
35
+ @template = lexer
36
+ @strategy = :window if strategy == :auto
37
+ @fallback_reason = error.message
38
+ end
39
+
40
+ def tokens_for(index)
41
+ validate_index(index)
42
+ advance(until_line: index) unless @tokens.key?(index) && index < frontier
43
+ @tokens.fetch(index) { plain(index) }
44
+ end
45
+
46
+ def tokens_in(range)
47
+ indexes = range.to_a
48
+ return [] if indexes.empty?
49
+ indexes.each { |index| validate_index(index) }
50
+ advance(until_line: indexes.max, from_line: indexes.min)
51
+ indexes.map { |index| @tokens.fetch(index) { plain(index) } }
52
+ end
53
+
54
+ # Provider contents must already reflect this line edit. Indices are zero based.
55
+ def edit(from_line:, removed:, inserted:)
56
+ values = [from_line, removed, inserted]
57
+ raise ArgumentError, "edit fields must be nonnegative integers" unless values.all? { |value| value.is_a?(Integer) && value >= 0 }
58
+ raise RangeError, "edit outside previous document" unless from_line <= @count && from_line + removed <= @count
59
+ updated_count = count
60
+ raise ArgumentError, "line_count disagrees with edit" unless updated_count == @count - removed + inserted
61
+ @last_scanned_lines = 0
62
+ if strategy == :incremental
63
+ @tokens.delete_if { |line, _| line >= frontier } if @old_fingerprints
64
+ start = @checkpoints.keys.select { |line| line <= from_line && line <= frontier }.max || 0
65
+ restart = @checkpoints.fetch(start)
66
+ shift_cache(@tokens, from_line, removed, inserted)
67
+ shift_cache(@fingerprints, from_line, removed, inserted)
68
+ shift_cache(@checkpoints, from_line, removed, inserted)
69
+ @old_fingerprints = @fingerprints.dup
70
+ @old_checkpoints = @checkpoints.dup
71
+ @fingerprints.delete_if { |line, _| line > start }
72
+ @checkpoints.delete_if { |line, _| line > start }
73
+ @checkpoints[start] = restart
74
+ @frontier = start
75
+ @dirty_until = from_line + inserted
76
+ @lexer = @checkpoints.fetch(start).restore
77
+ else
78
+ @tokens.clear
79
+ @frontier = 0
80
+ end
81
+ @count = updated_count
82
+ @source = @offsets = @driver = nil
83
+ self
84
+ end
85
+
86
+ # frontier is the first line not yet proven current (an exclusive boundary).
87
+ def advance(until_line:, from_line: nil)
88
+ return self if @count.zero?
89
+ validate_index(until_line)
90
+ @last_scanned_lines = 0
91
+ return self if until_line < frontier && @tokens.key?(until_line) &&
92
+ (from_line.nil? || (from_line..until_line).all? { |line| @tokens.key?(line) })
93
+ Timeout.timeout(@max_seconds) do
94
+ if @count > @max_lines
95
+ fallback!(:window, "document exceeds #{@max_lines} lines")
96
+ end
97
+ case strategy
98
+ when :incremental then advance_incremental(until_line)
99
+ when :full then advance_full
100
+ when :window then advance_window(from_line || until_line, until_line)
101
+ end
102
+ end
103
+ self
104
+ rescue ResourceLimitError, UnsupportedLexerError => error
105
+ raise if requested_strategy == :incremental && error.is_a?(UnsupportedLexerError)
106
+ fallback!(:window, error.message)
107
+ advance_window(from_line || until_line, until_line)
108
+ self
109
+ rescue Timeout::Error
110
+ fallback!(:window, "lexing exceeded #{@max_seconds} seconds")
111
+ ((from_line || until_line)..until_line).each { |index| @tokens[index] = plain(index) }
112
+ @frontier = until_line + 1
113
+ self
114
+ end
115
+
116
+ def checkpoint_bytes = checkpoints.values.sum(&:bytesize)
117
+
118
+ private
119
+
120
+ def initialize_lexer
121
+ @lexer = fresh_lexer
122
+ if strategy == :incremental
123
+ raise UnsupportedLexerError, "#{@lexer.class.tag} has a custom stream driver" unless LexerDriver.supported?(@lexer)
124
+ @checkpoints[0] = LexerStateSnapshot.new(@lexer, max_bytes: @max_checkpoint_bytes)
125
+ end
126
+ end
127
+
128
+ def fresh_lexer
129
+ lexer = LexerStateSnapshot.copy(@template)
130
+ lexer.reset!
131
+ lexer
132
+ rescue UnsupportedLexerError
133
+ @template.class.new(@template.options).tap(&:reset!)
134
+ end
135
+
136
+ def count
137
+ value = @line_count.call
138
+ raise ArgumentError, "line_count must return a nonnegative integer" unless value.is_a?(Integer) && value >= 0
139
+ value
140
+ end
141
+
142
+ def validate_index(index)
143
+ raise RangeError, "line outside document" unless index.is_a?(Integer) && index >= 0 && index < @count
144
+ end
145
+
146
+ def source_line(index)
147
+ value = @lines.call(index)
148
+ raise TypeError, "line provider must return a String" unless value.is_a?(String)
149
+ raise EncodingError, "line provider must return valid UTF-8" unless value.valid_encoding? && [Encoding::UTF_8, Encoding::US_ASCII].include?(value.encoding)
150
+ raise ArgumentError, "line provider returned multiple logical lines" if value.count("\n") > 1 || (value.include?("\n") && !value.end_with?("\n"))
151
+ # Providers may omit separators, but an empty final line remains empty.
152
+ index < @count - 1 && !value.end_with?("\n") ? value + "\n" : value
153
+ end
154
+
155
+ def build_source
156
+ return if @source
157
+ source = +""
158
+ offsets = [0]
159
+ @count.times do |index|
160
+ line = source_line(index)
161
+ raise ResourceLimitError, "line exceeds #{@max_line_bytes} bytes" if line.bytesize > @max_line_bytes
162
+ raise ResourceLimitError, "document exceeds #{@max_bytes} bytes" if source.bytesize + line.bytesize > @max_bytes
163
+ source << line
164
+ offsets << source.bytesize
165
+ end
166
+ @source, @offsets = source.freeze, offsets.freeze
167
+ end
168
+
169
+ def advance_incremental(until_line)
170
+ build_source
171
+ @driver ||= LexerDriver.new(@lexer, @source, @offsets, start_line: frontier) do |line, tokens|
172
+ @tokens[line] = tokens
173
+ end
174
+ before = @driver.scanned_lines
175
+ @driver.advance(until_line: until_line) do |line, lexer|
176
+ fingerprint = LexerStateSnapshot.fingerprint(lexer)
177
+ previous = @old_fingerprints&.[](line)
178
+ @fingerprints[line] = fingerprint
179
+ @frontier = line
180
+ if line >= @dirty_until && previous == fingerprint && @tokens.key?(line)
181
+ # Only the declared edited interval changed. Equal state at an aligned
182
+ # old boundary makes the untouched cached suffix reusable.
183
+ @frontier += 1 while @tokens.key?(@frontier)
184
+ @fingerprints.merge!(@old_fingerprints.select { |position, _| position >= line && position <= frontier })
185
+ @checkpoints.merge!(@old_checkpoints.select { |position, _| position >= line && position <= frontier })
186
+ @old_fingerprints = @old_checkpoints = nil
187
+ frontier > until_line
188
+ else
189
+ checkpoint = @checkpoints.keys.select { |position| position < line }.max || 0
190
+ if line - checkpoint >= @checkpoint_interval || line == @count || @checkpoints.key?(line)
191
+ @checkpoints[line] = LexerStateSnapshot.new(lexer, max_bytes: @max_checkpoint_bytes)
192
+ end
193
+ false
194
+ end
195
+ end
196
+ @last_scanned_lines = @driver.scanned_lines - before
197
+ @frontier = [@frontier, @driver.line].max
198
+ @old_fingerprints = @old_checkpoints = nil if frontier >= @count
199
+ end
200
+
201
+ def advance_full
202
+ build_source
203
+ @tokens = split_tokens(fresh_lexer.lex(@source), 0)
204
+ @frontier = @count
205
+ @last_scanned_lines = @count
206
+ end
207
+
208
+ def advance_window(first, last)
209
+ Timeout.timeout(@max_seconds) { window_tokens(first, last) }
210
+ rescue Timeout::Error
211
+ @fallback_reason = "window lexing exceeded #{@max_seconds} seconds"
212
+ (first..last).each { |index| @tokens[index] = plain(index) }
213
+ @frontier = last + 1
214
+ end
215
+
216
+ def window_tokens(first, last)
217
+ first = [first - @window_context, 0].max
218
+ source = +""
219
+ selected = []
220
+ (first..last).each do |index|
221
+ line = source_line(index)
222
+ if line.bytesize > @max_line_bytes || source.bytesize + line.bytesize > @max_bytes
223
+ @tokens[index] = plain(index)
224
+ else
225
+ selected << index
226
+ source << line
227
+ end
228
+ end
229
+ unless selected.empty?
230
+ rows = split_tokens(fresh_lexer.lex(source), 0)
231
+ selected.each_with_index { |index, offset| @tokens[index] = rows.fetch(offset, [].freeze) }
232
+ end
233
+ @frontier = last + 1
234
+ @last_scanned_lines = selected.length
235
+ end
236
+
237
+ def split_tokens(tokens, first)
238
+ result = {first => []}
239
+ line = first
240
+ tokens.each do |type, value|
241
+ next if value.empty?
242
+ unless value.include?("\n")
243
+ row = result[line] ||= []
244
+ row.last&.first == type ? row.last[1] << value : row << [type, value.dup]
245
+ next
246
+ end
247
+ value.each_line do |part|
248
+ row = result[line] ||= []
249
+ row.last&.first == type ? row.last[1] << part : row << [type, part.dup]
250
+ line += 1 if part.end_with?("\n")
251
+ end
252
+ end
253
+ result.transform_values { |row| row.each { |pair| pair.last.freeze; pair.freeze }.freeze }
254
+ end
255
+
256
+ def plain(index)
257
+ value = source_line(index)
258
+ value.empty? ? [].freeze : [[Rouge::Token::Tokens::Text, value.dup.freeze].freeze].freeze
259
+ end
260
+
261
+ def fallback!(strategy, reason)
262
+ @strategy, @fallback_reason = strategy, reason
263
+ @driver = @source = @offsets = nil
264
+ @tokens.clear
265
+ @checkpoints.clear
266
+ @fingerprints.clear
267
+ @old_fingerprints = @old_checkpoints = nil
268
+ @frontier = 0
269
+ end
270
+
271
+ def shift_cache(cache, first, removed, inserted)
272
+ delta = inserted - removed
273
+ shifted = {}
274
+ cache.each do |line, value|
275
+ if line < first
276
+ shifted[line] = value
277
+ elsif line >= first + removed
278
+ shifted[line + delta] = value
279
+ end
280
+ end
281
+ cache.replace(shifted)
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module Antares
6
+ # Reuses Rouge's own rule interpreter. Supplying the complete bounded source
7
+ # preserves cross-line matches, fixed anchors and lookbehind at checkpoints.
8
+ class LexerDriver
9
+ attr_reader :lexer, :line, :scanner, :scanned_lines
10
+
11
+ def self.supported?(lexer)
12
+ lexer.is_a?(Rouge::RegexLexer) && lexer.method(:stream_tokens).owner == Rouge::RegexLexer
13
+ end
14
+
15
+ def initialize(lexer, source, offsets, start_line: 0, &emit)
16
+ raise UnsupportedLexerError, "#{lexer.class.tag} has a custom stream driver" unless self.class.supported?(lexer)
17
+ @lexer = lexer
18
+ @source = source
19
+ @offsets = offsets
20
+ @line = start_line
21
+ @scanned_lines = 0
22
+ @emit = emit
23
+ @tokens = []
24
+ @scanner = StringScanner.new(source, fixed_anchor: true)
25
+ @scanner.pos = offsets.fetch(start_line)
26
+ lexer.instance_variable_set(:@current_stream, scanner)
27
+ lexer.instance_variable_set(:@output_stream, method(:token).to_proc)
28
+ lexer.instance_variable_set(:@states, lexer.class.states)
29
+ lexer.instance_variable_set(:@null_steps, 0)
30
+ end
31
+
32
+ # Checkpoint only after an entire Rouge rule callback, never inside a yield:
33
+ # callbacks frequently emit a token before updating their persistent state.
34
+ def advance(until_line:)
35
+ until scanner.eos? || line > until_line
36
+ before = scanner.pos
37
+ success = lexer.step(lexer.state, scanner)
38
+ token(Rouge::Token::Tokens::Error, scanner.getch) unless success
39
+ if scanner.pos > before && @tokens.empty? && scanner.pos == @offsets[line]
40
+ return if block_given? && yield(line, lexer)
41
+ end
42
+ end
43
+ if scanner.eos?
44
+ finish_line unless @tokens.empty?
45
+ finish_line while line < @offsets.length - 1
46
+ yield(line, lexer) if block_given?
47
+ end
48
+ self
49
+ end
50
+
51
+ private
52
+
53
+ def token(type, value)
54
+ return if value.nil? || value.empty?
55
+ value.each_line do |part|
56
+ if @tokens.last&.first == type
57
+ @tokens.last[1] << part
58
+ else
59
+ @tokens << [type, part.dup]
60
+ end
61
+ finish_line if part.end_with?("\n")
62
+ end
63
+ end
64
+
65
+ def finish_line
66
+ @emit.call(line, @tokens.map { |pair| pair[1].freeze; pair.freeze }.freeze)
67
+ @tokens = []
68
+ @line += 1
69
+ @scanned_lines += 1
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "set"
5
+
6
+ module Antares
7
+ # The RegexLexer engine replaces these on every scan; retaining them would
8
+ # also retain a whole source String and an output callback in every checkpoint.
9
+ class LexerStateSnapshot
10
+ VOLATILE = %i[@current_stream @output_stream @states @null_steps].freeze
11
+ attr_reader :fingerprint, :bytesize
12
+
13
+ def initialize(lexer, max_bytes: 256 * 1024)
14
+ @value = self.class.copy(lexer)
15
+ normalized = Marshal.dump(self.class.normalize(@value))
16
+ raise ResourceLimitError, "lexer checkpoint exceeds #{max_bytes} bytes" if normalized.bytesize > max_bytes
17
+ @bytesize = normalized.bytesize
18
+ @fingerprint = Digest::SHA256.digest(normalized).freeze
19
+ freeze
20
+ end
21
+
22
+ def restore = self.class.copy(@value)
23
+
24
+ def self.fingerprint(value)
25
+ Digest::SHA256.digest(Marshal.dump(normalize(value)))
26
+ end
27
+
28
+ def self.variables(value)
29
+ names = value.instance_variables.sort
30
+ value.is_a?(Rouge::Lexer) ? names - VOLATILE : names
31
+ end
32
+
33
+ def self.immutable?(value)
34
+ value.nil? || value == true || value == false || value.is_a?(Numeric) || value.is_a?(Symbol) ||
35
+ value.is_a?(Module) || value.is_a?(Regexp) || value.is_a?(Encoding) || value.is_a?(Rouge::RegexLexer::State)
36
+ end
37
+
38
+ def self.copy(value, memo = {})
39
+ return value if immutable?(value)
40
+ return memo[value.object_id] if memo.key?(value.object_id)
41
+ raise UnsupportedLexerError, "lexer state graph exceeds 10000 objects" if memo.length >= 10_000
42
+ case value
43
+ when String
44
+ memo[value.object_id] = value.dup
45
+ when Array
46
+ result = memo[value.object_id] = value.dup.clear
47
+ value.each { |item| result << copy(item, memo) }
48
+ variables(value).each { |name| result.instance_variable_set(name, copy(value.instance_variable_get(name), memo)) }
49
+ result
50
+ when Hash
51
+ raise UnsupportedLexerError, "state Hash has a closure default" if value.default_proc
52
+ result = memo[value.object_id] = value.dup.clear
53
+ result.default = copy(value.default, memo)
54
+ value.each { |key, item| result[copy(key, memo)] = copy(item, memo) }
55
+ variables(value).each { |name| result.instance_variable_set(name, copy(value.instance_variable_get(name), memo)) }
56
+ result
57
+ when Set
58
+ result = memo[value.object_id] = value.dup.clear
59
+ value.each { |item| result.add(copy(item, memo)) }
60
+ variables(value).each { |name| result.instance_variable_set(name, copy(value.instance_variable_get(name), memo)) }
61
+ result
62
+ when Struct
63
+ result = memo[value.object_id] = value.dup
64
+ value.each_pair { |name, item| result[name] = copy(item, memo) }
65
+ result
66
+ when Proc, Method, IO, StringScanner, MatchData
67
+ raise UnsupportedLexerError, "cannot safely snapshot #{value.class}"
68
+ else
69
+ names = variables(value)
70
+ raise UnsupportedLexerError, "opaque state object #{value.class}" if names.empty? && !value.is_a?(Rouge::Lexer)
71
+ result = memo[value.object_id] = value.class.allocate
72
+ names.each { |name| result.instance_variable_set(name, copy(value.instance_variable_get(name), memo)) }
73
+ result
74
+ end
75
+ end
76
+
77
+ def self.normalize(value, memo = {})
78
+ case value
79
+ when nil, true, false, Numeric, Symbol then value
80
+ when String then [:string, value.encoding.name, value]
81
+ when Regexp then [:regexp, value.source, value.options]
82
+ when Module then [:module, value.name]
83
+ when Encoding then [:encoding, value.name]
84
+ when Rouge::RegexLexer::State
85
+ # Static states are immutable rule templates. Dynamic states contain
86
+ # closures, so identity is conservative: never converge distinct ones.
87
+ [:state, value.name.is_a?(Symbol) ? value.name : value.object_id]
88
+ else
89
+ return [:reference, memo[value.object_id]] if memo.key?(value.object_id)
90
+ raise UnsupportedLexerError, "lexer state graph exceeds 10000 objects" if memo.length >= 10_000
91
+ memo[value.object_id] = memo.length
92
+ case value
93
+ when Array then [value.class.name, value.map { |item| normalize(item, memo) }, variables(value).map { |name| [name, normalize(value.instance_variable_get(name), memo)] }]
94
+ when Hash
95
+ raise UnsupportedLexerError, "state Hash has a closure default" if value.default_proc
96
+ [value.class.name, normalize(value.default, memo), value.map { |key, item| [normalize(key, memo), normalize(item, memo)] }, variables(value).map { |name| [name, normalize(value.instance_variable_get(name), memo)] }]
97
+ when Set then [value.class.name, value.map { |item| normalize(item, memo) }, variables(value).map { |name| [name, normalize(value.instance_variable_get(name), memo)] }]
98
+ when Struct then [value.class.name, value.each_pair.map { |name, item| [name, normalize(item, memo)] }]
99
+ else
100
+ raise UnsupportedLexerError, "cannot fingerprint #{value.class}" if [Proc, Method, IO, StringScanner, MatchData].any? { |klass| value.is_a?(klass) }
101
+ [value.class.name, variables(value).map { |name| [name, normalize(value.instance_variable_get(name), memo)] }]
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antares
4
+ VERSION = "0.1.0"
5
+ end
data/lib/antares.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "antares/version"
4
+ require "rouge"
5
+
6
+ module Antares
7
+ class Error < StandardError; end
8
+ class UnsupportedLexerError < Error; end
9
+ class ResourceLimitError < Error; end
10
+
11
+ def self.compatible?(lexer)
12
+ klass = lexer.is_a?(Class) ? lexer : lexer.class
13
+ return :window unless Rouge.version == COMPATIBILITY_VERSION
14
+ COMPATIBILITY.fetch(klass.tag, :window)
15
+ end
16
+ end
17
+
18
+ require_relative "antares/compatibility"
19
+ require_relative "antares/lexer_state_snapshot"
20
+ require_relative "antares/lexer_driver"
21
+ require_relative "antares/highlighter"
data/sig/antares.rbs ADDED
@@ -0,0 +1,47 @@
1
+ module Antares
2
+ VERSION: String
3
+ COMPATIBILITY_VERSION: String
4
+ COMPATIBILITY: Hash[String, :incremental | :window]
5
+ COMPATIBILITY_DETAILS: Hash[String, Hash[Symbol, untyped]]
6
+
7
+ class Error < StandardError
8
+ end
9
+ class UnsupportedLexerError < Error
10
+ end
11
+ class ResourceLimitError < Error
12
+ end
13
+
14
+ type strategy = :auto | :incremental | :window | :full
15
+ type token = [Class, String]
16
+ type row = Array[token]
17
+
18
+ def self.compatible?: (untyped lexer) -> (:incremental | :window)
19
+
20
+ class LexerStateSnapshot
21
+ attr_reader fingerprint: String
22
+ attr_reader bytesize: Integer
23
+ def initialize: (untyped lexer, ?max_bytes: Integer) -> void
24
+ def restore: () -> untyped
25
+ def self.copy: (untyped value, ?Hash[Integer, untyped] memo) -> untyped
26
+ def self.fingerprint: (untyped value) -> String
27
+ end
28
+
29
+ class Highlighter
30
+ attr_reader strategy: strategy
31
+ attr_reader requested_strategy: strategy
32
+ attr_reader frontier: Integer
33
+ attr_reader last_scanned_lines: Integer
34
+ attr_reader fallback_reason: String?
35
+ attr_reader checkpoints: Hash[Integer, LexerStateSnapshot]
36
+
37
+ def initialize: (lexer: untyped, lines: ^(Integer) -> String, line_count: ^() -> Integer,
38
+ ?strategy: strategy, ?checkpoint_interval: Integer, ?window_context: Integer,
39
+ ?max_bytes: Integer, ?max_lines: Integer, ?max_line_bytes: Integer,
40
+ ?max_checkpoint_bytes: Integer, ?max_seconds: Numeric) -> void
41
+ def tokens_for: (Integer index) -> row
42
+ def tokens_in: (Range[Integer] range) -> Array[row]
43
+ def edit: (from_line: Integer, removed: Integer, inserted: Integer) -> self
44
+ def advance: (until_line: Integer, ?from_line: Integer?) -> self
45
+ def checkpoint_bytes: () -> Integer
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: antares
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rouge
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ email:
27
+ - t.yudai92@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - CHANGELOG.md
33
+ - LICENSE.txt
34
+ - README.md
35
+ - lib/antares.rb
36
+ - lib/antares/compatibility.rb
37
+ - lib/antares/highlighter.rb
38
+ - lib/antares/lexer_driver.rb
39
+ - lib/antares/lexer_state_snapshot.rb
40
+ - lib/antares/version.rb
41
+ - sig/antares.rbs
42
+ homepage: https://github.com/noxdea/antares
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ source_code_uri: https://github.com/noxdea/antares
47
+ changelog_uri: https://github.com/noxdea/antares/blob/main/CHANGELOG.md
48
+ allowed_push_host: https://rubygems.org
49
+ rubygems_mfa_required: 'true'
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3.1'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 4.0.19
65
+ specification_version: 4
66
+ summary: Incremental highlighting for Rouge lexers
67
+ test_files: []