ruby-lsp 0.4.1 → 0.4.3
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/README.md +44 -52
- data/VERSION +1 -1
- data/exe/ruby-lsp +12 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +62 -0
- data/lib/ruby_lsp/document.rb +13 -4
- data/lib/ruby_lsp/executor.rb +70 -26
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/requests/base_request.rb +15 -6
- data/lib/ruby_lsp/requests/code_action_resolve.rb +40 -19
- data/lib/ruby_lsp/requests/code_actions.rb +5 -4
- data/lib/ruby_lsp/requests/diagnostics.rb +4 -4
- data/lib/ruby_lsp/requests/document_highlight.rb +3 -3
- data/lib/ruby_lsp/requests/document_link.rb +7 -7
- data/lib/ruby_lsp/requests/document_symbol.rb +14 -11
- data/lib/ruby_lsp/requests/folding_ranges.rb +38 -11
- data/lib/ruby_lsp/requests/formatting.rb +18 -5
- data/lib/ruby_lsp/requests/hover.rb +7 -6
- data/lib/ruby_lsp/requests/inlay_hints.rb +5 -4
- data/lib/ruby_lsp/requests/path_completion.rb +9 -3
- data/lib/ruby_lsp/requests/selection_ranges.rb +3 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +72 -8
- data/lib/ruby_lsp/requests/support/highlight_target.rb +5 -4
- data/lib/ruby_lsp/requests/support/rails_document_client.rb +7 -6
- data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
- data/lib/ruby_lsp/requests/support/sorbet.rb +5 -15
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +39 -0
- data/lib/ruby_lsp/server.rb +4 -1
- data/lib/ruby_lsp/store.rb +11 -7
- data/lib/ruby_lsp/utils.rb +3 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7bf79fd6cdc704c8f874f58985f0e72ac034c76bb70a68166654185f6f00cf1
|
4
|
+
data.tar.gz: '083edc726c05ffb6e5e3ae63d480b17fa0f7161ee62349665cc57e309acd5598'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6192daf27dfd82dbb505f5184eebd66308690ce10ead4d4cedc4d9c6667be882a8b9de4c87e66eb88ff43e896307be24bbcc6506b0544b496e1bcb3c9804498
|
7
|
+
data.tar.gz: bb68928d9a72b3b2873509bf915f753a202a520187bf5f55da1870d552e0ba0917a7d21ea70d20853ac60aa614f7d85d117d7291ec9c0778cab80e4561a7aba0
|
data/README.md
CHANGED
@@ -1,56 +1,50 @@
|
|
1
|
-

