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 +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +59 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +21 -10
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/index_test.rb +2 -1
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +71 -8
- data/lib/ruby_lsp/base_server.rb +8 -13
- data/lib/ruby_lsp/document.rb +74 -19
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -6
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +95 -81
- data/lib/ruby_lsp/store.rb +16 -12
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d295233e64f7b79aa061f243c61c6d5bfb69cbdc5dac64474106473d7033bc8
|
4
|
+
data.tar.gz: 559fbc2fb40b2a8043fefbf332cb4bf94ce8e3953299d6740d1d21034b79e92b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52e0176c0e9516801f94d9bd387ad6acfcd640c500443527448b69f3b55e2627aafa3bea75a60e131c21c0b06b309aba28a98b263344d20ce6406e1e66b69a37
|
7
|
+
data.tar.gz: 52b47878d9c5a71dadd618a5562a30901abf3fe167f71e08d92f1a5418710a4e360043ae08d553975ad7008d7f1fb657a9b650d05a311ddcfc92157a7283ec6c
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
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([
|
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(
|
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(
|
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
|
-
|
337
|
-
|
343
|
+
location,
|
344
|
+
name_location,
|
338
345
|
comments,
|
339
|
-
|
340
|
-
|
341
|
-
|
346
|
+
signatures,
|
347
|
+
scope.visibility,
|
348
|
+
owner,
|
342
349
|
))
|
343
|
-
when Prism::SelfNode
|
344
|
-
owner = @owner_stack.last
|
345
350
|
|
346
|
-
if
|
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
|
-
|
353
|
-
|
357
|
+
location,
|
358
|
+
name_location,
|
354
359
|
comments,
|
355
|
-
|
356
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
911
|
-
|
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(
|
987
|
-
def
|
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(
|
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
|
-
|
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
|
-
|
623
|
-
|
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
|
-
|
628
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
91
|
+
class Foo
|
92
|
+
private def foo
|
93
|
+
end
|
93
94
|
|
94
|
-
|
95
|
+
def bar; end
|
95
96
|
|
96
|
-
|
97
|
+
protected
|
97
98
|
|
98
|
-
|
99
|
+
def baz; end
|
100
|
+
end
|
99
101
|
RUBY
|
100
102
|
|
101
|
-
assert_entry("foo", Entry::Method, "/fake/path/foo.rb:
|
102
|
-
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:
|
103
|
-
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:
|
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 }
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
159
|
+
@global_state.synchronize do
|
161
160
|
if id && @cancelled_requests.include?(id)
|
162
|
-
send_message(
|
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
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -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,
|
47
|
-
def initialize(source:, version:, uri:,
|
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
|
-
|
97
|
-
|
98
|
-
|
100
|
+
@global_state.synchronize do
|
101
|
+
edits.each do |edit|
|
102
|
+
range = edit[:range]
|
103
|
+
scanner = create_scanner
|
99
104
|
|
100
|
-
|
101
|
-
|
105
|
+
start_position = scanner.find_char_position(range[:start])
|
106
|
+
end_position = scanner.find_char_position(range[:end])
|
102
107
|
|
103
|
-
|
104
|
-
|
108
|
+
@source[start_position...end_position] = edit[:text]
|
109
|
+
end
|
105
110
|
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
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
|
|