solargraph 0.26.1 → 0.27.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +5 -2
  3. data/lib/solargraph/api_map.rb +236 -234
  4. data/lib/solargraph/api_map/store.rb +18 -53
  5. data/lib/solargraph/bundle.rb +22 -0
  6. data/lib/solargraph/complex_type.rb +9 -5
  7. data/lib/solargraph/complex_type/type_methods.rb +113 -0
  8. data/lib/solargraph/complex_type/unique_type.rb +35 -0
  9. data/lib/solargraph/core_fills.rb +1 -0
  10. data/lib/solargraph/diagnostics.rb +6 -4
  11. data/lib/solargraph/diagnostics/base.rb +3 -0
  12. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  13. data/lib/solargraph/diagnostics/rubocop.rb +21 -6
  14. data/lib/solargraph/diagnostics/type_not_defined.rb +4 -3
  15. data/lib/solargraph/diagnostics/update_errors.rb +18 -0
  16. data/lib/solargraph/language_server/host.rb +90 -222
  17. data/lib/solargraph/language_server/host/cataloger.rb +68 -0
  18. data/lib/solargraph/language_server/host/diagnoser.rb +85 -0
  19. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +35 -24
  20. data/lib/solargraph/language_server/message/text_document/completion.rb +6 -8
  21. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  22. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +0 -1
  23. data/lib/solargraph/language_server/transport/socket.rb +4 -6
  24. data/lib/solargraph/language_server/transport/stdio.rb +4 -6
  25. data/lib/solargraph/library.rb +152 -99
  26. data/lib/solargraph/live_map.rb +1 -1
  27. data/lib/solargraph/location.rb +28 -0
  28. data/lib/solargraph/pin.rb +2 -0
  29. data/lib/solargraph/pin/attribute.rb +26 -12
  30. data/lib/solargraph/pin/base.rb +15 -35
  31. data/lib/solargraph/pin/base_variable.rb +7 -15
  32. data/lib/solargraph/pin/block.rb +5 -9
  33. data/lib/solargraph/pin/block_parameter.rb +9 -7
  34. data/lib/solargraph/pin/conversions.rb +5 -5
  35. data/lib/solargraph/pin/duck_method.rb +1 -1
  36. data/lib/solargraph/pin/instance_variable.rb +0 -4
  37. data/lib/solargraph/pin/keyword.rb +4 -0
  38. data/lib/solargraph/pin/localized.rb +5 -3
  39. data/lib/solargraph/pin/method.rb +11 -0
  40. data/lib/solargraph/pin/namespace.rb +7 -3
  41. data/lib/solargraph/pin/proxy_type.rb +3 -7
  42. data/lib/solargraph/pin/reference.rb +2 -2
  43. data/lib/solargraph/pin/symbol.rb +1 -1
  44. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  45. data/lib/solargraph/pin/yard_pin/namespace.rb +16 -7
  46. data/lib/solargraph/position.rb +103 -0
  47. data/lib/solargraph/range.rb +70 -0
  48. data/lib/solargraph/source.rb +159 -328
  49. data/lib/solargraph/source/chain.rb +38 -55
  50. data/lib/solargraph/source/chain/call.rb +47 -29
  51. data/lib/solargraph/source/chain/class_variable.rb +2 -2
  52. data/lib/solargraph/source/chain/constant.rb +3 -3
  53. data/lib/solargraph/source/chain/definition.rb +7 -3
  54. data/lib/solargraph/source/chain/global_variable.rb +1 -1
  55. data/lib/solargraph/source/chain/head.rb +22 -9
  56. data/lib/solargraph/source/chain/instance_variable.rb +2 -2
  57. data/lib/solargraph/source/chain/link.rb +4 -4
  58. data/lib/solargraph/source/chain/literal.rb +1 -1
  59. data/lib/solargraph/source/chain/variable.rb +2 -2
  60. data/lib/solargraph/source/change.rb +0 -6
  61. data/lib/solargraph/source/cursor.rb +161 -0
  62. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  63. data/lib/solargraph/source/node_chainer.rb +28 -21
  64. data/lib/solargraph/source/node_methods.rb +1 -1
  65. data/lib/solargraph/source/source_chainer.rb +217 -0
  66. data/lib/solargraph/source_map.rb +138 -0
  67. data/lib/solargraph/source_map/clip.rb +123 -0
  68. data/lib/solargraph/{source → source_map}/completion.rb +3 -3
  69. data/lib/solargraph/{source → source_map}/mapper.rb +143 -41
  70. data/lib/solargraph/version.rb +1 -1
  71. data/lib/solargraph/workspace.rb +13 -20
  72. data/lib/solargraph/yard_map.rb +77 -48
  73. metadata +17 -11
  74. data/lib/solargraph/basic_type.rb +0 -33
  75. data/lib/solargraph/basic_type_methods.rb +0 -111
  76. data/lib/solargraph/source/call_chainer.rb +0 -273
  77. data/lib/solargraph/source/fragment.rb +0 -342
  78. data/lib/solargraph/source/location.rb +0 -23
  79. data/lib/solargraph/source/position.rb +0 -95
  80. data/lib/solargraph/source/range.rb +0 -64
