ruby-lsp 0.23.1 → 0.23.2

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: d4a7b6b44dae357ccbc6a9f6494bd892d890bebf1287aa6c989ef4f2aee14e82
4
- data.tar.gz: cbfc554b33a2cf45663ab394e4fb81e27eb2ec4309907943e3fe67d591222d63
3
+ metadata.gz: 5d295233e64f7b79aa061f243c61c6d5bfb69cbdc5dac64474106473d7033bc8
4
+ data.tar.gz: 559fbc2fb40b2a8043fefbf332cb4bf94ce8e3953299d6740d1d21034b79e92b
5
5
  SHA512:
6
- metadata.gz: bee0f714bd2f2865eeefa2de4eeb2f31bbf55d1fb106cb2a19af208bfc8c868815e2fcad86dcd58a119c9494c6a8254b01fcacdbeb02ce4379a8242ad94d2fcb
7
- data.tar.gz: 690d71bce5781f0de3ff170782d76e1d6ef34afd6801525c9ebbb92cfc64c2b9adc25b591710eaf4100c138706a8a358bfe6959d133a0da96e7cdd16279549c7
6
+ metadata.gz: 52e0176c0e9516801f94d9bd387ad6acfcd640c500443527448b69f3b55e2627aafa3bea75a60e131c21c0b06b309aba28a98b263344d20ce6406e1e66b69a37
7
+ data.tar.gz: 52b47878d9c5a71dadd618a5562a30901abf3fe167f71e08d92f1a5418710a4e360043ae08d553975ad7008d7f1fb657a9b650d05a311ddcfc92157a7283ec6c
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.23.1
1
+ 0.23.2
@@ -24,7 +24,7 @@ module RubyIndexer
24
24
  @index = index
25
25
  @uri = uri
26
26
  @enhancements = T.let(Enhancement.all(self), T::Array[Enhancement])
27
- @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
27
+ @visibility_stack = T.let([VisibilityScope.public_scope], T::Array[VisibilityScope])
28
28
  @comments_by_line = T.let(
29
29
  parse_result.comments.to_h do |c|
30
30
  [c.location.start_line, c]
@@ -64,7 +64,6 @@ module RubyIndexer
64
64
  :on_constant_path_or_write_node_enter,
65
65
  :on_constant_path_operator_write_node_enter,
66
66
  :on_constant_path_and_write_node_enter,
67
- :on_constant_or_write_node_enter,
68
67
  :on_constant_write_node_enter,
69
68
  :on_constant_or_write_node_enter,
70
69
  :on_constant_and_write_node_enter,
@@ -138,7 +137,7 @@ module RubyIndexer
138
137
 
139
138
  sig { params(node: Prism::SingletonClassNode).void }
140
139
  def on_singleton_class_node_enter(node)
141
- @visibility_stack.push(Entry::Visibility::PUBLIC)
140
+ @visibility_stack.push(VisibilityScope.public_scope)
142
141
 
143
142
  current_owner = @owner_stack.last
144
143
 
@@ -280,15 +279,15 @@ module RubyIndexer
280
279
  when :include, :prepend, :extend
281
280
  handle_module_operation(node, message)
282
281
  when :public
283
- @visibility_stack.push(Entry::Visibility::PUBLIC)
282
+ @visibility_stack.push(VisibilityScope.public_scope)
284
283
  when :protected
285
- @visibility_stack.push(Entry::Visibility::PROTECTED)
284
+ @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PROTECTED))
286
285
  when :private
287
- @visibility_stack.push(Entry::Visibility::PRIVATE)
286
+ @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PRIVATE))
288
287
  when :module_function
289
288
  handle_module_function(node)
290
289
  when :private_class_method
291
- @visibility_stack.push(Entry::Visibility::PRIVATE)
290
+ @visibility_stack.push(VisibilityScope.new(visibility: Entry::Visibility::PRIVATE))
292
291
  handle_private_class_method(node)
