ruby-lsp 0.17.11 → 0.17.13
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 +4 -4
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +20 -3
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +23 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +76 -2
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_lsp/addon.rb +9 -4
- data/lib/ruby_lsp/base_server.rb +7 -2
- data/lib/ruby_lsp/document.rb +0 -30
- data/lib/ruby_lsp/global_state.rb +33 -13
- data/lib/ruby_lsp/listeners/completion.rb +5 -5
- data/lib/ruby_lsp/listeners/definition.rb +3 -3
- data/lib/ruby_lsp/listeners/hover.rb +9 -6
- data/lib/ruby_lsp/listeners/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/code_action_resolve.rb +105 -7
- data/lib/ruby_lsp/requests/code_actions.rb +6 -0
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -0
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +64 -0
- data/lib/ruby_lsp/server.rb +33 -11
- data/lib/ruby_lsp/utils.rb +12 -0
- metadata +5 -3
@@ -57,21 +57,48 @@ module RubyLsp
|
|
57
57
|
@linters.filter_map { |name| @supported_formatters[name] }
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
# Applies the options provided by the editor and returns an array of notifications to send back to the client
|
61
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Array[Notification]) }
|
61
62
|
def apply_options(options)
|
63
|
+
notifications = []
|
62
64
|
direct_dependencies = gather_direct_dependencies
|
63
65
|
all_dependencies = gather_direct_and_indirect_dependencies
|
64
66
|
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
|
65
67
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
66
68
|
|
67
69
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
68
|
-
|
69
|
-
|
70
|
+
|
71
|
+
if specified_formatter
|
72
|
+
@formatter = specified_formatter
|
73
|
+
|
74
|
+
if specified_formatter != "auto"
|
75
|
+
notifications << Notification.window_log_message("Using formatter specified by user: #{@formatter}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if @formatter == "auto"
|
80
|
+
@formatter = detect_formatter(direct_dependencies, all_dependencies)
|
81
|
+
notifications << Notification.window_log_message("Auto detected formatter: #{@formatter}")
|
82
|
+
end
|
70
83
|
|
71
84
|
specified_linters = options.dig(:initializationOptions, :linters)
|
72
85
|
@linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
|
86
|
+
|
87
|
+
notifications << if specified_linters
|
88
|
+
Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
|
89
|
+
else
|
90
|
+
Notification.window_log_message("Auto detected linters: #{@linters.join(", ")}")
|
91
|
+
end
|
92
|
+
|
73
93
|
@test_library = detect_test_library(direct_dependencies)
|
94
|
+
notifications << Notification.window_log_message("Detected test library: #{@test_library}")
|
95
|
+
|
74
96
|
@has_type_checker = detect_typechecker(direct_dependencies)
|
97
|
+
if @has_type_checker
|
98
|
+
notifications << Notification.window_log_message(
|
99
|
+
"Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
|
100
|
+
)
|
101
|
+
end
|
75
102
|
|
76
103
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
77
104
|
@encoding = if !encodings || encodings.empty?
|
@@ -91,6 +118,8 @@ module RubyLsp
|
|
91
118
|
|
92
119
|
@experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
93
120
|
@type_inferrer.experimental_features = @experimental_features
|
121
|
+
|
122
|
+
notifications
|
94
123
|
end
|
95
124
|
|
96
125
|
sig { returns(String) }
|
@@ -163,16 +192,7 @@ module RubyLsp
|
|
163
192
|
def detect_typechecker(dependencies)
|
164
193
|
return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
|
165
194
|
|
166
|
-
|
167
|
-
ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
|
168
|
-
Bundler.with_original_env do
|
169
|
-
sorbet_static_detected = dependencies.any?(/^sorbet-static/)
|
170
|
-
# Don't show message while running tests, since it's noisy
|
171
|
-
if sorbet_static_detected && !ruby_lsp_env_is_test
|
172
|
-
$stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
|
173
|
-
end
|
174
|
-
sorbet_static_detected
|
175
|
-
end
|
195
|
+
dependencies.any?(/^sorbet-static/)
|
176
196
|
rescue Bundler::GemfileNotFound
|
177
197
|
false
|
178
198
|
end
|
@@ -56,7 +56,7 @@ module RubyLsp
|
|
56
56
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
57
57
|
global_state: GlobalState,
|
58
58
|
node_context: NodeContext,
|
59
|
-
sorbet_level:
|
59
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
60
60
|
dispatcher: Prism::Dispatcher,
|
61
61
|
uri: URI::Generic,
|
62
62
|
trigger_character: T.nilable(String),
|
@@ -99,7 +99,7 @@ module RubyLsp
|
|
99
99
|
def on_constant_read_node_enter(node)
|
100
100
|
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
101
101
|
# no sigil, Sorbet will still provide completion for constants
|
102
|
-
return if @sorbet_level !=
|
102
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
103
103
|
|
104
104
|
name = constant_name(node)
|
105
105
|
return if name.nil?
|
@@ -122,7 +122,7 @@ module RubyLsp
|
|
122
122
|
def on_constant_path_node_enter(node)
|
123
123
|
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
124
124
|
# no sigil, Sorbet will still provide completion for constants
|
125
|
-
return if @sorbet_level !=
|
125
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
126
126
|
|
127
127
|
name = constant_name(node)
|
128
128
|
return if name.nil?
|
@@ -134,7 +134,7 @@ module RubyLsp
|
|
134
134
|
def on_call_node_enter(node)
|
135
135
|
# The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
|
136
136
|
# no sigil, Sorbet will still provide completion for constants
|
137
|
-
if @sorbet_level ==
|
137
|
+
if @sorbet_level == RubyDocument::SorbetLevel::Ignore
|
138
138
|
receiver = node.receiver
|
139
139
|
|
140
140
|
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
|
@@ -257,7 +257,7 @@ module RubyLsp
|
|
257
257
|
def handle_instance_variable_completion(name, location)
|
258
258
|
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
259
259
|
# to provide all features for them
|
260
|
-
return if @sorbet_level ==
|
260
|
+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
|
261
261
|
|
262
262
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
263
263
|
return unless type
|
@@ -20,7 +20,7 @@ module RubyLsp
|
|
20
20
|
uri: URI::Generic,
|
21
21
|
node_context: NodeContext,
|
22
22
|
dispatcher: Prism::Dispatcher,
|
23
|
-
sorbet_level:
|
23
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
24
24
|
).void
|
25
25
|
end
|
26
26
|
def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
@@ -181,7 +181,7 @@ module RubyLsp
|
|
181
181
|
def handle_instance_variable_definition(name)
|
182
182
|
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
183
183
|
# to provide all features for them
|
184
|
-
return if @sorbet_level ==
|
184
|
+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
|
185
185
|
|
186
186
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
187
187
|
return unless type
|
@@ -289,7 +289,7 @@ module RubyLsp
|
|
289
289
|
# additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
|
290
290
|
# ignore
|
291
291
|
file_path = entry.file_path
|
292
|
-
next if @sorbet_level !=
|
292
|
+
next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
|
293
293
|
|
294
294
|
@response_builder << Interface::LocationLink.new(
|
295
295
|
target_uri: URI::Generic.from_path(path: file_path).to_s,
|
@@ -42,7 +42,7 @@ module RubyLsp
|
|
42
42
|
uri: URI::Generic,
|
43
43
|
node_context: NodeContext,
|
44
44
|
dispatcher: Prism::Dispatcher,
|
45
|
-
sorbet_level:
|
45
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
46
46
|
).void
|
47
47
|
end
|
48
48
|
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
@@ -73,7 +73,7 @@ module RubyLsp
|
|
73
73
|
|
74
74
|
sig { params(node: Prism::ConstantReadNode).void }
|
75
75
|
def on_constant_read_node_enter(node)
|
76
|
-
return if @sorbet_level !=
|
76
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
77
77
|
|
78
78
|
name = constant_name(node)
|
79
79
|
return if name.nil?
|
@@ -83,14 +83,14 @@ module RubyLsp
|
|
83
83
|
|
84
84
|
sig { params(node: Prism::ConstantWriteNode).void }
|
85
85
|
def on_constant_write_node_enter(node)
|
86
|
-
return if @sorbet_level !=
|
86
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
87
87
|
|
88
88
|
generate_hover(node.name.to_s, node.name_loc)
|
89
89
|
end
|
90
90
|
|
91
91
|
sig { params(node: Prism::ConstantPathNode).void }
|
92
92
|
def on_constant_path_node_enter(node)
|
93
|
-
return if @sorbet_level !=
|
93
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
94
94
|
|
95
95
|
name = constant_name(node)
|
96
96
|
return if name.nil?
|
@@ -174,7 +174,10 @@ module RubyLsp
|
|
174
174
|
methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
|
175
175
|
return unless methods
|
176
176
|
|
177
|
-
|
177
|
+
first_method = T.must(methods.first)
|
178
|
+
|
179
|
+
title = "#{message}#{first_method.decorated_parameters}"
|
180
|
+
title << first_method.formatted_signatures
|
178
181
|
|
179
182
|
if type.is_a?(TypeInferrer::GuessedType)
|
180
183
|
title << "\n\nGuessed receiver: #{type.name}"
|
@@ -190,7 +193,7 @@ module RubyLsp
|
|
190
193
|
def handle_instance_variable_hover(name)
|
191
194
|
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
192
195
|
# to provide all features for them
|
193
|
-
return if @sorbet_level ==
|
196
|
+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
|
194
197
|
|
195
198
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
196
199
|
return unless type
|
@@ -13,7 +13,7 @@ module RubyLsp
|
|
13
13
|
global_state: GlobalState,
|
14
14
|
node_context: NodeContext,
|
15
15
|
dispatcher: Prism::Dispatcher,
|
16
|
-
sorbet_level:
|
16
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
17
17
|
).void
|
18
18
|
end
|
19
19
|
def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
|
@@ -23,6 +23,8 @@ module RubyLsp
|
|
23
23
|
#
|
24
24
|
class CodeActionResolve < Request
|
25
25
|
extend T::Sig
|
26
|
+
include Support::Common
|
27
|
+
|
26
28
|
NEW_VARIABLE_NAME = "new_variable"
|
27
29
|
NEW_METHOD_NAME = "new_method"
|
28
30
|
|
@@ -36,7 +38,7 @@ module RubyLsp
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
sig { params(document:
|
41
|
+
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
|
40
42
|
def initialize(document, code_action)
|
41
43
|
super()
|
42
44
|
@document = document
|
@@ -45,20 +47,62 @@ module RubyLsp
|
|
45
47
|
|
46
48
|
sig { override.returns(T.any(Interface::CodeAction, Error)) }
|
47
49
|
def perform
|
50
|
+
return Error::EmptySelection if @document.source.empty?
|
51
|
+
|
48
52
|
case @code_action[:title]
|
49
53
|
when CodeActions::EXTRACT_TO_VARIABLE_TITLE
|
50
54
|
refactor_variable
|
51
55
|
when CodeActions::EXTRACT_TO_METHOD_TITLE
|
52
56
|
refactor_method
|
57
|
+
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
|
58
|
+
switch_block_style
|
53
59
|
else
|
54
60
|
Error::UnknownCodeAction
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
64
|
+
private
|
65
|
+
|
58
66
|
sig { returns(T.any(Interface::CodeAction, Error)) }
|
59
|
-
def
|
60
|
-
|
67
|
+
def switch_block_style
|
68
|
+
source_range = @code_action.dig(:data, :range)
|
69
|
+
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
70
|
+
|
71
|
+
target = @document.locate_first_within_range(
|
72
|
+
@code_action.dig(:data, :range),
|
73
|
+
node_types: [Prism::CallNode],
|
74
|
+
)
|
75
|
+
|
76
|
+
return Error::InvalidTargetRange unless target.is_a?(Prism::CallNode)
|
77
|
+
|
78
|
+
node = target.block
|
79
|
+
return Error::InvalidTargetRange unless node.is_a?(Prism::BlockNode)
|
61
80
|
|
81
|
+
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
|
82
|
+
|
83
|
+
Interface::CodeAction.new(
|
84
|
+
title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
|
85
|
+
edit: Interface::WorkspaceEdit.new(
|
86
|
+
document_changes: [
|
87
|
+
Interface::TextDocumentEdit.new(
|
88
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
89
|
+
uri: @code_action.dig(:data, :uri),
|
90
|
+
version: nil,
|
91
|
+
),
|
92
|
+
edits: [
|
93
|
+
Interface::TextEdit.new(
|
94
|
+
range: range_from_location(node.location),
|
95
|
+
new_text: recursively_switch_nested_block_styles(node, indentation),
|
96
|
+
),
|
97
|
+
],
|
98
|
+
),
|
99
|
+
],
|
100
|
+
),
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { returns(T.any(Interface::CodeAction, Error)) }
|
105
|
+
def refactor_variable
|
62
106
|
source_range = @code_action.dig(:data, :range)
|
63
107
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
64
108
|
|
@@ -153,8 +197,6 @@ module RubyLsp
|
|
153
197
|
|
154
198
|
sig { returns(T.any(Interface::CodeAction, Error)) }
|
155
199
|
def refactor_method
|
156
|
-
return Error::EmptySelection if @document.source.empty?
|
157
|
-
|
158
200
|
source_range = @code_action.dig(:data, :range)
|
159
201
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
160
202
|
|
@@ -206,8 +248,6 @@ module RubyLsp
|
|
206
248
|
)
|
207
249
|
end
|
208
250
|
|
209
|
-
private
|
210
|
-
|
211
251
|
sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
|
212
252
|
def create_text_edit(range, new_text)
|
213
253
|
Interface::TextEdit.new(
|
@@ -218,6 +258,64 @@ module RubyLsp
|
|
218
258
|
new_text: new_text,
|
219
259
|
)
|
220
260
|
end
|
261
|
+
|
262
|
+
sig { params(node: Prism::BlockNode, indentation: T.nilable(String)).returns(String) }
|
263
|
+
def recursively_switch_nested_block_styles(node, indentation)
|
264
|
+
parameters = node.parameters
|
265
|
+
body = node.body
|
266
|
+
|
267
|
+
# We use the indentation to differentiate between do...end and brace style blocks because only the do...end
|
268
|
+
# style requires the indentation to build the edit.
|
269
|
+
#
|
270
|
+
# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
|
271
|
+
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
|
272
|
+
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
|
273
|
+
source = +""
|
274
|
+
|
275
|
+
if indentation
|
276
|
+
source << "do"
|
277
|
+
source << " #{parameters.slice}" if parameters
|
278
|
+
source << "\n#{indentation} "
|
279
|
+
source << switch_block_body(body, indentation) if body
|
280
|
+
source << "\n#{indentation}end"
|
281
|
+
else
|
282
|
+
source << "{ "
|
283
|
+
source << "#{parameters.slice} " if parameters
|
284
|
+
source << switch_block_body(body, nil) if body
|
285
|
+
source << "}"
|
286
|
+
end
|
287
|
+
|
288
|
+
source
|
289
|
+
end
|
290
|
+
|
291
|
+
sig { params(body: Prism::Node, indentation: T.nilable(String)).returns(String) }
|
292
|
+
def switch_block_body(body, indentation)
|
293
|
+
# Check if there are any nested blocks inside of the current block
|
294
|
+
body_loc = body.location
|
295
|
+
nested_block = @document.locate_first_within_range(
|
296
|
+
{
|
297
|
+
start: { line: body_loc.start_line - 1, character: body_loc.start_column },
|
298
|
+
end: { line: body_loc.end_line - 1, character: body_loc.end_column },
|
299
|
+
},
|
300
|
+
node_types: [Prism::BlockNode],
|
301
|
+
)
|
302
|
+
|
303
|
+
body_content = body.slice.dup
|
304
|
+
|
305
|
+
# If there are nested blocks, then we change their style too and we have to mutate the string using the
|
306
|
+
# relative position in respect to the beginning of the body
|
307
|
+
if nested_block.is_a?(Prism::BlockNode)
|
308
|
+
location = nested_block.location
|
309
|
+
correction_start = location.start_offset - body_loc.start_offset
|
310
|
+
correction_end = location.end_offset - body_loc.start_offset
|
311
|
+
next_indentation = indentation ? "#{indentation} " : nil
|
312
|
+
|
313
|
+
body_content[correction_start...correction_end] =
|
314
|
+
recursively_switch_nested_block_styles(nested_block, next_indentation)
|
315
|
+
end
|
316
|
+
|
317
|
+
indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
|
318
|
+
end
|
221
319
|
end
|
222
320
|
end
|
223
321
|
end
|
@@ -21,6 +21,7 @@ module RubyLsp
|
|
21
21
|
|
22
22
|
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
|
23
23
|
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
|
24
|
+
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
|
24
25
|
|
25
26
|
class << self
|
26
27
|
extend T::Sig
|
@@ -69,6 +70,11 @@ module RubyLsp
|
|
69
70
|
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
70
71
|
data: { range: @range, uri: @uri.to_s },
|
71
72
|
)
|
73
|
+
code_actions << Interface::CodeAction.new(
|
74
|
+
title: TOGGLE_BLOCK_STYLE_TITLE,
|
75
|
+
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
|
76
|
+
data: { range: @range, uri: @uri.to_s },
|
77
|
+
)
|
72
78
|
end
|
73
79
|
|
74
80
|
code_actions
|
@@ -42,7 +42,7 @@ module RubyLsp
|
|
42
42
|
global_state: GlobalState,
|
43
43
|
position: T::Hash[Symbol, T.untyped],
|
44
44
|
dispatcher: Prism::Dispatcher,
|
45
|
-
sorbet_level:
|
45
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
46
46
|
).void
|
47
47
|
end
|
48
48
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
global_state: GlobalState,
|
37
37
|
position: T::Hash[Symbol, T.untyped],
|
38
38
|
dispatcher: Prism::Dispatcher,
|
39
|
-
sorbet_level:
|
39
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
40
40
|
).void
|
41
41
|
end
|
42
42
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
@@ -46,7 +46,7 @@ module RubyLsp
|
|
46
46
|
position: T::Hash[Symbol, T.untyped],
|
47
47
|
context: T.nilable(T::Hash[Symbol, T.untyped]),
|
48
48
|
dispatcher: Prism::Dispatcher,
|
49
|
-
sorbet_level:
|
49
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
50
50
|
).void
|
51
51
|
end
|
52
52
|
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
@@ -209,9 +209,9 @@ module RubyLsp
|
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
-
sig { params(sorbet_level:
|
212
|
+
sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
|
213
213
|
def sorbet_level_true_or_higher?(sorbet_level)
|
214
|
-
sorbet_level ==
|
214
|
+
sorbet_level == RubyDocument::SorbetLevel::True || sorbet_level == RubyDocument::SorbetLevel::Strict
|
215
215
|
end
|
216
216
|
end
|
217
217
|
end
|
@@ -3,6 +3,16 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
class RubyDocument < Document
|
6
|
+
class SorbetLevel < T::Enum
|
7
|
+
enums do
|
8
|
+
None = new("none")
|
9
|
+
Ignore = new("ignore")
|
10
|
+
False = new("false")
|
11
|
+
True = new("true")
|
12
|
+
Strict = new("strict")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
6
16
|
sig { override.returns(Prism::ParseResult) }
|
7
17
|
def parse
|
8
18
|
return @parse_result unless @needs_parsing
|
@@ -20,5 +30,59 @@ module RubyLsp
|
|
20
30
|
def language_id
|
21
31
|
LanguageId::Ruby
|
22
32
|
end
|
33
|
+
|
34
|
+
sig { returns(SorbetLevel) }
|
35
|
+
def sorbet_level
|
36
|
+
sigil = parse_result.magic_comments.find do |comment|
|
37
|
+
comment.key == "typed"
|
38
|
+
end&.value
|
39
|
+
|
40
|
+
case sigil
|
41
|
+
when "ignore"
|
42
|
+
SorbetLevel::Ignore
|
43
|
+
when "false"
|
44
|
+
SorbetLevel::False
|
45
|
+
when "true"
|
46
|
+
SorbetLevel::True
|
47
|
+
when "strict", "strong"
|
48
|
+
SorbetLevel::Strict
|
49
|
+
else
|
50
|
+
SorbetLevel::None
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
range: T::Hash[Symbol, T.untyped],
|
57
|
+
node_types: T::Array[T.class_of(Prism::Node)],
|
58
|
+
).returns(T.nilable(Prism::Node))
|
59
|
+
end
|
60
|
+
def locate_first_within_range(range, node_types: [])
|
61
|
+
scanner = create_scanner
|
62
|
+
start_position = scanner.find_char_position(range[:start])
|
63
|
+
end_position = scanner.find_char_position(range[:end])
|
64
|
+
desired_range = (start_position...end_position)
|
65
|
+
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
66
|
+
|
67
|
+
until queue.empty?
|
68
|
+
candidate = queue.shift
|
69
|
+
|
70
|
+
# Skip nil child nodes
|
71
|
+
next if candidate.nil?
|
72
|
+
|
73
|
+
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
74
|
+
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
75
|
+
# sibling
|
76
|
+
T.unsafe(queue).unshift(*candidate.child_nodes)
|
77
|
+
|
78
|
+
# Skip if the current node doesn't cover the desired position
|
79
|
+
loc = candidate.location
|
80
|
+
|
81
|
+
if desired_range.cover?(loc.start_offset...loc.end_offset) &&
|
82
|
+
(node_types.empty? || node_types.any? { |type| candidate.class == type })
|
83
|
+
return candidate
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
23
87
|
end
|
24
88
|
end
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -19,10 +19,10 @@ module RubyLsp
|
|
19
19
|
def process_message(message)
|
20
20
|
case message[:method]
|
21
21
|
when "initialize"
|
22
|
-
|
22
|
+
send_log_message("Initializing Ruby LSP v#{VERSION}...")
|
23
23
|
run_initialize(message)
|
24
24
|
when "initialized"
|
25
|
-
|
25
|
+
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
26
26
|
run_initialized
|
27
27
|
when "textDocument/didOpen"
|
28
28
|
text_document_did_open(message)
|
@@ -121,12 +121,20 @@ module RubyLsp
|
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
124
|
-
|
124
|
+
send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
|
125
125
|
end
|
126
126
|
|
127
127
|
sig { void }
|
128
128
|
def load_addons
|
129
|
-
Addon.load_addons(@global_state, @outgoing_queue)
|
129
|
+
errors = Addon.load_addons(@global_state, @outgoing_queue)
|
130
|
+
|
131
|
+
if errors.any?
|
132
|
+
send_log_message(
|
133
|
+
"Error loading addons:\n\n#{errors.map(&:full_message).join("\n\n")}",
|
134
|
+
type: Constant::MessageType::WARNING,
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
130
138
|
errored_addons = Addon.addons.select(&:error?)
|
131
139
|
|
132
140
|
if errored_addons.any?
|
@@ -140,7 +148,12 @@ module RubyLsp
|
|
140
148
|
),
|
141
149
|
)
|
142
150
|
|
143
|
-
|
151
|
+
unless @test_mode
|
152
|
+
send_log_message(
|
153
|
+
errored_addons.map(&:errors_details).join("\n\n"),
|
154
|
+
type: Constant::MessageType::WARNING,
|
155
|
+
)
|
156
|
+
end
|
144
157
|
end
|
145
158
|
end
|
146
159
|
|
@@ -149,7 +162,7 @@ module RubyLsp
|
|
149
162
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
150
163
|
def run_initialize(message)
|
151
164
|
options = message[:params]
|
152
|
-
@global_state.apply_options(options)
|
165
|
+
global_state_notifications = @global_state.apply_options(options)
|
153
166
|
|
154
167
|
client_name = options.dig(:clientInfo, :name)
|
155
168
|
@store.client_name = client_name if client_name
|
@@ -258,6 +271,8 @@ module RubyLsp
|
|
258
271
|
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
259
272
|
|
260
273
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
274
|
+
|
275
|
+
global_state_notifications.each { |notification| send_message(notification) }
|
261
276
|
end
|
262
277
|
|
263
278
|
sig { void }
|
@@ -281,7 +296,7 @@ module RubyLsp
|
|
281
296
|
@mutex.synchronize do
|
282
297
|
text_document = message.dig(:params, :textDocument)
|
283
298
|
language_id = case text_document[:languageId]
|
284
|
-
when "erb"
|
299
|
+
when "erb", "eruby"
|
285
300
|
Document::LanguageId::ERB
|
286
301
|
else
|
287
302
|
Document::LanguageId::Ruby
|
@@ -480,9 +495,10 @@ module RubyLsp
|
|
480
495
|
)
|
481
496
|
end
|
482
497
|
|
483
|
-
sig { params(document: Document).returns(
|
498
|
+
sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
|
484
499
|
def sorbet_level(document)
|
485
|
-
return
|
500
|
+
return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
|
501
|
+
return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
|
486
502
|
|
487
503
|
document.sorbet_level
|
488
504
|
end
|
@@ -520,6 +536,12 @@ module RubyLsp
|
|
520
536
|
params = message[:params]
|
521
537
|
uri = URI(params.dig(:data, :uri))
|
522
538
|
document = @store.get(uri)
|
539
|
+
|
540
|
+
unless document.is_a?(RubyDocument)
|
541
|
+
send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
|
542
|
+
raise Requests::CodeActionResolve::CodeActionError
|
543
|
+
end
|
544
|
+
|
523
545
|
result = Requests::CodeActionResolve.new(document, params).perform
|
524
546
|
|
525
547
|
case result
|
@@ -862,7 +884,7 @@ module RubyLsp
|
|
862
884
|
|
863
885
|
if File.exist?(index_path)
|
864
886
|
begin
|
865
|
-
|
887
|
+
@global_state.index.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
|
866
888
|
send_message(
|
867
889
|
Notification.new(
|
868
890
|
method: "window/showMessage",
|
@@ -891,7 +913,7 @@ module RubyLsp
|
|
891
913
|
return unless indexing_options
|
892
914
|
|
893
915
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
894
|
-
|
916
|
+
@global_state.index.configuration.apply_config(
|
895
917
|
indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
|
896
918
|
)
|
897
919
|
end
|