solargraph 0.30.2 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +7 -0
  3. data/lib/solargraph/api_map.rb +31 -38
  4. data/lib/solargraph/api_map/store.rb +7 -1
  5. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  6. data/lib/solargraph/language_server/host.rb +34 -83
  7. data/lib/solargraph/language_server/host/cataloger.rb +17 -7
  8. data/lib/solargraph/language_server/host/diagnoser.rb +19 -10
  9. data/lib/solargraph/language_server/host/dispatch.rb +110 -0
  10. data/lib/solargraph/language_server/host/sources.rb +100 -1
  11. data/lib/solargraph/language_server/message/base.rb +15 -11
  12. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  13. data/lib/solargraph/language_server/message/initialize.rb +32 -27
  14. data/lib/solargraph/language_server/message/text_document/completion.rb +1 -8
  15. data/lib/solargraph/language_server/transport/adapter.rb +26 -15
  16. data/lib/solargraph/language_server/transport/data_reader.rb +2 -2
  17. data/lib/solargraph/library.rb +30 -58
  18. data/lib/solargraph/live_map.rb +1 -1
  19. data/lib/solargraph/pin.rb +1 -0
  20. data/lib/solargraph/pin/base.rb +1 -1
  21. data/lib/solargraph/pin/base_method.rb +1 -1
  22. data/lib/solargraph/pin/method.rb +1 -1
  23. data/lib/solargraph/pin/method_alias.rb +15 -4
  24. data/lib/solargraph/plugin/process.rb +1 -1
  25. data/lib/solargraph/position.rb +1 -2
  26. data/lib/solargraph/server_methods.rb +1 -0
  27. data/lib/solargraph/shell.rb +0 -28
  28. data/lib/solargraph/source.rb +116 -20
  29. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  30. data/lib/solargraph/source/source_chainer.rb +16 -8
  31. data/lib/solargraph/source_map.rb +11 -2
  32. data/lib/solargraph/source_map/clip.rb +1 -1
  33. data/lib/solargraph/source_map/mapper.rb +8 -5
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/views/environment.erb +3 -0
  36. data/lib/solargraph/workspace.rb +17 -14
  37. data/lib/solargraph/workspace/config.rb +1 -1
  38. data/lib/solargraph/yard_map.rb +6 -5
  39. data/lib/solargraph/yard_map/core_docs.rb +68 -18
  40. data/lib/solargraph/yard_map/core_gen.rb +47 -0
  41. data/lib/yard-coregen.rb +16 -0
  42. data/lib/yard-solargraph.rb +10 -1
  43. metadata +21 -4
@@ -1,6 +1,8 @@
1
1
  module Solargraph
2
2
  module LanguageServer
3
3
  class Host
4
+ # An asynchronous library cataloging handler.
5
+ #
4
6
  class Cataloger
5
7
  def initialize host
6
8
  @host = host
@@ -11,10 +13,12 @@ module Solargraph
11
13
 
12
14
  # Notify the Cataloger that changes are pending.
13
15
  #
16
+ # @param lib [Library] The library that needs cataloging
14
17
  # @return [void]
15
18
  def ping lib
16
19
  mutex.synchronize { pings.push lib }
17
20
  end
21
+ alias schedule ping
18
22
 
19
23
  def synchronizing?
20
24
  !pings.empty?
@@ -42,17 +46,23 @@ module Solargraph
42
46
  @stopped = false
43
47
  Thread.new do
44
48
  until stopped?
45
- sleep 0.1
46
- next if pings.empty?
47
- mutex.synchronize do
48
- lib = pings.shift
49
- next if pings.include?(lib)
50
- host.catalog lib
51
- end
49
+ tick
50
+ sleep 0.01
52
51
  end
53
52
  end
54
53
  end
55
54
 
55
+ # Perform cataloging.
56
+ #
57
+ # @return [void]
58
+ def tick
59
+ return if pings.empty?
60
+ mutex.synchronize do
61
+ lib = pings.shift
62
+ lib.catalog unless pings.include?(lib)
63
+ end
64
+ end
65
+
56
66
  private
57
67
 
58
68
  # @return [Host]
@@ -1,7 +1,10 @@
1
1
  module Solargraph
2
2
  module LanguageServer
