ruby-lsp 0.26.2 → 0.26.9

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +7 -10
  4. data/exe/ruby-lsp-launcher +16 -3
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +3 -2
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +19 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +33 -27
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +7 -2
  9. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +6 -2
  10. data/lib/ruby_lsp/base_server.rb +23 -12
  11. data/lib/ruby_lsp/global_state.rb +65 -33
  12. data/lib/ruby_lsp/listeners/definition.rb +34 -14
  13. data/lib/ruby_lsp/listeners/document_link.rb +62 -23
  14. data/lib/ruby_lsp/listeners/hover.rb +2 -2
  15. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +2 -2
  16. data/lib/ruby_lsp/requests/code_action_resolve.rb +33 -11
  17. data/lib/ruby_lsp/requests/code_actions.rb +20 -5
  18. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -6
  19. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  20. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +7 -1
  21. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +6 -10
  22. data/lib/ruby_lsp/requests/support/source_uri.rb +7 -6
  23. data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
  24. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +3 -3
  25. data/lib/ruby_lsp/scripts/compose_bundle.rb +3 -3
  26. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +3 -1
  27. data/lib/ruby_lsp/server.rb +5 -2
  28. data/lib/ruby_lsp/setup_bundler.rb +68 -37
  29. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +50 -27
  30. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +1 -1
  31. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +1 -1
  32. metadata +3 -16
  33. data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
  34. data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -770
  35. data/lib/ruby_indexer/test/configuration_test.rb +0 -279
  36. data/lib/ruby_indexer/test/constant_test.rb +0 -402
  37. data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
  38. data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
  39. data/lib/ruby_indexer/test/index_test.rb +0 -2276
  40. data/lib/ruby_indexer/test/instance_variables_test.rb +0 -264
  41. data/lib/ruby_indexer/test/method_test.rb +0 -990
  42. data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
  43. data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -381
  44. data/lib/ruby_indexer/test/reference_finder_test.rb +0 -395
  45. data/lib/ruby_indexer/test/test_case.rb +0 -57
  46. data/lib/ruby_indexer/test/uri_test.rb +0 -85
@@ -2,6 +2,21 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # Holds the detected value and the reason for detection
6
+ class DetectionResult
7
+ #: String
8
+ attr_reader :value
9
+
10
+ #: String
11
+ attr_reader :reason
12
+
13
+ #: (String value, String reason) -> void
14
+ def initialize(value, reason)
15
+ @value = value
16
+ @reason = reason
17
+ end
18
+ end
19
+
5
20
  class GlobalState
6
21
  #: String
7
22
  attr_reader :test_library
@@ -122,8 +137,11 @@ module RubyLsp
122
137
  end
123
138
 
124
139
  if @formatter == "auto"
125
- @formatter = detect_formatter(direct_dependencies, all_dependencies)
126
- notifications << Notification.window_log_message("Auto detected formatter: #{@formatter}")
140
+ formatter_result = detect_formatter(direct_dependencies, all_dependencies)
141
+ @formatter = formatter_result.value
142
+ notifications << Notification.window_log_message(
143
+ "Auto detected formatter: #{@formatter} (#{formatter_result.reason})",
144
+ )
127
145
  end
128
146
 
129
147
  specified_linters = options.dig(:initializationOptions, :linters)
@@ -144,21 +162,28 @@ module RubyLsp
144
162
  specified_linters << "rubocop_internal"
145
163
  end
146
164
 
147
- @linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
148
-
149
- notifications << if specified_linters
150
- Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
165
+ if specified_linters
166
+ @linters = specified_linters
167
+ notifications << Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
151
168
  else
152
- Notification.window_log_message("Auto detected linters: #{@linters.join(", ")}")
169
+ linter_results = detect_linters(direct_dependencies, all_dependencies)
170
+ @linters = linter_results.map(&:value)
171
+ linter_messages = linter_results.map { |r| "#{r.value} (#{r.reason})" }
172
+ notifications << Notification.window_log_message("Auto detected linters: #{linter_messages.join(", ")}")
153
173
  end
154
174
 
155
- @test_library = detect_test_library(direct_dependencies)
156
- notifications << Notification.window_log_message("Detected test library: #{@test_library}")
175
+ test_library_result = detect_test_library(direct_dependencies)
176
+ @test_library = test_library_result.value
177
+ notifications << Notification.window_log_message(
178
+ "Detected test library: #{@test_library} (#{test_library_result.reason})",
179
+ )
157
180
 
