ruby-lsp 0.8.0 → 0.9.0
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 +41 -33
- data/lib/core_ext/uri.rb +9 -14
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +166 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +147 -0
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +123 -0
- data/lib/ruby_indexer/ruby_indexer.rb +20 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +220 -0
- data/lib/ruby_indexer/test/configuration_test.rb +114 -0
- data/lib/ruby_indexer/test/constant_test.rb +108 -0
- data/lib/ruby_indexer/test/index_test.rb +129 -0
- data/lib/ruby_indexer/test/test_case.rb +42 -0
- data/lib/ruby_lsp/executor.rb +144 -10
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/requests/definition.rb +60 -5
- data/lib/ruby_lsp/requests/hover.rb +53 -30
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +7 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +86 -0
- data/lib/ruby_lsp/requests.rb +1 -0
- data/lib/ruby_lsp/setup_bundler.rb +24 -8
- data/lib/ruby_lsp/store.rb +15 -9
- metadata +33 -4
- data/lib/ruby_lsp/requests/support/rails_document_client.rb +0 -122
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -15,6 +15,7 @@ module RubyLsp
|
|
15
15
|
@store = store
|
16
16
|
@test_library = T.let(DependencyDetector.detected_test_library, String)
|
17
17
|
@message_queue = message_queue
|
18
|
+
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
18
19
|
end
|
19
20
|
|
20
21
|
sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
|
@@ -57,6 +58,7 @@ module RubyLsp
|
|
57
58
|
warn(errored_extensions.map(&:backtraces).join("\n\n"))
|
58
59
|
end
|
59
60
|
|
61
|
+
perform_initial_indexing
|
60
62
|
check_formatter_is_available
|
61
63
|
|
62
64
|
warn("Ruby LSP is ready")
|
@@ -169,25 +171,91 @@ module RubyLsp
|
|
169
171
|
completion(uri, request.dig(:params, :position))
|
170
172
|
when "textDocument/definition"
|
171
173
|
definition(uri, request.dig(:params, :position))
|
174
|
+
when "workspace/didChangeWatchedFiles"
|
175
|
+
did_change_watched_files(request.dig(:params, :changes))
|
176
|
+
when "workspace/symbol"
|
177
|
+
workspace_symbol(request.dig(:params, :query))
|
172
178
|
when "rubyLsp/textDocument/showSyntaxTree"
|
173
179
|
show_syntax_tree(uri, request.dig(:params, :range))
|
174
180
|
end
|
175
181
|
end
|
176
182
|
|
183
|
+
sig { params(changes: T::Array[{ uri: String, type: Integer }]).returns(Object) }
|
184
|
+
def did_change_watched_files(changes)
|
185
|
+
changes.each do |change|
|
186
|
+
# File change events include folders, but we're only interested in files
|
187
|
+
uri = URI(change[:uri])
|
188
|
+
file_path = uri.to_standardized_path
|
189
|
+
next if file_path.nil? || File.directory?(file_path)
|
190
|
+
|
191
|
+
case change[:type]
|
192
|
+
when Constant::FileChangeType::CREATED
|
193
|
+
@index.index_single(file_path)
|
194
|
+
when Constant::FileChangeType::CHANGED
|
195
|
+
@index.delete(file_path)
|
196
|
+
@index.index_single(file_path)
|
197
|
+
when Constant::FileChangeType::DELETED
|
198
|
+
@index.delete(file_path)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
VOID
|
203
|
+
end
|
204
|
+
|
205
|
+
sig { void }
|
206
|
+
def perform_initial_indexing
|
207
|
+
return unless @store.experimental_features
|
208
|
+
|
209
|
+
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
210
|
+
# stuck indexing files
|
211
|
+
RubyIndexer.configuration.load_config
|
212
|
+
|
213
|
+
begin
|
214
|
+
@index.index_all
|
215
|
+
rescue StandardError => error
|
216
|
+
@message_queue << Notification.new(
|
217
|
+
message: "window/showMessage",
|
218
|
+
params: Interface::ShowMessageParams.new(
|
219
|
+
type: Constant::MessageType::ERROR,
|
220
|
+
message: "Error while indexing: #{error.message}",
|
221
|
+
),
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Always end the progress notification even if indexing failed or else it never goes away and the user has no way
|
226
|
+
# of dismissing it
|
227
|
+
end_progress("indexing-progress")
|
228
|
+
end
|
229
|
+
|
230
|
+
sig { params(query: T.nilable(String)).returns(T::Array[Interface::WorkspaceSymbol]) }
|
231
|
+
def workspace_symbol(query)
|
232
|
+
Requests::WorkspaceSymbol.new(query, @index).run
|
233
|
+
end
|
234
|
+
|
177
235
|
sig { params(uri: URI::Generic, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
|
178
236
|
def show_syntax_tree(uri, range)
|
179
237
|
{ ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
|
180
238
|
end
|
181
239
|
|
182
|
-
sig
|
240
|
+
sig do
|
241
|
+
params(
|
242
|
+
uri: URI::Generic,
|
243
|
+
position: Document::PositionShape,
|
244
|
+
).returns(T.nilable(T.any(T::Array[Interface::Location], Interface::Location)))
|
245
|
+
end
|
183
246
|
def definition(uri, position)
|
184
247
|
document = @store.get(uri)
|
185
248
|
return if document.syntax_error?
|
186
249
|
|
187
|
-
target,
|
250
|
+
target, parent, nesting = document.locate_node(
|
251
|
+
position,
|
252
|
+
node_types: [SyntaxTree::Command, SyntaxTree::Const, SyntaxTree::ConstPathRef],
|
253
|
+
)
|
254
|
+
|
255
|
+
target = parent if target.is_a?(SyntaxTree::Const) && parent.is_a?(SyntaxTree::ConstPathRef)
|
188
256
|
|
189
257
|
emitter = EventEmitter.new
|
190
|
-
base_listener = Requests::Definition.new(uri, emitter, @message_queue)
|
258
|
+
base_listener = Requests::Definition.new(uri, nesting, @index, emitter, @message_queue)
|
191
259
|
emitter.emit_for_target(target)
|
192
260
|
base_listener.response
|
193
261
|
end
|
@@ -209,16 +277,20 @@ module RubyLsp
|
|
209
277
|
document = @store.get(uri)
|
210
278
|
return if document.syntax_error?
|
211
279
|
|
212
|
-
target, parent = document.locate_node(
|
280
|
+
target, parent, nesting = document.locate_node(
|
281
|
+
position,
|
282
|
+
node_types: Requests::Hover::ALLOWED_TARGETS,
|
283
|
+
)
|
213
284
|
|
214
|
-
if
|
215
|
-
Requests::Hover::ALLOWED_TARGETS.include?(
|
285
|
+
if (Requests::Hover::ALLOWED_TARGETS.include?(parent.class) &&
|
286
|
+
!Requests::Hover::ALLOWED_TARGETS.include?(target.class)) ||
|
287
|
+
(parent.is_a?(SyntaxTree::ConstPathRef) && target.is_a?(SyntaxTree::Const))
|
216
288
|
target = parent
|
217
289
|
end
|
218
290
|
|
219
291
|
# Instantiate all listeners
|
220
292
|
emitter = EventEmitter.new
|
221
|
-
hover = Requests::Hover.new(emitter, @message_queue)
|
293
|
+
hover = Requests::Hover.new(@index, nesting, emitter, @message_queue)
|
222
294
|
|
223
295
|
# Emit events for all listeners
|
224
296
|
emitter.emit_for_target(target)
|
@@ -436,6 +508,37 @@ module RubyLsp
|
|
436
508
|
listener.response
|
437
509
|
end
|
438
510
|
|
511
|
+
sig { params(id: String, title: String).void }
|
512
|
+
def begin_progress(id, title)
|
513
|
+
return unless @store.supports_progress
|
514
|
+
|
515
|
+
@message_queue << Request.new(
|
516
|
+
message: "window/workDoneProgress/create",
|
517
|
+
params: Interface::WorkDoneProgressCreateParams.new(token: id),
|
518
|
+
)
|
519
|
+
|
520
|
+
@message_queue << Notification.new(
|
521
|
+
message: "$/progress",
|
522
|
+
params: Interface::ProgressParams.new(
|
523
|
+
token: id,
|
524
|
+
value: Interface::WorkDoneProgressBegin.new(kind: "begin", title: title),
|
525
|
+
),
|
526
|
+
)
|
527
|
+
end
|
528
|
+
|
529
|
+
sig { params(id: String).void }
|
530
|
+
def end_progress(id)
|
531
|
+
return unless @store.supports_progress
|
532
|
+
|
533
|
+
@message_queue << Notification.new(
|
534
|
+
message: "$/progress",
|
535
|
+
params: Interface::ProgressParams.new(
|
536
|
+
token: id,
|
537
|
+
value: Interface::WorkDoneProgressEnd.new(kind: "end"),
|
538
|
+
),
|
539
|
+
)
|
540
|
+
end
|
541
|
+
|
439
542
|
sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
|
440
543
|
def initialize_request(options)
|
441
544
|
@store.clear
|
@@ -449,6 +552,7 @@ module RubyLsp
|
|
449
552
|
encodings.first
|
450
553
|
end
|
451
554
|
|
555
|
+
@store.supports_progress = options.dig(:capabilities, :window, :workDoneProgress) || true
|
452
556
|
formatter = options.dig(:initializationOptions, :formatter) || "auto"
|
453
557
|
@store.formatter = if formatter == "auto"
|
454
558
|
DependencyDetector.detected_formatter
|
@@ -457,9 +561,7 @@ module RubyLsp
|
|
457
561
|
end
|
458
562
|
|
459
563
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
460
|
-
|
461
|
-
# Uncomment the line below and use the variable to gate features behind the experimental flag
|
462
|
-
# experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
564
|
+
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
463
565
|
|
464
566
|
enabled_features = case configured_features
|
465
567
|
when Array
|
@@ -541,6 +643,37 @@ module RubyLsp
|
|
541
643
|
)
|
542
644
|
end
|
543
645
|
|
646
|
+
if @store.experimental_features
|
647
|
+
# Dynamically registered capabilities
|
648
|
+
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
649
|
+
|
650
|
+
# Not every client supports dynamic registration or file watching
|
651
|
+
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
652
|
+
@message_queue << Request.new(
|
653
|
+
message: "client/registerCapability",
|
654
|
+
params: Interface::RegistrationParams.new(
|
655
|
+
registrations: [
|
656
|
+
# Register watching Ruby files
|
657
|
+
Interface::Registration.new(
|
658
|
+
id: "workspace/didChangeWatchedFiles",
|
659
|
+
method: "workspace/didChangeWatchedFiles",
|
660
|
+
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
661
|
+
watchers: [
|
662
|
+
Interface::FileSystemWatcher.new(
|
663
|
+
glob_pattern: "**/*.rb",
|
664
|
+
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
665
|
+
),
|
666
|
+
],
|
667
|
+
),
|
668
|
+
),
|
669
|
+
],
|
670
|
+
),
|
671
|
+
)
|
672
|
+
end
|
673
|
+
|
674
|
+
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
675
|
+
end
|
676
|
+
|
544
677
|
Interface::InitializeResult.new(
|
545
678
|
capabilities: Interface::ServerCapabilities.new(
|
546
679
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
@@ -563,6 +696,7 @@ module RubyLsp
|
|
563
696
|
completion_provider: completion_provider,
|
564
697
|
code_lens_provider: code_lens_provider,
|
565
698
|
definition_provider: enabled_features["definition"],
|
699
|
+
workspace_symbol_provider: enabled_features["workspaceSymbol"],
|
566
700
|
),
|
567
701
|
)
|
568
702
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "syntax_tree"
|
6
|
+
require "yarp"
|
6
7
|
require "language_server-protocol"
|
7
8
|
require "benchmark"
|
8
9
|
require "bundler"
|
@@ -10,6 +11,7 @@ require "uri"
|
|
10
11
|
require "cgi"
|
11
12
|
|
12
13
|
require "ruby-lsp"
|
14
|
+
require "ruby_indexer/ruby_indexer"
|
13
15
|
require "core_ext/uri"
|
14
16
|
require "ruby_lsp/utils"
|
15
17
|
require "ruby_lsp/server"
|
@@ -9,29 +9,51 @@ module RubyLsp
|
|
9
9
|
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
|
10
10
|
# definition of the symbol under the cursor.
|
11
11
|
#
|
12
|
-
# Currently, only jumping to required files is supported.
|
12
|
+
# Currently, only jumping to classes, modules and required files is supported.
|
13
13
|
#
|
14
14
|
# # Example
|
15
15
|
#
|
16
16
|
# ```ruby
|
17
17
|
# require "some_gem/file" # <- Request go to definition on this string will take you to the file
|
18
|
+
# Product.new # <- Request go to definition on this class name will take you to its declaration.
|
18
19
|
# ```
|
19
20
|
class Definition < Listener
|
20
21
|
extend T::Sig
|
21
22
|
extend T::Generic
|
22
23
|
|
23
|
-
ResponseType = type_member { { fixed: T.nilable(Interface::Location) } }
|
24
|
+
ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
|
24
25
|
|
25
26
|
sig { override.returns(ResponseType) }
|
26
27
|
attr_reader :response
|
27
28
|
|
28
|
-
sig
|
29
|
-
|
29
|
+
sig do
|
30
|
+
params(
|
31
|
+
uri: URI::Generic,
|
32
|
+
nesting: T::Array[String],
|
33
|
+
index: RubyIndexer::Index,
|
34
|
+
emitter: EventEmitter,
|
35
|
+
message_queue: Thread::Queue,
|
36
|
+
).void
|
37
|
+
end
|
38
|
+
def initialize(uri, nesting, index, emitter, message_queue)
|
30
39
|
super(emitter, message_queue)
|
31
40
|
|
32
41
|
@uri = uri
|
42
|
+
@nesting = nesting
|
43
|
+
@index = index
|
33
44
|
@response = T.let(nil, ResponseType)
|
34
|
-
emitter.register(self, :on_command)
|
45
|
+
emitter.register(self, :on_command, :on_const, :on_const_path_ref)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { params(node: SyntaxTree::ConstPathRef).void }
|
49
|
+
def on_const_path_ref(node)
|
50
|
+
name = full_constant_name(node)
|
51
|
+
find_in_index(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(node: SyntaxTree::Const).void }
|
55
|
+
def on_const(node)
|
56
|
+
find_in_index(node.value)
|
35
57
|
end
|
36
58
|
|
37
59
|
sig { params(node: SyntaxTree::Command).void }
|
@@ -79,6 +101,39 @@ module RubyLsp
|
|
79
101
|
|
80
102
|
private
|
81
103
|
|
104
|
+
sig { params(value: String).void }
|
105
|
+
def find_in_index(value)
|
106
|
+
entries = @index.resolve(value, @nesting)
|
107
|
+
return unless entries
|
108
|
+
|
109
|
+
bundle_path = begin
|
110
|
+
Bundler.bundle_path.to_s
|
111
|
+
rescue Bundler::GemfileNotFound
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
@response = entries.filter_map do |entry|
|
116
|
+
location = entry.location
|
117
|
+
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
118
|
+
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
119
|
+
# in the project, even if the files are typed false
|
120
|
+
file_path = entry.file_path
|
121
|
+
if DependencyDetector::HAS_TYPECHECKER && bundle_path && !file_path.start_with?(bundle_path) &&
|
122
|
+
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
123
|
+
|
124
|
+
next
|
125
|
+
end
|
126
|
+
|
127
|
+
Interface::Location.new(
|
128
|
+
uri: URI::Generic.from_path(path: file_path).to_s,
|
129
|
+
range: Interface::Range.new(
|
130
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
131
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
132
|
+
),
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
82
137
|
sig { params(file: String).returns(T.nilable(String)) }
|
83
138
|
def find_file_in_load_path(file)
|
84
139
|
return unless file.include?("/")
|
@@ -3,19 +3,15 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
|
-
# 
|
7
7
|
#
|
8
8
|
# The [hover request](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover)
|
9
|
-
#
|
10
|
-
# It currently only supports Rails' documentation: when hovering over Rails DSLs/constants under certain paths,
|
11
|
-
# like `before_save :callback` in `models/post.rb`, it generates a link to `before_save`'s API documentation.
|
9
|
+
# displays the documentation for the symbol currently under the cursor.
|
12
10
|
#
|
13
11
|
# # Example
|
14
12
|
#
|
15
13
|
# ```ruby
|
16
|
-
# class
|
17
|
-
# before_save :do_something # when hovering on before_save, the link will be rendered
|
18
|
-
# end
|
14
|
+
# String # -> Hovering over the class reference will show all declaration locations and the documentation
|
19
15
|
# ```
|
20
16
|
class Hover < Listener
|
21
17
|
extend T::Sig
|
@@ -25,6 +21,7 @@ module RubyLsp
|
|
25
21
|
|
26
22
|
ALLOWED_TARGETS = T.let(
|
27
23
|
[
|
24
|
+
SyntaxTree::Const,
|
28
25
|
SyntaxTree::Command,
|
29
26
|
SyntaxTree::CallNode,
|
30
27
|
SyntaxTree::ConstPathRef,
|
@@ -35,15 +32,24 @@ module RubyLsp
|
|
35
32
|
sig { override.returns(ResponseType) }
|
36
33
|
attr_reader :response
|
37
34
|
|
38
|
-
sig
|
39
|
-
|
40
|
-
|
35
|
+
sig do
|
36
|
+
params(
|
37
|
+
index: RubyIndexer::Index,
|
38
|
+
nesting: T::Array[String],
|
39
|
+
emitter: EventEmitter,
|
40
|
+
message_queue: Thread::Queue,
|
41
|
+
).void
|
42
|
+
end
|
43
|
+
def initialize(index, nesting, emitter, message_queue)
|
44
|
+
super(emitter, message_queue)
|
41
45
|
|
46
|
+
@nesting = nesting
|
47
|
+
@index = index
|
42
48
|
@external_listeners.concat(
|
43
49
|
Extension.extensions.filter_map { |ext| ext.create_hover_listener(emitter, message_queue) },
|
44
50
|
)
|
45
51
|
@response = T.let(nil, ResponseType)
|
46
|
-
emitter.register(self, :
|
52
|
+
emitter.register(self, :on_const_path_ref, :on_const)
|
47
53
|
end
|
48
54
|
|
49
55
|
# Merges responses from other hover listeners
|
@@ -55,40 +61,57 @@ module RubyLsp
|
|
55
61
|
if @response.nil?
|
56
62
|
@response = other.response
|
57
63
|
else
|
58
|
-
@response.contents.value <<
|
64
|
+
@response.contents.value << "\n\n" << other_response.contents.value
|
59
65
|
end
|
60
66
|
|
61
67
|
self
|
62
68
|
end
|
63
69
|
|
64
|
-
sig { params(node: SyntaxTree::Command).void }
|
65
|
-
def on_command(node)
|
66
|
-
message = node.message
|
67
|
-
@response = generate_rails_document_link_hover(message.value, message)
|
68
|
-
end
|
69
|
-
|
70
70
|
sig { params(node: SyntaxTree::ConstPathRef).void }
|
71
71
|
def on_const_path_ref(node)
|
72
|
-
|
72
|
+
return if DependencyDetector::HAS_TYPECHECKER
|
73
|
+
|
74
|
+
name = full_constant_name(node)
|
75
|
+
generate_hover(name, node)
|
73
76
|
end
|
74
77
|
|
75
|
-
sig { params(node: SyntaxTree::
|
76
|
-
def
|
77
|
-
|
78
|
-
return if message.is_a?(Symbol)
|
78
|
+
sig { params(node: SyntaxTree::Const).void }
|
79
|
+
def on_const(node)
|
80
|
+
return if DependencyDetector::HAS_TYPECHECKER
|
79
81
|
|
80
|
-
|
82
|
+
generate_hover(node.value, node)
|
81
83
|
end
|
82
84
|
|
83
85
|
private
|
84
86
|
|
85
|
-
sig { params(name: String, node: SyntaxTree::Node).
|
86
|
-
def
|
87
|
-
|
88
|
-
return
|
87
|
+
sig { params(name: String, node: SyntaxTree::Node).void }
|
88
|
+
def generate_hover(name, node)
|
89
|
+
entries = @index.resolve(name, @nesting)
|
90
|
+
return unless entries
|
91
|
+
|
92
|
+
title = +"```ruby\n#{name}\n```"
|
93
|
+
definitions = []
|
94
|
+
content = +""
|
95
|
+
entries.each do |entry|
|
96
|
+
loc = entry.location
|
97
|
+
|
98
|
+
# We always handle locations as zero based. However, for file links in Markdown we need them to be one based,
|
99
|
+
# which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to columns. The
|
100
|
+
# format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
|
101
|
+
uri = URI::Generic.from_path(
|
102
|
+
path: entry.file_path,
|
103
|
+
fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
|
104
|
+
)
|
105
|
+
|
106
|
+
definitions << "[#{entry.file_name}](#{uri})"
|
107
|
+
content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
|
108
|
+
end
|
89
109
|
|
90
|
-
contents = Interface::MarkupContent.new(
|
91
|
-
|
110
|
+
contents = Interface::MarkupContent.new(
|
111
|
+
kind: "markdown",
|
112
|
+
value: "#{title}\n\n**Definitions**: #{definitions.join(" | ")}\n\n#{content}",
|
113
|
+
)
|
114
|
+
@response = Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
|
92
115
|
end
|
93
116
|
end
|
94
117
|
end
|
@@ -67,12 +67,10 @@ module RubyLsp
|
|
67
67
|
|
68
68
|
line = T.must(current_line)
|
69
69
|
|
70
|
-
# If the
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
line[@position[:character]] == "|"
|
75
|
-
|
70
|
+
# If the user inserts the closing pipe manually to the end of the block argument, we need to avoid adding
|
71
|
+
# an additional one and remove the previous one. This also helps to remove the user who accidentally
|
72
|
+
# inserts another pipe after the autocompleted one.
|
73
|
+
if line[..@position[:character]] =~ /(do|{)\s+\|[^|]*\|\|$/
|
76
74
|
@edits << Interface::TextEdit.new(
|
77
75
|
range: Interface::Range.new(
|
78
76
|
start: Interface::Position.new(
|
@@ -36,6 +36,11 @@ module RubyLsp
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
sig { returns(T::Boolean) }
|
40
|
+
def typechecker?
|
41
|
+
direct_dependency?(/^sorbet/) || direct_dependency?(/^sorbet-static-and-runtime/)
|
42
|
+
end
|
43
|
+
|
39
44
|
sig { params(gem_pattern: Regexp).returns(T::Boolean) }
|
40
45
|
def direct_dependency?(gem_pattern)
|
41
46
|
Bundler.with_original_env { Bundler.default_gemfile } &&
|
@@ -44,5 +49,7 @@ module RubyLsp
|
|
44
49
|
false
|
45
50
|
end
|
46
51
|
end
|
52
|
+
|
53
|
+
HAS_TYPECHECKER = T.let(typechecker?, T::Boolean)
|
47
54
|
end
|
48
55
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# The [workspace symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol)
|
9
|
+
# request allows fuzzy searching declarations in the entire project. On VS Code, use CTRL/CMD + T to search for
|
10
|
+
# symbols.
|
11
|
+
#
|
12
|
+
# # Example
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# # Searching for `Floo` will fuzzy match and return all declarations according to the query, including this `Foo`
|
16
|
+
# class
|
17
|
+
# class Foo
|
18
|
+
# end
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
class WorkspaceSymbol
|
22
|
+
extend T::Sig
|
23
|
+
|
24
|
+
sig { params(query: T.nilable(String), index: RubyIndexer::Index).void }
|
25
|
+
def initialize(query, index)
|
26
|
+
@query = query
|
27
|
+
@index = index
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { returns(T::Array[Interface::WorkspaceSymbol]) }
|
31
|
+
def run
|
32
|
+
bundle_path = begin
|
33
|
+
Bundler.bundle_path.to_s
|
34
|
+
rescue Bundler::GemfileNotFound
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
@index.fuzzy_search(@query).filter_map do |entry|
|
39
|
+
# If the project is using Sorbet, we let Sorbet handle symbols defined inside the project itself and RBIs, but
|
40
|
+
# we still return entries defined in gems to allow developers to jump directly to the source
|
41
|
+
file_path = entry.file_path
|
42
|
+
if DependencyDetector::HAS_TYPECHECKER && bundle_path && !file_path.start_with?(bundle_path) &&
|
43
|
+
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
44
|
+
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
kind = kind_for_entry(entry)
|
49
|
+
loc = entry.location
|
50
|
+
|
51
|
+
# We use the namespace as the container name, but we also use the full name as the regular name. The reason we
|
52
|
+
# do this is to allow people to search for fully qualified names (e.g.: `Foo::Bar`). If we only included the
|
53
|
+
# short name `Bar`, then searching for `Foo::Bar` would not return any results
|
54
|
+
*container, _short_name = entry.name.split("::")
|
55
|
+
|
56
|
+
Interface::WorkspaceSymbol.new(
|
57
|
+
name: entry.name,
|
58
|
+
container_name: T.must(container).join("::"),
|
59
|
+
kind: kind,
|
60
|
+
location: Interface::Location.new(
|
61
|
+
uri: URI::Generic.from_path(path: file_path).to_s,
|
62
|
+
range: Interface::Range.new(
|
63
|
+
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column),
|
64
|
+
end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
65
|
+
),
|
66
|
+
),
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
sig { params(entry: RubyIndexer::Index::Entry).returns(T.nilable(Integer)) }
|
74
|
+
def kind_for_entry(entry)
|
75
|
+
case entry
|
76
|
+
when RubyIndexer::Index::Entry::Class
|
77
|
+
Constant::SymbolKind::CLASS
|
78
|
+
when RubyIndexer::Index::Entry::Module
|
79
|
+
Constant::SymbolKind::NAMESPACE
|
80
|
+
when RubyIndexer::Index::Entry::Constant
|
81
|
+
Constant::SymbolKind::CONSTANT
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -41,6 +41,7 @@ module RubyLsp
|
|
41
41
|
autoload :CodeLens, "ruby_lsp/requests/code_lens"
|
42
42
|
autoload :Definition, "ruby_lsp/requests/definition"
|
43
43
|
autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
|
44
|
+
autoload :WorkspaceSymbol, "ruby_lsp/requests/workspace_symbol"
|
44
45
|
|
45
46
|
# :nodoc:
|
46
47
|
module Support
|