ruby_language_server 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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'
|