solargraph 0.43.0 → 0.44.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: 89a1a9d76094688ff7ebe1af3dec4452c379819408ffc458eddcd75f5b66d2eb
4
- data.tar.gz: 9b03143ba896ccd2d3bd0a44c9a1e90f06e3ed757a47a08e6b8aaf2a9f56e62e
3
+ metadata.gz: '0827e2be47eea923c2f61688ce4e97efa11d3d801f094ffb8a6cdb73a7fb52b3'
4
+ data.tar.gz: dcbad1eaa64acab63ccc511a93c0e1370fc469815e2e67735fc79c2f4dc08038
5
5
  SHA512:
6
- metadata.gz: a6d4e99416af96a8a11ca569d096f4ba1491155f3fde1efc734e59c73d1794007d0b7b1e936c1dfb248472df1fa679c7701866176de99119e8f6a0b8eef721d4
7
- data.tar.gz: edd612d341e6c9bb421e4bbb8db04badc855e924eca469c47943c374813025810151aca1abdd0da13100597803799a50c7355a362b8a0ae65187c0760464e840
6
+ metadata.gz: fa050d901186e2385685d2aab7934f611517d68b549f4cb064ab7c3524f99e890488675a06483c2b1f2e2cafc0417a3367972a1ea8b6c6614a3465e867313b03
7
+ data.tar.gz: 4b12d6b0f65f53afd0ccbbdfc84a9fe8159f2f3a4bb3c625a84f1a9ee80be53f8c8b1d1ecfbae9bcf01ceff691388af5d0cef7271bb5470730fc4aa7a2471df9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 0.44.0 - September 27, 2021
2
+ - Allow opening parenthesis, space, and comma when using Diff::LCS (#465)
3
+ - Support textDocument/documentHighlight
4
+ - Library#references_from performs shallow matches
5
+
6
+ ## 0.43.3 - September 25, 2021
7
+ - Avoid Dir.chdir in Bundler processes (#481)
8
+ - Check stdlib for gems with empty yardocs
9
+ - Library#maybe_map checks for source in workspace
10
+
11
+ ## 0.43.2 - September 23, 2021
12
+ - Synchronize server requests (#461)
13
+
14
+ ## 0.43.1 - September 20, 2021
15
+ - Complete nested namespaces in open gates
16
+ - SourceMap::Mapper reports filename for encoding errors (#474)
17
+ - Handle request on a specific thread, and cancel completion when there has newer completion request (#459)
18
+ - Fix namespace links generated by views/_method.erb (#472)
19
+ - Source handles long squiggly heredocs (#460)
20
+
1
21
  ## 0.43.0 - July 25, 2021
2
22
  - Correct arity checks when restarg precedes arg (#418)
3
23
  - Improve the performance of catalog by 4 times (#457)
data/README.md CHANGED
@@ -34,7 +34,8 @@ Plug-ins and extensions are available for the following editors:
34
34
  * GitHub: https://github.com/autozimu/LanguageClient-neovim
35
35
 
36
36
  * **Emacs**
37
- * GitHub: https://github.com/guskovd/emacs-solargraph
37
+ * GitHub: `eglot.el`, https://github.com/joaotavora/eglot
38
+ * GitHub: `lsp-mode.el`, https://github.com/emacs-lsp/lsp-mode
38
39
 
39
40
  * **Eclipse**
40
41
  * Plugin: https://marketplace.eclipse.org/content/ruby-solargraph
@@ -59,18 +59,16 @@ module Solargraph
59
59
  # @return [Hash]
60
60
  def self.specs_from_bundle directory
61
61
  Solargraph.with_clean_env do
62
- Dir.chdir directory do
63
- cmd = [
64
- 'bundle', 'exec', 'ruby', '-e',
65
- "require 'bundler'; require 'json'; puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json"
66
- ]
67
- o, e, s = Open3.capture3(*cmd)
68
- if s.success?
69
- o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
70
- else
71
- Solargraph.logger.warn e
72
- raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
73
- end
62
+ cmd = [
63
+ 'ruby', '-e',
64
+ "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts Bundler.definition.specs_for([:default]).map { |spec| [spec.name, spec.version] }.to_h.to_json }"
65
+ ]
66
+ o, e, s = Open3.capture3(*cmd)
67
+ if s.success?
68
+ o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
69
+ else
70
+ Solargraph.logger.warn e
71
+ raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
74
72
  end
75
73
  end
76
74
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module LanguageServer
5
+ class Host
6
+ # A serial worker Thread to handle message.
7
+ #
8
+ # this make check pending message possible, and maybe cancelled to speedup process
9
+ class MessageWorker
10
+ # @param host [Host]
11
+ def initialize(host)
12
+ @host = host
13
+ @mutex = Mutex.new
14
+ @resource = ConditionVariable.new
15
+ @stopped = true
16
+ end
17
+
18
+ # pending handle messages
19
+ def messages
20
+ @messages ||= []
21
+ end
22
+
23
+ def stopped?
24
+ @stopped
25
+ end
26
+
27
+ def stop
28
+ @stopped = true
29
+ end
30
+
31
+ # @param message [Hash] The message should be handle. will pass back to Host#receive
32
+ # @return [void]
33
+ def queue(message)
34
+ @mutex.synchronize do
35
+ messages.push(message)
36
+ @resource.signal
37
+ end
38
+ end
39
+
40
+ def start
41
+ return unless @stopped
42
+ @stopped = false
43
+ Thread.new do
44
+ tick until stopped?
45
+ end
46
+ end
47
+
48
+ def tick
49
+ message = @mutex.synchronize do
50
+ @resource.wait(@mutex) if messages.empty?
51
+ messages.shift
52
+ end
53
+ handler = @host.receive(message)
54
+ handler && handler.send_response
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -12,10 +12,11 @@ module Solargraph
12
12
  # safety for multi-threaded transports.
13
13
  #
14
14
  class Host
15
- autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
16
- autoload :Cataloger, 'solargraph/language_server/host/cataloger'
17
- autoload :Sources, 'solargraph/language_server/host/sources'
18
- autoload :Dispatch, 'solargraph/language_server/host/dispatch'
15
+ autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
16
+ autoload :Cataloger, 'solargraph/language_server/host/cataloger'
17
+ autoload :Sources, 'solargraph/language_server/host/sources'
18
+ autoload :Dispatch, 'solargraph/language_server/host/dispatch'
19
+ autoload :MessageWorker, 'solargraph/language_server/host/message_worker'
19
20
 
20
21
  include UriHelpers
21
22
  include Logging
@@ -27,7 +28,7 @@ module Solargraph
27
28
  def initialize
28
29
  @cancel_semaphore = Mutex.new
29
30
  @buffer_semaphore = Mutex.new
30
- @register_semaphore = Mutex.new
31
+ @request_mutex = Mutex.new
31
32
  @cancel = []
32
33
  @buffer = String.new
33
34
  @stopped = true
@@ -45,6 +46,7 @@ module Solargraph
45
46
  diagnoser.start
46
47
  cataloger.start
47
48
  sources.start
49
+ message_worker.start
48
50
  end
49
51
 
50
52
  # Update the configuration options with the provided hash.
@@ -89,8 +91,15 @@ module Solargraph
89
91
  @cancel_semaphore.synchronize { @cancel.delete id }
90
92
  end
91
93
 
94
+ # Called by adapter, to handle the request
95
+ # @param request [Hash]
96
+ # @return [void]
97
+ def process request
98
+ message_worker.queue(request)
99
+ end
100
+
92
101
  # Start processing a request from the client. After the message is
93
- # processed, the transport is responsible for sending the response.
102
+ # processed, caller is responsible for sending the response.
94
103
  #
95
104
  # @param request [Hash] The contents of the message.
96
105
  # @return [Solargraph::LanguageServer::Message::Base] The message handler.
@@ -355,19 +364,21 @@ module Solargraph
355
364
  # @yieldparam [Hash] The result sent by the client
356
365
  # @return [void]
357
366
  def send_request method, params, &block
358
- message = {
359
- jsonrpc: "2.0",
360
- method: method,
361
- params: params,
362
- id: @next_request_id
363
- }
364
- json = message.to_json
365
- requests[@next_request_id] = Request.new(@next_request_id, &block)
366
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
367
- queue envelope
368
- @next_request_id += 1
369
- logger.info "Server sent #{method}"
370
- logger.debug params
367
+ @request_mutex.synchronize do
368
+ message = {
369
+ jsonrpc: "2.0",
370
+ method: method,
371
+ params: params,
372
+ id: @next_request_id
373
+ }
374
+ json = message.to_json
375
+ requests[@next_request_id] = Request.new(@next_request_id, &block)
376
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
377
+ queue envelope
378
+ @next_request_id += 1
379
+ logger.info "Server sent #{method}"
380
+ logger.debug params
381
+ end
371
382
  end
372
383
 
373
384
  # Register the methods as capabilities with the client.
@@ -378,20 +389,16 @@ module Solargraph
378
389
  # @return [void]
379
390
  def register_capabilities methods
380
391
  logger.debug "Registering capabilities: #{methods}"
381
- registrations = methods.select{|m| can_register?(m) and !registered?(m)}.map { |m|
392
+ registrations = methods.select { |m| can_register?(m) and !registered?(m) }.map do |m|
382
393
  @registered_capabilities.add m
383
394
  {
384
395
  id: m,
385
396
  method: m,
386
397
  registerOptions: dynamic_capability_options[m]
387
398
  }
388
- }
389
- return if registrations.empty?
390
- @register_semaphore.synchronize do
391
- send_request 'client/registerCapability', {
392
- registrations: registrations
393
- }
394
399
  end
400
+ return if registrations.empty?
401
+ send_request 'client/registerCapability', { registrations: registrations }
395
402
  end
396
403
 
397
404
  # Unregister the methods with the client.
@@ -410,11 +417,7 @@ module Solargraph
410
417
  }
411
418
  }
412
419
  return if unregisterations.empty?
413
- @register_semaphore.synchronize do
414
- send_request 'client/unregisterCapability', {
415
- unregisterations: unregisterations
416
- }
417
- end
420
+ send_request 'client/unregisterCapability', { unregisterations: unregisterations }
418
421
  end
419
422
 
420
423
  # Flag a method as available for dynamic registration.
@@ -422,9 +425,7 @@ module Solargraph
422
425
  # @param method [String] The method name, e.g., 'textDocument/completion'
423
426
  # @return [void]
424
427
  def allow_registration method
425
- @register_semaphore.synchronize do
426
- @dynamic_capabilities.add method
427
- end
428
+ @dynamic_capabilities.add method
428
429
  end
429
430
 
430
431
  # True if the specified LSP method can be dynamically registered.
@@ -451,6 +452,7 @@ module Solargraph
451
452
  def stop
452
453
  return if @stopped
453
454
  @stopped = true
455
+ message_worker.stop
454
456
  cataloger.stop
455
457
  diagnoser.stop
456
458
  sources.stop
@@ -513,6 +515,11 @@ module Solargraph
513
515
  library.completions_at uri_to_file(uri), line, column
514
516
  end
515
517
 
518
+ # @return [Bool] if has pending completion request
519
+ def has_pending_completions?
520
+ message_worker.messages.reverse_each.any? { |req| req['method'] == 'textDocument/completion' }
521
+ end
522
+
516
523
  # @param uri [String]
517
524
  # @param line [Integer]
518
525
  # @param column [Integer]
@@ -535,10 +542,11 @@ module Solargraph
535
542
  # @param line [Integer]
536
543
  # @param column [Integer]
537
544
  # @param strip [Boolean] Strip special characters from variable names
545
+ # @param only [Boolean] If true, search current file only
538
546
  # @return [Array<Solargraph::Range>]
539
- def references_from uri, line, column, strip: true
547
+ def references_from uri, line, column, strip: true, only: false
540
548
  library = library_for(uri)
541
- library.references_from(uri_to_file(uri), line, column, strip: strip)
549
+ library.references_from(uri_to_file(uri), line, column, strip: strip, only: only)
542
550
  end
543
551
 
544
552
  # @param query [String]
@@ -624,6 +632,7 @@ module Solargraph
624
632
  'diagnostics' => false,
625
633
  'formatting' => false,
626
634
  'folding' => true,
635
+ 'highlights' => true,
627
636
  'logLevel' => 'warn'
628
637
  }
629
638
  end
@@ -646,6 +655,11 @@ module Solargraph
646
655
 
647
656
  private
648
657
 
658
+ # @return [MessageWorker]
659
+ def message_worker
660
+ @message_worker ||= MessageWorker.new(self)
661
+ end
662
+
649
663
  # @return [Diagnoser]
650
664
  def diagnoser
651
665
  @diagnoser ||= Diagnoser.new(self)
@@ -703,7 +717,7 @@ module Solargraph
703
717
  return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
704
718
  # @type [Diff::LCS::Change]
705
719
  diff = diffs.first.first
706
- return change unless diff.adding? && ['.', ':'].include?(diff.element)
720
+ return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
707
721
  position = Solargraph::Position.from_offset(source.code, diff.position)
708
722
  {
709
723
  'range' => {
@@ -771,6 +785,9 @@ module Solargraph
771
785
  },
772
786
  'textDocument/codeAction' => {
773
787
  codeActionProvider: true
788
+ },
789
+ 'textDocument/documentHighlight' => {
790
+ documentHighlightProvider: true
774
791
  }
775
792
  }
776
793
  end
@@ -62,10 +62,14 @@ module Solargraph
62
62
  def send_response
63
63
  return if id.nil?
64
64
  if host.cancel?(id)
65
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
66
+ # cancel should send response RequestCancelled
65
67
  Solargraph::Logging.logger.info "Cancelled response to #{method}"
66
- return
68
+ set_result nil
69
+ set_error ErrorCodes::REQUEST_CANCELLED, "cancelled by client"
70
+ else
71
+ Solargraph::Logging.logger.info "Sending response to #{method}"
67
72
  end
68
- Solargraph::Logging.logger.info "Sending response to #{method}"
69
73
  response = {
70
74
  jsonrpc: "2.0",
71
75
  id: id,
@@ -36,6 +36,7 @@ module Solargraph
36
36
  result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
37
37
  result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
38
38
  result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
39
+ result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', 'documentHighlight')
39
40
  # @todo Temporarily disabled
40
41
  # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
41
42
  set_result result
@@ -138,6 +139,12 @@ module Solargraph
138
139
  }
139
140
  end
140
141
 
142
+ def static_highlights
143
+ {
144
+ documentHighlightProvider: true
145
+ }
146
+ end
147
+
141
148
  # @param section [String]
142
149
  # @param capability [String]
143
150
  # @return [Boolean]
@@ -17,6 +17,7 @@ module Solargraph
17
17
  textDocument/rename
18
18
  textDocument/prepareRename
19
19
  textDocument/foldingRange
20
+ textDocument/documentHighlight
20
21
  workspace/symbol
21
22
  ]
22
23
  end
@@ -6,6 +6,8 @@ module Solargraph
6
6
  module TextDocument
7
7
  class Completion < Base
8
8
  def process
9
+ return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.has_pending_completions?
10
+
9
11
  line = params['position']['line']
10
12
  col = params['position']['character']
11
13
  begin
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph::LanguageServer::Message::TextDocument
4
+ class DocumentHighlight < Base
5
+ def process
6
+ locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true, only: true)
7
+ result = locs.map do |loc|
8
+ {
9
+ range: loc.range.to_hash,
10
+ kind: 1
11
+ }
12
+ end
13
+ set_result result
14
+ end
15
+ end
16
+ end
@@ -4,24 +4,24 @@ module Solargraph
4
4
  module LanguageServer
5
5
  module Message
6
6
  module TextDocument
7
- autoload :Base, 'solargraph/language_server/message/text_document/base'
8
- autoload :Completion, 'solargraph/language_server/message/text_document/completion'
9
- autoload :DidOpen, 'solargraph/language_server/message/text_document/did_open'
10
- autoload :DidChange, 'solargraph/language_server/message/text_document/did_change'
11
- autoload :DidClose, 'solargraph/language_server/message/text_document/did_close'
12
- autoload :DidSave, 'solargraph/language_server/message/text_document/did_save'
13
- autoload :Hover, 'solargraph/language_server/message/text_document/hover'
14
- autoload :SignatureHelp, 'solargraph/language_server/message/text_document/signature_help'
15
- autoload :DiagnosticsQueue, 'solargraph/language_server/message/text_document/diagnostics_queue'
16
- autoload :OnTypeFormatting, 'solargraph/language_server/message/text_document/on_type_formatting'
17
- autoload :Definition, 'solargraph/language_server/message/text_document/definition'
18
- autoload :DocumentSymbol, 'solargraph/language_server/message/text_document/document_symbol'
19
- autoload :Formatting, 'solargraph/language_server/message/text_document/formatting'
20
- autoload :References, 'solargraph/language_server/message/text_document/references'
21
- autoload :Rename, 'solargraph/language_server/message/text_document/rename'
22
- autoload :PrepareRename, 'solargraph/language_server/message/text_document/prepare_rename'
23
- autoload :FoldingRange, 'solargraph/language_server/message/text_document/folding_range'
24
- autoload :CodeAction, 'solargraph/language_server/message/text_document/code_action'
7
+ autoload :Base, 'solargraph/language_server/message/text_document/base'
8
+ autoload :Completion, 'solargraph/language_server/message/text_document/completion'
9
+ autoload :DidOpen, 'solargraph/language_server/message/text_document/did_open'
10
+ autoload :DidChange, 'solargraph/language_server/message/text_document/did_change'
11
+ autoload :DidClose, 'solargraph/language_server/message/text_document/did_close'
12
+ autoload :DidSave, 'solargraph/language_server/message/text_document/did_save'
13
+ autoload :Hover, 'solargraph/language_server/message/text_document/hover'
14
+ autoload :SignatureHelp, 'solargraph/language_server/message/text_document/signature_help'
15
+ autoload :DiagnosticsQueue, 'solargraph/language_server/message/text_document/diagnostics_queue'
16
+ autoload :OnTypeFormatting, 'solargraph/language_server/message/text_document/on_type_formatting'
17
+ autoload :Definition, 'solargraph/language_server/message/text_document/definition'
18
+ autoload :DocumentSymbol, 'solargraph/language_server/message/text_document/document_symbol'
19
+ autoload :Formatting, 'solargraph/language_server/message/text_document/formatting'
20
+ autoload :References, 'solargraph/language_server/message/text_document/references'
21
+ autoload :Rename, 'solargraph/language_server/message/text_document/rename'
22
+ autoload :PrepareRename, 'solargraph/language_server/message/text_document/prepare_rename'
23
+ autoload :FoldingRange, 'solargraph/language_server/message/text_document/folding_range'
24
+ autoload :DocumentHighlight, 'solargraph/language_server/message/text_document/document_highlight'
25
25
  end
26
26
  end
27
27
  end
@@ -22,6 +22,7 @@ module Solargraph::LanguageServer::Message::Workspace
22
22
  (host.options['definitions'] ? y : n).push('textDocument/definition')
23
23
  (host.options['references'] ? y : n).push('textDocument/references')
24
24
  (host.options['folding'] ? y : n).push('textDocument/folding')
25
+ (host.options['highlights'] ? y : n).push('textDocument/documentHighlight')
25
26
  host.register_capabilities y
26
27
  host.unregister_capabilities n
27
28
  end
@@ -73,7 +73,8 @@ module Solargraph
73
73
  register 'textDocument/rename', TextDocument::Rename
74
74
  register 'textDocument/prepareRename', TextDocument::PrepareRename
75
75
  register 'textDocument/foldingRange', TextDocument::FoldingRange
76
- register 'textDocument/codeAction', TextDocument::CodeAction
76
+ # register 'textDocument/codeAction', TextDocument::CodeAction
77
+ register 'textDocument/documentHighlight', TextDocument::DocumentHighlight
77
78
  register 'workspace/didChangeWatchedFiles', Workspace::DidChangeWatchedFiles
78
79
  register 'workspace/didChangeConfiguration', Workspace::DidChangeConfiguration
79
80
  register 'workspace/didChangeWorkspaceFolders', Workspace::DidChangeWorkspaceFolders
@@ -41,8 +41,7 @@ module Solargraph
41
41
  # @param request [String]
42
42
  # @return [void]
43
43
  def process request
44
- message = @host.receive(request)
45
- message && message.send_response
44
+ @host.process(request)
46
45
  end
47
46
 
48
47
  def shutdown
@@ -216,33 +216,35 @@ module Solargraph
216
216
  # @param line [Integer]
217
217
  # @param column [Integer]
218
218
  # @param strip [Boolean] Strip special characters from variable names
219
+ # @param only [Boolean] Search for references in the current file only
219
220
  # @return [Array<Solargraph::Range>]
220
221
  # @todo Take a Location instead of filename/line/column
221
- def references_from filename, line, column, strip: false
222
+ def references_from filename, line, column, strip: false, only: false
222
223
  cursor = api_map.cursor_at(filename, Position.new(line, column))
223
224
  clip = api_map.clip(cursor)
224
- pins = clip.define
225
- return [] if pins.empty?
225
+ pin = clip.define.first
226
+ return [] unless pin
226
227
  result = []
227
- pins.uniq.each do |pin|
228
- (workspace.sources + (@current ? [@current] : [])).uniq(&:filename).each do |source|
229
- found = source.references(pin.name)
230
- found.select! do |loc|
231
- referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
232
- # HACK: The additional location comparison is necessary because
233
- # Clip#define can return proxies for parameter pins
234
- referenced.any? { |r| r == pin || r.location == pin.location }
235
- end
236
- # HACK: for language clients that exclude special characters from the start of variable names
237
- if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
238
- found.map! do |loc|
239
- Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
240
- end
228
+ files = if only
229
+ [api_map.source_map(filename)]
230
+ else
231
+ (workspace.sources + (@current ? [@current] : []))
232
+ end
233
+ files.uniq(&:filename).each do |source|
234
+ found = source.references(pin.name)
235
+ found.select! do |loc|
236
+ referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
237
+ referenced && referenced.path == pin.path
238
+ end
239
+ # HACK: for language clients that exclude special characters from the start of variable names
240
+ if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
241
+ found.map! do |loc|
242
+ Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
241
243
  end
242
- result.concat(found.sort do |a, b|
243
- a.range.start.line <=> b.range.start.line
244
- end)
245
244
  end
245
+ result.concat(found.sort do |a, b|
246
+ a.range.start.line <=> b.range.start.line
247
+ end)
246
248
  end
247
249
  result.uniq
248
250
  end
@@ -518,6 +520,7 @@ module Solargraph
518
520
 
519
521
  def maybe_map source
520
522
  return unless source
523
+ return unless @current == source || workspace.has_file?(source.filename)
521
524
  if source_map_hash.key?(source.filename)
522
525
  return if source_map_hash[source.filename].code == source.code &&
523
526
  source_map_hash[source.filename].source.synchronized? &&
@@ -2,7 +2,7 @@ require 'ripper'
2
2
 
3
3
  module Solargraph
4
4
  module Parser
5
- class CommentRipper < Ripper::SexpBuilder
5
+ class CommentRipper < Ripper::SexpBuilderPP
6
6
  def initialize src, filename = '(ripper)', lineno = 0
7
7
  super
8
8
  @buffer = src
@@ -103,6 +103,31 @@ module Solargraph
103
103
  en = Position.new(node.loc.last_line, node.loc.last_column)
104
104
  Range.new(st, en)
105
105
  end
106
+
107
+ def string_ranges node
108
+ return [] unless is_ast_node?(node)
109
+ result = []
110
+ if node.type == :str
111
+ result.push Range.from_node(node)
112
+ elsif node.type == :dstr
113
+ here = Range.from_node(node)
114
+ there = Range.from_node(node.children[1])
115
+ result.push Range.new(here.start, there.start)
116
+ end
117
+ node.children.each do |child|
118
+ result.concat string_ranges(child)
119
+ end
120
+ if node.type == :dstr && node.children.last.nil?
121
+ # result.push Range.new(result.last.ending, result.last.ending)
122
+ last = node.children[-2]
123
+ unless last.nil?
124
+ rng = Range.from_node(last)
125
+ pos = Position.new(rng.ending.line, rng.ending.column - 1)
126
+ result.push Range.new(pos, pos)
127
+ end
128
+ end
129
+ result
130
+ end
106
131
  end
107
132
  end
108
133
  end
@@ -178,6 +178,7 @@ module Solargraph
178
178
 
179
179
  # @param cursor [Solargraph::Source::Cursor]
180
180
  def find_recipient_node cursor
181
+ return repaired_find_recipient_node(cursor) if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '('
181
182
  source = cursor.source
182
183
  position = cursor.position
183
184
  offset = cursor.offset
@@ -210,6 +211,12 @@ module Solargraph
210
211
  nil
211
212
  end
212
213
 
214
+ def repaired_find_recipient_node cursor
215
+ cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
216
+ node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first
217
+ return node if node && node.type == :send
218
+ end
219
+
213
220
  module DeepInference
214
221
  class << self
215
222
  CONDITIONAL = [:if, :unless]
@@ -141,36 +141,45 @@ module Solargraph
141
141
  class << self
142
142
  protected
143
143
 
144
+ # @param cursor [Source::Cursor]
145
+ # @return [RubyVM::AbstractSyntaxTree::Node, nil]
144
146
  def synchronized_find_recipient_node cursor
147
+ cursor = maybe_adjust_cursor(cursor)
145
148
  source = cursor.source
146
149
  position = cursor.position
147
150
  offset = cursor.offset
148
151
  tree = source.tree_at(position.line, position.column)
149
- tree.shift while tree.first && [:FCALL, :VCALL, :CALL].include?(tree.first.type) && !source.code_for(tree.first).strip.end_with?(')')
152
+ .select { |n| [:FCALL, :VCALL, :CALL].include?(n.type) }
153
+ unless source.repaired?
154
+ tree.shift while tree.first && !source.code_for(tree.first).strip.end_with?(')')
155
+ end
156
+ return tree.first if source.repaired? || source.code[0..offset-1] =~ /\(\s*$/
150
157
  tree.each do |node|
151
- if [:FCALL, :VCALL, :CALL].include?(node.type)
152
- args = node.children.find { |c| Parser.is_ast_node?(c) && [:ARRAY, :ZARRAY, :LIST].include?(c.type) }
153
- if args
154
- match = source.code[0..offset-1].match(/,[^\)]*\z/)
155
- rng = Solargraph::Range.from_node(args)
156
- if match
157
- rng = Solargraph::Range.new(rng.start, position)
158
- end
159
- return node if rng.contain?(position)
160
- elsif source.code[0..offset-1] =~ /\(\s*$/
161
- break unless source.code_for(node).strip.end_with?(')')
162
- return node
158
+ args = node.children.find { |c| Parser.is_ast_node?(c) && [:ARRAY, :ZARRAY, :LIST].include?(c.type) }
159
+ if args
160
+ match = source.code[0..offset-1].match(/,[^\)]*\z/)
161
+ rng = Solargraph::Range.from_node(args)
162
+ if match
163
+ rng = Solargraph::Range.new(rng.start, position)
163
164
  end
165
+ return node if rng.contain?(position)
164
166
  end
165
167
  end
166
168
  nil
167
169
  end
168
170
 
171
+ # @param cursor [Source::Cursor]
172
+ # @return [Source::Cursor]
173
+ def maybe_adjust_cursor cursor
174
+ return cursor unless (cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(') || [',', ' '].include?(cursor.source.code[cursor.offset - 1])
175
+ cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
176
+ end
177
+
169
178
  def unsynchronized_find_recipient_node cursor
170
179
  source = cursor.source
171
180
  position = cursor.position
172
181
  offset = cursor.offset
173
- if source.code[0..offset-1] =~ /\([A-Zaz0-9_\s]*\z$/ #&& source.code[offset] == ')'
182
+ if source.code[0..offset-1] =~ /\([A-Zaz0-9_\s]*\z$/
174
183
  tree = source.tree_at(position.line, position.column - 1)
175
184
  if tree.first && [:FCALL, :VCALL, :CALL].include?(tree.first.type)
176
185
  return tree.first
@@ -22,12 +22,18 @@ module Solargraph
22
22
  @closure = Solargraph::Pin::ROOT_PIN
23
23
  end
24
24
  @open_gates = gates
25
- if @open_gates.one? && @open_gates.first.empty? && @name.include?('::')
25
+ if @name.include?('::')
26
26
  # In this case, a chained namespace was opened (e.g., Foo::Bar)
27
27
  # but Foo does not exist.
28
28
  parts = @name.split('::')
29
29
  @name = parts.pop
30
- @closure = Pin::Namespace.new(name: parts.join('::'), gates: [parts.join('::')])
30
+ closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure)
31
+ ''
32
+ else
33
+ closure.full_context.namespace + '::'
34
+ end
35
+ closure_name += parts.join('::')
36
+ @closure = Pin::Namespace.new(name: closure_name, gates: [parts.join('::')])
31
37
  @context = nil
32
38
  end
33
39
  end
@@ -204,6 +204,8 @@ module Solargraph
204
204
  com_pos = Position.new(line + 1 - comments.lines.length, 0)
205
205
  process_comment(src_pos, com_pos, comments)
206
206
  end
207
+ rescue StandardError => e
208
+ raise e.class, "Error processing comment directives in #{@filename}: #{e.message}"
207
209
  end
208
210
  end
209
211
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.43.0'
4
+ VERSION = '0.44.0'
5
5
  end
@@ -2,7 +2,7 @@
2
2
  Namespace:
3
3
  </h2>
4
4
  <p>
5
- <a href="command:solargraph._openDocument?<%= CGI.escape "\"solargraph:/document?query=#{object.namespace}\"" %>"><%= object.namespace %></a>
5
+ <a href="solargraph:/document?query=<%= CGI.escape object.namespace.path %>"><%= object.namespace %></a>
6
6
  </p>
7
7
  <h2>
8
8
  Overview:
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+ require 'rubygems'
5
+
3
6
  module Solargraph
4
7
  # A workspace consists of the files in a project's directory and the
5
8
  # project's configuration. It provides a Source for each file to be used
@@ -168,19 +171,20 @@ module Solargraph
168
171
  # HACK: Evaluating gemspec files violates the goal of not running
169
172
  # workspace code, but this is how Gem::Specification.load does it
170
173
  # anyway.
171
- Dir.chdir base do
174
+ cmd = ['ruby', '-e', "require 'rubygems'; require 'json'; spec = eval(File.read('#{file}'), TOPLEVEL_BINDING, '#{file}'); return unless Gem::Specification === spec; puts({name: spec.name, paths: spec.require_paths}.to_json)"]
175
+ o, e, s = Open3.capture3(*cmd)
176
+ if s.success?
172
177
  begin
173
- # @type [Gem::Specification]
174
- spec = eval(File.read(file), TOPLEVEL_BINDING, file)
175
- next unless Gem::Specification === spec
176
- @gemnames.push spec.name
177
- result.concat(spec.require_paths.map { |path| File.join(base, path) })
178
- rescue RuntimeError, ScriptError, Errno::ENOENT => e
179
- # Don't die if we have an error during eval-ing a gem spec.
180
- # Concat the default lib directory instead.
178
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
179
+ next if hash.empty?
180
+ @gemnames.push hash['name']
181
+ result.concat(hash['paths'].map { |path| File.join(base, path) })
182
+ rescue StandardError => e
181
183
  Solargraph.logger.warn "Error reading #{file}: [#{e.class}] #{e.message}"
182
- result.push File.join(base, 'lib')
183
184
  end
185
+ else
186
+ Solargraph.logger.warn "Error reading #{file}"
187
+ Solargraph.logger.warn e
184
188
  end
185
189
  end
186
190
  result.concat(config.require_paths.map { |p| File.join(directory, p) })
@@ -10,6 +10,8 @@ module Solargraph
10
10
  # stdlib, and gems.
11
11
  #
12
12
  class YardMap
13
+ class NoYardocError < StandardError; end
14
+
13
15
  autoload :Cache, 'solargraph/yard_map/cache'
14
16
  autoload :CoreDocs, 'solargraph/yard_map/core_docs'
15
17
  autoload :CoreGen, 'solargraph/yard_map/core_gen'
@@ -224,7 +226,7 @@ module Solargraph
224
226
  result.concat process_yardoc yd, spec
225
227
  result.concat add_gem_dependencies(spec) if with_dependencies?
226
228
  end
227
- rescue Gem::LoadError => e
229
+ rescue Gem::LoadError, NoYardocError => e
228
230
  base = r.split('/').first
229
231
  next if from_std.include?(base)
230
232
  from_std.push base
@@ -299,6 +301,7 @@ module Solargraph
299
301
  result = Marshal.load(dump)
300
302
  return result unless result.nil? || result.empty?
301
303
  Solargraph.logger.warn "Empty cache for #{spec.name} #{spec.version}. Reloading"
304
+ File.unlink ser
302
305
  rescue StandardError => e
303
306
  Solargraph.logger.warn "Error loading pin cache: [#{e.class}] #{e.message}"
304
307
  File.unlink ser
@@ -315,6 +318,7 @@ module Solargraph
315
318
  Solargraph.logger.info "Loading #{spec.name} #{spec.version} from #{y}"
316
319
  load_yardoc y
317
320
  result = Mapper.new(YARD::Registry.all, spec).map
321
+ raise NoYardocError, "Yardoc at #{y} is empty" if result.empty?
318
322
  if spec
319
323
  ser = File.join(CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}.ser")
320
324
  file = File.open(ser, 'wb')
data/lib/solargraph.rb CHANGED
@@ -57,6 +57,7 @@ module Solargraph
57
57
  # A helper method that runs Bundler.with_unbundled_env or falls back to
58
58
  # Bundler.with_clean_env for earlier versions of Bundler.
59
59
  #
60
+ # @return [void]
60
61
  def self.with_clean_env &block
61
62
  meth = if Bundler.respond_to?(:with_unbundled_env)
62
63
  :with_unbundled_env
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.0
4
+ version: 0.44.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-25 00:00:00.000000000 Z
11
+ date: 2021-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -350,6 +350,7 @@ files:
350
350
  - lib/solargraph/language_server/host/cataloger.rb
351
351
  - lib/solargraph/language_server/host/diagnoser.rb
352
352
  - lib/solargraph/language_server/host/dispatch.rb
353
+ - lib/solargraph/language_server/host/message_worker.rb
353
354
  - lib/solargraph/language_server/host/sources.rb
354
355
  - lib/solargraph/language_server/message.rb
355
356
  - lib/solargraph/language_server/message/base.rb
@@ -380,6 +381,7 @@ files:
380
381
  - lib/solargraph/language_server/message/text_document/did_close.rb
381
382
  - lib/solargraph/language_server/message/text_document/did_open.rb
382
383
  - lib/solargraph/language_server/message/text_document/did_save.rb
384
+ - lib/solargraph/language_server/message/text_document/document_highlight.rb
383
385
  - lib/solargraph/language_server/message/text_document/document_symbol.rb
384
386
  - lib/solargraph/language_server/message/text_document/folding_range.rb
385
387
  - lib/solargraph/language_server/message/text_document/formatting.rb