ruby-lsp 0.4.1 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)
|
1
|
+
[![Build Status](https://github.com/Shopify/ruby-lsp/workflows/CI/badge.svg)](https://github.com/Shopify/ruby-lsp/actions/workflows/ci.yml)
|
2
|
+
[![Ruby LSP extension](https://img.shields.io/badge/VS%20Code-Ruby%20LSP-success?logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
|
3
|
+
[![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](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(
|