ruby-lsp 0.17.15 → 0.17.17
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 +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +5 -4
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +26 -8
- data/lib/ruby_indexer/test/index_test.rb +41 -0
- data/lib/ruby_lsp/addon.rb +3 -2
- data/lib/ruby_lsp/document.rb +16 -2
- data/lib/ruby_lsp/global_state.rb +12 -0
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/inlay_hints.rb +11 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +20 -117
- data/lib/ruby_lsp/requests/diagnostics.rb +3 -1
- data/lib/ruby_lsp/requests/formatting.rb +3 -1
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +113 -6
- data/lib/ruby_lsp/requests/support/common.rb +0 -9
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -4
- data/lib/ruby_lsp/server.rb +78 -16
- data/lib/ruby_lsp/store.rb +1 -1
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +38 -0
- metadata +6 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4b4c9204834ddf87c84009003c5eef5872211eaef6ed64022a93a2749e1849e
|
4
|
+
data.tar.gz: 0d770cf2f67f90fb974a9fca08d9fb4dea1a73d668984ff96e5cc5785d7c5766
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3534227d67761a6738c5e2ae4a06a08964a0836c4a30c9d306917efdd93aae1285d6ed03992e88486a44e5d3f7e7271997e56bbb12ab908599687c6a1e622cb4
|
7
|
+
data.tar.gz: 5345bcf362b6206a0be392eebe5db2175fe9d06b1a38f113fe11d979e5fdcc48851cb47e5f016ae49726ffc73432e81ef801522588cee79ebb36fe545e02d965
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.17
|
data/exe/ruby-lsp
CHANGED
@@ -98,16 +98,17 @@ if options[:debug]
|
|
98
98
|
end
|
99
99
|
|
100
100
|
if options[:time_index]
|
101
|
-
require "benchmark"
|
102
|
-
|
103
101
|
index = RubyIndexer::Index.new
|
104
102
|
|
105
|
-
|
103
|
+
time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
104
|
+
index.index_all
|
105
|
+
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start
|
106
|
+
|
106
107
|
entries = index.instance_variable_get(:@entries)
|
107
108
|
entries_by_entry_type = entries.values.flatten.group_by(&:class)
|
108
109
|
|
109
110
|
puts <<~MSG
|
110
|
-
Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{
|
111
|
+
Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{elapsed_time.round(5)} seconds and generated:
|
111
112
|
- #{entries_by_entry_type.sort_by { |k, _| k.to_s }.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
|
112
113
|
MSG
|
113
114
|
return
|
@@ -232,7 +232,7 @@ module RubyIndexer
|
|
232
232
|
next unless ancestor_index && (!existing_entry || ancestor_index < existing_entry_index)
|
233
233
|
|
234
234
|
if entry.is_a?(Entry::UnresolvedMethodAlias)
|
235
|
-
resolved_alias = resolve_method_alias(entry, receiver_name)
|
235
|
+
resolved_alias = resolve_method_alias(entry, receiver_name, [])
|
236
236
|
hash[entry_name] = [resolved_alias, ancestor_index] if resolved_alias.is_a?(Entry::MethodAlias)
|
237
237
|
else
|
238
238
|
hash[entry_name] = [entry, ancestor_index]
|
@@ -394,16 +394,18 @@ module RubyIndexer
|
|
394
394
|
real_parts.join("::")
|
395
395
|
end
|
396
396
|
|
397
|
-
# Attempts to find methods for a resolved fully qualified receiver name.
|
397
|
+
# Attempts to find methods for a resolved fully qualified receiver name. Do not provide the `seen_names` parameter
|
398
|
+
# as it is used only internally to prevent infinite loops when resolving circular aliases
|
398
399
|
# Returns `nil` if the method does not exist on that receiver
|
399
400
|
sig do
|
400
401
|
params(
|
401
402
|
method_name: String,
|
402
403
|
receiver_name: String,
|
404
|
+
seen_names: T::Array[String],
|
403
405
|
inherited_only: T::Boolean,
|
404
406
|
).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
|
405
407
|
end
|
406
|
-
def resolve_method(method_name, receiver_name, inherited_only: false)
|
408
|
+
def resolve_method(method_name, receiver_name, seen_names = [], inherited_only: false)
|
407
409
|
method_entries = self[method_name]
|
408
410
|
return unless method_entries
|
409
411
|
|
@@ -418,7 +420,7 @@ module RubyIndexer
|
|
418
420
|
when Entry::UnresolvedMethodAlias
|
419
421
|
# Resolve aliases lazily as we find them
|
420
422
|
if entry.owner&.name == ancestor
|
421
|
-
resolved_alias = resolve_method_alias(entry, receiver_name)
|
423
|
+
resolved_alias = resolve_method_alias(entry, receiver_name, seen_names)
|
422
424
|
resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
|
423
425
|
end
|
424
426
|
end
|
@@ -614,6 +616,17 @@ module RubyIndexer
|
|
614
616
|
singleton
|
615
617
|
end
|
616
618
|
|
619
|
+
sig do
|
620
|
+
type_parameters(:T).params(
|
621
|
+
path: String,
|
622
|
+
type: T::Class[T.all(T.type_parameter(:T), Entry)],
|
623
|
+
).returns(T.nilable(T::Array[T.type_parameter(:T)]))
|
624
|
+
end
|
625
|
+
def entries_for(path, type)
|
626
|
+
entries = @files_to_entries[path]
|
627
|
+
entries&.grep(type)
|
628
|
+
end
|
629
|
+
|
617
630
|
private
|
618
631
|
|
619
632
|
# Runs the registered included hooks
|
@@ -919,16 +932,21 @@ module RubyIndexer
|
|
919
932
|
params(
|
920
933
|
entry: Entry::UnresolvedMethodAlias,
|
921
934
|
receiver_name: String,
|
935
|
+
seen_names: T::Array[String],
|
922
936
|
).returns(T.any(Entry::MethodAlias, Entry::UnresolvedMethodAlias))
|
923
937
|
end
|
924
|
-
def resolve_method_alias(entry, receiver_name)
|
925
|
-
|
938
|
+
def resolve_method_alias(entry, receiver_name, seen_names)
|
939
|
+
new_name = entry.new_name
|
940
|
+
return entry if new_name == entry.old_name
|
941
|
+
return entry if seen_names.include?(new_name)
|
942
|
+
|
943
|
+
seen_names << new_name
|
926
944
|
|
927
|
-
target_method_entries = resolve_method(entry.old_name, receiver_name)
|
945
|
+
target_method_entries = resolve_method(entry.old_name, receiver_name, seen_names)
|
928
946
|
return entry unless target_method_entries
|
929
947
|
|
930
948
|
resolved_alias = Entry::MethodAlias.new(T.must(target_method_entries.first), entry)
|
931
|
-
original_entries = T.must(@entries[
|
949
|
+
original_entries = T.must(@entries[new_name])
|
932
950
|
original_entries.delete(entry)
|
933
951
|
original_entries << resolved_alias
|
934
952
|
resolved_alias
|
@@ -1822,5 +1822,46 @@ module RubyIndexer
|
|
1822
1822
|
@index.linearized_ancestors_of("Foo::Child::<Class:Child>"),
|
1823
1823
|
)
|
1824
1824
|
end
|
1825
|
+
|
1826
|
+
def test_resolving_circular_method_aliases_on_class_reopen
|
1827
|
+
index(<<~RUBY)
|
1828
|
+
class Foo
|
1829
|
+
alias bar ==
|
1830
|
+
def ==(other) = true
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
class Foo
|
1834
|
+
alias == bar
|
1835
|
+
end
|
1836
|
+
RUBY
|
1837
|
+
|
1838
|
+
method = @index.resolve_method("==", "Foo").first
|
1839
|
+
assert_kind_of(Entry::Method, method)
|
1840
|
+
assert_equal("==", method.name)
|
1841
|
+
|
1842
|
+
candidates = @index.method_completion_candidates("=", "Foo")
|
1843
|
+
assert_equal(["==", "==="], candidates.map(&:name))
|
1844
|
+
end
|
1845
|
+
|
1846
|
+
def test_entries_for
|
1847
|
+
index(<<~RUBY)
|
1848
|
+
class Foo; end
|
1849
|
+
|
1850
|
+
module Bar
|
1851
|
+
def my_def; end
|
1852
|
+
def self.my_singleton_def; end
|
1853
|
+
end
|
1854
|
+
RUBY
|
1855
|
+
|
1856
|
+
entries = @index.entries_for("/fake/path/foo.rb", Entry)
|
1857
|
+
assert_equal(["Foo", "Bar", "my_def", "Bar::<Class:Bar>", "my_singleton_def"], entries.map(&:name))
|
1858
|
+
|
1859
|
+
entries = @index.entries_for("/fake/path/foo.rb", RubyIndexer::Entry::Namespace)
|
1860
|
+
assert_equal(["Foo", "Bar", "Bar::<Class:Bar>"], entries.map(&:name))
|
1861
|
+
end
|
1862
|
+
|
1863
|
+
def test_entries_for_returns_nil_if_no_matches
|
1864
|
+
assert_nil(@index.entries_for("non_existing_file.rb", Entry::Namespace))
|
1865
|
+
end
|
1825
1866
|
end
|
1826
1867
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -30,6 +30,8 @@ module RubyLsp
|
|
30
30
|
# Addon instances that have declared a handler to accept file watcher events
|
31
31
|
@file_watcher_addons = T.let([], T::Array[Addon])
|
32
32
|
|
33
|
+
AddonNotFoundError = Class.new(StandardError)
|
34
|
+
|
33
35
|
class << self
|
34
36
|
extend T::Sig
|
35
37
|
|
@@ -78,11 +80,10 @@ module RubyLsp
|
|
78
80
|
errors
|
79
81
|
end
|
80
82
|
|
81
|
-
# Intended for use by tests for addons
|
82
83
|
sig { params(addon_name: String).returns(Addon) }
|
83
84
|
def get(addon_name)
|
84
85
|
addon = addons.find { |addon| addon.name == addon_name }
|
85
|
-
raise "Could not find addon '#{addon_name}'" unless addon
|
86
|
+
raise AddonNotFoundError, "Could not find addon '#{addon_name}'" unless addon
|
86
87
|
|
87
88
|
addon
|
88
89
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -17,6 +17,11 @@ module RubyLsp
|
|
17
17
|
|
18
18
|
ParseResultType = type_member
|
19
19
|
|
20
|
+
# This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
|
21
|
+
# This is the same number used by the TypeScript extension in VS Code
|
22
|
+
MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
|
23
|
+
EMPTY_CACHE = T.let(Object.new.freeze, Object)
|
24
|
+
|
20
25
|
abstract!
|
21
26
|
|
22
27
|
sig { returns(ParseResultType) }
|
@@ -34,9 +39,13 @@ module RubyLsp
|
|
34
39
|
sig { returns(Encoding) }
|
35
40
|
attr_reader :encoding
|
36
41
|
|
42
|
+
sig { returns(T.any(Interface::SemanticTokens, Object)) }
|
43
|
+
attr_accessor :semantic_tokens
|
44
|
+
|
37
45
|
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
|
38
46
|
def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
|
39
|
-
@cache = T.let(
|
47
|
+
@cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
|
48
|
+
@semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
|
40
49
|
@encoding = T.let(encoding, Encoding)
|
41
50
|
@source = T.let(source, String)
|
42
51
|
@version = T.let(version, Integer)
|
@@ -63,7 +72,7 @@ module RubyLsp
|
|
63
72
|
end
|
64
73
|
def cache_fetch(request_name, &block)
|
65
74
|
cached = @cache[request_name]
|
66
|
-
return cached if cached
|
75
|
+
return cached if cached != EMPTY_CACHE
|
67
76
|
|
68
77
|
result = block.call(self)
|
69
78
|
@cache[request_name] = result
|
@@ -108,6 +117,11 @@ module RubyLsp
|
|
108
117
|
Scanner.new(@source, @encoding)
|
109
118
|
end
|
110
119
|
|
120
|
+
sig { returns(T::Boolean) }
|
121
|
+
def past_expensive_limit?
|
122
|
+
@source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
|
123
|
+
end
|
124
|
+
|
111
125
|
class Scanner
|
112
126
|
extend T::Sig
|
113
127
|
|
@@ -40,6 +40,12 @@ module RubyLsp
|
|
40
40
|
@supports_watching_files = T.let(false, T::Boolean)
|
41
41
|
@experimental_features = T.let(false, T::Boolean)
|
42
42
|
@type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
|
43
|
+
@addon_settings = T.let({}, T::Hash[String, T.untyped])
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
47
|
+
def settings_for_addon(addon_name)
|
48
|
+
@addon_settings[addon_name]
|
43
49
|
end
|
44
50
|
|
45
51
|
sig { params(identifier: String, instance: Requests::Support::Formatter).void }
|
@@ -119,6 +125,12 @@ module RubyLsp
|
|
119
125
|
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
120
126
|
@type_inferrer.experimental_features = @experimental_features
|
121
127
|
|
128
|
+
addon_settings = options.dig(:initializationOptions, :addonSettings)
|
129
|
+
if addon_settings
|
130
|
+
addon_settings.transform_keys!(&:to_s)
|
131
|
+
@addon_settings.merge!(addon_settings)
|
132
|
+
end
|
133
|
+
|
122
134
|
notifications
|
123
135
|
end
|
124
136
|
|
@@ -366,7 +366,7 @@ module RubyLsp
|
|
366
366
|
|
367
367
|
return unless range
|
368
368
|
|
369
|
-
guessed_type = type.name
|
369
|
+
guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name
|
370
370
|
|
371
371
|
@index.method_completion_candidates(method_name, type.name).each do |entry|
|
372
372
|
entry_name = entry.name
|
@@ -242,7 +242,7 @@ module RubyLsp
|
|
242
242
|
body = statements.body
|
243
243
|
return if body.empty?
|
244
244
|
|
245
|
-
add_lines_range(node.location.start_line,
|
245
|
+
add_lines_range(node.location.start_line, body.last.location.end_line)
|
246
246
|
end
|
247
247
|
|
248
248
|
sig { params(node: Prism::Node).void }
|
@@ -69,6 +69,17 @@ module RubyLsp
|
|
69
69
|
tooltip: tooltip,
|
70
70
|
)
|
71
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
sig { params(node: T.nilable(Prism::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
76
|
+
def visible?(node, range)
|
77
|
+
return true if range.nil?
|
78
|
+
return false if node.nil?
|
79
|
+
|
80
|
+
loc = node.location
|
81
|
+
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
@@ -14,7 +14,7 @@ module RubyLsp
|
|
14
14
|
Kernel.methods(false),
|
15
15
|
Bundler::Dsl.instance_methods(false),
|
16
16
|
Module.private_instance_methods(false),
|
17
|
-
].flatten.map(&:to_s),
|
17
|
+
].flatten.map(&:to_s).freeze,
|
18
18
|
T::Array[String],
|
19
19
|
)
|
20
20
|
|
@@ -22,12 +22,10 @@ module RubyLsp
|
|
22
22
|
params(
|
23
23
|
dispatcher: Prism::Dispatcher,
|
24
24
|
response_builder: ResponseBuilders::SemanticHighlighting,
|
25
|
-
range: T.nilable(T::Range[Integer]),
|
26
25
|
).void
|
27
26
|
end
|
28
|
-
def initialize(dispatcher, response_builder
|
27
|
+
def initialize(dispatcher, response_builder)
|
29
28
|
@response_builder = response_builder
|
30
|
-
@range = range
|
31
29
|
@special_methods = T.let(nil, T.nilable(T::Array[String]))
|
32
30
|
@current_scope = T.let(ParameterScope.new, ParameterScope)
|
33
31
|
@inside_regex_capture = T.let(false, T::Boolean)
|
@@ -52,13 +50,6 @@ module RubyLsp
|
|
52
50
|
:on_optional_parameter_node_enter,
|
53
51
|
:on_required_parameter_node_enter,
|
54
52
|
:on_rest_parameter_node_enter,
|
55
|
-
:on_constant_read_node_enter,
|
56
|
-
:on_constant_write_node_enter,
|
57
|
-
:on_constant_and_write_node_enter,
|
58
|
-
:on_constant_operator_write_node_enter,
|
59
|
-
:on_constant_or_write_node_enter,
|
60
|
-
:on_constant_target_node_enter,
|
61
|
-
:on_constant_path_node_enter,
|
62
53
|
:on_local_variable_and_write_node_enter,
|
63
54
|
:on_local_variable_operator_write_node_enter,
|
64
55
|
:on_local_variable_or_write_node_enter,
|
@@ -74,7 +65,6 @@ module RubyLsp
|
|
74
65
|
sig { params(node: Prism::CallNode).void }
|
75
66
|
def on_call_node_enter(node)
|
76
67
|
return if @inside_implicit_node
|
77
|
-
return unless visible?(node, @range)
|
78
68
|
|
79
69
|
message = node.message
|
80
70
|
return unless message
|
@@ -85,8 +75,14 @@ module RubyLsp
|
|
85
75
|
return if message == "=~"
|
86
76
|
return if special_method?(message)
|
87
77
|
|
88
|
-
|
89
|
-
|
78
|
+
if Requests::Support::Sorbet.annotation?(node)
|
79
|
+
@response_builder.add_token(T.must(node.message_loc), :type)
|
80
|
+
elsif !node.receiver && !node.opening_loc
|
81
|
+
# If the node has a receiver, then the syntax is not ambiguous and semantic highlighting is not necessary to
|
82
|
+
# determine that the token is a method call. The only ambiguous case is method calls with implicit self
|
83
|
+
# receiver and no parenthesis, which may be confused with local variables
|
84
|
+
@response_builder.add_token(T.must(node.message_loc), :method)
|
85
|
+
end
|
90
86
|
end
|
91
87
|
|
92
88
|
sig { params(node: Prism::MatchWriteNode).void }
|
@@ -104,55 +100,9 @@ module RubyLsp
|
|
104
100
|
@inside_regex_capture = true if node.call.message == "=~"
|
105
101
|
end
|
106
102
|
|
107
|
-
sig { params(node: Prism::ConstantReadNode).void }
|
108
|
-
def on_constant_read_node_enter(node)
|
109
|
-
return if @inside_implicit_node
|
110
|
-
return unless visible?(node, @range)
|
111
|
-
|
112
|
-
@response_builder.add_token(node.location, :namespace)
|
113
|
-
end
|
114
|
-
|
115
|
-
sig { params(node: Prism::ConstantWriteNode).void }
|
116
|
-
def on_constant_write_node_enter(node)
|
117
|
-
return unless visible?(node, @range)
|
118
|
-
|
119
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
120
|
-
end
|
121
|
-
|
122
|
-
sig { params(node: Prism::ConstantAndWriteNode).void }
|
123
|
-
def on_constant_and_write_node_enter(node)
|
124
|
-
return unless visible?(node, @range)
|
125
|
-
|
126
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
127
|
-
end
|
128
|
-
|
129
|
-
sig { params(node: Prism::ConstantOperatorWriteNode).void }
|
130
|
-
def on_constant_operator_write_node_enter(node)
|
131
|
-
return unless visible?(node, @range)
|
132
|
-
|
133
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
134
|
-
end
|
135
|
-
|
136
|
-
sig { params(node: Prism::ConstantOrWriteNode).void }
|
137
|
-
def on_constant_or_write_node_enter(node)
|
138
|
-
return unless visible?(node, @range)
|
139
|
-
|
140
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
141
|
-
end
|
142
|
-
|
143
|
-
sig { params(node: Prism::ConstantTargetNode).void }
|
144
|
-
def on_constant_target_node_enter(node)
|
145
|
-
return unless visible?(node, @range)
|
146
|
-
|
147
|
-
@response_builder.add_token(node.location, :namespace)
|
148
|
-
end
|
149
|
-
|
150
103
|
sig { params(node: Prism::DefNode).void }
|
151
104
|
def on_def_node_enter(node)
|
152
105
|
@current_scope = ParameterScope.new(@current_scope)
|
153
|
-
return unless visible?(node, @range)
|
154
|
-
|
155
|
-
@response_builder.add_token(node.name_loc, :method, [:declaration])
|
156
106
|
end
|
157
107
|
|
158
108
|
sig { params(node: Prism::DefNode).void }
|
@@ -184,77 +134,49 @@ module RubyLsp
|
|
184
134
|
sig { params(node: Prism::RequiredKeywordParameterNode).void }
|
185
135
|
def on_required_keyword_parameter_node_enter(node)
|
186
136
|
@current_scope << node.name
|
187
|
-
return unless visible?(node, @range)
|
188
|
-
|
189
|
-
location = node.name_loc
|
190
|
-
@response_builder.add_token(location.copy(length: location.length - 1), :parameter)
|
191
137
|
end
|
192
138
|
|
193
139
|
sig { params(node: Prism::OptionalKeywordParameterNode).void }
|
194
140
|
def on_optional_keyword_parameter_node_enter(node)
|
195
141
|
@current_scope << node.name
|
196
|
-
return unless visible?(node, @range)
|
197
|
-
|
198
|
-
location = node.name_loc
|
199
|
-
@response_builder.add_token(location.copy(length: location.length - 1), :parameter)
|
200
142
|
end
|
201
143
|
|
202
144
|
sig { params(node: Prism::KeywordRestParameterNode).void }
|
203
145
|
def on_keyword_rest_parameter_node_enter(node)
|
204
146
|
name = node.name
|
205
|
-
|
206
|
-
if name
|
207
|
-
@current_scope << name.to_sym
|
208
|
-
|
209
|
-
@response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
|
210
|
-
end
|
147
|
+
@current_scope << name.to_sym if name
|
211
148
|
end
|
212
149
|
|
213
150
|
sig { params(node: Prism::OptionalParameterNode).void }
|
214
151
|
def on_optional_parameter_node_enter(node)
|
215
152
|
@current_scope << node.name
|
216
|
-
return unless visible?(node, @range)
|
217
|
-
|
218
|
-
@response_builder.add_token(node.name_loc, :parameter)
|
219
153
|
end
|
220
154
|
|
221
155
|
sig { params(node: Prism::RequiredParameterNode).void }
|
222
156
|
def on_required_parameter_node_enter(node)
|
223
157
|
@current_scope << node.name
|
224
|
-
return unless visible?(node, @range)
|
225
|
-
|
226
|
-
@response_builder.add_token(node.location, :parameter)
|
227
158
|
end
|
228
159
|
|
229
160
|
sig { params(node: Prism::RestParameterNode).void }
|
230
161
|
def on_rest_parameter_node_enter(node)
|
231
162
|
name = node.name
|
232
|
-
|
233
|
-
if name
|
234
|
-
@current_scope << name.to_sym
|
235
|
-
|
236
|
-
@response_builder.add_token(T.must(node.name_loc), :parameter) if visible?(node, @range)
|
237
|
-
end
|
163
|
+
@current_scope << name.to_sym if name
|
238
164
|
end
|
239
165
|
|
240
166
|
sig { params(node: Prism::SelfNode).void }
|
241
167
|
def on_self_node_enter(node)
|
242
|
-
return unless visible?(node, @range)
|
243
|
-
|
244
168
|
@response_builder.add_token(node.location, :variable, [:default_library])
|
245
169
|
end
|
246
170
|
|
247
171
|
sig { params(node: Prism::LocalVariableWriteNode).void }
|
248
172
|
def on_local_variable_write_node_enter(node)
|
249
|
-
|
250
|
-
|
251
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
173
|
+
type = @current_scope.type_for(node.name)
|
174
|
+
@response_builder.add_token(node.name_loc, type) if type == :parameter
|
252
175
|
end
|
253
176
|
|
254
177
|
sig { params(node: Prism::LocalVariableReadNode).void }
|
255
178
|
def on_local_variable_read_node_enter(node)
|
256
179
|
return if @inside_implicit_node
|
257
|
-
return unless visible?(node, @range)
|
258
180
|
|
259
181
|
# Numbered parameters
|
260
182
|
if /_\d+/.match?(node.name)
|
@@ -267,23 +189,20 @@ module RubyLsp
|
|
267
189
|
|
268
190
|
sig { params(node: Prism::LocalVariableAndWriteNode).void }
|
269
191
|
def on_local_variable_and_write_node_enter(node)
|
270
|
-
|
271
|
-
|
272
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
192
|
+
type = @current_scope.type_for(node.name)
|
193
|
+
@response_builder.add_token(node.name_loc, type) if type == :parameter
|
273
194
|
end
|
274
195
|
|
275
196
|
sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
|
276
197
|
def on_local_variable_operator_write_node_enter(node)
|
277
|
-
|
278
|
-
|
279
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
198
|
+
type = @current_scope.type_for(node.name)
|
199
|
+
@response_builder.add_token(node.name_loc, type) if type == :parameter
|
280
200
|
end
|
281
201
|
|
282
202
|
sig { params(node: Prism::LocalVariableOrWriteNode).void }
|
283
203
|
def on_local_variable_or_write_node_enter(node)
|
284
|
-
|
285
|
-
|
286
|
-
@response_builder.add_token(node.name_loc, @current_scope.type_for(node.name))
|
204
|
+
type = @current_scope.type_for(node.name)
|
205
|
+
@response_builder.add_token(node.name_loc, type) if type == :parameter
|
287
206
|
end
|
288
207
|
|
289
208
|
sig { params(node: Prism::LocalVariableTargetNode).void }
|
@@ -294,15 +213,11 @@ module RubyLsp
|
|
294
213
|
# prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
|
295
214
|
return if @inside_regex_capture
|
296
215
|
|
297
|
-
return unless visible?(node, @range)
|
298
|
-
|
299
216
|
@response_builder.add_token(node.location, @current_scope.type_for(node.name))
|
300
217
|
end
|
301
218
|
|
302
219
|
sig { params(node: Prism::ClassNode).void }
|
303
220
|
def on_class_node_enter(node)
|
304
|
-
return unless visible?(node, @range)
|
305
|
-
|
306
221
|
constant_path = node.constant_path
|
307
222
|
|
308
223
|
if constant_path.is_a?(Prism::ConstantReadNode)
|
@@ -342,8 +257,6 @@ module RubyLsp
|
|
342
257
|
|
343
258
|
sig { params(node: Prism::ModuleNode).void }
|
344
259
|
def on_module_node_enter(node)
|
345
|
-
return unless visible?(node, @range)
|
346
|
-
|
347
260
|
constant_path = node.constant_path
|
348
261
|
|
349
262
|
if constant_path.is_a?(Prism::ConstantReadNode)
|
@@ -365,8 +278,6 @@ module RubyLsp
|
|
365
278
|
|
366
279
|
sig { params(node: Prism::ImplicitNode).void }
|
367
280
|
def on_implicit_node_enter(node)
|
368
|
-
return unless visible?(node, @range)
|
369
|
-
|
370
281
|
@inside_implicit_node = true
|
371
282
|
end
|
372
283
|
|
@@ -375,14 +286,6 @@ module RubyLsp
|
|
375
286
|
@inside_implicit_node = false
|
376
287
|
end
|
377
288
|
|
378
|
-
sig { params(node: Prism::ConstantPathNode).void }
|
379
|
-
def on_constant_path_node_enter(node)
|
380
|
-
return if @inside_implicit_node
|
381
|
-
return unless visible?(node, @range)
|
382
|
-
|
383
|
-
@response_builder.add_token(node.name_loc, :namespace)
|
384
|
-
end
|
385
|
-
|
386
289
|
private
|
387
290
|
|
388
291
|
# Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
|
@@ -46,7 +46,9 @@ module RubyLsp
|
|
46
46
|
diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)
|
47
47
|
|
48
48
|
# Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
|
49
|
-
|
49
|
+
if @document.syntax_error? || @active_linters.empty? || @document.past_expensive_limit?
|
50
|
+
return diagnostics
|
51
|
+
end
|
50
52
|
|
51
53
|
@active_linters.each do |linter|
|
52
54
|
linter_diagnostics = linter.run_diagnostic(@uri, @document)
|
@@ -58,14 +58,16 @@ module RubyLsp
|
|
58
58
|
formatted_text = @active_formatter.run_formatting(@uri, @document)
|
59
59
|
return unless formatted_text
|
60
60
|
|
61
|
+
lines = @document.source.lines
|
61
62
|
size = @document.source.size
|
63
|
+
|
62
64
|
return if formatted_text.size == size && formatted_text == @document.source
|
63
65
|
|
64
66
|
[
|
65
67
|
Interface::TextEdit.new(
|
66
68
|
range: Interface::Range.new(
|
67
69
|
start: Interface::Position.new(line: 0, character: 0),
|
68
|
-
end: Interface::Position.new(line: size, character:
|
70
|
+
end: Interface::Position.new(line: lines.size, character: 0),
|
69
71
|
),
|
70
72
|
new_text: formatted_text,
|
71
73
|
),
|
@@ -19,6 +19,16 @@ module RubyLsp
|
|
19
19
|
# some_invocation # --> semantic highlighting: method invocation
|
20
20
|
# var # --> semantic highlighting: local variable
|
21
21
|
# end
|
22
|
+
#
|
23
|
+
# # Strategy
|
24
|
+
#
|
25
|
+
# To maximize editor performance, the Ruby LSP will return the minimum number of semantic tokens, since applying
|
26
|
+
# them is an expensive operation for the client. This means that the server only returns tokens for ambiguous pieces
|
27
|
+
# of syntax, such as method invocations with no receivers or parenthesis (which can be confused with local
|
28
|
+
# variables).
|
29
|
+
#
|
30
|
+
# Offloading as much handling as possible to Text Mate grammars or equivalent will guarantee responsiveness in the
|
31
|
+
# editor and allow for a much smoother experience.
|
22
32
|
# ```
|
23
33
|
class SemanticHighlighting < Request
|
24
34
|
extend T::Sig
|
@@ -35,28 +45,125 @@ module RubyLsp
|
|
35
45
|
token_modifiers: ResponseBuilders::SemanticHighlighting::TOKEN_MODIFIERS.keys,
|
36
46
|
),
|
37
47
|
range: true,
|
38
|
-
full: { delta:
|
48
|
+
full: { delta: true },
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The compute_delta method receives the current semantic tokens and the previous semantic tokens and then tries
|
53
|
+
# to compute the smallest possible semantic token edit that will turn previous into current
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
current_tokens: T::Array[Integer],
|
57
|
+
previous_tokens: T::Array[Integer],
|
58
|
+
result_id: String,
|
59
|
+
).returns(Interface::SemanticTokensDelta)
|
60
|
+
end
|
61
|
+
def compute_delta(current_tokens, previous_tokens, result_id)
|
62
|
+
# Find the index of the first token that is different between the two sets of tokens
|
63
|
+
first_different_position = current_tokens.zip(previous_tokens).find_index { |new, old| new != old }
|
64
|
+
|
65
|
+
# When deleting a token from the end, the first_different_position will be nil, but since we're removing at
|
66
|
+
# the end, then we have to initialize it to the length of the current tokens after the deletion
|
67
|
+
if !first_different_position && current_tokens.length < previous_tokens.length
|
68
|
+
first_different_position = current_tokens.length
|
69
|
+
end
|
70
|
+
|
71
|
+
unless first_different_position
|
72
|
+
return Interface::SemanticTokensDelta.new(result_id: result_id, edits: [])
|
73
|
+
end
|
74
|
+
|
75
|
+
# Filter the tokens based on the first different position. This must happen at this stage, before we try to
|
76
|
+
# find the next position from the end or else we risk confusing sets of token that may have different lengths,
|
77
|
+
# but end with the exact same token
|
78
|
+
old_tokens = T.must(previous_tokens[first_different_position...])
|
79
|
+
new_tokens = T.must(current_tokens[first_different_position...])
|
80
|
+
|
81
|
+
# Then search from the end to find the first token that doesn't match. Since the user is normally editing the
|
82
|
+
# middle of the file, this will minimize the number of edits since the end of the token array has not changed
|
83
|
+
first_different_token_from_end = new_tokens.reverse.zip(old_tokens.reverse).find_index do |new, old|
|
84
|
+
new != old
|
85
|
+
end || 0
|
86
|
+
|
87
|
+
# Filter the old and new tokens to only the section that will be replaced/inserted/deleted
|
88
|
+
old_tokens = T.must(old_tokens[...old_tokens.length - first_different_token_from_end])
|
89
|
+
new_tokens = T.must(new_tokens[...new_tokens.length - first_different_token_from_end])
|
90
|
+
|
91
|
+
# And we send back a single edit, replacing an entire section for the new tokens
|
92
|
+
Interface::SemanticTokensDelta.new(
|
93
|
+
result_id: result_id,
|
94
|
+
edits: [{ start: first_different_position, deleteCount: old_tokens.length, data: new_tokens }],
|
39
95
|
)
|
40
96
|
end
|
97
|
+
|
98
|
+
sig { returns(Integer) }
|
99
|
+
def next_result_id
|
100
|
+
@mutex.synchronize do
|
101
|
+
@result_id += 1
|
102
|
+
end
|
103
|
+
end
|
41
104
|
end
|
42
105
|
|
43
|
-
|
44
|
-
|
106
|
+
@result_id = T.let(0, Integer)
|
107
|
+
@mutex = T.let(Mutex.new, Mutex)
|
108
|
+
|
109
|
+
sig do
|
110
|
+
params(
|
111
|
+
global_state: GlobalState,
|
112
|
+
dispatcher: Prism::Dispatcher,
|
113
|
+
document: T.any(RubyDocument, ERBDocument),
|
114
|
+
previous_result_id: T.nilable(String),
|
115
|
+
range: T.nilable(T::Range[Integer]),
|
116
|
+
).void
|
117
|
+
end
|
118
|
+
def initialize(global_state, dispatcher, document, previous_result_id, range: nil)
|
45
119
|
super()
|
120
|
+
|
121
|
+
@document = document
|
122
|
+
@previous_result_id = previous_result_id
|
123
|
+
@range = range
|
124
|
+
@result_id = T.let(SemanticHighlighting.next_result_id.to_s, String)
|
46
125
|
@response_builder = T.let(
|
47
126
|
ResponseBuilders::SemanticHighlighting.new(global_state.encoding),
|
48
127
|
ResponseBuilders::SemanticHighlighting,
|
49
128
|
)
|
50
|
-
Listeners::SemanticHighlighting.new(dispatcher, @response_builder
|
129
|
+
Listeners::SemanticHighlighting.new(dispatcher, @response_builder)
|
51
130
|
|
52
131
|
Addon.addons.each do |addon|
|
53
132
|
addon.create_semantic_highlighting_listener(@response_builder, dispatcher)
|
54
133
|
end
|
55
134
|
end
|
56
135
|
|
57
|
-
sig { override.returns(Interface::SemanticTokens) }
|
136
|
+
sig { override.returns(T.any(Interface::SemanticTokens, Interface::SemanticTokensDelta)) }
|
58
137
|
def perform
|
59
|
-
@
|
138
|
+
previous_tokens = @document.semantic_tokens
|
139
|
+
tokens = @response_builder.response
|
140
|
+
encoded_tokens = ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens)
|
141
|
+
full_response = Interface::SemanticTokens.new(result_id: @result_id, data: encoded_tokens)
|
142
|
+
@document.semantic_tokens = full_response
|
143
|
+
|
144
|
+
if @range
|
145
|
+
tokens_within_range = tokens.select { |token| @range.cover?(token.start_line - 1) }
|
146
|
+
|
147
|
+
return Interface::SemanticTokens.new(
|
148
|
+
result_id: @result_id,
|
149
|
+
data: ResponseBuilders::SemanticHighlighting::SemanticTokenEncoder.new.encode(tokens_within_range),
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Semantic tokens full delta
|
154
|
+
if @previous_result_id
|
155
|
+
response = if previous_tokens.is_a?(Interface::SemanticTokens) &&
|
156
|
+
previous_tokens.result_id == @previous_result_id
|
157
|
+
Requests::SemanticHighlighting.compute_delta(encoded_tokens, previous_tokens.data, @result_id)
|
158
|
+
else
|
159
|
+
full_response
|
160
|
+
end
|
161
|
+
|
162
|
+
return response
|
163
|
+
end
|
164
|
+
|
165
|
+
# Semantic tokens full
|
166
|
+
full_response
|
60
167
|
end
|
61
168
|
end
|
62
169
|
end
|
@@ -37,15 +37,6 @@ module RubyLsp
|
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
|
-
sig { params(node: T.nilable(Prism::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
|
41
|
-
def visible?(node, range)
|
42
|
-
return true if range.nil?
|
43
|
-
return false if node.nil?
|
44
|
-
|
45
|
-
loc = node.location
|
46
|
-
range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
|
47
|
-
end
|
48
|
-
|
49
40
|
sig do
|
50
41
|
params(
|
51
42
|
node: Prism::Node,
|
@@ -91,9 +91,9 @@ module RubyLsp
|
|
91
91
|
@stack.last
|
92
92
|
end
|
93
93
|
|
94
|
-
sig { override.returns(
|
94
|
+
sig { override.returns(T::Array[SemanticToken]) }
|
95
95
|
def response
|
96
|
-
|
96
|
+
@stack
|
97
97
|
end
|
98
98
|
|
99
99
|
class SemanticToken
|
@@ -162,7 +162,7 @@ module RubyLsp
|
|
162
162
|
sig do
|
163
163
|
params(
|
164
164
|
tokens: T::Array[SemanticToken],
|
165
|
-
).returns(
|
165
|
+
).returns(T::Array[Integer])
|
166
166
|
end
|
167
167
|
def encode(tokens)
|
168
168
|
sorted_tokens = tokens.sort_by.with_index do |token, index|
|
@@ -176,7 +176,7 @@ module RubyLsp
|
|
176
176
|
compute_delta(token)
|
177
177
|
end
|
178
178
|
|
179
|
-
|
179
|
+
delta
|
180
180
|
end
|
181
181
|
|
182
182
|
# The delta array is computed according to the LSP specification:
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -23,6 +23,7 @@ module RubyLsp
|
|
23
23
|
run_initialize(message)
|
24
24
|
when "initialized"
|
25
25
|
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
26
|
+
|
26
27
|
run_initialized
|
27
28
|
when "textDocument/didOpen"
|
28
29
|
text_document_did_open(message)
|
@@ -40,6 +41,8 @@ module RubyLsp
|
|
40
41
|
text_document_code_lens(message)
|
41
42
|
when "textDocument/semanticTokens/full"
|
42
43
|
text_document_semantic_tokens_full(message)
|
44
|
+
when "textDocument/semanticTokens/full/delta"
|
45
|
+
text_document_semantic_tokens_delta(message)
|
43
46
|
when "textDocument/foldingRange"
|
44
47
|
text_document_folding_range(message)
|
45
48
|
when "textDocument/semanticTokens/range"
|
@@ -304,13 +307,26 @@ module RubyLsp
|
|
304
307
|
Document::LanguageId::Ruby
|
305
308
|
end
|
306
309
|
|
307
|
-
@store.set(
|
310
|
+
document = @store.set(
|
308
311
|
uri: text_document[:uri],
|
309
312
|
source: text_document[:text],
|
310
313
|
version: text_document[:version],
|
311
314
|
encoding: @global_state.encoding,
|
312
315
|
language_id: language_id,
|
313
316
|
)
|
317
|
+
|
318
|
+
if document.past_expensive_limit?
|
319
|
+
send_message(
|
320
|
+
Notification.new(
|
321
|
+
method: "window/showMessage",
|
322
|
+
params: Interface::ShowMessageParams.new(
|
323
|
+
type: Constant::MessageType::WARNING,
|
324
|
+
message: "This file is too long. For performance reasons, semantic highlighting and " \
|
325
|
+
"diagnostics will be disabled",
|
326
|
+
),
|
327
|
+
),
|
328
|
+
)
|
329
|
+
end
|
314
330
|
end
|
315
331
|
end
|
316
332
|
|
@@ -378,7 +394,7 @@ module RubyLsp
|
|
378
394
|
|
379
395
|
# If the response has already been cached by another request, return it
|
380
396
|
cached_response = document.cache_get(message[:method])
|
381
|
-
if cached_response
|
397
|
+
if cached_response != Document::EMPTY_CACHE
|
382
398
|
send_message(Result.new(id: message[:id], response: cached_response))
|
383
399
|
return
|
384
400
|
end
|
@@ -391,8 +407,6 @@ module RubyLsp
|
|
391
407
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
392
408
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
393
409
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
394
|
-
|
395
|
-
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
|
396
410
|
dispatcher.dispatch(parse_result.value)
|
397
411
|
|
398
412
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
@@ -401,19 +415,61 @@ module RubyLsp
|
|
401
415
|
document.cache_set("textDocument/documentSymbol", document_symbol.perform)
|
402
416
|
document.cache_set("textDocument/documentLink", document_link.perform)
|
403
417
|
document.cache_set("textDocument/codeLens", code_lens.perform)
|
404
|
-
|
405
|
-
"textDocument/semanticTokens/full",
|
406
|
-
semantic_highlighting.perform,
|
407
|
-
)
|
418
|
+
|
408
419
|
send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
|
409
420
|
end
|
410
421
|
|
411
422
|
alias_method :text_document_document_symbol, :run_combined_requests
|
412
423
|
alias_method :text_document_document_link, :run_combined_requests
|
413
424
|
alias_method :text_document_code_lens, :run_combined_requests
|
414
|
-
alias_method :text_document_semantic_tokens_full, :run_combined_requests
|
415
425
|
alias_method :text_document_folding_range, :run_combined_requests
|
416
426
|
|
427
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
428
|
+
def text_document_semantic_tokens_full(message)
|
429
|
+
document = @store.get(message.dig(:params, :textDocument, :uri))
|
430
|
+
|
431
|
+
if document.past_expensive_limit?
|
432
|
+
send_empty_response(message[:id])
|
433
|
+
return
|
434
|
+
end
|
435
|
+
|
436
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
437
|
+
send_empty_response(message[:id])
|
438
|
+
return
|
439
|
+
end
|
440
|
+
|
441
|
+
dispatcher = Prism::Dispatcher.new
|
442
|
+
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
|
443
|
+
dispatcher.visit(document.parse_result.value)
|
444
|
+
|
445
|
+
send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
|
446
|
+
end
|
447
|
+
|
448
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
449
|
+
def text_document_semantic_tokens_delta(message)
|
450
|
+
document = @store.get(message.dig(:params, :textDocument, :uri))
|
451
|
+
|
452
|
+
if document.past_expensive_limit?
|
453
|
+
send_empty_response(message[:id])
|
454
|
+
return
|
455
|
+
end
|
456
|
+
|
457
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
458
|
+
send_empty_response(message[:id])
|
459
|
+
return
|
460
|
+
end
|
461
|
+
|
462
|
+
dispatcher = Prism::Dispatcher.new
|
463
|
+
request = Requests::SemanticHighlighting.new(
|
464
|
+
@global_state,
|
465
|
+
dispatcher,
|
466
|
+
document,
|
467
|
+
message.dig(:params, :previousResultId),
|
468
|
+
)
|
469
|
+
dispatcher.visit(document.parse_result.value)
|
470
|
+
send_message(Result.new(id: message[:id], response: request.perform))
|
471
|
+
end
|
472
|
+
|
417
473
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
418
474
|
def text_document_semantic_tokens_range(message)
|
419
475
|
params = message[:params]
|
@@ -421,20 +477,26 @@ module RubyLsp
|
|
421
477
|
uri = params.dig(:textDocument, :uri)
|
422
478
|
document = @store.get(uri)
|
423
479
|
|
424
|
-
|
480
|
+
if document.past_expensive_limit?
|
425
481
|
send_empty_response(message[:id])
|
426
482
|
return
|
427
483
|
end
|
428
484
|
|
429
|
-
|
430
|
-
|
485
|
+
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
486
|
+
send_empty_response(message[:id])
|
487
|
+
return
|
488
|
+
end
|
431
489
|
|
432
490
|
dispatcher = Prism::Dispatcher.new
|
433
|
-
request = Requests::SemanticHighlighting.new(
|
491
|
+
request = Requests::SemanticHighlighting.new(
|
492
|
+
@global_state,
|
493
|
+
dispatcher,
|
494
|
+
document,
|
495
|
+
nil,
|
496
|
+
range: range.dig(:start, :line)..range.dig(:end, :line),
|
497
|
+
)
|
434
498
|
dispatcher.visit(document.parse_result.value)
|
435
|
-
|
436
|
-
response = request.perform
|
437
|
-
send_message(Result.new(id: message[:id], response: response))
|
499
|
+
send_message(Result.new(id: message[:id], response: request.perform))
|
438
500
|
end
|
439
501
|
|
440
502
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -66,7 +66,7 @@ module RubyLsp
|
|
66
66
|
version: Integer,
|
67
67
|
language_id: Document::LanguageId,
|
68
68
|
encoding: Encoding,
|
69
|
-
).
|
69
|
+
).returns(Document[T.untyped])
|
70
70
|
end
|
71
71
|
def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
|
72
72
|
@state[uri.to_s] = case language_id
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -20,8 +20,8 @@ module RubyLsp
|
|
20
20
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
21
21
|
&block)
|
22
22
|
server = RubyLsp::Server.new(test_mode: true)
|
23
|
-
server.global_state.stubs(:has_type_checker).returns(false) if stub_no_typechecker
|
24
23
|
server.global_state.apply_options({ initializationOptions: { experimentalFeaturesEnabled: true } })
|
24
|
+
server.global_state.instance_variable_set(:@has_type_checker, false) if stub_no_typechecker
|
25
25
|
language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
|
26
26
|
|
27
27
|
if source
|
@@ -36,9 +36,47 @@ module RubyLsp
|
|
36
36
|
def infer_receiver_for_call_node(node, node_context)
|
37
37
|
receiver = node.receiver
|
38
38
|
|
39
|
+
# For receivers inside parenthesis, such as ranges like (0...2), we need to unwrap the parenthesis to get the
|
40
|
+
# actual node
|
41
|
+
if receiver.is_a?(Prism::ParenthesesNode)
|
42
|
+
statements = receiver.body
|
43
|
+
|
44
|
+
if statements.is_a?(Prism::StatementsNode)
|
45
|
+
body = statements.body
|
46
|
+
|
47
|
+
if body.length == 1
|
48
|
+
receiver = body.first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
39
53
|
case receiver
|
40
54
|
when Prism::SelfNode, nil
|
41
55
|
self_receiver_handling(node_context)
|
56
|
+
when Prism::StringNode
|
57
|
+
Type.new("String")
|
58
|
+
when Prism::SymbolNode
|
59
|
+
Type.new("Symbol")
|
60
|
+
when Prism::ArrayNode
|
61
|
+
Type.new("Array")
|
62
|
+
when Prism::HashNode
|
63
|
+
Type.new("Hash")
|
64
|
+
when Prism::IntegerNode
|
65
|
+
Type.new("Integer")
|
66
|
+
when Prism::FloatNode
|
67
|
+
Type.new("Float")
|
68
|
+
when Prism::RegularExpressionNode
|
69
|
+
Type.new("Regexp")
|
70
|
+
when Prism::NilNode
|
71
|
+
Type.new("NilClass")
|
72
|
+
when Prism::TrueNode
|
73
|
+
Type.new("TrueClass")
|
74
|
+
when Prism::FalseNode
|
75
|
+
Type.new("FalseClass")
|
76
|
+
when Prism::RangeNode
|
77
|
+
Type.new("Range")
|
78
|
+
when Prism::LambdaNode
|
79
|
+
Type.new("Proc")
|
42
80
|
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
43
81
|
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
|
44
82
|
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.17.
|
4
|
+
version: 0.17.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -28,22 +28,16 @@ dependencies:
|
|
28
28
|
name: prism
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 0.29.0
|
34
|
-
- - "<"
|
31
|
+
- - "~>"
|
35
32
|
- !ruby/object:Gem::Version
|
36
|
-
version: '0
|
33
|
+
version: '1.0'
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
|
-
- - "
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: 0.29.0
|
44
|
-
- - "<"
|
38
|
+
- - "~>"
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0
|
40
|
+
version: '1.0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: rbs
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|