@@ -0,0 +1,68 @@
1
+ module Solargraph
2
+ module LanguageServer
3
+ class Host
4
+ class Cataloger
5
+ def initialize host
6
+ @host = host
7
+ @mutex = Mutex.new
8
+ @stopped = true
9
+ @pings = []
10
+ end
11
+
12
+ # Notify the Cataloger that changes are pending.
13
+ #
14
+ # @return [void]
15
+ def ping
16
+ mutex.synchronize { pings.push nil }
17
+ end
18
+
19
+ def synchronizing?
20
+ !pings.empty?
21
+ end
22
+
23
+ # Stop the catalog thread.
24
+ #
25
+ # @return [void]
26
+ def stop
27
+ @stopped = true
28
+ end
29
+
30
+ # True if the cataloger is stopped.
31
+ #
32
+ # @return [Boolean]
33
+ def stopped?
34
+ @stopped
35
+ end
36
+
37
+ # Start the catalog thread.
38
+ #
39
+ # @return [void]
40
+ def start
41
+ return unless stopped?
42
+ @stopped = false
43
+ Thread.new do
44
+ until stopped?
45
+ sleep 0.1
46
+ next if pings.empty?
47
+ mutex.synchronize do
48
+ host.catalog
49
+ pings.clear
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # @return [Host]
58
+ attr_reader :host
59
+
60
+ # @return [Mutex]
61
+ attr_reader :mutex
62
+
63
+ # @return [Array]
64
+ attr_reader :pings
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,85 @@
1
+ module Solargraph
2
+ module LanguageServer
3
+ class Host
4
+ class Diagnoser
5
+ def initialize host
6
+ @host = host
7
+ @mutex = Mutex.new
8
+ @queue = []
9
+ @stopped = true
10
+ end
11
+
12
+ # Schedule a file to be diagnosed.
13
+ #
14
+ # @param uri [String]
15
+ # @return [void]
16
+ def schedule uri
17
+ mutex.synchronize { queue.push uri }
18
+ end
19
+
20
+ # Stop the diagnosis thread.
21
+ #
22
+ # @return [void]
23
+ def stop
24
+ @stopped = true
25
+ end
26
+
27
+ # True is the diagnoser is stopped.
28
+ #
29
+ # @return [Boolean]
30
+ def stopped?
31
+ @stopped
32
+ end
33
+
34
+ # Start the diagnosis thread.
35
+ #
36
+ # @return [self]
37
+ def start
38
+ return unless @stopped
39
+ @stopped = false
40
+ Thread.new do
41
+ until stopped?
42
+ 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
+ begin
49
+ current = nil
50
+ mutex.synchronize { current = queue.shift }
51
+ next if queue.include?(current)
52
+ results = []
53
+ results.concat host.diagnose(current) if host.open?(current)
54
+ host.send_notification "textDocument/publishDiagnostics", {
55
+ uri: current,
56
+ diagnostics: results
57
+ }
58
+ sleep 0.5
59
+ rescue DiagnosticsError => e
60
+ STDERR.puts "Error in diagnostics: #{e.message}"
61
+ options['diagnostics'] = false
62
+ host.send_notification 'window/showMessage', {
63
+ type: LanguageServer::MessageTypes::ERROR,
64
+ message: "Error in diagnostics: #{e.message}"
65
+ }
66
+ end
67
+ end
68
+ end
69
+ self
70
+ end
71
+
72
+ private
73
+
74
+ # @return [Host]
75
+ attr_reader :host
76
+
77
+ # @return [Mutex]
78
+ attr_reader :mutex
79
+
80
+ # @return [Array]
81
+ attr_reader :queue
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,4 +1,4 @@
1
- require 'open3'
1
+ require 'rubygems'
2
2
 
