ruby_language_server 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.txt +67 -0
  3. data/FAQ_ROADMAP.md +30 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +111 -0
  6. data/Guardfile +36 -0
  7. data/LICENSE +21 -0
  8. data/Makefile +35 -0
  9. data/README.md +55 -0
  10. data/Rakefile +17 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/exe/ruby_language_server +9 -0
  14. data/lib/ruby_language_server/code_file.rb +129 -0
  15. data/lib/ruby_language_server/completion.rb +87 -0
  16. data/lib/ruby_language_server/gem_installer.rb +24 -0
  17. data/lib/ruby_language_server/good_cop.rb +125 -0
  18. data/lib/ruby_language_server/io.rb +130 -0
  19. data/lib/ruby_language_server/line_context.rb +39 -0
  20. data/lib/ruby_language_server/location.rb +29 -0
  21. data/lib/ruby_language_server/logger.rb +14 -0
  22. data/lib/ruby_language_server/project_manager.rb +231 -0
  23. data/lib/ruby_language_server/scope_data/base.rb +23 -0
  24. data/lib/ruby_language_server/scope_data/scope.rb +104 -0
  25. data/lib/ruby_language_server/scope_data/variable.rb +25 -0
  26. data/lib/ruby_language_server/scope_parser.rb +334 -0
  27. data/lib/ruby_language_server/scope_parser_commands/rails_commands.rb +29 -0
  28. data/lib/ruby_language_server/scope_parser_commands/rake_commands.rb +31 -0
  29. data/lib/ruby_language_server/scope_parser_commands/readme.txt +9 -0
  30. data/lib/ruby_language_server/scope_parser_commands/rspec_commands.rb +26 -0
  31. data/lib/ruby_language_server/scope_parser_commands/ruby_commands.rb +70 -0
  32. data/lib/ruby_language_server/server.rb +123 -0
  33. data/lib/ruby_language_server/version.rb +5 -0
  34. data/lib/ruby_language_server.rb +14 -0
  35. data/ruby_language_server.gemspec +56 -0
  36. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLanguageServer
4
+ VERSION = '0.2.0'
5
+ 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'