textbringer 24 → 25

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.
@@ -1,4 +1,5 @@
1
- require "ripper"
1
+ require "set"
2
+ require "prism"
2
3
 
3
4
  module Textbringer
4
5
  CONFIG[:ruby_indent_level] = 2
@@ -10,81 +11,6 @@ module Textbringer
10
11
  (?:Gem|Rake|Cap|Thor|Vagrant|Guard|Pod)file)\z/ix
11
12
  self.interpreter_name_pattern = /ruby/i
12
13
 
13
- define_syntax :comment, /
14
- (?: \#.*(?:\\\n.*)*(?:\z|(?<!\\)\n) ) |
15
- (?: ^=begin (?:.|\n)* (?> ^=end \b ) )
16
- /x
17
-
18
- define_syntax :keyword, /
19
- (?<![$@.]) \b (?: (?:
20
- class | module | def | undef | begin | rescue | ensure | end |
21
- if | unless | then | elsif | else | case | when | while | until |
22
- for | break | next | redo | retry | in | do | return | yield |
23
- super | self | nil | true | false | and | or | not | alias
24
- ) \b (?![!?]) | defined\? )
25
- /x
26
-
27
- define_syntax :string, /
28
- (?: (?<! [a-zA-Z] ) \?
29
- (:?
30
- [^\\\s] |
31
- \\ [0-7]{1,3} |
32
- \\x [0-9a-fA-F]{2} |
33
- \\u [0-9a-fA-F]{4} |
34
- \\u \{ [0-9a-fA-F]+ \} |
35
- \\C - . |
36
- \\M - . |
37
- \\ .
38
- )
39
- ) |
40
- (?: %[qQrwWsiIx]?\{ (?: [^\\}] | \\ . )* \} ) |
41
- (?: %[qQrwWsiIx]?\( (?: [^\\)] | \\ . )* \) ) |
42
- (?: %[qQrwWsiIx]?\[ (?: [^\\\]] | \\ . )* \] ) |
43
- (?: %[qQrwWsiIx]?< (?: [^\\>] | \\ . )* > ) |
44
- (?:
45
- %[qQrwWsiIx]?
46
- (?<string_delimiter>[^{(\[<a-zA-Z0-9\s\u{0100}-\u{10ffff}])
47
- (?: (?! \k<string_delimiter> ) [^\\] | \\ . )*
48
- \k<string_delimiter>
49
- ) |
50
- (?:
51
- (?<! \$ )
52
- " (?: [^\\"] | \\ . )* "
53
- ) |
54
- (?:
55
- (?<! \$ )
56
- ' (?: [^\\'] | \\ . )* '
57
- ) |
58
- (?:
59
- (?<! [$.] | def | def \s )
60
- ` (?: [^\\`] | \\ . )* `
61
- ) |
62
- (?:
63
- (?<=
64
- ^ |
65
- \b and | \b or | \b while | \b until | \b unless | \b if |
66
- \b elsif | \b when | \b not | \b then | \b else |
67
- [;~=!|&(,\[<>?:*+-]
68
- ) \s*
69
- \/ (?: [^\\\/] | \\ . )* \/[iomxneus]*
70
- ) |
71
- (?:
72
- (?<! class | class \s | [\]})"'.] | :: | \w )
73
- <<[\-~]?(?<heredoc_quote>['"`]?)
74
- (?<heredoc_terminator>
75
- (?> [_a-zA-Z\u{0100}-\u{10ffff}]
76
- [_a-zA-Z0-9\u{0100}-\u{10ffff}]* )
77
- )
78
- \k<heredoc_quote>
79
- (?> (?:.|\n)*? ^ [\ \t]* \k<heredoc_terminator> $ )
80
- ) |
81
- (?:
82
- (?<! : ) :
83
- [_a-zA-Z\u{0100}-\u{10ffff}]
84
- [_a-zA-Z0-9\u{0100}-\u{10ffff}]*
85
- )
86
- /x
87
-
88
14
  def comment_start
89
15
  "#"
90
16
  end
@@ -93,17 +19,28 @@ module Textbringer
93
19
  super(buffer)
94
20
  @buffer[:indent_level] = CONFIG[:ruby_indent_level]
95
21
  @buffer[:indent_tabs_mode] = CONFIG[:ruby_indent_tabs_mode]
