ruby-lsp 0.9.4 → 0.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a300c17567fae60c2ed50c941d8ada19bf6b6fd951b621029880c03afd6d6f5
4
- data.tar.gz: 297d0d2064fbe7e97783e26fbfbdbb2191b6a32a43636e743864ad08665f49dd
3
+ metadata.gz: 36eafde3135250941805bf340b782dfab3002839b66ba4722d261edd7fe1356a
4
+ data.tar.gz: f8dbfe5eb36489223c21d2e816f91324d0928895632a4344c16ecc756c0cb279
5
5
  SHA512:
6
- metadata.gz: ee884d5c7f60e1d3fac9069a170967a761407a6470b7fe7442686bdbdfc1a4cbd626b7bcd4d368072bce0bab349df935bc0e7e9bdcad7b2fa238143afbc78075
7
- data.tar.gz: 317ebe547e3a9730c0161a2ad8b0dd130e819bf92a97c59525e5bc40e1e84bc95e0e610f87f144aebef957a622e141254938954bafd354e7f3d1249e022d2edb
6
+ metadata.gz: 24312d8a758e8388c02165d8f0ee4a8f8ccb93bb71efbd3ed3a4eeeadeb750373856cdcd3842f2cb1138ef7067c7c497260adeee347cc37f81e960a1febb7a44
7
+ data.tar.gz: c637ef4a50da10f0a7d81a15e9fc7af209e6a056642c93b315e0410fbfe732a8f42cd841ca3d70f66fac79d54474253a8febfe60f487c970632e64fd88ce103b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.4
1
+ 0.10.1
@@ -28,7 +28,7 @@ module RubyIndexer
28
28
  @files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
29
29
 
30
30
  # Holds all require paths for every indexed item so that we can provide autocomplete for requires
31
- @require_paths_tree = T.let(PrefixTree[String].new, PrefixTree[String])
31
+ @require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])
32
32
  end
33
33
 
34
34
  sig { params(indexable: IndexablePath).void }
@@ -73,7 +73,7 @@ module RubyIndexer
73
73
  @entries[fully_qualified_name.delete_prefix("::")]
74
74
  end
75
75
 
76
- sig { params(query: String).returns(T::Array[String]) }
76
+ sig { params(query: String).returns(T::Array[IndexablePath]) }
77
77
  def search_require_paths(query)
78
78
  @require_paths_tree.search(query)
79
79
  end
@@ -147,7 +147,7 @@ module RubyIndexer
147
147
  visitor.run
148
148
 
149
149
  require_path = indexable_path.require_path
150
- @require_paths_tree.insert(require_path, require_path) if require_path
150
+ @require_paths_tree.insert(require_path, indexable_path) if require_path
151
151
  rescue Errno::EISDIR
152
152
  # If `path` is a directory, just ignore it and continue indexing
153
153
  end
@@ -57,7 +57,7 @@ module RubyIndexer
57
57
  end
58
58
  def add_constant(node)
59
59
  comments = collect_comments(node)
60
- @index << Index::Entry::Constant.new(fully_qualify_name(node.name), @file_path, node.location, comments)
60
+ @index << Index::Entry::Constant.new(fully_qualify_name(node.name.to_s), @file_path, node.location, comments)
61
61
  end
62
62
 
63
63
  sig do
@@ -11,7 +11,7 @@ module RubyIndexer
11
11
  end
12
12
  RUBY
13
13
 
14
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
14
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
15
15
  end
16
16
 
17
17
  def test_class_with_statements
@@ -21,7 +21,7 @@ module RubyIndexer
21
21
  end
22
22
  RUBY
23
23
 
24
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-2")
24
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-3")
25
25
  end
26
26
 
27
27
  def test_colon_colon_class
@@ -30,7 +30,7 @@ module RubyIndexer
30
30
  end
31
31
  RUBY
32
32
 
33
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
33
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
34
34
  end
35
35
 
36
36
  def test_colon_colon_class_inside_class
@@ -41,8 +41,8 @@ module RubyIndexer
41
41
  end
42
42
  RUBY
43
43
 
44
- assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-2")
45
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-4")
44
+ assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-3")
45
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
46
46
  end
47
47
 
48
48
  def test_namespaced_class
@@ -51,7 +51,7 @@ module RubyIndexer
51
51
  end
52
52
  RUBY
53
53
 
54
- assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
54
+ assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
55
55
  end
56
56
 
57
57
  def test_dynamically_namespaced_class
@@ -69,7 +69,7 @@ module RubyIndexer
69
69
  end
70
70
  RUBY
71
71
 
72
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
72
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
73
73
  end
74
74
 
75
75
  def test_module_with_statements
@@ -79,7 +79,7 @@ module RubyIndexer
79
79
  end
80
80
  RUBY
81
81
 
82
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-2")
82
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-3")
83
83
  end
84
84
 
85
85
  def test_colon_colon_module
@@ -88,7 +88,7 @@ module RubyIndexer
88
88
  end
89
89
  RUBY
90
90
 
91
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
91
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
92
92
  end
93
93
 
94
94
  def test_namespaced_module
@@ -97,7 +97,7 @@ module RubyIndexer
97
97
  end
98
98
  RUBY
99
99
 
100
- assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-2")
100
+ assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
101
101
  end
102
102
 
103
103
  def test_dynamically_namespaced_module
@@ -124,11 +124,11 @@ module RubyIndexer
124
124
  end
125
125
  RUBY
126
126
 
127
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-2")
128
- assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-4")
129
- assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-4")
130
- assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-6")
131
- assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-8")
127
+ assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-3")
128
+ assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
129
+ assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-5")
130
+ assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-7")
131
+ assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-9")
132
132
  end
133
133
 
134
134
  def test_deleting_from_index_based_on_file_path
@@ -137,7 +137,7 @@ module RubyIndexer
137
137
  end
138
138
  RUBY
139
139
 
140
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
140
+ assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
141
141
 
142
142
  @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
143
143
  refute_entry("Foo")