3
3
  class Host
4
+ # An asynchronous diagnosis reporter.
5
+ #
4
6
  class Diagnoser
7
+ # @param host [Host]
5
8
  def initialize host
6
9
  @host = host
7
10
  @mutex = Mutex.new
@@ -39,22 +42,28 @@ module Solargraph
39
42
  @stopped = false
40
43
  Thread.new do
41
44
  until stopped?
45
+ tick
42
46
  sleep 0.1
43
- next if queue.empty? || host.synchronizing?
44
- if !host.options['diagnostics']
45
- mutex.synchronize { queue.clear }
46
- next
47
- end
48
- current = nil
49
- mutex.synchronize { current = queue.shift }
50
- next if queue.include?(current)
51
- host.diagnose current
52
- sleep 0.5
53
47
  end
54
48
  end
55
49
  self
56
50
  end
57
51
 
52
+ # Perform diagnoses.
53
+ #
54
+ # @return [void]
55
+ def tick
56
+ return if queue.empty? || host.synchronizing?
57
+ if !host.options['diagnostics']
58
+ mutex.synchronize { queue.clear }
59
+ return
60
+ end
61
+ current = nil
62
+ mutex.synchronize { current = queue.shift }
63
+ return if queue.include?(current)
64
+ host.diagnose current
65
+ end
66
+
58
67
  private
59
68
 
60
69
  # @return [Host]
@@ -0,0 +1,110 @@
1
+ module Solargraph
2
+ module LanguageServer
3
+ class Host
4
+ # Methods for associating sources with libraries via URIs.
5
+ #
6
+ module Dispatch
7
+ # @return [Sources]
8
+ def sources
9
+ @sources ||= begin
10
+ src = Sources.new
11
+ src.add_observer self, :update_libraries
12
+ src
13
+ end
14
+ end
15
+
16
+ # @return [Array<Library>]
17
+ def libraries
18
+ @libraries ||= []
19
+ end
20
+
21
+ # The Sources observer callback that merges a source into the host's
22
+ # libraries when it gets updated.
23
+ #
24
+ # @param src [Source]
25
+ # @return [void]
26
+ def update_libraries src
27
+ # @todo This module should not call cataloger and diagnoser
28
+ libraries.each do |lib|
29
+ lib.merge src
30
+ cataloger.ping(lib) if lib.contain?(src.filename) || lib.open?(src.filename)
31
+ end
32
+ diagnoser.schedule file_to_uri(src.filename) if src.synchronized?
33
+ end
34
+
35
+ # Find the best libary match for the given URI.
36
+ #
37
+ # @param uri [String]
38
+ # @return [Library]
39
+ def library_for uri
40
+ explicit_library_for(uri) ||
41
+ implicit_library_for(uri) ||
42
+ generic_library_for(uri)
43
+ end
44
+
45
+ # Find an explicit library match for the given URI. An explicit match
46
+ # means the libary's workspace includes the file.
47
+ #
48
+ # If a matching library is found, the source corresponding to the URI
49
+ # gets attached to it.
50
+ #
51
+ # @raise [FileNotFoundError] if the source could not be attached.
52
+ #
53
+ # @param uri [String]
54
+ # @return [Library, nil]
55
+ def explicit_library_for uri
56
+ filename = UriHelpers.uri_to_file(uri)
57
+ libraries.each do |lib|
58
+ if lib.contain?(filename)
59
+ lib.attach sources.find(uri) if sources.include?(uri)
60
+ return lib
61
+ end
62
+ end
63
+ nil
64
+ end
65
+
66
+ # Find an implicit library match for the given URI. An implicit match
67
+ # means the file is located inside the library's workspace directory,
68
+ # regardless of whether the workspace is configured to include it.
69
+ #
70
+ # If a matching library is found, the source corresponding to the URI
71
+ # gets attached to it.
72
+ #
73
+ # @raise [FileNotFoundError] if the source could not be attached.
74
+ #
75
+ # @param uri [String]
76
+ # @return [Library, nil]
77
+ def implicit_library_for uri
78
+ filename = UriHelpers.uri_to_file(uri)
79
+ libraries.each do |lib|
80
+ # @todo We probably shouldn't depend on attachments to select
81
+ # a library.
82
+ # return lib if lib.open?(filename)
83
+ if filename.start_with?(lib.workspace.directory)
84
+ lib.attach sources.find(uri)
85
+ return lib
86
+ end
87
+ end
88
+ nil
89
+ end
90
+
91
+ # Get a generic library for the given URI and attach the corresponding
92
+ # source.
93
+ #
94
+ # @raise [FileNotFoundError] if the source could not be attached.
95
+ #
96
+ # @param uri [String]
97
+ # @return [Library]
98
+ def generic_library_for uri
99
+ generic_library.attach sources.find(uri)
100
+ generic_library
101
+ end
102
+
103
+ # @return [Library]
104
+ def generic_library
105
+ @generic_library ||= Solargraph::Library.new
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,38 +1,137 @@
1
+ require 'observer'
2
+
1
3
  module Solargraph
