ruby-lsp 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|