ruby-lsp 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -99,7 +99,7 @@ module RubyLsp
99
99
  file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
100
100
  return if file_path.nil?
101
101
 
102
- @response << Interface::DocumentLink.new(
102
+ @_response << Interface::DocumentLink.new(
103
103
  range: range_from_syntax_tree_node(node),
104
104
  target: "file://#{file_path}##{uri.line_number}",
105
105
  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"
@@ -53,7 +53,6 @@ module RubyLsp
53
53
  autoload :Sorbet, "ruby_lsp/requests/support/sorbet"
54
54
  autoload :HighlightTarget, "ruby_lsp/requests/support/highlight_target"
55
55
  autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
56
- autoload :PrefixTree, "ruby_lsp/requests/support/prefix_tree"
57
56
  autoload :Common, "ruby_lsp/requests/support/common"
58
57
  autoload :FormatterRunner, "ruby_lsp/requests/support/formatter_runner"
59
58
  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.3
4
+ version: 0.10.0
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-01 00:00:00.000000000 Z
11
+ date: 2023-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -67,7 +67,7 @@ dependencies:
67
67
  version: '0.9'
68
68
  - - "<"
69
69
  - !ruby/object:Gem::Version
70
- version: '0.10'
70
+ version: '0.11'
71
71
  type: :runtime
72
72
  prerelease: false
73
73
  version_requirements: !ruby/object:Gem::Requirement
@@ -77,7 +77,7 @@ dependencies:
77
77
  version: '0.9'
78
78
  - - "<"
79
79
  - !ruby/object:Gem::Version
80
- version: '0.10'
80
+ version: '0.11'
81
81
  description: An opinionated language server for Ruby
82
82
  email:
83
83
  - ruby@shopify.com
@@ -97,12 +97,15 @@ files:
97
97
  - lib/ruby-lsp.rb
98
98
  - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
99
99
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
100
+ - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
101
+ - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
100
102
  - lib/ruby_indexer/lib/ruby_indexer/visitor.rb
101
103
  - lib/ruby_indexer/ruby_indexer.rb
102
104
  - lib/ruby_indexer/test/classes_and_modules_test.rb
103
105
  - lib/ruby_indexer/test/configuration_test.rb
104
106
  - lib/ruby_indexer/test/constant_test.rb
105
107
  - lib/ruby_indexer/test/index_test.rb
108
+ - lib/ruby_indexer/test/prefix_tree_test.rb
106
109
  - lib/ruby_indexer/test/test_case.rb
107
110
  - lib/ruby_lsp/check_docs.rb
108
111
  - lib/ruby_lsp/document.rb
@@ -116,6 +119,7 @@ files:
116
119
  - lib/ruby_lsp/requests/code_action_resolve.rb
117
120
  - lib/ruby_lsp/requests/code_actions.rb
118
121
  - lib/ruby_lsp/requests/code_lens.rb
122
+ - lib/ruby_lsp/requests/completion.rb
119
123
  - lib/ruby_lsp/requests/definition.rb
120
124
  - lib/ruby_lsp/requests/diagnostics.rb
121
125
  - lib/ruby_lsp/requests/document_highlight.rb
@@ -126,7 +130,6 @@ files:
126
130
  - lib/ruby_lsp/requests/hover.rb
127
131
  - lib/ruby_lsp/requests/inlay_hints.rb
128
132
  - lib/ruby_lsp/requests/on_type_formatting.rb
129
- - lib/ruby_lsp/requests/path_completion.rb
130
133
  - lib/ruby_lsp/requests/selection_ranges.rb
131
134
  - lib/ruby_lsp/requests/semantic_highlighting.rb
132
135
  - lib/ruby_lsp/requests/show_syntax_tree.rb
@@ -135,7 +138,6 @@ files:
135
138
  - lib/ruby_lsp/requests/support/dependency_detector.rb
136
139
  - lib/ruby_lsp/requests/support/formatter_runner.rb
137
140
  - lib/ruby_lsp/requests/support/highlight_target.rb
138
- - lib/ruby_lsp/requests/support/prefix_tree.rb
139
141
  - lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
140
142
  - lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb
141
143
  - lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
@@ -170,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
172
  - !ruby/object:Gem::Version
171
173
  version: '0'
172
174
  requirements: []
173
- rubygems_version: 3.4.18
175
+ rubygems_version: 3.4.19
174
176
  signing_key:
175
177
  specification_version: 4
176
178
  summary: An opinionated language server for Ruby
@@ -1,65 +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(emitter: EventEmitter, message_queue: Thread::Queue).void }
26
- def initialize(emitter, message_queue)
27
- super
28
- @response = T.let([], ResponseType)
29
- @tree = T.let(Support::PrefixTree.new(collect_load_path_files), Support::PrefixTree)
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
- @tree.search(node.value).sort.each do |path|
37
- @response << build_completion(path, node)
38
- end
39
- end
40
-
41
- private
42
-
43
- sig { returns(T::Array[String]) }
44
- def collect_load_path_files
45
- $LOAD_PATH.flat_map do |p|
46
- Dir.glob("**/*.rb", base: p)
47
- end.map! do |result|
48
- result.delete_suffix!(".rb")
49
- end
50
- end
51
-
52
- sig { params(label: String, node: SyntaxTree::TStringContent).returns(Interface::CompletionItem) }
53
- def build_completion(label, node)
54
- Interface::CompletionItem.new(
55
- label: label,
56
- text_edit: Interface::TextEdit.new(
57
- range: range_from_syntax_tree_node(node),
58
- new_text: label,
59
- ),
60
- kind: Constant::CompletionItemKind::REFERENCE,
61
- )
62
- end
63
- end
64
- end
65
- end
@@ -1,80 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyLsp
5
- module Requests
6
- module Support
7
- class PrefixTree
8
- extend T::Sig
9
-
10
- sig { params(items: T::Array[String]).void }
11
- def initialize(items)
12
- @root = T.let(Node.new(""), Node)
13
-
14
- items.each do |item|
15
- insert(item)
16
- end
17
- end
18
-
19
- sig { params(prefix: String).returns(T::Array[String]) }
20
- def search(prefix)
21
- node = T.let(@root, Node)
22
-
23
- prefix.each_char do |char|
24
- snode = node.children[char]
25
- return [] unless snode
26
-
27
- node = snode
28
- end
29
-
30
- node.collect
31
- end
32
-
33
- private
34
-
35
- sig { params(item: String).void }
36
- def insert(item)
37
- node = T.let(@root, Node)
38
-
39
- item.each_char do |char|
40
- node = node.children[char] ||= Node.new(node.value + char)
41
- end
42
-
43
- node.leaf = true
44
- end
45
-
46
- class Node
47
- extend T::Sig
48
-
49
- sig { returns(T::Hash[String, Node]) }
50
- attr_reader :children
51
-
52
- sig { returns(String) }
53
- attr_reader :value
54
-
55
- sig { returns(T::Boolean) }
56
- attr_accessor :leaf
57
-
58
- sig { params(value: String).void }
59
- def initialize(value)
60
- @children = T.let({}, T::Hash[String, Node])
61
- @value = T.let(value, String)
62
- @leaf = T.let(false, T::Boolean)
63
- end
64
-
65
- sig { returns(T::Array[String]) }
66
- def collect
67
- result = T.let([], T::Array[String])
68
- result << value if leaf
69
-
70
- children.each_value do |node|
71
- result.concat(node.collect)
72
- end
73
-
74
- result
75
- end
76
- end
77
- end
78
- end
79
- end
80
- end