2
4
  module LanguageServer
3
5
  class Host
6
+ # A Host class for managing sources.
7
+ #
4
8
  class Sources
9
+ include Observable
5
10
  include UriHelpers
6
11
 
12
+ def initialize
13
+ @stopped = true
14
+ end
15
+
16
+ def stopped?
17
+ @stopped
18
+ end
19
+
20
+ # @return [void]
21
+ def start
22
+ return unless @stopped
23
+ @stopped = false
24
+ Thread.new do
25
+ until stopped?
26
+ tick
27
+ sleep 0.25 if queue.empty?
28
+ end
29
+ end
30
+ end
31
+
32
+ # @return [void]
33
+ def tick
34
+ return if queue.empty?
35
+ uri = mutex.synchronize { queue.shift }
36
+ return if queue.include?(uri)
37
+ mutex.synchronize do
38
+ nxt = open_source_hash[uri].finish_synchronize
39
+ open_source_hash[uri] = nxt
40
+ changed
41
+ notify_observers open_source_hash[uri]
42
+ end
43
+ end
44
+
45
+ # @return [void]
46
+ def stop
47
+ @stopped = true
48
+ end
49
+
50
+ # Open a source.
51
+ #
52
+ # @param uri [String]
53
+ # @param text [String]
54
+ # @param version [Integer]
55
+ # @return [Source]
7
56
  def open uri, text, version
8
57
  filename = uri_to_file(uri)
9
58
  source = Solargraph::Source.new(text, filename, version)
10
59
  open_source_hash[uri] = source
11
60
  end
12
61
 
62
+ # Update an existing source.
63
+ #
64
+ # @raise [FileNotFoundError] if the URI does not match an open source.
65
+ #
66
+ # @param uri [String]
67
+ # @param updater [Source::Updater]
68
+ # @return [Source]
13
69
  def update uri, updater
14
70
  src = find(uri)
15
- open_source_hash[uri] = src.synchronize(updater)
71
+ mutex.synchronize { open_source_hash[uri] = src.synchronize(updater) }
72
+ changed
73
+ notify_observers open_source_hash[uri]
74
+ end
75
+
76
+ # @param uri [String]
77
+ # @param updater [Source::Updater]
78
+ # @return [Thread]
79
+ def async_update uri, updater
80
+ src = find(uri)
81
+ mutex.synchronize { open_source_hash[uri] = src.start_synchronize(updater) }
82
+ mutex.synchronize { queue.push uri }
83
+ changed
84
+ notify_observers open_source_hash[uri]
16
85
  end
17
86
 
87
+ # Find the source with the given URI.
88
+ #
89
+ # @raise [FileNotFoundError] if the URI does not match an open source.
90
+ #
91
+ # @param uri [String]
18
92
  # @return [Source]
19
93
  def find uri
20
94
  open_source_hash[uri] || raise(Solargraph::FileNotFoundError, "Host could not find #{uri}")
21
95
  end
22
96
 
97
+ # Close the source with the given URI.
98
+ #
99
+ # @param uri [String]
100
+ # @return [void]
23
101
  def close uri
24
102
  open_source_hash.delete uri
25
103
  end
26
104
 
105
+ # True if a source with given URI is currently open.
106
+ # @param uri [String]
107
+ # @return [Boolean]
27
108
  def include? uri
28
109
  open_source_hash.key? uri
29
110
  end