@@ -14,8 +14,8 @@ module RubyIndexer
14
14
  end
15
15
  RUBY
16
16
 
17
- assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-6")
18
- assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-8")
17
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-7")
18
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-9")
19
19
  end
20
20
 
21
21
  def test_constant_or_writes
@@ -27,8 +27,8 @@ module RubyIndexer
27
27
  end
28
28
  RUBY
29
29
 
30
- assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-8")
31
- assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-10")
30
+ assert_entry("FOO", Index::Entry::Constant, "/fake/path/foo.rb:0-0:0-9")
31
+ assert_entry("Bar::FOO", Index::Entry::Constant, "/fake/path/foo.rb:3-2:3-11")
32
32
  end
33
33
 
34
34
  def test_constant_path_writes
@@ -45,10 +45,10 @@ module RubyIndexer
45
45
  A::BAZ = 1
46
46
  RUBY
47
47
 
48
- assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-8")
49
- assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-10")
50
- assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-10")
51
- assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-9")
48
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-9")
49
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-11")
50
+ assert_entry("A::B::FOO", Index::Entry::Constant, "/fake/path/foo.rb:5-4:5-11")
51
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:9-0:9-10")
52
52
  end
53
53
 
54
54
  def test_constant_path_or_writes
@@ -61,9 +61,9 @@ module RubyIndexer
61
61
  A::BAZ ||= 1
62
62
  RUBY
63
63
 
64
- assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-10")
65
- assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-12")
66
- assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-11")
64
+ assert_entry("A::FOO", Index::Entry::Constant, "/fake/path/foo.rb:1-2:1-11")
65
+ assert_entry("BAR", Index::Entry::Constant, "/fake/path/foo.rb:2-2:2-13")
66
+ assert_entry("A::BAZ", Index::Entry::Constant, "/fake/path/foo.rb:5-0:5-12")
67
67
  end
68
68
 
69
69
  def test_comments_for_constants
@@ -136,7 +136,7 @@ module RubyIndexer
136
136
  end
137
137
  RUBY
138
138
 
139
- assert_equal(["path/foo", "path/other_foo"], @index.search_require_paths("path"))
139
+ assert_equal(["path/foo", "path/other_foo"], @index.search_require_paths("path").map(&:require_path))
140
140
  end
141
141
 
142
142
  def test_searching_for_entries_based_on_prefix
@@ -53,7 +53,8 @@ module RubyLsp
53
53
  # documented
54
54
  features = ObjectSpace.each_object(Class).filter_map do |k|
55
55
  klass = T.unsafe(k)
56
- klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
56
+ klass if klass < RubyLsp::Requests::BaseRequest ||
57
+ (klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener)
57
58
  end
58
59
 
59
60
  missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
@@ -48,6 +48,8 @@ module RubyLsp
48
48
  @listeners[:on_const_path_ref]&.each { |l| T.unsafe(l).on_const_path_ref(node) }
49
49
  when SyntaxTree::Const
50
50
  @listeners[:on_const]&.each { |l| T.unsafe(l).on_const(node) }
51
+ when SyntaxTree::TopConstRef
52
+ @listeners[:on_top_const_ref]&.each { |l| T.unsafe(l).on_top_const_ref(node) }
51
53
  end
52
54
  end
53
55
 
@@ -103,9 +103,6 @@ module RubyLsp
103
103
  semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue)
104
104
  emitter.visit(document.tree) if document.parsed?
105
105
 
106
- code_lens.merge_external_listeners_responses!
107
- document_symbol.merge_external_listeners_responses!
108
-
109
106
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
110
107
  # we actually received
111
108
  document.cache_set("textDocument/documentSymbol", document_symbol.response)
@@ -299,7 +296,6 @@ module RubyLsp
299
296
  # Emit events for all listeners
300
297
  emitter.emit_for_target(target)
301
298
 
302
- hover.merge_external_listeners_responses!
303
299
  hover.response
304
300
  end
305
301
 
@@ -478,12 +474,18 @@ module RubyLsp
478
474
  return unless document.parsed?
479
475
 
480
476
  char_position = document.create_scanner.find_char_position(position)
481
- matched, parent = document.locate(
482
- T.must(document.tree),
483
- char_position,
484
- node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
485
- )
486
477
 
478
+ # When the user types in the first letter of a constant name, we actually receive the position of the next
479
+ # immediate character. We check to see if the character is uppercase and then remove the offset to try to locate
480
+ # the node, as it could not be a constant
481
+ target_node_types = if ("A".."Z").cover?(document.source[char_position - 1])
482
+ char_position -= 1
483
+ [SyntaxTree::Const, SyntaxTree::ConstPathRef, SyntaxTree::TopConstRef]
484
+ else
485
+ [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode]
486
+ end
487
+
488
+ matched, parent, nesting = document.locate(T.must(document.tree), char_position, node_types: target_node_types)
487
489
  return unless matched && parent
488
490
 
489
491
  target = case matched
@@ -504,12 +506,19 @@ module RubyLsp
504
506
  return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
505
507
 
506
508
  path_node
509
+ when SyntaxTree::Const, SyntaxTree::ConstPathRef
510
+ if (parent.is_a?(SyntaxTree::ConstPathRef) || parent.is_a?(SyntaxTree::TopConstRef)) &&
511
+ matched.is_a?(SyntaxTree::Const)
512
+ parent
513
+ else
514
+ matched
515
+ end
507
516
  end
508
517
 
509
518
  return unless target
510
519
 
511
520
  emitter = EventEmitter.new
512
- listener = Requests::PathCompletion.new(@index, emitter, @message_queue)
521
+ listener = Requests::Completion.new(@index, nesting, emitter, @message_queue)
513
522
  emitter.emit_for_target(target)
514
523
  listener.response
515
524
  end
@@ -645,7 +654,10 @@ module RubyLsp
645
654
  completion_provider = if enabled_features["completion"]
