ruby-lsp 0.7.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a645362acbfb8b0f819bd89fbaf1e53d0f7d9eda81974e62bc0f114aeefc221e
4
- data.tar.gz: 861b5c68f9355f551ebfb519507ca1c0028f0e4e2edff70ae6ad62c652bdfd2c
3
+ metadata.gz: ed62114ae68bf84cf95f96a2517bc2e729ebaf572500b3871f253bc4b584d7ba
4
+ data.tar.gz: d34dd2719e3b8088f59f4c38988b3fa7058c866a0d34b8b0b0ea391e18ec64d2
5
5
  SHA512:
6
- metadata.gz: 4283abdb1ce9865e6dbecd0827ddd88f07a63937a9f75ba8ca78142d3536624ac374b165e7a1277adee4197553c74615ed560dad3093eb346ae3e505e174dae5
7
- data.tar.gz: 6084fd63d0d780c169b8f8766f347cdb699e2490a0c8de542a3fa969ad37d7f49e281ffa7f92a7ee15a764dccac9832e5e9a49265ba9077f9d204185d0c00e64
6
+ metadata.gz: d9d7bdf1a6ce4d3a7711a0032236d25d3b79682239f801354c15c01661a8e3b1110be90dd503e1d75dcd442520893d6cc852ef5a1eaeeb5a0834305a1c3350c7
7
+ data.tar.gz: f55f82e138cb65fb7450afe86092d294bca06bcd6043439723567ed6538a1b373eed10ba5f3702e9d17f16fc4faa6b5447c048fb5683388e52bea1588d23dc63
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.6
1
+ 0.8.0
data/exe/ruby-lsp-check CHANGED
@@ -30,13 +30,13 @@ message_queue = Thread::Queue.new
30
30
  executor = RubyLsp::Executor.new(store, message_queue)
31
31
 
32
32
  files.each_with_index do |file, index|
33
- uri = "file://#{file}"
33
+ uri = URI("file://#{file}")
34
34
  store.set(uri: uri, source: File.read(file), version: 1)
35
35
 
36
36
  # Executing any of the automatic requests will execute all of them, so here we just pick one
37
37
  result = executor.execute({
38
38
  method: "textDocument/documentSymbol",
39
- params: { textDocument: { uri: uri } },
39
+ params: { textDocument: { uri: uri.to_s } },
40
40
  })
41
41
 
42
42
  error = result.error
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module URI
5
+ class Generic
6
+ class << self
7
+ extend T::Sig
8
+
9
+ sig { params(path: String, scheme: String).returns(URI::Generic) }
10
+ def from_path(path:, scheme: "file")
11
+ # On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
12
+ escaped_path = if /^[A-Z]:/.match?(path)
13
+ DEFAULT_PARSER.escape("/#{path}")
14
+ else
15
+ DEFAULT_PARSER.escape(path)
16
+ end
17
+
18
+ build(scheme: scheme, path: escaped_path)
19
+ end
20
+ end
21
+
22
+ extend T::Sig
23
+
24
+ sig { returns(T.nilable(String)) }
25
+ def to_standardized_path
26
+ parsed_path = path
27
+ return unless parsed_path
28
+
29
+ # On Windows, when we're getting the file system path back from the URI, we need to remove the leading forward
30
+ # slash
31
+ actual_path = if %r{^/[A-Z]:}.match?(parsed_path)
32
+ parsed_path.delete_prefix("/")
33
+ else
34
+ parsed_path
35
+ end
36
+
37
+ CGI.unescape(actual_path)
38
+ end
39
+
40
+ sig { returns(String) }
41
+ def storage_key
42
+ T.must(to_standardized_path || opaque)
43
+ end
44
+ end
45
+ end
@@ -18,16 +18,16 @@ module RubyLsp
18
18
  sig { returns(Integer) }
19
19
  attr_reader :version
20
20
 
21
- sig { returns(String) }
21
+ sig { returns(URI::Generic) }
22
22
  attr_reader :uri
23
23
 
24
- sig { params(source: String, version: Integer, uri: String, encoding: String).void }
24
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
25
25
  def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
26
  @cache = T.let({}, T::Hash[String, T.untyped])
27
27
  @encoding = T.let(encoding, String)
