solargraph 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +33 -0
  4. data/README.md +15 -0
  5. data/SPONSORS.md +1 -0
  6. data/lib/.rubocop.yml +1 -1
  7. data/lib/solargraph.rb +8 -7
  8. data/lib/solargraph/api_map.rb +52 -75
  9. data/lib/solargraph/api_map/store.rb +5 -0
  10. data/lib/solargraph/bench.rb +16 -19
  11. data/lib/solargraph/compat.rb +15 -1
  12. data/lib/solargraph/diagnostics/rubocop.rb +10 -2
  13. data/lib/solargraph/diagnostics/rubocop_helpers.rb +19 -20
  14. data/lib/solargraph/language_server/host.rb +74 -1
  15. data/lib/solargraph/language_server/message/completion_item/resolve.rb +1 -0
  16. data/lib/solargraph/language_server/message/extended/environment.rb +3 -3
  17. data/lib/solargraph/language_server/message/initialize.rb +30 -35
  18. data/lib/solargraph/language_server/message/text_document/formatting.rb +69 -21
  19. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  20. data/lib/solargraph/library.rb +94 -24
  21. data/lib/solargraph/parser/legacy/node_methods.rb +9 -0
  22. data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
  23. data/lib/solargraph/parser/rubyvm/node_methods.rb +18 -1
  24. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +11 -12
  25. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +1 -6
  26. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +1 -1
  27. data/lib/solargraph/source.rb +1 -1
  28. data/lib/solargraph/source/chain/head.rb +0 -16
  29. data/lib/solargraph/source/source_chainer.rb +1 -0
  30. data/lib/solargraph/source_map/mapper.rb +0 -5
  31. data/lib/solargraph/type_checker.rb +49 -39
  32. data/lib/solargraph/type_checker/checks.rb +9 -5
  33. data/lib/solargraph/type_checker/rules.rb +5 -1
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/workspace/config.rb +19 -3
  36. data/lib/solargraph/yard_map/core_fills.rb +1 -0
  37. data/solargraph.gemspec +1 -1
  38. metadata +4 -4
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'observer'
4
4
  require 'set'
5
+ require 'securerandom'
5
6
 
6
7
  module Solargraph
7
8
  module LanguageServer
@@ -192,7 +193,7 @@ module Solargraph
192
193
  def diagnose uri
193
194
  if sources.include?(uri)
194
195
  library = library_for(uri)
195
- if library.synchronized?
196
+ if library.mapped? && library.synchronized?
196
197
  logger.info "Diagnosing #{uri}"
197
198
  begin
198
199
  results = library.diagnose uri_to_file(uri)
@@ -277,6 +278,7 @@ module Solargraph
277
278
  begin
278
279
  lib = Solargraph::Library.load(path, name)
279
280
  libraries.push lib
281
+ async_library_map lib
280
282
  rescue WorkspaceTooLargeError => e