3
3
  module Solargraph
4
4
  module LanguageServer
@@ -10,30 +10,41 @@ module Solargraph
10
10
  #
11
11
  class CheckGemVersion < Base
12
12
  def process
13
- o, s = Open3.capture2("gem search solargraph")
14
- match = o.match(/solargraph \(([\d]*\.[\d]*\.[\d]*)\)/)
15
- # @todo Error if no match or status code != 0
16
- available = Gem::Version.new(match[1])
17
- current = Gem::Version.new(Solargraph::VERSION)
18
- if available > current
19
- host.show_message_request "Solagraph gem version #{available} is available.",
20
- LanguageServer::MessageTypes::INFO,
21
- ['Update now'] do |result|
22
- break unless result == 'Update now'
23
- o, s = Open3.capture2("gem update solargraph")
24
- if s == 0
25
- host.show_message 'Successfully updated the Solargraph gem.', LanguageServer::MessageTypes::INFO
26
- else
27
- host.show_message 'An error occurred while updating the gem.', LanguageServer::MessageTypes::ERROR
28
- end
29
- end
30
- elsif params['verbose']
31
- host.show_message "The Solargraph gem is up to date (version #{Solargraph::VERSION})."
13
+ begin
14
+ fetcher = Gem::SpecFetcher.new
15
+ tuple = fetcher.search_for_dependency(Gem::Dependency.new('solargraph')).flatten.first
16
+ if tuple.nil?
17
+ msg = "An error occurred checking the Solargraph gem version."
18
+ STDERR.puts msg
19
+ host.show_message(msg, MessageTypes::ERROR) if params['verbose']
20
+ else
21
+ available = Gem::Version.new(tuple.version)
22
+ current = Gem::Version.new(Solargraph::VERSION)
23
+ if available > current
24
+ host.show_message_request "Solagraph gem version #{available} is available.",
25
+ LanguageServer::MessageTypes::INFO,
26
+ ['Update now'] do |result|
27
+ break unless result == 'Update now'
28
+ o, s = Open3.capture2("gem update solargraph")
29
+ if s == 0
30
+ host.show_message 'Successfully updated the Solargraph gem.', LanguageServer::MessageTypes::INFO
31
+ else
32
+ host.show_message 'An error occurred while updating the gem.', LanguageServer::MessageTypes::ERROR
33
+ end
34
+ end
35
+ elsif params['verbose']
36
+ host.show_message "The Solargraph gem is up to date (version #{Solargraph::VERSION})."
37
+ end
38
+ set_result({
39
+ installed: current,
40
+ available: available
41
+ })
42
+ end
43
+ rescue Errno::EADDRNOTAVAIL => e
44
+ msg = "Unable to connect to gem source: #{e.message}"
45
+ STDERR.puts msg
46
+ host.show_message(msg, MessageTypes::ERROR) if params['verbose']
32
47
  end
33
- set_result({
34
- installed: current,
35
- available: available
36
- })
37
48
  end
38
49
  end
39
50
  end
@@ -6,11 +6,7 @@ module Solargraph
6
6
  module TextDocument
7
7
  class Completion < Base
8
8
  def process
9
- if host.changing?(params['textDocument']['uri'])
10
- set_result empty_result(true)
11
- else
12
- inner_process
13
- end
9
+ inner_process
14
10
  end
15
11
 
16
12
  private
@@ -26,16 +22,18 @@ module Solargraph
26
22
  return set_result(empty_result) if host.cancel?(id)
27
23
  end
28
24
  items = []
29
- idx = 0
25
+ last_context = nil
26
+ idx = -1
30
27
  completion.pins.each do |pin|
28
+ idx += 1 if last_context != pin.context
31
29
  items.push pin.completion_item.merge({
32
30
  textEdit: {
33
31
  range: completion.range.to_hash,
34
32
  newText: pin.name.sub(/=$/, ' = ')
35
33
  },
36
- sortText: "#{pin.name}#{idx.to_s.rjust(4, '0')}"
34
+ sortText: "#{idx.to_s.rjust(4, '0')}#{pin.name}"
37
35
  })
38
- idx += 1
36
+ last_context = pin.context
39
37
  end
