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
|
@@ -1,116 +1,187 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RubyLsp
|
|
4
5
|
module Requests
|
|
6
|
+
# The [semantic
|
|
7
|
+
# highlighting](https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens)
|
|
8
|
+
# request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.
|
|
9
|
+
#
|
|
10
|
+
# # Example
|
|
11
|
+
#
|
|
12
|
+
# ```ruby
|
|
13
|
+
# def foo
|
|
14
|
+
# var = 1 # --> semantic highlighting: local variable
|
|
15
|
+
# some_invocation # --> semantic highlighting: method invocation
|
|
16
|
+
# var # --> semantic highlighting: local variable
|
|
17
|
+
# end
|
|
18
|
+
# ```
|
|
5
19
|
class SemanticHighlighting < BaseRequest
|
|
6
|
-
|
|
20
|
+
extend T::Sig
|
|
21
|
+
|
|
22
|
+
TOKEN_TYPES = T.let([
|
|
7
23
|
:variable,
|
|
8
24
|
:method,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
:namespace,
|
|
26
|
+
].freeze, T::Array[Symbol])
|
|
27
|
+
|
|
28
|
+
TOKEN_MODIFIERS = T.let({
|
|
29
|
+
declaration: 0,
|
|
30
|
+
definition: 1,
|
|
31
|
+
readonly: 2,
|
|
32
|
+
static: 3,
|
|
33
|
+
deprecated: 4,
|
|
34
|
+
abstract: 5,
|
|
35
|
+
async: 6,
|
|
36
|
+
modification: 7,
|
|
37
|
+
documentation: 8,
|
|
38
|
+
default_library: 9,
|
|
39
|
+
}.freeze, T::Hash[Symbol, Integer])
|
|
40
|
+
|
|
41
|
+
class SemanticToken < T::Struct
|
|
42
|
+
const :location, SyntaxTree::Location
|
|
43
|
+
const :length, Integer
|
|
44
|
+
const :type, Integer
|
|
45
|
+
const :modifier, T::Array[Integer]
|
|
19
46
|
end
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
48
|
+
sig { params(document: Document, encoder: T.nilable(Support::SemanticTokenEncoder)).void }
|
|
49
|
+
def initialize(document, encoder: nil)
|
|
50
|
+
super(document)
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
52
|
+
@encoder = encoder
|
|
53
|
+
@tokens = T.let([], T::Array[SemanticToken])
|
|
54
|
+
@tree = T.let(document.tree, SyntaxTree::Node)
|
|
30
55
|
end
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
sig do
|
|
58
|
+
override.returns(
|
|
59
|
+
T.any(
|
|
60
|
+
LanguageServer::Protocol::Interface::SemanticTokens,
|
|
61
|
+
T.all(T::Array[SemanticToken], Object),
|
|
62
|
+
)
|
|
63
|
+
)
|
|
37
64
|
end
|
|
65
|
+
def run
|
|
66
|
+
visit(@tree)
|
|
67
|
+
return @tokens unless @encoder
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
case node.value
|
|
41
|
-
when SyntaxTree::Ident
|
|
42
|
-
add_token(node.value.location, :variable)
|
|
43
|
-
end
|
|
69
|
+
@encoder.encode(@tokens)
|
|
44
70
|
end
|
|
45
71
|
|
|
72
|
+
sig { params(node: SyntaxTree::ARefField).void }
|
|
46
73
|
def visit_a_ref_field(node)
|
|
47
74
|
add_token(node.collection.value.location, :variable)
|
|
48
75
|
end
|
|
49
76
|
|
|
77
|
+
sig { params(node: SyntaxTree::Call).void }
|
|
50
78
|
def visit_call(node)
|
|
51
79
|
visit(node.receiver)
|
|
52
80
|
add_token(node.message.location, :method)
|
|
53
81
|
visit(node.arguments)
|
|
54
82
|
end
|
|
55
83
|
|
|
84
|
+
sig { params(node: SyntaxTree::Command).void }
|
|
56
85
|
def visit_command(node)
|
|
57
86
|
add_token(node.message.location, :method)
|
|
58
87
|
visit(node.arguments)
|
|
59
88
|
end
|
|
60
89
|
|
|
90
|
+
sig { params(node: SyntaxTree::CommandCall).void }
|
|
61
91
|
def visit_command_call(node)
|
|
62
92
|
visit(node.receiver)
|
|
63
93
|
add_token(node.message.location, :method)
|
|
64
94
|
visit(node.arguments)
|
|
65
95
|
end
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
sig { params(node: SyntaxTree::Const).void }
|
|
98
|
+
def visit_const(node)
|
|
99
|
+
add_token(node.location, :namespace)
|
|
70
100
|
end
|
|
71
101
|
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
sig { params(node: SyntaxTree::Def).void }
|
|
103
|
+
def visit_def(node)
|
|
104
|
+
add_token(node.name.location, :method, [:declaration])
|
|
105
|
+
visit(node.params)
|
|
106
|
+
visit(node.bodystmt)
|
|
74
107
|
end
|
|
75
108
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
sig { params(node: SyntaxTree::DefEndless).void }
|
|
110
|
+
def visit_def_endless(node)
|
|
111
|
+
add_token(node.name.location, :method, [:declaration])
|
|
112
|
+
visit(node.paren)
|
|
113
|
+
visit(node.operator)
|
|
114
|
+
visit(node.statement)
|
|
82
115
|
end
|
|
83
116
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
sig { params(node: SyntaxTree::Defs).void }
|
|
118
|
+
def visit_defs(node)
|
|
119
|
+
visit(node.target)
|
|
120
|
+
visit(node.operator)
|
|
121
|
+
add_token(node.name.location, :method, [:declaration])
|
|
122
|
+
visit(node.params)
|
|
123
|
+
visit(node.bodystmt)
|
|
124
|
+
end
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
sig { params(node: SyntaxTree::FCall).void }
|
|
127
|
+
def visit_fcall(node)
|
|
128
|
+
add_token(node.value.location, :method)
|
|
129
|
+
visit(node.arguments)
|
|
130
|
+
end
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
sig { params(node: SyntaxTree::Kw).void }
|
|
133
|
+
def visit_kw(node)
|
|
134
|
+
case node.value
|
|
135
|
+
when "self"
|
|
136
|
+
add_token(node.location, :variable, [:default_library])
|
|
100
137
|
end
|
|
138
|
+
end
|
|
101
139
|
|
|
102
|
-
|
|
140
|
+
sig { params(node: SyntaxTree::MAssign).void }
|
|
141
|
+
def visit_m_assign(node)
|
|
142
|
+
node.target.parts.each do |var_ref|
|
|
143
|
+
add_token(var_ref.value.location, :variable)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
103
146
|
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
sig { params(node: SyntaxTree::VarField).void }
|
|
148
|
+
def visit_var_field(node)
|
|
149
|
+
case node.value
|
|
150
|
+
when SyntaxTree::Ident
|
|
151
|
+
add_token(node.value.location, :variable)
|
|
152
|
+
else
|
|
153
|
+
visit(node.value)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
106
156
|
|
|
107
|
-
|
|
157
|
+
sig { params(node: SyntaxTree::VarRef).void }
|
|
158
|
+
def visit_var_ref(node)
|
|
159
|
+
case node.value
|
|
160
|
+
when SyntaxTree::Ident
|
|
161
|
+
add_token(node.value.location, :variable)
|
|
162
|
+
else
|
|
163
|
+
visit(node.value)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
108
166
|
|
|
109
|
-
|
|
110
|
-
|
|
167
|
+
sig { params(node: SyntaxTree::VCall).void }
|
|
168
|
+
def visit_vcall(node)
|
|
169
|
+
add_token(node.value.location, :method)
|
|
111
170
|
end
|
|
112
171
|
|
|
113
|
-
|
|
172
|
+
sig { params(location: SyntaxTree::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
|
|
173
|
+
def add_token(location, type, modifiers = [])
|
|
174
|
+
length = location.end_char - location.start_char
|
|
175
|
+
modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
|
|
176
|
+
@tokens.push(
|
|
177
|
+
SemanticToken.new(
|
|
178
|
+
location: location,
|
|
179
|
+
length: length,
|
|
180
|
+
type: T.must(TOKEN_TYPES.index(type)),
|
|
181
|
+
modifier: modifiers_indices
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
end
|
|
114
185
|
end
|
|
115
186
|
end
|
|
116
187
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
module Support
|
|
7
|
+
class RuboCopDiagnostic
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
RUBOCOP_TO_LSP_SEVERITY = T.let({
|
|
11
|
+
convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
12
|
+
info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
13
|
+
refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
|
14
|
+
warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
|
|
15
|
+
error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
|
16
|
+
fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
|
17
|
+
}.freeze, T::Hash[Symbol, Integer])
|
|
18
|
+
|
|
19
|
+
sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
|
20
|
+
attr_reader :replacements
|
|
21
|
+
|
|
22
|
+
sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
|
|
23
|
+
def initialize(offense, uri)
|
|
24
|
+
@offense = offense
|
|
25
|
+
@uri = uri
|
|
26
|
+
@replacements = T.let(
|
|
27
|
+
offense.correctable? ? offense_replacements : [],
|
|
28
|
+
T::Array[LanguageServer::Protocol::Interface::TextEdit]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { returns(T::Boolean) }
|
|
33
|
+
def correctable?
|
|
34
|
+
@offense.correctable?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { params(range: T::Range[Integer]).returns(T::Boolean) }
|
|
38
|
+
def in_range?(range)
|
|
39
|
+
range.cover?(@offense.line - 1)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { returns(LanguageServer::Protocol::Interface::CodeAction) }
|
|
43
|
+
def to_lsp_code_action
|
|
44
|
+
LanguageServer::Protocol::Interface::CodeAction.new(
|
|
45
|
+
title: "Autocorrect #{@offense.cop_name}",
|
|
46
|
+
kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
|
|
47
|
+
edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
|
|
48
|
+
document_changes: [
|
|
49
|
+
LanguageServer::Protocol::Interface::TextDocumentEdit.new(
|
|
50
|
+
text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
|
|
51
|
+
uri: @uri,
|
|
52
|
+
version: nil
|
|
53
|
+
),
|
|
54
|
+
edits: @replacements
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
),
|
|
58
|
+
is_preferred: true,
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
|
|
63
|
+
def to_lsp_diagnostic
|
|
64
|
+
LanguageServer::Protocol::Interface::Diagnostic.new(
|
|
65
|
+
message: @offense.message,
|
|
66
|
+
source: "RuboCop",
|
|
67
|
+
code: @offense.cop_name,
|
|
68
|
+
severity: RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name],
|
|
69
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
|
70
|
+
start: LanguageServer::Protocol::Interface::Position.new(
|
|
71
|
+
line: @offense.line - 1,
|
|
72
|
+
character: @offense.column
|
|
73
|
+
),
|
|
74
|
+
end: LanguageServer::Protocol::Interface::Position.new(
|
|
75
|
+
line: @offense.last_line - 1,
|
|
76
|
+
character: @offense.last_column
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
|
85
|
+
def offense_replacements
|
|
86
|
+
@offense.corrector.as_replacements.map do |range, replacement|
|
|
87
|
+
LanguageServer::Protocol::Interface::TextEdit.new(
|
|
88
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
|
89
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
|
|
90
|
+
end: LanguageServer::Protocol::Interface::Position.new(line: range.last_line - 1,
|
|
91
|
+
character: range.last_column)
|
|
92
|
+
),
|
|
93
|
+
new_text: replacement
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
module Support
|
|
7
|
+
class SelectionRange < LanguageServer::Protocol::Interface::SelectionRange
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
sig { params(position: Document::PositionShape).returns(T::Boolean) }
|
|
11
|
+
def cover?(position)
|
|
12
|
+
line_range = (range.start.line..range.end.line)
|
|
13
|
+
character_range = (range.start.character..range.end.character)
|
|
14
|
+
|
|
15
|
+
line_range.cover?(position[:line]) && character_range.cover?(position[:character])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
module Support
|
|
7
|
+
class SemanticTokenEncoder
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
sig { void }
|
|
11
|
+
def initialize
|
|
12
|
+
@current_row = T.let(0, Integer)
|
|
13
|
+
@current_column = T.let(0, Integer)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
sig do
|
|
17
|
+
params(
|
|
18
|
+
tokens: T::Array[SemanticHighlighting::SemanticToken]
|
|
19
|
+
).returns(LanguageServer::Protocol::Interface::SemanticTokens)
|
|
20
|
+
end
|
|
21
|
+
def encode(tokens)
|
|
22
|
+
delta = tokens
|
|
23
|
+
.sort_by do |token|
|
|
24
|
+
[token.location.start_line, token.location.start_column]
|
|
25
|
+
end
|
|
26
|
+
.flat_map do |token|
|
|
27
|
+
compute_delta(token)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
LanguageServer::Protocol::Interface::SemanticTokens.new(data: delta)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The delta array is computed according to the LSP specification:
|
|
34
|
+
# > The protocol for the token format relative uses relative
|
|
35
|
+
# > positions, because most tokens remain stable relative to
|
|
36
|
+
# > each other when edits are made in a file. This simplifies
|
|
37
|
+
# > the computation of a delta if a server supports it. So each
|
|
38
|
+
# > token is represented using 5 integers.
|
|
39
|
+
|
|
40
|
+
# For more information on how each number is calculated, read:
|
|
41
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
|
42
|
+
sig { params(token: SemanticHighlighting::SemanticToken).returns(T::Array[Integer]) }
|
|
43
|
+
def compute_delta(token)
|
|
44
|
+
row = token.location.start_line - 1
|
|
45
|
+
column = token.location.start_column
|
|
46
|
+
delta_line = row - @current_row
|
|
47
|
+
|
|
48
|
+
delta_column = column
|
|
49
|
+
delta_column -= @current_column if delta_line == 0
|
|
50
|
+
|
|
51
|
+
[delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
|
|
52
|
+
ensure
|
|
53
|
+
@current_row = row
|
|
54
|
+
@current_column = column
|
|
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
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
module Support
|
|
7
|
+
class SyntaxErrorDiagnostic
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
sig { params(edit: Document::EditShape).void }
|
|
11
|
+
def initialize(edit)
|
|
12
|
+
@edit = edit
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
sig { returns(FalseClass) }
|
|
16
|
+
def correctable?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
|
|
21
|
+
def to_lsp_diagnostic
|
|
22
|
+
LanguageServer::Protocol::Interface::Diagnostic.new(
|
|
23
|
+
message: "Syntax error",
|
|
24
|
+
source: "SyntaxTree",
|
|
25
|
+
severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
|
26
|
+
range: @edit[:range]
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module RubyLsp
|
|
@@ -5,10 +6,19 @@ module RubyLsp
|
|
|
5
6
|
autoload :BaseRequest, "ruby_lsp/requests/base_request"
|
|
6
7
|
autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
|
|
7
8
|
autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
|
|
9
|
+
autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
|
|
8
10
|
autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
|
|
9
11
|
autoload :RuboCopRequest, "ruby_lsp/requests/rubocop_request"
|
|
10
12
|
autoload :Formatting, "ruby_lsp/requests/formatting"
|
|
11
13
|
autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
|
|
12
14
|
autoload :CodeActions, "ruby_lsp/requests/code_actions"
|
|
15
|
+
autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
|
|
16
|
+
|
|
17
|
+
module Support
|
|
18
|
+
autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
|
|
19
|
+
autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
|
|
20
|
+
autoload :SemanticTokenEncoder, "ruby_lsp/requests/support/semantic_token_encoder"
|
|
21
|
+
autoload :SyntaxErrorDiagnostic, "ruby_lsp/requests/support/syntax_error_diagnostic"
|
|
22
|
+
end
|
|
13
23
|
end
|
|
14
24
|
end
|
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,32 +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 }
|
|
34
|
+
def push_edits(uri, edits)
|
|
35
|
+
T.must(@state[uri]).push_edits(edits)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { void }
|
|
27
39
|
def clear
|
|
28
40
|
@state.clear
|
|
29
41
|
end
|
|
30
42
|
|
|
43
|
+
sig { params(uri: String).void }
|
|
31
44
|
def delete(uri)
|
|
32
45
|
@state.delete(uri)
|
|
33
46
|
end
|
|
34
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
|
|
35
56
|
def cache_fetch(uri, request_name, &block)
|
|
36
57
|
get(uri).cache_fetch(request_name, &block)
|
|
37
58
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
desc "Check if all LSP requests are documented"
|
|
5
|
+
task :check_docs do
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
require "language_server-protocol"
|
|
8
|
+
require "syntax_tree"
|
|
9
|
+
require "logger"
|
|
10
|
+
require "ruby_lsp/requests/base_request"
|
|
11
|
+
require "ruby_lsp/requests/rubocop_request"
|
|
12
|
+
|
|
13
|
+
Dir["#{Dir.pwd}/lib/ruby_lsp/requests/*.rb"].each do |file|
|
|
14
|
+
require(file)
|
|
15
|
+
YARD.parse(file, [], Logger::Severity::FATAL)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
spec_matcher = %r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}
|
|
19
|
+
error_messages = RubyLsp::Requests
|
|
20
|
+
.constants # rubocop:disable Sorbet/ConstantsFromStrings
|
|
21
|
+
.each_with_object(Hash.new { |h, k| h[k] = [] }) do |request, errors|
|
|
22
|
+
full_name = "RubyLsp::Requests::#{request}"
|
|
23
|
+
docs = YARD::Registry.at(full_name).docstring
|
|
24
|
+
next if /:nodoc:/.match?(docs)
|
|
25
|
+
|
|
26
|
+
if docs.empty?
|
|
27
|
+
errors[full_name] << "Missing documentation for request handler class"
|
|
28
|
+
elsif !spec_matcher.match?(docs)
|
|
29
|
+
errors[full_name] << <<~MESSAGE
|
|
30
|
+
Documentation for request handler classes must link to the official LSP specification.
|
|
31
|
+
|
|
32
|
+
For example, if your request handles text document hover, you should add a link to
|
|
33
|
+
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover.
|
|
34
|
+
MESSAGE
|
|
35
|
+
elsif !/# Example/.match?(docs)
|
|
36
|
+
errors[full_name] << <<~MESSAGE
|
|
37
|
+
Documentation for request handler class must contain an example.
|
|
38
|
+
|
|
39
|
+
= Example
|
|
40
|
+
def my_method # <-- something happens here
|
|
41
|
+
end
|
|
42
|
+
MESSAGE
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
formatted_errors = error_messages.map { |name, errors| "#{name}: #{errors.join(", ")}" }
|
|
47
|
+
|
|
48
|
+
if error_messages.any?
|
|
49
|
+
puts <<~MESSAGE
|
|
50
|
+
The following requests have invalid documentation:
|
|
51
|
+
|
|
52
|
+
#{formatted_errors.join("\n")}
|
|
53
|
+
MESSAGE
|
|
54
|
+
|
|
55
|
+
exit!
|
|
56
|
+
end
|
|
57
|
+
end
|
data/ruby-lsp.gemspec
CHANGED
data/sorbet/config
ADDED