281
283
  send_notification 'window/showMessage', {
282
284
  'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
@@ -494,6 +496,11 @@ module Solargraph
494
496
  library.read_text(filename)
495
497
  end
496
498
 
499
+ def formatter_config uri
500
+ library = library_for(uri)
501
+ library.workspace.config.formatter
502
+ end
503
+
497
504
  # @param uri [String]
498
505
  # @param line [Integer]
499
506
  # @param column [Integer]
@@ -626,6 +633,7 @@ module Solargraph
626
633
 
627
634
  # @return [void]
628
635
  def catalog
636
+ return unless libraries.all?(&:mapped?)
629
637
  libraries.each(&:catalog)
630
638
  end
631
639
 
@@ -736,6 +744,71 @@ module Solargraph
736
744
  def prepare_rename?
737
745
  client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
738
746
  end
747
+
748
+ def client_supports_progress?
749
+ client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
750
+ end
751
+
752
+ # @param library [Library]
753
+ # @return [void]
754
+ def async_library_map library
755
+ return if library.mapped?
756
+ Thread.new do
757
+ if client_supports_progress?
758
+ uuid = SecureRandom.uuid
759
+ send_request 'window/workDoneProgress/create', {
760
+ token: uuid
761
+ } do |response|
762
+ do_async_library_map library, response.nil? ? uuid : nil
763
+ end
764
+ else
765
+ do_async_library_map library
766
+ end
767
+ end
768
+ end
769
+
770
+ def do_async_library_map library, uuid = nil
771
+ total = library.workspace.sources.length
772
+ if uuid
773
+ send_notification '$/progress', {
774
+ token: uuid,
775
+ value: {
776
+ kind: 'begin',
777
+ title: "Mapping workspace",
778
+ message: "0/#{total} files",
779
+ cancellable: false,
780
+ percentage: 0
781
+ }
782
+ }
783
+ end
784
+ pct = 0
785
+ mod = 10
786
+ while library.next_map
787
+ next unless uuid
788
+ cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
789
+ if cur > pct && cur % mod == 0
790
+ pct = cur
791
+ send_notification '$/progress', {
792
+ token: uuid,
793
+ value: {
794
+ kind: 'report',
795
+ cancellable: false,
796
+ message: "#{library.source_map_hash.keys.length}/#{total} files",
797
+ percentage: pct
798
+ }
799
+ }
800
+ end
801
+ end
802
+ if uuid
803
+ send_notification '$/progress', {
804
+ token: uuid,
805
+ value: {
806
+ kind: 'end',
807
+ message: 'Mapping complete'
808
+ }
809
+ }
810
+ end
811
+ end
739
812
  end
740
813
  end
741
814
  end
@@ -21,6 +21,7 @@ module Solargraph
21
21
  docs = pins
22
22
  .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
23
23
  result = params
24
+ .transform_keys(&:to_sym)
24
25
  .merge(pins.first.resolve_completion_item)
25
26
  .merge(documentation: markup_content(join_docs(docs)))
26
27
  result[:detail] = pins.first.detail
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Make sure the environment page can report RuboCop's version
4
- require 'rubocop'
5
-
6
3
  module Solargraph
7
4
  module LanguageServer
8
5
  module Message
@@ -12,6 +9,9 @@ module Solargraph
12
9
  #
13
10
  class Environment < Base
14
11
  def process
12
+ # Make sure the environment page can report RuboCop's version
13
+ require 'rubocop'
14
+
15
15
  page = Solargraph::Page.new(host.options['viewsPath'])
16
16
  content = page.render('environment', layout: true, locals: { config: host.options, folders: host.folders })
17
17
  set_result(
@@ -1,49 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
-
5
3
  module Solargraph
6
4
  module LanguageServer
7
5
  module Message
8
6
  class Initialize < Base
9
7
  def process
10
- bm = Benchmark.measure {
11
- host.configure params['initializationOptions']
12
- host.client_capabilities = params['capabilities']
13
- if support_workspace_folders?
14
- host.prepare_folders params['workspaceFolders']
15
- elsif params['rootUri']
16
- host.prepare UriHelpers.uri_to_file(params['rootUri'])
17
- else
18
- host.prepare params['rootPath']
19
- end
20
- result = {
21
- capabilities: {
22
- textDocumentSync: 2, # @todo What should this be?
23
- workspace: {
24
- workspaceFolders: {
25
- supported: true,
26
- changeNotifications: true
27
- }
8
+ host.configure params['initializationOptions']
9
+ host.client_capabilities = params['capabilities']
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
28
24
  }
29
25
  }
30
26
  }
31
- result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
32
- result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
33
- # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
34
- result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
35
- result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
36
- result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
37
- result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
38
- result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
39
- result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
40
- result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
41
- result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
42
- # @todo Temporarily disabled
43
- # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
44
- set_result result
45
27
  }
46
- Solargraph.logger.unknown "Solargraph initialized (#{bm.real} seconds)"
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
+ # @todo Temporarily disabled
40
+ # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
41
+ set_result result
47
42
  end
48
43
 
49
44
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop'
4
3
  require 'securerandom'
4
+ require 'tmpdir'
5
5
 
6
6
  module Solargraph
7
7
  module LanguageServer
@@ -11,31 +11,79 @@ module Solargraph
11
11
  include Solargraph::Diagnostics::RubocopHelpers
12
12
 
13
13
  def process
14
- filename = uri_to_file(params['textDocument']['uri'])
15
- # Make the temp file in the original file's directory so RuboCop
16
- # detects the correct configuration
17
- # the .rb extension is needed for ruby file without extension, else rubocop won't format
18
- tempfile = File.join(File.dirname(filename), "_tmp_#{SecureRandom.hex(8)}_#{File.basename(filename)}.rb")
19
- rubocop_file = Diagnostics::RubocopHelpers.find_rubocop_file(filename)
20
- original = host.read_text(params['textDocument']['uri'])
21
- File.write tempfile, original
22
- begin
23
- args = ['-a', '-f', 'fi', tempfile]
24
- args.unshift('-c', fix_drive_letter(rubocop_file)) unless rubocop_file.nil?
25
- options, paths = RuboCop::Options.new.parse(args)
26
- store = RuboCop::ConfigStore.new
27
- redirect_stdout { RuboCop::Runner.new(options, store).run(paths) }
28
- result = File.read(tempfile)
29
- format original, result
30
- rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
31
- set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
32
- ensure
33
- File.unlink tempfile
14
+ file_uri = params['textDocument']['uri']
15
+ config = config_for(file_uri)
16
+ original = host.read_text(file_uri)
17
+ args = cli_args(file_uri, config)
18
+
19
+ require_rubocop(config['version'])
20
+ options, paths = RuboCop::Options.new.parse(args)
21
+ options[:stdin] = original
22
+ corrections = redirect_stdout do
23
+ RuboCop::Runner.new(options, RuboCop::ConfigStore.new).run(paths)
34
24
  end
25
+ result = options[:stdin]
26
+
27
+ log_corrections(corrections)
28
+
29
+ format original, result
30
+ rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
31
+ set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
35
32
  end
36
33
 
37
34
  private
38
35
 
36
+ def log_corrections(corrections)
37
+ corrections = corrections&.strip
38
+ return if corrections&.empty?
39
+
40
+ Solargraph.logger.info('Formatting result:')
41
+ corrections.each_line do |line|
42
+ next if line.strip.empty?
43
+ Solargraph.logger.info(line.strip)
44
+ end
45
+ end
46
+
47
+ def config_for(file_uri)
48
+ conf = host.formatter_config(file_uri)
49
+ return {} unless conf.is_a?(Hash)
50
+
51
+ conf['rubocop'] || {}
52
+ end
53
+
54
+ def cli_args file_uri, config
55
+ file = UriHelpers.uri_to_file(file_uri)
56
+ args = [
57
+ config['cops'] == 'all' ? '--auto-correct-all' : '--auto-correct',
58
+ '--cache', 'false',
59
+ '--format', formatter_class(config).name,
60
+ ]
61
+
62
+ ['except', 'only'].each do |arg|
63
+ cops = cop_list(config[arg])
64
+ args += ["--#{arg}", cops] if cops
65
+ end
66
+
67
+ args += config['extra_args'] if config['extra_args']
68
+ args + [file]
69
+ end
70
+
71
+ def formatter_class(config)
72
+ if self.class.const_defined?('BlankRubocopFormatter')
73
+ BlankRubocopFormatter
74
+ else
75
+ require_rubocop(config['version'])
76
+ klass = Class.new(::RuboCop::Formatter::BaseFormatter)
77
+ self.class.const_set 'BlankRubocopFormatter', klass
78
+ end
79
+ end
80
+
81
+ def cop_list(value)
82
+ value = value.join(',') if value.respond_to?(:join)
83
+ return nil if value == '' || !value.is_a?(String)
84
+ value
85
+ end
86
+
39
87
  # @param original [String]
40
88
  # @param result [String]
41
89
  # @return [void]
@@ -17,7 +17,7 @@ module Solargraph
17
17
  if !this_link.nil? && this_link != last_link
18
18
  parts.push this_link
19
19
  end
20
- parts.push pin.detail.gsub(':', '\\:') unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
20
+ parts.push "`" + pin.detail.gsub(':', '\\:') + "`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
21
21
  parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty?
22
22
  unless parts.empty?
23
23
  data = parts.join("\n\n")
@@ -20,9 +20,7 @@ module Solargraph
20
20
  def initialize workspace = Solargraph::Workspace.new, name = nil
21
21
  @workspace = workspace
22
22
  @name = name
23
- api_map.catalog bench
24
- @synchronized = true
25
- @catalog_mutex = Mutex.new
23
+ @synchronized = false
26
24
  end
27
25
 
28
26
  def inspect
@@ -48,9 +46,13 @@ module Solargraph
48
46
  # @return [void]
49
47
  def attach source
50
48
  mutex.synchronize do
51
- @synchronized = (@current == source) if synchronized?
49
+ if @current && @current.filename != source.filename && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
50
+ source_map_hash.delete @current.filename
51
+ @synchronized = false
52
+ end
52
53
  @current = source
53
- catalog
54
+ maybe_map @current
55
+ api_map.catalog bench unless synchronized?
54
56
  end
55
57
  end
56
58
 
@@ -110,9 +112,9 @@ module Solargraph
110
112
  mutex.synchronize do
111
113
  next if File.directory?(filename) || !File.exist?(filename)
112
114
  next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
113
- @synchronized = false
114
115
  source = Solargraph::Source.load_string(File.read(filename), filename)
115
116
  workspace.merge(source)
117
+ maybe_map source
116
118
  result = true
117
119
  end
118
120
  result
@@ -158,6 +160,8 @@ module Solargraph
158
160
  position = Position.new(line, column)
159
161
  cursor = Source::Cursor.new(read(filename), position)
160
162
  api_map.clip(cursor).complete
163
+ rescue FileNotFoundError => e
164
+ handle_file_not_found filename, e
161
165
  end
162
166
 
163
167
  # Get definition suggestions for the expression at the specified file and
@@ -186,6 +190,8 @@ module Solargraph
186
190
  else
187
191
  api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
188
192
  end
193
+ rescue FileNotFoundError => e
194
+ handle_file_not_found(filename, e)
189
195
  end
190
196
 
191
197
  # Get signature suggestions for the method at the specified file and
@@ -209,7 +215,6 @@ module Solargraph
209
215
  # @return [Array<Solargraph::Range>]
210
216
  # @todo Take a Location instead of filename/line/column
211
217
  def references_from filename, line, column, strip: false
212
- # checkout filename
213
218
  cursor = api_map.cursor_at(filename, Position.new(line, column))
214
219
  clip = api_map.clip(cursor)
215
220
  pins = clip.define
@@ -222,7 +227,7 @@ module Solargraph
222
227
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
223
228
  # HACK: The additional location comparison is necessary because
224
229
  # Clip#define can return proxies for parameter pins
225
- referenced.any?{|r| r == pin || r.location == pin.location}
230
+ referenced.any? { |r| r == pin || r.location == pin.location }
226
231
  end
227
232
  # HACK: for language clients that exclude special characters from the start of variable names
228
233
  if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
@@ -261,14 +266,12 @@ module Solargraph
261
266
  # @param query [String]
262
267
  # @return [Array<YARD::CodeObjects::Base>]
263
268
  def document query
264
- catalog
265
269
  api_map.document query
266
270
  end
267
271
 
268
272
  # @param query [String]
269
273
  # @return [Array<String>]
270
274
  def search query
271
- catalog
272
275
  api_map.search query
273
276
  end
274
277
 
@@ -277,7 +280,6 @@ module Solargraph
277
280
  # @param query [String]
278
281
  # @return [Array<Pin::Base>]
279
282
  def query_symbols query
280
- catalog
281
283
  api_map.query_symbols query
282
284
  end
283
285
 
@@ -290,17 +292,19 @@ module Solargraph
290
292
  # @param filename [String]
291
293
  # @return [Array<Solargraph::Pin::Base>]
292
294
  def document_symbols filename
293
- # checkout filename
294
295
  api_map.document_symbols(filename)
295
296
  end
296
297
 
297
298
  # @param path [String]
298
299
  # @return [Array<Solargraph::Pin::Base>]
299
300
  def path_pins path
300
- catalog
301
301
  api_map.get_path_suggestions(path)
302
302
  end
303
303
 
304
+ def source_maps
305
+ source_map_hash.values
306
+ end
307
+
304
308
  # Get the current text of a file in the library.
305
309
  #
306
310
  # @param filename [String]
@@ -320,7 +324,6 @@ module Solargraph
320
324
  # be an option to do so.
321
325
  #
322
326
  return [] unless open?(filename)
323
- catalog
324
327
  result = []
325
328
  source = read(filename)
326
329
  repargs = {}
@@ -348,7 +351,7 @@ module Solargraph
348
351
  #
349
352
  # @return [void]
350
353
  def catalog
351
- @catalog_mutex.synchronize do
354
+ mutex.synchronize do
352
355
  break if synchronized?
353
356
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
354
357
  api_map.catalog bench
@@ -357,6 +360,16 @@ module Solargraph
357
360
  end
358
361
  end
359
362
 
363
+ def bench
364
+ source_maps = @current ? [@current] : []
365
+ source_maps.concat source_map_hash.values
366
+ Bench.new(
367
+ source_maps: source_maps,
368
+ load_paths: workspace.require_paths,
369
+ gemnames: workspace.gemnames
370
+ )
371
+ end
372
+
360
373
  # Get an array of foldable ranges for the specified file.
361
374
  #
362
375
  # @deprecated The library should not need to handle folding ranges. The
@@ -383,14 +396,49 @@ module Solargraph
383
396
  # @param source [Source]
384
397
  # @return [Boolean] True if the source was merged into the workspace.
385
398
  def merge source
399
+ Logging.logger.debug "Merging source: #{source.filename}"
386
400
  result = false
387
401
  mutex.synchronize do
388
402
  result = workspace.merge(source)
389
- @synchronized = !result if synchronized?
403
+ maybe_map source
390
404
  end
405
+ # catalog
391
406
  result
392
407
  end
393
408
 
409
+ def source_map_hash
410
+ @source_map_hash ||= {}
411
+ end
412
+
413
+ def mapped?
414
+ (workspace.filenames - source_map_hash.keys).empty?
415
+ end
416
+
417
+ def next_map
418
+ return false if mapped?
419
+ mutex.synchronize do
420
+ @synchronized = false
421
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
422
+ if src
423
+ Logging.logger.debug "Mapping #{src.filename}"
424
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
425
+ else
426
+ false
427
+ end
428
+ end
429
+ end
430
+
431
+ def map!
432
+ workspace.sources.each do |src|
433
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
434
+ end
435
+ self
436
+ end
437
+
438
+ def pins
439
+ @pins ||= []
440
+ end
441
+
394
442
  private
395
443
 
396
444
  # @return [Mutex]
@@ -403,14 +451,6 @@ module Solargraph
403
451
  @api_map ||= Solargraph::ApiMap.new
404
452
  end
405
453
 
406
- # @return [Bench]
407
- def bench
408
- Bench.new(
409
- workspace: workspace,
410
- opened: @current ? [@current] : []
411
- )
412
- end
413
-
414
454
  # Get the source for an open file or create a new source if the file
415
455
  # exists on disk. Sources created from disk are not added to the open
416
456
  # workspace files, i.e., the version on disk remains the authoritative
@@ -424,5 +464,35 @@ module Solargraph
424
464
  raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
425
465
  workspace.source(filename)
426
466
  end
467
+
468
+ def handle_file_not_found filename, error
469
+ if workspace.source(filename)
470
+ Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
471
+ nil
472
+ else
473
+ raise error
474
+ end
475
+ end
476
+
477
+ def maybe_map source
478
+ if source_map_hash.key?(source.filename)
479
+ return if source_map_hash[source.filename].code == source.code &&
480
+ source_map_hash[source.filename].source.synchronized? &&
481
+ source.synchronized?
482
+ if source.synchronized?
483
+ new_map = Solargraph::SourceMap.map(source)
484
+ unless source_map_hash[source.filename].try_merge!(new_map)
485
+ source_map_hash[source.filename] = new_map
486
+ @synchronized = false
487
+ end
488
+ else
489
+ # @todo Smelly instance variable access
490
+ source_map_hash[source.filename].instance_variable_set(:@source, source)
491
+ end
492
+ else
493
+ source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
494
+ @synchronized = false
495
+ end
496
+ end
427
497
  end
428
498
  end