ruby_language_server 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.txt +67 -0
- data/FAQ_ROADMAP.md +30 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +111 -0
- data/Guardfile +36 -0
- data/LICENSE +21 -0
- data/Makefile +35 -0
- data/README.md +55 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/ruby_language_server +9 -0
- data/lib/ruby_language_server/code_file.rb +129 -0
- data/lib/ruby_language_server/completion.rb +87 -0
- data/lib/ruby_language_server/gem_installer.rb +24 -0
- data/lib/ruby_language_server/good_cop.rb +125 -0
- data/lib/ruby_language_server/io.rb +130 -0
- data/lib/ruby_language_server/line_context.rb +39 -0
- data/lib/ruby_language_server/location.rb +29 -0
- data/lib/ruby_language_server/logger.rb +14 -0
- data/lib/ruby_language_server/project_manager.rb +231 -0
- data/lib/ruby_language_server/scope_data/base.rb +23 -0
- data/lib/ruby_language_server/scope_data/scope.rb +104 -0
- data/lib/ruby_language_server/scope_data/variable.rb +25 -0
- data/lib/ruby_language_server/scope_parser.rb +334 -0
- data/lib/ruby_language_server/scope_parser_commands/rails_commands.rb +29 -0
- data/lib/ruby_language_server/scope_parser_commands/rake_commands.rb +31 -0
- data/lib/ruby_language_server/scope_parser_commands/readme.txt +9 -0
- data/lib/ruby_language_server/scope_parser_commands/rspec_commands.rb +26 -0
- data/lib/ruby_language_server/scope_parser_commands/ruby_commands.rb +70 -0
- data/lib/ruby_language_server/server.rb +123 -0
- data/lib/ruby_language_server/version.rb +5 -0
- data/lib/ruby_language_server.rb +14 -0
- data/ruby_language_server.gemspec +56 -0
- metadata +293 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLanguageServer
|
4
|
+
module ScopeData
|
5
|
+
class Variable < Base
|
6
|
+
attr_accessor :line # line
|
7
|
+
attr_accessor :column # column
|
8
|
+
attr_accessor :name # name
|
9
|
+
attr_accessor :full_name # Module::Class name
|
10
|
+
attr_accessor :type # type
|
11
|
+
|
12
|
+
def initialize(scope, name, line = 1, column = 1, type = TYPE_VARIABLE)
|
13
|
+
@name = name
|
14
|
+
@line = line
|
15
|
+
@column = column
|
16
|
+
@full_name = [scope.full_name, @name].join(JoinHash[TYPE_VARIABLE])
|
17
|
+
@type = type
|
18
|
+
end
|
19
|
+
|
20
|
+
def constant?
|
21
|
+
!@name&.match(/^[A-Z]/).nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
4
|
+
require_relative 'scope_parser_commands/rake_commands'
|
5
|
+
require_relative 'scope_parser_commands/rspec_commands'
|
6
|
+
require_relative 'scope_parser_commands/ruby_commands'
|
7
|
+
require_relative 'scope_parser_commands/rails_commands'
|
8
|
+
|
9
|
+
module RubyLanguageServer
|
10
|
+
# This class is responsible for processing the generated sexp from the ScopeParser below.
|
11
|
+
# It builds scopes that amount to heirarchical arrays with information about what
|
12
|
+
# classes, methods, variables, etc - are in each scope.
|
13
|
+
class SEXPProcessor
|
14
|
+
include ScopeParserCommands::RakeCommands
|
15
|
+
include ScopeParserCommands::RspecCommands
|
16
|
+
include ScopeParserCommands::RailsCommands
|
17
|
+
include ScopeParserCommands::RubyCommands
|
18
|
+
attr_reader :sexp
|
19
|
+
attr_reader :lines
|
20
|
+
attr_reader :current_scope
|
21
|
+
|
22
|
+
def initialize(sexp, lines = 1)
|
23
|
+
@sexp = sexp
|
24
|
+
@lines = lines
|
25
|
+
end
|
26
|
+
|
27
|
+
def root_scope
|
28
|
+
return @root_scope unless @root_scope.nil?
|
29
|
+
|
30
|
+
@root_scope = new_root_scope
|
31
|
+
@current_scope = @root_scope
|
32
|
+
process(@sexp)
|
33
|
+
@root_scope
|
34
|
+
end
|
35
|
+
|
36
|
+
def process(sexp)
|
37
|
+
return if sexp.nil?
|
38
|
+
|
39
|
+
root, args, *rest = sexp
|
40
|
+
# RubyLanguageServer.logger.error("Doing #{[root, args, rest]}")
|
41
|
+
case root
|
42
|
+
when Array
|
43
|
+
sexp.each { |child| process(child) }
|
44
|
+
when Symbol
|
45
|
+
root = root.to_s.gsub(/^@+/, '')
|
46
|
+
method_name = "on_#{root}"
|
47
|
+
if respond_to? method_name
|
48
|
+
send(method_name, args, rest)
|
49
|
+
else
|
50
|
+
RubyLanguageServer.logger.debug("We don't have a #{method_name} with #{args}")
|
51
|
+
process(args)
|
52
|
+
end
|
53
|
+
when String
|
54
|
+
# We really don't do anything with it!
|
55
|
+
RubyLanguageServer.logger.debug("We don't do Strings like #{root} with #{args}")
|
56
|
+
when NilClass
|
57
|
+
process(args)
|
58
|
+
else
|
59
|
+
RubyLanguageServer.logger.warn("We don't respond to the likes of #{root} of class #{root.class}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_sclass(_args, rest)
|
64
|
+
process(rest)
|
65
|
+
end
|
66
|
+
|
67
|
+
# foo = bar -- bar is in the vcall. Pretty sure we don't want to remember this.
|
68
|
+
def on_vcall(_args, rest)
|
69
|
+
# Seriously - discard args. Maybe process rest?
|
70
|
+
process(rest)
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_program(args, _rest)
|
74
|
+
process(args)
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_var_field(args, rest)
|
78
|
+
(_, name, (line, column)) = args
|
79
|
+
return if name.nil?
|
80
|
+
|
81
|
+
if name.start_with?('@')
|
82
|
+
add_ivar(name, line, column)
|
83
|
+
else
|
84
|
+
add_variable(name, line, column)
|
85
|
+
end
|
86
|
+
process(rest)
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_bodystmt(args, _rest)
|
90
|
+
process(args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_module(args, rest)
|
94
|
+
add_scope(args.last, rest, ScopeData::Scope::TYPE_MODULE)
|
95
|
+
end
|
96
|
+
|
97
|
+
def on_class(args, rest)
|
98
|
+
add_scope(args.last, rest, ScopeData::Scope::TYPE_CLASS)
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_method_add_block(args, rest)
|
102
|
+
scope = @current_scope
|
103
|
+
process(args)
|
104
|
+
process(rest)
|
105
|
+
# add_scope(args, rest, ScopeData::Scope::TYPE_BLOCK)
|
106
|
+
unless @current_scope == scope
|
107
|
+
scope.bottom_line = [scope&.bottom_line, @current_scope.bottom_line].compact.max
|
108
|
+
pop_scope
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_do_block(args, rest)
|
113
|
+
((_, ((_, (_, (_, _name, (line, column))))))) = args
|
114
|
+
push_scope(ScopeData::Scope::TYPE_BLOCK, nil, line, column, false)
|
115
|
+
process(args)
|
116
|
+
process(rest)
|
117
|
+
pop_scope
|
118
|
+
end
|
119
|
+
|
120
|
+
# Used only to describe subclasses?
|
121
|
+
def on_var_ref(args, _)
|
122
|
+
# [:@const, "Bar", [13, 20]]
|
123
|
+
(_, name) = args
|
124
|
+
@current_scope.set_superclass_name(name)
|
125
|
+
end
|
126
|
+
|
127
|
+
def on_assign(args, rest)
|
128
|
+
process(args)
|
129
|
+
process(rest)
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_def(args, rest)
|
133
|
+
add_scope(args, rest, ScopeData::Scope::TYPE_METHOD)
|
134
|
+
end
|
135
|
+
|
136
|
+
# def self.something(par)...
|
137
|
+
# [:var_ref, [:@kw, "self", [28, 14]]], [[:@period, ".", [28, 18]], [:@ident, "something", [28, 19]], [:paren, [:params, [[:@ident, "par", [28, 23]]], nil, nil, nil, nil, nil, nil]], [:bodystmt, [[:assign, [:var_field, [:@ident, "pax", [29, 12]]], [:var_ref, [:@ident, "par", [29, 18]]]]], nil, nil, nil]]
|
138
|
+
def on_defs(args, rest)
|
139
|
+
on_def(rest[1], rest[2]) if args[1][1] == 'self' && rest[0][1] == '.'
|
140
|
+
end
|
141
|
+
|
142
|
+
# ident is something that gets processed at parameters to a function or block
|
143
|
+
def on_ident(name, ((line, column)))
|
144
|
+
add_variable(name, line, column)
|
145
|
+
end
|
146
|
+
|
147
|
+
def on_params(args, rest)
|
148
|
+
process(args)
|
149
|
+
process(rest)
|
150
|
+
end
|
151
|
+
|
152
|
+
# The on_command function idea is stolen from RipperTags https://github.com/tmm1/ripper-tags/blob/master/lib/ripper-tags/parser.rb
|
153
|
+
def on_command(args, rest)
|
154
|
+
# [:@ident, "public", [6, 8]]
|
155
|
+
(_, name, (line, _column)) = args
|
156
|
+
|
157
|
+
method_name = "on_#{name}_command"
|
158
|
+
if respond_to? method_name
|
159
|
+
return send(method_name, line, args, rest)
|
160
|
+
else
|
161
|
+
RubyLanguageServer.logger.debug("We don't have a #{method_name} with #{args}")
|
162
|
+
end
|
163
|
+
|
164
|
+
case name
|
165
|
+
when 'public', 'private', 'protected'
|
166
|
+
# FIXME: access control...
|
167
|
+
process(rest)
|
168
|
+
when 'delegate'
|
169
|
+
# on_delegate(*args[0][1..-1])
|
170
|
+
when 'def_delegator', 'def_instance_delegator'
|
171
|
+
# on_def_delegator(*args[0][1..-1])
|
172
|
+
when 'def_delegators', 'def_instance_delegators'
|
173
|
+
# on_def_delegators(*args[0][1..-1])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# The on_method_add_arg function is downright stolen from RipperTags https://github.com/tmm1/ripper-tags/blob/master/lib/ripper-tags/parser.rb
|
178
|
+
def on_method_add_arg(call, args)
|
179
|
+
call_name = call && call[0]
|
180
|
+
first_arg = args && args[0] == :args && args[1]
|
181
|
+
|
182
|
+
if call_name == :call && first_arg
|
183
|
+
if args.length == 2
|
184
|
+
# augment call if a single argument was used
|
185
|
+
call = call.dup
|
186
|
+
call[3] = args[1]
|
187
|
+
end
|
188
|
+
call
|
189
|
+
elsif call_name == :fcall && first_arg
|
190
|
+
name, line = call[1]
|
191
|
+
case name
|
192
|
+
when 'alias_method' # this is an fcall
|
193
|
+
[:alias, args[1][0], args[2][0], line] if args[1] && args[2]
|
194
|
+
when 'define_method' # this is an fcall
|
195
|
+
[:def, args[1][0], line]
|
196
|
+
when 'public_class_method', 'private_class_method', 'private', 'public', 'protected'
|
197
|
+
access = name.sub('_class_method', '')
|
198
|
+
|
199
|
+
if args[1][1] == 'self'
|
200
|
+
klass = 'self'
|
201
|
+
method_name = args[1][2]
|
202
|
+
else
|
203
|
+
klass = nil
|
204
|
+
method_name = args[1][1]
|
205
|
+
end
|
206
|
+
|
207
|
+
[:def_with_access, klass, method_name, access, line]
|
208
|
+
# when 'scope', 'named_scope'
|
209
|
+
# [:rails_def, :scope, args[1][0], line]
|
210
|
+
# when /^attr_(accessor|reader|writer)$/
|
211
|
+
# gen_reader = Regexp.last_match(1) != 'writer'
|
212
|
+
# gen_writer = Regexp.last_match(1) != 'reader'
|
213
|
+
# args[1..-1].each_with_object([]) do |arg, gen|
|
214
|
+
# gen << [:def, arg[0], line] if gen_reader
|
215
|
+
# gen << [:def, "#{arg[0]}=", line] if gen_writer
|
216
|
+
# end
|
217
|
+
# when 'has_many', 'has_and_belongs_to_many'
|
218
|
+
# a = args[1][0]
|
219
|
+
# kind = name.to_sym
|
220
|
+
# gen = []
|
221
|
+
# unless a.is_a?(Enumerable) && !a.is_a?(String)
|
222
|
+
# a = a.to_s
|
223
|
+
# gen << [:rails_def, kind, a, line]
|
224
|
+
# gen << [:rails_def, kind, "#{a}=", line]
|
225
|
+
# if (sing = a.chomp('s')) != a
|
226
|
+
# # poor man's singularize
|
227
|
+
# gen << [:rails_def, kind, "#{sing}_ids", line]
|
228
|
+
# gen << [:rails_def, kind, "#{sing}_ids=", line]
|
229
|
+
# end
|
230
|
+
# end
|
231
|
+
# gen
|
232
|
+
# when 'belongs_to', 'has_one'
|
233
|
+
# a = args[1][0]
|
234
|
+
# unless a.is_a?(Enumerable) && !a.is_a?(String)
|
235
|
+
# kind = name.to_sym
|
236
|
+
# %W[#{a} #{a}= build_#{a} create_#{a} create_#{a}!].inject([]) do |all, ident|
|
237
|
+
# all << [:rails_def, kind, ident, line]
|
238
|
+
# end
|
239
|
+
# end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def add_variable(name, line, column, scope = @current_scope)
|
247
|
+
new_variable = ScopeData::Variable.new(scope, name, line, column)
|
248
|
+
scope.variables << new_variable unless scope.has_variable_or_constant?(new_variable)
|
249
|
+
end
|
250
|
+
|
251
|
+
def add_ivar(name, line, column)
|
252
|
+
scope = @current_scope
|
253
|
+
unless scope == root_scope
|
254
|
+
ivar_scope_types = [ScopeData::Base::TYPE_CLASS, ScopeData::Base::TYPE_MODULE]
|
255
|
+
while !ivar_scope_types.include?(scope.type) && !scope.parent.nil?
|
256
|
+
scope = scope.parent
|
257
|
+
end
|
258
|
+
end
|
259
|
+
add_variable(name, line, column, scope)
|
260
|
+
end
|
261
|
+
|
262
|
+
def add_scope(args, rest, type)
|
263
|
+
(_, name, (line, column)) = args
|
264
|
+
push_scope(type, name, line, column)
|
265
|
+
process(rest)
|
266
|
+
pop_scope
|
267
|
+
end
|
268
|
+
|
269
|
+
def type_is_class_or_module(type)
|
270
|
+
[RubyLanguageServer::ScopeData::Base::TYPE_CLASS, RubyLanguageServer::ScopeData::Base::TYPE_MODULE].include?(type)
|
271
|
+
end
|
272
|
+
|
273
|
+
def push_scope(type, name, top_line, column, close_siblings = true)
|
274
|
+
close_sibling_scopes(top_line) if close_siblings
|
275
|
+
# The default root scope is Object. Which is fine if we're adding methods.
|
276
|
+
# But if we're adding a class, we don't care that it's in Object.
|
277
|
+
new_scope =
|
278
|
+
if (type_is_class_or_module(type) && (@current_scope == root_scope))
|
279
|
+
ScopeData::Scope.new(nil, type, name, top_line, column)
|
280
|
+
else
|
281
|
+
ScopeData::Scope.new(@current_scope, type, name, top_line, column)
|
282
|
+
end
|
283
|
+
new_scope.bottom_line = @lines
|
284
|
+
if new_scope.parent.nil? # was it a class or module at the root
|
285
|
+
root_scope.children << new_scope
|
286
|
+
else
|
287
|
+
@current_scope.children << new_scope
|
288
|
+
end
|
289
|
+
@current_scope = new_scope
|
290
|
+
end
|
291
|
+
|
292
|
+
# This is a very poor man's "end" handler because there is no end handler.
|
293
|
+
# The notion is that when you start the next scope, all the previous peers and unclosed descendents of the previous peer should be closed.
|
294
|
+
def close_sibling_scopes(line)
|
295
|
+
parent_scope = @current_scope.parent
|
296
|
+
unless parent_scope.nil?
|
297
|
+
last_sibling = parent_scope.children.last
|
298
|
+
until last_sibling.nil?
|
299
|
+
last_sibling.bottom_line = line - 1
|
300
|
+
last_sibling = last_sibling.children.last
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def pop_scope
|
306
|
+
@current_scope = @current_scope.parent || root_scope # in case we are leaving a root class/module
|
307
|
+
end
|
308
|
+
|
309
|
+
def new_root_scope
|
310
|
+
ScopeData::Scope.new.tap do |scope|
|
311
|
+
scope.type = ScopeData::Scope::TYPE_ROOT
|
312
|
+
scope.name = nil
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# This class builds on Ripper's sexp processor to add ruby and rails magic.
|
318
|
+
# Specifically it knows about things like alias, attr_*, has_one/many, etc.
|
319
|
+
# It adds the appropriate definitions for those magic words.
|
320
|
+
class ScopeParser < Ripper
|
321
|
+
attr_reader :root_scope
|
322
|
+
|
323
|
+
def initialize(text)
|
324
|
+
text ||= '' # empty is the same as nil - but it doesn't crash
|
325
|
+
begin
|
326
|
+
sexp = self.class.sexp(text)
|
327
|
+
rescue TypeError => exception
|
328
|
+
RubyLanguageServer.logger.error("Exception in sexp: #{exception} for text: #{text}")
|
329
|
+
end
|
330
|
+
processor = SEXPProcessor.new(sexp, text.length)
|
331
|
+
@root_scope = processor.root_scope
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLanguageServer
|
4
|
+
module ScopeParserCommands
|
5
|
+
module RailsCommands
|
6
|
+
def rails_add_reference(line, args, rest)
|
7
|
+
# args: [:@ident, "has_one", [2, 2]]
|
8
|
+
# rest: [[:args_add_block, [[:symbol_literal, [:symbol, [:@ident, "bar", [2, 11]]]]], false]]
|
9
|
+
|
10
|
+
# Looks like the first string is gonna be the name
|
11
|
+
(_, _, (_, column)) = args
|
12
|
+
name = rest.flatten.detect { |o| o.instance_of? String }
|
13
|
+
[name, "#{name}="].each do |method_name|
|
14
|
+
push_scope(RubyLanguageServer::ScopeData::Base::TYPE_METHOD, method_name, line, column)
|
15
|
+
process(rest)
|
16
|
+
pop_scope
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
alias on_has_one_command rails_add_reference
|
21
|
+
alias on_has_many_command rails_add_reference
|
22
|
+
alias on_belongs_to_command rails_add_reference
|
23
|
+
alias on_has_and_belongs_to_many_command rails_add_reference
|
24
|
+
alias on_scope_command rails_add_reference
|
25
|
+
alias on_named_scope_command rails_add_reference
|
26
|
+
# alias on_ xxx _command rails_add_reference
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLanguageServer
|
4
|
+
module ScopeParserCommands
|
5
|
+
module RakeCommands
|
6
|
+
def on_task_command(line, args, rest)
|
7
|
+
# OMG. Rake commands can have like any form.
|
8
|
+
# The most reliable way I can see to name them is to grab the string
|
9
|
+
# I *so* do not want to hear about it when it doesn't work.
|
10
|
+
name = rest.flatten.detect { |o| o.instance_of?(String) }
|
11
|
+
# add_scope(args, rest, ScopeData::Scope::TYPE_METHOD)
|
12
|
+
push_scope(ScopeData::Scope::TYPE_MODULE, name, line, 0, false)
|
13
|
+
process(args)
|
14
|
+
process(rest)
|
15
|
+
# We push a scope and don't pop it because we're called inside on_method_add_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_namespace_command(line, args, rest)
|
19
|
+
# OMG. Rake commands can have like any form.
|
20
|
+
# The most reliable way I can see to name them is to grab the string
|
21
|
+
# I *so* do not want to hear about it when it doesn't work.
|
22
|
+
name = rest.flatten.detect { |o| o.instance_of?(String) }
|
23
|
+
# add_scope(args, rest, ScopeData::Scope::TYPE_METHOD)
|
24
|
+
push_scope(ScopeData::Scope::TYPE_MODULE, name, line, 0, false)
|
25
|
+
process(args)
|
26
|
+
process(rest)
|
27
|
+
# We push a scope and don't pop it because we're called inside on_method_add_block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
There are various ruby and rails DSLs that are so common it makes sense to add
|
2
|
+
support for them. Things like
|
3
|
+
attr_reader :foo
|
4
|
+
it 'should do something' do ...
|
5
|
+
describe 'some thing' do ...
|
6
|
+
task something: ... do ...
|
7
|
+
|
8
|
+
Eventually maybe it will make sense to add these things using gems. For now,
|
9
|
+
just dump it in.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLanguageServer
|
4
|
+
module ScopeParserCommands
|
5
|
+
module RspecCommands
|
6
|
+
def on_describe_command(line, args, rest)
|
7
|
+
rspec_block_command('describe', line, args, rest)
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_it_command(line, args, rest)
|
11
|
+
rspec_block_command('it', line, args, rest)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def rspec_block_command(prefix, line, args, rest)
|
17
|
+
name = "#{prefix} "
|
18
|
+
name += rest.flatten.select { |part| part.instance_of?(String) }.join('::')
|
19
|
+
push_scope(ScopeData::Scope::TYPE_MODULE, name, line, 0, false)
|
20
|
+
process(args)
|
21
|
+
process(rest)
|
22
|
+
# We push a scope and don't pop it because we're called inside on_method_add_block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLanguageServer
|
4
|
+
module ScopeParserCommands
|
5
|
+
module RubyCommands
|
6
|
+
# when 'define_method', 'alias_method',
|
7
|
+
# 'public_class_method', 'private_class_method',
|
8
|
+
# # "public", "protected", "private",
|
9
|
+
# /^attr_(accessor|reader|writer)$/
|
10
|
+
# # on_method_add_arg([:fcall, name], args[0])
|
11
|
+
# when 'attr'
|
12
|
+
# # [[:args_add_block, [[:symbol_literal, [:symbol, [:@ident, "top", [3, 14]]]]], false]]
|
13
|
+
# ((_, ((_, (_, (_, name, (line, column))))))) = rest
|
14
|
+
# add_ivar("@#{name}", line, column)
|
15
|
+
# push_scope(ScopeData::Scope::TYPE_METHOD, name, line, column)
|
16
|
+
# pop_scope
|
17
|
+
# push_scope(ScopeData::Scope::TYPE_METHOD, "#{name}=", line, column)
|
18
|
+
# pop_scope
|
19
|
+
|
20
|
+
def on_attr_command(line, args, rest)
|
21
|
+
column = ruby_command_column(args)
|
22
|
+
names = ruby_command_names(rest)
|
23
|
+
ruby_command_add_attr(line, column, names, true, true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_attr_accessor_command(line, args, rest)
|
27
|
+
column = ruby_command_column(args)
|
28
|
+
names = ruby_command_names(rest)
|
29
|
+
ruby_command_add_attr(line, column, names, true, true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_attr_reader_command(line, args, rest)
|
33
|
+
column = ruby_command_column(args)
|
34
|
+
names = ruby_command_names(rest)
|
35
|
+
ruby_command_add_attr(line, column, names, true, false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_attr_writer_command(line, args, rest)
|
39
|
+
column = ruby_command_column(args)
|
40
|
+
names = ruby_command_names(rest)
|
41
|
+
ruby_command_add_attr(line, column, names, false, true)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def ruby_command_names(rest)
|
47
|
+
names = rest.flatten.select { |o| o.instance_of? String }
|
48
|
+
names
|
49
|
+
end
|
50
|
+
|
51
|
+
def ruby_command_column(args)
|
52
|
+
(_, _, (_, column)) = args
|
53
|
+
column
|
54
|
+
end
|
55
|
+
|
56
|
+
def ruby_command_add_attr(line, column, names, reader, writer)
|
57
|
+
names.each do |name|
|
58
|
+
if reader
|
59
|
+
push_scope(RubyLanguageServer::ScopeData::Base::TYPE_METHOD, name, line, column)
|
60
|
+
pop_scope
|
61
|
+
end
|
62
|
+
if writer
|
63
|
+
push_scope(RubyLanguageServer::ScopeData::Base::TYPE_METHOD, "#{name}=", line, column)
|
64
|
+
pop_scope
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# Deal with the various languageserver calls.
|
6
|
+
module RubyLanguageServer
|
7
|
+
class Server
|
8
|
+
attr_accessor :io
|
9
|
+
|
10
|
+
def on_initialize(params)
|
11
|
+
RubyLanguageServer.logger.info("on_initialize: #{params}")
|
12
|
+
root_path = params['rootPath']
|
13
|
+
@project_manager = ProjectManager.new(root_path)
|
14
|
+
gem_string = ENV.fetch('ADDITIONAL_GEMS') {}
|
15
|
+
gem_array = (gem_string.split(',').compact.map(&:strip).reject { |string| string == '' } if gem_string && !gem_string.empty?)
|
16
|
+
@project_manager.install_additional_gems(gem_array)
|
17
|
+
{
|
18
|
+
capabilities: {
|
19
|
+
textDocumentSync: 1,
|
20
|
+
hoverProvider: true,
|
21
|
+
signatureHelpProvider: {
|
22
|
+
triggerCharacters: ['(', ',']
|
23
|
+
},
|
24
|
+
definitionProvider: true,
|
25
|
+
referencesProvider: true,
|
26
|
+
documentSymbolProvider: true,
|
27
|
+
workspaceSymbolProvider: true,
|
28
|
+
xworkspaceReferencesProvider: true,
|
29
|
+
xdefinitionProvider: true,
|
30
|
+
xdependenciesProvider: true,
|
31
|
+
completionProvider: {
|
32
|
+
resolveProvider: true,
|
33
|
+
triggerCharacters: ['.', '::']
|
34
|
+
},
|
35
|
+
codeActionProvider: true,
|
36
|
+
renameProvider: true,
|
37
|
+
executeCommandProvider: {
|
38
|
+
commands: []
|
39
|
+
},
|
40
|
+
xpackagesProvider: true
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_workspace_didChangeWatchedFiles(params)
|
46
|
+
RubyLanguageServer.logger.debug('on_workspace_didChangeWatchedFiles')
|
47
|
+
RubyLanguageServer.logger.debug(params)
|
48
|
+
{}
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_textDocument_hover(params)
|
52
|
+
RubyLanguageServer.logger.debug('on_textDocument_hover')
|
53
|
+
RubyLanguageServer.logger.debug(params)
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_textDocument_documentSymbol(params)
|
58
|
+
RubyLanguageServer.logger.debug('on_textDocument_documentSymbol')
|
59
|
+
RubyLanguageServer.logger.debug(params)
|
60
|
+
uri = uri_from_params(params)
|
61
|
+
|
62
|
+
# {"kind":"module","line":4,"language":"Ruby","path":"(eval)","pattern":"module RubyLanguageServer","full_name":"RubyLanguageServer","name":"RubyLanguageServer"}
|
63
|
+
symbols = @project_manager.tags_for_uri(uri)
|
64
|
+
RubyLanguageServer.logger.debug("symbols #{symbols}")
|
65
|
+
symbols
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_textDocument_definition(params)
|
69
|
+
RubyLanguageServer.logger.debug("on_textDocument_definition #{params}")
|
70
|
+
uri = uri_from_params(params)
|
71
|
+
position = postition_from_params(params)
|
72
|
+
@project_manager.possible_definitions(uri, position)
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_diagnostics(uri, text)
|
76
|
+
hash = @project_manager.update_document_content(uri, text)
|
77
|
+
io.send_notification('textDocument/publishDiagnostics', uri: uri, diagnostics: hash)
|
78
|
+
end
|
79
|
+
|
80
|
+
def on_textDocument_didOpen(params)
|
81
|
+
textDocument = params['textDocument']
|
82
|
+
uri = textDocument['uri']
|
83
|
+
RubyLanguageServer.logger.debug("on_textDocument_didOpen #{uri}")
|
84
|
+
text = textDocument['text']
|
85
|
+
send_diagnostics(uri, text)
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_textDocument_didChange(params)
|
89
|
+
uri = uri_from_params(params)
|
90
|
+
RubyLanguageServer.logger.debug("on_textDocument_didChange #{uri}")
|
91
|
+
content_changes = params['contentChanges']
|
92
|
+
text = content_changes.first['text']
|
93
|
+
send_diagnostics(uri, text)
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_textDocument_completion(params)
|
97
|
+
RubyLanguageServer.logger.info("on_textDocument_completion #{params}")
|
98
|
+
uri = uri_from_params(params)
|
99
|
+
position = postition_from_params(params)
|
100
|
+
completions = @project_manager.completion_at(uri, position)
|
101
|
+
# RubyLanguageServer.logger.debug("completions: #{completions}")
|
102
|
+
completions
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_shutdown(_params)
|
106
|
+
RubyLanguageServer.logger.info('on_shutdown')
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def uri_from_params(params)
|
112
|
+
textDocument = params['textDocument']
|
113
|
+
textDocument['uri']
|
114
|
+
end
|
115
|
+
|
116
|
+
Position = Struct.new('Position', :line, :character)
|
117
|
+
|
118
|
+
def postition_from_params(params)
|
119
|
+
position = params['position']
|
120
|
+
Position.new((position['line']).to_i, position['character'].to_i)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ruby_language_server/logger' # do this first!
|
4
|
+
require_relative 'ruby_language_server/version'
|
5
|
+
require_relative 'ruby_language_server/gem_installer'
|
6
|
+
require_relative 'ruby_language_server/io'
|
7
|
+
require_relative 'ruby_language_server/location'
|
8
|
+
require_relative 'ruby_language_server/code_file'
|
9
|
+
require_relative 'ruby_language_server/scope_parser'
|
10
|
+
require_relative 'ruby_language_server/good_cop'
|
11
|
+
require_relative 'ruby_language_server/project_manager'
|
12
|
+
require_relative 'ruby_language_server/server'
|
13
|
+
require_relative 'ruby_language_server/line_context'
|
14
|
+
require_relative 'ruby_language_server/completion'
|