22
+ @prism_version = nil
23
+ @prism_tokens = nil
24
+ @prism_ast = nil
25
+ @prism_method_call_locs = nil
26
+ @literal_levels = nil
27
+ @literal_levels_version = nil
96
28
  end
97
29
 
98
30
  def forward_definition(n = number_prefix_arg || 1)
99
- tokens = Ripper.lex(@buffer.to_s)
31
+ ensure_prism_tokens
32
+ tokens = @prism_tokens.filter_map { |token, _state|
33
+ type = token.type
34
+ next if type == :EOF
35
+ [token.location.start_line, type]
36
+ }
100
37
  @buffer.forward_line
101
38
  n.times do |i|
102
- tokens = tokens.drop_while { |(l, _), e, t|
39
+ tokens = tokens.drop_while { |l, type|
103
40
  l < @buffer.current_line ||
104
- e != :on_kw || /\A(?:class|module|def)\z/ !~ t
41
+ !DEFINITION_KEYWORDS.include?(type)
105
42
  }
106
- (line,), = tokens.first
43
+ line, = tokens.first
107
44
  if line.nil?
108
45
  @buffer.end_of_buffer
109
46
  break
@@ -117,14 +54,19 @@ module Textbringer
117
54
  end
118
55
 
119
56
  def backward_definition(n = number_prefix_arg || 1)
120
- tokens = Ripper.lex(@buffer.to_s).reverse
57
+ ensure_prism_tokens
58
+ tokens = @prism_tokens.filter_map { |token, _state|
59
+ type = token.type
60
+ next if type == :EOF
61
+ [token.location.start_line, type]
62
+ }.reverse
121
63
  @buffer.beginning_of_line
122
64
  n.times do |i|
123
- tokens = tokens.drop_while { |(l, _), e, t|
65
+ tokens = tokens.drop_while { |l, type|
124
66
  l >= @buffer.current_line ||
125
- e != :on_kw || /\A(?:class|module|def)\z/ !~ t
67
+ !DEFINITION_KEYWORDS.include?(type)
126
68
  }
127
- (line,), = tokens.first
69
+ line, = tokens.first
128
70
  if line.nil?
129
71
  @buffer.beginning_of_buffer
130
72
  break
@@ -175,8 +117,171 @@ module Textbringer
175
117
  end
176
118
  end
177
119
 
120
+ def highlight(ctx)
121
+ ensure_prism_tokens
122
+ return unless @prism_tokens
123
+ ensure_method_call_locs
124
+ base_pos = ctx.buffer.point_min
125
+ hl_start = ctx.highlight_start
126
+ hl_end = ctx.highlight_end
127
+ in_symbol = false
128
+ after_def = false
129
+ after_class_or_module = false
130
+ @prism_tokens.each do |token_info|
131
+ token = token_info[0]
132
+ type = token.type
133
+ offset = token.location.start_offset
134
+ length = token.location.length
135
+ pos = base_pos + offset
136
+ pos_end = pos + length
137
+ break if pos >= hl_end
138
+ if pos_end > hl_start
139
+ face_name = PRISM_TOKEN_FACES[type]
140
+ if in_symbol
141
+ face_name = :string if face_name.nil? || face_name == :constant ||
142
+ face_name == :operator
143
+ elsif after_def
144
+ face_name = :function_name if type == :IDENTIFIER ||
145
+ type == :CONSTANT || type == :METHOD_NAME ||
146
+ PRISM_TOKEN_FACES[type] == :operator
147
+ elsif face_name == :constant &&
148
+ (after_class_or_module || token.location.slice.match?(/\p{Lower}/))
149
+ face_name = :type
150
+ elsif @prism_method_call_locs.key?(offset)
151
+ face_name = :function_name
152
+ end
153
+ if face_name && (face = Face[face_name])
154
+ ctx.highlight(pos, pos_end, face)
155
+ end
156
+ end
157
+ in_symbol = type == :SYMBOL_BEGIN
158
+ after_def = type == :KEYWORD_DEF ||
159
+ (after_def && (type == :KEYWORD_SELF || type == :DOT ||
160
+ type == :NEWLINE || type == :IGNORED_NEWLINE ||
161
+ type == :COMMENT))
162
+ after_class_or_module = (type == :KEYWORD_CLASS || type == :KEYWORD_MODULE) ||
163
+ (after_class_or_module && !(type == :NEWLINE || type == :SEMICOLON))
164
+ end
165
+ end
166
+
178
167
  private
179
168
 
169
+ PRISM_TOKEN_FACES = {
170
+ # Keywords
171
+ KEYWORD_ALIAS: :keyword, KEYWORD_AND: :keyword, KEYWORD_BEGIN: :keyword,
172
+ KEYWORD_BEGIN_UPCASE: :keyword, KEYWORD_BREAK: :keyword,
173
+ KEYWORD_CASE: :keyword, KEYWORD_CLASS: :keyword, KEYWORD_DEF: :keyword,
174
+ KEYWORD_DEFINED: :keyword, KEYWORD_DO: :keyword,
175
+ KEYWORD_DO_LOOP: :keyword, KEYWORD_ELSE: :keyword,
176
+ KEYWORD_ELSIF: :keyword, KEYWORD_END: :keyword,
177
+ KEYWORD_END_UPCASE: :keyword, KEYWORD_ENSURE: :keyword,
178
+ KEYWORD_FALSE: :builtin, KEYWORD_FOR: :keyword, KEYWORD_IF: :keyword,
179
+ KEYWORD_IF_MODIFIER: :keyword, KEYWORD_IN: :keyword,
180
+ KEYWORD_MODULE: :keyword, KEYWORD_NEXT: :keyword,
181
+ KEYWORD_NIL: :builtin,
182
+ KEYWORD_NOT: :keyword, KEYWORD_OR: :keyword, KEYWORD_REDO: :keyword,
183
+ KEYWORD_RESCUE: :keyword, KEYWORD_RESCUE_MODIFIER: :keyword,
184
+ KEYWORD_RETRY: :keyword, KEYWORD_RETURN: :keyword,
185
+ KEYWORD_SELF: :builtin, KEYWORD_SUPER: :builtin,
186
+ KEYWORD_THEN: :keyword, KEYWORD_TRUE: :builtin,
187
+ KEYWORD_UNDEF: :keyword,
188
+ KEYWORD_UNLESS: :keyword, KEYWORD_UNLESS_MODIFIER: :keyword,
189
+ KEYWORD_UNTIL: :keyword, KEYWORD_UNTIL_MODIFIER: :keyword,
190
+ KEYWORD_WHEN: :keyword, KEYWORD_WHILE: :keyword,
191
+ KEYWORD_WHILE_MODIFIER: :keyword, KEYWORD_YIELD: :keyword,
192
+ KEYWORD___FILE__: :builtin, KEYWORD___LINE__: :builtin,
193
+ KEYWORD___ENCODING__: :builtin,
194
+
195
+ # Comments
196
+ COMMENT: :comment, EMBDOC_BEGIN: :comment, EMBDOC_LINE: :comment,
197
+ EMBDOC_END: :comment,
198
+
199
+ # Strings and string-like
200
+ STRING_BEGIN: :string, STRING_CONTENT: :string, STRING_END: :string,
201
+ SYMBOL_BEGIN: :string, REGEXP_BEGIN: :string, REGEXP_END: :string,
202
+ HEREDOC_START: :string, HEREDOC_END: :string,
203
+ LABEL: :property,
204
+
205
+ # Numbers
206
+ INTEGER: :number, FLOAT: :number,
207
+ INTEGER_RATIONAL: :number, FLOAT_RATIONAL: :number,
208
+ INTEGER_IMAGINARY: :number, FLOAT_IMAGINARY: :number,
209
+ INTEGER_RATIONAL_IMAGINARY: :number, FLOAT_RATIONAL_IMAGINARY: :number,
210
+
211
+ # Constants
212
+ CONSTANT: :constant,
213
+
214
+ # Variables
215
+ INSTANCE_VARIABLE: :variable, CLASS_VARIABLE: :variable,
216
+ GLOBAL_VARIABLE: :variable,
217
+
218
+ # Operators
219
+ PLUS: :operator, MINUS: :operator, STAR: :operator, SLASH: :operator,
220
+ PERCENT: :operator, STAR_STAR: :operator,
221
+ EQUAL: :operator, EQUAL_EQUAL: :operator, BANG_EQUAL: :operator,
222
+ LESS: :operator, GREATER: :operator,
223
+ LESS_EQUAL: :operator, GREATER_EQUAL: :operator,
224
+ LESS_EQUAL_GREATER: :operator, EQUAL_EQUAL_EQUAL: :operator,
225
+ EQUAL_TILDE: :operator, BANG_TILDE: :operator,
226
+ AMPERSAND_AMPERSAND: :operator, PIPE_PIPE: :operator,
227
+ BANG: :operator, TILDE: :operator,
228
+ LESS_LESS: :operator, GREATER_GREATER: :operator,
229
+ AMPERSAND: :operator, PIPE: :operator, CARET: :operator,
230
+ PLUS_EQUAL: :operator, MINUS_EQUAL: :operator,
231
+ STAR_EQUAL: :operator, SLASH_EQUAL: :operator,
232
+ PERCENT_EQUAL: :operator, STAR_STAR_EQUAL: :operator,
233
+ AMPERSAND_EQUAL: :operator, PIPE_EQUAL: :operator,
234
+ CARET_EQUAL: :operator,
235
+ AMPERSAND_AMPERSAND_EQUAL: :operator, PIPE_PIPE_EQUAL: :operator,
236
+ LESS_LESS_EQUAL: :operator, GREATER_GREATER_EQUAL: :operator,
237
+ DOT_DOT: :operator, DOT_DOT_DOT: :operator,
238
+ EQUAL_GREATER: :operator, UMINUS: :operator, UPLUS: :operator,
239
+ USTAR: :operator, USTAR_STAR: :operator, UAMPERSAND: :operator,
240
+
241
+ # Punctuation
242
+ DOT: :punctuation, COLON_COLON: :punctuation,
243
+ SEMICOLON: :punctuation, COMMA: :punctuation,
244
+ PARENTHESIS_LEFT: :punctuation, PARENTHESIS_RIGHT: :punctuation,
245
+ BRACKET_LEFT: :punctuation, BRACKET_LEFT_ARRAY: :punctuation,
246
+ BRACKET_RIGHT: :punctuation,
247
+ BRACE_LEFT: :punctuation, BRACE_RIGHT: :punctuation,
248
+ QUESTION_MARK: :punctuation, COLON: :punctuation,
249
+ LAMBDA_BEGIN: :punctuation,
250
+
251
+ # Method names (e.g. block_given?, is_a?)
252
+ METHOD_NAME: :function_name,
253
+ }.freeze
254
+
255
+ DEFINITION_KEYWORDS = %i[KEYWORD_CLASS KEYWORD_MODULE KEYWORD_DEF].to_set
256
+
257
+ LITERAL_BEGIN_TYPES = %i[STRING_BEGIN HEREDOC_START REGEXP_BEGIN EMBDOC_BEGIN].to_set
258
+ LITERAL_END_TYPES = %i[STRING_END HEREDOC_END REGEXP_END EMBDOC_END].to_set
259
+
260
+ CONTINUATION_OPERATOR_TYPES = %i[
261
+ PLUS MINUS STAR SLASH PERCENT STAR_STAR
262
+ EQUAL EQUAL_EQUAL BANG_EQUAL
263
+ LESS GREATER LESS_EQUAL GREATER_EQUAL LESS_EQUAL_GREATER
264
+ EQUAL_TILDE BANG_TILDE
265
+ AMPERSAND_AMPERSAND PIPE_PIPE
266
+ BANG TILDE
267
+ LESS_LESS GREATER_GREATER
268
+ AMPERSAND CARET
269
+ PLUS_EQUAL MINUS_EQUAL STAR_EQUAL SLASH_EQUAL PERCENT_EQUAL
270
+ STAR_STAR_EQUAL AMPERSAND_EQUAL PIPE_EQUAL CARET_EQUAL
271
+ AMPERSAND_AMPERSAND_EQUAL PIPE_PIPE_EQUAL
272
+ LESS_LESS_EQUAL GREATER_GREATER_EQUAL
273
+ EQUAL_GREATER
274
+ ].to_set
275
+
276
+ BLOCK_END = {
277
+ EMBEXPR_BEGIN: :EMBEXPR_END,
278
+ BRACE_LEFT: :BRACE_RIGHT,
279
+ PARENTHESIS_LEFT: :PARENTHESIS_RIGHT,
280
+ BRACKET_LEFT: :BRACKET_RIGHT,
281
+ BRACKET_LEFT_ARRAY: :BRACKET_RIGHT,
282
+ LAMBDA_BEGIN: :BRACE_RIGHT,
283
+ }
284
+
180
285
  INDENT_BEG_RE = /^([ \t]*)(class|module|def|if|unless|case|while|until|for|begin)\b/
181
286
 
182
287
  def space_width(s)
@@ -187,8 +292,7 @@ module Textbringer
187
292
  loop do
188
293
  @buffer.re_search_backward(INDENT_BEG_RE)
189
294
  space = @buffer.match_string(1)
190
- s = @buffer.substring(@buffer.point_min, @buffer.point)
191
- if PartialLiteralAnalyzer.in_literal?(s)
295
+ if in_literal?(@buffer.point)
192
296
  next
193
297
  end
194
298
  return space_width(space)
@@ -199,19 +303,12 @@ module Textbringer
199
303
  end
200
304
 
201
305
  def lex(source)
202
- line_count = source.count("\n")
203
- s = source
204
- lineno = 1
205
- tokens = []
206
- loop do
207
- lexer = Ripper::Lexer.new(s, "-", lineno)
208
- tokens.concat(lexer.lex)
209
- last_line = tokens.dig(-1, 0, 0)
210
- return tokens if last_line.nil? || last_line >= line_count
211
- s = source.sub(/(.*\n?){#{last_line}}/, "")
212
- return tokens if last_line + 1 <= lineno
213
- lineno = last_line + 1
214
- end
306
+ Prism.lex(source).value.filter_map { |token, _state|
307
+ type = token.type
308
+ next if type == :EOF
309
+ loc = token.location
310
+ [[loc.start_line, loc.start_column], type, token.value]
311
+ }
215
312
  end
216
313
 
217
314
  def calculate_indentation
@@ -227,19 +324,20 @@ module Textbringer
227
324
  start_line = @buffer.current_line
228
325
  tokens = lex(@buffer.substring(start_pos, bol_pos))
229
326
  _, event, text = tokens.last
230
- if event == :on_nl
327
+ if event == :NEWLINE || event == :IGNORED_NEWLINE
231
328
  _, event, text = tokens[-2]
232
329
  end
233
- if event == :on_tstring_beg ||
234
- event == :on_heredoc_beg ||
235
- event == :on_regexp_beg ||
236
- (event == :on_regexp_end && text.size > 1) ||
237
- event == :on_tstring_content
330
+ if event == :STRING_BEGIN ||
331
+ event == :HEREDOC_START ||
332
+ (event == :HEREDOC_END && text.empty?) ||
333
+ event == :REGEXP_BEGIN ||
334
+ event == :STRING_CONTENT ||
335
+ event == :HEREDOC_CONTENT
238
336
  return nil
239
337
  end
240
338
  i, extra_end_count = find_nearest_beginning_token(tokens)
241
339
  (line, column), event, = i ? tokens[i] : nil
242
- if event == :on_lparen && tokens.dig(i + 1, 1) != :on_ignored_nl
340
+ if event == :PARENTHESIS_LEFT && tokens.dig(i + 1, 1) != :IGNORED_NEWLINE
243
341
  return column + 1
244
342
  end
245
343
  if line
@@ -268,101 +366,87 @@ module Textbringer
268
366
  if @buffer.looking_at?(/[ \t]*([}\])]|(end|else|elsif|when|in|rescue|ensure)\b)/)
269
367
  indentation -= @buffer[:indent_level]
270
368
  end
271
- _, last_event, last_text = tokens.reverse_each.find { |_, e, _|
272
- e != :on_sp && e != :on_nl && e != :on_ignored_nl
369
+ _, last_event, = tokens.reverse_each.find { |_, e, _|
370
+ e != :NEWLINE && e != :IGNORED_NEWLINE
273
371
  }
274
372
  if start_with_period ||
275
- (last_event == :on_op && last_text != "|") ||
276
- (last_event == :on_kw && /\A(and|or)\z/.match?(last_text)) ||
277
- last_event == :on_period ||
278
- (last_event == :on_comma && event != :on_lbrace &&
279
- event != :on_lparen && event != :on_lbracket) ||
280
- last_event == :on_label
373
+ CONTINUATION_OPERATOR_TYPES.include?(last_event) ||
374
+ last_event == :KEYWORD_AND || last_event == :KEYWORD_OR ||
375
+ last_event == :DOT ||
376
+ (last_event == :COMMA && event != :BRACE_LEFT &&
377
+ event != :PARENTHESIS_LEFT && event != :BRACKET_LEFT &&
378
+ event != :BRACKET_LEFT_ARRAY) ||
379
+ last_event == :LABEL
281
380
  indentation += @buffer[:indent_level]
282
381
  end
283
382
  indentation
284
383
  end
285
384
  end
286
385
 
287
- BLOCK_END = {
288
- '#{' => "}",
289
- "{" => "}",
290
- "(" => ")",
291
- "[" => "]"
292
- }
293
-
294
386
  def find_nearest_beginning_token(tokens)
295
387
  stack = []
296
388
  (tokens.size - 1).downto(0) do |i|
297
389
  (line, ), event, text = tokens[i]
298
390
  case event
299
- when :on_kw
300
- _, prev_event, _ = tokens[i - 1]
301
- next if prev_event == :on_symbeg
302
- case text
303
- when "class", "module", "def", "if", "unless", "case",
304
- "do", "for", "while", "until", "begin"
305
- if /\A(if|unless|while|until)\z/.match?(text) &&
306
- modifier?(tokens, i)
307
- next
308
- end
309
- if text == "def" && endless_method_def?(tokens, i)
310
- next
311
- end
312
- if stack.empty?
313
- return i
314
- end
315
- if stack.last != "end"
316
- raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
317
- end
318
- stack.pop
319
- when "end"
320
- stack.push(text)
391
+ when :KEYWORD_CLASS, :KEYWORD_MODULE, :KEYWORD_DEF,
392
+ :KEYWORD_IF, :KEYWORD_UNLESS, :KEYWORD_CASE,
393
+ :KEYWORD_DO, :KEYWORD_DO_LOOP, :KEYWORD_FOR,
394
+ :KEYWORD_WHILE, :KEYWORD_UNTIL, :KEYWORD_BEGIN
395
+ if i > 0
396
+ _, prev_event, _ = tokens[i - 1]
397
+ next if prev_event == :SYMBOL_BEGIN
398
+ end
399
+ if event == :KEYWORD_DEF && endless_method_def?(tokens, i)
400
+ next
321
401
  end
322
- when :on_rbrace, :on_rparen, :on_rbracket, :on_embexpr_end
323
- stack.push(text)
324
- when :on_lbrace, :on_lparen, :on_lbracket, :on_tlambeg, :on_embexpr_beg
325
402
  if stack.empty?
326
403
  return i
327
404
  end
328
- if stack.last != BLOCK_END[text]
405
+ if stack.last != :KEYWORD_END
406
+ raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
407
+ end
408
+ stack.pop
409
+ when :KEYWORD_END
410
+ if i > 0
411
+ _, prev_event, _ = tokens[i - 1]
412
+ next if prev_event == :SYMBOL_BEGIN
413
+ end
414
+ stack.push(:KEYWORD_END)
415
+ when :BRACE_RIGHT, :PARENTHESIS_RIGHT, :BRACKET_RIGHT, :EMBEXPR_END
416
+ stack.push(event)
417
+ when :BRACE_LEFT, :PARENTHESIS_LEFT, :BRACKET_LEFT,
418
+ :BRACKET_LEFT_ARRAY, :LAMBDA_BEGIN, :EMBEXPR_BEGIN
419
+ if stack.empty?
420
+ return i
421
+ end
422
+ if stack.last != BLOCK_END[event]
329
423
  raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
330
424
  end
331
425
  stack.pop
332
426
  end
333
427
  end
334
- return nil, stack.grep_v(/[)\]]/).size
335
- end
336
-
337
- def modifier?(tokens, i)
338
- (line,), = tokens[i]
339
- ts = tokens[0...i].reverse_each.take_while { |(l,_),| l == line }
340
- t = ts.find { |_, e| e != :on_sp }
341
- t && !(t[1] == :on_op && t[2] == "=")
428
+ return nil, stack.count { |t| t != :PARENTHESIS_RIGHT && t != :BRACKET_RIGHT }
342
429
  end