158
- @has_type_checker = detect_typechecker(all_dependencies)
159
- if @has_type_checker
181
+ typechecker_result = detect_typechecker(all_dependencies)
182
+ @has_type_checker = !typechecker_result.nil?
183
+ if typechecker_result
160
184
  notifications << Notification.window_log_message(
161
- "Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
185
+ "Ruby LSP detected this is a Sorbet project (#{typechecker_result.reason}) and will defer to the " \
186
+ "Sorbet LSP for some functionality",
162
187
  )
163
188
  end
164
189
 
@@ -228,60 +253,67 @@ module RubyLsp
228
253
 
229
254
  private
230
255
 
231
- #: (Array[String] direct_dependencies, Array[String] all_dependencies) -> String
256
+ #: (Array[String] direct_dependencies, Array[String] all_dependencies) -> DetectionResult
232
257
  def detect_formatter(direct_dependencies, all_dependencies)
233
258
  # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
234
- return "rubocop_internal" if direct_dependencies.any?(/^rubocop/)
259
+ if direct_dependencies.any?(/^rubocop/)
260
+ return DetectionResult.new("rubocop_internal", "direct dependency matching /^rubocop/")
261
+ end
235
262
 
236
- syntax_tree_is_direct_dependency = direct_dependencies.include?("syntax_tree")
237
- return "syntax_tree" if syntax_tree_is_direct_dependency
263
+ if direct_dependencies.include?("syntax_tree")
264
+ return DetectionResult.new("syntax_tree", "direct dependency")
265
+ end
238
266
 
239
- rubocop_is_transitive_dependency = all_dependencies.include?("rubocop")
240
- return "rubocop_internal" if dot_rubocop_yml_present && rubocop_is_transitive_dependency
267
+ if all_dependencies.include?("rubocop") && dot_rubocop_yml_present
268
+ return DetectionResult.new("rubocop_internal", "transitive dependency with .rubocop.yml present")
269
+ end
241
270
 
242
- "none"
271
+ DetectionResult.new("none", "no formatter detected")
243
272
  end
244
273
 
245
274
  # Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
246
275
  # single linter. To have multiple linters running, the user must configure them manually
247
- #: (Array[String] dependencies, Array[String] all_dependencies) -> Array[String]
276
+ #: (Array[String] dependencies, Array[String] all_dependencies) -> Array[DetectionResult]
248
277
  def detect_linters(dependencies, all_dependencies)
249
- linters = []
278
+ linters = [] #: Array[DetectionResult]
250
279
 
251
- if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
252
- linters << "rubocop_internal"
280
+ if dependencies.any?(/^rubocop/)
281
+ linters << DetectionResult.new("rubocop_internal", "direct dependency matching /^rubocop/")
282
+ elsif all_dependencies.include?("rubocop") && dot_rubocop_yml_present
283
+ linters << DetectionResult.new("rubocop_internal", "transitive dependency with .rubocop.yml present")
253
284
  end
254
285
 
255
286
  linters
256
287
  end
257
288
 
258
- #: (Array[String] dependencies) -> String
289
+ #: (Array[String] dependencies) -> DetectionResult
259
290
  def detect_test_library(dependencies)
260
291
  if dependencies.any?(/^rspec/)
261
- "rspec"
292
+ DetectionResult.new("rspec", "direct dependency matching /^rspec/")
262
293
  # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
263
294
  # by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
264
295
  # activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
265
296
  # of bin/rails to support these cases.
266
297
  elsif bin_rails_present
267
- "rails"
298
+ DetectionResult.new("rails", "bin/rails present")
268
299
  # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
269
300
  elsif dependencies.any?(/^minitest$/)
270
- "minitest"
301
+ DetectionResult.new("minitest", "direct dependency matching /^minitest$/")
271
302
  elsif dependencies.any?(/^test-unit/)
272
- "test-unit"
303
+ DetectionResult.new("test-unit", "direct dependency matching /^test-unit/")
273
304
  else
274
- "unknown"
305
+ DetectionResult.new("unknown", "no test library detected")
275
306
  end
276
307
  end
277
308
 
278
- #: (Array[String] dependencies) -> bool
309
+ #: (Array[String] dependencies) -> DetectionResult?
279
310
  def detect_typechecker(dependencies)
280
- return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
311
+ return if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
312
+ return if dependencies.none?(/^sorbet-static/)
281
313
 
282
- dependencies.any?(/^sorbet-static/)
314
+ DetectionResult.new("sorbet", "sorbet-static in dependencies")
283
315
  rescue Bundler::GemfileNotFound
284
- false
316
+ nil
285
317
  end
286
318
 
287
319
  #: -> bool
