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
@@ -0,0 +1,242 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class ReferenceFinderTest < Minitest::Test
|
8
|
+
def test_finds_constant_references
|
9
|
+
refs = find_const_references("Foo::Bar", <<~RUBY)
|
10
|
+
module Foo
|
11
|
+
class Bar
|
12
|
+
end
|
13
|
+
|
14
|
+
Bar
|
15
|
+
end
|
16
|
+
|
17
|
+
Foo::Bar
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
assert_equal("Bar", refs[0].name)
|
21
|
+
assert_equal(2, refs[0].location.start_line)
|
22
|
+
|
23
|
+
assert_equal("Bar", refs[1].name)
|
24
|
+
assert_equal(5, refs[1].location.start_line)
|
25
|
+
|
26
|
+
assert_equal("Foo::Bar", refs[2].name)
|
27
|
+
assert_equal(8, refs[2].location.start_line)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_finds_constant_references_inside_singleton_contexts
|
31
|
+
refs = find_const_references("Foo::<Class:Foo>::Bar", <<~RUBY)
|
32
|
+
class Foo
|
33
|
+
class << self
|
34
|
+
class Bar
|
35
|
+
end
|
36
|
+
|
37
|
+
Bar
|
38
|
+
end
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
|
42
|
+
assert_equal("Bar", refs[0].name)
|
43
|
+
assert_equal(3, refs[0].location.start_line)
|
44
|
+
|
45
|
+
assert_equal("Bar", refs[1].name)
|
46
|
+
assert_equal(6, refs[1].location.start_line)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_finds_top_level_constant_references
|
50
|
+
refs = find_const_references("Bar", <<~RUBY)
|
51
|
+
class Bar
|
52
|
+
end
|
53
|
+
|
54
|
+
class Foo
|
55
|
+
::Bar
|
56
|
+
|
57
|
+
class << self
|
58
|
+
::Bar
|
59
|
+
end
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
assert_equal("Bar", refs[0].name)
|
64
|
+
assert_equal(1, refs[0].location.start_line)
|
65
|
+
|
66
|
+
assert_equal("::Bar", refs[1].name)
|
67
|
+
assert_equal(5, refs[1].location.start_line)
|
68
|
+
|
69
|
+
assert_equal("::Bar", refs[2].name)
|
70
|
+
assert_equal(8, refs[2].location.start_line)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_finds_method_references
|
74
|
+
refs = find_method_references("foo", <<~RUBY)
|
75
|
+
class Bar
|
76
|
+
def foo
|
77
|
+
end
|
78
|
+
|
79
|
+
def baz
|
80
|
+
foo
|
81
|
+
end
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
assert_equal(2, refs.size)
|
86
|
+
|
87
|
+
assert_equal("foo", refs[0].name)
|
88
|
+
assert_equal(2, refs[0].location.start_line)
|
89
|
+
|
90
|
+
assert_equal("foo", refs[1].name)
|
91
|
+
assert_equal(6, refs[1].location.start_line)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_does_not_mismatch_on_readers_and_writers
|
95
|
+
refs = find_method_references("foo", <<~RUBY)
|
96
|
+
class Bar
|
97
|
+
def foo
|
98
|
+
end
|
99
|
+
|
100
|
+
def foo=(value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def baz
|
104
|
+
self.foo = 1
|
105
|
+
self.foo
|
106
|
+
end
|
107
|
+
end
|
108
|
+
RUBY
|
109
|
+
|
110
|
+
# We want to match `foo` but not `foo=`
|
111
|
+
assert_equal(2, refs.size)
|
112
|
+
|
113
|
+
assert_equal("foo", refs[0].name)
|
114
|
+
assert_equal(2, refs[0].location.start_line)
|
115
|
+
|
116
|
+
assert_equal("foo", refs[1].name)
|
117
|
+
assert_equal(10, refs[1].location.start_line)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_matches_writers
|
121
|
+
refs = find_method_references("foo=", <<~RUBY)
|
122
|
+
class Bar
|
123
|
+
def foo
|
124
|
+
end
|
125
|
+
|
126
|
+
def foo=(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def baz
|
130
|
+
self.foo = 1
|
131
|
+
self.foo
|
132
|
+
end
|
133
|
+
end
|
134
|
+
RUBY
|
135
|
+
|
136
|
+
# We want to match `foo=` but not `foo`
|
137
|
+
assert_equal(2, refs.size)
|
138
|
+
|
139
|
+
assert_equal("foo=", refs[0].name)
|
140
|
+
assert_equal(5, refs[0].location.start_line)
|
141
|
+
|
142
|
+
assert_equal("foo=", refs[1].name)
|
143
|
+
assert_equal(9, refs[1].location.start_line)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_find_inherited_methods
|
147
|
+
refs = find_method_references("foo", <<~RUBY)
|
148
|
+
class Bar
|
149
|
+
def foo
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Baz < Bar
|
154
|
+
super.foo
|
155
|
+
end
|
156
|
+
RUBY
|
157
|
+
|
158
|
+
assert_equal(2, refs.size)
|
159
|
+
|
160
|
+
assert_equal("foo", refs[0].name)
|
161
|
+
assert_equal(2, refs[0].location.start_line)
|
162
|
+
|
163
|
+
assert_equal("foo", refs[1].name)
|
164
|
+
assert_equal(7, refs[1].location.start_line)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_finds_methods_created_in_mixins
|
168
|
+
refs = find_method_references("foo", <<~RUBY)
|
169
|
+
module Mixin
|
170
|
+
def foo
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Bar
|
175
|
+
include Mixin
|
176
|
+
end
|
177
|
+
|
178
|
+
Bar.foo
|
179
|
+
RUBY
|
180
|
+
|
181
|
+
assert_equal(2, refs.size)
|
182
|
+
|
183
|
+
assert_equal("foo", refs[0].name)
|
184
|
+
assert_equal(2, refs[0].location.start_line)
|
185
|
+
|
186
|
+
assert_equal("foo", refs[1].name)
|
187
|
+
assert_equal(10, refs[1].location.start_line)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_finds_singleton_methods
|
191
|
+
# The current implementation matches on both `Bar.foo` and `Bar#foo` even though they are different
|
192
|
+
|
193
|
+
refs = find_method_references("foo", <<~RUBY)
|
194
|
+
class Bar
|
195
|
+
class << self
|
196
|
+
def foo
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def foo
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
Bar.foo
|
205
|
+
RUBY
|
206
|
+
|
207
|
+
assert_equal(3, refs.size)
|
208
|
+
|
209
|
+
assert_equal("foo", refs[0].name)
|
210
|
+
assert_equal(3, refs[0].location.start_line)
|
211
|
+
|
212
|
+
assert_equal("foo", refs[1].name)
|
213
|
+
assert_equal(7, refs[1].location.start_line)
|
214
|
+
|
215
|
+
assert_equal("foo", refs[2].name)
|
216
|
+
assert_equal(11, refs[2].location.start_line)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def find_const_references(const_name, source)
|
222
|
+
target = ReferenceFinder::ConstTarget.new(const_name)
|
223
|
+
find_references(target, source)
|
224
|
+
end
|
225
|
+
|
226
|
+
def find_method_references(method_name, source)
|
227
|
+
target = ReferenceFinder::MethodTarget.new(method_name)
|
228
|
+
find_references(target, source)
|
229
|
+
end
|
230
|
+
|
231
|
+
def find_references(target, source)
|
232
|
+
file_path = "/fake.rb"
|
233
|
+
index = Index.new
|
234
|
+
index.index_single(IndexablePath.new(nil, file_path), source)
|
235
|
+
parse_result = Prism.parse(source)
|
236
|
+
dispatcher = Prism::Dispatcher.new
|
237
|
+
finder = ReferenceFinder.new(target, index, dispatcher)
|
238
|
+
dispatcher.visit(parse_result.value)
|
239
|
+
finder.references
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyLsp
|
5
|
-
# To register an
|
5
|
+
# To register an add-on, inherit from this class and implement both `name` and `activate`
|
6
6
|
#
|
7
7
|
# # Example
|
8
8
|
#
|
@@ -14,7 +14,7 @@ module RubyLsp
|
|
14
14
|
# end
|
15
15
|
#
|
16
16
|
# def name
|
17
|
-
# "My
|
17
|
+
# "My add-on name"
|
18
18
|
# end
|
19
19
|
# end
|
20
20
|
# end
|
@@ -27,11 +27,13 @@ module RubyLsp
|
|
27
27
|
|
28
28
|
@addons = T.let([], T::Array[Addon])
|
29
29
|
@addon_classes = T.let([], T::Array[T.class_of(Addon)])
|
30
|
-
#
|
30
|
+
# Add-on instances that have declared a handler to accept file watcher events
|
31
31
|
@file_watcher_addons = T.let([], T::Array[Addon])
|
32
32
|
|
33
33
|
AddonNotFoundError = Class.new(StandardError)
|
34
34
|
|
35
|
+
class IncompatibleApiError < StandardError; end
|
36
|
+
|
35
37
|
class << self
|
36
38
|
extend T::Sig
|
37
39
|
|
@@ -51,15 +53,30 @@ module RubyLsp
|
|
51
53
|
super
|
52
54
|
end
|
53
55
|
|
54
|
-
# Discovers and loads all
|
56
|
+
# Discovers and loads all add-ons. Returns a list of errors when trying to require add-ons
|
55
57
|
sig do
|
56
|
-
params(
|
58
|
+
params(
|
59
|
+
global_state: GlobalState,
|
60
|
+
outgoing_queue: Thread::Queue,
|
61
|
+
include_project_addons: T::Boolean,
|
62
|
+
).returns(T::Array[StandardError])
|
57
63
|
end
|
58
|
-
def load_addons(global_state, outgoing_queue)
|
59
|
-
# Require all
|
60
|
-
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
|
61
|
-
|
62
|
-
|
64
|
+
def load_addons(global_state, outgoing_queue, include_project_addons: true)
|
65
|
+
# Require all add-ons entry points, which should be placed under
|
66
|
+
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` or in the workspace under
|
67
|
+
# `your_project/ruby_lsp/project_name/addon.rb`
|
68
|
+
addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
|
69
|
+
|
70
|
+
if include_project_addons
|
71
|
+
addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
|
72
|
+
end
|
73
|
+
|
74
|
+
errors = addon_files.filter_map do |addon_path|
|
75
|
+
# Avoid requiring this file twice. This may happen if you're working on the Ruby LSP itself and at the same
|
76
|
+
# time have `ruby-lsp` installed as a vendored gem
|
77
|
+
next if File.basename(File.dirname(addon_path)) == "ruby_lsp"
|
78
|
+
|
79
|
+
require File.expand_path(addon_path)
|
63
80
|
nil
|
64
81
|
rescue => e
|
65
82
|
e
|
@@ -69,7 +86,7 @@ module RubyLsp
|
|
69
86
|
self.addons = addon_classes.map(&:new)
|
70
87
|
self.file_watcher_addons = addons.select { |addon| addon.respond_to?(:workspace_did_change_watched_files) }
|
71
88
|
|
72
|
-
# Activate each one of the discovered
|
89
|
+
# Activate each one of the discovered add-ons. If any problems occur in the add-ons, we don't want to
|
73
90
|
# fail to boot the server
|
74
91
|
addons.each do |addon|
|
75
92
|
addon.activate(global_state, outgoing_queue)
|
@@ -80,13 +97,53 @@ module RubyLsp
|
|
80
97
|
errors
|
81
98
|
end
|
82
99
|
|
83
|
-
|
84
|
-
|
100
|
+
# Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
|
101
|
+
# other add-ons, this is the way to get access to that API.
|
102
|
+
#
|
103
|
+
# Important: if the add-on is not found, AddonNotFoundError will be raised. If the add-on is found, but its
|
104
|
+
# current version does not satisfy the given version constraint, then IncompatibleApiError will be raised. It is
|
105
|
+
# the responsibility of the add-ons using this API to handle these errors appropriately.
|
106
|
+
sig { params(addon_name: String, version_constraints: String).returns(Addon) }
|
107
|
+
def get(addon_name, *version_constraints)
|
108
|
+
if version_constraints.empty?
|
109
|
+
raise IncompatibleApiError, "Must specify version constraints when accessing other add-ons"
|
110
|
+
end
|
111
|
+
|
85
112
|
addon = addons.find { |addon| addon.name == addon_name }
|
86
|
-
raise AddonNotFoundError, "Could not find
|
113
|
+
raise AddonNotFoundError, "Could not find add-on '#{addon_name}'" unless addon
|
114
|
+
|
115
|
+
version_object = Gem::Version.new(addon.version)
|
116
|
+
|
117
|
+
unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
|
118
|
+
raise IncompatibleApiError,
|
119
|
+
"Constraints #{version_constraints.inspect} is incompatible with #{addon_name} version #{addon.version}"
|
120
|
+
end
|
87
121
|
|
88
122
|
addon
|
89
123
|
end
|
124
|
+
|
125
|
+
# Depend on a specific version of the Ruby LSP. This method should only be used if the add-on is distributed in a
|
126
|
+
# gem that does not have a runtime dependency on the ruby-lsp gem. This method should be invoked at the top of the
|
127
|
+
# `addon.rb` file before defining any classes or requiring any files. For example:
|
128
|
+
#
|
129
|
+
# ```ruby
|
130
|
+
# RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0")
|
131
|
+
#
|
132
|
+
# module MyGem
|
133
|
+
# class MyAddon < RubyLsp::Addon
|
134
|
+
# # ...
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
# ```
|
138
|
+
sig { params(version_constraints: String).void }
|
139
|
+
def depend_on_ruby_lsp!(*version_constraints)
|
140
|
+
version_object = Gem::Version.new(RubyLsp::VERSION)
|
141
|
+
|
142
|
+
unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
|
143
|
+
raise IncompatibleApiError,
|
144
|
+
"Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
|
145
|
+
end
|
146
|
+
end
|
90
147
|
end
|
91
148
|
|
92
149
|
sig { void }
|
@@ -118,20 +175,25 @@ module RubyLsp
|
|
118
175
|
@errors.map(&:full_message).join("\n\n")
|
119
176
|
end
|
120
177
|
|
121
|
-
# Each
|
178
|
+
# Each add-on should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
122
179
|
# reading information into memory or even spawning a separate process
|
123
180
|
sig { abstract.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
|
124
181
|
def activate(global_state, outgoing_queue); end
|
125
182
|
|
126
|
-
# Each
|
183
|
+
# Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
|
127
184
|
# child process
|
128
185
|
sig { abstract.void }
|
129
186
|
def deactivate; end
|
130
187
|
|
131
|
-
#
|
188
|
+
# Add-ons should override the `name` method to return the add-on name
|
132
189
|
sig { abstract.returns(String) }
|
133
190
|
def name; end
|
134
191
|
|
192
|
+
# Add-ons should override the `version` method to return a semantic version string representing the add-on's
|
193
|
+
# version. This is used for compatibility checks
|
194
|
+
sig { abstract.returns(String) }
|
195
|
+
def version; end
|
196
|
+
|
135
197
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
136
198
|
sig do
|
137
199
|
overridable.params(
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -130,6 +130,12 @@ module RubyLsp
|
|
130
130
|
sig { abstract.void }
|
131
131
|
def shutdown; end
|
132
132
|
|
133
|
+
sig { params(id: Integer, message: String, type: Integer).void }
|
134
|
+
def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
|
135
|
+
send_message(Error.new(id: id, code: Constant::ErrorCodes::REQUEST_FAILED, message: message))
|
136
|
+
send_message(Notification.window_show_message(message, type: type))
|
137
|
+
end
|
138
|
+
|
133
139
|
sig { returns(Thread) }
|
134
140
|
def new_worker
|
135
141
|
Thread.new do
|
@@ -27,8 +27,9 @@ module RubyLsp
|
|
27
27
|
scanner = ERBScanner.new(@source)
|
28
28
|
scanner.scan
|
29
29
|
@host_language_source = scanner.host_language
|
30
|
-
#
|
31
|
-
|
30
|
+
# Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
|
31
|
+
# which they will be evaluated
|
32
|
+
@parse_result = Prism.parse(scanner.ruby, partial_script: true)
|
32
33
|
true
|
33
34
|
end
|
34
35
|
|
@@ -49,7 +50,12 @@ module RubyLsp
|
|
49
50
|
).returns(NodeContext)
|
50
51
|
end
|
51
52
|
def locate_node(position, node_types: [])
|
52
|
-
RubyDocument.locate(
|
53
|
+
RubyDocument.locate(
|
54
|
+
@parse_result.value,
|
55
|
+
create_scanner.find_char_position(position),
|
56
|
+
node_types: node_types,
|
57
|
+
encoding: @encoding,
|
58
|
+
)
|
53
59
|
end
|
54
60
|
|
55
61
|
sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
|
@@ -23,6 +23,9 @@ module RubyLsp
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
24
|
attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
|
25
25
|
|
26
|
+
sig { returns(T::Array[String]) }
|
27
|
+
attr_reader :supported_resource_operations
|
28
|
+
|
26
29
|
sig { returns(TypeInferrer) }
|
27
30
|
attr_reader :type_inferrer
|
28
31
|
|
@@ -42,6 +45,7 @@ module RubyLsp
|
|
42
45
|
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
43
46
|
@addon_settings = T.let({}, T::Hash[String, T.untyped])
|
44
47
|
@supports_request_delegation = T.let(false, T::Boolean)
|
48
|
+
@supported_resource_operations = T.let([], T::Array[String])
|
45
49
|
end
|
46
50
|
|
47
51
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -117,6 +121,7 @@ module RubyLsp
|
|
117
121
|
else
|
118
122
|
Encoding::UTF_32
|
119
123
|
end
|
124
|
+
@index.configuration.encoding = @encoding
|
120
125
|
|
121
126
|
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
122
127
|
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
@@ -132,6 +137,9 @@ module RubyLsp
|
|
132
137
|
end
|
133
138
|
|
134
139
|
@supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
|
140
|
+
supported_resource_operations = options.dig(:capabilities, :workspace, :workspaceEdit, :resourceOperations)
|
141
|
+
@supported_resource_operations = supported_resource_operations if supported_resource_operations
|
142
|
+
|
135
143
|
notifications
|
136
144
|
end
|
137
145
|
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -26,7 +26,8 @@ require "ruby_lsp/base_server"
|
|
26
26
|
require "ruby_indexer/ruby_indexer"
|
27
27
|
require "core_ext/uri"
|
28
28
|
require "ruby_lsp/utils"
|
29
|
-
require "ruby_lsp/
|
29
|
+
require "ruby_lsp/static_docs"
|
30
|
+
require "ruby_lsp/scope"
|
30
31
|
require "ruby_lsp/global_state"
|
31
32
|
require "ruby_lsp/server"
|
32
33
|
require "ruby_lsp/type_inferrer"
|
@@ -74,6 +75,9 @@ require "ruby_lsp/requests/hover"
|
|
74
75
|
require "ruby_lsp/requests/inlay_hints"
|
75
76
|
require "ruby_lsp/requests/on_type_formatting"
|
76
77
|
require "ruby_lsp/requests/prepare_type_hierarchy"
|
78
|
+
require "ruby_lsp/requests/range_formatting"
|
79
|
+
require "ruby_lsp/requests/references"
|
80
|
+
require "ruby_lsp/requests/rename"
|
77
81
|
require "ruby_lsp/requests/selection_ranges"
|
78
82
|
require "ruby_lsp/requests/semantic_highlighting"
|
79
83
|
require "ruby_lsp/requests/show_syntax_tree"
|
@@ -21,8 +21,10 @@ module RubyLsp
|
|
21
21
|
Prism::InstanceVariableWriteNode,
|
22
22
|
Prism::SymbolNode,
|
23
23
|
Prism::StringNode,
|
24
|
+
Prism::InterpolatedStringNode,
|
24
25
|
Prism::SuperNode,
|
25
26
|
Prism::ForwardingSuperNode,
|
27
|
+
Prism::YieldNode,
|
26
28
|
],
|
27
29
|
T::Array[T.class_of(Prism::Node)],
|
28
30
|
)
|
@@ -68,9 +70,22 @@ module RubyLsp
|
|
68
70
|
:on_instance_variable_target_node_enter,
|
69
71
|
:on_super_node_enter,
|
70
72
|
:on_forwarding_super_node_enter,
|
73
|
+
:on_string_node_enter,
|
74
|
+
:on_interpolated_string_node_enter,
|
75
|
+
:on_yield_node_enter,
|
71
76
|
)
|
72
77
|
end
|
73
78
|
|
79
|
+
sig { params(node: Prism::StringNode).void }
|
80
|
+
def on_string_node_enter(node)
|
81
|
+
generate_heredoc_hover(node)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(node: Prism::InterpolatedStringNode).void }
|
85
|
+
def on_interpolated_string_node_enter(node)
|
86
|
+
generate_heredoc_hover(node)
|
87
|
+
end
|
88
|
+
|
74
89
|
sig { params(node: Prism::ConstantReadNode).void }
|
75
90
|
def on_constant_read_node_enter(node)
|
76
91
|
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
@@ -153,8 +168,50 @@ module RubyLsp
|
|
153
168
|
handle_super_node_hover
|
154
169
|
end
|
155
170
|
|
171
|
+
sig { params(node: Prism::YieldNode).void }
|
172
|
+
def on_yield_node_enter(node)
|
173
|
+
handle_keyword_documentation(node.keyword)
|
174
|
+
end
|
175
|
+
|
156
176
|
private
|
157
177
|
|
178
|
+
sig { params(node: T.any(Prism::InterpolatedStringNode, Prism::StringNode)).void }
|
179
|
+
def generate_heredoc_hover(node)
|
180
|
+
return unless node.heredoc?
|
181
|
+
|
182
|
+
opening_content = node.opening_loc&.slice
|
183
|
+
return unless opening_content
|
184
|
+
|
185
|
+
match = /(<<(?<type>(-|~)?))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(opening_content)
|
186
|
+
return unless match
|
187
|
+
|
188
|
+
heredoc_delimiter = match.named_captures["delimiter"]
|
189
|
+
|
190
|
+
if heredoc_delimiter
|
191
|
+
message = if match["type"] == "~"
|
192
|
+
"This is a squiggly heredoc definition using the `#{heredoc_delimiter}` delimiter. " \
|
193
|
+
"Indentation will be ignored in the resulting string."
|
194
|
+
else
|
195
|
+
"This is a heredoc definition using the `#{heredoc_delimiter}` delimiter. " \
|
196
|
+
"Indentation will be considered part of the string."
|
197
|
+
end
|
198
|
+
|
199
|
+
@response_builder.push(message, category: :documentation)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(keyword: String).void }
|
204
|
+
def handle_keyword_documentation(keyword)
|
205
|
+
content = KEYWORD_DOCS[keyword]
|
206
|
+
return unless content
|
207
|
+
|
208
|
+
doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
|
209
|
+
|
210
|
+
@response_builder.push("```ruby\n#{keyword}\n```", category: :title)
|
211
|
+
@response_builder.push("[Read more](#{doc_path})", category: :links)
|
212
|
+
@response_builder.push(content, category: :documentation)
|
213
|
+
end
|
214
|
+
|
158
215
|
sig { void }
|
159
216
|
def handle_super_node_hover
|
160
217
|
# Sorbet can handle super hover on typed true or higher
|