ruby-lsp 0.23.1 → 0.23.3
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/exe/ruby-lsp-launcher +15 -9
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +89 -58
- 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 +86 -8
- data/lib/ruby_lsp/base_server.rb +9 -15
- data/lib/ruby_lsp/document.rb +62 -9
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/listeners/definition.rb +7 -2
- 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 +3 -3
- 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/requests/workspace_symbol.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +68 -48
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
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(
|
@@ -106,6 +110,19 @@ module RubyLsp
|
|
106
110
|
@version = version
|
107
111
|
@needs_parsing = true
|
108
112
|
@cache.clear
|
113
|
+
|
114
|
+
last_edit = edits.last
|
115
|
+
return unless last_edit
|
116
|
+
|
117
|
+
last_edit_range = last_edit[:range]
|
118
|
+
|
119
|
+
@last_edit = if last_edit_range[:start] == last_edit_range[:end]
|
120
|
+
Insert.new(last_edit_range)
|
121
|
+
elsif last_edit[:text].empty?
|
122
|
+
Delete.new(last_edit_range)
|
123
|
+
else
|
124
|
+
Replace.new(last_edit_range)
|
125
|
+
end
|
109
126
|
end
|
110
127
|
|
111
128
|
# Returns `true` if the document was parsed and `false` if nothing needed parsing
|
@@ -115,16 +132,52 @@ module RubyLsp
|
|
115
132
|
sig { abstract.returns(T::Boolean) }
|
116
133
|
def syntax_error?; end
|
117
134
|
|
135
|
+
sig { returns(T::Boolean) }
|
136
|
+
def past_expensive_limit?
|
137
|
+
@source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
|
138
|
+
end
|
139
|
+
|
140
|
+
sig do
|
141
|
+
params(
|
142
|
+
start_pos: T::Hash[Symbol, T.untyped],
|
143
|
+
end_pos: T.nilable(T::Hash[Symbol, T.untyped]),
|
144
|
+
).returns([Integer, T.nilable(Integer)])
|
145
|
+
end
|
146
|
+
def find_index_by_position(start_pos, end_pos = nil)
|
147
|
+
@global_state.synchronize do
|
148
|
+
scanner = create_scanner
|
149
|
+
start_index = scanner.find_char_position(start_pos)
|
150
|
+
end_index = scanner.find_char_position(end_pos) if end_pos
|
151
|
+
[start_index, end_index]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
118
157
|
sig { returns(Scanner) }
|
119
158
|
def create_scanner
|
120
159
|
Scanner.new(@source, @encoding)
|
121
160
|
end
|
122
161
|
|
123
|
-
|
124
|
-
|
125
|
-
|
162
|
+
class Edit
|
163
|
+
extend T::Sig
|
164
|
+
extend T::Helpers
|
165
|
+
|
166
|
+
abstract!
|
167
|
+
|
168
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
169
|
+
attr_reader :range
|
170
|
+
|
171
|
+
sig { params(range: T::Hash[Symbol, T.untyped]).void }
|
172
|
+
def initialize(range)
|
173
|
+
@range = range
|
174
|
+
end
|
126
175
|
end
|
127
176
|
|
177
|
+
class Insert < Edit; end
|
178
|
+
class Replace < Edit; end
|
179
|
+
class Delete < Edit; end
|
180
|
+
|
128
181
|
class Scanner
|
129
182
|
extend T::Sig
|
130
183
|
|
@@ -19,8 +19,8 @@ module RubyLsp
|
|
19
19
|
end
|
20
20
|
attr_reader :code_units_cache
|
21
21
|
|
22
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
23
|
-
def initialize(source:, version:, uri:,
|
22
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
23
|
+
def initialize(source:, version:, uri:, global_state:)
|
24
24
|
# This has to be initialized before calling super because we call `parse` in the parent constructor, which
|
25
25
|
# overrides this with the proper virtual host language source
|
26
26
|
@host_language_source = T.let("", String)
|
@@ -63,9 +63,11 @@ module RubyLsp
|
|
63
63
|
).returns(NodeContext)
|
64
64
|
end
|
65
65
|
def locate_node(position, node_types: [])
|
66
|
+
char_position, _ = find_index_by_position(position)
|
67
|
+
|
66
68
|
RubyDocument.locate(
|
67
69
|
@parse_result.value,
|
68
|
-
|
70
|
+
char_position,
|
69
71
|
code_units_cache: @code_units_cache,
|
70
72
|
node_types: node_types,
|
71
73
|
)
|
@@ -29,6 +29,9 @@ module RubyLsp
|
|
29
29
|
sig { returns(ClientCapabilities) }
|
30
30
|
attr_reader :client_capabilities
|
31
31
|
|
32
|
+
sig { returns(URI::Generic) }
|
33
|
+
attr_reader :workspace_uri
|
34
|
+
|
32
35
|
sig { void }
|
33
36
|
def initialize
|
34
37
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -53,6 +56,12 @@ module RubyLsp
|
|
53
56
|
)
|
54
57
|
@client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
|
55
58
|
@enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
|
59
|
+
@mutex = T.let(Mutex.new, Mutex)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
63
|
+
def synchronize(&block)
|
64
|
+
@mutex.synchronize(&block)
|
56
65
|
end
|
57
66
|
|
58
67
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -330,7 +330,8 @@ module RubyLsp
|
|
330
330
|
|
331
331
|
methods.each do |target_method|
|
332
332
|
uri = target_method.uri
|
333
|
-
|
333
|
+
full_path = uri.full_path
|
334
|
+
next if sorbet_level_true_or_higher?(@sorbet_level) && (!full_path || not_in_dependencies?(full_path))
|
334
335
|
|
335
336
|
@response_builder << Interface::LocationLink.new(
|
336
337
|
target_uri: uri.to_s,
|
@@ -403,7 +404,11 @@ module RubyLsp
|
|
403
404
|
# additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
|
404
405
|
# ignore
|
405
406
|
uri = entry.uri
|
406
|
-
|
407
|
+
full_path = uri.full_path
|
408
|
+
|
409
|
+
if @sorbet_level != RubyDocument::SorbetLevel::Ignore && (!full_path || not_in_dependencies?(full_path))
|
410
|
+
next
|
411
|
+
end
|
407
412
|
|
408
413
|
@response_builder << Interface::LocationLink.new(
|
409
414
|
target_uri: uri.to_s,
|
@@ -8,8 +8,8 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
|
10
10
|
|
11
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
12
|
-
def initialize(source:, version:, uri:,
|
11
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
12
|
+
def initialize(source:, version:, uri:, global_state:)
|
13
13
|
@syntax_error = T.let(false, T::Boolean)
|
14
14
|
super
|
15
15
|
end
|
@@ -92,9 +92,7 @@ module RubyLsp
|
|
92
92
|
source_range = @code_action.dig(:data, :range)
|
93
93
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
94
94
|
|
95
|
-
|
96
|
-
start_index = scanner.find_char_position(source_range[:start])
|
97
|
-
end_index = scanner.find_char_position(source_range[:end])
|
95
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
98
96
|
extracted_source = T.must(@document.source[start_index...end_index])
|
99
97
|
|
100
98
|
# Find the closest statements node, so that we place the refactor in a valid position
|
@@ -192,9 +190,7 @@ module RubyLsp
|
|
192
190
|
source_range = @code_action.dig(:data, :range)
|
193
191
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
194
192
|
|
195
|
-
|
196
|
-
start_index = scanner.find_char_position(source_range[:start])
|
197
|
-
end_index = scanner.find_char_position(source_range[:end])
|
193
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
198
194
|
extracted_source = T.must(@document.source[start_index...end_index])
|
199
195
|
|
200
196
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
@@ -40,7 +40,8 @@ module RubyLsp
|
|
40
40
|
@dispatcher = dispatcher
|
41
41
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
42
42
|
# back by 1, so that we find the right node
|
43
|
-
char_position = document.
|
43
|
+
char_position, _ = document.find_index_by_position(params[:position])
|
44
|
+
char_position -= 1
|
44
45
|
delegate_request_if_needed!(global_state, document, char_position)
|
45
46
|
|
46
47
|
node_context = RubyDocument.locate(
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
)
|
30
30
|
@dispatcher = dispatcher
|
31
31
|
|
32
|
-
char_position = document.
|
32
|
+
char_position, _ = document.find_index_by_position(position)
|
33
33
|
delegate_request_if_needed!(global_state, document, char_position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
end
|
26
26
|
def initialize(global_state, document, position, dispatcher)
|
27
27
|
super()
|
28
|
-
char_position = document.
|
28
|
+
char_position, _ = document.find_index_by_position(position)
|
29
29
|
delegate_request_if_needed!(global_state, document, char_position)
|
30
30
|
|
31
31
|
node_context = RubyDocument.locate(
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
35
35
|
super()
|
36
36
|
|
37
|
-
char_position = document.
|
37
|
+
char_position, _ = document.find_index_by_position(position)
|
38
38
|
delegate_request_if_needed!(global_state, document, char_position)
|
39
39
|
|
40
40
|
node_context = RubyDocument.locate(
|
@@ -24,7 +24,7 @@ module RubyLsp
|
|
24
24
|
|
25
25
|
sig { override.returns(T.nilable(Interface::Range)) }
|
26
26
|
def perform
|
27
|
-
char_position = @document.
|
27
|
+
char_position, _ = @document.find_index_by_position(@position)
|
28
28
|
|
29
29
|
node_context = RubyDocument.locate(
|
30
30
|
@document.parse_result.value,
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
sig { override.returns(T::Array[Interface::Location]) }
|
31
31
|
def perform
|
32
32
|
position = @params[:position]
|
33
|
-
char_position = @document.
|
33
|
+
char_position, _ = @document.find_index_by_position(position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
36
36
|
@document.parse_result.value,
|
@@ -40,7 +40,7 @@ module RubyLsp
|
|
40
40
|
|
41
41
|
sig { override.returns(T.nilable(Interface::WorkspaceEdit)) }
|
42
42
|
def perform
|
43
|
-
char_position = @document.
|
43
|
+
char_position, _ = @document.find_index_by_position(@position)
|
44
44
|
|
45
45
|
node_context = RubyDocument.locate(
|
46
46
|
@document.parse_result.value,
|
@@ -116,8 +116,8 @@ module RubyLsp
|
|
116
116
|
T.must(@global_state.index[fully_qualified_name]).each do |entry|
|
117
117
|
# Do not rename files that are not part of the workspace
|
118
118
|
uri = entry.uri
|
119
|
-
file_path =
|
120
|
-
next unless file_path
|
119
|
+
file_path = uri.full_path
|
120
|
+
next unless file_path&.start_with?(@global_state.workspace_path)
|
121
121
|
|
122
122
|
case entry
|
123
123
|
when RubyIndexer::Entry::Class, RubyIndexer::Entry::Module, RubyIndexer::Entry::Constant,
|
@@ -31,10 +31,7 @@ module RubyLsp
|
|
31
31
|
sig { returns(String) }
|
32
32
|
def ast_for_range
|
33
33
|
range = T.must(@range)
|
34
|
-
|
35
|
-
scanner = @document.create_scanner
|
36
|
-
start_char = scanner.find_char_position(range[:start])
|
37
|
-
end_char = scanner.find_char_position(range[:end])
|
34
|
+
start_char, end_char = @document.find_index_by_position(range[:start], range[:end])
|
38
35
|
|
39
36
|
queue = @tree.statements.body.dup
|
40
37
|
found_nodes = []
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
37
37
|
super()
|
38
38
|
|
39
|
-
char_position = document.
|
39
|
+
char_position, _ = document.find_index_by_position(position)
|
40
40
|
delegate_request_if_needed!(global_state, document, char_position)
|
41
41
|
|
42
42
|
node_context = RubyDocument.locate(
|
@@ -22,10 +22,10 @@ module RubyLsp
|
|
22
22
|
def perform
|
23
23
|
@index.fuzzy_search(@query).filter_map do |entry|
|
24
24
|
uri = entry.uri
|
25
|
-
file_path =
|
25
|
+
file_path = uri.full_path
|
26
26
|
|
27
27
|
# We only show symbols declared in the workspace
|
28
|
-
in_dependencies = !not_in_dependencies?(file_path)
|
28
|
+
in_dependencies = file_path && !not_in_dependencies?(file_path)
|
29
29
|
next if in_dependencies
|
30
30
|
|
31
31
|
# We should never show private symbols when searching the entire workspace
|
@@ -8,6 +8,22 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
10
|
|
11
|
+
METHODS_THAT_CHANGE_DECLARATIONS = [
|
12
|
+
:private_constant,
|
13
|
+
:attr_reader,
|
14
|
+
:attr_writer,
|
15
|
+
:attr_accessor,
|
16
|
+
:alias_method,
|
17
|
+
:include,
|
18
|
+
:prepend,
|
19
|
+
:extend,
|
20
|
+
:public,
|
21
|
+
:protected,
|
22
|
+
:private,
|
23
|
+
:module_function,
|
24
|
+
:private_class_method,
|
25
|
+
].freeze
|
26
|
+
|
11
27
|
class SorbetLevel < T::Enum
|
12
28
|
enums do
|
13
29
|
None = new("none")
|
@@ -142,8 +158,8 @@ module RubyLsp
|
|
142
158
|
end
|
143
159
|
attr_reader :code_units_cache
|
144
160
|
|
145
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
146
|
-
def initialize(source:, version:, uri:,
|
161
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
162
|
+
def initialize(source:, version:, uri:, global_state:)
|
147
163
|
super
|
148
164
|
@code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
|
149
165
|
T.proc.params(arg0: Integer).returns(Integer),
|
@@ -198,9 +214,8 @@ module RubyLsp
|
|
198
214
|
).returns(T.nilable(Prism::Node))
|
199
215
|
end
|
200
216
|
def locate_first_within_range(range, node_types: [])
|
201
|
-
|
202
|
-
|
203
|
-
end_position = scanner.find_char_position(range[:end])
|
217
|
+
start_position, end_position = find_index_by_position(range[:start], range[:end])
|
218
|
+
|
204
219
|
desired_range = (start_position...end_position)
|
205
220
|
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
206
221
|
|
@@ -232,12 +247,66 @@ module RubyLsp
|
|
232
247
|
).returns(NodeContext)
|
233
248
|
end
|
234
249
|
def locate_node(position, node_types: [])
|
250
|
+
char_position, _ = find_index_by_position(position)
|
251
|
+
|
235
252
|
RubyDocument.locate(
|
236
253
|
@parse_result.value,
|
237
|
-
|
254
|
+
char_position,
|
238
255
|
code_units_cache: @code_units_cache,
|
239
256
|
node_types: node_types,
|
240
257
|
)
|
241
258
|
end
|
259
|
+
|
260
|
+
sig { returns(T::Boolean) }
|
261
|
+
def last_edit_may_change_declarations?
|
262
|
+
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
263
|
+
# opened, we need to index it
|
264
|
+
return true unless @last_edit
|
265
|
+
|
266
|
+
case @last_edit
|
267
|
+
when Delete
|
268
|
+
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
269
|
+
# longer there and we don't remember the deleted text
|
270
|
+
true
|
271
|
+
when Insert, Replace
|
272
|
+
position_may_impact_declarations?(@last_edit.range[:start])
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
|
281
|
+
def position_may_impact_declarations?(position)
|
282
|
+
node_context = locate_node(position)
|
283
|
+
node_at_edit = node_context.node
|
284
|
+
|
285
|
+
# Adjust to the parent when editing the constant of a class/module declaration
|
286
|
+
if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
|
287
|
+
node_at_edit = node_context.parent
|
288
|
+
end
|
289
|
+
|
290
|
+
case node_at_edit
|
291
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
|
292
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
293
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
|
294
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
|
295
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
|
296
|
+
Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
297
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
298
|
+
Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
|
299
|
+
true
|
300
|
+
when Prism::MultiWriteNode
|
301
|
+
[*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
|
302
|
+
node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
303
|
+
end
|
304
|
+
when Prism::CallNode
|
305
|
+
receiver = node_at_edit.receiver
|
306
|
+
(!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
|
307
|
+
else
|
308
|
+
false
|
309
|
+
end
|
310
|
+
end
|
242
311
|
end
|
243
312
|
end
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -107,7 +107,7 @@ module RubyLsp
|
|
107
107
|
),
|
108
108
|
)
|
109
109
|
when "$/cancelRequest"
|
110
|
-
@
|
110
|
+
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
|
111
111
|
when nil
|
112
112
|
process_response(message) if message[:result]
|
113
113
|
end
|
@@ -298,29 +298,11 @@ module RubyLsp
|
|
298
298
|
|
299
299
|
# Not every client supports dynamic registration or file watching
|
300
300
|
if @global_state.client_capabilities.supports_watching_files
|
301
|
-
send_message(
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
registrations: [
|
307
|
-
# Register watching Ruby files
|
308
|
-
Interface::Registration.new(
|
309
|
-
id: "workspace/didChangeWatchedFiles",
|
310
|
-
method: "workspace/didChangeWatchedFiles",
|
311
|
-
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
312
|
-
watchers: [
|
313
|
-
Interface::FileSystemWatcher.new(
|
314
|
-
glob_pattern: "**/*.rb",
|
315
|
-
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
316
|
-
),
|
317
|
-
],
|
318
|
-
),
|
319
|
-
),
|
320
|
-
],
|
321
|
-
),
|
322
|
-
),
|
323
|
-
)
|
301
|
+
send_message(Request.register_watched_files(@current_request_id, "**/*.rb"))
|
302
|
+
send_message(Request.register_watched_files(
|
303
|
+
@current_request_id,
|
304
|
+
Interface::RelativePattern.new(base_uri: @global_state.workspace_uri.to_s, pattern: ".rubocop.yml"),
|
305
|
+
))
|
324
306
|
end
|
325
307
|
|
326
308
|
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
@@ -377,7 +359,7 @@ module RubyLsp
|
|
377
359
|
|
378
360
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
379
361
|
def text_document_did_open(message)
|
380
|
-
@
|
362
|
+
@global_state.synchronize do
|
381
363
|
text_document = message.dig(:params, :textDocument)
|
382
364
|
language_id = case text_document[:languageId]
|
383
365
|
when "erb", "eruby"
|
@@ -392,7 +374,6 @@ module RubyLsp
|
|
392
374
|
uri: text_document[:uri],
|
393
375
|
source: text_document[:text],
|
394
376
|
version: text_document[:version],
|
395
|
-
encoding: @global_state.encoding,
|
396
377
|
language_id: language_id,
|
397
378
|
)
|
398
379
|
|
@@ -417,17 +398,12 @@ module RubyLsp
|
|
417
398
|
|
418
399
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
419
400
|
def text_document_did_close(message)
|
420
|
-
@
|
401
|
+
@global_state.synchronize do
|
421
402
|
uri = message.dig(:params, :textDocument, :uri)
|
422
403
|
@store.delete(uri)
|
423
404
|
|
424
405
|
# Clear diagnostics for the closed file, so that they no longer appear in the problems tab
|
425
|
-
send_message(
|
426
|
-
Notification.new(
|
427
|
-
method: "textDocument/publishDiagnostics",
|
428
|
-
params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
|
429
|
-
),
|
430
|
-
)
|
406
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
431
407
|
end
|
432
408
|
end
|
433
409
|
|
@@ -436,7 +412,7 @@ module RubyLsp
|
|
436
412
|
params = message[:params]
|
437
413
|
text_document = params[:textDocument]
|
438
414
|
|
439
|
-
@
|
415
|
+
@global_state.synchronize do
|
440
416
|
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
441
417
|
end
|
442
418
|
end
|
@@ -493,7 +469,22 @@ module RubyLsp
|
|
493
469
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
494
470
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
495
471
|
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
|
496
|
-
|
472
|
+
|
473
|
+
if document.is_a?(RubyDocument) && document.last_edit_may_change_declarations?
|
474
|
+
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
475
|
+
# updated on save
|
476
|
+
@global_state.synchronize do
|
477
|
+
send_log_message("Detected that last edit may have modified declarations. Re-indexing #{uri}")
|
478
|
+
|
479
|
+
@global_state.index.handle_change(uri) do |index|
|
480
|
+
index.delete(uri, skip_require_paths_tree: true)
|
481
|
+
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
482
|
+
dispatcher.dispatch(parse_result.value)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
else
|
486
|
+
dispatcher.dispatch(parse_result.value)
|
487
|
+
end
|
497
488
|
|
498
489
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
499
490
|
# we actually received
|
@@ -1011,26 +1002,55 @@ module RubyLsp
|
|
1011
1002
|
uri = URI(change[:uri])
|
1012
1003
|
file_path = uri.to_standardized_path
|
1013
1004
|
next if file_path.nil? || File.directory?(file_path)
|
1014
|
-
next unless file_path.end_with?(".rb")
|
1015
1005
|
|
1016
|
-
|
1017
|
-
|
1006
|
+
if file_path.end_with?(".rb")
|
1007
|
+
handle_ruby_file_change(index, file_path, change[:type])
|
1008
|
+
next
|
1009
|
+
end
|
1018
1010
|
|
1019
|
-
|
1011
|
+
file_name = File.basename(file_path)
|
1020
1012
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
index.index_single(uri, content)
|
1024
|
-
when Constant::FileChangeType::CHANGED
|
1025
|
-
index.handle_change(uri, content)
|
1026
|
-
when Constant::FileChangeType::DELETED
|
1027
|
-
index.delete(uri)
|
1013
|
+
if file_name == ".rubocop.yml" || file_name == ".rubocop"
|
1014
|
+
handle_rubocop_config_change(uri)
|
1028
1015
|
end
|
1029
1016
|
end
|
1030
1017
|
|
1031
1018
|
Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
|
1032
1019
|
end
|
1033
1020
|
|
1021
|
+
sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
|
1022
|
+
def handle_ruby_file_change(index, file_path, change_type)
|
1023
|
+
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
|
1024
|
+
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
|
1025
|
+
|
1026
|
+
content = File.read(file_path)
|
1027
|
+
|
1028
|
+
case change_type
|
1029
|
+
when Constant::FileChangeType::CREATED
|
1030
|
+
index.index_single(uri, content)
|
1031
|
+
when Constant::FileChangeType::CHANGED
|
1032
|
+
index.handle_change(uri, content)
|
1033
|
+
when Constant::FileChangeType::DELETED
|
1034
|
+
index.delete(uri)
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
sig { params(uri: URI::Generic).void }
|
1039
|
+
def handle_rubocop_config_change(uri)
|
1040
|
+
return unless defined?(Requests::Support::RuboCopFormatter)
|
1041
|
+
|
1042
|
+
send_log_message("Reloading RuboCop since #{uri} changed")
|
1043
|
+
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
1044
|
+
|
1045
|
+
# Clear all existing diagnostics since the config changed. This has to happen under a mutex because the `state`
|
1046
|
+
# hash cannot be mutated during iteration or that will throw an error
|
1047
|
+
@global_state.synchronize do
|
1048
|
+
@store.each do |uri, _document|
|
1049
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1034
1054
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1035
1055
|
def workspace_symbol(message)
|
1036
1056
|
send_message(
|
@@ -1240,10 +1260,10 @@ module RubyLsp
|
|
1240
1260
|
return
|
1241
1261
|
end
|
1242
1262
|
|
1243
|
-
return unless indexing_options
|
1244
|
-
|
1245
1263
|
configuration = @global_state.index.configuration
|
1246
1264
|
configuration.workspace_path = @global_state.workspace_path
|
1265
|
+
return unless indexing_options
|
1266
|
+
|
1247
1267
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
1248
1268
|
configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
|
1249
1269
|
end
|