@@ -71,24 +71,26 @@ module RubyLsp
71
71
 
72
72
  #: (Prism::StringNode node) -> void
73
73
  def on_string_node_enter(node)
74
- enclosing_call = @node_context.call_node
75
- return unless enclosing_call
76
-
77
- name = enclosing_call.name
78
- return unless name == :require || name == :require_relative
79
-
80
- handle_require_definition(node, name)
74
+ with_enclosing_call(node) do |enclosing_call, name|
75
+ case name
76
+ when :require, :require_relative
77
+ handle_require_definition(node, name)
78
+ when :send, :public_send
79
+ handle_send_or_public_send_definition(enclosing_call, node) { node.content }
80
+ end
81
+ end
81
82
  end
82
83
 
83
84
  #: (Prism::SymbolNode node) -> void
84
85
  def on_symbol_node_enter(node)
85
- enclosing_call = @node_context.call_node
86
- return unless enclosing_call
87
-
88
- name = enclosing_call.name
89
- return unless name == :autoload
90
-
91
- handle_autoload_definition(enclosing_call)
86
+ with_enclosing_call(node) do |enclosing_call, name|
87
+ case name
88
+ when :autoload
89
+ handle_autoload_definition(enclosing_call)
90
+ when :send, :public_send
91
+ handle_send_or_public_send_definition(enclosing_call, node) { node.unescaped }
92
+ end
93
+ end
92
94
  end
93
95
 
94
96
  #: (Prism::BlockArgumentNode node) -> void
@@ -220,6 +222,24 @@ module RubyLsp
220
222
 
221
223
  private
222
224
 
225
+ #: (Prism::Node node) { (Prism::CallNode, Symbol) -> void } -> void
226
+ def with_enclosing_call(node, &block)
227
+ enclosing_call = @node_context.call_node
228
+ return unless enclosing_call
229
+
230
+ block.call(enclosing_call, enclosing_call.name)
231
+ end
232
+
233
+ #: (Prism::CallNode enclosing_call, Prism::Node node) { -> String } -> void
234
+ def handle_send_or_public_send_definition(enclosing_call, node, &block)
235
+ first_argument = enclosing_call.arguments&.arguments&.first
236
+ return unless first_argument.eql?(node)
237
+
238
+ method_name = block.call
239
+
240
+ handle_method_definition(method_name, nil)
241
+ end
242
+
223
243
  #: -> void
224
244
  def handle_super_node_definition
225
245
  # Sorbet can handle super hover on typed true or higher
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ruby_lsp/requests/support/source_uri"
5
+ require "ruby_lsp/requests/support/package_url"
5
6
 
6
7
  module RubyLsp
7
8
  module Listeners
@@ -59,6 +60,7 @@ module RubyLsp
59
60
  @lines_to_comments = comments.to_h do |comment|
60
61
  [comment.location.end_line, comment]
61
62
  end #: Hash[Integer, Prism::Comment]
63
+ @sig_comments = {} #: Hash[Integer, Prism::Comment]
62
64
 
63
65
  dispatcher.register(
64
66
  self,
@@ -67,9 +69,20 @@ module RubyLsp
67
69
  :on_module_node_enter,
68
70
  :on_constant_write_node_enter,
69
71
  :on_constant_path_write_node_enter,
72
+ :on_call_node_enter,
70
73
  )
71
74
  end
72
75
 
76
+ #: (Prism::CallNode node) -> void
77
+ def on_call_node_enter(node)
78
+ return unless node.name == :sig
79
+
80
+ comment = @lines_to_comments[node.location.start_line - 1]
81
+ return unless comment
82
+
83
+ @sig_comments[node.location.end_line] = comment
84
+ end
85
+
73
86
  #: (Prism::DefNode node) -> void
74
87
  def on_def_node_enter(node)
75
88
  extract_document_link(node)
@@ -99,55 +112,81 @@ module RubyLsp
99
112
 
100
113
  #: (Prism::Node node) -> void
101
114
  def extract_document_link(node)
102
- comment = @lines_to_comments[node.location.start_line - 1]
115
+ comment = @lines_to_comments[node.location.start_line - 1] || @sig_comments[node.location.start_line - 1]
103
116
  return unless comment
104
117
 
