ruby-lsp 0.26.2 → 0.26.3

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: a3c596f1104438061fe9ee2b890b980a7048e2137c10d34783d55defa0f17354
4
- data.tar.gz: b9f328146d02d36436a48385f1156a3a94ce9f321123220a2316a794ab253695
3
+ metadata.gz: 90c12f93d4750216ef84e046fe970f0ccc8edd7567943b9f4be02dc3828fd151
4
+ data.tar.gz: ef810d7d1599f9539474e87067c5972ef2aa3a6143452494e9d03857d64771e7
5
5
  SHA512:
6
- metadata.gz: 21c6b94baa433cf5c944a96f2ef8829f36ec81b0a093fb1e6fb5c53537698df6adc936f71ff2856bc68411d9ccefbd143cb75ee703d8d2b5a171e940faada191
7
- data.tar.gz: 7e84f101f485088c24e2afccaaa779f8cfdae3bb91798ec24f30199402bc3bbd5e142ece40a86de7845cb342b31d68be5c953383b0a0b081bf388b0574b25ab9
6
+ metadata.gz: 4a6ff236ecaabfd5e927a6f57f47d97ad6822dc647ce3531c57565adfc05fd592167decbabfa2ac3420d8294f193188103c7aa3a468b5f92dca7b18a92bb1b87
7
+ data.tar.gz: e34f78b58b73536faecf0bafb0f7a620bbbc3122c8fb8d1b526112b000055b78fe6d153ff0bf1bbd2300aaaffb3f5a6327fd7ef4248332b39ebb5a7c13fe94d5
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.26.2
1
+ 0.26.3
@@ -195,12 +195,13 @@ module RubyIndexer
195
195
  end
196
196
 
197
197
  # Fuzzy searches index entries based on Jaro-Winkler similarity. If no query is provided, all entries are returned
198
- #: (String? query) -> Array[Entry]
199
- def fuzzy_search(query)
198
+ #: (String? query) ?{ (Entry) -> bool? } -> Array[Entry]
199
+ def fuzzy_search(query, &condition)
200
200
  unless query
201
201
  entries = @entries.filter_map do |_name, entries|
202
202
  next if entries.first.is_a?(Entry::SingletonClass)
203
203
 
204
+ entries = entries.select(&condition) if condition
204
205
  entries
205
206
  end
206
207
 
@@ -212,6 +213,9 @@ module RubyIndexer
212
213
  results = @entries.filter_map do |name, entries|
213
214
  next if entries.first.is_a?(Entry::SingletonClass)
214
215
 
216
+ entries = entries.select(&condition) if condition
217
+ next if entries.empty?
218
+
215
219
  similarity = DidYouMean::JaroWinkler.distance(name.gsub("::", "").downcase, normalized_query)
216
220
  [entries, -similarity] if similarity > ENTRY_SIMILARITY_THRESHOLD
217
221
  end
@@ -83,7 +83,7 @@ module RubyLsp
83
83
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
84
84
  # else is pushed into the incoming queue
85
85
  case method
86
- when "initialize", "initialized", "rubyLsp/diagnoseState"
86
+ when "initialize", "initialized", "rubyLsp/diagnoseState", "$/cancelRequest"
87
87
  process_message(message)
88
88
  when "shutdown"
89
89
  @global_state.synchronize do
@@ -154,20 +154,29 @@ module RubyLsp
154
154
  def new_worker
155
155
  Thread.new do
156
156
  while (message = @incoming_queue.pop)
157
- id = message[:id]
158
-
159
- # Check if the request was cancelled before trying to process it
160
- @global_state.synchronize do
161
- if id && @cancelled_requests.include?(id)
162
- send_message(Result.new(id: id, response: nil))
163
- @cancelled_requests.delete(id)
164
- next
165
- end
166
- end
157
+ handle_incoming_message(message)
158
+ end
159
+ end
160
+ end
167
161
 
