solargraph 0.26.1 → 0.27.0

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