ruby-lsp 0.0.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -0
- data/.rubocop.yml +25 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +10 -6
- data/Gemfile.lock +63 -16
- data/README.md +41 -0
- data/Rakefile +8 -1
- data/VERSION +1 -1
- data/bin/console +19 -0
- data/bin/tapioca +29 -0
- data/dev.yml +3 -0
- data/exe/ruby-lsp +19 -4
- data/lib/ruby-lsp.rb +2 -1
- data/lib/ruby_lsp/cli.rb +13 -5
- data/lib/ruby_lsp/document.rb +43 -14
- data/lib/ruby_lsp/handler.rb +107 -37
- data/lib/ruby_lsp/internal.rb +7 -0
- data/lib/ruby_lsp/requests/base_request.rb +18 -5
- data/lib/ruby_lsp/requests/code_actions.rb +20 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +25 -7
- data/lib/ruby_lsp/requests/document_highlight.rb +113 -0
- data/lib/ruby_lsp/requests/document_symbol.rb +56 -16
- data/lib/ruby_lsp/requests/folding_ranges.rb +70 -34
- data/lib/ruby_lsp/requests/formatting.rb +24 -14
- data/lib/ruby_lsp/requests/selection_ranges.rb +18 -4
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -34
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +16 -3
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +61 -0
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +50 -0
- data/lib/ruby_lsp/requests/support/selection_range.rb +4 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +24 -3
- data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +6 -0
- data/lib/ruby_lsp/requests.rb +13 -1
- data/lib/ruby_lsp/store.rb +20 -3
- data/rakelib/check_docs.rake +34 -6
- data/ruby-lsp.gemspec +7 -5
- 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 +62 -13
- data/lib/ruby_lsp/requests/rubocop_request.rb +0 -49
- data/shipit.production.yml +0 -1
@@ -1,7 +1,10 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
4
5
|
module Requests
|
6
|
+
# ![Semantic highlighting demo](../../misc/semantic_highlighting.gif)
|
7
|
+
#
|
5
8
|
# The [semantic
|
6
9
|
# highlighting](https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens)
|
7
10
|
# request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.
|
@@ -16,22 +19,77 @@ module RubyLsp
|
|
16
19
|
# end
|
17
20
|
# ```
|
18
21
|
class SemanticHighlighting < BaseRequest
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
extend T::Sig
|
23
|
+
|
24
|
+
TOKEN_TYPES = T.let({
|
25
|
+
namespace: 0,
|
26
|
+
type: 1,
|
27
|
+
class: 2,
|
28
|
+
enum: 3,
|
29
|
+
interface: 4,
|
30
|
+
struct: 5,
|
31
|
+
typeParameter: 6,
|
32
|
+
parameter: 7,
|
33
|
+
variable: 8,
|
34
|
+
property: 9,
|
35
|
+
enumMember: 10,
|
36
|
+
event: 11,
|
37
|
+
function: 12,
|
38
|
+
method: 13,
|
39
|
+
macro: 14,
|
40
|
+
keyword: 15,
|
41
|
+
modifier: 16,
|
42
|
+
comment: 17,
|
43
|
+
string: 18,
|
44
|
+
number: 19,
|
45
|
+
regexp: 20,
|
46
|
+
operator: 21,
|
47
|
+
decorator: 22,
|
48
|
+
}.freeze, T::Hash[Symbol, Integer])
|
49
|
+
|
50
|
+
TOKEN_MODIFIERS = T.let({
|
51
|
+
declaration: 0,
|
52
|
+
definition: 1,
|
53
|
+
readonly: 2,
|
54
|
+
static: 3,
|
55
|
+
deprecated: 4,
|
56
|
+
abstract: 5,
|
57
|
+
async: 6,
|
58
|
+
modification: 7,
|
59
|
+
documentation: 8,
|
60
|
+
default_library: 9,
|
61
|
+
}.freeze, T::Hash[Symbol, Integer])
|
62
|
+
|
63
|
+
SPECIAL_RUBY_METHODS = T.let((Module.instance_methods(false) +
|
64
|
+
Kernel.methods(false) + Bundler::Dsl.instance_methods(false) +
|
65
|
+
Module.private_instance_methods(false))
|
66
|
+
.map(&:to_s), T::Array[String])
|
67
|
+
|
68
|
+
class SemanticToken < T::Struct
|
69
|
+
const :location, SyntaxTree::Location
|
70
|
+
const :length, Integer
|
71
|
+
const :type, Integer
|
72
|
+
const :modifier, T::Array[Integer]
|
73
|
+
end
|
26
74
|
|
75
|
+
sig { params(document: Document, encoder: T.nilable(Support::SemanticTokenEncoder)).void }
|
27
76
|
def initialize(document, encoder: nil)
|
28
77
|
super(document)
|
29
78
|
|
30
79
|
@encoder = encoder
|
31
|
-
@tokens = []
|
32
|
-
@tree = document.tree
|
80
|
+
@tokens = T.let([], T::Array[SemanticToken])
|
81
|
+
@tree = T.let(document.tree, SyntaxTree::Node)
|
82
|
+
@special_methods = T.let(nil, T.nilable(T::Array[String]))
|
33
83
|
end
|
34
84
|
|
85
|
+
sig do
|
86
|
+
override.returns(
|
87
|
+
T.any(
|
88
|
+
LanguageServer::Protocol::Interface::SemanticTokens,
|
89
|
+
T.all(T::Array[SemanticToken], Object),
|
90
|
+
)
|
91
|
+
)
|
92
|
+
end
|
35
93
|
def run
|
36
94
|
visit(@tree)
|
37
95
|
return @tokens unless @encoder
|
@@ -39,59 +97,154 @@ module RubyLsp
|
|
39
97
|
@encoder.encode(@tokens)
|
40
98
|
end
|
41
99
|
|
42
|
-
|
43
|
-
node.target.parts.each do |var_ref|
|
44
|
-
add_token(var_ref.value.location, :variable)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def visit_var_field(node)
|
49
|
-
case node.value
|
50
|
-
when SyntaxTree::Ident
|
51
|
-
add_token(node.value.location, :variable)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def visit_var_ref(node)
|
56
|
-
case node.value
|
57
|
-
when SyntaxTree::Ident
|
58
|
-
add_token(node.value.location, :variable)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
100
|
+
sig { params(node: SyntaxTree::ARefField).void }
|
62
101
|
def visit_a_ref_field(node)
|
63
102
|
add_token(node.collection.value.location, :variable)
|
64
103
|
end
|
65
104
|
|
105
|
+
sig { params(node: SyntaxTree::Call).void }
|
66
106
|
def visit_call(node)
|
67
107
|
visit(node.receiver)
|
68
108
|
add_token(node.message.location, :method)
|
69
109
|
visit(node.arguments)
|
70
110
|
end
|
71
111
|
|
112
|
+
sig { params(node: SyntaxTree::Command).void }
|
72
113
|
def visit_command(node)
|
73
|
-
add_token(node.message.location, :method)
|
114
|
+
add_token(node.message.location, :method) unless special_method?(node.message.value)
|
74
115
|
visit(node.arguments)
|
75
116
|
end
|
76
117
|
|
118
|
+
sig { params(node: SyntaxTree::CommandCall).void }
|
77
119
|
def visit_command_call(node)
|
78
120
|
visit(node.receiver)
|
79
121
|
add_token(node.message.location, :method)
|
80
122
|
visit(node.arguments)
|
81
123
|
end
|
82
124
|
|
125
|
+
sig { params(node: SyntaxTree::Const).void }
|
126
|
+
def visit_const(node)
|
127
|
+
add_token(node.location, :namespace)
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { params(node: SyntaxTree::Def).void }
|
131
|
+
def visit_def(node)
|
132
|
+
add_token(node.name.location, :method, [:declaration])
|
133
|
+
visit(node.params)
|
134
|
+
visit(node.bodystmt)
|
135
|
+
end
|
136
|
+
|
137
|
+
sig { params(node: SyntaxTree::DefEndless).void }
|
138
|
+
def visit_def_endless(node)
|
139
|
+
add_token(node.name.location, :method, [:declaration])
|
140
|
+
visit(node.paren)
|
141
|
+
visit(node.operator)
|
142
|
+
visit(node.statement)
|
143
|
+
end
|
144
|
+
|
145
|
+
sig { params(node: SyntaxTree::Defs).void }
|
146
|
+
def visit_defs(node)
|
147
|
+
visit(node.target)
|
148
|
+
visit(node.operator)
|
149
|
+
add_token(node.name.location, :method, [:declaration])
|
150
|
+
visit(node.params)
|
151
|
+
visit(node.bodystmt)
|
152
|
+
end
|
153
|
+
|
154
|
+
sig { params(node: SyntaxTree::FCall).void }
|
83
155
|
def visit_fcall(node)
|
84
|
-
add_token(node.value.location, :method)
|
156
|
+
add_token(node.value.location, :method) unless special_method?(node.value.value)
|
85
157
|
visit(node.arguments)
|
86
158
|
end
|
87
159
|
|
160
|
+
sig { params(node: SyntaxTree::Kw).void }
|
161
|
+
def visit_kw(node)
|
162
|
+
case node.value
|
163
|
+
when "self"
|
164
|
+
add_token(node.location, :variable, [:default_library])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
sig { params(node: SyntaxTree::MAssign).void }
|
169
|
+
def visit_m_assign(node)
|
170
|
+
node.target.parts.each do |var_ref|
|
171
|
+
add_token(var_ref.value.location, :variable)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
sig { params(node: SyntaxTree::Params).void }
|
176
|
+
def visit_params(node)
|
177
|
+
node.keywords.each do |keyword,|
|
178
|
+
location = keyword.location
|
179
|
+
add_token(location_without_colon(location), :variable)
|
180
|
+
end
|
181
|
+
|
182
|
+
add_token(node.keyword_rest.name.location, :variable) if node.keyword_rest
|
183
|
+
end
|
184
|
+
|
185
|
+
sig { params(node: SyntaxTree::VarField).void }
|
186
|
+
def visit_var_field(node)
|
187
|
+
case node.value
|
188
|
+
when SyntaxTree::Ident
|
189
|
+
add_token(node.value.location, :variable)
|
190
|
+
else
|
191
|
+
visit(node.value)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
sig { params(node: SyntaxTree::VarRef).void }
|
196
|
+
def visit_var_ref(node)
|
197
|
+
case node.value
|
198
|
+
when SyntaxTree::Ident
|
199
|
+
add_token(node.value.location, :variable)
|
200
|
+
else
|
201
|
+
visit(node.value)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
sig { params(node: SyntaxTree::VCall).void }
|
88
206
|
def visit_vcall(node)
|
89
|
-
add_token(node.value.location, :method)
|
207
|
+
add_token(node.value.location, :method) unless special_method?(node.value.value)
|
90
208
|
end
|
91
209
|
|
92
|
-
|
210
|
+
sig { params(location: SyntaxTree::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
|
211
|
+
def add_token(location, type, modifiers = [])
|
93
212
|
length = location.end_char - location.start_char
|
94
|
-
|
213
|
+
modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
|
214
|
+
@tokens.push(
|
215
|
+
SemanticToken.new(
|
216
|
+
location: location,
|
217
|
+
length: length,
|
218
|
+
type: T.must(TOKEN_TYPES[type]),
|
219
|
+
modifier: modifiers_indices
|
220
|
+
)
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
# Exclude the ":" symbol at the end of a location
|
227
|
+
# We use it on keyword parameters to be consistent
|
228
|
+
# with the rest of the parameters
|
229
|
+
sig { params(location: T.untyped).returns(SyntaxTree::Location) }
|
230
|
+
def location_without_colon(location)
|
231
|
+
SyntaxTree::Location.new(
|
232
|
+
start_line: location.start_line,
|
233
|
+
start_column: location.start_column,
|
234
|
+
start_char: location.start_char,
|
235
|
+
end_char: location.end_char - 1,
|
236
|
+
end_column: location.end_column - 1,
|
237
|
+
end_line: location.end_line,
|
238
|
+
)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Textmate provides highlighting for a subset
|
242
|
+
# of these special Ruby-specific methods.
|
243
|
+
# We want to utilize that highlighting, so we
|
244
|
+
# avoid making a semantic token for it.
|
245
|
+
sig { params(method_name: String).returns(T::Boolean) }
|
246
|
+
def special_method?(method_name)
|
247
|
+
SPECIAL_RUBY_METHODS.include?(method_name)
|
95
248
|
end
|
96
249
|
end
|
97
250
|
end
|
@@ -1,34 +1,45 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
4
5
|
module Requests
|
5
6
|
module Support
|
6
7
|
class RuboCopDiagnostic
|
7
|
-
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
RUBOCOP_TO_LSP_SEVERITY = T.let({
|
8
11
|
convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
9
12
|
info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
10
13
|
refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
11
14
|
warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
|
12
15
|
error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
13
16
|
fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
14
|
-
}.freeze
|
17
|
+
}.freeze, T::Hash[Symbol, Integer])
|
15
18
|
|
19
|
+
sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
16
20
|
attr_reader :replacements
|
17
21
|
|
22
|
+
sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
|
18
23
|
def initialize(offense, uri)
|
19
24
|
@offense = offense
|
20
25
|
@uri = uri
|
21
|
-
@replacements =
|
26
|
+
@replacements = T.let(
|
27
|
+
offense.correctable? ? offense_replacements : [],
|
28
|
+
T::Array[LanguageServer::Protocol::Interface::TextEdit]
|
29
|
+
)
|
22
30
|
end
|
23
31
|
|
32
|
+
sig { returns(T::Boolean) }
|
24
33
|
def correctable?
|
25
34
|
@offense.correctable?
|
26
35
|
end
|
27
36
|
|
37
|
+
sig { params(range: T::Range[Integer]).returns(T::Boolean) }
|
28
38
|
def in_range?(range)
|
29
39
|
range.cover?(@offense.line - 1)
|
30
40
|
end
|
31
41
|
|
42
|
+
sig { returns(LanguageServer::Protocol::Interface::CodeAction) }
|
32
43
|
def to_lsp_code_action
|
33
44
|
LanguageServer::Protocol::Interface::CodeAction.new(
|
34
45
|
title: "Autocorrect #{@offense.cop_name}",
|
@@ -48,6 +59,7 @@ module RubyLsp
|
|
48
59
|
)
|
49
60
|
end
|
50
61
|
|
62
|
+
sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
|
51
63
|
def to_lsp_diagnostic
|
52
64
|
LanguageServer::Protocol::Interface::Diagnostic.new(
|
53
65
|
message: @offense.message,
|
@@ -69,6 +81,7 @@ module RubyLsp
|
|
69
81
|
|
70
82
|
private
|
71
83
|
|
84
|
+
sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
72
85
|
def offense_replacements
|
73
86
|
@offense.corrector.as_replacements.map do |range, replacement|
|
74
87
|
LanguageServer::Protocol::Interface::TextEdit.new(
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "rubocop"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
require "cgi"
|
11
|
+
require "singleton"
|
12
|
+
|
13
|
+
module RubyLsp
|
14
|
+
module Requests
|
15
|
+
module Support
|
16
|
+
# :nodoc:
|
17
|
+
class RuboCopDiagnosticsRunner < RuboCop::Runner
|
18
|
+
extend T::Sig
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
sig { void }
|
22
|
+
def initialize
|
23
|
+
@options = T.let({}, T::Hash[Symbol, T.untyped])
|
24
|
+
@uri = T.let(nil, T.nilable(String))
|
25
|
+
@diagnostics = T.let([], T::Array[Support::RuboCopDiagnostic])
|
26
|
+
|
27
|
+
super(
|
28
|
+
::RuboCop::Options.new.parse([
|
29
|
+
"--stderr", # Print any output to stderr so that our stdout does not get polluted
|
30
|
+
"--force-exclusion",
|
31
|
+
"--format",
|
32
|
+
"RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
|
33
|
+
]).first,
|
34
|
+
::RuboCop::ConfigStore.new
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { params(uri: String, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
|
39
|
+
def run(uri, document)
|
40
|
+
@diagnostics.clear
|
41
|
+
@uri = uri
|
42
|
+
|
43
|
+
file = CGI.unescape(URI.parse(uri).path)
|
44
|
+
# We communicate with Rubocop via stdin
|
45
|
+
@options[:stdin] = document.source
|
46
|
+
|
47
|
+
# Invoke RuboCop with just this file in `paths`
|
48
|
+
super([file])
|
49
|
+
@diagnostics
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
|
55
|
+
def file_finished(_file, offenses)
|
56
|
+
@diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, T.must(@uri)) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "rubocop"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
require "cgi"
|
11
|
+
require "singleton"
|
12
|
+
|
13
|
+
module RubyLsp
|
14
|
+
module Requests
|
15
|
+
module Support
|
16
|
+
# :nodoc:
|
17
|
+
class RuboCopFormattingRunner < RuboCop::Runner
|
18
|
+
extend T::Sig
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
sig { void }
|
22
|
+
def initialize
|
23
|
+
@options = T.let({}, T::Hash[Symbol, T.untyped])
|
24
|
+
|
25
|
+
super(
|
26
|
+
::RuboCop::Options.new.parse([
|
27
|
+
"--stderr", # Print any output to stderr so that our stdout does not get polluted
|
28
|
+
"--force-exclusion",
|
29
|
+
"--format",
|
30
|
+
"RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
|
31
|
+
"-a", # --auto-correct
|
32
|
+
]).first,
|
33
|
+
::RuboCop::ConfigStore.new
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { params(uri: String, document: Document).returns(T.nilable(String)) }
|
38
|
+
def run(uri, document)
|
39
|
+
file = CGI.unescape(URI.parse(uri).path)
|
40
|
+
# We communicate with Rubocop via stdin
|
41
|
+
@options[:stdin] = document.source
|
42
|
+
|
43
|
+
# Invoke RuboCop with just this file in `paths`
|
44
|
+
super([file])
|
45
|
+
@options[:stdin]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
4
5
|
module Requests
|
5
6
|
module Support
|
6
7
|
class SelectionRange < LanguageServer::Protocol::Interface::SelectionRange
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(position: Document::PositionShape).returns(T::Boolean) }
|
7
11
|
def cover?(position)
|
8
12
|
line_range = (range.start.line..range.end.line)
|
9
13
|
character_range = (range.start.character..range.end.character)
|
@@ -1,14 +1,23 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
4
5
|
module Requests
|
5
6
|
module Support
|
6
7
|
class SemanticTokenEncoder
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { void }
|
7
11
|
def initialize
|
8
|
-
@current_row = 0
|
9
|
-
@current_column = 0
|
12
|
+
@current_row = T.let(0, Integer)
|
13
|
+
@current_column = T.let(0, Integer)
|
10
14
|
end
|
11
15
|
|
16
|
+
sig do
|
17
|
+
params(
|
18
|
+
tokens: T::Array[SemanticHighlighting::SemanticToken]
|
19
|
+
).returns(LanguageServer::Protocol::Interface::SemanticTokens)
|
20
|
+
end
|
12
21
|
def encode(tokens)
|
13
22
|
delta = tokens
|
14
23
|
.sort_by do |token|
|
@@ -30,6 +39,7 @@ module RubyLsp
|
|
30
39
|
|
31
40
|
# For more information on how each number is calculated, read:
|
32
41
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
42
|
+
sig { params(token: SemanticHighlighting::SemanticToken).returns(T::Array[Integer]) }
|
33
43
|
def compute_delta(token)
|
34
44
|
row = token.location.start_line - 1
|
35
45
|
column = token.location.start_column
|
@@ -38,11 +48,22 @@ module RubyLsp
|
|
38
48
|
delta_column = column
|
39
49
|
delta_column -= @current_column if delta_line == 0
|
40
50
|
|
41
|
-
[delta_line, delta_column, token.length, token.type, token.modifier]
|
51
|
+
[delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
|
42
52
|
ensure
|
43
53
|
@current_row = row
|
44
54
|
@current_column = column
|
45
55
|
end
|
56
|
+
|
57
|
+
# Encode an array of modifiers to positions onto a bit flag
|
58
|
+
# For example, [:default_library] will be encoded as
|
59
|
+
# 0b1000000000, as :default_library is the 10th bit according
|
60
|
+
# to the token modifiers index map.
|
61
|
+
sig { params(modifiers: T::Array[Integer]).returns(Integer) }
|
62
|
+
def encode_modifiers(modifiers)
|
63
|
+
modifiers.inject(0) do |encoded_modifiers, modifier|
|
64
|
+
encoded_modifiers | (1 << modifier)
|
65
|
+
end
|
66
|
+
end
|
46
67
|
end
|
47
68
|
end
|
48
69
|
end
|
@@ -1,17 +1,23 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
4
5
|
module Requests
|
5
6
|
module Support
|
6
7
|
class SyntaxErrorDiagnostic
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(edit: Document::EditShape).void }
|
7
11
|
def initialize(edit)
|
8
12
|
@edit = edit
|
9
13
|
end
|
10
14
|
|
15
|
+
sig { returns(FalseClass) }
|
11
16
|
def correctable?
|
12
17
|
false
|
13
18
|
end
|
14
19
|
|
20
|
+
sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
|
15
21
|
def to_lsp_diagnostic
|
16
22
|
LanguageServer::Protocol::Interface::Diagnostic.new(
|
17
23
|
message: "Syntax error",
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -1,17 +1,29 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module RubyLsp
|
5
|
+
# Supported features
|
6
|
+
#
|
7
|
+
# - {RubyLsp::Requests::DocumentSymbol}
|
8
|
+
# - {RubyLsp::Requests::FoldingRanges}
|
9
|
+
# - {RubyLsp::Requests::SelectionRanges}
|
10
|
+
# - {RubyLsp::Requests::SemanticHighlighting}
|
11
|
+
# - {RubyLsp::Requests::Formatting}
|
12
|
+
# - {RubyLsp::Requests::Diagnostics}
|
13
|
+
# - {RubyLsp::Requests::CodeActions}
|
14
|
+
# - {RubyLsp::Requests::DocumentHighlight}
|
4
15
|
module Requests
|
5
16
|
autoload :BaseRequest, "ruby_lsp/requests/base_request"
|
6
17
|
autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
|
7
18
|
autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
|
8
19
|
autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
|
9
20
|
autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
|
10
|
-
autoload :RuboCopRequest, "ruby_lsp/requests/rubocop_request"
|
11
21
|
autoload :Formatting, "ruby_lsp/requests/formatting"
|
12
22
|
autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
|
13
23
|
autoload :CodeActions, "ruby_lsp/requests/code_actions"
|
24
|
+
autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
|
14
25
|
|
26
|
+
# :nodoc:
|
15
27
|
module Support
|
16
28
|
autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
|
17
29
|
autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require "cgi"
|
@@ -6,36 +7,52 @@ require "ruby_lsp/document"
|
|
6
7
|
|
7
8
|
module RubyLsp
|
8
9
|
class Store
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { void }
|
9
13
|
def initialize
|
10
|
-
@state = {}
|
14
|
+
@state = T.let({}, T::Hash[String, Document])
|
11
15
|
end
|
12
16
|
|
17
|
+
sig { params(uri: String).returns(Document) }
|
13
18
|
def get(uri)
|
14
19
|
document = @state[uri]
|
15
20
|
return document unless document.nil?
|
16
21
|
|
17
22
|
set(uri, File.binread(CGI.unescape(URI.parse(uri).path)))
|
18
|
-
@state[uri]
|
23
|
+
T.must(@state[uri])
|
19
24
|
end
|
20
25
|
|
26
|
+
sig { params(uri: String, content: String).void }
|
21
27
|
def set(uri, content)
|
22
28
|
@state[uri] = Document.new(content)
|
23
29
|
rescue SyntaxTree::Parser::ParseError
|
24
30
|
# Do not update the store if there are syntax errors
|
25
31
|
end
|
26
32
|
|
33
|
+
sig { params(uri: String, edits: T::Array[Document::EditShape]).void }
|
27
34
|
def push_edits(uri, edits)
|
28
|
-
@state[uri].push_edits(edits)
|
35
|
+
T.must(@state[uri]).push_edits(edits)
|
29
36
|
end
|
30
37
|
|
38
|
+
sig { void }
|
31
39
|
def clear
|
32
40
|
@state.clear
|
33
41
|
end
|
34
42
|
|
43
|
+
sig { params(uri: String).void }
|
35
44
|
def delete(uri)
|
36
45
|
@state.delete(uri)
|
37
46
|
end
|
38
47
|
|
48
|
+
sig do
|
49
|
+
type_parameters(:T)
|
50
|
+
.params(
|
51
|
+
uri: String,
|
52
|
+
request_name: Symbol,
|
53
|
+
block: T.proc.params(document: Document).returns(T.type_parameter(:T))
|
54
|
+
).returns(T.type_parameter(:T))
|
55
|
+
end
|
39
56
|
def cache_fetch(uri, request_name, &block)
|
40
57
|
get(uri).cache_fetch(request_name, &block)
|
41
58
|
end
|