ruby-lsp 0.23.1 → 0.23.2

Sign up to get free protection for your applications and to get access to all the features.
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