105
- match = comment.location.slice.match(%r{source://.*#\d+$})
118
+ match = comment.location.slice.match(%r{(source://.*#\d+|pkg:gem/.*#.*)$})
106
119
  return unless match
107
120
 
108
- uri = begin
109
- URI(
110
- match[0], #: as !nil
111
- )
121
+ uri_string = match[0] #: as !nil
122
+
123
+ file_path, line_number = if uri_string.start_with?("pkg:gem/")
124
+ parse_package_url(uri_string)
125
+ else
126
+ parse_source_uri(uri_string)
127
+ end
128
+
129
+ return unless file_path
130
+
131
+ @response_builder << Interface::DocumentLink.new(
132
+ range: range_from_location(comment.location),
133
+ target: "file://#{file_path}##{line_number}",
134
+ tooltip: "Jump to #{file_path}##{line_number}",
135
+ )
136
+ end
137
+
138
+ #: (String uri_string) -> [String, String]?
139
+ def parse_package_url(uri_string)
140
+ purl = PackageURL.parse(uri_string) #: as PackageURL?
141
+ return unless purl
142
+
143
+ gem_version = resolve_version(purl.version, purl.name)
144
+ return if gem_version.nil?
145
+
146
+ path, line_number = purl.subpath.split(":", 2)
147
+ return unless path
148
+
149
+ gem_name = purl.name
150
+ file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
151
+ return if file_path.nil?
152
+
153
+ [file_path, line_number]
154
+ rescue PackageURL::InvalidPackageURL
155
+ nil
156
+ end
157
+
158
+ #: (String uri_string) -> [String, String]?
159
+ def parse_source_uri(uri_string)
160
+ uri = begin
161
+ URI(uri_string)
112
162
  rescue URI::Error
113
163
  nil
114
164
  end #: as URI::Source?
115
165
  return unless uri
116
166
 
117
- gem_version = resolve_version(uri)
167
+ gem_version = resolve_version(uri.gem_version, uri.gem_name)
118
168
  return if gem_version.nil?
119
169
 
120
170
  path = uri.path
121
171
  return unless path
122
172
 
123
- gem_name = uri.gem_name
124
- return unless gem_name
125
-
126
- file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
173
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(path))
127
174
  return if file_path.nil?
128
175
 
129
- @response_builder << Interface::DocumentLink.new(
130
- range: range_from_location(comment.location),
131
- target: "file://#{file_path}##{uri.line_number}",
132
- tooltip: "Jump to #{file_path}##{uri.line_number}",
133
- )
176
+ [file_path, uri.line_number || "0"]
134
177
  end
135
178
 
136
179
  # Try to figure out the gem version for a source:// link. The order of precedence is:
137
180
  # 1. The version in the URI
138
181
  # 2. The version in the RBI file name
139
182
  # 3. The version from the gemspec
140
- #: (URI::Source uri) -> String?
141
- def resolve_version(uri)
142
- version = uri.gem_version
183
+ #: (String? version, String? gem_name) -> String?
184
+ def resolve_version(version, gem_name)
143
185
  return version unless version.nil? || version.empty?
144
186
 
145
187
  return @gem_version unless @gem_version.nil? || @gem_version.empty?
146
188
 
147
- gem_name = uri.gem_name
148
- return unless gem_name
149
-
150
- GEM_TO_VERSION_MAP[gem_name]
189
+ GEM_TO_VERSION_MAP[gem_name.to_s]
151
190
  end
152
191
  end
153
192
  end
@@ -283,10 +283,10 @@ module RubyLsp
283
283
  content = KEYWORD_DOCS[keyword]
284
284
  return unless content
285
285
 
286
- doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
286
+ doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
287
287
 
288
288
  @response_builder.push("```ruby\n#{keyword}\n```", category: :title)
289
- @response_builder.push("[Read more](#{doc_path})", category: :links)
289
+ @response_builder.push("[Read more](#{doc_uri})", category: :links)
290
290
  @response_builder.push(content, category: :documentation)
291
291
  end
292
292
 
@@ -94,7 +94,7 @@ module RubyLsp
94
94
 
95
95
  #: (Prism::MatchWriteNode node) -> void
96
96
  def on_match_write_node_leave(node)
97
- @inside_regex_capture = true if node.call.message == "=~"
97
+ @inside_regex_capture = false if node.call.message == "=~"
98
98
  end
99
99
 
100
100
  #: (Prism::DefNode node) -> void
@@ -162,7 +162,7 @@ module RubyLsp
162
162
 
163
163
  #: (Prism::SelfNode node) -> void
164
164
  def on_self_node_enter(node)
165
- @response_builder.add_token(node.location, :variable, [:default_library])
165
+ @response_builder.add_token(node.location, :variable, [:defaultLibrary])
166
166
  end
167
167
 
168
168
  #: (Prism::LocalVariableWriteNode node) -> void
@@ -51,20 +51,42 @@ module RubyLsp
51
51
  #: -> (Interface::CodeAction)
52
52
  def switch_block_style
53
53
  source_range = @code_action.dig(:data, :range)
54
- raise EmptySelectionError, "Invalid selection for refactor" if source_range[:start] == source_range[:end]
54
+ if source_range[:start] == source_range[:end]
55
+ block_context = @document.locate_node(
56
+ source_range[:start],
57
+ node_types: [Prism::BlockNode],
58
+ )
59
+ node = block_context.node
60
+ unless node.is_a?(Prism::BlockNode)
61
+ raise InvalidTargetRangeError, "Cursor is not inside a block"
62
+ end
55
63
 
56
- target = @document.locate_first_within_range(
57
- @code_action.dig(:data, :range),
58
- node_types: [Prism::CallNode],
59
- )
64
+ # Find the call node at the block node's start position.
65
+ # This should be the call node whose block the cursor is inside of.
66
+ call_context = RubyDocument.locate(
67
+ @document.ast,
68
+ node.location.cached_start_code_units_offset(@document.code_units_cache),
69
+ node_types: [Prism::CallNode],
70
+ code_units_cache: @document.code_units_cache,
71
+ )
72
+ target = call_context.node
73
+ unless target.is_a?(Prism::CallNode) && target.block == node
74
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
75
+ end
76
+ else
77
+ target = @document.locate_first_within_range(
78
+ @code_action.dig(:data, :range),
79
+ node_types: [Prism::CallNode],
80
+ )
60
81
 
61
- unless target.is_a?(Prism::CallNode)
62
- raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
63
- end
82
+ unless target.is_a?(Prism::CallNode)
83
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
84
+ end
64
85
 
65
- node = target.block
66
- unless node.is_a?(Prism::BlockNode)
67
- raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
86
+ node = target.block
87
+ unless node.is_a?(Prism::BlockNode)
88
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
89
+ end
68
90
  end
69
91
 
70
92
  indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
@@ -63,12 +63,8 @@ module RubyLsp
63
63
  kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
64
64
  data: { range: @range, uri: @uri.to_s },
65
65
  )
66
- code_actions << Interface::CodeAction.new(
67
- title: TOGGLE_BLOCK_STYLE_TITLE,
68
- kind: Constant::CodeActionKind::REFACTOR_REWRITE,
69
- data: { range: @range, uri: @uri.to_s },
70
- )
71
66
  end
67
+ code_actions.concat(toggle_block_style_action)
72
68
  code_actions.concat(attribute_actions)
73
69
 
74
70
  code_actions
@@ -113,6 +109,25 @@ module RubyLsp
113
109
  ),
114
110
  ]
115
111
  end
112
+
113
+ #: -> Array[Interface::CodeAction]
114
+ def toggle_block_style_action
115
+ if @range[:start] == @range[:end]
116
+ block_context = @document.locate_node(
117
+ @range[:start],
118
+ node_types: [Prism::BlockNode],
119
+ )
120
+ return [] unless block_context.node.is_a?(Prism::BlockNode)
121
+ end
122
+
123
+ [
124
+ Interface::CodeAction.new(
125
+ title: TOGGLE_BLOCK_STYLE_TITLE,
126
+ kind: Constant::CodeActionKind::REFACTOR_REWRITE,
127
+ data: { range: @range, uri: @uri.to_s },
128
+ ),
129
+ ]
130
+ end
116
131
  end
117
132
  end
118
133
  end
@@ -68,10 +68,12 @@ module RubyLsp
68
68
  "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
69
69
  end
70
70
 
71
- @item[:documentation] = Interface::MarkupContent.new(
72
- kind: "markdown",
73
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
74
- )
71
+ unless @item[:kind] == Constant::CompletionItemKind::FILE
72
+ @item[:documentation] = Interface::MarkupContent.new(
73
+ kind: "markdown",
74
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
75
+ )
76
+ end
75
77
 
76
78
  @item
77
79
  end
@@ -84,7 +86,7 @@ module RubyLsp
84
86
  content = KEYWORD_DOCS[keyword]
85
87
 
86
88
  if content
87
- doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
89
+ doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
88
90
 
89
91
  @item[:documentation] = Interface::MarkupContent.new(
90
92
  kind: "markdown",
@@ -93,7 +95,7 @@ module RubyLsp
93
95
  #{keyword}
94
96
  ```
95
97
 
96
- [Read more](#{doc_path})
98
+ [Read more](#{doc_uri})
97
99
 
98
100
  #{content}
99
101
  MARKDOWN