293
292
  end
294
293
 
@@ -324,42 +323,61 @@ module RubyIndexer
324
323
 
325
324
  sig { params(node: Prism::DefNode).void }
326
325
  def on_def_node_enter(node)
326
+ owner = @owner_stack.last
327
+ return unless owner
328
+
327
329
  @inside_def = true
328
330
  method_name = node.name.to_s
329
331
  comments = collect_comments(node)
332
+ scope = current_visibility_scope
330
333
 
331
334
  case node.receiver
332
335
  when nil
336
+ location = Location.from_prism_location(node.location, @code_units_cache)
337
+ name_location = Location.from_prism_location(node.name_loc, @code_units_cache)
338
+ signatures = [Entry::Signature.new(list_params(node.parameters))]
339
+
333
340
  @index.add(Entry::Method.new(
334
341
  method_name,
335
342
  @uri,
336
- Location.from_prism_location(node.location, @code_units_cache),
337
- Location.from_prism_location(node.name_loc, @code_units_cache),
343
+ location,
344
+ name_location,
338
345
  comments,
339
- [Entry::Signature.new(list_params(node.parameters))],
340
- current_visibility,
341
- @owner_stack.last,
346
+ signatures,
347
+ scope.visibility,
348
+ owner,
342
349
  ))
343
- when Prism::SelfNode
344
- owner = @owner_stack.last
345
350
 
346
- if owner
351
+ if scope.module_func
347
352
  singleton = @index.existing_or_new_singleton_class(owner.name)
348
353
 
349
354
  @index.add(Entry::Method.new(
350
355
  method_name,
351
356
  @uri,
352
- Location.from_prism_location(node.location, @code_units_cache),
353
- Location.from_prism_location(node.name_loc, @code_units_cache),
357
+ location,
358
+ name_location,
354
359
  comments,
355
- [Entry::Signature.new(list_params(node.parameters))],
356
- current_visibility,
360
+ signatures,
361
+ Entry::Visibility::PUBLIC,
357
362
  singleton,
358
363
  ))
359
-
360
- @owner_stack << singleton
361
- @stack << "<Class:#{@stack.last}>"
362
364
  end
365
+ when Prism::SelfNode
366
+ singleton = @index.existing_or_new_singleton_class(owner.name)
367
+
368
+ @index.add(Entry::Method.new(
369
+ method_name,
370
+ @uri,
371
+ Location.from_prism_location(node.location, @code_units_cache),
372
+ Location.from_prism_location(node.name_loc, @code_units_cache),
373
+ comments,
374
+ [Entry::Signature.new(list_params(node.parameters))],
375
+ scope.visibility,
376
+ singleton,
377
+ ))
378
+
379
+ @owner_stack << singleton
380
+ @stack << "<Class:#{@stack.last}>"
363
381
  end
364
382
  end
365
383
 
@@ -834,6 +852,8 @@ module RubyIndexer
834
852
  return unless receiver.nil? || receiver.is_a?(Prism::SelfNode)
835
853
 
836
854
  comments = collect_comments(node)
855
+ scope = current_visibility_scope
856
+
837
857
  arguments.each do |argument|
838
858
  name, loc = case argument
839
859
  when Prism::SymbolNode
@@ -850,7 +870,7 @@ module RubyIndexer
850
870
  @uri,
851
871
  Location.from_prism_location(loc, @code_units_cache),
852
872
  comments,
853
- current_visibility,
873
+ scope.visibility,
854
874
  @owner_stack.last,
855
875
  ))
856
876
  end
@@ -862,7 +882,7 @@ module RubyIndexer
862
882
  @uri,
863
883
  Location.from_prism_location(loc, @code_units_cache),
864
884
  comments,
865
- current_visibility,
885
+ scope.visibility,
866
886
  @owner_stack.last,
867
887
  ))
868
888
  end
@@ -904,11 +924,20 @@ module RubyIndexer
904
924
 