343
430
 
344
431
  def endless_method_def?(tokens, i)
345
432
  ts = tokens.drop(i + 1)
346
- ts.shift while ts[0][1] == :on_sp
347
433
  _, event = ts.shift
348
- return false if event != :on_ident
349
- ts.shift while ts[0][1] == :on_sp
350
- if ts[0][1] == :on_lparen
434
+ return false if event != :IDENTIFIER && event != :METHOD_NAME
435
+ if ts[0][1] == :PARENTHESIS_LEFT
351
436
  ts.shift
352
437
  count = 1
353
438
  while count > 0
354
439
  _, event = ts.shift
355
440
  return false if event.nil?
356
441
  case event
357
- when :on_lparen
358
- count +=1
359
- when :on_rparen
360
- count -=1
442
+ when :PARENTHESIS_LEFT
443
+ count += 1
444
+ when :PARENTHESIS_RIGHT
445
+ count -= 1
361
446
  end
362
447
  end
363
- ts.shift while ts[0][1] == :on_sp
364
448
  end
365
- ts[0][1] == :on_op && ts[0][2] == "="
449
+ ts[0][1] == :EQUAL
366
450
  rescue NoMethodError # no token
367
451
  return false
368
452
  end
@@ -395,31 +479,64 @@ module Textbringer
395
479
  nil
