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,216 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# The [document
|
|
7
|
+
# symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) request
|
|
8
|
+
# informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
|
|
9
|
+
# this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
|
|
10
|
+
#
|
|
11
|
+
# In VS Code, fuzzy symbol search can be accessed by opened the command palette and inserting an `@` symbol.
|
|
12
|
+
#
|
|
13
|
+
# # Example
|
|
14
|
+
#
|
|
15
|
+
# ```ruby
|
|
16
|
+
# class Person # --> document symbol: class
|
|
17
|
+
# attr_reader :age # --> document symbol: field
|
|
18
|
+
#
|
|
19
|
+
# def initialize
|
|
20
|
+
# @age = 0 # --> document symbol: variable
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# def age # --> document symbol: method
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# ```
|
|
27
|
+
class DocumentSymbol < BaseRequest
|
|
28
|
+
SYMBOL_KIND = {
|
|
29
|
+
file: 1,
|
|
30
|
+
module: 2,
|
|
31
|
+
namespace: 3,
|
|
32
|
+
package: 4,
|
|
33
|
+
class: 5,
|
|
34
|
+
method: 6,
|
|
35
|
+
property: 7,
|
|
36
|
+
field: 8,
|
|
37
|
+
constructor: 9,
|
|
38
|
+
enum: 10,
|
|
39
|
+
interface: 11,
|
|
40
|
+
function: 12,
|
|
41
|
+
variable: 13,
|
|
42
|
+
constant: 14,
|
|
43
|
+
string: 15,
|
|
44
|
+
number: 16,
|
|
45
|
+
boolean: 17,
|
|
46
|
+
array: 18,
|
|
47
|
+
object: 19,
|
|
48
|
+
key: 20,
|
|
49
|
+
null: 21,
|
|
50
|
+
enummember: 22,
|
|
51
|
+
struct: 23,
|
|
52
|
+
event: 24,
|
|
53
|
+
operator: 25,
|
|
54
|
+
typeparameter: 26,
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
ATTR_ACCESSORS = ["attr_reader", "attr_writer", "attr_accessor"].freeze
|
|
58
|
+
|
|
59
|
+
class SymbolHierarchyRoot
|
|
60
|
+
attr_reader :children
|
|
61
|
+
|
|
62
|
+
def initialize
|
|
63
|
+
@children = []
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(document)
|
|
68
|
+
super
|
|
69
|
+
|
|
70
|
+
@root = SymbolHierarchyRoot.new
|
|
71
|
+
@stack = [@root]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def run
|
|
75
|
+
visit(@document.tree)
|
|
76
|
+
@root.children
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def visit_class(node)
|
|
80
|
+
symbol = create_document_symbol(
|
|
81
|
+
name: node.constant.constant.value,
|
|
82
|
+
kind: :class,
|
|
83
|
+
range_node: node,
|
|
84
|
+
selection_range_node: node.constant
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@stack << symbol
|
|
88
|
+
visit(node.bodystmt)
|
|
89
|
+
@stack.pop
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def visit_command(node)
|
|
93
|
+
return unless ATTR_ACCESSORS.include?(node.message.value)
|
|
94
|
+
|
|
95
|
+
node.arguments.parts.each do |argument|
|
|
96
|
+
next unless argument.is_a?(SyntaxTree::SymbolLiteral)
|
|
97
|
+
|
|
98
|
+
create_document_symbol(
|
|
99
|
+
name: argument.value.value,
|
|
100
|
+
kind: :field,
|
|
101
|
+
range_node: argument,
|
|
102
|
+
selection_range_node: argument.value
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def visit_const_path_field(node)
|
|
108
|
+
create_document_symbol(
|
|
109
|
+
name: node.constant.value,
|
|
110
|
+
kind: :constant,
|
|
111
|
+
range_node: node,
|
|
112
|
+
selection_range_node: node.constant
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def visit_def(node)
|
|
117
|
+
name = node.name.value
|
|
118
|
+
|
|
119
|
+
symbol = create_document_symbol(
|
|
120
|
+
name: name,
|
|
121
|
+
kind: name == "initialize" ? :constructor : :method,
|
|
122
|
+
range_node: node,
|
|
123
|
+
selection_range_node: node.name
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@stack << symbol
|
|
127
|
+
visit(node.bodystmt)
|
|
128
|
+
@stack.pop
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def visit_def_endless(node)
|
|
132
|
+
name = node.name.value
|
|
133
|
+
|
|
134
|
+
symbol = create_document_symbol(
|
|
135
|
+
name: name,
|
|
136
|
+
kind: name == "initialize" ? :constructor : :method,
|
|
137
|
+
range_node: node,
|
|
138
|
+
selection_range_node: node.name
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@stack << symbol
|
|
142
|
+
visit(node.statement)
|
|
143
|
+
@stack.pop
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def visit_defs(node)
|
|
147
|
+
symbol = create_document_symbol(
|
|
148
|
+
name: "self.#{node.name.value}",
|
|
149
|
+
kind: :method,
|
|
150
|
+
range_node: node,
|
|
151
|
+
selection_range_node: node.name
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
@stack << symbol
|
|
155
|
+
visit(node.bodystmt)
|
|
156
|
+
@stack.pop
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def visit_module(node)
|
|
160
|
+
symbol = create_document_symbol(
|
|
161
|
+
name: node.constant.constant.value,
|
|
162
|
+
kind: :module,
|
|
163
|
+
range_node: node,
|
|
164
|
+
selection_range_node: node.constant
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@stack << symbol
|
|
168
|
+
visit(node.bodystmt)
|
|
169
|
+
@stack.pop
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def visit_top_const_field(node)
|
|
173
|
+
create_document_symbol(
|
|
174
|
+
name: node.constant.value,
|
|
175
|
+
kind: :constant,
|
|
176
|
+
range_node: node,
|
|
177
|
+
selection_range_node: node.constant
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def visit_var_field(node)
|
|
182
|
+
kind = case node.value
|
|
183
|
+
when SyntaxTree::Const
|
|
184
|
+
:constant
|
|
185
|
+
when SyntaxTree::CVar, SyntaxTree::IVar
|
|
186
|
+
:variable
|
|
187
|
+
else
|
|
188
|
+
return
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
create_document_symbol(
|
|
192
|
+
name: node.value.value,
|
|
193
|
+
kind: kind,
|
|
194
|
+
range_node: node,
|
|
195
|
+
selection_range_node: node.value
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
|
|
202
|
+
symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
|
|
203
|
+
name: name,
|
|
204
|
+
kind: SYMBOL_KIND[kind],
|
|
205
|
+
range: range_from_syntax_tree_node(range_node),
|
|
206
|
+
selection_range: range_from_syntax_tree_node(selection_range_node),
|
|
207
|
+
children: [],
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@stack.last.children << symbol
|
|
211
|
+
|
|
212
|
+
symbol
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# The [folding ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
|
|
7
|
+
# request informs the editor of the ranges where code can be folded.
|
|
8
|
+
#
|
|
9
|
+
# # Example
|
|
10
|
+
# ```ruby
|
|
11
|
+
# def say_hello # <-- folding range start
|
|
12
|
+
# puts "Hello"
|
|
13
|
+
# end # <-- folding range end
|
|
14
|
+
# ```
|
|
15
|
+
class FoldingRanges < BaseRequest
|
|
16
|
+
SIMPLE_FOLDABLES = [
|
|
17
|
+
SyntaxTree::ArrayLiteral,
|
|
18
|
+
SyntaxTree::BraceBlock,
|
|
19
|
+
SyntaxTree::Case,
|
|
20
|
+
SyntaxTree::ClassDeclaration,
|
|
21
|
+
SyntaxTree::Command,
|
|
22
|
+
SyntaxTree::DoBlock,
|
|
23
|
+
SyntaxTree::FCall,
|
|
24
|
+
SyntaxTree::For,
|
|
25
|
+
SyntaxTree::HashLiteral,
|
|
26
|
+
SyntaxTree::Heredoc,
|
|
27
|
+
SyntaxTree::If,
|
|
28
|
+
SyntaxTree::ModuleDeclaration,
|
|
29
|
+
SyntaxTree::SClass,
|
|
30
|
+
SyntaxTree::Unless,
|
|
31
|
+
SyntaxTree::Until,
|
|
32
|
+
SyntaxTree::While,
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
NODES_WITH_STATEMENTS = [
|
|
36
|
+
SyntaxTree::Else,
|
|
37
|
+
SyntaxTree::Elsif,
|
|
38
|
+
SyntaxTree::Ensure,
|
|
39
|
+
SyntaxTree::In,
|
|
40
|
+
SyntaxTree::Rescue,
|
|
41
|
+
SyntaxTree::When,
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
def initialize(document)
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
@ranges = []
|
|
48
|
+
@partial_range = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def run
|
|
52
|
+
visit(@document.tree)
|
|
53
|
+
emit_partial_range
|
|
54
|
+
@ranges
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def visit(node)
|
|
60
|
+
return unless handle_partial_range(node)
|
|
61
|
+
|
|
62
|
+
case node
|
|
63
|
+
when *SIMPLE_FOLDABLES
|
|
64
|
+
add_node_range(node)
|
|
65
|
+
when *NODES_WITH_STATEMENTS
|
|
66
|
+
add_statements_range(node, node.statements)
|
|
67
|
+
when SyntaxTree::Begin
|
|
68
|
+
add_statements_range(node, node.bodystmt.statements)
|
|
69
|
+
when SyntaxTree::Call, SyntaxTree::CommandCall
|
|
70
|
+
add_call_range(node)
|
|
71
|
+
return
|
|
72
|
+
when SyntaxTree::Def, SyntaxTree::Defs
|
|
73
|
+
add_def_range(node)
|
|
74
|
+
when SyntaxTree::StringConcat
|
|
75
|
+
add_string_concat(node)
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class PartialRange
|
|
83
|
+
attr_reader :kind, :end_line
|
|
84
|
+
|
|
85
|
+
def self.from(node, kind)
|
|
86
|
+
new(node.location.start_line - 1, node.location.end_line - 1, kind)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def initialize(start_line, end_line, kind)
|
|
90
|
+
@start_line = start_line
|
|
91
|
+
@end_line = end_line
|
|
92
|
+
@kind = kind
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def extend_to(node)
|
|
96
|
+
@end_line = node.location.end_line - 1
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def new_section?(node)
|
|
101
|
+
node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_range
|
|
105
|
+
LanguageServer::Protocol::Interface::FoldingRange.new(
|
|
106
|
+
start_line: @start_line,
|
|
107
|
+
end_line: @end_line,
|
|
108
|
+
kind: @kind
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle_partial_range(node)
|
|
114
|
+
kind = partial_range_kind(node)
|
|
115
|
+
|
|
116
|
+
if kind.nil?
|
|
117
|
+
emit_partial_range
|
|
118
|
+
return true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@partial_range = if @partial_range.nil?
|
|
122
|
+
PartialRange.from(node, kind)
|
|
123
|
+
elsif @partial_range.kind != kind || @partial_range.new_section?(node)
|
|
124
|
+
emit_partial_range
|
|
125
|
+
PartialRange.from(node, kind)
|
|
126
|
+
else
|
|
127
|
+
@partial_range.extend_to(node)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def partial_range_kind(node)
|
|
134
|
+
case node
|
|
135
|
+
when SyntaxTree::Comment
|
|
136
|
+
"comment"
|
|
137
|
+
when SyntaxTree::Command
|
|
138
|
+
if node.message.value == "require" || node.message.value == "require_relative"
|
|
139
|
+
"imports"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def emit_partial_range
|
|
145
|
+
return if @partial_range.nil?
|
|
146
|
+
|
|
147
|
+
@ranges << @partial_range.to_range
|
|
148
|
+
@partial_range = nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def add_call_range(node)
|
|
152
|
+
receiver = T.let(node.receiver, SyntaxTree::Node)
|
|
153
|
+
loop do
|
|
154
|
+
case receiver
|
|
155
|
+
when SyntaxTree::Call
|
|
156
|
+
visit(receiver.arguments)
|
|
157
|
+
receiver = receiver.receiver
|
|
158
|
+
when SyntaxTree::MethodAddBlock
|
|
159
|
+
visit(receiver.block)
|
|
160
|
+
receiver = receiver.call.receiver
|
|
161
|
+
else
|
|
162
|
+
break
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
add_lines_range(receiver.location.start_line, node.location.end_line)
|
|
167
|
+
|
|
168
|
+
visit(node.arguments)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def add_def_range(node)
|
|
172
|
+
params_location = node.params.location
|
|
173
|
+
|
|
174
|
+
if params_location.start_line < params_location.end_line
|
|
175
|
+
add_lines_range(params_location.end_line, node.location.end_line)
|
|
176
|
+
else
|
|
177
|
+
add_node_range(node)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
visit(node.bodystmt.statements)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def add_statements_range(node, statements)
|
|
184
|
+
add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def add_string_concat(node)
|
|
188
|
+
left = T.let(node.left, SyntaxTree::Node)
|
|
189
|
+
left = left.left while left.is_a?(SyntaxTree::StringConcat)
|
|
190
|
+
|
|
191
|
+
add_lines_range(left.location.start_line, node.right.location.end_line)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def add_node_range(node)
|
|
195
|
+
add_location_range(node.location)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def add_location_range(location)
|
|
199
|
+
add_lines_range(location.start_line, location.end_line)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def add_lines_range(start_line, end_line)
|
|
203
|
+
return if start_line >= end_line
|
|
204
|
+
|
|
205
|
+
@ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
|
|
206
|
+
start_line: start_line - 1,
|
|
207
|
+
end_line: end_line - 1,
|
|
208
|
+
kind: "region"
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# The [formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
|
|
7
|
+
# request uses RuboCop to fix auto-correctable offenses in the document. This requires enabling format on save and
|
|
8
|
+
# registering the ruby-lsp as the Ruby formatter.
|
|
9
|
+
#
|
|
10
|
+
# # Example
|
|
11
|
+
#
|
|
12
|
+
# ```ruby
|
|
13
|
+
# def say_hello
|
|
14
|
+
# puts "Hello" # --> formatting: fixes the indentation on save
|
|
15
|
+
# end
|
|
16
|
+
# ```
|
|
17
|
+
class Formatting < RuboCopRequest
|
|
18
|
+
RUBOCOP_FLAGS = (COMMON_RUBOCOP_FLAGS + ["--autocorrect"]).freeze
|
|
19
|
+
|
|
20
|
+
def initialize(uri, document)
|
|
21
|
+
super
|
|
22
|
+
@formatted_text = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
@formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
|
|
29
|
+
return unless @formatted_text
|
|
30
|
+
|
|
31
|
+
[
|
|
32
|
+
LanguageServer::Protocol::Interface::TextEdit.new(
|
|
33
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
|
34
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
|
|
35
|
+
end: LanguageServer::Protocol::Interface::Position.new(
|
|
36
|
+
line: text.size,
|
|
37
|
+
character: text.size
|
|
38
|
+
)
|
|
39
|
+
),
|
|
40
|
+
new_text: @formatted_text
|
|
41
|
+
),
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def rubocop_flags
|
|
48
|
+
RUBOCOP_FLAGS
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "rubocop"
|
|
5
|
+
require "cgi"
|
|
6
|
+
|
|
7
|
+
module RubyLsp
|
|
8
|
+
module Requests
|
|
9
|
+
# :nodoc:
|
|
10
|
+
class RuboCopRequest < RuboCop::Runner
|
|
11
|
+
COMMON_RUBOCOP_FLAGS = [
|
|
12
|
+
"--stderr", # Print any output to stderr so that our stdout does not get polluted
|
|
13
|
+
"--format",
|
|
14
|
+
"RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :file, :text
|
|
18
|
+
|
|
19
|
+
def self.run(uri, document)
|
|
20
|
+
new(uri, document).run
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(uri, document)
|
|
24
|
+
@file = CGI.unescape(URI.parse(uri).path)
|
|
25
|
+
@document = document
|
|
26
|
+
@text = document.source
|
|
27
|
+
@uri = uri
|
|
28
|
+
|
|
29
|
+
super(
|
|
30
|
+
::RuboCop::Options.new.parse(rubocop_flags).first,
|
|
31
|
+
::RuboCop::ConfigStore.new
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
# We communicate with Rubocop via stdin
|
|
37
|
+
@options[:stdin] = text
|
|
38
|
+
|
|
39
|
+
# Invoke the actual run method with just this file in `paths`
|
|
40
|
+
super([file])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def rubocop_flags
|
|
46
|
+
COMMON_RUBOCOP_FLAGS
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module RubyLsp
|
|
5
|
+
module Requests
|
|
6
|
+
# The [selection ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange)
|
|
7
|
+
# request informs the editor of ranges that the user may want to select based on the location(s)
|
|
8
|
+
# of their cursor(s).
|
|
9
|
+
#
|
|
10
|
+
# Trigger this request with: Ctrl + Shift + -> or Ctrl + Shift + <-
|
|
11
|
+
#
|
|
12
|
+
# # Example
|
|
13
|
+
#
|
|
14
|
+
# ```ruby
|
|
15
|
+
# def foo # --> The next selection range encompasses the entire method definition.
|
|
16
|
+
# puts "Hello, world!" # --> Cursor is on this line
|
|
17
|
+
# end
|
|
18
|
+
# ```
|
|
19
|
+
class SelectionRanges < BaseRequest
|
|
20
|
+
NODES_THAT_CAN_BE_PARENTS = [
|
|
21
|
+
SyntaxTree::Assign,
|
|
22
|
+
SyntaxTree::ArrayLiteral,
|
|
23
|
+
SyntaxTree::Begin,
|
|
24
|
+
SyntaxTree::BraceBlock,
|
|
25
|
+
SyntaxTree::Call,
|
|
26
|
+
SyntaxTree::Case,
|
|
27
|
+
SyntaxTree::ClassDeclaration,
|
|
28
|
+
SyntaxTree::Command,
|
|
29
|
+
SyntaxTree::Def,
|
|
30
|
+
SyntaxTree::Defs,
|
|
31
|
+
SyntaxTree::DoBlock,
|
|
32
|
+
SyntaxTree::Elsif,
|
|
33
|
+
SyntaxTree::Else,
|
|
34
|
+
SyntaxTree::EmbDoc,
|
|
35
|
+
SyntaxTree::Ensure,
|
|
36
|
+
SyntaxTree::FCall,
|
|
37
|
+
SyntaxTree::For,
|
|
38
|
+
SyntaxTree::HashLiteral,
|
|
39
|
+
SyntaxTree::Heredoc,
|
|
40
|
+
SyntaxTree::HeredocBeg,
|
|
41
|
+
SyntaxTree::HshPtn,
|
|
42
|
+
SyntaxTree::If,
|
|
43
|
+
SyntaxTree::In,
|
|
44
|
+
SyntaxTree::Lambda,
|
|
45
|
+
SyntaxTree::MethodAddBlock,
|
|
46
|
+
SyntaxTree::ModuleDeclaration,
|
|
47
|
+
SyntaxTree::Params,
|
|
48
|
+
SyntaxTree::Rescue,
|
|
49
|
+
SyntaxTree::RescueEx,
|
|
50
|
+
SyntaxTree::StringConcat,
|
|
51
|
+
SyntaxTree::StringLiteral,
|
|
52
|
+
SyntaxTree::Unless,
|
|
53
|
+
SyntaxTree::Until,
|
|
54
|
+
SyntaxTree::VCall,
|
|
55
|
+
SyntaxTree::When,
|
|
56
|
+
SyntaxTree::While,
|
|
57
|
+
].freeze
|
|
58
|
+
|
|
59
|
+
def initialize(document)
|
|
60
|
+
super(document)
|
|
61
|
+
|
|
62
|
+
@ranges = []
|
|
63
|
+
@stack = []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run
|
|
67
|
+
visit(@document.tree)
|
|
68
|
+
@ranges.reverse!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def visit(node)
|
|
74
|
+
return if node.nil?
|
|
75
|
+
|
|
76
|
+
range = create_selection_range(node.location, @stack.last)
|
|
77
|
+
|
|
78
|
+
@ranges << range
|
|
79
|
+
return if node.child_nodes.empty?
|
|
80
|
+
|
|
81
|
+
@stack << range if NODES_THAT_CAN_BE_PARENTS.include?(node.class)
|
|
82
|
+
visit_all(node.child_nodes)
|
|
83
|
+
@stack.pop if NODES_THAT_CAN_BE_PARENTS.include?(node.class)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def create_selection_range(location, parent = nil)
|
|
87
|
+
RubyLsp::Requests::Support::SelectionRange.new(
|
|
88
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
|
89
|
+
start: LanguageServer::Protocol::Interface::Position.new(
|
|
90
|
+
line: location.start_line - 1,
|
|
91
|
+
character: location.start_column,
|
|
92
|
+
),
|
|
93
|
+
end: LanguageServer::Protocol::Interface::Position.new(
|
|
94
|
+
line: location.end_line - 1,
|
|
95
|
+
character: location.end_column,
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
parent: parent
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|