905
925
  sig { params(node: Prism::CallNode).void }
906
926
  def handle_module_function(node)
927
+ # Invoking `module_function` in a class raises
928
+ owner = @owner_stack.last
929
+ return unless owner.is_a?(Entry::Module)
930
+
907
931
  arguments_node = node.arguments
908
- return unless arguments_node
909
932
 
910
- owner_name = @owner_stack.last&.name
911
- return unless owner_name
933
+ # If `module_function` is invoked without arguments, all methods defined after it become singleton methods and the
934
+ # visibility for instance methods changes to private
935
+ unless arguments_node
936
+ @visibility_stack.push(VisibilityScope.module_function_scope)
937
+ return
938
+ end
939
+
940
+ owner_name = owner.name
912
941
 
913
942
  arguments_node.arguments.each do |argument|
914
943
  method_name = case argument
@@ -983,8 +1012,8 @@ module RubyIndexer
983
1012
  end
984
1013
  end
985
1014
 
986
- sig { returns(Entry::Visibility) }
987
- def current_visibility
1015
+ sig { returns(VisibilityScope) }
1016
+ def current_visibility_scope
988
1017
  T.must(@visibility_stack.last)
989
1018
  end
990
1019
 
@@ -1091,7 +1120,7 @@ module RubyIndexer
1091
1120
 
1092
1121
  sig { params(short_name: String, entry: Entry::Namespace).void }
1093
1122
  def advance_namespace_stack(short_name, entry)
1094
- @visibility_stack.push(Entry::Visibility::PUBLIC)
1123
+ @visibility_stack.push(VisibilityScope.public_scope)
1095
1124
  @owner_stack << entry
1096
1125
  @index.add(entry)
1097
1126
  @stack << short_name
@@ -48,6 +48,8 @@ module RubyIndexer
48
48
  )
49
49
 
50
50
  @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
51
+
52
+ @initial_indexing_completed = T.let(false, T::Boolean)
51
53
  end
52
54
 
53
55
  # Register an included `hook` that will be executed when `module_name` is included into any namespace
@@ -56,8 +58,8 @@ module RubyIndexer
56
58
  (@included_hooks[module_name] ||= []) << hook
57
59
  end
58
60
 
59
- sig { params(uri: URI::Generic).void }
60
- def delete(uri)
61
+ sig { params(uri: URI::Generic, skip_require_paths_tree: T::Boolean).void }
62
+ def delete(uri, skip_require_paths_tree: false)
61
63
  key = uri.to_s
62
64
  # For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
63
65
  # left, delete the constant from the index.
@@ -80,6 +82,7 @@ module RubyIndexer
80
82
  end
81
83
 
82
84
  @uris_to_entries.delete(key)
85
+ return if skip_require_paths_tree
83
86
 
84
87
  require_path = uri.require_path
85
88
  @require_paths_tree.delete(require_path) if require_path
@@ -357,12 +360,13 @@ module RubyIndexer
357
360
  # When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
358
361
  # existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
359
362
  # behavior and can take appropriate action.
360
- # binding.break
361
- if @entries.any?
363
+ if @initial_indexing_completed
362
364
  raise IndexNotEmptyError,
363
365
  "The index is not empty. To prevent invalid entries, `index_all` can only be called once."
364
366
  end
365
367
 
368
+ @initial_indexing_completed = true
369
+
366
370
  RBSIndexer.new(self).index_ruby_core
367
371
  # Calculate how many paths are worth 1% of progress
368
372
  progress_step = (uris.length / 100.0).ceil
@@ -618,17 +622,24 @@ module RubyIndexer
618
622
  end
619
623
 
620
624
  # Synchronizes a change made to the given URI. This method will ensure that new declarations are indexed, removed