396
480
  end
397
481
 
398
- class PartialLiteralAnalyzer < Ripper
399
- def self.in_literal?(src)
400
- new(src).in_literal?
401
- end
482
+ def in_literal?(byte_offset)
483
+ ensure_prism_tokens
484
+ ensure_literal_levels
485
+ return false if @literal_levels.empty?
486
+ i = @literal_levels.bsearch_index { |offset, _| offset > byte_offset }
487
+ i = i ? i - 1 : @literal_levels.size - 1
488
+ return false if i < 0
489
+ @literal_levels[i][1] > 0
490
+ end
402
491
 
403
- def in_literal?
404
- @literal_level = 0
405
- parse
406
- @literal_level > 0
492
+ def ensure_prism_tokens
493
+ return if @prism_version == @buffer.version
494
+ source = @buffer.to_s
495
+ if source.valid_encoding?
496
+ result = Prism.parse_lex(source)
497
+ @prism_ast, @prism_tokens = result.value
498
+ else
499
+ @prism_ast = nil
500
+ @prism_tokens = []
407
501
  end
502
+ @prism_method_call_locs = nil
503
+ @prism_version = @buffer.version
504
+ @literal_levels_version = nil
505
+ end
408
506
 
409
- private
507
+ def ensure_method_call_locs
508
+ return if @prism_method_call_locs
509
+ @prism_method_call_locs = {}
510
+ return unless @prism_ast
511
+ collect_method_call_locs(@prism_ast)
512
+ end
410
513
 