40
38
  set_result(
41
39
  isIncomplete: false,
@@ -2,7 +2,7 @@ class Solargraph::LanguageServer::Message::TextDocument::DocumentSymbol < Solarg
2
2
  include Solargraph::LanguageServer::UriHelpers
3
3
 
4
4
  def process
5
- pins = host.file_symbols params['textDocument']['uri']
5
+ pins = host.document_symbols params['textDocument']['uri']
6
6
  info = pins.map do |pin|
7
7
  result = {
8
8
  name: pin.name,
@@ -22,7 +22,6 @@ module Solargraph::LanguageServer::Message::Workspace
22
22
  elsif change['type'] == DELETED
23
23
  host.delete change['uri']
24
24
  else
25
- # @todo Handle error
26
25
  set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}"
27
26
  end
28
27
  end
@@ -16,12 +16,10 @@ module Solargraph
16
16
  end
17
17
 
18
18
  def process request
19
- Thread.new do
20
- message = @host.start(request)
21
- message.send_response
22
- tmp = @host.flush
23
- send_data tmp unless tmp.empty?
24
- end
19
+ message = @host.start(request)
20
+ message.send_response
21
+ tmp = @host.flush
22
+ send_data tmp unless tmp.empty?
25
23
  end
26
24
 
27
25
  # @param data [String]
@@ -44,12 +44,10 @@ module Solargraph
44
44
  end
45
45
 
46
46
  def process request
47
- Thread.new do
48
- message = @host.start(request)
49
- message.send_response
50
- tmp = @host.flush
51
- send_data tmp unless tmp.empty?
52
- end
47
+ message = @host.start(request)
48
+ message.send_response
49
+ tmp = @host.flush
50
+ send_data tmp unless tmp.empty?
53
51
  end
54
52
 
55
53
  def start_timers
@@ -1,11 +1,23 @@
1
+ require 'set'
2
+
1
3
  module Solargraph
2
4
  # A library handles coordination between a Workspace and an ApiMap.
3
5
  #
4
6
  class Library
5
7
  # @param workspace [Solargraph::Workspace]
6
8
  def initialize workspace = Solargraph::Workspace.new(nil)
9
+ @mutex = Mutex.new
7
10
  @workspace = workspace
8
- api_map
11
+ api_map.catalog bundle
12
+ @synchronized = true
13
+ end
14
+
15
+ # True if the ApiMap is up to date with the library's workspace and open
16
+ # files.
17
+ #
18
+ # @return [Boolean]
19
+ def synchronized?
20
+ @synchronized
9
21
  end
10
22
 
11
23
  # Open a file in the library. Opening a file will make it available for
@@ -14,20 +26,22 @@ module Solargraph
14
26
  # @param filename [String]
15
27
  # @param text [String]
16
28
  # @param version [Integer]
29
+ # @return [void]
17
30
  def open filename, text, version
18
- source = Solargraph::Source.load_string(text, filename)
19
- source.version = version
20
- source_hash[filename] = source
21
- workspace.merge source
22
- api_map.refresh
31
+ mutex.synchronize do
32
+ source = Solargraph::Source.load_string(text, filename, version)
33
+ workspace.merge source
34
+ open_file_hash[filename] = source
35
+ catalog #unless api_map.try_merge!(source)
36
+ end
23
37
  end
24
38
 
25
- # True if the specified file is currently open in the workspace.
39
+ # True if the specified file is currently open.
26
40
  #
27
41
  # @param filename [String]
28
42
  # @return [Boolean]
29
43
  def open? filename
30
- source_hash.has_key? filename
44
+ open_file_hash.has_key? filename
31
45
  end
32
46
 
33
47
  # True if the specified file is included in the workspace (but not
@@ -39,18 +53,22 @@ module Solargraph
39
53
  workspace.has_file?(filename)
40
54
  end
41
55
 
42
- # Create a file source to be added to the workspace. The file is ignored
43
- # if the workspace is not configured to include the file.
56
+ # Create a source to be added to the workspace. The file is ignored if the
57
+ # workspace is not configured to include the file.
44
58
  #
45
59
  # @param filename [String]
46
60
  # @param text [String] The contents of the file
47
61
  # @return [Boolean] True if the file was added to the workspace.
48
62
  def create filename, text
49
- return false unless workspace.would_merge?(filename)
50
- source = Solargraph::Source.load_string(text, filename)
51
- workspace.merge(source)
52
- api_map.refresh
53
- true
63
+ result = false
64
+ mutex.synchronize do
65
+ next unless workspace.would_merge?(filename)
66
+ source = Solargraph::Source.load_string(text, filename)
67
+ workspace.merge(source)
68
+ catalog #unless api_map.try_merge!(source)
69
+ result = true
70
+ end
71
+ result
54
72
  end
55
73
 
56
74
  # Create a file source from a file on disk. The file is ignored if the
@@ -59,12 +77,16 @@ module Solargraph
59
77
  # @param filename [String]
60
78
  # @return [Boolean] True if the file was added to the workspace.
61
79
  def create_from_disk filename
62
- return false if File.directory?(filename) or !File.exist?(filename)
63
- return false unless workspace.would_merge?(filename)
64
- source = Solargraph::Source.load_string(File.read(filename), filename)
65
- workspace.merge(source)
66
- api_map.refresh
67
- true
80
+ result = false
81
+ mutex.synchronize do
82
+ next if File.directory?(filename) or !File.exist?(filename)
83
+ next unless workspace.would_merge?(filename)
84
+ source = Solargraph::Source.load_string(File.read(filename), filename)
85
+ workspace.merge(source)
86
+ catalog #unless api_map.try_merge!(source)
87
+ result = true
88
+ end
89
+ result
68
90
  end
69
91
 
70
92
  # Delete a file from the library. Deleting a file will make it unavailable
@@ -72,35 +94,40 @@ module Solargraph
72
94
  # workspace configuration determines that it should still exist.
73
95
  #
74
96
  # @param filename [String]
97
+ # @return [void]
75
98
  def delete filename
76
- source = source_hash[filename]
77
- return if source.nil?
78
- source_hash.delete filename
79
- workspace.remove source
80
- api_map.refresh
99
+ mutex.synchronize do
100
+ open_file_hash.delete filename
101
+ workspace.remove filename
102
+ catalog
103
+ end
81
104
  end
82
105
 
83
106
  # Close a file in the library. Closing a file will make it unavailable for
84
107
  # checkout although it may still exist in the workspace.
85
108
  #
86
109
  # @param filename [String]
110
+ # @return [void]
87
111
  def close filename
88
- source_hash.delete filename
89
- if workspace.has_file?(filename)
90
- source = Solargraph::Source.load(filename)
91
- workspace.merge source
112
+ mutex.synchronize do
113
+ open_file_hash.delete filename
114
+ catalog
92
115
  end
93
116
  end
94
117
 
95
118
  # @param filename [String]
96
119
  # @param version [Integer]
120
+ # @return [void]
97
121
  def overwrite filename, version
98
- source = source_hash[filename]
99
- return if source.nil?
100
- if source.version > version
101
- STDERR.puts "Save out of sync for #{filename} (current #{source.version}, overwrite #{version})" if source.version > version
102
- else
103
- open filename, File.read(filename), version
122
+ mutex.synchronize do
123
+ source = source_hash[filename]
124
+ return if source.nil?
125
+ if source.version > version
126
+ STDERR.puts "Save out of sync for #{filename} (current #{source.version}, overwrite #{version})" if source.version > version
127
+ else
128
+ open filename, File.read(filename), version
129
+ end
130
+ catalog
104
131
  end
105
132
  end
106
133
 
@@ -109,12 +136,12 @@ module Solargraph
109
136
  # @param filename [String] The file to analyze
110
137
  # @param line [Integer] The zero-based line number
111
138
  # @param column [Integer] The zero-based column number
112
- # @return [ApiMap::Completion]
139
+ # @return [SourceMap::Completion]
140
+ # @todo Take a Location instead of filename/line/column
113
141
  def completions_at filename, line, column
114
- source = read(filename)
115
- api_map.virtualize source
116
- fragment = source.fragment_at(line, column)
117
- fragment.complete(api_map)
142
+ position = Position.new(line, column)
143
+ cursor = Source::Cursor.new(checkout(filename), position)
144
+ api_map.clip(cursor).complete
118
145
  end
119
146
 
120
147
  # Get definition suggestions for the expression at the specified file and
@@ -124,11 +151,11 @@ module Solargraph
124
151
  # @param line [Integer] The zero-based line number
125
152
  # @param column [Integer] The zero-based column number
126
153
  # @return [Array<Solargraph::Pin::Base>]
154
+ # @todo Take filename/position instead of filename/line/column
127
155
  def definitions_at filename, line, column
128
- source = read(filename)
129
- api_map.virtualize source
130
- fragment = source.fragment_at(line, column)
131
- fragment.define(api_map)
156
+ position = Position.new(line, column)
157
+ cursor = Source::Cursor.new(checkout(filename), position)
158
+ api_map.clip(cursor).define
132
159
  end
133
160
 
134
161
  # Get signature suggestions for the method at the specified file and
@@ -138,37 +165,33 @@ module Solargraph
138
165
  # @param line [Integer] The zero-based line number
139
166
  # @param column [Integer] The zero-based column number
140
167
  # @return [Array<Solargraph::Pin::Base>]
168
+ # @todo Take filename/position instead of filename/line/column
141
169
  def signatures_at filename, line, column
142
- source = read(filename)
143
- api_map.virtualize source
144
- fragment = source.fragment_at(line, column)
145
- fragment.signify(api_map)
170
+ position = Position.new(line, column)
171
+ cursor = Source::Cursor.new(checkout(filename), position)
172
+ api_map.clip(cursor).signify
146
173
  end
147
174
 
148
175
  # @param filename [String]
149
176
  # @param line [Integer]
150
177
  # @param column [Integer]
151
- # @return [Array<Solargraph::Source::Range>]
178
+ # @return [Array<Solargraph::Range>]
179
+ # @todo Take a Location instead of filename/line/column
152
180
  def references_from filename, line, column
153
- source = read(filename)
154
- api_map.virtualize source
155
- fragment = source.fragment_at(line, column)
156
- pins = fragment.define(api_map)
181
+ clip = api_map.clip_at(filename, Position.new(line, column))
182
+ pins = clip.define
157
183
  return [] if pins.empty?
158
184
  result = []
159
- # @param pin [Solargraph::Pin::Base]
160
185
  pins.uniq.each do |pin|
161
- if pin.kind != Solargraph::Pin::NAMESPACE and !pin.location.nil?
162
- mn_loc = get_symbol_name_location(pin)
163
- result.push mn_loc unless mn_loc.nil?
164
- end
165
- (workspace.sources + source_hash.values).uniq(&:filename).each do |source|
186
+ (workspace.sources + open_file_hash.values).uniq.each do |source|
166
187
  found = source.references(pin.name)
167
188
  found.select do |loc|
168
189
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
169
190
  referenced.any?{|r| r.path == pin.path}
170
191
  end
171
- result.concat found.sort{|a, b| a.range.start.line <=> b.range.start.line}
192
+ result.concat(found.sort{ |a, b|
193
+ a.range.start.line <=> b.range.start.line
194
+ })
172
195
  end
173
196
  end
174
197
  result
@@ -176,6 +199,7 @@ module Solargraph
176
199
 
177
200
  # Get the pin at the specified location or nil if the pin does not exist.
178
201
  #
202
+ # @param location [Location]
179
203
  # @return [Solargraph::Pin::Base]
180
204
  def locate_pin location
181
205
  api_map.locate_pin location
@@ -200,16 +224,7 @@ module Solargraph
200
224
  # @param filename [String]
201
225
  # @return [Source]
202
226
  def checkout filename
203
- if filename.nil?
204
- api_map.virtualize nil
205
- nil
206
- else
207
- read filename
208
- end
209
- end
210
-
211
- def refresh force = false
212
- api_map.refresh force
227
+ read filename
213
228
  end
214
229
 
215
230
  # @param query [String]
@@ -232,10 +247,17 @@ module Solargraph
232
247
  api_map.query_symbols query
233
248
  end
234
249
 
250
+ # Get an array of document symbols.
251
+ #
252
+ # Document symbols are composed of namespace, method, and constant pins.
253
+ # The results of this query are appropriate for building the response to a
254
+ # textDocument/documentSymbol message in the language server protocol.
255
+ #
235
256
  # @param filename [String]
236
257
  # @return [Array<Solargraph::Pin::Base>]
237
- def file_symbols filename
238
- read(filename).all_symbols
258
+ def document_symbols filename
259
+ return [] unless open_file_hash.has_key?(filename)
260
+ api_map.document_symbols(filename)
239
261
  end
240
262
 
241
263
  # @param path [String]
@@ -244,10 +266,26 @@ module Solargraph
244
266
  api_map.get_path_suggestions(path)
245
267
  end
246
268
 
269
+ # Update a source in the library from the provided updater.
270
+ #
271
+ # @note This method will not update the library's ApiMap. See
272
+ # Library#ynchronized? and Library#catalog for more information.
273
+ #
274
+ #
275
+ # @raise [FileNotFoundError] if the updater's file is not available.
247
276
  # @param updater [Solargraph::Source::Updater]
248
- def synchronize updater, reparse = true
249
- source = read(updater.filename)
250
- source.synchronize updater, reparse
277
+ # @return [void]
278
+ def update updater
279
+ mutex.synchronize do
280
+ if workspace.has_file?(updater.filename)
281
+ workspace.synchronize!(updater)
282
+ open_file_hash[updater.filename] = workspace.source(updater.filename) if open?(updater.filename)
283
+ else
284
+ raise FileNotFoundError, "Unable to update #{updater.filename}" unless open?(updater.filename)
285
+ open_file_hash[updater.filename] = open_file_hash[updater.filename].synchronize(updater)
286
+ end
287
+ @synchronized = false
288
+ end
251
289
  end
252
290
 
253
291
  # Get the current text of a file in the library.
@@ -278,6 +316,14 @@ module Solargraph
278
316
  result
279
317
  end
280
318
 
319
+ # Update the ApiMap from the library's workspace and open files.
320
+ #
321
+ # @return [void]
322
+ def catalog
323
+ api_map.catalog bundle
324
+ @synchronized = true
325
+ end
326
+
281
327
  # Create a library from a directory.
282
328
  #
283
329
  # @param directory [String] The path to be used for the workspace
@@ -288,14 +334,27 @@ module Solargraph
288
334
 
289
335
  private
290
336
 
291
- # @return [Hash<String, Solargraph::Source>]
292
- def source_hash
293
- @source_hash ||= {}
294
- end
337
+ # @return [Mutex]
338
+ attr_reader :mutex
295
339
 
296
- # @return [Solargraph::ApiMap]
340
+ # @return [ApiMap]
297
341
  def api_map
298
- @api_map ||= Solargraph::ApiMap.new(workspace)
342
+ @api_map ||= Solargraph::ApiMap.new
343
+ end
344
+
345
+ # @return [YardMap]
346
+ def yard_map
347
+ @yard_map ||= Solargraph::YardMap.new
348
+ end
349
+
350
+ # @return [Bundle]
351
+ def bundle
352
+ Bundle.new(
353
+ sources: (workspace.sources + open_file_hash.values).uniq(&:filename),
354
+ required: workspace.config.required,
355
+ load_paths: workspace.require_paths,
356
+ yard_map: yard_map
357
+ )
299
358
  end
300
359
 
301
360
  # @return [Solargraph::Workspace]
@@ -303,6 +362,14 @@ module Solargraph
303
362
  @workspace
304
363
  end
305
364
 
365
+ # A collection of files that are currently open in the library. Open
366
+ # files do not need to be in the workspace.
367
+ #
368
+ # @return [Hash{String => Source}]
369
+ def open_file_hash
370
+ @open_file_hash ||= {}
371
+ end
372
+
306
373
  # Get the source for an open file or create a new source if the file
307
374
  # exists on disk. Sources created from disk are not added to the open
308
375
  # workspace files, i.e., the version on disk remains the authoritative
@@ -312,23 +379,9 @@ module Solargraph
312
379
  # @param filename [String]
313
380
  # @return [Solargraph::Source]
314
381
  def read filename
315
- return source_hash[filename] if open?(filename)
316
- return workspace.source(filename) if workspace.has_file?(filename)
317
- raise FileNotFoundError, "File not found: #{filename}" unless File.file?(filename)
318
- Solargraph::Source.load(filename)
319
- end
320
-
321
- def get_symbol_name_location pin
322
- decsrc = read(pin.location.filename)
323
- offset = Solargraph::Source::Position.to_offset(decsrc.code, pin.location.range.start)
324
- soff = decsrc.code.index(pin.name, offset)
325
- eoff = soff + pin.name.length
326
- Solargraph::Source::Location.new(
327
- pin.location.filename, Solargraph::Source::Range.new(
328
- Solargraph::Source::Position.from_offset(decsrc.code, soff),
329
- Solargraph::Source::Position.from_offset(decsrc.code, eoff)
330
- )
331
- )
382
+ return open_file_hash[filename] if open_file_hash.has_key?(filename)
383
+ raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
384
+ workspace.source(filename)
332
385
  end
333
386
  end
334
387
  end