28
28
  @source = T.let(source, String)
29
29
  @version = T.let(version, Integer)
30
- @uri = T.let(uri, String)
30
+ @uri = T.let(uri, URI::Generic)
31
31
  @unparsed_edits = T.let([], T::Array[EditShape])
32
32
  @syntax_error = T.let(false, T::Boolean)
33
33
  @tree = T.let(SyntaxTree.parse(@source), T.nilable(SyntaxTree::Node))
@@ -35,7 +35,7 @@ module RubyLsp
35
35
 
36
36
  sig { params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
37
37
  def run(request)
38
- uri = request.dig(:params, :textDocument, :uri)
38
+ uri = URI(request.dig(:params, :textDocument, :uri).to_s)
39
39
 
40
40
  case request[:method]
41
41
  when "initialize"
@@ -70,7 +70,7 @@ module RubyLsp
70
70
  when "textDocument/didClose"
71
71
  @message_queue << Notification.new(
72
72
  message: "textDocument/publishDiagnostics",
73
- params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
73
+ params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
74
74
  )
75
75
 
76
76
  text_document_did_close(uri)
@@ -174,12 +174,12 @@ module RubyLsp
174
174
  end
175
175
  end
176
176
 
177
- sig { params(uri: String, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
177
+ sig { params(uri: URI::Generic, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
178
178
  def show_syntax_tree(uri, range)
179
179
  { ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
180
180
  end
181
181
 
182
- sig { params(uri: String, position: Document::PositionShape).returns(T.nilable(Interface::Location)) }
182
+ sig { params(uri: URI::Generic, position: Document::PositionShape).returns(T.nilable(Interface::Location)) }
183
183
  def definition(uri, position)
184
184
  document = @store.get(uri)
185
185
  return if document.syntax_error?
@@ -192,7 +192,7 @@ module RubyLsp
192
192
  base_listener.response
193
193
  end
194
194
 
195
- sig { params(uri: String).returns(T::Array[Interface::FoldingRange]) }
195
+ sig { params(uri: URI::Generic).returns(T::Array[Interface::FoldingRange]) }
196
196
  def folding_range(uri)
197
197
  @store.cache_fetch(uri, "textDocument/foldingRange") do |document|
198
198
  Requests::FoldingRanges.new(document).run
@@ -201,7 +201,7 @@ module RubyLsp
201
201
 
202
202
  sig do
203
203
  params(
204
- uri: String,
204
+ uri: URI::Generic,
205
205
  position: Document::PositionShape,
206
206
  ).returns(T.nilable(Interface::Hover))
207
207
  end
@@ -227,19 +227,19 @@ module RubyLsp
227
227
  hover.response
228
228
  end
229
229
 
230
- sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
230
+ sig { params(uri: URI::Generic, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
231
231
  def text_document_did_change(uri, content_changes, version)
232
232
  @store.push_edits(uri: uri, edits: content_changes, version: version)
233
233
  VOID
234
234
  end
235
235
 
236
- sig { params(uri: String, text: String, version: Integer).returns(Object) }
236
+ sig { params(uri: URI::Generic, text: String, version: Integer).returns(Object) }
237
237
  def text_document_did_open(uri, text, version)
238
238
  @store.set(uri: uri, source: text, version: version)
239
239
  VOID
240
240
  end
241
241
 
242
- sig { params(uri: String).returns(Object) }
242
+ sig { params(uri: URI::Generic).returns(Object) }
243
243
  def text_document_did_close(uri)
244
244
  @store.delete(uri)
245
245
  VOID
@@ -247,7 +247,7 @@ module RubyLsp
247
247
 
248
248
  sig do
249
249
  params(
250
- uri: String,
250
+ uri: URI::Generic,
251
251
  positions: T::Array[Document::PositionShape],
252
252
  ).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
253
253
  end
@@ -270,7 +270,7 @@ module RubyLsp
270
270
  end
271
271
  end
272
272
 
273
- sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
273
+ sig { params(uri: URI::Generic).returns(T.nilable(T::Array[Interface::TextEdit])) }
274
274
  def formatting(uri)
275
275
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
276
276
  return if @store.formatter == "none"
@@ -280,7 +280,7 @@ module RubyLsp
280
280
 
281
281
  sig do
282
282
  params(
283
- uri: String,
283
+ uri: URI::Generic,
284
284
  position: Document::PositionShape,
285
285
  character: String,
286
286
  ).returns(T::Array[Interface::TextEdit])
@@ -291,7 +291,7 @@ module RubyLsp
291
291
 
292
292
  sig do
293
293
  params(
294
- uri: String,
294
+ uri: URI::Generic,
295
295
  position: Document::PositionShape,
296
296
  ).returns(T.nilable(T::Array[Interface::DocumentHighlight]))
297
297
  end
@@ -306,7 +306,7 @@ module RubyLsp
306
306
  listener.response
307
307
  end
308
308
 
309
- sig { params(uri: String, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
309
+ sig { params(uri: URI::Generic, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
310
310
  def inlay_hint(uri, range)
311
311
  document = @store.get(uri)
312
312
  return if document.syntax_error?
@@ -322,7 +322,7 @@ module RubyLsp
322
322
 
323
323
  sig do
324
324
  params(
325
- uri: String,
325
+ uri: URI::Generic,
326
326
  range: Document::RangeShape,
327
327
  context: T::Hash[Symbol, T.untyped],
328
328
  ).returns(T.nilable(T::Array[Interface::CodeAction]))
@@ -335,7 +335,7 @@ module RubyLsp
335
335
 
336
336
  sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
337
337
  def code_action_resolve(params)
338
- uri = params.dig(:data, :uri)
338
+ uri = URI(params.dig(:data, :uri))
339
339
  document = @store.get(uri)
340
340
  result = Requests::CodeActionResolve.new(document, params).run
341
341
 
@@ -363,7 +363,7 @@ module RubyLsp
363
363
  end
364
364
  end
365
365
 
366
- sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
366
+ sig { params(uri: URI::Generic).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
367
367
  def diagnostic(uri)
368
368
  response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
369
369
  Requests::Diagnostics.new(document).run
@@ -372,7 +372,7 @@ module RubyLsp
372
372
  Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response.map(&:to_lsp_diagnostic)) if response
373
373
  end
374
374
 
375
- sig { params(uri: String, range: Document::RangeShape).returns(Interface::SemanticTokens) }
375
+ sig { params(uri: URI::Generic, range: Document::RangeShape).returns(Interface::SemanticTokens) }
376
376
  def semantic_tokens_range(uri, range)
377
377
  document = @store.get(uri)
378
378
  start_line = range.dig(:start, :line)
@@ -390,7 +390,10 @@ module RubyLsp
390
390
  end
391
391
 
392
392
  sig do
393
- params(uri: String, position: Document::PositionShape).returns(T.nilable(T::Array[Interface::CompletionItem]))
393
+ params(
394
+ uri: URI::Generic,
395
+ position: Document::PositionShape,
396
+ ).returns(T.nilable(T::Array[Interface::CompletionItem]))
394
397
  end
395
398
  def completion(uri, position)
396
399
  document = @store.get(uri)
@@ -97,8 +97,32 @@ module RubyLsp
97
97
  sig { abstract.void }
98
98
  def activate; end
99
99
 
100
+ # Each extension should implement `MyExtension#deactivate` and use to perform any clean up, like shutting down a
101
+ # child process
102
+ sig { abstract.void }
103
+ def deactivate; end
104
+
100
105
  # Extensions should override the `name` method to return the extension name
101
106
  sig { abstract.returns(String) }
102
107
  def name; end
108
+
109
+ # Creates a new CodeLens listener. This method is invoked on every CodeLens request
110
+ sig do
111
+ overridable.params(
112
+ uri: URI::Generic,
113
+ emitter: EventEmitter,
114
+ message_queue: Thread::Queue,
115
+ ).returns(T.nilable(Listener[T::Array[Interface::CodeLens]]))
116
+ end
117
+ def create_code_lens_listener(uri, emitter, message_queue); end
118
+
119
+ # Creates a new Hover listener. This method is invoked on every Hover request
120
+ sig do
121
+ overridable.params(
122
+ emitter: EventEmitter,
123
+ message_queue: Thread::Queue,
124
+ ).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
125
+ end
126
+ def create_hover_listener(emitter, message_queue); end
103
127
  end
104
128
  end
@@ -7,8 +7,10 @@ require "language_server-protocol"
7
7
  require "benchmark"
8
8
  require "bundler"
9
9
  require "uri"
10
+ require "cgi"
10
11
 
11
12
  require "ruby-lsp"
13
+ require "core_ext/uri"
12
14
  require "ruby_lsp/utils"
13
15
  require "ruby_lsp/server"
14
16
  require "ruby_lsp/executor"
@@ -18,25 +18,26 @@ module RubyLsp
18
18
  def initialize(emitter, message_queue)
19
19
  @emitter = emitter
20
20
  @message_queue = message_queue
21
- end
22
-
23
- class << self
24
- extend T::Sig
25
-
26
- sig { returns(T::Array[T.class_of(Listener)]) }
27
- def listeners
28
- @listeners ||= T.let([], T.nilable(T::Array[T.class_of(Listener)]))
29
- end
30
-
31
- sig { params(listener: T.class_of(Listener)).void }
32
- def add_listener(listener)
33
- listeners << listener
34
- end
21
+ @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
35
22
  end
36
23
 
37
24
  # Override this method with an attr_reader that returns the response of your listener. The listener should
38
25
  # accumulate results in a @response variable and then provide the reader so that it is accessible
39
26
  sig { abstract.returns(ResponseType) }
40
27
  def response; end
28
+
29
+ # Merge responses from all external listeners into the base listener's response. We do this to return a single
30
+ # response to the editor including the results of all extensions
31
+ sig { void }
32
+ def merge_external_listeners_responses!
33
+ @external_listeners.each { |l| merge_response!(l) }
34
+ end
35
+
36
+ # Does nothing by default. Requests that accept extensions should override this method to define how to merge
37
+ # responses coming from external listeners
38
+ sig { overridable.params(other: Listener[T.untyped]).returns(T.self_type) }
39
+ def merge_response!(other)
40
+ self
41
+ end
41
42
  end
42
43
  end
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  def initialize(document, range, context)
30
30
  super(document)
31
31
 
32
- @uri = T.let(document.uri, String)
32
+ @uri = T.let(document.uri, URI::Generic)
33
33
  @range = range
34
34
  @context = context
35
35
  end
@@ -63,14 +63,14 @@ module RubyLsp
63
63
  )
64
64
  end
65
65
 
66
- sig { params(range: Document::RangeShape, uri: String).returns(Interface::CodeAction) }
66
+ sig { params(range: Document::RangeShape, uri: URI::Generic).returns(Interface::CodeAction) }
67
67
  def refactor_code_action(range, uri)
68
68
  Interface::CodeAction.new(
69
69
  title: "Refactor: Extract Variable",
70
70
  kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
71
71
  data: {
72
72
  range: range,
73
- uri: uri,
73
+ uri: uri.to_s,
74
74
  },
75
75
  )
76
76
  end
@@ -31,15 +31,17 @@ module RubyLsp
31
31
  sig { override.returns(ResponseType) }
32
32
  attr_reader :response
33
33
 
34
- sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue, test_library: String).void }
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
36
  super(emitter, message_queue)
37
37
 
38
- @uri = T.let(uri, String)
39
- @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
38
+ @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
+ )
40
42
  @test_library = T.let(test_library, String)
41
43
  @response = T.let([], ResponseType)
42
- @path = T.let(T.must(URI(uri).path), String)
44
+ @path = T.let(uri.to_standardized_path, T.nilable(String))
43
45
  # visibility_stack is a stack of [current_visibility, previous_visibility]
44
46
  @visibility_stack = T.let([["public", "public"]], T::Array[T::Array[T.nilable(String)]])
45
47
  @class_stack = T.let([], T::Array[String])
@@ -55,22 +57,6 @@ module RubyLsp
55
57
  :after_call,
56
58
  :on_vcall,
57
59
  )
58
-
59
- register_external_listeners!
60
- end
61
-
62
- sig { void }
63
- def register_external_listeners!
64
- self.class.listeners.each do |l|
65
- @external_listeners << T.unsafe(l).new(@uri, @emitter, @message_queue)
66
- end
67
- end
68
-
69
- sig { void }
70
- def merge_external_listeners_responses!
71
- @external_listeners.each do |l|
72
- merge_response!(l)
73
- end
74
60
  end
75
61
 
76
62
  sig { params(node: SyntaxTree::ClassDeclaration).void }
@@ -120,7 +106,7 @@ module RubyLsp
120
106
  if ACCESS_MODIFIERS.include?(node_message) && node.arguments.parts.any?
121
107
  visibility, _ = @visibility_stack.pop
122
108
  @visibility_stack.push([node_message, visibility])
123
- elsif @path.include?("Gemfile") && node_message.include?("gem") && node.arguments.parts.any?
109
+ elsif @path&.include?("Gemfile") && node_message.include?("gem") && node.arguments.parts.any?
124
110
  remote = resolve_gem_remote(node)
125
111
  return unless remote
126
112
 
@@ -163,7 +149,7 @@ module RubyLsp
163
149
  end
164
150
  end
165
151
 
166
- sig { params(other: Listener[ResponseType]).returns(T.self_type) }
152
+ sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
167
153
  def merge_response!(other)
168
154
  @response.concat(other.response)
169
155
  self
@@ -174,7 +160,7 @@ module RubyLsp
174
160
  sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
175
161
  def add_test_code_lens(node, name:, command:, kind:)
176
162
  # don't add code lenses if the test library is not supported or unknown
177
- return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library)
163
+ return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library) && @path
178
164
 
179
165
  arguments = [
180
166
  @path,
@@ -231,7 +217,7 @@ module RubyLsp
231
217
 
232
218
  sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
233
219
  def generate_test_command(class_name:, method_name: nil)
234
- command = BASE_COMMAND + @path
220
+ command = BASE_COMMAND + T.must(@path)
235
221
 
236
222
  case @test_library
237
223
  when "minitest"
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  sig { override.returns(ResponseType) }
26
26
  attr_reader :response
27
27
 
28
- sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
28
+ sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
29
29
  def initialize(uri, emitter, message_queue)
30
30
  super(emitter, message_queue)
31
31
 
@@ -53,7 +53,7 @@ module RubyLsp
53
53
 
54
54
  if candidate
55
55
  @response = Interface::Location.new(
56
- uri: "file://#{candidate}",
56
+ uri: URI::Generic.from_path(path: candidate).to_s,
57
57
  range: Interface::Range.new(
58
58
  start: Interface::Position.new(line: 0, character: 0),
59
59
  end: Interface::Position.new(line: 0, character: 0),
@@ -61,13 +61,13 @@ module RubyLsp
61
61
  )
62
62
  end
63
63
  when "require_relative"
64
- current_file = T.must(URI.parse(@uri).path)
65
- current_folder = Pathname.new(current_file).dirname
64
+ path = @uri.to_standardized_path
65
+ current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
66
66
  candidate = File.expand_path(File.join(current_folder, required_file))
67
67
 
68
68
  if candidate
69
69
  @response = Interface::Location.new(
70
- uri: "file://#{candidate}",
70
+ uri: URI::Generic.from_path(path: candidate).to_s,
71
71
  range: Interface::Range.new(
72
72
  start: Interface::Position.new(line: 0, character: 0),
73
73
  end: Interface::Position.new(line: 0, character: 0),
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  def initialize(document)
26
26
  super(document)
27
27
 
28
- @uri = T.let(document.uri, String)
28
+ @uri = T.let(document.uri, URI::Generic)
29
29
  end
30
30
 
31
31
  sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
@@ -36,7 +36,8 @@ module RubyLsp
36
36
  return unless defined?(Support::RuboCopDiagnosticsRunner)
37
37
 
38
38
  # Don't try to run RuboCop diagnostics for files outside the current working directory
39
- return unless URI(@uri).path&.start_with?(T.must(WORKSPACE_URI.path))
39
+ path = @uri.to_standardized_path
40
+ return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
40
41
 
41
42
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
42
43
  end
@@ -75,13 +75,14 @@ module RubyLsp
75
75
  sig { override.returns(ResponseType) }
76
76
  attr_reader :response
77
77
 
78
- sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
78
+ sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
79
79
  def initialize(uri, emitter, message_queue)
80
80
  super(emitter, message_queue)
81
81
 
82
82
  # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
83
83
  # in the URI
84
- version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(uri)
84
+ path = uri.to_standardized_path
85
+ version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
85
86
  @gem_version = T.let(version_match && version_match[0], T.nilable(String))
86
87
  @response = T.let([], T::Array[Interface::DocumentLink])
87
88
 
@@ -95,7 +96,7 @@ module RubyLsp
95
96
 
96
97
  uri = T.cast(URI(T.must(match[0])), URI::Source)
97
98
  gem_version = T.must(resolve_version(uri))
98
- file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
99
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
99
100
  return if file_path.nil?
100
101
 
101
102
  @response << Interface::DocumentLink.new(
@@ -58,7 +58,7 @@ module RubyLsp
58
58
  def initialize(document, formatter: "auto")
59
59
  super(document)
60
60
 
61
- @uri = T.let(document.uri, String)
61
+ @uri = T.let(document.uri, URI::Generic)
62
62
  @formatter = formatter
63
63
  end
64
64
 
@@ -67,7 +67,8 @@ module RubyLsp
67
67
  return if @formatter == "none"
68
68
 
69
69
  # Don't try to format files outside the current working directory
70
- return unless @uri.sub("file://", "").start_with?(Dir.pwd)
70
+ path = @uri.to_standardized_path
71
+ return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
71
72
 
72
73
  return if @document.syntax_error?
73
74
 
@@ -39,29 +39,15 @@ module RubyLsp
39
39
  def initialize(emitter, message_queue)
40
40
  super
41
41
 
42
- @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
42
+ @external_listeners.concat(
43
+ Extension.extensions.filter_map { |ext| ext.create_hover_listener(emitter, message_queue) },
44
+ )
43
45
  @response = T.let(nil, ResponseType)
44
46
  emitter.register(self, :on_command, :on_const_path_ref, :on_call)
45
-
46
- register_external_listeners!
47
- end
48
-
49
- sig { void }
50
- def register_external_listeners!
51
- self.class.listeners.each do |l|
52
- @external_listeners << T.unsafe(l).new(@emitter, @message_queue)
53
- end
54
- end
55
-
56
- sig { void }
57
- def merge_external_listeners_responses!
58
- @external_listeners.each do |l|
59
- merge_response!(l)
60
- end
61
47
  end
62
48
 
63
49
  # Merges responses from other hover listeners
64
- sig { params(other: Listener[ResponseType]).returns(T.self_type) }
50
+ sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
65
51
  def merge_response!(other)
66
52
  other_response = other.response
67
53
  return self unless other_response
@@ -10,7 +10,7 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: String, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
14
14
  def run(uri, document); end
15
15
  end
16
16
  end
@@ -19,7 +19,7 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
22
+ sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
23
23
  def initialize(offense, uri)
24
24
  @offense = offense
25
25
  @uri = uri
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  document_changes: [
35
35
  Interface::TextDocumentEdit.new(
36
36
  text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
37
- uri: @uri,
37
+ uri: @uri.to_s,
38
38
  version: nil,
39
39
  ),
40
40
  edits: @offense.correctable? ? offense_replacements : [],
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -19,9 +18,9 @@ module RubyLsp
19
18
  @runner = T.let(RuboCopRunner.new, RuboCopRunner)
20
19
  end
21
20
 
22
- sig { params(uri: String, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
21
+ sig { params(uri: URI::Generic, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
23
22
  def run(uri, document)
24
- filename = CGI.unescape(URI.parse(uri).path)
23
+ filename = T.must(uri.to_standardized_path || uri.opaque)
25
24
  # Invoke RuboCop with just this file in `paths`
26
25
  @runner.run(filename, document.source)
27
26
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -21,9 +20,9 @@ module RubyLsp
21
20
  @runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
22
21
  end
23
22
 
24
- sig { override.params(uri: String, document: Document).returns(String) }
23
+ sig { override.params(uri: URI::Generic, document: Document).returns(String) }
25
24
  def run(uri, document)
26
- filename = CGI.unescape(URI.parse(uri).path)
25
+ filename = T.must(uri.to_standardized_path || uri.opaque)
27
26
 
28
27
  # Invoke RuboCop with just this file in `paths`
29
28
  @runner.run(filename, document.source)
@@ -26,9 +26,10 @@ module RubyLsp
26
26
  )
27
27
  end
28
28
 
29
- sig { override.params(uri: String, document: Document).returns(T.nilable(String)) }
29
+ sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
30
30
  def run(uri, document)
31
- relative_path = Pathname.new(URI(uri).path).relative_path_from(T.must(WORKSPACE_URI.path))
31
+ relative_path = Pathname.new(T.must(uri.to_standardized_path || uri.opaque))
32
+ .relative_path_from(T.must(WORKSPACE_URI.to_standardized_path))
32
33
  return if @options.ignore_files.any? { |pattern| File.fnmatch(pattern, relative_path) }
33
34
 
34
35
  SyntaxTree.format(
@@ -86,6 +86,7 @@ module RubyLsp
86
86
  @message_dispatcher.join
87
87
  @store.clear
88
88
 
89
+ Extension.extensions.each(&:deactivate)
89
90
  finalize_request(Result.new(response: nil), request)
90
91
  when "exit"
91
92
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
@@ -105,7 +106,7 @@ module RubyLsp
105
106
  # source. Altering the source reference during parsing will put the parser in an invalid internal state,
106
107
  # since it started parsing with one source but then it changed in the middle
107
108
  uri = request.dig(:params, :textDocument, :uri)
108
- @store.get(uri).parse if uri
109
+ @store.get(URI(uri)).parse if uri
109
110
  end
110
111
 
111
112
  @job_queue << job
@@ -192,7 +193,14 @@ module RubyLsp
192
193
  params[:backtrace] = backtrace.map { |bt| bt.sub(/^#{Dir.home}/, "~") }.join("\n") if backtrace
193
194
  end
194
195
 
195
- params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
196
+ if uri
197
+ home = URI::Generic.from_path(path: Dir.home)
198
+
199
+ parsed_uri = URI(uri)
200
+ path = parsed_uri.path
201
+ params[:uri] = path ? path.sub(T.must(home.path), "~") : parsed_uri.opaque
202
+ end
203
+
196
204
  params
197
205
  end
198
206
  end
@@ -5,6 +5,7 @@ require "sorbet-runtime"
5
5
  require "bundler"
6
6
  require "fileutils"
7
7
  require "pathname"
8
+ require "digest"
8
9
 
9
10
  # This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
10
11
  # the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
@@ -24,6 +25,7 @@ module RubyLsp
24
25
  @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
25
26
  @custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
26
27
  @custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
28
+ @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
27
29
 
28
30
  # Regular bundle paths
29
31
  @gemfile = T.let(
@@ -37,7 +39,6 @@ module RubyLsp
37
39
  @lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
38
40
 
39
41
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
40
- @custom_bundle_dependencies = T.let(custom_bundle_dependencies, T::Hash[String, T.untyped])
41
42
  end
42
43
 
43
44
  # Setups up the custom bundle and returns the `BUNDLE_GEMFILE` and `BUNDLE_PATH` that should be used for running the
@@ -69,14 +70,16 @@ module RubyLsp
69
70
  return run_bundle_install(@custom_gemfile)
70
71
  end
71
72
 
72
- # If .ruby-lsp/Gemfile.lock already exists and the top level Gemfile.lock hasn't been modified since it was last
73
- # updated, then we're ready to boot the server
74
- if @custom_lockfile.exist? && @custom_lockfile.stat.mtime > @lockfile.stat.mtime
73
+ lockfile_contents = @lockfile.read
74
+ current_lockfile_hash = Digest::SHA256.hexdigest(lockfile_contents)
75
+
76
+ if @custom_lockfile.exist? && @lockfile_hash_path.exist? && @lockfile_hash_path.read == current_lockfile_hash
75
77
  warn("Ruby LSP> Skipping custom bundle setup since #{@custom_lockfile} already exists and is up to date")
76
78
  return run_bundle_install(@custom_gemfile)
77
79
  end
78
80
 
79
81
  FileUtils.cp(@lockfile.to_s, @custom_lockfile.to_s)
82
+ @lockfile_hash_path.write(current_lockfile_hash)
80
83
  run_bundle_install(@custom_gemfile)
81
84
  end
82
85
 
@@ -84,10 +87,17 @@ module RubyLsp
84
87
 
85
88
  sig { returns(T::Hash[String, T.untyped]) }
86
89
  def custom_bundle_dependencies
87
- return {} unless @custom_lockfile.exist?
88
-
89
- ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
90
- Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
90
+ @custom_bundle_dependencies ||= T.let(
91
+ begin
92
+ if @custom_lockfile.exist?
93
+ ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
94
+ Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
95
+ else
96
+ {}
97
+ end
98
+ end,
99
+ T.nilable(T::Hash[String, T.untyped]),
100
+ )
91
101
  ensure
92
102
  ENV.delete("BUNDLE_GEMFILE")
93
103
  end
@@ -160,7 +170,7 @@ module RubyLsp
160
170
  command = +""
161
171
 
162
172
  if (@dependencies["ruby-lsp"] && @dependencies["debug"]) ||
163
- @custom_bundle_dependencies["ruby-lsp"].nil? || @custom_bundle_dependencies["debug"].nil?
173
+ custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
164
174
  # Install gems using the custom bundle
165
175
  command << "bundle install "
166
176
  else
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cgi"
5
- require "uri"
6
4
  require "ruby_lsp/document"
7
5
 
8
6
  module RubyLsp
@@ -22,24 +20,27 @@ module RubyLsp
22
20
  @formatter = T.let("auto", String)
23
21
  end
24
22
 
25
- sig { params(uri: String).returns(Document) }
23
+ sig { params(uri: URI::Generic).returns(Document) }
26
24
  def get(uri)
27
- document = @state[uri]
25
+ path = uri.to_standardized_path
26
+ return T.must(@state[T.must(uri.opaque)]) unless path
27
+
28
+ document = @state[path]
28
29
  return document unless document.nil?
29
30
 
30
- set(uri: uri, source: File.binread(CGI.unescape(URI.parse(uri).path)), version: 0)
31
- T.must(@state[uri])
31
+ set(uri: uri, source: File.binread(CGI.unescape(path)), version: 0)
32
+ T.must(@state[path])
32
33
  end
33
34
 
34
- sig { params(uri: String, source: String, version: Integer).void }
35
+ sig { params(uri: URI::Generic, source: String, version: Integer).void }
35
36
  def set(uri:, source:, version:)
36
37
  document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
37
- @state[uri] = document
38
+ @state[uri.storage_key] = document
38
39
  end
39
40
 
40
- sig { params(uri: String, edits: T::Array[Document::EditShape], version: Integer).void }
41
+ sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
41
42
  def push_edits(uri:, edits:, version:)
42
- T.must(@state[uri]).push_edits(edits, version: version)
43
+ T.must(@state[uri.storage_key]).push_edits(edits, version: version)
43
44
  end
44
45
 
45
46
  sig { void }
@@ -52,15 +53,15 @@ module RubyLsp
52
53
  @state.empty?
53
54
  end
54
55
 
55
- sig { params(uri: String).void }
56
+ sig { params(uri: URI::Generic).void }
56
57
  def delete(uri)
57
- @state.delete(uri)
58
+ @state.delete(uri.storage_key)
58
59
  end
59
60
 
60
61
  sig do
61
62
  type_parameters(:T)
62
63
  .params(
63
- uri: String,
64
+ uri: URI::Generic,
64
65
  request_name: String,
65
66
  block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
66
67
  ).returns(T.type_parameter(:T))
@@ -6,7 +6,7 @@ module RubyLsp
6
6
  VOID = T.let(Object.new.freeze, Object)
7
7
 
8
8
  # This freeze is not redundant since the interpolated string is mutable
9
- WORKSPACE_URI = T.let(URI("file://#{Dir.pwd}".freeze), URI::Generic)
9
+ WORKSPACE_URI = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
10
10
 
11
11
  # A notification to be sent to the client
12
12
  class Message
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.7.6
4
+ version: 0.8.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-08-02 00:00:00.000000000 Z
11
+ date: 2023-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -72,6 +72,7 @@ files:
72
72
  - VERSION
73
73
  - exe/ruby-lsp
74
74
  - exe/ruby-lsp-check
75
+ - lib/core_ext/uri.rb
75
76
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
76
77
  - lib/ruby-lsp.rb
77
78
  - lib/ruby_lsp/check_docs.rb