30
111
 
112
+ # @return [void]
113
+ def clear
114
+ open_source_hash.clear
115
+ end
116
+
31
117
  private
32
118
 
119
+ # @return [Array<Source>]
33
120
  def open_source_hash
34
121
  @open_source_hash ||= {}
35
122
  end
123
+
124
+ # @return [Mutex]
125
+ def mutex
126
+ @mutex ||= Mutex.new
127
+ end
128
+
129
+ # An array of source URIs that are waiting to finish synchronizing.
130
+ #
131
+ # @return [Array<String>]
132
+ def queue
133
+ @queue ||= []
134
+ end
36
135
  end
37
136
  end
38
137
  end
@@ -58,18 +58,22 @@ module Solargraph
58
58
 
59
59
  # @return [void]
60
60
  def send_response
61
- unless id.nil? or host.cancel?(id)
62
- response = {
63
- jsonrpc: "2.0",
64
- id: id,
65
- }
66
- response[:result] = result unless result.nil?
67
- response[:error] = error unless error.nil?
68
- response[:result] = nil if result.nil? and error.nil?
69
- json = response.to_json
70
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
71
- host.queue envelope
61
+ return if id.nil?
62
+ if host.cancel?(id)
63
+ Solargraph::Logging.logger.info "Cancelled response to #{method}"
64
+ return
72
65
  end
66
+ Solargraph::Logging.logger.info "Sending response to #{method}"
67
+ response = {
68
+ jsonrpc: "2.0",
69
+ id: id,
70
+ }
71
+ response[:result] = result unless result.nil?
72
+ response[:error] = error unless error.nil?
73
+ response[:result] = nil if result.nil? and error.nil?
74
+ json = response.to_json
75
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
76
+ host.queue envelope
73
77
  host.clear id
74
78
  end
75
79
  end
@@ -36,7 +36,7 @@ module Solargraph
36
36
  host.show_message "The Solargraph gem is up to date (version #{Solargraph::VERSION})."
37
37
  end
38
38
  elsif fetched?
39
- STDERR.puts error
39
+ Solargraph::Logging.logger.warn error
40
40
  host.show_message(error, MessageTypes::ERROR) if params['verbose']
41
41
  end
42
42
  set_result({
@@ -1,39 +1,44 @@
1
+ require 'benchmark'
2
+
1
3
  module Solargraph
2
4
  module LanguageServer
3
5
  module Message
4
6
  class Initialize < Base
5
7
  def process
6
- host.configure params['initializationOptions']
7
- if support_workspace_folders?
8
- host.prepare_folders params['workspaceFolders']
9
- elsif params['rootUri']
10
- host.prepare UriHelpers.uri_to_file(params['rootUri'])
11
- else
12
- host.prepare params['rootPath']
13
- end
14
- result = {
15
- capabilities: {
16
- textDocumentSync: 2, # @todo What should this be?
17
- workspace: {
18
- workspaceFolders: {
19
- supported: true,
20
- changeNotifications: true
8
+ bm = Benchmark.measure {
9
+ host.configure params['initializationOptions']
10
+ if support_workspace_folders?
11
+ host.prepare_folders params['workspaceFolders']
12
+ elsif params['rootUri']
13
+ host.prepare UriHelpers.uri_to_file(params['rootUri'])
14
+ else
15
+ host.prepare params['rootPath']
16
+ end
17
+ result = {
18
+ capabilities: {
19
+ textDocumentSync: 2, # @todo What should this be?
20
+ workspace: {
21
+ workspaceFolders: {
22
+ supported: true,
23
+ changeNotifications: true
24
+ }
21
25
  }
22
26
  }
23
27
  }
28
+ result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
29
+ result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
30
+ # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
31
+ result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
32
+ result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
33
+ result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
34
+ result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
35
+ result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
36
+ result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
37
+ result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
38
+ result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
39
+ set_result result
24
40
  }
25
- result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
26
- result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
27
- # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
28
- result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
29
- result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
30
- result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
31
- result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
32
- result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
33
- result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
34
- result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
35
- result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
36
- set_result result
41
+ Solargraph.logger.unknown "Solargraph initialized (#{bm.real} seconds)"
37
42
  end
38
43
 
39
44
  private