ruby-lsp 0.17.12 → 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 +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +6 -1
- data/lib/ruby_indexer/ruby_indexer.rb +0 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -4
- data/lib/ruby_lsp/base_server.rb +7 -2
- data/lib/ruby_lsp/document.rb +0 -64
- 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 +5 -5
- data/lib/ruby_lsp/listeners/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_actions.rb +2 -2
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- 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 +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
|
4
|
+
data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
|
7
|
+
data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.13
|
data/exe/ruby-lsp
CHANGED
@@ -112,20 +112,20 @@ if options[:time_index]
|
|
112
112
|
end
|
113
113
|
|
114
114
|
if options[:doctor]
|
115
|
+
index = RubyIndexer::Index.new
|
116
|
+
|
115
117
|
if File.exist?(".index.yml")
|
116
118
|
begin
|
117
119
|
config = YAML.parse_file(".index.yml").to_ruby
|
118
120
|
rescue => e
|
119
121
|
abort("Error parsing config: #{e.message}")
|
120
122
|
end
|
121
|
-
|
123
|
+
index.configuration.apply_config(config)
|
122
124
|
end
|
123
125
|
|
124
|
-
index = RubyIndexer::Index.new
|
125
|
-
|
126
126
|
puts "Globbing for indexable files"
|
127
127
|
|
128
|
-
|
128
|
+
index.configuration.indexables.each do |indexable|
|
129
129
|
puts "indexing: #{indexable.full_path}"
|
130
130
|
index.index_single(indexable)
|
131
131
|
end
|
data/exe/ruby-lsp-check
CHANGED
@@ -44,7 +44,7 @@ puts "\n"
|
|
44
44
|
puts "Verifying that indexing executes successfully. This may take a while..."
|
45
45
|
|
46
46
|
index = RubyIndexer::Index.new
|
47
|
-
indexables =
|
47
|
+
indexables = index.configuration.indexables
|
48
48
|
|
49
49
|
indexables.each_with_index do |indexable, i|
|
50
50
|
index.index_single(indexable)
|
@@ -552,7 +552,7 @@ module RubyIndexer
|
|
552
552
|
comment_content = comment.location.slice.chomp
|
553
553
|
|
554
554
|
# invalid encodings would raise an "invalid byte sequence" exception
|
555
|
-
if !comment_content.valid_encoding? || comment_content.match?(
|
555
|
+
if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
|
556
556
|
next
|
557
557
|
end
|
558
558
|
|
@@ -11,6 +11,9 @@ module RubyIndexer
|
|
11
11
|
# The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
|
12
12
|
ENTRY_SIMILARITY_THRESHOLD = 0.7
|
13
13
|
|
14
|
+
sig { returns(Configuration) }
|
15
|
+
attr_reader :configuration
|
16
|
+
|
14
17
|
sig { void }
|
15
18
|
def initialize
|
16
19
|
# Holds all entries in the index using the following format:
|
@@ -44,6 +47,8 @@ module RubyIndexer
|
|
44
47
|
{},
|
45
48
|
T::Hash[String, T::Array[T.proc.params(index: Index, base: Entry::Namespace).void]],
|
46
49
|
)
|
50
|
+
|
51
|
+
@configuration = T.let(RubyIndexer::Configuration.new, Configuration)
|
47
52
|
end
|
48
53
|
|
49
54
|
# Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
|
@@ -296,7 +301,7 @@ module RubyIndexer
|
|
296
301
|
block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
|
297
302
|
).void
|
298
303
|
end
|
299
|
-
def index_all(indexable_paths:
|
304
|
+
def index_all(indexable_paths: @configuration.indexables, &block)
|
300
305
|
RBSIndexer.new(self).index_ruby_core
|
301
306
|
# Calculate how many paths are worth 1% of progress
|
302
307
|
progress_step = (indexable_paths.length / 100.0).ceil
|
@@ -15,12 +15,4 @@ require "ruby_indexer/lib/ruby_indexer/location"
|
|
15
15
|
require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
|
16
16
|
|
17
17
|
module RubyIndexer
|
18
|
-
@configuration = T.let(Configuration.new, Configuration)
|
19
|
-
|
20
|
-
class << self
|
21
|
-
extend T::Sig
|
22
|
-
|
23
|
-
sig { returns(Configuration) }
|
24
|
-
attr_reader :configuration
|
25
|
-
end
|
26
18
|
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -49,15 +49,18 @@ module RubyLsp
|
|
49
49
|
super
|
50
50
|
end
|
51
51
|
|
52
|
-
# Discovers and loads all addons. Returns
|
53
|
-
sig
|
52
|
+
# Discovers and loads all addons. Returns a list of errors when trying to require addons
|
53
|
+
sig do
|
54
|
+
params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
|
55
|
+
end
|
54
56
|
def load_addons(global_state, outgoing_queue)
|
55
57
|
# Require all addons entry points, which should be placed under
|
56
58
|
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
57
|
-
Gem.find_files("ruby_lsp/**/addon.rb").
|
59
|
+
errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
|
58
60
|
require File.expand_path(addon)
|
61
|
+
nil
|
59
62
|
rescue => e
|
60
|
-
|
63
|
+
e
|
61
64
|
end
|
62
65
|
|
63
66
|
# Instantiate all discovered addon classes
|
@@ -71,6 +74,8 @@ module RubyLsp
|
|
71
74
|
rescue => e
|
72
75
|
addon.add_error(e)
|
73
76
|
end
|
77
|
+
|
78
|
+
errors
|
74
79
|
end
|
75
80
|
|
76
81
|
# Intended for use by tests for addons
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -65,7 +65,7 @@ module RubyLsp
|
|
65
65
|
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
66
66
|
process_message(message)
|
67
67
|
when "shutdown"
|
68
|
-
|
68
|
+
send_log_message("Shutting down Ruby LSP...")
|
69
69
|
|
70
70
|
shutdown
|
71
71
|
|
@@ -76,7 +76,7 @@ module RubyLsp
|
|
76
76
|
when "exit"
|
77
77
|
@mutex.synchronize do
|
78
78
|
status = @incoming_queue.closed? ? 0 : 1
|
79
|
-
|
79
|
+
send_log_message("Shutdown complete with status #{status}")
|
80
80
|
exit(status)
|
81
81
|
end
|
82
82
|
else
|
@@ -145,5 +145,10 @@ module RubyLsp
|
|
145
145
|
def send_empty_response(id)
|
146
146
|
send_message(Result.new(id: id, response: nil))
|
147
147
|
end
|
148
|
+
|
149
|
+
sig { params(message: String, type: Integer).void }
|
150
|
+
def send_log_message(message, type: Constant::MessageType::LOG)
|
151
|
+
send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
|
152
|
+
end
|
148
153
|
end
|
149
154
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -10,16 +10,6 @@ module RubyLsp
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
class SorbetLevel < T::Enum
|
14
|
-
enums do
|
15
|
-
None = new("none")
|
16
|
-
Ignore = new("ignore")
|
17
|
-
False = new("false")
|
18
|
-
True = new("true")
|
19
|
-
Strict = new("strict")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
13
|
extend T::Sig
|
24
14
|
extend T::Helpers
|
25
15
|
|
@@ -223,60 +213,6 @@ module RubyLsp
|
|
223
213
|
NodeContext.new(closest, parent, nesting_nodes, call_node)
|
224
214
|
end
|
225
215
|
|
226
|
-
sig do
|
227
|
-
params(
|
228
|
-
range: T::Hash[Symbol, T.untyped],
|
229
|
-
node_types: T::Array[T.class_of(Prism::Node)],
|
230
|
-
).returns(T.nilable(Prism::Node))
|
231
|
-
end
|
232
|
-
def locate_first_within_range(range, node_types: [])
|
233
|
-
scanner = create_scanner
|
234
|
-
start_position = scanner.find_char_position(range[:start])
|
235
|
-
end_position = scanner.find_char_position(range[:end])
|
236
|
-
desired_range = (start_position...end_position)
|
237
|
-
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
238
|
-
|
239
|
-
until queue.empty?
|
240
|
-
candidate = queue.shift
|
241
|
-
|
242
|
-
# Skip nil child nodes
|
243
|
-
next if candidate.nil?
|
244
|
-
|
245
|
-
# Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
|
246
|
-
# same order as the visiting mechanism, which means searching the child nodes before moving on to the next
|
247
|
-
# sibling
|
248
|
-
T.unsafe(queue).unshift(*candidate.child_nodes)
|
249
|
-
|
250
|
-
# Skip if the current node doesn't cover the desired position
|
251
|
-
loc = candidate.location
|
252
|
-
|
253
|
-
if desired_range.cover?(loc.start_offset...loc.end_offset) &&
|
254
|
-
(node_types.empty? || node_types.any? { |type| candidate.class == type })
|
255
|
-
return candidate
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
sig { returns(SorbetLevel) }
|
261
|
-
def sorbet_level
|
262
|
-
sigil = parse_result.magic_comments.find do |comment|
|
263
|
-
comment.key == "typed"
|
264
|
-
end&.value
|
265
|
-
|
266
|
-
case sigil
|
267
|
-
when "ignore"
|
268
|
-
SorbetLevel::Ignore
|
269
|
-
when "false"
|
270
|
-
SorbetLevel::False
|
271
|
-
when "true"
|
272
|
-
SorbetLevel::True
|
273
|
-
when "strict", "strong"
|
274
|
-
SorbetLevel::Strict
|
275
|
-
else
|
276
|
-
SorbetLevel::None
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
216
|
class Scanner
|
281
217
|
extend T::Sig
|
282
218
|
|
@@ -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?
|
@@ -193,7 +193,7 @@ module RubyLsp
|
|
193
193
|
def handle_instance_variable_hover(name)
|
194
194
|
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
195
195
|
# to provide all features for them
|
196
|
-
return if @sorbet_level ==
|
196
|
+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
|
197
197
|
|
198
198
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
199
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)
|
@@ -38,7 +38,7 @@ module RubyLsp
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
sig { params(document:
|
41
|
+
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
|
42
42
|
def initialize(document, code_action)
|
43
43
|
super()
|
44
44
|
@document = document
|
@@ -54,7 +54,7 @@ module RubyLsp
|
|
54
54
|
refactor_variable
|
55
55
|
when CodeActions::EXTRACT_TO_METHOD_TITLE
|
56
56
|
refactor_method
|
57
|
-
when CodeActions::
|
57
|
+
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
|
58
58
|
switch_block_style
|
59
59
|
else
|
60
60
|
Error::UnknownCodeAction
|
@@ -81,7 +81,7 @@ module RubyLsp
|
|
81
81
|
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
|
82
82
|
|
83
83
|
Interface::CodeAction.new(
|
84
|
-
title: CodeActions::
|
84
|
+
title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
|
85
85
|
edit: Interface::WorkspaceEdit.new(
|
86
86
|
document_changes: [
|
87
87
|
Interface::TextDocumentEdit.new(
|
@@ -21,7 +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
|
-
|
24
|
+
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
|
25
25
|
|
26
26
|
class << self
|
27
27
|
extend T::Sig
|
@@ -71,7 +71,7 @@ module RubyLsp
|
|
71
71
|
data: { range: @range, uri: @uri.to_s },
|
72
72
|
)
|
73
73
|
code_actions << Interface::CodeAction.new(
|
74
|
-
title:
|
74
|
+
title: TOGGLE_BLOCK_STYLE_TITLE,
|
75
75
|
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
|
76
76
|
data: { range: @range, uri: @uri.to_s },
|
77
77
|
)
|
@@ -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
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -53,6 +53,7 @@ module RubyLsp
|
|
53
53
|
class Notification < Message
|
54
54
|
class << self
|
55
55
|
extend T::Sig
|
56
|
+
|
56
57
|
sig { params(message: String).returns(Notification) }
|
57
58
|
def window_show_error(message)
|
58
59
|
new(
|
@@ -63,6 +64,14 @@ module RubyLsp
|
|
63
64
|
),
|
64
65
|
)
|
65
66
|
end
|
67
|
+
|
68
|
+
sig { params(message: String, type: Integer).returns(Notification) }
|
69
|
+
def window_log_message(message, type: Constant::MessageType::LOG)
|
70
|
+
new(
|
71
|
+
method: "window/logMessage",
|
72
|
+
params: Interface::LogMessageParams.new(type: type, message: message),
|
73
|
+
)
|
74
|
+
end
|
66
75
|
end
|
67
76
|
|
68
77
|
extend T::Sig
|
@@ -122,6 +131,9 @@ module RubyLsp
|
|
122
131
|
sig { returns(T.untyped) }
|
123
132
|
attr_reader :response
|
124
133
|
|
134
|
+
sig { returns(Integer) }
|
135
|
+
attr_reader :id
|
136
|
+
|
125
137
|
sig { params(id: Integer, response: T.untyped).void }
|
126
138
|
def initialize(id:, response:)
|
127
139
|
@id = id
|
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.13
|
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-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
206
|
- !ruby/object:Gem::Version
|
207
207
|
version: '0'
|
208
208
|
requirements: []
|
209
|
-
rubygems_version: 3.5.
|
209
|
+
rubygems_version: 3.5.17
|
210
210
|
signing_key:
|
211
211
|
specification_version: 4
|
212
212
|
summary: An opinionated language server for Ruby
|