ruby-lsp 0.0.1 → 0.0.2
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 +23 -0
- data/.gitignore +9 -12
- data/.rubocop.yml +4 -2
- data/.vscode/settings.json +5 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +24 -13
- data/README.md +12 -2
- data/VERSION +1 -1
- data/bin/test +7 -1
- data/dev.yml +4 -7
- data/exe/ruby-lsp +3 -2
- data/lib/ruby-lsp.rb +2 -1
- data/lib/ruby_lsp/cli.rb +72 -0
- data/lib/ruby_lsp/document.rb +27 -0
- data/lib/ruby_lsp/handler.rb +133 -0
- data/lib/ruby_lsp/requests/base_request.rb +21 -0
- data/lib/ruby_lsp/requests/code_actions.rb +25 -0
- data/lib/ruby_lsp/requests/diagnostics.rb +97 -0
- data/lib/ruby_lsp/requests/document_symbol.rb +204 -0
- data/lib/ruby_lsp/requests/folding_ranges.rb +203 -0
- data/lib/ruby_lsp/requests/formatting.rb +40 -0
- data/lib/ruby_lsp/requests/rubocop_request.rb +47 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +116 -0
- data/lib/ruby_lsp/requests.rb +14 -0
- data/lib/ruby_lsp/store.rb +39 -0
- data/ruby-lsp.gemspec +4 -1
- data/{shipit.yml → shipit.production.yml} +0 -0
- metadata +50 -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,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Requests
|
5
|
+
class Diagnostics < RuboCopRequest
|
6
|
+
RUBOCOP_TO_LSP_SEVERITY = {
|
7
|
+
convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
8
|
+
info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
9
|
+
refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
|
10
|
+
warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
|
11
|
+
error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
12
|
+
fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def run
|
16
|
+
super
|
17
|
+
|
18
|
+
@diagnostics
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_finished(_file, offenses)
|
22
|
+
@diagnostics = offenses.map { |offense| Diagnostic.new(offense, @uri) }
|
23
|
+
end
|
24
|
+
|
25
|
+
class Diagnostic
|
26
|
+
attr_reader :replacements
|
27
|
+
|
28
|
+
def initialize(offense, uri)
|
29
|
+
@offense = offense
|
30
|
+
@uri = uri
|
31
|
+
@replacements = offense.correctable? ? offense_replacements : []
|
32
|
+
end
|
33
|
+
|
34
|
+
def correctable?
|
35
|
+
@offense.correctable?
|
36
|
+
end
|
37
|
+
|
38
|
+
def in_range?(range)
|
39
|
+
range.cover?(@offense.line - 1)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_lsp_code_action
|
43
|
+
LanguageServer::Protocol::Interface::CodeAction.new(
|
44
|
+
title: "Autocorrect #{@offense.cop_name}",
|
45
|
+
kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
|
46
|
+
edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
|
47
|
+
document_changes: [
|
48
|
+
LanguageServer::Protocol::Interface::TextDocumentEdit.new(
|
49
|
+
text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
|
50
|
+
uri: @uri,
|
51
|
+
version: nil
|
52
|
+
),
|
53
|
+
edits: @replacements
|
54
|
+
),
|
55
|
+
]
|
56
|
+
),
|
57
|
+
is_preferred: true,
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_lsp_diagnostic
|
62
|
+
LanguageServer::Protocol::Interface::Diagnostic.new(
|
63
|
+
message: @offense.message,
|
64
|
+
source: "RuboCop",
|
65
|
+
code: @offense.cop_name,
|
66
|
+
severity: RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name],
|
67
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
68
|
+
start: LanguageServer::Protocol::Interface::Position.new(
|
69
|
+
line: @offense.line - 1,
|
70
|
+
character: @offense.column
|
71
|
+
),
|
72
|
+
end: LanguageServer::Protocol::Interface::Position.new(
|
73
|
+
line: @offense.last_line - 1,
|
74
|
+
character: @offense.last_column
|
75
|
+
)
|
76
|
+
)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def offense_replacements
|
83
|
+
@offense.corrector.as_replacements.map do |range, replacement|
|
84
|
+
LanguageServer::Protocol::Interface::TextEdit.new(
|
85
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
86
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
|
87
|
+
end: LanguageServer::Protocol::Interface::Position.new(line: range.last_line - 1,
|
88
|
+
character: range.last_column)
|
89
|
+
),
|
90
|
+
new_text: replacement
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Requests
|
5
|
+
class DocumentSymbol < BaseRequest
|
6
|
+
SYMBOL_KIND = {
|
7
|
+
file: 1,
|
8
|
+
module: 2,
|
9
|
+
namespace: 3,
|
10
|
+
package: 4,
|
11
|
+
class: 5,
|
12
|
+
method: 6,
|
13
|
+
property: 7,
|
14
|
+
field: 8,
|
15
|
+
constructor: 9,
|
16
|
+
enum: 10,
|
17
|
+
interface: 11,
|
18
|
+
function: 12,
|
19
|
+
variable: 13,
|
20
|
+
constant: 14,
|
21
|
+
string: 15,
|
22
|
+
number: 16,
|
23
|
+
boolean: 17,
|
24
|
+
array: 18,
|
25
|
+
object: 19,
|
26
|
+
key: 20,
|
27
|
+
null: 21,
|
28
|
+
enummember: 22,
|
29
|
+
struct: 23,
|
30
|
+
event: 24,
|
31
|
+
operator: 25,
|
32
|
+
typeparameter: 26,
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
ATTR_ACCESSORS = ["attr_reader", "attr_writer", "attr_accessor"].freeze
|
36
|
+
|
37
|
+
class SymbolHierarchyRoot
|
38
|
+
attr_reader :children
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@children = []
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(document)
|
46
|
+
super
|
47
|
+
|
48
|
+
@root = SymbolHierarchyRoot.new
|
49
|
+
@stack = [@root]
|
50
|
+
end
|
51
|
+
|
52
|
+
def run
|
53
|
+
visit(@document.tree)
|
54
|
+
@root.children
|
55
|
+
end
|
56
|
+
|
57
|
+
def visit_class(node)
|
58
|
+
symbol = create_document_symbol(
|
59
|
+
name: node.constant.constant.value,
|
60
|
+
kind: :class,
|
61
|
+
range_node: node,
|
62
|
+
selection_range_node: node.constant
|
63
|
+
)
|
64
|
+
|
65
|
+
@stack << symbol
|
66
|
+
visit(node.bodystmt)
|
67
|
+
@stack.pop
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_command(node)
|
71
|
+
return unless ATTR_ACCESSORS.include?(node.message.value)
|
72
|
+
|
73
|
+
node.arguments.parts.each do |argument|
|
74
|
+
next unless argument.is_a?(SyntaxTree::SymbolLiteral)
|
75
|
+
|
76
|
+
create_document_symbol(
|
77
|
+
name: argument.value.value,
|
78
|
+
kind: :field,
|
79
|
+
range_node: argument,
|
80
|
+
selection_range_node: argument.value
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_const_path_field(node)
|
86
|
+
create_document_symbol(
|
87
|
+
name: node.constant.value,
|
88
|
+
kind: :constant,
|
89
|
+
range_node: node,
|
90
|
+
selection_range_node: node.constant
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def visit_def(node)
|
95
|
+
name = node.name.value
|
96
|
+
|
97
|
+
symbol = create_document_symbol(
|
98
|
+
name: name,
|
99
|
+
kind: name == "initialize" ? :constructor : :method,
|
100
|
+
range_node: node,
|
101
|
+
selection_range_node: node.name
|
102
|
+
)
|
103
|
+
|
104
|
+
@stack << symbol
|
105
|
+
visit(node.bodystmt)
|
106
|
+
@stack.pop
|
107
|
+
end
|
108
|
+
|
109
|
+
def visit_def_endless(node)
|
110
|
+
name = node.name.value
|
111
|
+
|
112
|
+
symbol = create_document_symbol(
|
113
|
+
name: name,
|
114
|
+
kind: name == "initialize" ? :constructor : :method,
|
115
|
+
range_node: node,
|
116
|
+
selection_range_node: node.name
|
117
|
+
)
|
118
|
+
|
119
|
+
@stack << symbol
|
120
|
+
visit(node.statement)
|
121
|
+
@stack.pop
|
122
|
+
end
|
123
|
+
|
124
|
+
def visit_defs(node)
|
125
|
+
symbol = create_document_symbol(
|
126
|
+
name: "self.#{node.name.value}",
|
127
|
+
kind: :method,
|
128
|
+
range_node: node,
|
129
|
+
selection_range_node: node.name
|
130
|
+
)
|
131
|
+
|
132
|
+
@stack << symbol
|
133
|
+
visit(node.bodystmt)
|
134
|
+
@stack.pop
|
135
|
+
end
|
136
|
+
|
137
|
+
def visit_module(node)
|
138
|
+
symbol = create_document_symbol(
|
139
|
+
name: node.constant.constant.value,
|
140
|
+
kind: :module,
|
141
|
+
range_node: node,
|
142
|
+
selection_range_node: node.constant
|
143
|
+
)
|
144
|
+
|
145
|
+
@stack << symbol
|
146
|
+
visit(node.bodystmt)
|
147
|
+
@stack.pop
|
148
|
+
end
|
149
|
+
|
150
|
+
def visit_top_const_field(node)
|
151
|
+
create_document_symbol(
|
152
|
+
name: node.constant.value,
|
153
|
+
kind: :constant,
|
154
|
+
range_node: node,
|
155
|
+
selection_range_node: node.constant
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
def visit_var_field(node)
|
160
|
+
kind = case node.value
|
161
|
+
when SyntaxTree::Const
|
162
|
+
:constant
|
163
|
+
when SyntaxTree::CVar, SyntaxTree::IVar
|
164
|
+
:variable
|
165
|
+
else
|
166
|
+
return
|
167
|
+
end
|
168
|
+
|
169
|
+
create_document_symbol(
|
170
|
+
name: node.value.value,
|
171
|
+
kind: kind,
|
172
|
+
range_node: node,
|
173
|
+
selection_range_node: node.value
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
|
180
|
+
symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
|
181
|
+
name: name,
|
182
|
+
kind: SYMBOL_KIND[kind],
|
183
|
+
range: range_from_syntax_tree_node(range_node),
|
184
|
+
selection_range: range_from_syntax_tree_node(selection_range_node),
|
185
|
+
children: [],
|
186
|
+
)
|
187
|
+
|
188
|
+
@stack.last.children << symbol
|
189
|
+
|
190
|
+
symbol
|
191
|
+
end
|
192
|
+
|
193
|
+
def range_from_syntax_tree_node(node)
|
194
|
+
loc = node.location
|
195
|
+
|
196
|
+
LanguageServer::Protocol::Interface::Range.new(
|
197
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: loc.start_line - 1,
|
198
|
+
character: loc.start_column),
|
199
|
+
end: LanguageServer::Protocol::Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Requests
|
5
|
+
class FoldingRanges < BaseRequest
|
6
|
+
SIMPLE_FOLDABLES = [
|
7
|
+
SyntaxTree::ArrayLiteral,
|
8
|
+
SyntaxTree::BraceBlock,
|
9
|
+
SyntaxTree::Case,
|
10
|
+
SyntaxTree::ClassDeclaration,
|
11
|
+
SyntaxTree::Command,
|
12
|
+
SyntaxTree::DoBlock,
|
13
|
+
SyntaxTree::FCall,
|
14
|
+
SyntaxTree::For,
|
15
|
+
SyntaxTree::HashLiteral,
|
16
|
+
SyntaxTree::Heredoc,
|
17
|
+
SyntaxTree::If,
|
18
|
+
SyntaxTree::ModuleDeclaration,
|
19
|
+
SyntaxTree::SClass,
|
20
|
+
SyntaxTree::Unless,
|
21
|
+
SyntaxTree::Until,
|
22
|
+
SyntaxTree::While,
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
NODES_WITH_STATEMENTS = [
|
26
|
+
SyntaxTree::Else,
|
27
|
+
SyntaxTree::Elsif,
|
28
|
+
SyntaxTree::Ensure,
|
29
|
+
SyntaxTree::In,
|
30
|
+
SyntaxTree::Rescue,
|
31
|
+
SyntaxTree::When,
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
def initialize(document)
|
35
|
+
super
|
36
|
+
|
37
|
+
@ranges = []
|
38
|
+
@partial_range = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
visit(@document.tree)
|
43
|
+
emit_partial_range
|
44
|
+
@ranges
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def visit(node)
|
50
|
+
return unless handle_partial_range(node)
|
51
|
+
|
52
|
+
case node
|
53
|
+
when *SIMPLE_FOLDABLES
|
54
|
+
add_node_range(node)
|
55
|
+
when *NODES_WITH_STATEMENTS
|
56
|
+
add_statements_range(node, node.statements)
|
57
|
+
when SyntaxTree::Begin
|
58
|
+
add_statements_range(node, node.bodystmt.statements)
|
59
|
+
when SyntaxTree::Call, SyntaxTree::CommandCall
|
60
|
+
add_call_range(node)
|
61
|
+
return
|
62
|
+
when SyntaxTree::Def, SyntaxTree::Defs
|
63
|
+
add_def_range(node)
|
64
|
+
when SyntaxTree::StringConcat
|
65
|
+
add_string_concat(node)
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
class PartialRange
|
73
|
+
attr_reader :kind, :end_line
|
74
|
+
|
75
|
+
def self.from(node, kind)
|
76
|
+
new(node.location.start_line - 1, node.location.end_line - 1, kind)
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(start_line, end_line, kind)
|
80
|
+
@start_line = start_line
|
81
|
+
@end_line = end_line
|
82
|
+
@kind = kind
|
83
|
+
end
|
84
|
+
|
85
|
+
def extend_to(node)
|
86
|
+
@end_line = node.location.end_line - 1
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_section?(node)
|
91
|
+
node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_range
|
95
|
+
LanguageServer::Protocol::Interface::FoldingRange.new(
|
96
|
+
start_line: @start_line,
|
97
|
+
end_line: @end_line,
|
98
|
+
kind: @kind
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_partial_range(node)
|
104
|
+
kind = partial_range_kind(node)
|
105
|
+
|
106
|
+
if kind.nil?
|
107
|
+
emit_partial_range
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
@partial_range = if @partial_range.nil?
|
112
|
+
PartialRange.from(node, kind)
|
113
|
+
elsif @partial_range.kind != kind || @partial_range.new_section?(node)
|
114
|
+
emit_partial_range
|
115
|
+
PartialRange.from(node, kind)
|
116
|
+
else
|
117
|
+
@partial_range.extend_to(node)
|
118
|
+
end
|
119
|
+
|
120
|
+
false
|
121
|
+
end
|
122
|
+
|
123
|
+
def partial_range_kind(node)
|
124
|
+
case node
|
125
|
+
when SyntaxTree::Comment
|
126
|
+
"comment"
|
127
|
+
when SyntaxTree::Command
|
128
|
+
if node.message.value == "require" || node.message.value == "require_relative"
|
129
|
+
"imports"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def emit_partial_range
|
135
|
+
return if @partial_range.nil?
|
136
|
+
|
137
|
+
@ranges << @partial_range.to_range
|
138
|
+
@partial_range = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_call_range(node)
|
142
|
+
receiver = node.receiver
|
143
|
+
loop do
|
144
|
+
case receiver
|
145
|
+
when SyntaxTree::Call
|
146
|
+
visit(receiver.arguments)
|
147
|
+
receiver = receiver.receiver
|
148
|
+
when SyntaxTree::MethodAddBlock
|
149
|
+
visit(receiver.block)
|
150
|
+
receiver = receiver.call.receiver
|
151
|
+
else
|
152
|
+
break
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
add_lines_range(receiver.location.start_line, node.location.end_line)
|
157
|
+
|
158
|
+
visit(node.arguments)
|
159
|
+
end
|
160
|
+
|
161
|
+
def add_def_range(node)
|
162
|
+
params_location = node.params.location
|
163
|
+
|
164
|
+
if params_location.start_line < params_location.end_line
|
165
|
+
add_lines_range(params_location.end_line, node.location.end_line)
|
166
|
+
else
|
167
|
+
add_node_range(node)
|
168
|
+
end
|
169
|
+
|
170
|
+
visit(node.bodystmt.statements)
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_statements_range(node, statements)
|
174
|
+
add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
|
175
|
+
end
|
176
|
+
|
177
|
+
def add_string_concat(node)
|
178
|
+
left = node.left
|
179
|
+
left = left.left while left.is_a?(SyntaxTree::StringConcat)
|
180
|
+
|
181
|
+
add_lines_range(left.location.start_line, node.right.location.end_line)
|
182
|
+
end
|
183
|
+
|
184
|
+
def add_node_range(node)
|
185
|
+
add_location_range(node.location)
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_location_range(location)
|
189
|
+
add_lines_range(location.start_line, location.end_line)
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_lines_range(start_line, end_line)
|
193
|
+
return if start_line >= end_line
|
194
|
+
|
195
|
+
@ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
|
196
|
+
start_line: start_line - 1,
|
197
|
+
end_line: end_line - 1,
|
198
|
+
kind: "region"
|
199
|
+
)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Requests
|
5
|
+
class Formatting < RuboCopRequest
|
6
|
+
RUBOCOP_FLAGS = (COMMON_RUBOCOP_FLAGS + ["--auto-correct"]).freeze
|
7
|
+
|
8
|
+
def initialize(uri, document)
|
9
|
+
super
|
10
|
+
@formatted_text = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
super
|
15
|
+
|
16
|
+
@formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
|
17
|
+
return unless @formatted_text
|
18
|
+
|
19
|
+
[
|
20
|
+
LanguageServer::Protocol::Interface::TextEdit.new(
|
21
|
+
range: LanguageServer::Protocol::Interface::Range.new(
|
22
|
+
start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
|
23
|
+
end: LanguageServer::Protocol::Interface::Position.new(
|
24
|
+
line: text.size,
|
25
|
+
character: text.size
|
26
|
+
)
|
27
|
+
),
|
28
|
+
new_text: @formatted_text
|
29
|
+
),
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def rubocop_flags
|
36
|
+
RUBOCOP_FLAGS
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
require "cgi"
|
5
|
+
|
6
|
+
module RubyLsp
|
7
|
+
module Requests
|
8
|
+
class RuboCopRequest < RuboCop::Runner
|
9
|
+
COMMON_RUBOCOP_FLAGS = [
|
10
|
+
"--stderr", # Print any output to stderr so that our stdout does not get polluted
|
11
|
+
"--format",
|
12
|
+
"RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
attr_reader :file, :text
|
16
|
+
|
17
|
+
def self.run(uri, document)
|
18
|
+
new(uri, document).run
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(uri, document)
|
22
|
+
@file = CGI.unescape(URI.parse(uri).path)
|
23
|
+
@text = document.source
|
24
|
+
@uri = uri
|
25
|
+
|
26
|
+
super(
|
27
|
+
::RuboCop::Options.new.parse(rubocop_flags).first,
|
28
|
+
::RuboCop::ConfigStore.new
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
# We communicate with Rubocop via stdin
|
34
|
+
@options[:stdin] = text
|
35
|
+
|
36
|
+
# Invoke the actual run method with just this file in `paths`
|
37
|
+
super([file])
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def rubocop_flags
|
43
|
+
COMMON_RUBOCOP_FLAGS
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Requests
|
5
|
+
class SemanticHighlighting < BaseRequest
|
6
|
+
TOKEN_TYPES = [
|
7
|
+
:variable,
|
8
|
+
:method,
|
9
|
+
].freeze
|
10
|
+
TOKEN_MODIFIERS = [].freeze
|
11
|
+
|
12
|
+
def initialize(document)
|
13
|
+
super
|
14
|
+
|
15
|
+
@tokens = []
|
16
|
+
@tree = document.tree
|
17
|
+
@current_row = 0
|
18
|
+
@current_column = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
visit(@tree)
|
23
|
+
LanguageServer::Protocol::Interface::SemanticTokens.new(data: @tokens)
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_m_assign(node)
|
27
|
+
node.target.parts.each do |var_ref|
|
28
|
+
add_token(var_ref.value.location, :variable)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_var_field(node)
|
33
|
+
case node.value
|
34
|
+
when SyntaxTree::Ident
|
35
|
+
add_token(node.value.location, :variable)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_var_ref(node)
|
40
|
+
case node.value
|
41
|
+
when SyntaxTree::Ident
|
42
|
+
add_token(node.value.location, :variable)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_a_ref_field(node)
|
47
|
+
add_token(node.collection.value.location, :variable)
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_call(node)
|
51
|
+
visit(node.receiver)
|
52
|
+
add_token(node.message.location, :method)
|
53
|
+
visit(node.arguments)
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_command(node)
|
57
|
+
add_token(node.message.location, :method)
|
58
|
+
visit(node.arguments)
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_command_call(node)
|
62
|
+
visit(node.receiver)
|
63
|
+
add_token(node.message.location, :method)
|
64
|
+
visit(node.arguments)
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_fcall(node)
|
68
|
+
add_token(node.value.location, :method)
|
69
|
+
visit(node.arguments)
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_vcall(node)
|
73
|
+
add_token(node.value.location, :method)
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_token(location, classification)
|
77
|
+
length = location.end_char - location.start_char
|
78
|
+
|
79
|
+
compute_delta(location) do |delta_line, delta_column|
|
80
|
+
@tokens.push(delta_line, delta_column, length, TOKEN_TYPES.index(classification), 0)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# The delta array is computed according to the LSP specification:
|
85
|
+
# > The protocol for the token format relative uses relative
|
86
|
+
# > positions, because most tokens remain stable relative to
|
87
|
+
# > each other when edits are made in a file. This simplifies
|
88
|
+
# > the computation of a delta if a server supports it. So each
|
89
|
+
# > token is represented using 5 integers.
|
90
|
+
|
91
|
+
# For more information on how each number is calculated, read:
|
92
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
93
|
+
def compute_delta(location)
|
94
|
+
row = location.start_line - 1
|
95
|
+
column = location.start_column
|
96
|
+
|
97
|
+
if row < @current_row
|
98
|
+
raise InvalidTokenRowError, "Invalid token row detected: " \
|
99
|
+
"Ensure tokens are added in the expected order."
|
100
|
+
end
|
101
|
+
|
102
|
+
delta_line = row - @current_row
|
103
|
+
|
104
|
+
delta_column = column
|
105
|
+
delta_column -= @current_column if delta_line == 0
|
106
|
+
|
107
|
+
yield delta_line, delta_column
|
108
|
+
|
109
|
+
@current_row = row
|
110
|
+
@current_column = column
|
111
|
+
end
|
112
|
+
|
113
|
+
class InvalidTokenRowError < StandardError; end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|