ruby-lsp 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +24 -3
- data/exe/ruby-lsp-launcher +127 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +63 -12
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -2
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +21 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -21
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +2 -2
- data/lib/ruby_indexer/test/enhancements_test.rb +51 -19
- data/lib/ruby_indexer/test/index_test.rb +91 -2
- data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +26 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -2
- data/lib/ruby_lsp/base_server.rb +14 -5
- data/lib/ruby_lsp/client_capabilities.rb +60 -0
- data/lib/ruby_lsp/document.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +20 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +62 -0
- data/lib/ruby_lsp/listeners/definition.rb +48 -13
- data/lib/ruby_lsp/listeners/hover.rb +52 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/completion.rb +7 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +26 -11
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
- data/lib/ruby_lsp/requests/hover.rb +24 -6
- data/lib/ruby_lsp/requests/references.rb +2 -0
- data/lib/ruby_lsp/requests/rename.rb +3 -1
- data/lib/ruby_lsp/requests/request.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +11 -1
- data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
- data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
- data/lib/ruby_lsp/server.rb +54 -16
- data/lib/ruby_lsp/setup_bundler.rb +132 -24
- data/lib/ruby_lsp/utils.rb +8 -0
- metadata +8 -3
@@ -121,7 +121,7 @@ module RubyLsp
|
|
121
121
|
return Error::InvalidTargetRange if closest_node.is_a?(Prism::MissingNode)
|
122
122
|
|
123
123
|
closest_node_loc = closest_node.location
|
124
|
-
# If the parent expression is a single line block, then we have to extract it inside of the
|
124
|
+
# If the parent expression is a single line block, then we have to extract it inside of the one-line block
|
125
125
|
if parent_statements.is_a?(Prism::BlockNode) &&
|
126
126
|
parent_statements.location.start_line == parent_statements.location.end_line
|
127
127
|
|
@@ -17,7 +17,7 @@ module RubyLsp
|
|
17
17
|
def provider
|
18
18
|
Interface::CompletionOptions.new(
|
19
19
|
resolve_provider: true,
|
20
|
-
trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<"],
|
20
|
+
trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<", "$"],
|
21
21
|
completion_item: {
|
22
22
|
labelDetailsSupport: true,
|
23
23
|
},
|
@@ -50,6 +50,12 @@ module RubyLsp
|
|
50
50
|
Prism::CallNode,
|
51
51
|
Prism::ConstantReadNode,
|
52
52
|
Prism::ConstantPathNode,
|
53
|
+
Prism::GlobalVariableAndWriteNode,
|
54
|
+
Prism::GlobalVariableOperatorWriteNode,
|
55
|
+
Prism::GlobalVariableOrWriteNode,
|
56
|
+
Prism::GlobalVariableReadNode,
|
57
|
+
Prism::GlobalVariableTargetNode,
|
58
|
+
Prism::GlobalVariableWriteNode,
|
53
59
|
Prism::InstanceVariableReadNode,
|
54
60
|
Prism::InstanceVariableAndWriteNode,
|
55
61
|
Prism::InstanceVariableOperatorWriteNode,
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
|
35
35
|
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
36
36
|
# a completion resolve request must always return the original completion item without modifying ANY fields
|
37
|
-
# other than detail and documentation (NOT labelDetails). If we modify anything, the completion
|
37
|
+
# other than detail and documentation (NOT labelDetails). If we modify anything, the completion behavior might
|
38
38
|
# be broken.
|
39
39
|
#
|
40
40
|
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
@@ -12,12 +12,6 @@ module RubyLsp
|
|
12
12
|
extend T::Sig
|
13
13
|
extend T::Generic
|
14
14
|
|
15
|
-
SPECIAL_METHOD_CALLS = [
|
16
|
-
:require,
|
17
|
-
:require_relative,
|
18
|
-
:autoload,
|
19
|
-
].freeze
|
20
|
-
|
21
15
|
sig do
|
22
16
|
params(
|
23
17
|
document: T.any(RubyDocument, ERBDocument),
|
@@ -46,7 +40,12 @@ module RubyLsp
|
|
46
40
|
Prism::ConstantReadNode,
|
47
41
|
Prism::ConstantPathNode,
|
48
42
|
Prism::BlockArgumentNode,
|
43
|
+
Prism::GlobalVariableAndWriteNode,
|
44
|
+
Prism::GlobalVariableOperatorWriteNode,
|
45
|
+
Prism::GlobalVariableOrWriteNode,
|
49
46
|
Prism::GlobalVariableReadNode,
|
47
|
+
Prism::GlobalVariableTargetNode,
|
48
|
+
Prism::GlobalVariableWriteNode,
|
50
49
|
Prism::InstanceVariableReadNode,
|
51
50
|
Prism::InstanceVariableAndWriteNode,
|
52
51
|
Prism::InstanceVariableOperatorWriteNode,
|
@@ -72,11 +71,7 @@ module RubyLsp
|
|
72
71
|
parent,
|
73
72
|
position,
|
74
73
|
)
|
75
|
-
elsif
|
76
|
-
target.message_loc, position
|
77
|
-
)
|
78
|
-
# If the target is a method call, we need to ensure that the requested position is exactly on top of the
|
79
|
-
# method identifier. Otherwise, we risk showing definitions for unrelated things
|
74
|
+
elsif position_outside_target?(position, target)
|
80
75
|
target = nil
|
81
76
|
# For methods with block arguments using symbol-to-proc
|
82
77
|
elsif target.is_a?(Prism::SymbolNode) && parent.is_a?(Prism::BlockArgumentNode)
|
@@ -107,6 +102,26 @@ module RubyLsp
|
|
107
102
|
@dispatcher.dispatch_once(@target) if @target
|
108
103
|
@response_builder.response
|
109
104
|
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
|
109
|
+
def position_outside_target?(position, target)
|
110
|
+
case target
|
111
|
+
when Prism::GlobalVariableAndWriteNode,
|
112
|
+
Prism::GlobalVariableOperatorWriteNode,
|
113
|
+
Prism::GlobalVariableOrWriteNode,
|
114
|
+
Prism::GlobalVariableWriteNode,
|
115
|
+
Prism::InstanceVariableAndWriteNode,
|
116
|
+
Prism::InstanceVariableOperatorWriteNode,
|
117
|
+
Prism::InstanceVariableOrWriteNode,
|
118
|
+
Prism::InstanceVariableWriteNode
|
119
|
+
|
120
|
+
!covers_position?(target.name_loc, position)
|
121
|
+
else
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
110
125
|
end
|
111
126
|
end
|
112
127
|
end
|
@@ -10,7 +10,8 @@ module RubyLsp
|
|
10
10
|
# informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
|
11
11
|
# this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
|
12
12
|
#
|
13
|
-
# In VS Code,
|
13
|
+
# In VS Code, symbol search known as 'Go To Symbol in Editor' and can be accessed with Ctrl/Cmd-Shift-O,
|
14
|
+
# or by opening the command palette and inserting an `@` symbol.
|
14
15
|
class DocumentSymbol < Request
|
15
16
|
extend T::Sig
|
16
17
|
|
@@ -46,17 +46,13 @@ module RubyLsp
|
|
46
46
|
target = node_context.node
|
47
47
|
parent = node_context.parent
|
48
48
|
|
49
|
-
if
|
50
|
-
!Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
|
51
|
-
(parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
|
49
|
+
if should_refine_target?(parent, target)
|
52
50
|
target = determine_target(
|
53
51
|
T.must(target),
|
54
52
|
T.must(parent),
|
55
53
|
position,
|
56
54
|
)
|
57
|
-
elsif
|
58
|
-
!covers_position?(target.message_loc, position)
|
59
|
-
|
55
|
+
elsif position_outside_target?(position, target)
|
60
56
|
target = nil
|
61
57
|
end
|
62
58
|
|
@@ -89,6 +85,28 @@ module RubyLsp
|
|
89
85
|
),
|
90
86
|
)
|
91
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
sig { params(parent: T.nilable(Prism::Node), target: T.nilable(Prism::Node)).returns(T::Boolean) }
|
92
|
+
def should_refine_target?(parent, target)
|
93
|
+
(Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
|
94
|
+
!Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
|
95
|
+
(parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
|
99
|
+
def position_outside_target?(position, target)
|
100
|
+
case target
|
101
|
+
when Prism::GlobalVariableAndWriteNode,
|
102
|
+
Prism::GlobalVariableOperatorWriteNode,
|
103
|
+
Prism::GlobalVariableOrWriteNode,
|
104
|
+
Prism::GlobalVariableWriteNode
|
105
|
+
!covers_position?(target.name_loc, position)
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
92
110
|
end
|
93
111
|
end
|
94
112
|
end
|
@@ -78,6 +78,8 @@ module RubyLsp
|
|
78
78
|
|
79
79
|
parse_result = Prism.parse_file(path)
|
80
80
|
collect_references(reference_target, parse_result, uri)
|
81
|
+
rescue Errno::EISDIR, Errno::ENOENT
|
82
|
+
# If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
|
81
83
|
end
|
82
84
|
|
83
85
|
@store.each do |_uri, document|
|
@@ -72,7 +72,7 @@ module RubyLsp
|
|
72
72
|
|
73
73
|
# If the client doesn't support resource operations, such as renaming files, then we can only return the basic
|
74
74
|
# text changes
|
75
|
-
unless @global_state.
|
75
|
+
unless @global_state.client_capabilities.supports_rename?
|
76
76
|
return Interface::WorkspaceEdit.new(changes: changes)
|
77
77
|
end
|
78
78
|
|
@@ -147,6 +147,8 @@ module RubyLsp
|
|
147
147
|
parse_result = Prism.parse_file(path)
|
148
148
|
edits = collect_changes(target, parse_result, name, uri)
|
149
149
|
changes[uri.to_s] = edits unless edits.empty?
|
150
|
+
rescue Errno::EISDIR, Errno::ENOENT
|
151
|
+
# If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
|
150
152
|
end
|
151
153
|
|
152
154
|
@store.each do |uri, document|
|
@@ -26,7 +26,7 @@ module RubyLsp
|
|
26
26
|
).void
|
27
27
|
end
|
28
28
|
def delegate_request_if_needed!(global_state, document, char_position)
|
29
|
-
if global_state.supports_request_delegation &&
|
29
|
+
if global_state.client_capabilities.supports_request_delegation &&
|
30
30
|
document.is_a?(ERBDocument) &&
|
31
31
|
document.inside_host_language?(char_position)
|
32
32
|
raise DelegateRequestError
|
@@ -1,16 +1,26 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
# If there's no top level Gemfile, don't load RuboCop from a global installation
|
5
|
+
begin
|
6
|
+
Bundler.with_original_env { Bundler.default_gemfile }
|
7
|
+
rescue Bundler::GemfileNotFound
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
# Ensure that RuboCop is available
|
4
12
|
begin
|
5
13
|
require "rubocop"
|
6
14
|
rescue LoadError
|
7
15
|
return
|
8
16
|
end
|
9
17
|
|
18
|
+
# Ensure that RuboCop is at least version 1.4.0
|
10
19
|
begin
|
11
20
|
gem("rubocop", ">= 1.4.0")
|
12
21
|
rescue LoadError
|
13
|
-
|
22
|
+
$stderr.puts "Incompatible RuboCop version. Ruby LSP requires >= 1.4.0"
|
23
|
+
return
|
14
24
|
end
|
15
25
|
|
16
26
|
if RuboCop.const_defined?(:LSP) # This condition will be removed when requiring RuboCop >= 1.61.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def compose(raw_initialize)
|
5
|
+
require_relative "../setup_bundler"
|
6
|
+
require "json"
|
7
|
+
require "uri"
|
8
|
+
require_relative "../../core_ext/uri"
|
9
|
+
|
10
|
+
initialize_request = JSON.parse(raw_initialize, symbolize_names: true)
|
11
|
+
workspace_uri = initialize_request.dig(:params, :workspaceFolders, 0, :uri)
|
12
|
+
workspace_path = workspace_uri && URI(workspace_uri).to_standardized_path
|
13
|
+
workspace_path ||= Dir.pwd
|
14
|
+
|
15
|
+
env = RubyLsp::SetupBundler.new(workspace_path, launcher: true).setup!
|
16
|
+
File.write(
|
17
|
+
File.join(".ruby-lsp", "bundle_env"),
|
18
|
+
env.map { |k, v| "#{k}=#{v}" }.join("\n"),
|
19
|
+
)
|
20
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "compose_bundle"
|
5
|
+
|
6
|
+
# When this is invoked on Windows, we pass the raw initialize as an argument to this script. On other platforms, we
|
7
|
+
# invoke the compose method from inside a forked process
|
8
|
+
compose(ARGV.first)
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -81,6 +81,8 @@ module RubyLsp
|
|
81
81
|
workspace_did_change_watched_files(message)
|
82
82
|
when "workspace/symbol"
|
83
83
|
workspace_symbol(message)
|
84
|
+
when "window/showMessageRequest"
|
85
|
+
window_show_message_request(message)
|
84
86
|
when "rubyLsp/textDocument/showSyntaxTree"
|
85
87
|
text_document_show_syntax_tree(message)
|
86
88
|
when "rubyLsp/workspace/dependencies"
|
@@ -140,6 +142,11 @@ module RubyLsp
|
|
140
142
|
|
141
143
|
sig { params(include_project_addons: T::Boolean).void }
|
142
144
|
def load_addons(include_project_addons: true)
|
145
|
+
# If invoking Bundler.setup failed, then the load path will not be configured properly and trying to load add-ons
|
146
|
+
# with Gem.find_files will find every single version installed of an add-on, leading to requiring several
|
147
|
+
# different versions of the same files. We cannot load add-ons if Bundler.setup failed
|
148
|
+
return if @setup_error
|
149
|
+
|
143
150
|
errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
|
144
151
|
|
145
152
|
if errors.any?
|
@@ -254,12 +261,13 @@ module RubyLsp
|
|
254
261
|
version: VERSION,
|
255
262
|
},
|
256
263
|
formatter: @global_state.formatter,
|
264
|
+
degraded_mode: !!(@install_error || @setup_error),
|
257
265
|
}
|
258
266
|
|
259
267
|
send_message(Result.new(id: message[:id], response: response))
|
260
268
|
|
261
269
|
# Not every client supports dynamic registration or file watching
|
262
|
-
if global_state.supports_watching_files
|
270
|
+
if @global_state.client_capabilities.supports_watching_files
|
263
271
|
send_message(
|
264
272
|
Request.new(
|
265
273
|
id: @current_request_id,
|
@@ -290,6 +298,24 @@ module RubyLsp
|
|
290
298
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
291
299
|
|
292
300
|
global_state_notifications.each { |notification| send_message(notification) }
|
301
|
+
|
302
|
+
if @setup_error
|
303
|
+
send_message(Notification.telemetry(
|
304
|
+
type: "error",
|
305
|
+
errorMessage: @setup_error.message,
|
306
|
+
errorClass: @setup_error.class,
|
307
|
+
stack: @setup_error.backtrace&.join("\n"),
|
308
|
+
))
|
309
|
+
end
|
310
|
+
|
311
|
+
if @install_error
|
312
|
+
send_message(Notification.telemetry(
|
313
|
+
type: "error",
|
314
|
+
errorMessage: @install_error.message,
|
315
|
+
errorClass: @install_error.class,
|
316
|
+
stack: @install_error.backtrace&.join("\n"),
|
317
|
+
))
|
318
|
+
end
|
293
319
|
end
|
294
320
|
|
295
321
|
sig { void }
|
@@ -297,20 +323,22 @@ module RubyLsp
|
|
297
323
|
load_addons
|
298
324
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
299
325
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
326
|
+
unless @setup_error
|
327
|
+
if defined?(Requests::Support::RuboCopFormatter)
|
328
|
+
begin
|
329
|
+
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
330
|
+
rescue RuboCop::Error => e
|
331
|
+
# The user may have provided unknown config switches in .rubocop or
|
332
|
+
# is trying to load a non-existent config file.
|
333
|
+
send_message(Notification.window_show_message(
|
334
|
+
"RuboCop configuration error: #{e.message}. Formatting will not be available.",
|
335
|
+
type: Constant::MessageType::ERROR,
|
336
|
+
))
|
337
|
+
end
|
338
|
+
end
|
339
|
+
if defined?(Requests::Support::SyntaxTreeFormatter)
|
340
|
+
@global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
|
310
341
|
end
|
311
|
-
end
|
312
|
-
if defined?(Requests::Support::SyntaxTreeFormatter)
|
313
|
-
@global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
|
314
342
|
end
|
315
343
|
|
316
344
|
perform_initial_indexing
|
@@ -1017,7 +1045,7 @@ module RubyLsp
|
|
1017
1045
|
|
1018
1046
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1019
1047
|
def workspace_dependencies(message)
|
1020
|
-
response =
|
1048
|
+
response = if @global_state.top_level_bundle
|
1021
1049
|
Bundler.with_original_env do
|
1022
1050
|
definition = Bundler.definition
|
1023
1051
|
dep_keys = definition.locked_deps.keys.to_set
|
@@ -1031,7 +1059,7 @@ module RubyLsp
|
|
1031
1059
|
}
|
1032
1060
|
end
|
1033
1061
|
end
|
1034
|
-
|
1062
|
+
else
|
1035
1063
|
[]
|
1036
1064
|
end
|
1037
1065
|
|
@@ -1138,6 +1166,7 @@ module RubyLsp
|
|
1138
1166
|
|
1139
1167
|
sig { void }
|
1140
1168
|
def check_formatter_is_available
|
1169
|
+
return if @setup_error
|
1141
1170
|
# Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
|
1142
1171
|
# Syntax Tree will always be available via Ruby LSP so we don't need to check for it.
|
1143
1172
|
return unless @global_state.formatter == "rubocop"
|
@@ -1194,5 +1223,14 @@ module RubyLsp
|
|
1194
1223
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
1195
1224
|
configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
|
1196
1225
|
end
|
1226
|
+
|
1227
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1228
|
+
def window_show_message_request(message)
|
1229
|
+
addon_name = message[:addon_name]
|
1230
|
+
addon = Addon.addons.find { |addon| addon.name == addon_name }
|
1231
|
+
return unless addon
|
1232
|
+
|
1233
|
+
addon.handle_window_show_message_response(message[:title])
|
1234
|
+
end
|
1197
1235
|
end
|
1198
1236
|
end
|