ruby-lsp 0.18.3 → 0.19.1
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-check +1 -1
- data/lib/core_ext/uri.rb +9 -4
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +63 -32
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -5
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +52 -8
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +324 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -0
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
- data/lib/ruby_indexer/test/index_test.rb +3 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
- data/lib/ruby_indexer/test/method_test.rb +10 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +22 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +242 -0
- data/lib/ruby_lsp/addon.rb +79 -17
- data/lib/ruby_lsp/base_server.rb +6 -0
- data/lib/ruby_lsp/erb_document.rb +9 -3
- data/lib/ruby_lsp/global_state.rb +8 -0
- data/lib/ruby_lsp/internal.rb +5 -1
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +57 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
- data/lib/ruby_lsp/requests/code_action_resolve.rb +9 -3
- data/lib/ruby_lsp/requests/completion.rb +1 -0
- data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
- data/lib/ruby_lsp/requests/definition.rb +1 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -0
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/range_formatting.rb +55 -0
- data/lib/ruby_lsp/requests/references.rb +146 -0
- data/lib/ruby_lsp/requests/rename.rb +196 -0
- data/lib/ruby_lsp/requests/signature_help.rb +6 -1
- data/lib/ruby_lsp/requests/support/common.rb +2 -2
- data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
- data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
- data/lib/ruby_lsp/ruby_document.rb +23 -8
- data/lib/ruby_lsp/scope.rb +47 -0
- data/lib/ruby_lsp/server.rb +127 -34
- data/lib/ruby_lsp/static_docs.rb +15 -0
- data/lib/ruby_lsp/store.rb +12 -0
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +6 -1
- data/lib/ruby_lsp/utils.rb +3 -6
- data/static_docs/yield.md +81 -0
- metadata +21 -8
- data/lib/ruby_lsp/parameter_scope.rb +0 -33
@@ -13,6 +13,9 @@ module RubyLsp
|
|
13
13
|
sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
|
14
14
|
def run_formatting(uri, document); end
|
15
15
|
|
16
|
+
sig { abstract.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
|
17
|
+
def run_range_formatting(uri, source, base_indentation); end
|
18
|
+
|
16
19
|
sig do
|
17
20
|
abstract.params(
|
18
21
|
uri: URI::Generic,
|
@@ -28,6 +28,12 @@ module RubyLsp
|
|
28
28
|
@format_runner.formatted_source
|
29
29
|
end
|
30
30
|
|
31
|
+
# RuboCop does not support range formatting
|
32
|
+
sig { override.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
|
33
|
+
def run_range_formatting(uri, source, base_indentation)
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
31
37
|
sig do
|
32
38
|
override.params(
|
33
39
|
uri: URI::Generic,
|
@@ -19,6 +19,13 @@ module URI
|
|
19
19
|
T::Array[Symbol],
|
20
20
|
)
|
21
21
|
|
22
|
+
# `uri` for Ruby 3.4 switched the default parser from RFC2396 to RFC3986. The new parser emits a deprecation
|
23
|
+
# warning on a few methods and delegates them to RFC2396, namely `extract`/`make_regexp`/`escape`/`unescape`.
|
24
|
+
# On earlier versions of the uri gem, the RFC2396_PARSER constant doesn't exist, so it needs some special
|
25
|
+
# handling to select a parser that doesn't emit deprecations. While it was backported to Ruby 3.1, users may
|
26
|
+
# have the uri gem in their own bundle and thus not use a compatible version.
|
27
|
+
PARSER = T.let(const_defined?(:RFC2396_PARSER) ? RFC2396_PARSER : DEFAULT_PARSER, RFC2396_Parser)
|
28
|
+
|
22
29
|
T.unsafe(self).alias_method(:gem_name, :host)
|
23
30
|
T.unsafe(self).alias_method(:line_number, :fragment)
|
24
31
|
|
@@ -41,7 +48,7 @@ module URI
|
|
41
48
|
{
|
42
49
|
scheme: "source",
|
43
50
|
host: gem_name,
|
44
|
-
path:
|
51
|
+
path: PARSER.escape("/#{gem_version}/#{path}"),
|
45
52
|
fragment: line_number,
|
46
53
|
}
|
47
54
|
)
|
@@ -37,6 +37,14 @@ module RubyLsp
|
|
37
37
|
SyntaxTree.format(document.source, @options.print_width, options: @options.formatter_options)
|
38
38
|
end
|
39
39
|
|
40
|
+
sig { override.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
|
41
|
+
def run_range_formatting(uri, source, base_indentation)
|
42
|
+
path = uri.to_standardized_path
|
43
|
+
return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
|
44
|
+
|
45
|
+
SyntaxTree.format(source, @options.print_width, base_indentation, options: @options.formatter_options)
|
46
|
+
end
|
47
|
+
|
40
48
|
sig do
|
41
49
|
override.params(
|
42
50
|
uri: URI::Generic,
|
@@ -26,9 +26,10 @@ module RubyLsp
|
|
26
26
|
node: Prism::Node,
|
27
27
|
char_position: Integer,
|
28
28
|
node_types: T::Array[T.class_of(Prism::Node)],
|
29
|
+
encoding: Encoding,
|
29
30
|
).returns(NodeContext)
|
30
31
|
end
|
31
|
-
def locate(node, char_position, node_types: [])
|
32
|
+
def locate(node, char_position, node_types: [], encoding: Encoding::UTF_8)
|
32
33
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
33
34
|
closest = node
|
34
35
|
parent = T.let(nil, T.nilable(Prism::Node))
|
@@ -61,16 +62,21 @@ module RubyLsp
|
|
61
62
|
|
62
63
|
# Skip if the current node doesn't cover the desired position
|
63
64
|
loc = candidate.location
|
64
|
-
|
65
|
+
loc_start_offset = loc.start_code_units_offset(encoding)
|
66
|
+
loc_end_offset = loc.end_code_units_offset(encoding)
|
67
|
+
next unless (loc_start_offset...loc_end_offset).cover?(char_position)
|
65
68
|
|
66
69
|
# If the node's start character is already past the position, then we should've found the closest node
|
67
70
|
# already
|
68
|
-
break if char_position <
|
71
|
+
break if char_position < loc_start_offset
|
69
72
|
|
70
73
|
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
|
71
74
|
# and need to pop the stack
|
72
75
|
previous_level = nesting_nodes.last
|
73
|
-
|
76
|
+
if previous_level &&
|
77
|
+
(loc_start_offset > previous_level.location.end_code_units_offset(encoding))
|
78
|
+
nesting_nodes.pop
|
79
|
+
end
|
74
80
|
|
75
81
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
|
76
82
|
# the target when it is a constant
|
@@ -83,8 +89,10 @@ module RubyLsp
|
|
83
89
|
if candidate.is_a?(Prism::CallNode)
|
84
90
|
arg_loc = candidate.arguments&.location
|
85
91
|
blk_loc = candidate.block&.location
|
86
|
-
if (arg_loc && (arg_loc.
|
87
|
-
|
92
|
+
if (arg_loc && (arg_loc.start_code_units_offset(encoding)...
|
93
|
+
arg_loc.end_code_units_offset(encoding)).cover?(char_position)) ||
|
94
|
+
(blk_loc && (blk_loc.start_code_units_offset(encoding)...
|
95
|
+
blk_loc.end_code_units_offset(encoding)).cover?(char_position))
|
88
96
|
call_node = candidate
|
89
97
|
end
|
90
98
|
end
|
@@ -94,7 +102,9 @@ module RubyLsp
|
|
94
102
|
|
95
103
|
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
96
104
|
closest_loc = closest.location
|
97
|
-
|
105
|
+
closest_node_start_offset = closest_loc.start_code_units_offset(encoding)
|
106
|
+
closest_node_end_offset = closest_loc.end_code_units_offset(encoding)
|
107
|
+
if loc_end_offset - loc_start_offset <= closest_node_end_offset - closest_node_start_offset
|
98
108
|
parent = closest
|
99
109
|
closest = candidate
|
100
110
|
end
|
@@ -201,7 +211,12 @@ module RubyLsp
|
|
201
211
|
).returns(NodeContext)
|
202
212
|
end
|
203
213
|
def locate_node(position, node_types: [])
|
204
|
-
RubyDocument.locate(
|
214
|
+
RubyDocument.locate(
|
215
|
+
@parse_result.value,
|
216
|
+
create_scanner.find_char_position(position),
|
217
|
+
node_types: node_types,
|
218
|
+
encoding: @encoding,
|
219
|
+
)
|
205
220
|
end
|
206
221
|
end
|
207
222
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class Scope
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(T.nilable(Scope)) }
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
sig { params(parent: T.nilable(Scope)).void }
|
12
|
+
def initialize(parent = nil)
|
13
|
+
@parent = parent
|
14
|
+
|
15
|
+
# A hash of name => type
|
16
|
+
@locals = T.let({}, T::Hash[Symbol, Local])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add a new local to this scope. The types should only be `:parameter` or `:variable`
|
20
|
+
sig { params(name: T.any(String, Symbol), type: Symbol).void }
|
21
|
+
def add(name, type)
|
22
|
+
@locals[name.to_sym] = Local.new(type)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(name: T.any(String, Symbol)).returns(T.nilable(Local)) }
|
26
|
+
def lookup(name)
|
27
|
+
sym = name.to_sym
|
28
|
+
entry = @locals[sym]
|
29
|
+
return entry if entry
|
30
|
+
return unless @parent
|
31
|
+
|
32
|
+
@parent.lookup(sym)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Local
|
36
|
+
extend T::Sig
|
37
|
+
|
38
|
+
sig { returns(Symbol) }
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
sig { params(type: Symbol).void }
|
42
|
+
def initialize(type)
|
43
|
+
@type = type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -43,6 +43,8 @@ module RubyLsp
|
|
43
43
|
text_document_semantic_tokens_range(message)
|
44
44
|
when "textDocument/formatting"
|
45
45
|
text_document_formatting(message)
|
46
|
+
when "textDocument/rangeFormatting"
|
47
|
+
text_document_range_formatting(message)
|
46
48
|
when "textDocument/documentHighlight"
|
47
49
|
text_document_document_highlight(message)
|
48
50
|
when "textDocument/onTypeFormatting"
|
@@ -67,6 +69,10 @@ module RubyLsp
|
|
67
69
|
text_document_definition(message)
|
68
70
|
when "textDocument/prepareTypeHierarchy"
|
69
71
|
text_document_prepare_type_hierarchy(message)
|
72
|
+
when "textDocument/rename"
|
73
|
+
text_document_rename(message)
|
74
|
+
when "textDocument/references"
|
75
|
+
text_document_references(message)
|
70
76
|
when "typeHierarchy/supertypes"
|
71
77
|
type_hierarchy_supertypes(message)
|
72
78
|
when "typeHierarchy/subtypes"
|
@@ -85,7 +91,16 @@ module RubyLsp
|
|
85
91
|
id: message[:id],
|
86
92
|
response:
|
87
93
|
Addon.addons.map do |addon|
|
88
|
-
|
94
|
+
version_method = addon.method(:version)
|
95
|
+
|
96
|
+
# If the add-on doesn't define a `version` method, we'd be calling the abstract method defined by
|
97
|
+
# Sorbet, which would raise an error.
|
98
|
+
# Therefore, we only call the method if it's defined by the add-on itself
|
99
|
+
if version_method.owner != Addon
|
100
|
+
version = addon.version
|
101
|
+
end
|
102
|
+
|
103
|
+
{ name: addon.name, version: version, errored: addon.error? }
|
89
104
|
end,
|
90
105
|
),
|
91
106
|
)
|
@@ -123,9 +138,9 @@ module RubyLsp
|
|
123
138
|
send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
|
124
139
|
end
|
125
140
|
|
126
|
-
sig { void }
|
127
|
-
def load_addons
|
128
|
-
errors = Addon.load_addons(@global_state, @outgoing_queue)
|
141
|
+
sig { params(include_project_addons: T::Boolean).void }
|
142
|
+
def load_addons(include_project_addons: true)
|
143
|
+
errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
|
129
144
|
|
130
145
|
if errors.any?
|
131
146
|
send_log_message(
|
@@ -142,7 +157,7 @@ module RubyLsp
|
|
142
157
|
method: "window/showMessage",
|
143
158
|
params: Interface::ShowMessageParams.new(
|
144
159
|
type: Constant::MessageType::WARNING,
|
145
|
-
message: "Error loading
|
160
|
+
message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
|
146
161
|
),
|
147
162
|
),
|
148
163
|
)
|
@@ -227,6 +242,9 @@ module RubyLsp
|
|
227
242
|
workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.has_type_checker,
|
228
243
|
signature_help_provider: signature_help_provider,
|
229
244
|
type_hierarchy_provider: type_hierarchy_provider,
|
245
|
+
rename_provider: !@global_state.has_type_checker,
|
246
|
+
references_provider: !@global_state.has_type_checker,
|
247
|
+
document_range_formatting_provider: true,
|
230
248
|
experimental: {
|
231
249
|
addon_detection: true,
|
232
250
|
},
|
@@ -285,8 +303,9 @@ module RubyLsp
|
|
285
303
|
rescue RuboCop::Error => e
|
286
304
|
# The user may have provided unknown config switches in .rubocop or
|
287
305
|
# is trying to load a non-existant config file.
|
288
|
-
send_message(Notification.
|
306
|
+
send_message(Notification.window_show_message(
|
289
307
|
"RuboCop configuration error: #{e.message}. Formatting will not be available.",
|
308
|
+
type: Constant::MessageType::ERROR,
|
290
309
|
))
|
291
310
|
end
|
292
311
|
end
|
@@ -319,14 +338,18 @@ module RubyLsp
|
|
319
338
|
language_id: language_id,
|
320
339
|
)
|
321
340
|
|
322
|
-
if document.past_expensive_limit?
|
341
|
+
if document.past_expensive_limit? && text_document[:uri].scheme == "file"
|
342
|
+
log_message = <<~MESSAGE
|
343
|
+
The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
|
344
|
+
diagnostics will be disabled.
|
345
|
+
MESSAGE
|
346
|
+
|
323
347
|
send_message(
|
324
348
|
Notification.new(
|
325
|
-
method: "window/
|
326
|
-
params: Interface::
|
349
|
+
method: "window/logMessage",
|
350
|
+
params: Interface::LogMessageParams.new(
|
327
351
|
type: Constant::MessageType::WARNING,
|
328
|
-
message:
|
329
|
-
"diagnostics will be disabled",
|
352
|
+
message: log_message,
|
330
353
|
),
|
331
354
|
),
|
332
355
|
)
|
@@ -505,6 +528,34 @@ module RubyLsp
|
|
505
528
|
send_message(Result.new(id: message[:id], response: request.perform))
|
506
529
|
end
|
507
530
|
|
531
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
532
|
+
def text_document_range_formatting(message)
|
533
|
+
# If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
|
534
|
+
if @global_state.formatter == "none"
|
535
|
+
send_empty_response(message[:id])
|
536
|
+
return
|
537
|
+
end
|
538
|
+
|
539
|
+
params = message[:params]
|
540
|
+
uri = params.dig(:textDocument, :uri)
|
541
|
+
# Do not format files outside of the workspace. For example, if someone is looking at a gem's source code, we
|
542
|
+
# don't want to format it
|
543
|
+
path = uri.to_standardized_path
|
544
|
+
unless path.nil? || path.start_with?(@global_state.workspace_path)
|
545
|
+
send_empty_response(message[:id])
|
546
|
+
return
|
547
|
+
end
|
548
|
+
|
549
|
+
document = @store.get(uri)
|
550
|
+
unless document.is_a?(RubyDocument)
|
551
|
+
send_empty_response(message[:id])
|
552
|
+
return
|
553
|
+
end
|
554
|
+
|
555
|
+
response = Requests::RangeFormatting.new(@global_state, document, params).perform
|
556
|
+
send_message(Result.new(id: message[:id], response: response))
|
557
|
+
end
|
558
|
+
|
508
559
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
509
560
|
def text_document_formatting(message)
|
510
561
|
# If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
|
@@ -531,10 +582,16 @@ module RubyLsp
|
|
531
582
|
response = Requests::Formatting.new(@global_state, document).perform
|
532
583
|
send_message(Result.new(id: message[:id], response: response))
|
533
584
|
rescue Requests::Request::InvalidFormatter => error
|
534
|
-
send_message(Notification.
|
585
|
+
send_message(Notification.window_show_message(
|
586
|
+
"Configuration error: #{error.message}",
|
587
|
+
type: Constant::MessageType::ERROR,
|
588
|
+
))
|
535
589
|
send_empty_response(message[:id])
|
536
590
|
rescue StandardError, LoadError => error
|
537
|
-
send_message(Notification.
|
591
|
+
send_message(Notification.window_show_message(
|
592
|
+
"Formatting error: #{error.message}",
|
593
|
+
type: Constant::MessageType::ERROR,
|
594
|
+
))
|
538
595
|
send_empty_response(message[:id])
|
539
596
|
end
|
540
597
|
|
@@ -602,6 +659,44 @@ module RubyLsp
|
|
602
659
|
)
|
603
660
|
end
|
604
661
|
|
662
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
663
|
+
def text_document_rename(message)
|
664
|
+
params = message[:params]
|
665
|
+
document = @store.get(params.dig(:textDocument, :uri))
|
666
|
+
|
667
|
+
unless document.is_a?(RubyDocument)
|
668
|
+
send_empty_response(message[:id])
|
669
|
+
return
|
670
|
+
end
|
671
|
+
|
672
|
+
send_message(
|
673
|
+
Result.new(
|
674
|
+
id: message[:id],
|
675
|
+
response: Requests::Rename.new(@global_state, @store, document, params).perform,
|
676
|
+
),
|
677
|
+
)
|
678
|
+
rescue Requests::Rename::InvalidNameError => e
|
679
|
+
send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
|
680
|
+
end
|
681
|
+
|
682
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
683
|
+
def text_document_references(message)
|
684
|
+
params = message[:params]
|
685
|
+
document = @store.get(params.dig(:textDocument, :uri))
|
686
|
+
|
687
|
+
unless document.is_a?(RubyDocument)
|
688
|
+
send_empty_response(message[:id])
|
689
|
+
return
|
690
|
+
end
|
691
|
+
|
692
|
+
send_message(
|
693
|
+
Result.new(
|
694
|
+
id: message[:id],
|
695
|
+
response: Requests::References.new(@global_state, @store, document, params).perform,
|
696
|
+
),
|
697
|
+
)
|
698
|
+
end
|
699
|
+
|
605
700
|
sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
|
606
701
|
def sorbet_level(document)
|
607
702
|
return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
|
@@ -673,30 +768,19 @@ module RubyLsp
|
|
673
768
|
document = @store.get(uri)
|
674
769
|
|
675
770
|
unless document.is_a?(RubyDocument)
|
676
|
-
|
677
|
-
|
771
|
+
fail_request_and_notify(message[:id], "Code actions are currently only available for Ruby documents")
|
772
|
+
return
|
678
773
|
end
|
679
774
|
|
680
|
-
result = Requests::CodeActionResolve.new(document, params).perform
|
775
|
+
result = Requests::CodeActionResolve.new(document, @global_state, params).perform
|
681
776
|
|
682
777
|
case result
|
683
778
|
when Requests::CodeActionResolve::Error::EmptySelection
|
684
|
-
|
685
|
-
raise Requests::CodeActionResolve::CodeActionError
|
779
|
+
fail_request_and_notify(message[:id], "Invalid selection for extract variable refactor")
|
686
780
|
when Requests::CodeActionResolve::Error::InvalidTargetRange
|
687
|
-
|
688
|
-
Notification.window_show_error(
|
689
|
-
"Couldn't find an appropriate location to place extracted refactor",
|
690
|
-
),
|
691
|
-
)
|
692
|
-
raise Requests::CodeActionResolve::CodeActionError
|
781
|
+
fail_request_and_notify(message[:id], "Couldn't find an appropriate location to place extracted refactor")
|
693
782
|
when Requests::CodeActionResolve::Error::UnknownCodeAction
|
694
|
-
|
695
|
-
Notification.window_show_error(
|
696
|
-
"Unknown code action",
|
697
|
-
),
|
698
|
-
)
|
699
|
-
raise Requests::CodeActionResolve::CodeActionError
|
783
|
+
fail_request_and_notify(message[:id], "Unknown code action")
|
700
784
|
else
|
701
785
|
send_message(Result.new(id: message[:id], response: result))
|
702
786
|
end
|
@@ -729,10 +813,16 @@ module RubyLsp
|
|
729
813
|
),
|
730
814
|
)
|
731
815
|
rescue Requests::Request::InvalidFormatter => error
|
732
|
-
send_message(Notification.
|
816
|
+
send_message(Notification.window_show_message(
|
817
|
+
"Configuration error: #{error.message}",
|
818
|
+
type: Constant::MessageType::ERROR,
|
819
|
+
))
|
733
820
|
send_empty_response(message[:id])
|
734
821
|
rescue StandardError, LoadError => error
|
735
|
-
send_message(Notification.
|
822
|
+
send_message(Notification.window_show_message(
|
823
|
+
"Error running diagnostics: #{error.message}",
|
824
|
+
type: Constant::MessageType::ERROR,
|
825
|
+
))
|
736
826
|
send_empty_response(message[:id])
|
737
827
|
end
|
738
828
|
|
@@ -969,7 +1059,9 @@ module RubyLsp
|
|
969
1059
|
false
|
970
1060
|
end
|
971
1061
|
rescue StandardError => error
|
972
|
-
|
1062
|
+
message = "Error while indexing (see [troubleshooting steps]" \
|
1063
|
+
"(https://shopify.github.io/ruby-lsp/troubleshooting#indexing)): #{error.message}"
|
1064
|
+
send_message(Notification.window_show_message(message, type: Constant::MessageType::ERROR))
|
973
1065
|
end
|
974
1066
|
|
975
1067
|
# Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
|
@@ -1054,8 +1146,9 @@ module RubyLsp
|
|
1054
1146
|
@global_state.formatter = "none"
|
1055
1147
|
|
1056
1148
|
send_message(
|
1057
|
-
Notification.
|
1149
|
+
Notification.window_show_message(
|
1058
1150
|
"Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
|
1151
|
+
type: Constant::MessageType::ERROR,
|
1059
1152
|
),
|
1060
1153
|
)
|
1061
1154
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
# The path to the `static_docs` directory, where we keep long-form static documentation
|
6
|
+
STATIC_DOCS_PATH = T.let(File.join(File.dirname(File.dirname(T.must(__dir__))), "static_docs"), String)
|
7
|
+
|
8
|
+
# A map of keyword => short documentation to be displayed on hover or completion
|
9
|
+
KEYWORD_DOCS = T.let(
|
10
|
+
{
|
11
|
+
"yield" => "Invokes the passed block with the given arguments",
|
12
|
+
}.freeze,
|
13
|
+
T::Hash[String, String],
|
14
|
+
)
|
15
|
+
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -99,6 +99,18 @@ module RubyLsp
|
|
99
99
|
@state.delete(uri.to_s)
|
100
100
|
end
|
101
101
|
|
102
|
+
sig { params(uri: URI::Generic).returns(T::Boolean) }
|
103
|
+
def key?(uri)
|
104
|
+
@state.key?(uri.to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { params(block: T.proc.params(uri: String, document: Document[T.untyped]).void).void }
|
108
|
+
def each(&block)
|
109
|
+
@state.each do |uri, document|
|
110
|
+
block.call(uri, document)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
102
114
|
sig do
|
103
115
|
type_parameters(:T)
|
104
116
|
.params(
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -42,7 +42,7 @@ module RubyLsp
|
|
42
42
|
RubyIndexer::IndexablePath.new(nil, T.must(uri.to_standardized_path)),
|
43
43
|
source,
|
44
44
|
)
|
45
|
-
server.load_addons if load_addons
|
45
|
+
server.load_addons(include_project_addons: false) if load_addons
|
46
46
|
block.call(server, uri)
|
47
47
|
ensure
|
48
48
|
if load_addons
|
@@ -89,7 +89,12 @@ module RubyLsp
|
|
89
89
|
|
90
90
|
Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
|
91
91
|
else
|
92
|
-
|
92
|
+
|
93
|
+
raw_receiver = if receiver.is_a?(Prism::CallNode)
|
94
|
+
receiver.message
|
95
|
+
else
|
96
|
+
receiver&.slice
|
97
|
+
end
|
93
98
|
|
94
99
|
if raw_receiver
|
95
100
|
guessed_name = raw_receiver
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -64,14 +64,11 @@ module RubyLsp
|
|
64
64
|
class << self
|
65
65
|
extend T::Sig
|
66
66
|
|
67
|
-
sig { params(message: String).returns(Notification) }
|
68
|
-
def
|
67
|
+
sig { params(message: String, type: Integer).returns(Notification) }
|
68
|
+
def window_show_message(message, type: Constant::MessageType::INFO)
|
69
69
|
new(
|
70
70
|
method: "window/showMessage",
|
71
|
-
params: Interface::ShowMessageParams.new(
|
72
|
-
type: Constant::MessageType::ERROR,
|
73
|
-
message: message,
|
74
|
-
),
|
71
|
+
params: Interface::ShowMessageParams.new(type: type, message: message),
|
75
72
|
)
|
76
73
|
end
|
77
74
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Yield
|
2
|
+
|
3
|
+
In Ruby, every method implicitly accepts a block, even when not included in the parameters list.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
def foo
|
7
|
+
end
|
8
|
+
|
9
|
+
foo { 123 } # works!
|
10
|
+
```
|
11
|
+
|
12
|
+
The `yield` keyword is used to invoke the block that was passed with arguments.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# Consider this method call. The block being passed to the method `foo` accepts an argument called `a`.
|
16
|
+
# It then takes whatever argument was passed and multiplies it by 2
|
17
|
+
foo do |a|
|
18
|
+
a * 2
|
19
|
+
end
|
20
|
+
|
21
|
+
# In the `foo` method declaration, we can use `yield` to invoke the block that was passed and provide the block
|
22
|
+
# with the value for the `a` argument
|
23
|
+
def foo
|
24
|
+
# Invoke the block passed to `foo` with the number 10 as the argument `a`
|
25
|
+
result = yield(10)
|
26
|
+
puts result # Will print 20
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
If `yield` is used to invoke the block, but no block was passed, that will result in a local jump error.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# If we invoke `foo` without a block, trying to `yield` will fail
|
34
|
+
foo
|
35
|
+
|
36
|
+
# `foo': no block given (yield) (LocalJumpError)
|
37
|
+
```
|
38
|
+
|
39
|
+
We can decide to use `yield` conditionally by using Ruby's `block_given?` method, which will return `true` if a block
|
40
|
+
was passed to the method.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
def foo
|
44
|
+
# If a block is passed when invoking `foo`, call the block with argument 10 and print the result.
|
45
|
+
# Otherwise, just print that no block was passed
|
46
|
+
if block_given?
|
47
|
+
result = yield(10)
|
48
|
+
puts result
|
49
|
+
else
|
50
|
+
puts "No block passed!"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
foo do |a|
|
55
|
+
a * 2
|
56
|
+
end
|
57
|
+
# => 20
|
58
|
+
|
59
|
+
foo
|
60
|
+
# => No block passed!
|
61
|
+
```
|
62
|
+
|
63
|
+
## Block parameter
|
64
|
+
|
65
|
+
In addition to implicit blocks, Ruby also allows developers to use explicit block parameters as part of the method's
|
66
|
+
signature. In this scenario, we can use the reference to the block directly instead of relying on the `yield` keyword.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# Block parameters are prefixed with & and a name
|
70
|
+
def foo(&my_block_param)
|
71
|
+
# If a block was passed to `foo`, `my_block_param` will be a `Proc` object. Otherwise, it will be `nil`. We can use
|
72
|
+
# that to check for its presence
|
73
|
+
if my_block_param
|
74
|
+
# Explicit block parameters are invoked using the method `call`, which is present in all `Proc` objects
|
75
|
+
result = my_block_param.call(10)
|
76
|
+
puts result
|
77
|
+
else
|
78
|
+
puts "No block passed!"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|