646
655
  Interface::CompletionOptions.new(
647
656
  resolve_provider: false,
648
- trigger_characters: ["/"],
657
+ trigger_characters: ["/", *"A".."Z"],
658
+ completion_item: {
659
+ labelDetailsSupport: true,
660
+ },
649
661
  )
650
662
  end
651
663
 
@@ -133,5 +133,17 @@ module RubyLsp
133
133
  ).returns(T.nilable(Listener[T::Array[Interface::DocumentSymbol]]))
134
134
  end
135
135
  def create_document_symbol_listener(emitter, message_queue); end
136
+
137
+ # Creates a new Definition listener. This method is invoked on every Definition request
138
+ sig do
139
+ overridable.params(
140
+ uri: URI::Generic,
141
+ nesting: T::Array[String],
142
+ index: RubyIndexer::Index,
143
+ emitter: EventEmitter,
144
+ message_queue: Thread::Queue,
145
+ ).returns(T.nilable(Listener[T.nilable(T.any(T::Array[Interface::Location], Interface::Location))]))
146
+ end
147
+ def create_definition_listener(uri, nesting, index, emitter, message_queue); end
136
148
  end
137
149
  end
@@ -18,13 +18,42 @@ module RubyLsp
18
18
  def initialize(emitter, message_queue)
19
19
  @emitter = emitter
20
20
  @message_queue = message_queue
21
- @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
21
+ end
22
+
23
+ sig { returns(ResponseType) }
24
+ def response
25
+ _response
22
26
  end
23
27
 
24
28
  # Override this method with an attr_reader that returns the response of your listener. The listener should
25
29
  # accumulate results in a @response variable and then provide the reader so that it is accessible
26
30
  sig { abstract.returns(ResponseType) }
27
- def response; end
31
+ def _response; end
32
+ end
33
+
34
+ # ExtensibleListener is an abstract class to be used by requests that accept extensions.
35
+ class ExtensibleListener < Listener
36
+ extend T::Sig
37
+ extend T::Generic
38
+
39
+ ResponseType = type_member
40
+
41
+ abstract!
42
+
43
+ # When inheriting from ExtensibleListener, the `super` of constructor must be called **after** the subclass's own
44
+ # ivars have been initialized. This is because the constructor of ExtensibleListener calls
45
+ # `initialize_external_listener` which may depend on the subclass's ivars.
46
+ sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void }
47
+ def initialize(emitter, message_queue)
48
+ super
49
+ @response_merged = T.let(false, T::Boolean)
50
+ @external_listeners = T.let(
51
+ Extension.extensions.filter_map do |ext|
52
+ initialize_external_listener(ext)
53
+ end,
54
+ T::Array[RubyLsp::Listener[ResponseType]],
55
+ )
56
+ end
28
57
 
29
58
  # Merge responses from all external listeners into the base listener's response. We do this to return a single
30
59
  # response to the editor including the results of all extensions
@@ -33,11 +62,21 @@ module RubyLsp
33
62
  @external_listeners.each { |l| merge_response!(l) }
34
63
  end
35
64
 
65
+ sig { returns(ResponseType) }
66
+ def response
67
+ merge_external_listeners_responses! unless @response_merged
68
+ super
69
+ end
70
+
71
+ sig do
72
+ abstract.params(extension: RubyLsp::Extension).returns(T.nilable(RubyLsp::Listener[ResponseType]))
73
+ end
74
+ def initialize_external_listener(extension); end
75
+
36
76
  # Does nothing by default. Requests that accept extensions should override this method to define how to merge
37
77
  # responses coming from external listeners
38
- sig { overridable.params(other: Listener[T.untyped]).returns(T.self_type) }
78
+ sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) }
39
79
  def merge_response!(other)
40
- self
41
80
  end
42
81
  end
43
82
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
  # class Test < Minitest::Test
19
19
  # end
20
20
  # ```
21
- class CodeLens < Listener
21
+ class CodeLens < ExtensibleListener
22
22
  extend T::Sig
23
23
  extend T::Generic
24
24
 
@@ -29,23 +29,20 @@ module RubyLsp
29
29
  SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
30
30
 
31
31
  sig { override.returns(ResponseType) }
32
- attr_reader :response
32
+ attr_reader :_response
33
33
 
34
34
  sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue, test_library: String).void }
35
35
  def initialize(uri, emitter, message_queue, test_library)
36
- super(emitter, message_queue)
37
-
38
36
  @uri = T.let(uri, URI::Generic)
39
- @external_listeners.concat(
40
- Extension.extensions.filter_map { |ext| ext.create_code_lens_listener(uri, emitter, message_queue) },
41
- )
42
37
  @test_library = T.let(test_library, String)
43
- @response = T.let([], ResponseType)
38
+ @_response = T.let([], ResponseType)
44
39
  @path = T.let(uri.to_standardized_path, T.nilable(String))
45
40
  # visibility_stack is a stack of [current_visibility, previous_visibility]
46
41
  @visibility_stack = T.let([["public", "public"]], T::Array[T::Array[T.nilable(String)]])
47
42
  @class_stack = T.let([], T::Array[String])
48
43
 
44
+ super(emitter, message_queue)
45
+
49
46
  emitter.register(
50
47
  self,
51
48
  :on_class,
@@ -149,9 +146,14 @@ module RubyLsp
149
146
  end
150
147
  end
151
148
 
149
+ sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
150
+ def initialize_external_listener(extension)
151
+ extension.create_code_lens_listener(@uri, @emitter, @message_queue)
152
+ end
153
+
152
154
  sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
153
155
  def merge_response!(other)
154
- @response.concat(other.response)
156
+ @_response.concat(other.response)
155
157
  self
156
158
  end
157
159
 
@@ -174,7 +176,7 @@ module RubyLsp
174
176
  },
175
177
  ]
176
178
 
