ruby-lsp 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -1
- data/.github/workflows/publish_docs.yml +32 -0
- data/.rubocop.yml +25 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +8 -4
- data/Gemfile.lock +64 -13
- data/README.md +58 -1
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/bin/tapioca +29 -0
- data/dev.yml +3 -0
- data/exe/ruby-lsp +19 -3
- data/lib/ruby-lsp.rb +2 -0
- data/lib/ruby_lsp/cli.rb +23 -7
- data/lib/ruby_lsp/document.rb +98 -6
- data/lib/ruby_lsp/handler.rb +119 -18
- data/lib/ruby_lsp/internal.rb +7 -0
- data/lib/ruby_lsp/requests/base_request.rb +19 -5
- data/lib/ruby_lsp/requests/code_actions.rb +30 -9
- data/lib/ruby_lsp/requests/diagnostics.rb +29 -77
- data/lib/ruby_lsp/requests/document_highlight.rb +111 -0
- data/lib/ruby_lsp/requests/document_symbol.rb +75 -16
- data/lib/ruby_lsp/requests/folding_ranges.rb +63 -19
- data/lib/ruby_lsp/requests/formatting.rb +19 -2
- data/lib/ruby_lsp/requests/rubocop_request.rb +21 -8
- data/lib/ruby_lsp/requests/selection_ranges.rb +114 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +132 -61
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +100 -0
- data/lib/ruby_lsp/requests/support/selection_range.rb +20 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +70 -0
- data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +32 -0
- data/lib/ruby_lsp/requests.rb +10 -0
- data/lib/ruby_lsp/store.rb +23 -2
- data/rakelib/check_docs.rake +57 -0
- data/ruby-lsp.gemspec +2 -1
- data/sorbet/config +4 -0
- data/sorbet/rbi/.rubocop.yml +8 -0
- data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
- data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
- data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
- data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
- data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
- data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
- data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
- data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
- data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
- data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
- data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
- data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
- data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
- data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
- data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
- data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
- data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
- data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
- data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
- data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
- data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
- data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
- data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
- data/sorbet/rbi/shims/fiddle.rbi +4 -0
- data/sorbet/rbi/shims/hash.rbi +6 -0
- data/sorbet/rbi/shims/rdoc.rbi +4 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +7 -0
- metadata +74 -6
- data/shipit.production.yml +0 -1
data/lib/ruby_lsp/handler.rb
CHANGED
|
@@ -1,50 +1,81 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require "ruby_lsp/requests"
|
|
4
5
|
require "ruby_lsp/store"
|
|
6
|
+
require "benchmark"
|
|
5
7
|
|
|
6
8
|
module RubyLsp
|
|
7
9
|
class Handler
|
|
10
|
+
extend T::Sig
|
|
11
|
+
VOID = T.let(Object.new.freeze, Object)
|
|
12
|
+
|
|
13
|
+
sig { returns(Store) }
|
|
8
14
|
attr_reader :store
|
|
9
15
|
|
|
10
16
|
Interface = LanguageServer::Protocol::Interface
|
|
11
17
|
Constant = LanguageServer::Protocol::Constant
|
|
12
18
|
Transport = LanguageServer::Protocol::Transport
|
|
13
19
|
|
|
20
|
+
sig { void }
|
|
14
21
|
def initialize
|
|
15
|
-
@writer = Transport::Stdio::Writer.new
|
|
16
|
-
@reader = Transport::Stdio::Reader.new
|
|
17
|
-
@handlers = {}
|
|
18
|
-
@store = Store.new
|
|
22
|
+
@writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
|
|
23
|
+
@reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
|
|
24
|
+
@handlers = T.let({}, T::Hash[String, T.proc.params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)])
|
|
25
|
+
@store = T.let(Store.new, Store)
|
|
19
26
|
end
|
|
20
27
|
|
|
28
|
+
sig { void }
|
|
21
29
|
def start
|
|
22
30
|
$stderr.puts "Starting Ruby LSP..."
|
|
23
|
-
@reader.read
|
|
24
|
-
handle(request)
|
|
25
|
-
end
|
|
31
|
+
@reader.read { |request| handle(request) }
|
|
26
32
|
end
|
|
27
33
|
|
|
34
|
+
sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
|
|
28
35
|
def config(&blk)
|
|
29
36
|
instance_exec(&blk)
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
private
|
|
33
40
|
|
|
41
|
+
sig do
|
|
42
|
+
params(
|
|
43
|
+
msg: String,
|
|
44
|
+
blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
|
|
45
|
+
).void
|
|
46
|
+
end
|
|
34
47
|
def on(msg, &blk)
|
|
35
|
-
@handlers[msg
|
|
48
|
+
@handlers[msg] = blk
|
|
36
49
|
end
|
|
37
50
|
|
|
51
|
+
sig { params(request: T::Hash[Symbol, T.untyped]).void }
|
|
38
52
|
def handle(request)
|
|
39
|
-
result =
|
|
40
|
-
|
|
53
|
+
result = T.let(nil, T.untyped)
|
|
54
|
+
error = T.let(nil, T.nilable(StandardError))
|
|
55
|
+
handler = @handlers[request[:method]]
|
|
56
|
+
|
|
57
|
+
request_time = Benchmark.realtime do
|
|
58
|
+
if handler
|
|
59
|
+
begin
|
|
60
|
+
result = handler.call(request)
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
error = e
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@writer.write(id: request[:id], result: result) unless result == VOID
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
|
|
41
70
|
end
|
|
42
71
|
|
|
72
|
+
sig { void }
|
|
43
73
|
def shutdown
|
|
44
74
|
$stderr.puts "Shutting down Ruby LSP..."
|
|
45
75
|
store.clear
|
|
46
76
|
end
|
|
47
77
|
|
|
78
|
+
sig { params(enabled_features: T::Array[String]).returns(Interface::InitializeResult) }
|
|
48
79
|
def respond_with_capabilities(enabled_features)
|
|
49
80
|
document_symbol_provider = if enabled_features.include?("documentSymbols")
|
|
50
81
|
Interface::DocumentSymbolClientCapabilities.new(
|
|
@@ -64,7 +95,7 @@ module RubyLsp
|
|
|
64
95
|
document_selector: { scheme: "file", language: "ruby" },
|
|
65
96
|
legend: Interface::SemanticTokensLegend.new(
|
|
66
97
|
token_types: Requests::SemanticHighlighting::TOKEN_TYPES,
|
|
67
|
-
token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS
|
|
98
|
+
token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys
|
|
68
99
|
),
|
|
69
100
|
range: false,
|
|
70
101
|
full: {
|
|
@@ -76,43 +107,75 @@ module RubyLsp
|
|
|
76
107
|
Interface::InitializeResult.new(
|
|
77
108
|
capabilities: Interface::ServerCapabilities.new(
|
|
78
109
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
|
79
|
-
change: Constant::TextDocumentSyncKind::
|
|
110
|
+
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
|
80
111
|
open_close: true,
|
|
81
112
|
),
|
|
113
|
+
selection_range_provider: enabled_features.include?("selectionRanges"),
|
|
82
114
|
document_symbol_provider: document_symbol_provider,
|
|
83
115
|
folding_range_provider: folding_ranges_provider,
|
|
84
116
|
semantic_tokens_provider: semantic_tokens_provider,
|
|
85
117
|
document_formatting_provider: enabled_features.include?("formatting"),
|
|
118
|
+
document_highlight_provider: enabled_features.include?("documentHighlights"),
|
|
86
119
|
code_action_provider: enabled_features.include?("codeActions")
|
|
87
120
|
)
|
|
88
121
|
)
|
|
89
122
|
end
|
|
90
123
|
|
|
124
|
+
sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
|
|
91
125
|
def respond_with_document_symbol(uri)
|
|
92
126
|
store.cache_fetch(uri, :document_symbol) do |document|
|
|
93
|
-
RubyLsp::Requests::DocumentSymbol.
|
|
127
|
+
RubyLsp::Requests::DocumentSymbol.new(document).run
|
|
94
128
|
end
|
|
95
129
|
end
|
|
96
130
|
|
|
131
|
+
sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::FoldingRange]) }
|
|
97
132
|
def respond_with_folding_ranges(uri)
|
|
98
133
|
store.cache_fetch(uri, :folding_ranges) do |document|
|
|
99
|
-
Requests::FoldingRanges.
|
|
134
|
+
Requests::FoldingRanges.new(document).run
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig do
|
|
139
|
+
params(
|
|
140
|
+
uri: String,
|
|
141
|
+
positions: T::Array[Document::PositionShape]
|
|
142
|
+
).returns(T::Array[T.nilable(RubyLsp::Requests::Support::SelectionRange)])
|
|
143
|
+
end
|
|
144
|
+
def respond_with_selection_ranges(uri, positions)
|
|
145
|
+
ranges = store.cache_fetch(uri, :selection_ranges) do |document|
|
|
146
|
+
Requests::SelectionRanges.new(document).run
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
|
|
150
|
+
# every position in the positions array should have an element at the same index in the response
|
|
151
|
+
# array. For positions without a valid selection range, the corresponding element in the response
|
|
152
|
+
# array will be nil.
|
|
153
|
+
positions.map do |position|
|
|
154
|
+
ranges.find do |range|
|
|
155
|
+
range.cover?(position)
|
|
156
|
+
end
|
|
100
157
|
end
|
|
101
158
|
end
|
|
102
159
|
|
|
160
|
+
sig { params(uri: String).returns(LanguageServer::Protocol::Interface::SemanticTokens) }
|
|
103
161
|
def respond_with_semantic_highlighting(uri)
|
|
104
162
|
store.cache_fetch(uri, :semantic_highlighting) do |document|
|
|
105
|
-
|
|
163
|
+
T.cast(
|
|
164
|
+
Requests::SemanticHighlighting.new(document, encoder: Requests::Support::SemanticTokenEncoder.new).run,
|
|
165
|
+
LanguageServer::Protocol::Interface::SemanticTokens
|
|
166
|
+
)
|
|
106
167
|
end
|
|
107
168
|
end
|
|
108
169
|
|
|
170
|
+
sig { params(uri: String).returns(T.nilable(T::Array[LanguageServer::Protocol::Interface::TextEdit])) }
|
|
109
171
|
def respond_with_formatting(uri)
|
|
110
|
-
Requests::Formatting.
|
|
172
|
+
Requests::Formatting.new(uri, store.get(uri)).run
|
|
111
173
|
end
|
|
112
174
|
|
|
175
|
+
sig { params(uri: String).void }
|
|
113
176
|
def send_diagnostics(uri)
|
|
114
177
|
response = store.cache_fetch(uri, :diagnostics) do |document|
|
|
115
|
-
Requests::Diagnostics.
|
|
178
|
+
Requests::Diagnostics.new(uri, document).run
|
|
116
179
|
end
|
|
117
180
|
|
|
118
181
|
@writer.write(
|
|
@@ -124,10 +187,48 @@ module RubyLsp
|
|
|
124
187
|
)
|
|
125
188
|
end
|
|
126
189
|
|
|
190
|
+
sig do
|
|
191
|
+
params(uri: String, range: T::Range[Integer]).returns(T::Array[LanguageServer::Protocol::Interface::CodeAction])
|
|
192
|
+
end
|
|
127
193
|
def respond_with_code_actions(uri, range)
|
|
128
194
|
store.cache_fetch(uri, :code_actions) do |document|
|
|
129
|
-
Requests::CodeActions.
|
|
195
|
+
Requests::CodeActions.new(uri, document, range).run
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
sig do
|
|
200
|
+
params(
|
|
201
|
+
uri: String,
|
|
202
|
+
position: Document::PositionShape
|
|
203
|
+
).returns(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
|
|
204
|
+
end
|
|
205
|
+
def respond_with_document_highlight(uri, position)
|
|
206
|
+
Requests::DocumentHighlight.new(store.get(uri), position).run
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
sig do
|
|
210
|
+
params(
|
|
211
|
+
request: T::Hash[Symbol, T.untyped],
|
|
212
|
+
request_time: Float,
|
|
213
|
+
error: T.nilable(StandardError)
|
|
214
|
+
).returns(T::Hash[Symbol, T.any(String, Float)])
|
|
215
|
+
end
|
|
216
|
+
def telemetry_params(request, request_time, error)
|
|
217
|
+
uri = request.dig(:params, :textDocument, :uri)
|
|
218
|
+
|
|
219
|
+
params = {
|
|
220
|
+
request: request[:method],
|
|
221
|
+
lspVersion: RubyLsp::VERSION,
|
|
222
|
+
requestTime: request_time,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if error
|
|
226
|
+
params[:errorClass] = error.class.name
|
|
227
|
+
params[:errorMessage] = error.message
|
|
130
228
|
end
|
|
229
|
+
|
|
230
|
+
params[:uri] = uri if uri
|
|
231
|
+
params
|
|
131
232
|
end
|
|
132
233
|
end
|
|
133
234
|
end
|
|
@@ -1,20 +1,34 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RubyLsp
|
|
4
5
|
module Requests
|
|
6
|
+
# :nodoc:
|
|
5
7
|
class BaseRequest < SyntaxTree::Visitor
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
extend T::Sig
|
|
9
|
+
extend T::Helpers
|
|
10
|
+
|
|
11
|
+
abstract!
|
|
9
12
|
|
|
13
|
+
sig { params(document: Document).void }
|
|
10
14
|
def initialize(document)
|
|
11
15
|
@document = document
|
|
12
16
|
|
|
13
17
|
super()
|
|
14
18
|
end
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
sig { abstract.returns(Object) }
|
|
21
|
+
def run; end
|
|
22
|
+
|
|
23
|
+
sig { params(node: SyntaxTree::Node).returns(LanguageServer::Protocol::Interface::Range) }
|
|
24
|
+
def range_from_syntax_tree_node(node)
|
|
25
|
+
loc = node.location
|
|
26
|
+
|
|
27
|
+
LanguageServer::Protocol::Interface::Range.new(
|
|
28
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: loc.start_line - 1,
|
|
29
|
+
character: loc.start_column),
|
|
30
|
+
end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
|
31
|
+
)
|
|
18
32
|
end
|
|
19
33
|
end
|
|
20
34
|
end
|
|
@@ -1,24 +1,45 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RubyLsp
|
|
4
5
|
module Requests
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
|
|
7
|
+
# request informs the editor of RuboCop quick fixes that can be applied. These are accesible by hovering over a
|
|
8
|
+
# specific diagnostic.
|
|
9
|
+
#
|
|
10
|
+
# # Example
|
|
11
|
+
#
|
|
12
|
+
# ```ruby
|
|
13
|
+
# def say_hello
|
|
14
|
+
# puts "Hello" # --> code action: quick fix indentation
|
|
15
|
+
# end
|
|
16
|
+
# ```
|
|
17
|
+
class CodeActions < BaseRequest
|
|
18
|
+
extend T::Sig
|
|
9
19
|
|
|
20
|
+
sig do
|
|
21
|
+
params(
|
|
22
|
+
uri: String,
|
|
23
|
+
document: Document,
|
|
24
|
+
range: T::Range[Integer]
|
|
25
|
+
).void
|
|
26
|
+
end
|
|
10
27
|
def initialize(uri, document, range)
|
|
11
|
-
|
|
28
|
+
super(document)
|
|
29
|
+
|
|
12
30
|
@uri = uri
|
|
13
31
|
@range = range
|
|
14
32
|
end
|
|
15
33
|
|
|
34
|
+
sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::CodeAction], Object)) }
|
|
16
35
|
def run
|
|
17
|
-
diagnostics = Diagnostics.
|
|
18
|
-
corrections = diagnostics.select
|
|
19
|
-
|
|
36
|
+
diagnostics = Diagnostics.new(@uri, @document).run
|
|
37
|
+
corrections = diagnostics.select do |diagnostic|
|
|
38
|
+
diagnostic.correctable? && T.cast(diagnostic, Support::RuboCopDiagnostic).in_range?(@range)
|
|
39
|
+
end
|
|
40
|
+
return [] if corrections.empty?
|
|
20
41
|
|
|
21
|
-
corrections.map!(&:to_lsp_code_action)
|
|
42
|
+
T.cast(corrections, T::Array[Support::RuboCopDiagnostic]).map!(&:to_lsp_code_action)
|
|
22
43
|
end
|
|
23
44
|
end
|
|
24
45
|
end
|
|
@@ -1,96 +1,48 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RubyLsp
|
|
4
5
|
module Requests
|
|
6
|
+
# The
|
|
7
|
+
# [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
|
|
8
|
+
# request informs the editor of RuboCop offenses for a given file.
|
|
9
|
+
#
|
|
10
|
+
# # Example
|
|
11
|
+
#
|
|
12
|
+
# ```ruby
|
|
13
|
+
# def say_hello
|
|
14
|
+
# puts "Hello" # --> diagnostics: incorrect indentantion
|
|
15
|
+
# end
|
|
16
|
+
# ```
|
|
5
17
|
class Diagnostics < RuboCopRequest
|
|
6
|
-
|
|
7
|
-
convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
8
|
-
info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
9
|
-
refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
10
|
-
warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
|
|
11
|
-
error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
|
12
|
-
fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
|
13
|
-
}.freeze
|
|
18
|
+
extend T::Sig
|
|
14
19
|
|
|
20
|
+
sig do
|
|
21
|
+
override.returns(
|
|
22
|
+
T.any(
|
|
23
|
+
T.all(T::Array[Support::RuboCopDiagnostic], Object),
|
|
24
|
+
T.all(T::Array[Support::SyntaxErrorDiagnostic], Object),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
15
28
|
def run
|
|
29
|
+
return syntax_error_diagnostics if @document.syntax_errors?
|
|
30
|
+
|
|
16
31
|
super
|
|
17
32
|
|
|
18
33
|
@diagnostics
|
|
19
34
|
end
|
|
20
35
|
|
|
36
|
+
sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
|
|
21
37
|
def file_finished(_file, offenses)
|
|
22
|
-
@diagnostics = offenses.map { |offense|
|
|
38
|
+
@diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
|
|
23
39
|
end
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
attr_reader :replacements
|
|
27
|
-
|
|
28
|
-
def initialize(offense, uri)
|
|
29
|
-
@offense = offense
|
|
30
|
-
@uri = uri
|
|
31
|
-
@replacements = offense.correctable? ? offense_replacements : []
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def correctable?
|
|
35
|
-
@offense.correctable?
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def in_range?(range)
|
|
39
|
-
range.cover?(@offense.line - 1)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def to_lsp_code_action
|
|
43
|
-
LanguageServer::Protocol::Interface::CodeAction.new(
|
|
44
|
-
title: "Autocorrect #{@offense.cop_name}",
|
|
45
|
-
kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
|
|
46
|
-
edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
|
|
47
|
-
document_changes: [
|
|
48
|
-
LanguageServer::Protocol::Interface::TextDocumentEdit.new(
|
|
49
|
-
text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
|
|
50
|
-
uri: @uri,
|
|
51
|
-
version: nil
|
|
52
|
-
),
|
|
53
|
-
edits: @replacements
|
|
54
|
-
),
|
|
55
|
-
]
|
|
56
|
-
),
|
|
57
|
-
is_preferred: true,
|
|
58
|
-
)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def to_lsp_diagnostic
|
|
62
|
-
LanguageServer::Protocol::Interface::Diagnostic.new(
|
|
63
|
-
message: @offense.message,
|
|
64
|
-
source: "RuboCop",
|
|
65
|
-
code: @offense.cop_name,
|
|
66
|
-
severity: RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name],
|
|
67
|
-
range: LanguageServer::Protocol::Interface::Range.new(
|
|
68
|
-
start: LanguageServer::Protocol::Interface::Position.new(
|
|
69
|
-
line: @offense.line - 1,
|
|
70
|
-
character: @offense.column
|
|
71
|
-
),
|
|
72
|
-
end: LanguageServer::Protocol::Interface::Position.new(
|
|
73
|
-
line: @offense.last_line - 1,
|
|
74
|
-
character: @offense.last_column
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
private
|
|
41
|
+
private
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
range: LanguageServer::Protocol::Interface::Range.new(
|
|
86
|
-
start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
|
|
87
|
-
end: LanguageServer::Protocol::Interface::Position.new(line: range.last_line - 1,
|
|
88
|
-
character: range.last_column)
|
|
89
|
-
),
|
|
90
|
-
new_text: replacement
|
|
91
|
-
)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
43
|
+
sig { returns(T::Array[Support::SyntaxErrorDiagnostic]) }
|
|
44
|
+
def syntax_error_diagnostics
|
|
45
|
+
@document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
|
|
94
46
|
end
|
|
95
47
|
end
|
|
96
48
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
|
|
7
|
+
# informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
|
|
8
|
+
# the cursor is on the `F` of the constant `FOO`, the editor should identify other occurences of `FOO`
|
|
9
|
+
# and highlight them.
|
|
10
|
+
#
|
|
11
|
+
# For writable elements like constants or variables, their read/write occurrences should be highlighted differently.
|
|
12
|
+
# This is achieved by sending different "kind" attributes to the editor (2 for read and 3 for write).
|
|
13
|
+
#
|
|
14
|
+
# # Example
|
|
15
|
+
#
|
|
16
|
+
# ```ruby
|
|
17
|
+
# FOO = 1 # should be highlighted as "write"
|
|
18
|
+
#
|
|
19
|
+
# def foo
|
|
20
|
+
# FOO # should be highlighted as "read"
|
|
21
|
+
# end
|
|
22
|
+
# ```
|
|
23
|
+
class DocumentHighlight < BaseRequest
|
|
24
|
+
extend T::Sig
|
|
25
|
+
|
|
26
|
+
VarNodes = T.type_alias do
|
|
27
|
+
T.any(
|
|
28
|
+
SyntaxTree::GVar,
|
|
29
|
+
SyntaxTree::Ident,
|
|
30
|
+
SyntaxTree::IVar,
|
|
31
|
+
SyntaxTree::Const,
|
|
32
|
+
SyntaxTree::CVar
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { params(document: Document, position: Document::PositionShape).void }
|
|
37
|
+
def initialize(document, position)
|
|
38
|
+
@highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
|
|
39
|
+
position = Document::Scanner.new(document.source).find_position(position)
|
|
40
|
+
@target = T.let(find(document.tree, position), T.nilable(VarNodes))
|
|
41
|
+
|
|
42
|
+
super(document)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
|
|
46
|
+
def run
|
|
47
|
+
# no @target means the target is not highlightable
|
|
48
|
+
return [] unless @target
|
|
49
|
+
|
|
50
|
+
visit(@document.tree)
|
|
51
|
+
@highlights
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
sig { params(node: SyntaxTree::VarField).void }
|
|
55
|
+
def visit_var_field(node)
|
|
56
|
+
if matches_target?(node.value)
|
|
57
|
+
add_highlight(
|
|
58
|
+
node.value,
|
|
59
|
+
LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sig { params(node: SyntaxTree::VarRef).void }
|
|
67
|
+
def visit_var_ref(node)
|
|
68
|
+
if matches_target?(node.value)
|
|
69
|
+
add_highlight(
|
|
70
|
+
node.value,
|
|
71
|
+
LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
sig { params(node: SyntaxTree::Node, position: Integer).returns(T.nilable(VarNodes)) }
|
|
81
|
+
def find(node, position)
|
|
82
|
+
matched =
|
|
83
|
+
node.child_nodes.compact.bsearch do |child|
|
|
84
|
+
if (child.location.start_char...child.location.end_char).cover?(position)
|
|
85
|
+
0
|
|
86
|
+
else
|
|
87
|
+
position <=> child.location.start_char
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
case matched
|
|
92
|
+
when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
|
|
93
|
+
matched
|
|
94
|
+
when SyntaxTree::Node
|
|
95
|
+
find(matched, position)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
sig { params(node: SyntaxTree::Node).returns(T::Boolean) }
|
|
100
|
+
def matches_target?(node)
|
|
101
|
+
node.is_a?(@target.class) && T.cast(node, VarNodes).value == T.must(@target).value
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
sig { params(node: SyntaxTree::Node, kind: Integer).void }
|
|
105
|
+
def add_highlight(node, kind)
|
|
106
|
+
range = range_from_syntax_tree_node(node)
|
|
107
|
+
@highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|