168
- process_message(message)
162
+ #: (Hash[Symbol, untyped]) -> void
163
+ def handle_incoming_message(message)
164
+ id = message[:id]
165
+
166
+ # Check if the request was cancelled before trying to process it
167
+ @global_state.synchronize do
168
+ if id && @cancelled_requests.include?(id)
169
+ send_message(Error.new(
170
+ id: id,
171
+ code: Constant::ErrorCodes::REQUEST_CANCELLED,
172
+ message: "Request #{id} was cancelled",
173
+ ))
174
+ @cancelled_requests.delete(id)
175
+ return
169
176
  end
170
177
  end
178
+
179
+ process_message(message)
171
180
  end
172
181
 
173
182
  #: ((Result | Error | Notification | Request) message) -> void
@@ -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
@@ -102,19 +103,58 @@ module RubyLsp
102
103
  comment = @lines_to_comments[node.location.start_line - 1]
103
104
  return unless comment
104
105
 
105
- match = comment.location.slice.match(%r{source://.*#\d+$})
106
+ match = comment.location.slice.match(%r{(source://.*#\d+|pkg:gem/.*#.*)$})
106
107
  return unless match
107
108
 
108
- uri = begin
109
- URI(
110
- match[0], #: as !nil
111
- )
109
+ uri_string = match[0] #: as !nil
110
+
111
+ file_path, line_number = if uri_string.start_with?("pkg:gem/")
112
+ parse_package_url(uri_string)
113
+ else
114
+ parse_source_uri(uri_string)
115
+ end
116
+
117
+ return unless file_path
118
+
119
+ @response_builder << Interface::DocumentLink.new(
120
+ range: range_from_location(comment.location),
121
+ target: "file://#{file_path}##{line_number}",
122
+ tooltip: "Jump to #{file_path}##{line_number}",
123
+ )
124
+ end
125
+
126
+ #: (String uri_string) -> [String, String]?
127
+ def parse_package_url(uri_string)
128
+ purl = PackageURL.parse(uri_string) #: as PackageURL?
129
+ return unless purl
130
+
131
+ gem_version = resolve_version(purl.version, purl.name)
132
+ return if gem_version.nil?
133
+
134
+ path, line_number = purl.subpath.split(":", 2)
135
+ return unless path
136
+
137
+ gem_name = purl.name
138
+ return unless gem_name
139
+
140
+ file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
141
+ return if file_path.nil?
142
+
143
+ [file_path, line_number]
144
+ rescue PackageURL::InvalidPackageURL
145
+ nil
146
+ end
147
+
148
+ #: (String uri_string) -> [String, String]?
149
+ def parse_source_uri(uri_string)
150
+ uri = begin
151
+ URI(uri_string)
112
152
  rescue URI::Error
113
153
  nil
114
154
  end #: as URI::Source?
115
155
  return unless uri
116
156
 
117
- gem_version = resolve_version(uri)
157
+ gem_version = resolve_version(uri.gem_version, uri.gem_name)
118
158
  return if gem_version.nil?
119
159
 
120
160
  path = uri.path
@@ -126,28 +166,20 @@ module RubyLsp
126
166
  file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
127
167
  return if file_path.nil?
128
168
 
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
- )
169
+ [file_path, uri.line_number || "0"]
134
170
  end
135
171
 
136
172
  # Try to figure out the gem version for a source:// link. The order of precedence is:
137
173
  # 1. The version in the URI
138
174
  # 2. The version in the RBI file name
139
175
  # 3. The version from the gemspec
140
- #: (URI::Source uri) -> String?
141
- def resolve_version(uri)
142
- version = uri.gem_version
176
+ #: (String? version, String? gem_name) -> String?
177
+ def resolve_version(version, gem_name)
143
178
  return version unless version.nil? || version.empty?
144
179
 
145
180
  return @gem_version unless @gem_version.nil? || @gem_version.empty?
146
181
 
147
- gem_name = uri.gem_name
148
- return unless gem_name
149
-
150
- GEM_TO_VERSION_MAP[gem_name]
182
+ GEM_TO_VERSION_MAP[gem_name.to_s]
151
183
  end
152
184
  end
153
185
  end
@@ -0,0 +1,414 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ # This is a copy of the implementation from the `package_url` gem with the
5
+ # following license. Original source can be found at:
6
+ # https://github.com/package-url/packageurl-ruby/blob/main/lib/package_url.rb
7
+
8
+ # MIT License
9
+ #
10
+ # Copyright (c) 2021 package-url
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in all
20
+ # copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ # SOFTWARE.
29
+
30
+ require "uri"
31
+
32
+ # A package URL, or _purl_, is a URL string used to
33
+ # identify and locate a software package in a mostly universal and uniform way
34
+ # across programing languages, package managers, packaging conventions, tools,
35
+ # APIs and databases.
36
+ #
37
+ # A purl is a URL composed of seven components:
38
+ #
39
+ # ```
40
+ # scheme:type/namespace/name@version?qualifiers#subpath
41
+ # ```
42
+ #
43
+ # For example,
44
+ # the package URL for this Ruby package at version 0.1.0 is
45
+ # `pkg:ruby/mattt/packageurl-ruby@0.1.0`.
46
+ module RubyLsp
47
+ class PackageURL
48
+ # Raised when attempting to parse an invalid package URL string.
49
+ # @see #parse
50
+ class InvalidPackageURL < ArgumentError; end
51
+
52
+ # The URL scheme, which has a constant value of `"pkg"`.
53
+ def scheme
54
+ "pkg"
55
+ end
56
+
57
+ # The package type or protocol, such as `"gem"`, `"npm"`, and `"github"`.
58
+ attr_reader :type
59
+
60
+ # A name prefix, specific to the type of package.
61
+ # For example, an npm scope, a Docker image owner, or a GitHub user.
62
+ attr_reader :namespace
63
+
64
+ # The name of the package.
65
+ attr_reader :name
66
+
67
+ # The version of the package.
68
+ attr_reader :version
69
+
70
+ # Extra qualifying data for a package, specific to the type of package.
71
+ # For example, the operating system or architecture.
72
+ attr_reader :qualifiers
73
+
74
+ # An extra subpath within a package, relative to the package root.
75
+ attr_reader :subpath
76
+
77
+ # Constructs a package URL from its components
78
+ # @param type [String] The package type or protocol.
79
+ # @param namespace [String] A name prefix, specific to the type of package.
80
+ # @param name [String] The name of the package.
81
+ # @param version [String] The version of the package.
82
+ # @param qualifiers [Hash] Extra qualifying data for a package, specific to the type of package.
83
+ # @param subpath [String] An extra subpath within a package, relative to the package root.
84
+ def initialize(type:, name:, namespace: nil, version: nil, qualifiers: nil, subpath: nil)
85
+ raise ArgumentError, "type is required" if type.nil? || type.empty?
86
+ raise ArgumentError, "name is required" if name.nil? || name.empty?
87
+
88
+ @type = type.downcase
89
+ @namespace = namespace
90
+ @name = name
91
+ @version = version
92
+ @qualifiers = qualifiers
93
+ @subpath = subpath
94
+ end
95
+
96
+ # Creates a new PackageURL from a string.
97
+ # @param [String] string The package URL string.
98
+ # @raise [InvalidPackageURL] If the string is not a valid package URL.
99
+ # @return [PackageURL]
100
+ def self.parse(string)
101
+ components = {
102
+ type: nil,
103
+ namespace: nil,
104
+ name: nil,
105
+ version: nil,
106
+ qualifiers: nil,
107
+ subpath: nil,
108
+ }
109
+
110
+ # Split the purl string once from right on '#'
111
+ # - The left side is the remainder
112
+ # - Strip the right side from leading and trailing '/'
113
+ # - Split this on '/'
114
+ # - Discard any empty string segment from that split
115
+ # - Discard any '.' or '..' segment from that split
116
+ # - Percent-decode each segment
117
+ # - UTF-8-decode each segment if needed in your programming language
118
+ # - Join segments back with a '/'
119
+ # - This is the subpath
120
+ case string.rpartition("#")
121
+ in String => remainder, separator, String => subpath unless separator.empty?
122
+ subpath_components = []
123
+ subpath.split("/").each do |segment|
124
+ next if segment.empty? || segment == "." || segment == ".."
125
+
126
+ subpath_components << URI.decode_www_form_component(segment)
127
+ end
128
+
129
+ components[:subpath] = subpath_components.compact.join("/")
130
+
131
+ string = remainder
132
+ else
133
+ components[:subpath] = nil
134
+ end
135
+
136
+ # Split the remainder once from right on '?'
137
+ # - The left side is the remainder
138
+ # - The right side is the qualifiers string
139
+ # - Split the qualifiers on '&'. Each part is a key=value pair
140
+ # - For each pair, split the key=value once from left on '=':
141
+ # - The key is the lowercase left side
142
+ # - The value is the percent-decoded right side
143
+ # - UTF-8-decode the value if needed in your programming language
144
+ # - Discard any key/value pairs where the value is empty
145
+ # - If the key is checksums,
146
+ # split the value on ',' to create a list of checksums
147
+ # - This list of key/value is the qualifiers object
148
+ case string.rpartition("?")
149
+ in String => remainder, separator, String => qualifiers unless separator.empty?
150
+ components[:qualifiers] = {}
151
+
152
+ qualifiers.split("&").each do |pair|
153
+ case pair.partition("=")
154
+ in String => key, separator, String => value unless separator.empty?
155
+ key = key.downcase
156
+ value = URI.decode_www_form_component(value)
157
+ next if value.empty?
158
+
159
+ components[:qualifiers][key] = case key
160
+ when "checksums"
161
+ value.split(",")
162
+ else
163
+ value
164
+ end
165
+ else
166
+ next
167
+ end
168
+ end
169
+
170
+ string = remainder
171
+ else
172
+ components[:qualifiers] = nil
173
+ end
174
+
175
+ # Split the remainder once from left on ':'
176
+ # - The left side lowercased is the scheme
177
+ # - The right side is the remainder
178
+ case string.partition(":")
179
+ in "pkg", separator, String => remainder unless separator.empty?
180
+ string = remainder
181
+ else
182
+ raise InvalidPackageURL, 'invalid or missing "pkg:" URL scheme'
183
+ end
184
+
185
+ # Strip the remainder from leading and trailing '/'
186
+ # Use gsub to remove ALL leading slashes instead of just one
187
+ string = string.gsub(%r{^/+}, "").delete_suffix("/")
188
+ # - Split this once from left on '/'
189
+ # - The left side lowercased is the type
190
+ # - The right side is the remainder
191
+ case string.partition("/")
192
+ in String => type, separator, remainder unless separator.empty?
193
+ components[:type] = type
194
+
195
+ string = remainder
196
+ else
197
+ raise InvalidPackageURL, "invalid or missing package type"
198
+ end
199
+
200
+ # Split the remainder once from right on '@'
201
+ # - The left side is the remainder
202
+ # - Percent-decode the right side. This is the version.
203
+ # - UTF-8-decode the version if needed in your programming language
204
+ # - This is the version
205
+ case string.rpartition("@")
206
+ in String => remainder, separator, String => version unless separator.empty?
207
+ components[:version] = URI.decode_www_form_component(version)
208
+
209
+ string = remainder
210
+ else
211
+ components[:version] = nil
212
+ end
213
+
214
+ # Split the remainder once from right on '/'
215
+ # - The left side is the remainder
216
+ # - Percent-decode the right side. This is the name
217
+ # - UTF-8-decode this name if needed in your programming language
218
+ # - Apply type-specific normalization to the name if needed
219
+ # - This is the name
220
+ case string.rpartition("/")
221
+ in String => remainder, separator, String => name unless separator.empty?
222
+ components[:name] = URI.decode_www_form_component(name)
223
+
224
+ # Split the remainder on '/'
225
+ # - Discard any empty segment from that split
226
+ # - Percent-decode each segment
227
+ # - UTF-8-decode the each segment if needed in your programming language
228
+ # - Apply type-specific normalization to each segment if needed
229
+ # - Join segments back with a '/'
230
+ # - This is the namespace
231
+ components[:namespace] = remainder.split("/").map { |s| URI.decode_www_form_component(s) }.compact.join("/")
232
+ in _, _, String => name
233
+ components[:name] = URI.decode_www_form_component(name)
234
+ components[:namespace] = nil
235
+ end
236
+
237
+ # Ensure type and name are not nil before creating the PackageURL instance
238
+ raise InvalidPackageURL, "missing package type" if components[:type].nil?
239
+ raise InvalidPackageURL, "missing package name" if components[:name].nil?
240
+
241
+ # Create a new PackageURL with validated components
242
+ type = components[:type] || "" # This ensures type is never nil
243
+ name = components[:name] || "" # This ensures name is never nil
244
+
245
+ new(
246
+ type: type,
247
+ name: name,
248
+ namespace: components[:namespace],
249
+ version: components[:version],
250
+ qualifiers: components[:qualifiers],
251
+ subpath: components[:subpath],
252
+ )
253
+ end
254
+
255
+ # Returns a hash containing the
256
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
257
+ # of the package URL.
258
+ def to_h
259
+ {
260
+ scheme: scheme,
261
+ type: @type,
262
+ namespace: @namespace,
263
+ name: @name,
264
+ version: @version,
265
+ qualifiers: @qualifiers,
266
+ subpath: @subpath,
267
+ }
268
+ end
269
+
270
+ # Returns a string representation of the package URL.
271
+ # Package URL representations are created according to the instructions from
272
+ # https://github.com/package-url/purl-spec/blob/0b1559f76b79829e789c4f20e6d832c7314762c5/PURL-SPECIFICATION.rst#how-to-build-purl-string-from-its-components.
273
+ def to_s
274
+ # Start a purl string with the "pkg:" scheme as a lowercase ASCII string
275
+ purl = "pkg:"
276
+
277
+ # Append the type string to the purl as a lowercase ASCII string
278
+ # Append '/' to the purl
279
+
280
+ purl += @type
281
+ purl += "/"
282
+
283
+ # If the namespace is not empty:
284
+ # - Strip the namespace from leading and trailing '/'
285
+ # - Split on '/' as segments
286
+ # - Apply type-specific normalization to each segment if needed
287
+ # - UTF-8-encode each segment if needed in your programming language
288
+ # - Percent-encode each segment
289
+ # - Join the segments with '/'
290
+ # - Append this to the purl
291
+ # - Append '/' to the purl
292
+ # - Strip the name from leading and trailing '/'
293
+ # - Apply type-specific normalization to the name if needed
294
+ # - UTF-8-encode the name if needed in your programming language
295
+ # - Append the percent-encoded name to the purl
296
+ #
297
+ # If the namespace is empty:
298
+ # - Apply type-specific normalization to the name if needed
299
+ # - UTF-8-encode the name if needed in your programming language
300
+ # - Append the percent-encoded name to the purl
301
+ case @namespace
302
+ in String => namespace unless namespace.empty?
303
+ segments = []
304
+ @namespace.delete_prefix("/").delete_suffix("/").split("/").each do |segment|
305
+ next if segment.empty?
306
+
307
+ segments << URI.encode_www_form_component(segment)
308
+ end
309
+ purl += segments.join("/")
310
+
311
+ purl += "/"
312
+ purl += URI.encode_www_form_component(@name.delete_prefix("/").delete_suffix("/"))
313
+ else
314
+ purl += URI.encode_www_form_component(@name)
315
+ end
316
+
317
+ # If the version is not empty:
318
+ # - Append '@' to the purl
319
+ # - UTF-8-encode the version if needed in your programming language
320
+ # - Append the percent-encoded version to the purl
321
+ case @version
322
+ in String => version unless version.empty?
323
+ purl += "@"
324
+ purl += URI.encode_www_form_component(@version)
325
+ else
326
+ nil
327
+ end
328
+
329
+ # If the qualifiers are not empty and not composed only of key/value pairs
330
+ # where the value is empty:
331
+ # - Append '?' to the purl
332
+ # - Build a list from all key/value pair:
333
+ # - discard any pair where the value is empty.
334
+ # - UTF-8-encode each value if needed in your programming language
335
+ # - If the key is checksums and this is a list of checksums
336
+ # join this list with a ',' to create this qualifier value
337
+ # - create a string by joining the lowercased key,
338
+ # the equal '=' sign and the percent-encoded value to create a qualifier
339
+ # - sort this list of qualifier strings lexicographically
340
+ # - join this list of qualifier strings with a '&' ampersand
341
+ # - Append this string to the purl
342
+ case @qualifiers
343
+ in Hash => qualifiers unless qualifiers.empty?
344
+ list = []
345
+ qualifiers.each do |key, value|
346
+ next if value.empty?
347
+
348
+ list << case [key, value]
349
+ in "checksums", Array => checksums
350
+ "#{key.downcase}=#{checksums.join(",")}"
351
+ else
352
+ "#{key.downcase}=#{URI.encode_www_form_component(value)}"
353
+ end
354
+ end
355
+
356
+ unless list.empty?
357
+ purl += "?"
358
+ purl += list.sort.join("&")
359
+ end
360
+ else
361
+ nil
362
+ end
363
+
364
+ # If the subpath is not empty and not composed only of
365
+ # empty, '.' and '..' segments:
366
+ # - Append '#' to the purl
367
+ # - Strip the subpath from leading and trailing '/'
368
+ # - Split this on '/' as segments
369
+ # - Discard empty, '.' and '..' segments
370
+ # - Percent-encode each segment
371
+ # - UTF-8-encode each segment if needed in your programming language
372
+ # - Join the segments with '/'
373
+ # - Append this to the purl
374
+ case @subpath
375
+ in String => subpath unless subpath.empty?
376
+ segments = []
377
+ subpath.delete_prefix("/").delete_suffix("/").split("/").each do |segment|
378
+ next if segment.empty? || segment == "." || segment == ".."
379
+
380
+ # Custom encoding for URL fragment segments:
381
+ # 1. Explicitly encode % as %25 to prevent double-encoding issues
382
+ # 2. Percent-encode special characters according to URL fragment rules
383
+ # 3. This ensures proper round-trip encoding/decoding with the parse method
384
+ segments << segment.gsub(/%|[^A-Za-z0-9\-\._~]/) do |m|
385
+ m == "%" ? "%25" : format("%%%02X", m.ord)
386
+ end
387
+ end
388
+
389
+ unless segments.empty?
390
+ purl += "#"
391
+ purl += segments.join("/")
392
+ end
393
+ else
394
+ nil
395
+ end
396
+
397
+ purl
398
+ end
399
+
400
+ # Returns an array containing the
401
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
402
+ # of the package URL.
403
+ def deconstruct
404
+ [scheme, @type, @namespace, @name, @version, @qualifiers, @subpath]
405
+ end
406
+
407
+ # Returns a hash containing the
408
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
409
+ # of the package URL.
410
+ def deconstruct_keys(_keys)
411
+ to_h
412
+ end
413
+ end
414
+ end
@@ -20,17 +20,7 @@ module RubyLsp
20
20
  # @override
21
21
  #: -> Array[Interface::WorkspaceSymbol]
22
22
  def perform
23
- @index.fuzzy_search(@query).filter_map do |entry|
24
- uri = entry.uri
25
- file_path = uri.full_path
26
-
27
- # We only show symbols declared in the workspace
28
- in_dependencies = file_path && !not_in_dependencies?(file_path)
29
- next if in_dependencies
30
-
31
- # We should never show private symbols when searching the entire workspace
32
- next if entry.private?
33
-
23
+ fuzzy_search.filter_map do |entry|
34
24
  kind = kind_for_entry(entry)
35
25
  loc = entry.location
36
26
 
@@ -44,7 +34,7 @@ module RubyLsp
44
34
  container_name: container.join("::"),
45
35
  kind: kind,
46
36
  location: Interface::Location.new(
47
- uri: uri.to_s,
37
+ uri: entry.uri.to_s,
48
38
  range: Interface::Range.new(
49
39
  start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column),
50
40
  end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
@@ -53,6 +43,24 @@ module RubyLsp
53
43
  )
54
44
  end
55
45
  end
46
+
47
+ private
48
+
49
+ #: -> Array[RubyIndexer::Entry]
50
+ def fuzzy_search
51
+ @index.fuzzy_search(@query) do |entry|
52
+ file_path = entry.uri.full_path
53
+
54
+ # We only show symbols declared in the workspace
55
+ in_dependencies = file_path && !not_in_dependencies?(file_path)
56
+ next if in_dependencies
57
+
58
+ # We should never show private symbols when searching the entire workspace
59
+ next if entry.private?
60
+
61
+ true
62
+ end
63
+ end
56
64
  end
57
65
  end
58
66
  end
@@ -270,12 +270,21 @@ module RubyLsp
270
270
  #: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
271
271
  def run_bundle_install_directly(env, force_install: false)
272
272
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
273
+
274
+ # The should_bundle_update? check needs to run on the original Bundler environment, but everything else (like
275
+ # updating or running install) requires the modified environment. Here we compute the check ahead of time and
276
+ # merge the environment to ensure correct results.
277
+ #
278
+ # The symptoms of having these operations in the wrong order is seeing unwanted modifications in the application's
279
+ # main lockfile because we accidentally run update on the main bundle instead of the composed one.
280
+ needs_update = should_bundle_update?
281
+ ENV.merge!(env)
282
+
273
283
  return update(env) if @needs_update_path.exist?
274
284
 
275
285
  # The ENV can only be merged after checking if an update is required because we depend on the original value of
276
286
  # ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
277
- FileUtils.touch(@needs_update_path) if should_bundle_update?
278
- ENV.merge!(env)
287
+ FileUtils.touch(@needs_update_path) if needs_update
279
288
 
280
289
  $stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
281
290
  missing_gems = bundle_check
@@ -35,10 +35,10 @@ module RubyLsp
35
35
  @io = begin
36
36
  # The environment variable is only used for tests. The extension always writes to the temporary file
37
37
  if port
38
- TCPSocket.new("localhost", port)
38
+ socket(port)
39
39
  elsif File.exist?(port_db_path)
40
40
  db = JSON.load_file(port_db_path)
41
- TCPSocket.new("localhost", db[Dir.pwd])
41
+ socket(db[Dir.pwd])
42
42
  else
43
43
  # For tests that don't spawn the TCP server
44
44
  require "stringio"
@@ -209,6 +209,14 @@ module RubyLsp
209
209
 
210
210
  private
211
211
 
212
+ #: (String) -> TCPSocket
213
+ def socket(port)
214
+ socket = TCPSocket.new("localhost", port)
215
+ socket.binmode
216
+ socket.sync = true
217
+ socket
218
+ end
219
+
212
220
  #: (String?, **untyped) -> void
213
221
  def send_message(method_name, **params)
214
222
  json_message = { method: method_name, params: params }.to_json
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.2
4
+ version: 0.26.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -163,6 +163,7 @@ files:
163
163
  - lib/ruby_lsp/requests/support/annotation.rb
164
164
  - lib/ruby_lsp/requests/support/common.rb
165
165
  - lib/ruby_lsp/requests/support/formatter.rb
166
+ - lib/ruby_lsp/requests/support/package_url.rb
166
167
  - lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
167
168
  - lib/ruby_lsp/requests/support/rubocop_formatter.rb
168
169
  - lib/ruby_lsp/requests/support/rubocop_runner.rb