411
- %w(embdoc heredoc tstring regexp
412
- symbols qsymbols words qwords).each do |name|
413
- define_method("on_#{name}_beg") do |token|
414
- @literal_level += 1
514
+ def collect_method_call_locs(node)
515
+ if node.is_a?(Prism::CallNode) && node.message_loc
516
+ name_str = node.name.to_s
517
+ unless name_str.match?(/\A[^a-zA-Z_]/) || name_str.end_with?("@")
518
+ loc = node.message_loc
519
+ @prism_method_call_locs[loc.start_offset] = true
415
520
  end
416
521
  end
522
+ node.compact_child_nodes.each { |child| collect_method_call_locs(child) }
523
+ end
417
524
 
418
- %w(embdoc heredoc tstring regexp).each do |name|
419
- define_method("on_#{name}_end") do |token|
420
- @literal_level -= 1
525
+ def ensure_literal_levels
526
+ return if @literal_levels_version == @prism_version
527
+ level = 0
528
+ @literal_levels = []
529
+ @prism_tokens&.each do |token, _state|
530
+ type = token.type
531
+ if LITERAL_BEGIN_TYPES.include?(type)
532
+ level += 1
533
+ elsif LITERAL_END_TYPES.include?(type)
534
+ next if type == :HEREDOC_END && token.value.empty?
535
+ level -= 1
421
536
  end
537
+ @literal_levels << [token.location.start_offset, level]
422
538
  end
539
+ @literal_levels_version = @prism_version
423
540
  end
424
541
  end
425
542
  end