ruby-lsp 0.0.1 → 0.0.4
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/dependabot.yml +7 -16
- data/.github/pull_request_template.md +15 -0
- data/.github/workflows/ci.yml +31 -0
- data/.github/workflows/publish_docs.yml +32 -0
- data/.gitignore +9 -12
- data/.rubocop.yml +20 -2
- data/.vscode/settings.json +5 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +8 -4
- data/Gemfile.lock +76 -14
- data/README.md +69 -2
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/bin/tapioca +29 -0
- data/bin/test +7 -1
- data/dev.yml +7 -7
- data/exe/ruby-lsp +19 -2
- data/lib/internal.rb +7 -0
- data/lib/ruby-lsp.rb +4 -1
- data/lib/ruby_lsp/cli.rb +88 -0
- data/lib/ruby_lsp/document.rb +113 -0
- data/lib/ruby_lsp/handler.rb +236 -0
- data/lib/ruby_lsp/requests/base_request.rb +33 -0
- data/lib/ruby_lsp/requests/code_actions.rb +37 -0
- data/lib/ruby_lsp/requests/diagnostics.rb +37 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +96 -0
- data/lib/ruby_lsp/requests/document_symbol.rb +216 -0
- data/lib/ruby_lsp/requests/folding_ranges.rb +213 -0
- data/lib/ruby_lsp/requests/formatting.rb +52 -0
- data/lib/ruby_lsp/requests/rubocop_request.rb +50 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +103 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +112 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +88 -0
- data/lib/ruby_lsp/requests/support/selection_range.rb +17 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +60 -0
- data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +27 -0
- data/lib/ruby_lsp/requests.rb +24 -0
- data/lib/ruby_lsp/store.rb +59 -0
- data/rakelib/check_docs.rake +56 -0
- data/ruby-lsp.gemspec +5 -1
- data/{shipit.yml → shipit.production.yml} +0 -0
- 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 +119 -9
- data/.vscode/launch.json +0 -19
- data/bin/package_extension +0 -5
- data/bin/style +0 -10
- data/lib/ruby/lsp/cli.rb +0 -37
- data/lib/ruby/lsp.rb +0 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
class Document
|
|
6
|
+
extend T::Sig
|
|
7
|
+
|
|
8
|
+
PositionShape = T.type_alias { { line: Integer, character: Integer } }
|
|
9
|
+
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
|
10
|
+
EditShape = T.type_alias { { range: RangeShape, text: String } }
|
|
11
|
+
|
|
12
|
+
sig { returns(SyntaxTree::Node) }
|
|
13
|
+
attr_reader :tree
|
|
14
|
+
|
|
15
|
+
sig { returns(String) }
|
|
16
|
+
attr_reader :source
|
|
17
|
+
|
|
18
|
+
sig { returns(T::Array[EditShape]) }
|
|
19
|
+
attr_reader :syntax_error_edits
|
|
20
|
+
|
|
21
|
+
sig { params(source: String).void }
|
|
22
|
+
def initialize(source)
|
|
23
|
+
@tree = T.let(SyntaxTree.parse(source), SyntaxTree::Node)
|
|
24
|
+
@cache = T.let({}, T::Hash[Symbol, T.untyped])
|
|
25
|
+
@syntax_error_edits = T.let([], T::Array[EditShape])
|
|
26
|
+
@source = source
|
|
27
|
+
@parsable_source = T.let(source.dup, String)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { params(other: Document).returns(T::Boolean) }
|
|
31
|
+
def ==(other)
|
|
32
|
+
@source == other.source
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { params(request_name: Symbol, block: T.proc.params(document: Document).returns(T.untyped)).returns(T.untyped) }
|
|
36
|
+
def cache_fetch(request_name, &block)
|
|
37
|
+
cached = @cache[request_name]
|
|
38
|
+
return cached if cached
|
|
39
|
+
|
|
40
|
+
result = block.call(self)
|
|
41
|
+
@cache[request_name] = result
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sig { params(edits: T::Array[EditShape]).void }
|
|
46
|
+
def push_edits(edits)
|
|
47
|
+
# Apply the edits on the real source
|
|
48
|
+
edits.each { |edit| apply_edit(@source, edit[:range], edit[:text]) }
|
|
49
|
+
|
|
50
|
+
@cache.clear
|
|
51
|
+
@tree = SyntaxTree.parse(@source)
|
|
52
|
+
@syntax_error_edits.clear
|
|
53
|
+
@parsable_source = @source.dup
|
|
54
|
+
nil
|
|
55
|
+
rescue SyntaxTree::Parser::ParseError
|
|
56
|
+
update_parsable_source(edits)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { returns(T::Boolean) }
|
|
60
|
+
def syntax_errors?
|
|
61
|
+
@syntax_error_edits.any?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
sig { params(edits: T::Array[EditShape]).void }
|
|
67
|
+
def update_parsable_source(edits)
|
|
68
|
+
# If the new edits caused a syntax error, make all edits blank spaces and line breaks to adjust the line and
|
|
69
|
+
# column numbers. This is attempt to make the document parsable while partial edits are being applied
|
|
70
|
+
edits.each do |edit|
|
|
71
|
+
@syntax_error_edits << edit
|
|
72
|
+
next if edit[:text].empty? # skip deletions, since they may have caused the syntax error
|
|
73
|
+
|
|
74
|
+
apply_edit(@parsable_source, edit[:range], edit[:text].gsub(/[^\r\n]/, " "))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@tree = SyntaxTree.parse(@parsable_source)
|
|
78
|
+
rescue SyntaxTree::Parser::ParseError
|
|
79
|
+
# If we can't parse the source even after emptying the edits, then just fallback to the previous source
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sig { params(source: String, range: RangeShape, text: String).void }
|
|
83
|
+
def apply_edit(source, range, text)
|
|
84
|
+
scanner = Scanner.new(source)
|
|
85
|
+
start_position = scanner.find_position(range[:start])
|
|
86
|
+
end_position = scanner.find_position(range[:end])
|
|
87
|
+
|
|
88
|
+
source[start_position...end_position] = text
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class Scanner
|
|
92
|
+
extend T::Sig
|
|
93
|
+
|
|
94
|
+
sig { params(source: String).void }
|
|
95
|
+
def initialize(source)
|
|
96
|
+
@current_line = T.let(0, Integer)
|
|
97
|
+
@pos = T.let(0, Integer)
|
|
98
|
+
@source = source
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
sig { params(position: PositionShape).returns(Integer) }
|
|
102
|
+
def find_position(position)
|
|
103
|
+
until @current_line == position[:line]
|
|
104
|
+
@pos += 1 until /\R/.match?(@source[@pos])
|
|
105
|
+
@pos += 1
|
|
106
|
+
@current_line += 1
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@pos + position[:character]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "ruby_lsp/requests"
|
|
5
|
+
require "ruby_lsp/store"
|
|
6
|
+
require "benchmark"
|
|
7
|
+
|
|
8
|
+
module RubyLsp
|
|
9
|
+
class Handler
|
|
10
|
+
extend T::Sig
|
|
11
|
+
VOID = T.let(Object.new.freeze, Object)
|
|
12
|
+
|
|
13
|
+
sig { returns(Store) }
|
|
14
|
+
attr_reader :store
|
|
15
|
+
|
|
16
|
+
Interface = LanguageServer::Protocol::Interface
|
|
17
|
+
Constant = LanguageServer::Protocol::Constant
|
|
18
|
+
Transport = LanguageServer::Protocol::Transport
|
|
19
|
+
|
|
20
|
+
sig { void }
|
|
21
|
+
def initialize
|
|
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)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { void }
|
|
29
|
+
def start
|
|
30
|
+
$stderr.puts "Starting Ruby LSP..."
|
|
31
|
+
@reader.read do |request|
|
|
32
|
+
with_telemetry(request) { handle(request) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { params(blk: T.proc.bind(Handler).params(arg0: T.untyped).void).void }
|
|
37
|
+
def config(&blk)
|
|
38
|
+
instance_exec(&blk)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
sig do
|
|
44
|
+
params(
|
|
45
|
+
msg: String,
|
|
46
|
+
blk: T.proc.bind(Handler).params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped)
|
|
47
|
+
).void
|
|
48
|
+
end
|
|
49
|
+
def on(msg, &blk)
|
|
50
|
+
@handlers[msg] = blk
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig { params(request: T::Hash[Symbol, T.untyped]).void }
|
|
54
|
+
def handle(request)
|
|
55
|
+
handler = @handlers[request[:method]]
|
|
56
|
+
return unless handler
|
|
57
|
+
|
|
58
|
+
result = handler.call(request)
|
|
59
|
+
@writer.write(id: request[:id], result: result) unless result == VOID
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
sig { void }
|
|
63
|
+
def shutdown
|
|
64
|
+
$stderr.puts "Shutting down Ruby LSP..."
|
|
65
|
+
store.clear
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { params(enabled_features: T::Array[String]).returns(Interface::InitializeResult) }
|
|
69
|
+
def respond_with_capabilities(enabled_features)
|
|
70
|
+
document_symbol_provider = if enabled_features.include?("documentSymbols")
|
|
71
|
+
Interface::DocumentSymbolClientCapabilities.new(
|
|
72
|
+
hierarchical_document_symbol_support: true,
|
|
73
|
+
symbol_kind: {
|
|
74
|
+
value_set: Requests::DocumentSymbol::SYMBOL_KIND.values,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
folding_ranges_provider = if enabled_features.include?("foldingRanges")
|
|
80
|
+
Interface::FoldingRangeClientCapabilities.new(line_folding_only: true)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
semantic_tokens_provider = if enabled_features.include?("semanticHighlighting")
|
|
84
|
+
Interface::SemanticTokensRegistrationOptions.new(
|
|
85
|
+
document_selector: { scheme: "file", language: "ruby" },
|
|
86
|
+
legend: Interface::SemanticTokensLegend.new(
|
|
87
|
+
token_types: Requests::SemanticHighlighting::TOKEN_TYPES,
|
|
88
|
+
token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys
|
|
89
|
+
),
|
|
90
|
+
range: false,
|
|
91
|
+
full: {
|
|
92
|
+
delta: true,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Interface::InitializeResult.new(
|
|
98
|
+
capabilities: Interface::ServerCapabilities.new(
|
|
99
|
+
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
|
100
|
+
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
|
101
|
+
open_close: true,
|
|
102
|
+
),
|
|
103
|
+
selection_range_provider: enabled_features.include?("selectionRanges"),
|
|
104
|
+
document_symbol_provider: document_symbol_provider,
|
|
105
|
+
folding_range_provider: folding_ranges_provider,
|
|
106
|
+
semantic_tokens_provider: semantic_tokens_provider,
|
|
107
|
+
document_formatting_provider: enabled_features.include?("formatting"),
|
|
108
|
+
document_highlight_provider: enabled_features.include?("documentHighlights"),
|
|
109
|
+
code_action_provider: enabled_features.include?("codeActions")
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
|
|
115
|
+
def respond_with_document_symbol(uri)
|
|
116
|
+
store.cache_fetch(uri, :document_symbol) do |document|
|
|
117
|
+
RubyLsp::Requests::DocumentSymbol.run(document)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::FoldingRange]) }
|
|
122
|
+
def respond_with_folding_ranges(uri)
|
|
123
|
+
store.cache_fetch(uri, :folding_ranges) do |document|
|
|
124
|
+
Requests::FoldingRanges.run(document)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sig do
|
|
129
|
+
params(
|
|
130
|
+
uri: String,
|
|
131
|
+
positions: T::Array[Document::PositionShape]
|
|
132
|
+
).returns(T::Array[RubyLsp::Requests::Support::SelectionRange])
|
|
133
|
+
end
|
|
134
|
+
def respond_with_selection_ranges(uri, positions)
|
|
135
|
+
ranges = store.cache_fetch(uri, :selection_ranges) do |document|
|
|
136
|
+
Requests::SelectionRanges.run(document)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
|
|
140
|
+
# every position in the positions array should have an element at the same index in the response
|
|
141
|
+
# array. For positions without a valid selection range, the corresponding element in the response
|
|
142
|
+
# array will be nil.
|
|
143
|
+
positions.map do |position|
|
|
144
|
+
ranges.find do |range|
|
|
145
|
+
range.cover?(position)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
sig { params(uri: String).returns(LanguageServer::Protocol::Interface::SemanticTokens) }
|
|
151
|
+
def respond_with_semantic_highlighting(uri)
|
|
152
|
+
store.cache_fetch(uri, :semantic_highlighting) do |document|
|
|
153
|
+
Requests::SemanticHighlighting.new(document, encoder: Requests::Support::SemanticTokenEncoder.new).run
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
sig { params(uri: String).returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
|
158
|
+
def respond_with_formatting(uri)
|
|
159
|
+
Requests::Formatting.run(uri, store.get(uri))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
sig { params(uri: String).void }
|
|
163
|
+
def send_diagnostics(uri)
|
|
164
|
+
response = store.cache_fetch(uri, :diagnostics) do |document|
|
|
165
|
+
Requests::Diagnostics.run(uri, document)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
@writer.write(
|
|
169
|
+
method: "textDocument/publishDiagnostics",
|
|
170
|
+
params: Interface::PublishDiagnosticsParams.new(
|
|
171
|
+
uri: uri,
|
|
172
|
+
diagnostics: response.map(&:to_lsp_diagnostic)
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
sig do
|
|
178
|
+
params(uri: String, range: T::Range[Integer]).returns(T::Array[LanguageServer::Protocol::Interface::Diagnostic])
|
|
179
|
+
end
|
|
180
|
+
def respond_with_code_actions(uri, range)
|
|
181
|
+
store.cache_fetch(uri, :code_actions) do |document|
|
|
182
|
+
Requests::CodeActions.run(uri, document, range)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
sig do
|
|
187
|
+
params(
|
|
188
|
+
uri: String,
|
|
189
|
+
position: Document::PositionShape
|
|
190
|
+
).returns(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
|
|
191
|
+
end
|
|
192
|
+
def respond_with_document_highlight(uri, position)
|
|
193
|
+
Requests::DocumentHighlight.run(store.get(uri), position)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
sig { params(request: T::Hash[Symbol, T.untyped], block: T.proc.void).returns(T.untyped) }
|
|
197
|
+
def with_telemetry(request, &block)
|
|
198
|
+
result = T.let(nil, T.untyped)
|
|
199
|
+
error = T.let(nil, T.nilable(StandardError))
|
|
200
|
+
|
|
201
|
+
request_time = Benchmark.realtime do
|
|
202
|
+
result = block.call
|
|
203
|
+
rescue StandardError => e
|
|
204
|
+
error = e
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
@writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
|
|
208
|
+
result
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
sig do
|
|
212
|
+
params(
|
|
213
|
+
request: T::Hash[Symbol, T.untyped],
|
|
214
|
+
request_time: Float,
|
|
215
|
+
error: T.nilable(StandardError)
|
|
216
|
+
).returns(T::Hash[Symbol, T.any(String, Float)])
|
|
217
|
+
end
|
|
218
|
+
def telemetry_params(request, request_time, error)
|
|
219
|
+
uri = request.dig(:params, :textDocument, :uri)
|
|
220
|
+
|
|
221
|
+
params = {
|
|
222
|
+
request: request[:method],
|
|
223
|
+
lspVersion: RubyLsp::VERSION,
|
|
224
|
+
requestTime: request_time,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if error
|
|
228
|
+
params[:errorClass] = error.class.name
|
|
229
|
+
params[:errorMessage] = error.message
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
params[:uri] = uri if uri
|
|
233
|
+
params
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# :nodoc:
|
|
7
|
+
class BaseRequest < SyntaxTree::Visitor
|
|
8
|
+
def self.run(document)
|
|
9
|
+
new(document).run
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(document)
|
|
13
|
+
@document = document
|
|
14
|
+
|
|
15
|
+
super()
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
raise NotImplementedError, "#{self.class}#run must be implemented"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def range_from_syntax_tree_node(node)
|
|
23
|
+
loc = node.location
|
|
24
|
+
|
|
25
|
+
LanguageServer::Protocol::Interface::Range.new(
|
|
26
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: loc.start_line - 1,
|
|
27
|
+
character: loc.start_column),
|
|
28
|
+
end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
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
|
|
18
|
+
def self.run(uri, document, range)
|
|
19
|
+
new(uri, document, range).run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(uri, document, range)
|
|
23
|
+
@document = document
|
|
24
|
+
@uri = uri
|
|
25
|
+
@range = range
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run
|
|
29
|
+
diagnostics = Diagnostics.run(@uri, @document)
|
|
30
|
+
corrections = diagnostics.select { |diagnostic| diagnostic.correctable? && diagnostic.in_range?(@range) }
|
|
31
|
+
return [] if corrections.empty?
|
|
32
|
+
|
|
33
|
+
corrections.map!(&:to_lsp_code_action)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
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
|
+
# ```
|
|
17
|
+
class Diagnostics < RuboCopRequest
|
|
18
|
+
def run
|
|
19
|
+
return syntax_error_diagnostics if @document.syntax_errors?
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
|
|
23
|
+
@diagnostics
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def file_finished(_file, offenses)
|
|
27
|
+
@diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def syntax_error_diagnostics
|
|
33
|
+
@document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# typed: true
|
|
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
|
+
def self.run(document, position)
|
|
25
|
+
new(document, position).run
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(document, position)
|
|
29
|
+
@highlights = []
|
|
30
|
+
position = Document::Scanner.new(document.source).find_position(position)
|
|
31
|
+
@target = find(document.tree, position)
|
|
32
|
+
|
|
33
|
+
super(document)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run
|
|
37
|
+
# no @target means the target is not highlightable
|
|
38
|
+
return [] unless @target
|
|
39
|
+
|
|
40
|
+
visit(@document.tree)
|
|
41
|
+
@highlights
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_var_field(node)
|
|
45
|
+
if matches_target?(node.value)
|
|
46
|
+
add_highlight(
|
|
47
|
+
node.value,
|
|
48
|
+
LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def visit_var_ref(node)
|
|
56
|
+
if matches_target?(node.value)
|
|
57
|
+
add_highlight(
|
|
58
|
+
node.value,
|
|
59
|
+
LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def find(node, position)
|
|
69
|
+
matched =
|
|
70
|
+
node.child_nodes.compact.bsearch do |child|
|
|
71
|
+
if (child.location.start_char...child.location.end_char).cover?(position)
|
|
72
|
+
0
|
|
73
|
+
else
|
|
74
|
+
position <=> child.location.start_char
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
case matched
|
|
79
|
+
when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
|
|
80
|
+
matched
|
|
81
|
+
when SyntaxTree::Node
|
|
82
|
+
find(matched, position)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def matches_target?(node)
|
|
87
|
+
node.is_a?(@target.class) && node.value == @target.value
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def add_highlight(node, kind)
|
|
91
|
+
range = range_from_syntax_tree_node(node)
|
|
92
|
+
@highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|