|
1
|
+
[](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
|
2
|
+
[](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
|
3
|
+
[](https://join.slack.com/t/ruby-dx/shared_invite/zt-1s6f4y15t-v9jedZ9YUPQLM91TEJ4Gew)
|
2
4
|
|
3
|
-
# Ruby LSP
|
4
|
-
|
5
|
-
This gem is an implementation of the [language server protocol specification](https://microsoft.github.io/language-server-protocol/) for Ruby, used to improve editor features.
|
6
|
-
|
7
|
-
# Overview
|
8
|
-
|
9
|
-
The intention of Ruby LSP is to provide a fast, robust and feature-rich coding environment for Ruby developers.
|
10
|
-
|
11
|
-
It's part of a [wider Shopify goal](https://github.com/Shopify/vscode-shopify-ruby) to provide a state-of-the-art experience to Ruby developers using modern standards for cross-editor features, documentation and debugging.
|
12
|
-
|
13
|
-
It provides many features, including:
|
14
5
|
|
15
|
-
|
16
|
-
* Linting and formatting
|
17
|
-
* Code folding
|
18
|
-
* Selection ranges
|
6
|
+
# Ruby LSP
|
19
7
|
|
20
|
-
|
8
|
+
The Ruby LSP is an implementation of the [language server protocol](https://microsoft.github.io/language-server-protocol/)
|
9
|
+
for Ruby, used to improve rich features in editors. It is a part of a wider goal to provide a state-of-the-art
|
10
|
+
experience to Ruby developers using modern standards for cross-editor features, documentation and debugging.
|
21
11
|
|
22
|
-
|
12
|
+
Want to discuss Ruby developer experience? Consider joining the public
|
13
|
+
[Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-1s6f4y15t-v9jedZ9YUPQLM91TEJ4Gew).
|
23
14
|
|
24
|
-
|
25
|
-
* Support for plug-ins to extend behavior
|
15
|
+
## Usage
|
26
16
|
|
27
|
-
|
17
|
+
### With VS Code
|
28
18
|
|
29
|
-
|
19
|
+
If using VS Code, all you have to do is install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to
|
20
|
+
get the extra features in the editor. Do not install this gem manually.
|
30
21
|
|
31
|
-
|
32
|
-
* Solargraph can be used as a globally installed gem, but Ruby LSP must be added to the Gemfile or gemspec if using RuboCop. (There are pros and cons to each approach)
|
22
|
+
### With other editors
|
33
23
|
|
34
|
-
|
35
|
-
|
36
|
-
* [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
|
24
|
+
See [editors](https://github.com/Shopify/ruby-lsp/blob/main/EDITORS.md) for community instructions on setting up the
|
25
|
+
Ruby LSP.
|
37
26
|
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
The gem can be installed by doing
|
28
|
+
```shell
|
29
|
+
gem install ruby-lsp
|
30
|
+
```
|
41
31
|
|
32
|
+
If you decide to add the gem to the bundle, it is not necessary to require it.
|
42
33
|
```ruby
|
43
34
|
group :development do
|
44
35
|
gem "ruby-lsp", require: false
|
45
36
|
end
|
46
37
|
```
|
47
38
|
|
48
|
-
|
49
|
-
the editor.
|
39
|
+
### Documentation
|
50
40
|
|
51
41
|
See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth details about the
|
52
42
|
[supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
|
53
43
|
|
44
|
+
## Learn More
|
45
|
+
|
46
|
+
* [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
|
47
|
+
|
54
48
|
## Contributing
|
55
49
|
|
56
50
|
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ruby-lsp.
|
@@ -112,31 +106,29 @@ To add a new expectations test runner for a new request handler:
|
|
112
106
|
* Tests with expectations will be checked with `assert_expectations`
|
113
107
|
* Tests without expectations will be ran against your new $HANDLER to check that nothing breaks
|
114
108
|
|
115
|
-
|
116
|
-
|
117
|
-
### Tracing LSP requests and responses
|
118
|
-
|
119
|
-
LSP server tracing can be controlled through the `ruby lsp.trace.server` config key in the `.vscode/settings.json` config file.
|
120
|
-
|
121
|
-
Possible values are:
|
109
|
+
### Debugging
|
122
110
|
|
123
|
-
|
124
|
-
* `messages`: display requests and responses notifications
|
125
|
-
* `verbose`: display each request and response as JSON
|
111
|
+
### Debugging Tests
|
126
112
|
|
127
|
-
|
113
|
+
1. Open the test file.
|
114
|
+
2. Set a breakpoint(s) on lines by clicking next to their numbers.
|
115
|
+
3. Open VS Code's `Run and Debug` panel.
|
116
|
+
4. At the top of the panel, select `Minitset - current file` and click the green triangle (or press F5).
|
128
117
|
|
129
|
-
|
118
|
+
### Debugging Running Ruby LSP Process
|
130
119
|
|
131
|
-
1.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
120
|
+
1. Open the `vscode-ruby-lsp` project in VS Code.
|
121
|
+
2. [`vscode-ruby-lsp`] Open VS Code's `Run and Debug` panel.
|
122
|
+
3. [`vscode-ruby-lsp`] Select `Run Extension` and click the green triangle (or press F5).
|
123
|
+
4. [`vscode-ruby-lsp`] Now VS Code will:
|
124
|
+
- Open another workspace as the `Extension Development Host`.
|
125
|
+
- Run `vscode-ruby-lsp` extension in debug mode, which will start a new `ruby-lsp` process with the `--debug` flag.
|
126
|
+
5. Open `ruby-lsp` in VS Code.
|
127
|
+
6. [`ruby-lsp`] Run `bin/rdbg -A` to connect to the running `ruby-lsp` process.
|
128
|
+
7. [`ruby-lsp`] Use commands like `b <file>:<line>` or `b Class#method` to set breakpoints and type `c` to continue the process.
|
129
|
+
8. In your `Extension Development Host` project (e.g. [`Tapioca`](https://github.com/Shopify/tapioca)), trigger the request that will hit the breakpoint.
|
138
130
|
|
139
|
-
|
131
|
+
### Spell Checking
|
140
132
|
|
141
133
|
VS Code users will be prompted to enable the [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) extension.
|
142
134
|
By default this will be enabled for all workspaces, but you can choose to selectively enable or disable it per workspace.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.3
|
data/exe/ruby-lsp
CHANGED
@@ -19,4 +19,16 @@ rescue
|
|
19
19
|
end
|
20
20
|
|
21
21
|
require_relative "../lib/ruby_lsp/internal"
|
22
|
+
|
23
|
+
if ARGV.include?("--debug")
|
24
|
+
sockets_dir = "/tmp/ruby-lsp-debug-sockets"
|
25
|
+
Dir.mkdir(sockets_dir) unless Dir.exist?(sockets_dir)
|
26
|
+
# ruby-debug-ENV["USER"] is an implicit naming pattern in ruby/debug
|
27
|
+
# if it's not present, rdbg will not find the socket
|
28
|
+
socket_identifier = "ruby-debug-#{ENV["USER"]}-#{File.basename(Dir.pwd)}.sock"
|
29
|
+
ENV["RUBY_DEBUG_SOCK_PATH"] = "#{sockets_dir}/#{socket_identifier}"
|
30
|
+
|
31
|
+
require "debug/open_nonstop"
|
32
|
+
end
|
33
|
+
|
22
34
|
RubyLsp::Server.new.start
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rubocop"
|
5
|
+
require "sorbet-runtime"
|
6
|
+
|
7
|
+
module RuboCop
|
8
|
+
module Cop
|
9
|
+
module RubyLsp
|
10
|
+
# Prefer using `Interface`, `Transport` and `Constant` aliases
|
11
|
+
# within the `RubyLsp` module, without having to prefix with
|
12
|
+
# `LanguageServer::Protocol`
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # bad
|
16
|
+
# module RubyLsp
|
17
|
+
# class FoldingRanges
|
18
|
+
# sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
|
19
|
+
# def run; end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# module RubyLsp
|
24
|
+
# class FoldingRanges
|
25
|
+
# sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) }
|
26
|
+
# def run; end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
class UseLanguageServerAliases < RuboCop::Cop::Base
|
30
|
+
extend RuboCop::Cop::AutoCorrector
|
31
|
+
|
32
|
+
ALIASED_CONSTANTS = T.let([:Interface, :Transport, :Constant].freeze, T::Array[Symbol])
|
33
|
+
|
34
|
+
MSG = "Use constant alias `%{constant}`."
|
35
|
+
|
36
|
+
def_node_search :ruby_lsp_modules, <<~PATTERN
|
37
|
+
(module (const nil? :RubyLsp) ...)
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def_node_search :lsp_constant_usages, <<~PATTERN
|
41
|
+
(const (const (const nil? :LanguageServer) :Protocol) {:Interface | :Transport | :Constant})
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
def on_new_investigation
|
45
|
+
return if processed_source.blank?
|
46
|
+
|
47
|
+
ruby_lsp_modules(processed_source.ast).each do |ruby_lsp_mod|
|
48
|
+
lsp_constant_usages(ruby_lsp_mod).each do |node|
|
49
|
+
lsp_const = node.children.last
|
50
|
+
|
51
|
+
next unless ALIASED_CONSTANTS.include?(lsp_const)
|
52
|
+
|
53
|
+
add_offense(node, message: format(MSG, constant: lsp_const)) do |corrector|
|
54
|
+
corrector.replace(node, lsp_const)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -15,11 +15,19 @@ module RubyLsp
|
|
15
15
|
sig { returns(String) }
|
16
16
|
attr_reader :source
|
17
17
|
|
18
|
-
sig {
|
19
|
-
|
18
|
+
sig { returns(Integer) }
|
19
|
+
attr_reader :version
|
20
|
+
|
21
|
+
sig { returns(String) }
|
22
|
+
attr_reader :uri
|
23
|
+
|
24
|
+
sig { params(source: String, version: Integer, uri: String, encoding: String).void }
|
25
|
+
def initialize(source:, version:, uri:, encoding: "utf-8")
|
20
26
|
@cache = T.let({}, T::Hash[Symbol, T.untyped])
|
21
27
|
@encoding = T.let(encoding, String)
|
22
28
|
@source = T.let(source, String)
|
29
|
+
@version = T.let(version, Integer)
|
30
|
+
@uri = T.let(uri, String)
|
23
31
|
@unparsed_edits = T.let([], T::Array[EditShape])
|
24
32
|
@syntax_error = T.let(false, T::Boolean)
|
25
33
|
@tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
|
@@ -48,8 +56,8 @@ module RubyLsp
|
|
48
56
|
result
|
49
57
|
end
|
50
58
|
|
51
|
-
sig { params(edits: T::Array[EditShape]).void }
|
52
|
-
def push_edits(edits)
|
59
|
+
sig { params(edits: T::Array[EditShape], version: Integer).void }
|
60
|
+
def push_edits(edits, version:)
|
53
61
|
edits.each do |edit|
|
54
62
|
range = edit[:range]
|
55
63
|
scanner = create_scanner
|
@@ -60,6 +68,7 @@ module RubyLsp
|
|
60
68
|
@source[start_position...end_position] = edit[:text]
|
61
69
|
end
|
62
70
|
|
71
|
+
@version = version
|
63
72
|
@unparsed_edits.concat(edits)
|
64
73
|
@cache.clear
|
65
74
|
end
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -41,7 +41,11 @@ module RubyLsp
|
|
41
41
|
warn("Ruby LSP is ready")
|
42
42
|
VOID
|
43
43
|
when "textDocument/didOpen"
|
44
|
-
text_document_did_open(
|
44
|
+
text_document_did_open(
|
45
|
+
uri,
|
46
|
+
request.dig(:params, :textDocument, :text),
|
47
|
+
request.dig(:params, :textDocument, :version),
|
48
|
+
)
|
45
49
|
when "textDocument/didClose"
|
46
50
|
@notifications << Notification.new(
|
47
51
|
message: "textDocument/publishDiagnostics",
|
@@ -50,7 +54,11 @@ module RubyLsp
|
|
50
54
|
|
51
55
|
text_document_did_close(uri)
|
52
56
|
when "textDocument/didChange"
|
53
|
-
text_document_did_change(
|
57
|
+
text_document_did_change(
|
58
|
+
uri,
|
59
|
+
request.dig(:params, :contentChanges),
|
60
|
+
request.dig(:params, :textDocument, :version),
|
61
|
+
)
|
54
62
|
when "textDocument/foldingRange"
|
55
63
|
folding_range(uri)
|
56
64
|
when "textDocument/documentLink"
|
@@ -66,6 +74,16 @@ module RubyLsp
|
|
66
74
|
when "textDocument/formatting"
|
67
75
|
begin
|
68
76
|
formatting(uri)
|
77
|
+
rescue Requests::Formatting::InvalidFormatter => error
|
78
|
+
@notifications << Notification.new(
|
79
|
+
message: "window/showMessage",
|
80
|
+
params: Interface::ShowMessageParams.new(
|
81
|
+
type: Constant::MessageType::ERROR,
|
82
|
+
message: "Configuration error: #{error.message}",
|
83
|
+
),
|
84
|
+
)
|
85
|
+
|
86
|
+
nil
|
69
87
|
rescue StandardError => error
|
70
88
|
@notifications << Notification.new(
|
71
89
|
message: "window/showMessage",
|
@@ -128,7 +146,7 @@ module RubyLsp
|
|
128
146
|
sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
|
129
147
|
def document_link(uri)
|
130
148
|
@store.cache_fetch(uri, :document_link) do |document|
|
131
|
-
RubyLsp::Requests::DocumentLink.new(
|
149
|
+
RubyLsp::Requests::DocumentLink.new(document).run
|
132
150
|
end
|
133
151
|
end
|
134
152
|
|
@@ -139,15 +157,15 @@ module RubyLsp
|
|
139
157
|
end
|
140
158
|
end
|
141
159
|
|
142
|
-
sig { params(uri: String, content_changes: T::Array[Document::EditShape]).returns(Object) }
|
143
|
-
def text_document_did_change(uri, content_changes)
|
144
|
-
@store.push_edits(uri, content_changes)
|
160
|
+
sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
|
161
|
+
def text_document_did_change(uri, content_changes, version)
|
162
|
+
@store.push_edits(uri: uri, edits: content_changes, version: version)
|
145
163
|
VOID
|
146
164
|
end
|
147
165
|
|
148
|
-
sig { params(uri: String, text: String).returns(Object) }
|
149
|
-
def text_document_did_open(uri, text)
|
150
|
-
@store.set(uri, text)
|
166
|
+
sig { params(uri: String, text: String, version: Integer).returns(Object) }
|
167
|
+
def text_document_did_open(uri, text, version)
|
168
|
+
@store.set(uri: uri, source: text, version: version)
|
151
169
|
VOID
|
152
170
|
end
|
153
171
|
|
@@ -197,7 +215,7 @@ module RubyLsp
|
|
197
215
|
|
198
216
|
sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
|
199
217
|
def formatting(uri)
|
200
|
-
Requests::Formatting.new(
|
218
|
+
Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).run
|
201
219
|
end
|
202
220
|
|
203
221
|
sig do
|
@@ -240,7 +258,7 @@ module RubyLsp
|
|
240
258
|
def code_action(uri, range, context)
|
241
259
|
document = @store.get(uri)
|
242
260
|
|
243
|
-
Requests::CodeActions.new(
|
261
|
+
Requests::CodeActions.new(document, range, context).run
|
244
262
|
end
|
245
263
|
|
246
264
|
sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
|
@@ -259,6 +277,15 @@ module RubyLsp
|
|
259
277
|
),
|
260
278
|
)
|
261
279
|
raise Requests::CodeActionResolve::CodeActionError
|
280
|
+
when Requests::CodeActionResolve::Error::InvalidTargetRange
|
281
|
+
@notifications << Notification.new(
|
282
|
+
message: "window/showMessage",
|
283
|
+
params: Interface::ShowMessageParams.new(
|
284
|
+
type: Constant::MessageType::ERROR,
|
285
|
+
message: "Couldn't find an appropriate location to place extracted refactor",
|
286
|
+
),
|
287
|
+
)
|
288
|
+
raise Requests::CodeActionResolve::CodeActionError
|
262
289
|
else
|
263
290
|
result
|
264
291
|
end
|
@@ -267,7 +294,7 @@ module RubyLsp
|
|
267
294
|
sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
|
268
295
|
def diagnostic(uri)
|
269
296
|
response = @store.cache_fetch(uri, :diagnostics) do |document|
|
270
|
-
Requests::Diagnostics.new(
|
297
|
+
Requests::Diagnostics.new(document).run
|
271
298
|
end
|
272
299
|
|
273
300
|
Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response.map(&:to_lsp_diagnostic)) if response
|
@@ -300,9 +327,26 @@ module RubyLsp
|
|
300
327
|
def initialize_request(options)
|
301
328
|
@store.clear
|
302
329
|
@store.encoding = options.dig(:capabilities, :general, :positionEncodings)
|
303
|
-
|
330
|
+
formatter = options.dig(:initializationOptions, :formatter)
|
331
|
+
@store.formatter = formatter unless formatter.nil?
|
332
|
+
|
333
|
+
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
334
|
+
|
335
|
+
enabled_features = case configured_features
|
336
|
+
when Array
|
337
|
+
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
338
|
+
# why we use `false` as the default value
|
339
|
+
Hash.new(false).merge!(configured_features.to_h { |feature| [feature, true] })
|
340
|
+
when Hash
|
341
|
+
# If the configuration is already a hash, merge it with a default value of `true`. That way clients don't have
|
342
|
+
# to opt-in to every single feature
|
343
|
+
Hash.new(true).merge!(configured_features)
|
344
|
+
else
|
345
|
+
# If no configuration was passed by the client, just enable every feature
|
346
|
+
Hash.new(true)
|
347
|
+
end
|
304
348
|
|
305
|
-
document_symbol_provider = if enabled_features
|
349
|
+
document_symbol_provider = if enabled_features["documentSymbols"]
|
306
350
|
Interface::DocumentSymbolClientCapabilities.new(
|
307
351
|
hierarchical_document_symbol_support: true,
|
308
352
|
symbol_kind: {
|
@@ -311,19 +355,19 @@ module RubyLsp
|
|
311
355
|
)
|
312
356
|
end
|
313
357
|
|
314
|
-
document_link_provider = if enabled_features
|
358
|
+
document_link_provider = if enabled_features["documentLink"]
|
315
359
|
Interface::DocumentLinkOptions.new(resolve_provider: false)
|
316
360
|
end
|
317
361
|
|
318
|
-
hover_provider = if enabled_features
|
362
|
+
hover_provider = if enabled_features["hover"]
|
319
363
|
Interface::HoverClientCapabilities.new(dynamic_registration: false)
|
320
364
|
end
|
321
365
|
|
322
|
-
folding_ranges_provider = if enabled_features
|
366
|
+
folding_ranges_provider = if enabled_features["foldingRanges"]
|
323
367
|
Interface::FoldingRangeClientCapabilities.new(line_folding_only: true)
|
324
368
|
end
|
325
369
|
|
326
|
-
semantic_tokens_provider = if enabled_features
|
370
|
+
semantic_tokens_provider = if enabled_features["semanticHighlighting"]
|
327
371
|
Interface::SemanticTokensRegistrationOptions.new(
|
328
372
|
document_selector: { scheme: "file", language: "ruby" },
|
329
373
|
legend: Interface::SemanticTokensLegend.new(
|
@@ -335,29 +379,29 @@ module RubyLsp
|
|
335
379
|
)
|
336
380
|
end
|
337
381
|
|
338
|
-
diagnostics_provider = if enabled_features
|
382
|
+
diagnostics_provider = if enabled_features["diagnostics"]
|
339
383
|
{
|
340
384
|
interFileDependencies: false,
|
341
385
|
workspaceDiagnostics: false,
|
342
386
|
}
|
343
387
|
end
|
344
388
|
|
345
|
-
on_type_formatting_provider = if enabled_features
|
389
|
+
on_type_formatting_provider = if enabled_features["onTypeFormatting"]
|
346
390
|
Interface::DocumentOnTypeFormattingOptions.new(
|
347
391
|
first_trigger_character: "{",
|
348
392
|
more_trigger_character: ["\n", "|"],
|
349
393
|
)
|
350
394
|
end
|
351
395
|
|
352
|
-
code_action_provider = if enabled_features
|
396
|
+
code_action_provider = if enabled_features["codeActions"]
|
353
397
|
Interface::CodeActionOptions.new(resolve_provider: true)
|
354
398
|
end
|
355
399
|
|
356
|
-
inlay_hint_provider = if enabled_features
|
400
|
+
inlay_hint_provider = if enabled_features["inlayHint"]
|
357
401
|
Interface::InlayHintOptions.new(resolve_provider: false)
|
358
402
|
end
|
359
403
|
|
360
|
-
completion_provider = if enabled_features
|
404
|
+
completion_provider = if enabled_features["completion"]
|
361
405
|
Interface::CompletionOptions.new(
|
362
406
|
resolve_provider: false,
|
363
407
|
trigger_characters: ["/"],
|
@@ -370,14 +414,14 @@ module RubyLsp
|
|
370
414
|
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
371
415
|
open_close: true,
|
372
416
|
),
|
373
|
-
selection_range_provider: enabled_features
|
417
|
+
selection_range_provider: enabled_features["selectionRanges"],
|
374
418
|
hover_provider: hover_provider,
|
375
419
|
document_symbol_provider: document_symbol_provider,
|
376
420
|
document_link_provider: document_link_provider,
|
377
421
|
folding_range_provider: folding_ranges_provider,
|
378
422
|
semantic_tokens_provider: semantic_tokens_provider,
|
379
|
-
document_formatting_provider: enabled_features
|
380
|
-
document_highlight_provider: enabled_features
|
423
|
+
document_formatting_provider: enabled_features["formatting"] && formatter != "none",
|
424
|
+
document_highlight_provider: enabled_features["documentHighlights"],
|
381
425
|
code_action_provider: code_action_provider,
|
382
426
|
document_on_type_formatting_provider: on_type_formatting_provider,
|
383
427
|
diagnostic_provider: diagnostics_provider,
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -28,16 +28,24 @@ module RubyLsp
|
|
28
28
|
sig { abstract.returns(Object) }
|
29
29
|
def run; end
|
30
30
|
|
31
|
-
|
31
|
+
# Syntax Tree implements `visit_all` using `map` instead of `each` for users who want to use the pattern
|
32
|
+
# `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
|
33
|
+
# every single node visited
|
34
|
+
sig { params(nodes: T::Array[SyntaxTree::Node]).void }
|
35
|
+
def visit_all(nodes)
|
36
|
+
nodes.each { |node| visit(node) }
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
|
32
40
|
def range_from_syntax_tree_node(node)
|
33
41
|
loc = node.location
|
34
42
|
|
35
|
-
|
36
|
-
start:
|
43
|
+
Interface::Range.new(
|
44
|
+
start: Interface::Position.new(
|
37
45
|
line: loc.start_line - 1,
|
38
46
|
character: loc.start_column,
|
39
47
|
),
|
40
|
-
end:
|
48
|
+
end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
41
49
|
)
|
42
50
|
end
|
43
51
|
|
@@ -89,14 +97,15 @@ module RubyLsp
|
|
89
97
|
# If the node's start character is already past the position, then we should've found the closest node already
|
90
98
|
break if position < loc.start_char
|
91
99
|
|
100
|
+
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
101
|
+
next if node_types.any? && node_types.none? { |type| candidate.is_a?(type) }
|
102
|
+
|
92
103
|
# If the current node is narrower than or equal to the previous closest node, then it is more precise
|
93
104
|
closest_loc = closest.location
|
94
105
|
if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
|
95
106
|
parent = T.let(closest, SyntaxTree::Node)
|
96
107
|
closest = candidate
|
97
108
|
end
|
98
|
-
|
99
|
-
break if node_types.any? { |type| candidate.is_a?(type) }
|
100
109
|
end
|
101
110
|
|
102
111
|
[closest, parent]
|
@@ -30,6 +30,7 @@ module RubyLsp
|
|
30
30
|
class Error < ::T::Enum
|
31
31
|
enums do
|
32
32
|
EmptySelection = new
|
33
|
+
InvalidTargetRange = new
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
@@ -45,11 +46,44 @@ module RubyLsp
|
|
45
46
|
source_range = @code_action.dig(:data, :range)
|
46
47
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
47
48
|
|
49
|
+
return Error::InvalidTargetRange if @document.syntax_error?
|
50
|
+
|
48
51
|
scanner = @document.create_scanner
|
49
52
|
start_index = scanner.find_char_position(source_range[:start])
|
50
53
|
end_index = scanner.find_char_position(source_range[:end])
|
51
|
-
|
52
|
-
|
54
|
+
extracted_source = T.must(@document.source[start_index...end_index])
|
55
|
+
|
56
|
+
# Find the closest statements node, so that we place the refactor in a valid position
|
57
|
+
closest_statements = locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements]).first
|
58
|
+
return Error::InvalidTargetRange if closest_statements.nil?
|
59
|
+
|
60
|
+
# Find the node with the end line closest to the requested position, so that we can place the refactor
|
61
|
+
# immediately after that closest node
|
62
|
+
closest_node = closest_statements.child_nodes.compact.min_by do |node|
|
63
|
+
distance = source_range.dig(:start, :line) - (node.location.end_line - 1)
|
64
|
+
distance <= 0 ? Float::INFINITY : distance
|
65
|
+
end
|
66
|
+
|
67
|
+
# When trying to extract the first node inside of a statements block, then we can just select one line above it
|
68
|
+
target_line = if closest_node == closest_statements.child_nodes.first
|
69
|
+
closest_node.location.start_line - 1
|
70
|
+
else
|
71
|
+
closest_node.location.end_line
|
72
|
+
end
|
73
|
+
|
74
|
+
lines = @document.source.lines
|
75
|
+
indentation = T.must(T.must(lines[target_line - 1])[/\A */]).size
|
76
|
+
|
77
|
+
target_range = {
|
78
|
+
start: { line: target_line, character: indentation },
|
79
|
+
end: { line: target_line, character: indentation },
|
80
|
+
}
|
81
|
+
|
82
|
+
variable_source = if T.must(lines[target_line]).strip.empty?
|
83
|
+
"\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
|
84
|
+
else
|
85
|
+
"#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
|
86
|
+
end
|
53
87
|
|
54
88
|
Interface::CodeAction.new(
|
55
89
|
title: "Refactor: Extract Variable",
|
@@ -60,7 +94,10 @@ module RubyLsp
|
|
60
94
|
uri: @code_action.dig(:data, :uri),
|
61
95
|
version: nil,
|
62
96
|
),
|
63
|
-
edits:
|
97
|
+
edits: [
|
98
|
+
create_text_edit(source_range, NEW_VARIABLE_NAME),
|
99
|
+
create_text_edit(target_range, variable_source),
|
100
|
+
],
|
64
101
|
),
|
65
102
|
],
|
66
103
|
),
|
@@ -69,22 +106,6 @@ module RubyLsp
|
|
69
106
|
|
70
107
|
private
|
71
108
|
|
72
|
-
sig do
|
73
|
-
params(range: Document::RangeShape, source: String, indentation: Integer)
|
74
|
-
.returns(T::Array[Interface::TextEdit])
|
75
|
-
end
|
76
|
-
def edits_to_extract_variable(range, source, indentation)
|
77
|
-
target_range = {
|
78
|
-
start: { line: range.dig(:start, :line), character: indentation },
|
79
|
-
end: { line: range.dig(:start, :line), character: indentation },
|
80
|
-
}
|
81
|
-
|
82
|
-
[
|
83
|
-
create_text_edit(range, NEW_VARIABLE_NAME),
|
84
|
-
create_text_edit(target_range, "#{NEW_VARIABLE_NAME} = #{source}\n#{" " * indentation}"),
|
85
|
-
]
|
86
|
-
end
|
87
|
-
|
88
109
|
sig { params(range: Document::RangeShape, new_text: String).returns(Interface::TextEdit) }
|
89
110
|
def create_text_edit(range, new_text)
|
90
111
|
Interface::TextEdit.new(
|