621
- # declarations removed and that the ancestor linearization cache is cleared if necessary
622
- sig { params(uri: URI::Generic, source: String).void }
623
- def handle_change(uri, source)
625
+ # declarations removed and that the ancestor linearization cache is cleared if necessary. If a block is passed, the
626
+ # consumer of this API has to handle deleting and inserting/updating entries in the index instead of passing the
627
+ # document's source (used to handle unsaved changes to files)
628
+ sig do
629
+ params(uri: URI::Generic, source: T.nilable(String), block: T.nilable(T.proc.params(index: Index).void)).void
630
+ end
631
+ def handle_change(uri, source = nil, &block)
624
632
  key = uri.to_s
625
633
  original_entries = @uris_to_entries[key]
626
634
 
627
- delete(uri)
628
- index_single(uri, source)
635
+ if block
636
+ block.call(self)
637
+ else
638
+ delete(uri)
639
+ index_single(uri, T.must(source))
640
+ end
629
641
 
630
642
  updated_entries = @uris_to_entries[key]
631
-
632
643
  return unless original_entries && updated_entries
633
644
 
634
645
  # A change in one ancestor may impact several different others, which could be including that ancestor through
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ # Represents the visibility scope in a Ruby namespace. This keeps track of whether methods are in a public, private or
6
+ # protected section, and whether they are module functions.
7
+ class VisibilityScope
8
+ extend T::Sig
9
+
10
+ class << self
11
+ extend T::Sig
12
+
13
+ sig { returns(T.attached_class) }
14
+ def module_function_scope
15
+ new(module_func: true, visibility: Entry::Visibility::PRIVATE)
16
+ end
17
+
18
+ sig { returns(T.attached_class) }
19
+ def public_scope
20
+ new
21
+ end
22
+ end
23
+
24
+ sig { returns(Entry::Visibility) }
25
+ attr_reader :visibility
26
+
27
+ sig { returns(T::Boolean) }
28
+ attr_reader :module_func
29
+
30
+ sig { params(visibility: Entry::Visibility, module_func: T::Boolean).void }
31
+ def initialize(visibility: Entry::Visibility::PUBLIC, module_func: false)
32
+ @visibility = visibility
33
+ @module_func = module_func
34
+ end
35
+ end
36
+ end
@@ -5,6 +5,7 @@ require "yaml"
5
5
  require "did_you_mean"
6
6
 
7
7
  require "ruby_indexer/lib/ruby_indexer/uri"
8
+ require "ruby_indexer/lib/ruby_indexer/visibility_scope"
8
9
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
10
  require "ruby_indexer/lib/ruby_indexer/reference_finder"
10
11
  require "ruby_indexer/lib/ruby_indexer/enhancement"
@@ -2061,7 +2061,8 @@ module RubyIndexer
2061
2061
  end
2062
2062
 
2063
2063
  def test_prevents_multiple_calls_to_index_all
2064
- # For this test class, `index_all` is already called once in `setup`.
2064
+ @index.index_all
2065
+
2065
2066
  assert_raises(Index::IndexNotEmptyError) do
2066
2067
  @index.index_all
2067
2068
  end
@@ -216,5 +216,25 @@ module RubyIndexer
216
216
  assert_instance_of(Entry::Class, owner)
217
217
  assert_equal("Foo", owner.name)
218
218
  end
219
+
220
+ def test_module_function_does_not_impact_instance_variables
221
+ # One possible way of implementing `module_function` would be to push a fake singleton class to the stack, so that
222
+ # methods are inserted into it. However, that would be incorrect because it would then bind instance variables to
223
+ # the wrong type. This test is here to prevent that from happening.
224
+ index(<<~RUBY)
225
+ module Foo
226
+ module_function
227
+
228
+ def something; end
229
+
230
+ @a = 123
231
+ end
232
+ RUBY
233
+
234
+ entry = T.must(@index["@a"]&.first)
235
+ owner = T.must(entry.owner)
236
+ assert_instance_of(Entry::SingletonClass, owner)
237
+ assert_equal("Foo::<Class:Foo>", owner.name)
238
+ end
219
239
  end
