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.
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'