177
- @response << create_code_lens(
179
+ @_response << create_code_lens(
178
180
  node,
179
181
  title: "Run",
180
182
  command_name: "rubyLsp.runTest",
@@ -182,7 +184,7 @@ module RubyLsp
182
184
  data: { type: "test", kind: kind },
183
185
  )
184
186
 
185
- @response << create_code_lens(
187
+ @_response << create_code_lens(
186
188
  node,
187
189
  title: "Run In Terminal",
188
190
  command_name: "rubyLsp.runTestInTerminal",
@@ -190,7 +192,7 @@ module RubyLsp
190
192
  data: { type: "test_in_terminal", kind: kind },
191
193
  )
192
194
 
193
- @response << create_code_lens(
195
+ @_response << create_code_lens(
194
196
  node,
195
197
  title: "Debug",
196
198
  command_name: "rubyLsp.debugTest",
@@ -239,7 +241,7 @@ module RubyLsp
239
241
 
240
242
  sig { params(node: SyntaxTree::Command, remote: String).void }
241
243
  def add_open_gem_remote_code_lens(node, remote)
242
- @response << create_code_lens(
244
+ @_response << create_code_lens(
243
245
  node,
244
246
  title: "Open remote",
245
247
  command_name: "rubyLsp.openLink",
@@ -0,0 +1,168 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Completion demo](../../completion.gif)
7
+ #
8
+ # The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
9
+ # suggests possible completions according to what the developer is typing. Currently, completion is support for
10
+ # - require paths
11
+ # - classes, modules and constant names
12
+ #
13
+ # # Example
14
+ #
15
+ # ```ruby
16
+ # require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
17
+ #
18
+ # RubyLsp::Requests:: # --> completion: suggests `Completion`, `Hover`, ...
19
+ # ```
20
+ class Completion < Listener
21
+ extend T::Sig
22
+ extend T::Generic
23
+
24
+ ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
25
+
26
+ sig { override.returns(ResponseType) }
27
+ attr_reader :_response
28
+
29
+ sig do
30
+ params(
31
+ index: RubyIndexer::Index,
32
+ nesting: T::Array[String],
33
+ emitter: EventEmitter,
34
+ message_queue: Thread::Queue,
35
+ ).void
36
+ end
37
+ def initialize(index, nesting, emitter, message_queue)
38
+ super(emitter, message_queue)
39
+ @_response = T.let([], ResponseType)
40
+ @index = index
41
+ @nesting = nesting
42
+
43
+ emitter.register(self, :on_tstring_content, :on_const_path_ref, :on_const, :on_top_const_ref)
44
+ end
45
+
46
+ sig { params(node: SyntaxTree::TStringContent).void }
47
+ def on_tstring_content(node)
48
+ @index.search_require_paths(node.value).map!(&:require_path).sort!.each do |path|
49
+ @_response << build_completion(T.must(path), node)
50
+ end
51
+ end
52
+
53
+ # Handle completion on regular constant references (e.g. `Bar`)
54
+ sig { params(node: SyntaxTree::Const).void }
55
+ def on_const(node)
56
+ return if DependencyDetector::HAS_TYPECHECKER
57
+
58
+ name = node.value
59
+ candidates = @index.prefix_search(name, @nesting)
60
+ candidates.each do |entries|
61
+ @_response << build_entry_completion(name, node, entries, top_level?(T.must(entries.first).name, candidates))
62
+ end
63
+ end
64
+
65
+ # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
66
+ sig { params(node: SyntaxTree::ConstPathRef).void }
67
+ def on_const_path_ref(node)
68
+ return if DependencyDetector::HAS_TYPECHECKER
69
+
70
+ name = full_constant_name(node)
71
+ candidates = @index.prefix_search(name, @nesting)
72
+ candidates.each do |entries|
73
+ @_response << build_entry_completion(name, node, entries, top_level?(T.must(entries.first).name, candidates))
74
+ end
75
+ end
76
+
77
+ # Handle completion on top level constant references (e.g. `::Bar`)
78
+ sig { params(node: SyntaxTree::TopConstRef).void }
79
+ def on_top_const_ref(node)
80
+ return if DependencyDetector::HAS_TYPECHECKER
81
+
82
+ name = full_constant_name(node)
83
+ candidates = @index.prefix_search(name, [])
84
+ candidates.each { |entries| @_response << build_entry_completion(name, node, entries, true) }
85
+ end
86
+
87
+ private
88
+
89
+ sig { params(label: String, node: SyntaxTree::TStringContent).returns(Interface::CompletionItem) }
90
+ def build_completion(label, node)
91
+ Interface::CompletionItem.new(
92
+ label: label,
93
+ text_edit: Interface::TextEdit.new(
94
+ range: range_from_syntax_tree_node(node),
95
+ new_text: label,
96
+ ),
97
+ kind: Constant::CompletionItemKind::REFERENCE,
98
+ )
99
+ end
100
+
101
+ sig do
102
+ params(
103
+ name: String,
104
+ node: SyntaxTree::Node,
105
+ entries: T::Array[RubyIndexer::Index::Entry],
106
+ top_level: T::Boolean,
107
+ ).returns(Interface::CompletionItem)
108
+ end
109
+ def build_entry_completion(name, node, entries, top_level)
110
+ first_entry = T.must(entries.first)
111
+ kind = case first_entry
112
+ when RubyIndexer::Index::Entry::Class
113
+ Constant::CompletionItemKind::CLASS
114
+ when RubyIndexer::Index::Entry::Module
115
+ Constant::CompletionItemKind::MODULE
116
+ when RubyIndexer::Index::Entry::Constant
117
+ Constant::CompletionItemKind::CONSTANT
118
+ else
119
+ Constant::CompletionItemKind::REFERENCE
120
+ end
121
+
122
+ insertion_text = first_entry.name.dup
123
+
124
+ # If we have two entries with the same name inside the current namespace and the user selects the top level
125
+ # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
126
+ # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
127
+ # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
128
+ # to complete to `::Bar`.
129
+ insertion_text.prepend("::") if top_level
130
+
131
+ # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
132
+ # of that constant. E.g.:
133
+ #
134
+ # module Foo
135
+ # class Bar
136
+ # end
137
+ #
138
+ # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
139
+ # end
140
+ @nesting.each { |namespace| insertion_text.delete_prefix!("#{namespace}::") }
141
+
142
+ # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
143
+ # For these top level references, we need to include the `::` as part of the filter text or else it won't match
144
+ # the right entries in the index
145
+ Interface::CompletionItem.new(
146
+ label: first_entry.name,
147
+ filter_text: top_level ? "::#{first_entry.name}" : first_entry.name,
148
+ text_edit: Interface::TextEdit.new(
149
+ range: range_from_syntax_tree_node(node),
150
+ new_text: insertion_text,
151
+ ),
152
+ kind: kind,
153
+ label_details: Interface::CompletionItemLabelDetails.new(
154
+ description: entries.map(&:file_name).join(","),
155
+ ),
156
+ documentation: markdown_from_index_entries(first_entry.name, entries),
157
+ )
158
+ end
159
+
160
+ # Check if the `entry_name` has potential conflicts in `candidates`, so that we use a top level reference instead
161
+ # of a short name
162
+ sig { params(entry_name: String, candidates: T::Array[T::Array[RubyIndexer::Index::Entry]]).returns(T::Boolean) }
163
+ def top_level?(entry_name, candidates)
164
+ candidates.any? { |entries| T.must(entries.first).name == "#{@nesting.join("::")}::#{entry_name}" }
165
+ end
166
+ end
167
+ end
168
+ end
@@ -17,14 +17,14 @@ module RubyLsp
17
17
  # require "some_gem/file" # <- Request go to definition on this string will take you to the file
18
18
  # Product.new # <- Request go to definition on this class name will take you to its declaration.
19
19
  # ```
20
- class Definition < Listener
20
+ class Definition < ExtensibleListener
21
21
  extend T::Sig
22
22
  extend T::Generic
23
23
 
24
24
  ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
25
25
 
26
26
  sig { override.returns(ResponseType) }
27
- attr_reader :response
27
+ attr_reader :_response
28
28
 
29
29
  sig do
30
30
  params(
@@ -36,15 +36,37 @@ module RubyLsp
36
36
  ).void
37
37
  end
38
38
  def initialize(uri, nesting, index, emitter, message_queue)
39
- super(emitter, message_queue)
40
-
41
39
  @uri = uri
42
40
  @nesting = nesting
43
41
  @index = index
44
- @response = T.let(nil, ResponseType)
42
+ @_response = T.let(nil, ResponseType)
43
+
44
+ super(emitter, message_queue)
45
+
45
46
  emitter.register(self, :on_command, :on_const, :on_const_path_ref)
46
47
  end
47
48
 
49
+ sig { override.params(ext: Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
50
+ def initialize_external_listener(ext)
51
+ ext.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue)
52
+ end
53
+
54
+ sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
55
+ def merge_response!(other)
56
+ other_response = other._response
57
+
58
+ case @_response
59
+ when Interface::Location
60
+ @_response = [@_response, *other_response]
61
+ when Array
62
+ @_response.concat(Array(other_response))
63
+ when nil
64
+ @_response = other_response
65
+ end
66
+
67
+ self
68
+ end
69
+
48
70
  sig { params(node: SyntaxTree::ConstPathRef).void }
49
71
  def on_const_path_ref(node)
50
72
  name = full_constant_name(node)
@@ -67,14 +89,16 @@ module RubyLsp
67
89
  string = argument.parts.first
68
90
  return unless string.is_a?(SyntaxTree::TStringContent)
69
91
 
70
- required_file = "#{string.value}.rb"
71
-
72
92
  case message
73
93
  when "require"
74
- candidate = find_file_in_load_path(required_file)
94
+ entry = @index.search_require_paths(string.value).find do |indexable_path|
95
+ indexable_path.require_path == string.value
96
+ end
97
+
98
+ if entry
99
+ candidate = entry.full_path
75
100
 
76
- if candidate
77
- @response = Interface::Location.new(
101
+ @_response = Interface::Location.new(
78
102
  uri: URI::Generic.from_path(path: candidate).to_s,
79
103
  range: Interface::Range.new(
80
104
  start: Interface::Position.new(line: 0, character: 0),
@@ -83,19 +107,18 @@ module RubyLsp
83
107
  )
84
108
  end
85
109
  when "require_relative"
110
+ required_file = "#{string.value}.rb"
86
111
  path = @uri.to_standardized_path
87
112
  current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
88
113
  candidate = File.expand_path(File.join(current_folder, required_file))
89
114
 
90
- if candidate
91
- @response = Interface::Location.new(
92
- uri: URI::Generic.from_path(path: candidate).to_s,
93
- range: Interface::Range.new(
94
- start: Interface::Position.new(line: 0, character: 0),
95
- end: Interface::Position.new(line: 0, character: 0),
96
- ),
97
- )
98
- end
115
+ @_response = Interface::Location.new(
116
+ uri: URI::Generic.from_path(path: candidate).to_s,
117
+ range: Interface::Range.new(
118
+ start: Interface::Position.new(line: 0, character: 0),
119
+ end: Interface::Position.new(line: 0, character: 0),
120
+ ),
121
+ )
99
122
  end
100
123
  end
101
124
 
@@ -112,7 +135,7 @@ module RubyLsp
112
135
  nil
113
136
  end
114
137
 
115
- @response = entries.filter_map do |entry|
138
+ @_response = entries.filter_map do |entry|
116
139
  location = entry.location
117
140
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
118
141
  # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
@@ -133,18 +156,6 @@ module RubyLsp
133
156
  )
134
157
  end
135
158
  end
136
-
137
- sig { params(file: String).returns(T.nilable(String)) }
138
- def find_file_in_load_path(file)
139
- return unless file.include?("/")
140
-
141
- $LOAD_PATH.each do |p|
142
- found = Dir.glob("**/#{file}", base: p).first
143
- return "#{p}/#{found}" if found
144
- end
145
-
146
- nil
147
- end
148
159
  end
149
160
  end
150
161
  end
@@ -28,7 +28,7 @@ module RubyLsp
28
28
  ResponseType = type_member { { fixed: T::Array[Interface::DocumentHighlight] } }
29
29
 
30
30
  sig { override.returns(ResponseType) }
31
- attr_reader :response
31
+ attr_reader :_response
32
32
 
33
33
  sig do
34
34
  params(
@@ -41,7 +41,7 @@ module RubyLsp
41
41
  def initialize(target, parent, emitter, message_queue)
42
42
  super(emitter, message_queue)
43
43
 
44
- @response = T.let([], T::Array[Interface::DocumentHighlight])
44
+ @_response = T.let([], T::Array[Interface::DocumentHighlight])
45
45
 
46
46
  return unless target && parent
47
47
 
@@ -83,7 +83,7 @@ module RubyLsp
83
83
  sig { params(match: Support::HighlightTarget::HighlightMatch).void }
84
84
  def add_highlight(match)
85
85
  range = range_from_syntax_tree_node(match.node)
86
- @response << Interface::DocumentHighlight.new(range: range, kind: match.type)
86
+ @_response << Interface::DocumentHighlight.new(range: range, kind: match.type)
87
87
  end
88
88
  end
89
89
  end
@@ -73,7 +73,7 @@ module RubyLsp
73
73
  end
74
74
 
75
75
  sig { override.returns(ResponseType) }
76
- attr_reader :response
76
+ attr_reader :_response
77
77
 
78
78
  sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
79
79
  def initialize(uri, emitter, message_queue)
@@ -84,7 +84,7 @@ module RubyLsp
84
84
  path = uri.to_standardized_path
85
85
  version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
86
86
  @gem_version = T.let(version_match && version_match[0], T.nilable(String))
87
- @response = T.let([], T::Array[Interface::DocumentLink])
87
+ @_response = T.let([], T::Array[Interface::DocumentLink])
88
88
 
89
89
  emitter.register(self, :on_comment)
90
90
  end
@@ -95,11 +95,13 @@ module RubyLsp
95
95
  return unless match
96
96
 
97
97
  uri = T.cast(URI(T.must(match[0])), URI::Source)
98
- gem_version = T.must(resolve_version(uri))
98
+ gem_version = resolve_version(uri)
99
+ return if gem_version.nil?
100
+
99
101
  file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
100
102
  return if file_path.nil?
101
103
 
102
- @response << Interface::DocumentLink.new(
104
+ @_response << Interface::DocumentLink.new(
103
105
  range: range_from_syntax_tree_node(node),
104
106
  target: "file://#{file_path}##{uri.line_number}",
105
107
  tooltip: "Jump to #{file_path}##{uri.line_number}",
@@ -26,7 +26,7 @@ module RubyLsp
26
26
  # end
27
27
  # end
28
28
  # ```
29
- class DocumentSymbol < Listener
29
+ class DocumentSymbol < ExtensibleListener
30
30
  extend T::Sig
31
31
  extend T::Generic
32
32
 
@@ -47,22 +47,18 @@ module RubyLsp
47
47
  end
48
48
 
49
49
  sig { override.returns(T::Array[Interface::DocumentSymbol]) }
50
- attr_reader :response
50
+ attr_reader :_response
51
51
 
52
52
  sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void }
53
53
  def initialize(emitter, message_queue)
54
- super
55
-
56
54
  @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
57
- @response = T.let(@root.children, T::Array[Interface::DocumentSymbol])
55
+ @_response = T.let(@root.children, T::Array[Interface::DocumentSymbol])
58
56
  @stack = T.let(
59
57
  [@root],
60
58
  T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
61
59
  )
62
60
 
63
- @external_listeners.concat(
64
- Extension.extensions.filter_map { |ext| ext.create_document_symbol_listener(emitter, message_queue) },
65
- )
61
+ super
66
62
 
67
63
  emitter.register(
68
64
  self,
@@ -79,10 +75,15 @@ module RubyLsp
79
75
  )
80
76
  end
81
77
 
78
+ sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
79
+ def initialize_external_listener(extension)
80
+ extension.create_document_symbol_listener(@emitter, @message_queue)
81
+ end
82
+
82
83
  # Merges responses from other listeners
83
84
  sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
84
85
  def merge_response!(other)
85
- @response.concat(other.response)
86
+ @_response.concat(other.response)
86
87
  self
87
88
  end
88
89
 
@@ -13,7 +13,7 @@ module RubyLsp
13
13
  # ```ruby
14
14
  # String # -> Hovering over the class reference will show all declaration locations and the documentation
15
15
  # ```
16
- class Hover < Listener
16
+ class Hover < ExtensibleListener
17
17
  extend T::Sig
18
18
  extend T::Generic
19
19
 
@@ -30,7 +30,7 @@ module RubyLsp
30
30
  )
31
31
 
32
32
  sig { override.returns(ResponseType) }
33
- attr_reader :response
33
+ attr_reader :_response
34
34
 
35
35
  sig do
36
36
  params(
@@ -41,27 +41,29 @@ module RubyLsp
41
41
  ).void
42
42
  end
43
43
  def initialize(index, nesting, emitter, message_queue)
44
- super(emitter, message_queue)
45
-
46
44
  @nesting = nesting
47
45
  @index = index
48
- @external_listeners.concat(
49
- Extension.extensions.filter_map { |ext| ext.create_hover_listener(emitter, message_queue) },
50
- )
51
- @response = T.let(nil, ResponseType)
46
+ @_response = T.let(nil, ResponseType)
47
+
48
+ super(emitter, message_queue)
52
49
  emitter.register(self, :on_const_path_ref, :on_const)
53
50
  end
54
51
 
52
+ sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
53
+ def initialize_external_listener(extension)
54
+ extension.create_hover_listener(@emitter, @message_queue)
55
+ end
56
+
55
57
  # Merges responses from other hover listeners
56
58
  sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
57
59
  def merge_response!(other)
58
60
  other_response = other.response
59
61
  return self unless other_response
60
62
 
61
- if @response.nil?
62
- @response = other.response
63
+ if @_response.nil?
64
+ @_response = other.response
63
65
  else
64
- @response.contents.value << "\n\n" << other_response.contents.value
66
+ @_response.contents.value << "\n\n" << other_response.contents.value
65
67
  end
66
68
 
67
69
  self
@@ -89,29 +91,10 @@ module RubyLsp
89
91
  entries = @index.resolve(name, @nesting)
90
92
  return unless entries
91
93
 
92
- title = +"```ruby\n#{name}\n```"
93
- definitions = []
94
- content = +""
95
- entries.each do |entry|
96
- loc = entry.location
97
-
98
- # We always handle locations as zero based. However, for file links in Markdown we need them to be one based,
99
- # which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to columns. The
100
- # format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
101
- uri = URI::Generic.from_path(
102
- path: entry.file_path,
103
- fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
104
- )
105
-
106
- definitions << "[#{entry.file_name}](#{uri})"
107
- content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
108
- end
109
-
110
- contents = Interface::MarkupContent.new(
111
- kind: "markdown",
112
- value: "#{title}\n\n**Definitions**: #{definitions.join(" | ")}\n\n#{content}",
94
+ @_response = Interface::Hover.new(
95
+ range: range_from_syntax_tree_node(node),
96
+ contents: markdown_from_index_entries(name, entries),
113
97
  )
114
- @response = Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
115
98
  end
116
99
  end
117
100
  end
@@ -27,13 +27,13 @@ module RubyLsp
27
27
  RESCUE_STRING_LENGTH = T.let("rescue".length, Integer)
28
28
 
29
29
  sig { override.returns(ResponseType) }
30
- attr_reader :response
30
+ attr_reader :_response
31
31
 
32
32
  sig { params(range: T::Range[Integer], emitter: EventEmitter, message_queue: Thread::Queue).void }
33
33
  def initialize(range, emitter, message_queue)
34
34
  super(emitter, message_queue)
35
35
 
36
- @response = T.let([], ResponseType)
36
+ @_response = T.let([], ResponseType)
37
37
  @range = range
38
38
 
39
39
  emitter.register(self, :on_rescue)
@@ -47,7 +47,7 @@ module RubyLsp
47
47
  loc = node.location
48
48
  return unless visible?(node, @range)
49
49
 
50
- @response << Interface::InlayHint.new(
50
+ @_response << Interface::InlayHint.new(
51
51
  position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
52
52
  label: "StandardError",
53
53
  padding_left: true,
@@ -105,7 +105,7 @@ module RubyLsp
105
105
  end
106
106
 
107
107
  sig { override.returns(ResponseType) }
108
- attr_reader :response
108
+ attr_reader :_response
109
109
 
110
110
  sig do
111
111
  params(
@@ -117,7 +117,7 @@ module RubyLsp
117
117
  def initialize(emitter, message_queue, range: nil)
118
118
  super(emitter, message_queue)
119
119
 
120
- @response = T.let([], ResponseType)
120
+ @_response = T.let([], ResponseType)
121
121
  @range = range
122
122
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
123
123
 
@@ -174,7 +174,7 @@ module RubyLsp
174
174
  # When finding a module or class definition, we will have already pushed a token related to this constant. We
175
175
  # need to look at the previous two tokens and if they match this locatione exactly, avoid pushing another token
176
176
  # on top of the previous one
177
- return if @response.last(2).any? { |token| token.location == node.location }
177
+ return if @_response.last(2).any? { |token| token.location == node.location }
178
178
 
179
179
  add_token(node.location, :namespace)
180
180
  end
@@ -327,7 +327,7 @@ module RubyLsp
327
327
  def add_token(location, type, modifiers = [])
328
328
  length = location.end_char - location.start_char
329
329
  modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
330
- @response.push(
330
+ @_response.push(
331
331
  SemanticToken.new(
332
332
  location: location,
333
333
  length: length,
@@ -74,6 +74,39 @@ module RubyLsp
74
74
  data: data,
75
75
  )
76
76
  end
77
+
78
+ sig { params(title: String, entries: T::Array[RubyIndexer::Index::Entry]).returns(Interface::MarkupContent) }
79
+ def markdown_from_index_entries(title, entries)
80
+ markdown_title = "```ruby\n#{title}\n```"
81
+ definitions = []
82
+ content = +""
83
+ entries.each do |entry|
84
+ loc = entry.location
85
+
86
+ # We always handle locations as zero based. However, for file links in Markdown we need them to be one
87
+ # based, which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to
88
+ # columns. The format for VS Code file URIs is
89
+ # `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
90
+ uri = URI::Generic.from_path(
91
+ path: entry.file_path,
92
+ fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
93
+ )
94
+
95
+ definitions << "[#{entry.file_name}](#{uri})"
96
+ content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
97
+ end
98
+
99
+ Interface::MarkupContent.new(
100
+ kind: "markdown",
101
+ value: <<~MARKDOWN.chomp,
102
+ #{markdown_title}
103
+
104
+ **Definitions**: #{definitions.join(" | ")}
105
+
106
+ #{content}
107
+ MARKDOWN
108
+ )
109
+ end
77
110
  end
78
111
  end
79
112
  end
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  # - [CodeActionResolve](rdoc-ref:RubyLsp::Requests::CodeActionResolve)
18
18
  # - [DocumentHighlight](rdoc-ref:RubyLsp::Requests::DocumentHighlight)
19
19
  # - [InlayHint](rdoc-ref:RubyLsp::Requests::InlayHints)
20
- # - [PathCompletion](rdoc-ref:RubyLsp::Requests::PathCompletion)
20
+ # - [Completion](rdoc-ref:RubyLsp::Requests::Completion)
21
21
  # - [CodeLens](rdoc-ref:RubyLsp::Requests::CodeLens)
22
22
  # - [Definition](rdoc-ref:RubyLsp::Requests::Definition)
23
23
  # - [ShowSyntaxTree](rdoc-ref:RubyLsp::Requests::ShowSyntaxTree)
@@ -38,7 +38,7 @@ module RubyLsp
38
38
  autoload :CodeActionResolve, "ruby_lsp/requests/code_action_resolve"
39
39
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
40
40
  autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
41
- autoload :PathCompletion, "ruby_lsp/requests/path_completion"
41
+ autoload :Completion, "ruby_lsp/requests/completion"
42
42
  autoload :CodeLens, "ruby_lsp/requests/code_lens"
43
43
  autoload :Definition, "ruby_lsp/requests/definition"
44
44
  autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
@@ -175,20 +175,24 @@ module RubyLsp
175
175
  # weren't a part of the Gemfile, then we need to run `bundle install` for the first time to generate the
176
176
  # Gemfile.lock with them included or else Bundler will complain that they're missing. We can only update if the
177
177
  # custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
178
- command = +""
179
178
 
180
- if should_bundle_install?
181
- # Install gems using the custom bundle
182
- command << "(bundle check || bundle install) "
183
- else
179
+ # When not updating, we run `(bundle check || bundle install)`
180
+ # When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
181
+ command = +"(bundle check"
182
+
183
+ if should_bundle_update?
184
184
  # If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
185
- command << "bundle update "
185
+ command.prepend("(")
186
+ command << " && bundle update "
186
187
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
187
- command << "debug " unless @dependencies["debug"]
188
+ command << "debug" unless @dependencies["debug"]
189
+ command << ")"
188
190
 
189
191
  @last_updated_path.write(Time.now.iso8601)
190
192
  end
191
193
 
194
+ command << " || bundle install) "
195
+
192
196
  # Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
193
197
  # responses
194
198
  command << "1>&2"
@@ -200,10 +204,17 @@ module RubyLsp
200
204
  end
201
205
 
202
206
  sig { returns(T::Boolean) }
203
- def should_bundle_install?
204
- (!@dependencies["ruby-lsp"].nil? && !@dependencies["debug"].nil?) ||
205
- custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil? ||
206
- (@last_updated_path.exist? && Time.parse(@last_updated_path.read) > (Time.now - FOUR_HOURS))
207
+ def should_bundle_update?
208
+ # If both `ruby-lsp` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it will produce
209
+ # version control changes
210
+ return false if @dependencies["ruby-lsp"] && @dependencies["debug"]
211
+
212
+ # If the custom lockfile doesn't include either the `ruby-lsp` or `debug`, we need to run bundle install before
213
+ # updating
214
+ return false if custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
215
+
216
+ # If the last updated file doesn't exist or was updated more than 4 hours ago, we should update
217
+ !@last_updated_path.exist? || Time.parse(@last_updated_path.read) < (Time.now - FOUR_HOURS)
207
218
  end
208
219
  end
209
220
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-05 00:00:00.000000000 Z
11
+ date: 2023-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -64,20 +64,20 @@ dependencies:
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '0.9'
67
+ version: '0.11'
68
68
  - - "<"
69
69
  - !ruby/object:Gem::Version
70
- version: '0.11'
70
+ version: '0.12'
71
71
  type: :runtime
72
72
  prerelease: false
73
73
  version_requirements: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - ">="
76
76
  - !ruby/object:Gem::Version
77
- version: '0.9'
77
+ version: '0.11'
78
78
  - - "<"
79
79
  - !ruby/object:Gem::Version
80
- version: '0.11'
80
+ version: '0.12'
81
81
  description: An opinionated language server for Ruby
82
82
  email:
83
83
  - ruby@shopify.com
@@ -119,6 +119,7 @@ files:
119
119
  - lib/ruby_lsp/requests/code_action_resolve.rb
120
120
  - lib/ruby_lsp/requests/code_actions.rb
121
121
  - lib/ruby_lsp/requests/code_lens.rb
122
+ - lib/ruby_lsp/requests/completion.rb
122
123
  - lib/ruby_lsp/requests/definition.rb
123
124
  - lib/ruby_lsp/requests/diagnostics.rb
124
125
  - lib/ruby_lsp/requests/document_highlight.rb
@@ -129,7 +130,6 @@ files:
129
130
  - lib/ruby_lsp/requests/hover.rb
130
131
  - lib/ruby_lsp/requests/inlay_hints.rb
131
132
  - lib/ruby_lsp/requests/on_type_formatting.rb
132
- - lib/ruby_lsp/requests/path_completion.rb
133
133
  - lib/ruby_lsp/requests/selection_ranges.rb
134
134
  - lib/ruby_lsp/requests/semantic_highlighting.rb
135
135
  - lib/ruby_lsp/requests/show_syntax_tree.rb
@@ -1,56 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyLsp
5
- module Requests
6
- # ![Path completion demo](../../path_completion.gif)
7
- #
8
- # The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
9
- # request looks up Ruby files in the $LOAD_PATH to suggest path completion inside `require` statements.
10
- #
11
- # # Example
12
- #
13
- # ```ruby
14
- # require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
15
- # ```
16
- class PathCompletion < Listener
17
- extend T::Sig
18
- extend T::Generic
19
-
20
- ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
21
-
22
- sig { override.returns(ResponseType) }
23
- attr_reader :response
24
-
25
- sig { params(index: RubyIndexer::Index, emitter: EventEmitter, message_queue: Thread::Queue).void }
26
- def initialize(index, emitter, message_queue)
27
- super(emitter, message_queue)
28
- @response = T.let([], ResponseType)
29
- @index = index
30
-
31
- emitter.register(self, :on_tstring_content)
32
- end
33
-
34
- sig { params(node: SyntaxTree::TStringContent).void }
35
- def on_tstring_content(node)
36
- @index.search_require_paths(node.value).sort!.each do |path|
37
- @response << build_completion(path, node)
38
- end
39
- end
40
-
41
- private
42
-
43
- sig { params(label: String, node: SyntaxTree::TStringContent).returns(Interface::CompletionItem) }
44
- def build_completion(label, node)
45
- Interface::CompletionItem.new(
46
- label: label,
47
- text_edit: Interface::TextEdit.new(
48
- range: range_from_syntax_tree_node(node),
49
- new_text: label,
50
- ),
51
- kind: Constant::CompletionItemKind::REFERENCE,
52
- )
53
- end
54
- end
55
- end
56
- end