ruby-lsp 0.7.6 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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