rucoa 0.2.0 → 0.5.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/.rubocop.yml +6 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +11 -2
- data/README.md +39 -6
- data/data/definitions_ruby_3_1 +0 -0
- data/images/diagnostics.gif +0 -0
- data/images/document-formatting.gif +0 -0
- data/images/document-symbol.gif +0 -0
- data/images/selection-ranges.gif +0 -0
- data/lib/rucoa/cli.rb +2 -2
- data/lib/rucoa/configuration.rb +97 -0
- data/lib/rucoa/definition_archiver.rb +29 -0
- data/lib/rucoa/definition_builders/rbs_constant_definition_builder.rb +44 -0
- data/lib/rucoa/definition_builders/rbs_method_definition_builder.rb +106 -0
- data/lib/rucoa/definition_builders/yard_method_definition_builder.rb +191 -0
- data/lib/rucoa/definition_builders.rb +9 -0
- data/lib/rucoa/definition_store.rb +63 -0
- data/lib/rucoa/definitions/base.rb +12 -0
- data/lib/rucoa/definitions/constant_definition.rb +51 -0
- data/lib/rucoa/definitions/method_definition.rb +126 -0
- data/lib/rucoa/definitions/method_parameter_definition.rb +30 -0
- data/lib/rucoa/definitions.rb +9 -0
- data/lib/rucoa/handler_concerns/configuration_requestable.rb +35 -0
- data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +172 -0
- data/lib/rucoa/handler_concerns.rb +8 -0
- data/lib/rucoa/handlers/base.rb +69 -0
- data/lib/rucoa/handlers/exit_handler.rb +11 -0
- data/lib/rucoa/handlers/initialize_handler.rb +29 -0
- data/lib/rucoa/handlers/initialized_handler.rb +23 -0
- data/lib/rucoa/handlers/shutdown_handler.rb +12 -0
- data/lib/rucoa/handlers/text_document_code_action_handler.rb +104 -0
- data/lib/rucoa/handlers/text_document_did_change_handler.rb +12 -0
- data/lib/rucoa/handlers/text_document_did_open_handler.rb +38 -0
- data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +241 -0
- data/lib/rucoa/handlers/text_document_formatting_handler.rb +64 -0
- data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +76 -0
- data/lib/rucoa/handlers/text_document_selection_range_handler.rb +141 -0
- data/lib/rucoa/handlers/text_document_signature_help_handler.rb +68 -0
- data/lib/rucoa/handlers/workspace_did_change_configuration_handler.rb +13 -0
- data/lib/rucoa/handlers.rb +20 -0
- data/lib/rucoa/message_reader.rb +3 -1
- data/lib/rucoa/message_writer.rb +3 -0
- data/lib/rucoa/node_concerns/name_full_qualifiable.rb +20 -0
- data/lib/rucoa/node_concerns.rb +7 -0
- data/lib/rucoa/node_inspector.rb +109 -0
- data/lib/rucoa/nodes/base.rb +43 -0
- data/lib/rucoa/nodes/casgn_node.rb +14 -0
- data/lib/rucoa/nodes/class_node.rb +8 -0
- data/lib/rucoa/nodes/const_node.rb +12 -0
- data/lib/rucoa/nodes/def_node.rb +71 -0
- data/lib/rucoa/nodes/defs_node.rb +12 -0
- data/lib/rucoa/nodes/lvar_node.rb +25 -0
- data/lib/rucoa/nodes/module_node.rb +21 -0
- data/lib/rucoa/nodes/sclass_node.rb +8 -0
- data/lib/rucoa/nodes/send_node.rb +60 -0
- data/lib/rucoa/nodes/str_node.rb +4 -0
- data/lib/rucoa/nodes/sym_node.rb +12 -0
- data/lib/rucoa/nodes.rb +10 -0
- data/lib/rucoa/parser_builder.rb +20 -10
- data/lib/rucoa/range.rb +89 -13
- data/lib/rucoa/rbs_document_loader.rb +43 -0
- data/lib/rucoa/rubocop_autocorrector.rb +1 -1
- data/lib/rucoa/rubocop_configuration_checker.rb +42 -0
- data/lib/rucoa/rubocop_investigator.rb +1 -1
- data/lib/rucoa/server.rb +96 -122
- data/lib/rucoa/source.rb +56 -6
- data/lib/rucoa/source_store.rb +11 -9
- data/lib/rucoa/types/method_type.rb +23 -0
- data/lib/rucoa/types.rb +7 -0
- data/lib/rucoa/version.rb +1 -1
- data/lib/rucoa/yard_glob_document_loader.rb +47 -0
- data/lib/rucoa/yard_string_document_loader.rb +70 -0
- data/lib/rucoa.rb +14 -4
- data/rucoa.gemspec +1 -0
- metadata +70 -6
- data/lib/rucoa/code_action_provider.rb +0 -102
- data/lib/rucoa/diagnostic_provider.rb +0 -159
- data/lib/rucoa/formatting_provider.rb +0 -52
- data/lib/rucoa/selection_range_provider.rb +0 -97
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rucoa
|
4
|
+
module Handlers
|
5
|
+
class TextDocumentCodeActionHandler < Base
|
6
|
+
def call
|
7
|
+
return unless diagnostics
|
8
|
+
|
9
|
+
respond(code_actions)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# @return [Array<Hash>]
|
15
|
+
def code_actions
|
16
|
+
return [] unless configuration.enables_code_action?
|
17
|
+
|
18
|
+
correctable_diagnostics.map do |diagnostic|
|
19
|
+
DiagnosticToCodeActionMapper.call(diagnostic)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Array<Hash>]
|
24
|
+
def correctable_diagnostics
|
25
|
+
diagnostics.select do |diagnostic|
|
26
|
+
diagnostic.dig('data', 'edits')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<Hash>, nil]
|
31
|
+
def diagnostics
|
32
|
+
@diagnostics ||= request.dig('params', 'context', 'diagnostics')
|
33
|
+
end
|
34
|
+
|
35
|
+
class DiagnosticToCodeActionMapper
|
36
|
+
class << self
|
37
|
+
# @param diagnostic [Hash]
|
38
|
+
# @return [Hash]
|
39
|
+
def call(diagnostic)
|
40
|
+
new(diagnostic).call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param diagnostic [Hash]
|
45
|
+
def initialize(diagnostic)
|
46
|
+
@diagnostic = diagnostic
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Hash]
|
50
|
+
def call
|
51
|
+
{
|
52
|
+
diagnostics: diagnostics,
|
53
|
+
edit: edit,
|
54
|
+
isPreferred: preferred?,
|
55
|
+
kind: kind,
|
56
|
+
title: title
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @return [Hash]
|
63
|
+
def edit
|
64
|
+
{
|
65
|
+
documentChanges: [
|
66
|
+
{
|
67
|
+
edits: @diagnostic.dig('data', 'edits'),
|
68
|
+
textDocument: {
|
69
|
+
uri: @diagnostic.dig('data', 'uri'),
|
70
|
+
version: nil
|
71
|
+
}
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [String]
|
78
|
+
def cop_name
|
79
|
+
@diagnostic.dig('data', 'cop_name')
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Array]
|
83
|
+
def diagnostics
|
84
|
+
[@diagnostic]
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Boolean]
|
88
|
+
def preferred?
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [String]
|
93
|
+
def kind
|
94
|
+
'quickfix'
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [String]
|
98
|
+
def title
|
99
|
+
"Autocorrect #{cop_name}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rucoa
|
4
|
+
module Handlers
|
5
|
+
class TextDocumentDidOpenHandler < Base
|
6
|
+
include HandlerConcerns::DiagnosticsPublishable
|
7
|
+
|
8
|
+
def call
|
9
|
+
source_store.update(source)
|
10
|
+
definition_store.update_definitions_defined_in(
|
11
|
+
source.path,
|
12
|
+
definitions: source.definitions
|
13
|
+
)
|
14
|
+
publish_diagnostics_on(uri)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# @return [Rucoa::Source]
|
20
|
+
def source
|
21
|
+
@source ||= Source.new(
|
22
|
+
content: text,
|
23
|
+
uri: uri
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def text
|
29
|
+
request.dig('params', 'textDocument', 'text')
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
def uri
|
34
|
+
@uri ||= request.dig('params', 'textDocument', 'uri')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Rucoa
|
6
|
+
module Handlers
|
7
|
+
class TextDocumentDocumentSymbolHandler < Base
|
8
|
+
DOCUMENT_SYMBOL_KIND_FOR_FILE = 1
|
9
|
+
DOCUMENT_SYMBOL_KIND_FOR_MODULE = 2
|
10
|
+
DOCUMENT_SYMBOL_KIND_FOR_NAMESPACE = 3
|
11
|
+
DOCUMENT_SYMBOL_KIND_FOR_PACKAGE = 4
|
12
|
+
DOCUMENT_SYMBOL_KIND_FOR_CLASS = 5
|
13
|
+
DOCUMENT_SYMBOL_KIND_FOR_METHOD = 6
|
14
|
+
DOCUMENT_SYMBOL_KIND_FOR_PROPERTY = 7
|
15
|
+
DOCUMENT_SYMBOL_KIND_FOR_FIELD = 8
|
16
|
+
DOCUMENT_SYMBOL_KIND_FOR_CONSTRUCTOR = 9
|
17
|
+
DOCUMENT_SYMBOL_KIND_FOR_ENUM = 10
|
18
|
+
DOCUMENT_SYMBOL_KIND_FOR_INTERFACE = 11
|
19
|
+
DOCUMENT_SYMBOL_KIND_FOR_FUNCTION = 12
|
20
|
+
DOCUMENT_SYMBOL_KIND_FOR_VARIABLE = 13
|
21
|
+
DOCUMENT_SYMBOL_KIND_FOR_CONSTANT = 14
|
22
|
+
DOCUMENT_SYMBOL_KIND_FOR_STRING = 15
|
23
|
+
DOCUMENT_SYMBOL_KIND_FOR_NUMBER = 16
|
24
|
+
DOCUMENT_SYMBOL_KIND_FOR_BOOLEAN = 17
|
25
|
+
DOCUMENT_SYMBOL_KIND_FOR_ARRAY = 18
|
26
|
+
DOCUMENT_SYMBOL_KIND_FOR_OBJECT = 19
|
27
|
+
DOCUMENT_SYMBOL_KIND_FOR_KEY = 20
|
28
|
+
DOCUMENT_SYMBOL_KIND_FOR_NULL = 21
|
29
|
+
DOCUMENT_SYMBOL_KIND_FOR_ENUMMEMBER = 22
|
30
|
+
DOCUMENT_SYMBOL_KIND_FOR_STRUCT = 23
|
31
|
+
DOCUMENT_SYMBOL_KIND_FOR_EVENT = 24
|
32
|
+
DOCUMENT_SYMBOL_KIND_FOR_OPERATOR = 25
|
33
|
+
DOCUMENT_SYMBOL_KIND_FOR_TYPEPARAMETER = 26
|
34
|
+
|
35
|
+
# @return [Set<String>]
|
36
|
+
ATTRIBUTE_METHOD_NAMES = ::Set[
|
37
|
+
'attr_accessor',
|
38
|
+
'attr_reader',
|
39
|
+
'attr_writer',
|
40
|
+
]
|
41
|
+
|
42
|
+
def call
|
43
|
+
return unless respondable?
|
44
|
+
|
45
|
+
respond(document_symbols)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# @return [Array<Hash>]
|
51
|
+
def document_symbols
|
52
|
+
visit(source.root_node)
|
53
|
+
document_symbol_stack.first[:children]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param node [Rucoa::Nodes::Base]
|
57
|
+
# @return [void]
|
58
|
+
def visit(node)
|
59
|
+
document_symbols = create_document_symbols_for(node)
|
60
|
+
document_symbol_stack.last[:children].push(*document_symbols)
|
61
|
+
with_document_symbol_stack(document_symbols.first) do
|
62
|
+
with_singleton_class_stack(node) do
|
63
|
+
node.each_child_node do |child_node|
|
64
|
+
visit(child_node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param document_symbol [Hash, nil]
|
71
|
+
# @return [void]
|
72
|
+
def with_document_symbol_stack(document_symbol)
|
73
|
+
unless document_symbol
|
74
|
+
yield
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
document_symbol_stack.push(document_symbol)
|
79
|
+
yield
|
80
|
+
document_symbol_stack.pop
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param node [Rucoa::Nodes::Base]
|
84
|
+
def with_singleton_class_stack(node)
|
85
|
+
unless node.is_a?(Rucoa::Nodes::SclassNode)
|
86
|
+
yield
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
singleton_class_stack.push(node)
|
91
|
+
yield
|
92
|
+
singleton_class_stack.pop
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param node [Rucoa::Nodes::Base]
|
96
|
+
# @return [Array<Hash>]
|
97
|
+
def create_document_symbols_for(node)
|
98
|
+
case node
|
99
|
+
when Nodes::CasgnNode
|
100
|
+
create_document_symbols_for_casgn(node)
|
101
|
+
when Nodes::ClassNode
|
102
|
+
create_document_symbols_for_class(node)
|
103
|
+
when Nodes::DefNode
|
104
|
+
create_document_symbols_for_def(node)
|
105
|
+
when Nodes::DefsNode
|
106
|
+
create_document_symbols_for_defs(node)
|
107
|
+
when Nodes::ModuleNode
|
108
|
+
create_document_symbols_for_module(node)
|
109
|
+
when Nodes::SendNode
|
110
|
+
create_document_symbols_for_send(node)
|
111
|
+
else
|
112
|
+
[]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param node [Rucoa::Nodes::CasgnNode]
|
117
|
+
# @return [Array<Hash>]
|
118
|
+
def create_document_symbols_for_casgn(node)
|
119
|
+
[
|
120
|
+
{
|
121
|
+
children: [],
|
122
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_CONSTANT,
|
123
|
+
name: node.name,
|
124
|
+
range: Range.from_parser_range(node.location.expression).to_vscode_range,
|
125
|
+
selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
|
126
|
+
}
|
127
|
+
]
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param node [Rucoa::Nodes::ClassNode]
|
131
|
+
# @return [Array<Hash>]
|
132
|
+
def create_document_symbols_for_class(node)
|
133
|
+
[
|
134
|
+
{
|
135
|
+
children: [],
|
136
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_CLASS,
|
137
|
+
name: node.name,
|
138
|
+
range: Range.from_parser_range(node.location.expression).to_vscode_range,
|
139
|
+
selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
|
140
|
+
}
|
141
|
+
]
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param node [Rucoa::Nodes::ModuleNode]
|
145
|
+
# @return [Array<Hash>]
|
146
|
+
def create_document_symbols_for_module(node)
|
147
|
+
[
|
148
|
+
{
|
149
|
+
children: [],
|
150
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_MODULE,
|
151
|
+
name: node.name,
|
152
|
+
range: Range.from_parser_range(node.location.expression).to_vscode_range,
|
153
|
+
selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
|
154
|
+
}
|
155
|
+
]
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param node [Rucoa::Nodes::DefNode]
|
159
|
+
# @return [Array<Hash>]
|
160
|
+
def create_document_symbols_for_def(node)
|
161
|
+
[
|
162
|
+
{
|
163
|
+
children: [],
|
164
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_METHOD,
|
165
|
+
name: singleton_class_stack.empty? ? "##{node.name}" : ".#{node.name}",
|
166
|
+
range: Range.from_parser_range(node.location.expression).to_vscode_range,
|
167
|
+
selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
|
168
|
+
}
|
169
|
+
]
|
170
|
+
end
|
171
|
+
|
172
|
+
# @param node [Rucoa::Nodes::DefNode]
|
173
|
+
# @return [Array<Hash>]
|
174
|
+
def create_document_symbols_for_defs(node)
|
175
|
+
[
|
176
|
+
{
|
177
|
+
children: [],
|
178
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_METHOD,
|
179
|
+
name: ".#{node.name}",
|
180
|
+
range: Range.from_parser_range(node.location.expression).to_vscode_range,
|
181
|
+
selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
|
182
|
+
}
|
183
|
+
]
|
184
|
+
end
|
185
|
+
|
186
|
+
# @param node [Rucoa::Nodes::SendNode]
|
187
|
+
# @return [Array<Hash>]
|
188
|
+
def create_document_symbols_for_send(node)
|
189
|
+
return [] unless ATTRIBUTE_METHOD_NAMES.include?(node.name)
|
190
|
+
|
191
|
+
simple_types = ::Set[
|
192
|
+
:str,
|
193
|
+
:sym
|
194
|
+
]
|
195
|
+
node.arguments.select do |argument|
|
196
|
+
simple_types.include?(argument.type)
|
197
|
+
end.map do |argument|
|
198
|
+
{
|
199
|
+
children: [],
|
200
|
+
kind: DOCUMENT_SYMBOL_KIND_FOR_FIELD,
|
201
|
+
name: argument.value.to_s,
|
202
|
+
range: Range.from_parser_range(argument.location.expression).to_vscode_range,
|
203
|
+
selectionRange: Range.from_parser_range(argument.location.expression).to_vscode_range
|
204
|
+
}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Boolean]
|
209
|
+
def respondable?
|
210
|
+
configuration.enables_document_symbol? && source
|
211
|
+
end
|
212
|
+
|
213
|
+
# @return [Rucoa::Source]
|
214
|
+
def source
|
215
|
+
@source ||= source_store.get(uri)
|
216
|
+
end
|
217
|
+
|
218
|
+
# @return [String]
|
219
|
+
def uri
|
220
|
+
request.dig('params', 'textDocument', 'uri')
|
221
|
+
end
|
222
|
+
|
223
|
+
# @return [Arrah<Hash>]
|
224
|
+
def document_symbol_stack
|
225
|
+
@document_symbol_stack ||= [dummy_document_symbol]
|
226
|
+
end
|
227
|
+
|
228
|
+
# @return [Hash]
|
229
|
+
def dummy_document_symbol
|
230
|
+
{
|
231
|
+
children: []
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
# @return [Array<Rucoa::Nodes::SclassNode>]
|
236
|
+
def singleton_class_stack
|
237
|
+
@singleton_class_stack ||= []
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rucoa
|
4
|
+
module Handlers
|
5
|
+
class TextDocumentFormattingHandler < Base
|
6
|
+
def call
|
7
|
+
respond(edits)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# @return [Boolean]
|
13
|
+
def formattable?
|
14
|
+
configuration.enables_formatting? &&
|
15
|
+
source &&
|
16
|
+
RubocopConfigurationChecker.call
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<Hash>]
|
20
|
+
def edits
|
21
|
+
return [] unless formattable?
|
22
|
+
|
23
|
+
[text_edit]
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Rucoa::Source, nil]
|
27
|
+
def source
|
28
|
+
@source ||= source_store.get(uri)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
def uri
|
33
|
+
request.dig('params', 'textDocument', 'uri')
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash]
|
37
|
+
def text_edit
|
38
|
+
{
|
39
|
+
newText: new_text,
|
40
|
+
range: range
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String]
|
45
|
+
def new_text
|
46
|
+
RubocopAutocorrector.call(source: source)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Hash]
|
50
|
+
def range
|
51
|
+
Range.new(
|
52
|
+
Position.new(
|
53
|
+
column: 0,
|
54
|
+
line: 1
|
55
|
+
),
|
56
|
+
Position.new(
|
57
|
+
column: @source.content.lines.last.length,
|
58
|
+
line: @source.content.lines.count + 1
|
59
|
+
)
|
60
|
+
).to_vscode_range
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rucoa
|
4
|
+
module Handlers
|
5
|
+
class TextDocumentRangeFormattingHandler < Base
|
6
|
+
def call
|
7
|
+
respond(edits)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# @return [Array<Hash>]
|
13
|
+
def edits
|
14
|
+
return [] unless formattable?
|
15
|
+
|
16
|
+
correctable_replacements.map do |replacement_range, replacement|
|
17
|
+
{
|
18
|
+
newText: replacement,
|
19
|
+
range: replacement_range.to_vscode_range
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def formattable?
|
26
|
+
configuration.enables_formatting? &&
|
27
|
+
source &&
|
28
|
+
RubocopConfigurationChecker.call
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Rucoa::Source]
|
32
|
+
def source
|
33
|
+
@source ||= source_store.get(uri)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
def uri
|
38
|
+
request.dig('params', 'textDocument', 'uri')
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Rucoa::Range]
|
42
|
+
def range
|
43
|
+
@range ||= Range.from_vscode_range(
|
44
|
+
request.dig('params', 'range')
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Array<RuboCop::Cop::Corrector>]
|
49
|
+
def correctable_offenses
|
50
|
+
offenses.select(&:corrector)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Array(Rucoa::Range, String)]
|
54
|
+
def correctable_replacements
|
55
|
+
replacements.select do |replacement_range, _|
|
56
|
+
range.contain?(replacement_range)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Array<RuboCop::Cop::Offense>]
|
61
|
+
def offenses
|
62
|
+
RubocopInvestigator.call(source: source)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Array(Rucoa::Range, String)]
|
66
|
+
def replacements
|
67
|
+
correctable_offenses.map(&:corrector).flat_map(&:as_replacements).map do |replacement_range, replacement|
|
68
|
+
[
|
69
|
+
Range.from_parser_range(replacement_range),
|
70
|
+
replacement
|
71
|
+
]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rucoa
|
4
|
+
module Handlers
|
5
|
+
class TextDocumentSelectionRangeHandler < Base
|
6
|
+
def call
|
7
|
+
return unless respondable?
|
8
|
+
|
9
|
+
respond(
|
10
|
+
positions.filter_map do |position|
|
11
|
+
SelectionRangeProvider.call(
|
12
|
+
position: position,
|
13
|
+
source: source
|
14
|
+
)
|
15
|
+
end
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @return [Boolean]
|
22
|
+
def respondable?
|
23
|
+
configuration.enables_selection_range? &&
|
24
|
+
source
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Rucoa::Source]
|
28
|
+
def source
|
29
|
+
@source ||= source_store.get(uri)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<Rucoa::Position>]
|
33
|
+
def positions
|
34
|
+
request.dig('params', 'positions').map do |position|
|
35
|
+
Position.from_vscode_position(position)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
def uri
|
41
|
+
request.dig('params', 'textDocument', 'uri')
|
42
|
+
end
|
43
|
+
|
44
|
+
class SelectionRangeProvider
|
45
|
+
class << self
|
46
|
+
# @param source [Rucoa::Source]
|
47
|
+
# @param position [Rucoa::Position]
|
48
|
+
# @return [Hash, nil]
|
49
|
+
def call(position:, source:)
|
50
|
+
new(
|
51
|
+
position: position,
|
52
|
+
source: source
|
53
|
+
).call
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param position [Rucoa::Position]
|
58
|
+
# @param source [Rucoa::Source]
|
59
|
+
def initialize(position:, source:)
|
60
|
+
@position = position
|
61
|
+
@source = source
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Hash, nil]
|
65
|
+
def call
|
66
|
+
ranges.reverse.reduce(nil) do |result, range|
|
67
|
+
{
|
68
|
+
parent: result,
|
69
|
+
range: range.to_vscode_range
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @return [Rucoa::Nodes::Base, nil]
|
77
|
+
def node_at_position
|
78
|
+
if instance_variable_defined?(:@node_at_position)
|
79
|
+
@node_at_position
|
80
|
+
else
|
81
|
+
@node_at_position = @source.node_at(@position)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [Array<Rucoa::Range>]
|
86
|
+
def ranges
|
87
|
+
return [] unless node_at_position
|
88
|
+
|
89
|
+
[node_at_position, *node_at_position.ancestors].flat_map do |node|
|
90
|
+
NodeToRangesMapper.call(node)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class NodeToRangesMapper
|
95
|
+
class << self
|
96
|
+
# @param node [Rucoa::Nodes::Base]
|
97
|
+
# @return [Array<Rucoa::Range>]
|
98
|
+
def call(node)
|
99
|
+
new(node).call
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param node [Rucoa::Nodes::Base]
|
104
|
+
def initialize(node)
|
105
|
+
@node = node
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [Array<Rucoa::Range>]
|
109
|
+
def call
|
110
|
+
case @node
|
111
|
+
when Nodes::StrNode
|
112
|
+
[
|
113
|
+
inner_range,
|
114
|
+
expression_range
|
115
|
+
]
|
116
|
+
else
|
117
|
+
[
|
118
|
+
expression_range
|
119
|
+
]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# @return [Rucoa::Range]
|
126
|
+
def inner_range
|
127
|
+
Range.new(
|
128
|
+
Position.from_parser_range_ending(@node.location.begin),
|
129
|
+
Position.from_parser_range_beginning(@node.location.end)
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @return [Rucoa::Range]
|
134
|
+
def expression_range
|
135
|
+
Range.from_parser_range(@node.location.expression)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|