220
240
  end
@@ -88,19 +88,21 @@ module RubyIndexer
88
88
 
89
89
  def test_visibility_tracking
90
90
  index(<<~RUBY)
91
- private def foo
92
- end
91
+ class Foo
92
+ private def foo
93
+ end
93
94
 
94
- def bar; end
95
+ def bar; end
95
96
 
96
- protected
97
+ protected
97
98
 
98
- def baz; end
99
+ def baz; end
100
+ end
99
101
  RUBY
100
102
 
101
- assert_entry("foo", Entry::Method, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
102
- assert_entry("bar", Entry::Method, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
103
- assert_entry("baz", Entry::Method, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
103
+ assert_entry("foo", Entry::Method, "/fake/path/foo.rb:1-10:2-5", visibility: Entry::Visibility::PRIVATE)
104
+ assert_entry("bar", Entry::Method, "/fake/path/foo.rb:4-2:4-14", visibility: Entry::Visibility::PUBLIC)
105
+ assert_entry("baz", Entry::Method, "/fake/path/foo.rb:8-2:8-14", visibility: Entry::Visibility::PROTECTED)
104
106
  end
105
107
 
106
108
  def test_visibility_tracking_with_nested_class_or_modules
@@ -846,6 +848,67 @@ module RubyIndexer
846
848
  assert_signature_matches(entry, "baz(1)")
847
849
  end
848
850
 
851
+ def test_module_function_with_no_arguments
852
+ index(<<~RUBY)
853
+ module Foo
854
+ def bar; end
855
+
856
+ module_function
857
+
858
+ def baz; end
859
+ attr_reader :attribute
860
+
861
+ public
862
+
863
+ def qux; end
864
+ end
865
+ RUBY
866
+
867
+ entry = T.must(@index["bar"].first)
868
+ assert_predicate(entry, :public?)
869
+ assert_equal("Foo", T.must(entry.owner).name)
870
+
871
+ instance_baz, singleton_baz = T.must(@index["baz"])
872
+ assert_predicate(instance_baz, :private?)
873
+ assert_equal("Foo", T.must(instance_baz.owner).name)
874
+
875
+ assert_predicate(singleton_baz, :public?)
876
+ assert_equal("Foo::<Class:Foo>", T.must(singleton_baz.owner).name)
877
+
878
+ # After invoking `public`, the state of `module_function` is reset
879
+ instance_qux, singleton_qux = T.must(@index["qux"])
880
+ assert_nil(singleton_qux)
881
+ assert_predicate(instance_qux, :public?)
882
+ assert_equal("Foo", T.must(instance_baz.owner).name)
883
+
884
+ # Attributes are not turned into class methods, they do become private
885
+ instance_attribute, singleton_attribute = @index["attribute"]
886
+ assert_nil(singleton_attribute)
887
+ assert_equal("Foo", T.must(instance_attribute.owner).name)
888
+ assert_predicate(instance_attribute, :private?)
889
+ end
890
+
891
+ def test_module_function_does_nothing_in_classes
892
+ # Invoking `module_function` in a class raises an error. We simply ignore it
893
+ index(<<~RUBY)
894
+ class Foo
895
+ def bar; end
896
+
897
+ module_function
898
+
899
+ def baz; end
900
+ end
901
+ RUBY
902
+
903
+ entry = T.must(@index["bar"].first)
904
+ assert_predicate(entry, :public?)
905
+ assert_equal("Foo", T.must(entry.owner).name)
906
+
907
+ entry = T.must(@index["baz"].first)
908
+ assert_predicate(entry, :public?)
909
+ assert_equal("Foo", T.must(entry.owner).name)
910
+ end
911
+
849
912
  private
850
913
 
851
914
  sig { params(entry: Entry::Method, call_string: String).void }
@@ -18,22 +18,21 @@ module RubyLsp
18
18
  @incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
19
19
  @outgoing_queue = T.let(Thread::Queue.new, Thread::Queue)
20
20
  @cancelled_requests = T.let([], T::Array[Integer])
21
- @mutex = T.let(Mutex.new, Mutex)
22
21
  @worker = T.let(new_worker, Thread)
23
22
  @current_request_id = T.let(1, Integer)
24
- @store = T.let(Store.new, Store)
23
+ @global_state = T.let(GlobalState.new, GlobalState)
24
+ @store = T.let(Store.new(@global_state), Store)
25
25
  @outgoing_dispatcher = T.let(
26
26
  Thread.new do
27
27
  unless @test_mode
28
28
  while (message = @outgoing_queue.pop)
29
- @mutex.synchronize { @writer.write(message.to_hash) }
29
+ @global_state.synchronize { @writer.write(message.to_hash) }
30
30
  end
31
31
  end
32
32
  end,
33
33
  Thread,
34
34
  )
35
35
 
36
- @global_state = T.let(GlobalState.new, GlobalState)
37
36
  Thread.main.priority = 1
38
37
 
39
38
  # We read the initialize request in `exe/ruby-lsp` to be able to determine the workspace URI where Bundler should
@@ -51,7 +50,7 @@ module RubyLsp
51
50
  # source. Altering the source reference during parsing will put the parser in an invalid internal state, since
52
51
  # it started parsing with one source but then it changed in the middle. We don't want to do this for text
53
52
  # synchronization notifications
54
- @mutex.synchronize do
53
+ @global_state.synchronize do
55
54
  uri = message.dig(:params, :textDocument, :uri)
56
55
 
57
56
  if uri
@@ -95,14 +94,14 @@ module RubyLsp
95
94
  "$/cancelRequest"
96
95
  process_message(message)
97
96
  when "shutdown"
98
- @mutex.synchronize do
97
+ @global_state.synchronize do
99
98
  send_log_message("Shutting down Ruby LSP...")
100
99
  shutdown
101
100
  run_shutdown
102
101
  @writer.write(Result.new(id: message[:id], response: nil).to_hash)
103
102
  end
104
103
  when "exit"
105
- @mutex.synchronize do
104
+ @global_state.synchronize do
106
105
  status = @incoming_queue.closed? ? 0 : 1
107
106
  send_log_message("Shutdown complete with status #{status}")
108
107
  exit(status)
@@ -157,13 +156,9 @@ module RubyLsp
157
156
  id = message[:id]
158
157
 
159
158
  # Check if the request was cancelled before trying to process it
160
- @mutex.synchronize do
159
+ @global_state.synchronize do
161
160
  if id && @cancelled_requests.include?(id)
162
- send_message(Error.new(
163
- id: id,
164
- code: Constant::ErrorCodes::REQUEST_CANCELLED,
165
- message: "Request #{id} was cancelled",
166
- ))
161
+ send_message(Result.new(id: id, response: nil))
167
162
  @cancelled_requests.delete(id)
168
163
  next
169
164
  end
@@ -40,19 +40,24 @@ module RubyLsp
40
40
  sig { returns(Encoding) }
41
41
  attr_reader :encoding
42
42
 
43
+ sig { returns(T.nilable(Edit)) }
44
+ attr_reader :last_edit
45
+
43
46
  sig { returns(T.any(Interface::SemanticTokens, Object)) }
44
47
  attr_accessor :semantic_tokens
45
48
 
46
- sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
47
- def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
49
+ sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
50
+ def initialize(source:, version:, uri:, global_state:)
51
+ @source = source
52
+ @version = version
53
+ @global_state = global_state
48
54
  @cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
49
55
  @semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
50
- @encoding = T.let(encoding, Encoding)
51
- @source = T.let(source, String)
52
- @version = T.let(version, Integer)
56
+ @encoding = T.let(global_state.encoding, Encoding)
53
57
  @uri = T.let(uri, URI::Generic)
54
58
  @needs_parsing = T.let(true, T::Boolean)
55
59
  @parse_result = T.let(T.unsafe(nil), ParseResultType)
60
+ @last_edit = T.let(nil, T.nilable(Edit))
56
61
  parse!
57
62
  end
58
63
 
@@ -64,7 +69,6 @@ module RubyLsp
64
69
  sig { abstract.returns(LanguageId) }
65
70
  def language_id; end
66
71
 
67
- # TODO: remove this method once all non-positional requests have been migrated to the listener pattern
68
72
  sig do
69
73
  type_parameters(:T)
70
74
  .params(
@@ -93,19 +97,34 @@ module RubyLsp
93
97
 
94
98
  sig { params(edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
95
99
  def push_edits(edits, version:)
96
- edits.each do |edit|
97
- range = edit[:range]
98
- scanner = create_scanner
100
+ @global_state.synchronize do
101
+ edits.each do |edit|
102
+ range = edit[:range]
103
+ scanner = create_scanner
99
104
 
100
- start_position = scanner.find_char_position(range[:start])
101
- end_position = scanner.find_char_position(range[:end])
105
+ start_position = scanner.find_char_position(range[:start])
106
+ end_position = scanner.find_char_position(range[:end])
102
107
 
103
- @source[start_position...end_position] = edit[:text]
104
- end
108
+ @source[start_position...end_position] = edit[:text]
109
+ end
105
110
 
106
- @version = version
107
- @needs_parsing = true
108
- @cache.clear
111
+ @version = version
112
+ @needs_parsing = true
113
+ @cache.clear
114
+
115
+ last_edit = edits.last
116
+ return unless last_edit
117
+
118
+ last_edit_range = last_edit[:range]
119
+
120
+ @last_edit = if last_edit_range[:start] == last_edit_range[:end]
121
+ Insert.new(last_edit_range)
122
+ elsif last_edit[:text].empty?
123
+ Delete.new(last_edit_range)
124
+ else
125
+ Replace.new(last_edit_range)
126
+ end
127
+ end
109
128
  end
110
129
 
111
130
  # Returns `true` if the document was parsed and `false` if nothing needed parsing
@@ -115,16 +134,52 @@ module RubyLsp
115
134
  sig { abstract.returns(T::Boolean) }
116
135
  def syntax_error?; end
117
136
 
137
+ sig { returns(T::Boolean) }
138
+ def past_expensive_limit?
139
+ @source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
140
+ end
141
+
142
+ sig do
143
+ params(
144
+ start_pos: T::Hash[Symbol, T.untyped],
145
+ end_pos: T.nilable(T::Hash[Symbol, T.untyped]),
146
+ ).returns([Integer, T.nilable(Integer)])
147
+ end
148
+ def find_index_by_position(start_pos, end_pos = nil)
149
+ @global_state.synchronize do
150
+ scanner = create_scanner
151
+ start_index = scanner.find_char_position(start_pos)
152
+ end_index = scanner.find_char_position(end_pos) if end_pos
153
+ [start_index, end_index]
154
+ end
155
+ end
156
+
157
+ private
158
+
118
159
  sig { returns(Scanner) }
119
160
  def create_scanner
120
161
  Scanner.new(@source, @encoding)
121
162
  end
122
163
 
123
- sig { returns(T::Boolean) }
124
- def past_expensive_limit?
125
- @source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
164
+ class Edit
165
+ extend T::Sig
166
+ extend T::Helpers
167
+
168
+ abstract!
169
+
170
+ sig { returns(T::Hash[Symbol, T.untyped]) }
171
+ attr_reader :range
172
+
173
+ sig { params(range: T::Hash[Symbol, T.untyped]).void }
174
+ def initialize(range)
175
+ @range = range
176
+ end
126
177
  end
127
178
 
179
+ class Insert < Edit; end
180
+ class Replace < Edit; end
181
+ class Delete < Edit; end
182
+
128
183
  class Scanner
129
184
  extend T::Sig
130
185