ruby-lsp 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
# ![Hover demo](../../
|
6
|
+
# ![Hover demo](../../hover.gif)
|
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
|
+
# ![Workspace symbol demo](../../workspace_symbol.gif)
|
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
|