solargraph 0.30.1 → 0.30.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f109fbce413be0100575d4375ff3bd0592626852334dfcb41161f7d7bbe6e67f
4
- data.tar.gz: 5fa2251f2b60f03c627add65eef3e42adf16e75cc579c36e581dc7972312b750
3
+ metadata.gz: 61453319a056f4779bf21c852bb34b5f797207c19ff9266d422d25c724934bc1
4
+ data.tar.gz: 405c13271fce7df56c5daae95b95430f5933a66728d3e2f6fff11762d4f392d0
5
5
  SHA512:
6
- metadata.gz: 2017a0eaea2090e26a3f99f0e8d9734e40b6f9692dfcdef8bcb2c424ba3180604807c3cceb49f029fc87fa25e17e6212a79e97e62f6fd570fc2a8f6a8a75652b
7
- data.tar.gz: e386dcd357c431214a64e1774097a61827c9d54f70b290313868250a392095a520d87788c0741bc660ceb1ceb114f16c71a54923fbfe3755dd84776c9c64e6a0
6
+ metadata.gz: 5dcd832eff27b926dd8d244feff2edc37398f0004fa62dd002f07981e325d6f5a88f7c93bc1ad91d1f0ce34954790afc0410ed9581936d5ad57a465f99cc2372
7
+ data.tar.gz: 246cb5bbb21a7134532512c0321e86633d21adcf4eebd24de3d33427ab3da39663dafba488b8e0a304abd252c874408513ced6370cdc8e88cde18187f1242b79
@@ -354,13 +354,7 @@ module Solargraph
354
354
  # @param scope [Symbol] :instance or :class
355
355
  # @return [Array<Solargraph::Pin::Base>]
356
356
  def get_method_stack fqns, name, scope: :instance
357
- # @todo This cache is still causing problems, but only when using
358
- # Solargraph on Solargraph itself.
359
- # cached = cache.get_method_stack(fqns, name, scope)
360
- # return cached unless cached.nil?
361
- result = get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
362
- # cache.set_method_stack(fqns, name, scope, result)
363
- # result
357
+ get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
364
358
  end
365
359
 
366
360
  # Get an array of all suggestions that match the specified path.
@@ -3,7 +3,6 @@ module Solargraph
3
3
  class Cache
4
4
  def initialize
5
5
  @methods = {}
6
- @method_stacks = {}
7
6
  @constants = {}
8
7
  @qualified_namespaces = {}
9
8
  end
@@ -16,14 +15,6 @@ module Solargraph
16
15
  @methods[[fqns, scope, visibility.sort, deep]] = value
17
16
  end
18
17
 
19
- def get_method_stack fqns, name, scope
20
- @method_stacks[[fqns, name, scope]]
21
- end
22
-
23
- def set_method_stack fqns, name, scope, value
24
- @method_stacks[[fqns, name, scope]] = value
25
- end
26
-
27
18
  def get_constants namespace, context
28
19
  @constants[[namespace, context]]
29
20
  end
@@ -43,7 +34,6 @@ module Solargraph
43
34
  # @return [void]
44
35
  def clear
45
36
  @methods.clear
46
- @method_stacks.clear
47
37
  @constants.clear
48
38
  @qualified_namespaces.clear
49
39
  end
@@ -51,7 +41,6 @@ module Solargraph
51
41
  # @return [Boolean]
52
42
  def empty?
53
43
  @methods.empty? &&
54
- @method_stacks.empty? &&
55
44
  @constants.empty? &&
56
45
  @qualified_namespaces.empty?
57
46
  end
@@ -6,6 +6,7 @@ module Solargraph
6
6
  autoload :Base, 'solargraph/diagnostics/base'
7
7
  autoload :Severities, 'solargraph/diagnostics/severities'
8
8
  autoload :Rubocop, 'solargraph/diagnostics/rubocop'
9
+ autoload :RubocopHelpers, 'solargraph/diagnostics/rubocop_helpers'
9
10
  autoload :RequireNotFound, 'solargraph/diagnostics/require_not_found'
10
11
  autoload :TypeNotDefined, 'solargraph/diagnostics/type_not_defined'
11
12
  autoload :UpdateErrors, 'solargraph/diagnostics/update_errors'
@@ -6,6 +6,8 @@ module Solargraph
6
6
  # This reporter provides linting through RuboCop.
7
7
  #
8
8
  class Rubocop < Base
9
+ include RubocopHelpers
10
+
9
11
  # Conversion of RuboCop severity names to LSP constants
10
12
  SEVERITIES = {
11
13
  'refactor' => Severities::HINT,
@@ -16,46 +18,23 @@ module Solargraph
16
18
  }
17
19
 
18
20
  # @param source [Solargraph::Source]
19
- # @param api_map [Solargraph::ApiMap]
21
+ # @param _api_map [Solargraph::ApiMap]
20
22
  # @return [Array<Hash>]
21
- def diagnose source, api_map
23
+ def diagnose source, _api_map
22
24
  options, paths = generate_options(source.filename, source.code)
23
25
  runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
24
26
  result = redirect_stdout{ runner.run(paths) }
25
27
  make_array JSON.parse(result)
28
+ rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
29
+ raise DiagnosticsError, "Error in RuboCop configuration: #{e.message}"
26
30
  rescue JSON::ParserError
27
31
  raise DiagnosticsError, 'RuboCop returned invalid data'
28
32
  end
29
33
 
30
34
  private
31
35
 
32
- # @param filename [String]
33
- # @param code [String]
34
- # @return [Array]
35
- def generate_options filename, code
36
- args = ['-f', 'j']
37
- rubocop_file = find_rubocop_file(filename)
38
- args.push('-c', fix_drive_letter(rubocop_file)) unless rubocop_file.nil?
39
- args.push filename
40
- options, paths = RuboCop::Options.new.parse(args)
41
- options[:stdin] = code
42
- [options, paths]
43
- end
44
-
45
- # @param filename [String]
46
- # @return [String, nil]
47
- def find_rubocop_file filename
48
- dir = File.dirname(filename)
49
- until File.dirname(dir) == dir
50
- here = File.join(dir, '.rubocop.yml')
51
- return here if File.exist?(here)
52
- dir = File.dirname(dir)
53
- end
54
- nil
55
- end
56
-
57
- # @todo This is a smelly way to redirect output, but the RuboCop specs do the
58
- # same thing.
36
+ # @todo This is a smelly way to redirect output, but the RuboCop specs do
37
+ # the same thing.
59
38
  # @return [String]
60
39
  def redirect_stdout
61
40
  redir = StringIO.new
@@ -71,43 +50,48 @@ module Solargraph
71
50
  diagnostics = []
72
51
  resp['files'].each do |file|
73
52
  file['offenses'].each do |off|
74
- if off['location']['start_line'] != off['location']['last_line']
75
- last_line = off['location']['start_line']
76
- last_column = 0
77
- else
78
- last_line = off['location']['last_line'] - 1
79
- last_column = off['location']['last_column']
80
- end
81
- diag = {
82
- range: {
83
- start: {
84
- line: off['location']['start_line'] - 1,
85
- character: off['location']['start_column'] - 1
86
- },
87
- end: {
88
- line: last_line,
89
- character: last_column
90
- }
91
- },
92
- # 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
93
- severity: SEVERITIES[off['severity']],
94
- source: off['cop_name'],
95
- message: off['message'].gsub(/^#{off['cop_name']}\:/, '')
96
- }
97
- diagnostics.push diag
53
+ diagnostics.push offense_to_diagnostic(off)
98
54
  end
99
55
  end
100
56
  diagnostics
101
57
  end
102
58
 
103
- # RuboCop internally uses capitalized drive letters for Windows paths,
104
- # so we need to convert the paths provided to the command.
59
+ # Convert a RuboCop offense to an LSP diagnostic
105
60
  #
106
- # @param path [String]
107
- # @return [String]
108
- def fix_drive_letter path
109
- return path unless path.match(/^[a-z]:/)
110
- path[0].upcase + path[1..-1]
61
+ # @param off [Hash] Offense received from Rubocop
62
+ # @return [Hash] LSP diagnostic
63
+ def offense_to_diagnostic off
64
+ {
65
+ range: offense_range(off).to_hash,
66
+ # 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
67
+ severity: SEVERITIES[off['severity']],
68
+ source: off['cop_name'],
69
+ message: off['message'].gsub(/^#{off['cop_name']}\:/, '')
70
+ }
71
+ end
72
+
73
+ # @param off [Hash]
74
+ # @return [Range]
75
+ def offense_range off
76
+ Range.new(offense_start_position(off), offense_ending_position(off))
77
+ end
78
+
79
+ # @param off [Hash]
80
+ # @return [Position]
81
+ def offense_start_position off
82
+ Position.new(off['location']['start_line'] - 1, off['location']['start_column'] - 1)
83
+ end
84
+
85
+ # @param off [Hash]
86
+ # @return [Position]
87
+ def offense_ending_position off
88
+ if off['location']['start_line'] != off['location']['last_line']
89
+ Position.new(off['location']['start_line'], 0)
90
+ else
91
+ Position.new(
92
+ off['location']['start_line'] - 1, off['location']['last_column']
93
+ )
94
+ end
111
95
  end
112
96
  end
113
97
  end
@@ -0,0 +1,46 @@
1
+ module Solargraph
2
+ module Diagnostics
3
+ module RubocopHelpers
4
+ module_function
5
+
6
+ # Generate command-line options for the specified filename and code.
7
+ #
8
+ # @param filename [String]
9
+ # @param code [String]
10
+ # @return [Array(Array<String>, Array<String>)]
11
+ def generate_options filename, code
12
+ args = ['-f', 'j']
13
+ rubocop_file = find_rubocop_file(filename)
14
+ args.push('-c', fix_drive_letter(rubocop_file)) unless rubocop_file.nil?
15
+ args.push filename
16
+ options, paths = RuboCop::Options.new.parse(args)
17
+ options[:stdin] = code
18
+ [options, paths]
19
+ end
20
+
21
+ # Find a RuboCop configuration file in a file's directory tree.
22
+ #
23
+ # @param filename [String]
24
+ # @return [String, nil]
25
+ def find_rubocop_file filename
26
+ dir = File.dirname(filename)
27
+ until File.dirname(dir) == dir
28
+ here = File.join(dir, '.rubocop.yml')
29
+ return here if File.exist?(here)
30
+ dir = File.dirname(dir)
31
+ end
32
+ nil
33
+ end
34
+
35
+ # RuboCop internally uses capitalized drive letters for Windows paths,
36
+ # so we need to convert the paths provided to the command.
37
+ #
38
+ # @param path [String]
39
+ # @return [String]
40
+ def fix_drive_letter path
41
+ return path unless path.match(/^[a-z]:/)
42
+ path[0].upcase + path[1..-1]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -10,6 +10,7 @@ module Solargraph
10
10
  class Host
11
11
  autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
12
12
  autoload :Cataloger, 'solargraph/language_server/host/cataloger'
13
+ autoload :Sources, 'solargraph/language_server/host/sources'
13
14
 
14
15
  include Solargraph::LanguageServer::UriHelpers
15
16
  include Logging
@@ -93,14 +94,17 @@ module Solargraph
93
94
  end
94
95
 
95
96
  # Respond to a notification that a file was created in the workspace.
96
- # The library will determine whether the file should be added to the
97
- # workspace; see Solargraph::Library#create_from_disk.
97
+ # The libraries will determine whether the file should be merged; see
98
+ # Solargraph::Library#create_from_disk.
98
99
  #
99
100
  # @param uri [String] The file uri.
101
+ # @return [Boolean] True if a library accepted the file.
100
102
  def create uri
101
- library = library_for(uri)
102
103
  filename = uri_to_file(uri)
103
- result = library.create_from_disk(filename)
104
+ result = false
105
+ libraries.each do |lib|
106
+ result = true if lib.create_from_disk filename
107
+ end
104
108
  diagnoser.schedule uri if open?(uri)
105
109
  result
106
110
  end
@@ -109,9 +113,11 @@ module Solargraph
109
113
  #
110
114
  # @param uri [String] The file uri.
111
115
  def delete uri
112
- library = library_for(uri)
116
+ sources.close uri
113
117
  filename = uri_to_file(uri)
114
- library.delete filename
118
+ libraries.each do |lib|
119
+ lib.delete filename
120
+ end
115
121
  send_notification "textDocument/publishDiagnostics", {
116
122
  uri: uri,
117
123
  diagnostics: []
@@ -124,8 +130,10 @@ module Solargraph
124
130
  # @param text [String] The contents of the file.
125
131
  # @param version [Integer] A version number.
126
132
  def open uri, text, version
127
- library = library_for(uri)
128
- library.open uri_to_file(uri), text, version
133
+ src = sources.open(uri, text, version)
134
+ libraries.each do |lib|
135
+ lib.merge src
136
+ end
129
137
  diagnoser.schedule uri
130
138
  end
131
139
 
@@ -140,45 +148,58 @@ module Solargraph
140
148
  # @param uri [String]
141
149
  # @return [Boolean]
142
150
  def open? uri
143
- unsafe_open?(uri)
151
+ sources.include? uri
144
152
  end
145
153
 
146
154
  # Close the file specified by the URI.
147
155
  #
148
156
  # @param uri [String]
157
+ # @return [void]
149
158
  def close uri
150
- library = library_for(uri)
151
- library.close uri_to_file(uri)
159
+ logger.info "Closing #{uri}"
160
+ sources.close uri
152
161
  diagnoser.schedule uri
153
162
  end
154
163
 
155
164
  # @param uri [String]
165
+ # @return [void]
156
166
  def diagnose uri
157
- logger.info "Diagnosing #{uri}"
158
- library = library_for(uri)
159
- begin
160
- results = library.diagnose uri_to_file(uri)
161
- send_notification "textDocument/publishDiagnostics", {
167
+ if sources.include?(uri)
168
+ logger.info "Diagnosing #{uri}"
169
+ library = library_for(uri)
170
+ begin
171
+ results = library.diagnose uri_to_file(uri)
172
+ send_notification "textDocument/publishDiagnostics", {
173
+ uri: uri,
174
+ diagnostics: results
175
+ }
176
+ rescue DiagnosticsError => e
177
+ logger.warn "Error in diagnostics: #{e.message}"
178
+ options['diagnostics'] = false
179
+ send_notification 'window/showMessage', {
180
+ type: LanguageServer::MessageTypes::ERROR,
181
+ message: "Error in diagnostics: #{e.message}"
182
+ }
183
+ end
184
+ else
185
+ send_notification 'textDocument/publishDiagnostics', {
162
186
  uri: uri,
163
- diagnostics: results
164
- }
165
- rescue DiagnosticsError => e
166
- STDERR.puts "Error in diagnostics: #{e.message}"
167
- options['diagnostics'] = false
168
- send_notification 'window/showMessage', {
169
- type: LanguageServer::MessageTypes::ERROR,
170
- message: "Error in diagnostics: #{e.message}"
187
+ diagnostics: []
171
188
  }
172
189
  end
173
190
  end
174
191
 
192
+ # Update a document from the parameters of a textDocument/didChange
193
+ # method.
194
+ #
195
+ # @param params [Hash]
196
+ # @return [void]
175
197
  def change params
176
- library = library_for(params['textDocument']['uri'])
177
198
  updater = generate_updater(params)
178
- library.update updater
179
- # @todo Since Library#checkout already catalogs, this cataloging
180
- # might not be necessary.
181
- cataloger.ping(library) unless library.synchronized?
199
+ src = sources.update(params['textDocument']['uri'], updater)
200
+ libraries.each do |lib|
201
+ lib.merge src
202
+ end
182
203
  diagnoser.schedule params['textDocument']['uri']
183
204
  end
184
205
 
@@ -206,8 +227,12 @@ module Solargraph
206
227
  # Prepare a library for the specified directory.
207
228
  #
208
229
  # @param directory [String]
209
- # @param name [String]
230
+ # @param name [String, nil]
231
+ # @return [void]
210
232
  def prepare directory, name = nil
233
+ # No need to create a library without a directory. The generic library
234
+ # will handle it.
235
+ return if directory.nil?
211
236
  logger.info "Preparing library for #{directory}"
212
237
  path = ''
213
238
  path = normalize_separators(directory) unless directory.nil?
@@ -225,20 +250,26 @@ module Solargraph
225
250
  cataloger.start
226
251
  end
227
252
 
253
+ # Prepare multiple folders.
254
+ #
255
+ # @param array [Array<Hash{String => String}>]
256
+ # @return [void]
228
257
  def prepare_folders array
258
+ return if array.nil?
229
259
  array.each do |folder|
230
260
  prepare uri_to_file(folder['uri']), folder['name']
231
261
  end
232
262
  end
233
263
 
264
+ # Remove a directory.
265
+ #
266
+ # @param directory [String]
267
+ # @return [void]
234
268
  def remove directory
235
269
  logger.info "Removing library for #{directory}"
236
270
  # @param lib [Library]
237
271
  libraries.delete_if do |lib|
238
272
  next false if lib.workspace.directory != directory
239
- lib.open_sources.each do |src|
240
- orphan_library.open(src.filename, src.code, src.version)
241
- end
242
273
  true
243
274
  end
244
275
  end
@@ -299,6 +330,7 @@ module Solargraph
299
330
  # that were not flagged for dynamic registration by the client.
300
331
  #
301
332
  # @param methods [Array<String>] The methods to register
333
+ # @return [void]
302
334
  def register_capabilities methods
303
335
  logger.debug "Registering capabilities: #{methods}"
304
336
  @register_semaphore.synchronize do
@@ -320,6 +352,7 @@ module Solargraph
320
352
  # that were not flagged for dynamic registration by the client.
321
353
  #
322
354
  # @param methods [Array<String>] The methods to unregister
355
+ # @return [void]
323
356
  def unregister_capabilities methods
324
357
  logger.debug "Unregistering capabilities: #{methods}"
325
358
  @register_semaphore.synchronize do
@@ -338,6 +371,7 @@ module Solargraph
338
371
  # Flag a method as available for dynamic registration.
339
372
  #
340
373
  # @param method [String] The method name, e.g., 'textDocument/completion'
374
+ # @return [void]
341
375
  def allow_registration method
342
376
  @register_semaphore.synchronize do
343
377
  @dynamic_capabilities.add method
@@ -364,6 +398,7 @@ module Solargraph
364
398
  cataloger.synchronizing?
365
399
  end
366
400
 
401
+ # @return [void]
367
402
  def stop
368
403
  @stopped = true
369
404
  cataloger.stop
@@ -454,15 +489,15 @@ module Solargraph
454
489
  # @return [Array<Solargraph::Range>]
455
490
  def references_from filename, line, column, strip: true
456
491
  library = library_for(file_to_uri(filename))
457
- result = library.references_from(filename, line, column, strip: strip)
492
+ library.references_from(filename, line, column, strip: strip)
458
493
  end
459
494
 
460
495
  # @param query [String]
461
496
  # @return [Array<Solargraph::Pin::Base>]
462
497
  def query_symbols query
463
498
  result = []
464
- libraries.each { |lib| result.concat lib.query_symbols(query) }
465
- result
499
+ (libraries + [generic_library]).each { |lib| result.concat lib.query_symbols(query) }
500
+ result.uniq
466
501
  end
467
502
 
468
503
  # @param query [String]
@@ -492,6 +527,7 @@ module Solargraph
492
527
  #
493
528
  # @param text [String]
494
529
  # @param type [Integer] A MessageType constant
530
+ # @return [void]
495
531
  def show_message text, type = LanguageServer::MessageTypes::INFO
496
532
  send_notification 'window/showMessage', {
497
533
  type: type,
@@ -506,6 +542,7 @@ module Solargraph
506
542
  # @param actions [Array<String>] Response options for the client
507
543
  # @param &block The block that processes the response
508
544
  # @yieldparam [String] The action received from the client
545
+ # @return [void]
509
546
  def show_message_request text, type, actions, &block
510
547
  send_request 'window/showMessageRequest', {
511
548
  type: type,
@@ -546,6 +583,8 @@ module Solargraph
546
583
  lib.catalog
547
584
  end
548
585
 
586
+ # @param uri [String]
587
+ # @return [Array<Range>]
549
588
  def folding_ranges uri
550
589
  library = library_for(uri)
551
590
  file = uri_to_file(uri)
@@ -559,26 +598,56 @@ module Solargraph
559
598
  @libraries ||= []
560
599
  end
561
600
 
601
+ # @return [Sources]
602
+ def sources
603
+ @sources ||= Sources.new
604
+ end
605
+
562
606
  # @param uri [String]
563
607
  # @return [Library]
564
608
  def library_for uri
565
- return libraries.first if libraries.length == 1
609
+ explicit_library_for(uri) ||
610
+ implicit_library_for(uri) ||
611
+ generic_library_for(uri)
612
+ end
613
+
614
+ # @param uri [String]
615
+ # @return [Library, nil]
616
+ def explicit_library_for uri
566
617
  filename = uri_to_file(uri)
567
- # Find a library with an explicit reference to the file
568
618
  libraries.each do |lib|
569
- return lib if lib.contain?(filename) || lib.open?(filename)
619
+ if lib.contain?(filename) #|| lib.open?(filename)
620
+ lib.attach sources.find(uri) if sources.include?(uri)
621
+ return lib
622
+ end
570
623
  end
571
- # Find a library with a workspace that contains the file
624
+ nil
625
+ end
626
+
627
+ # @param uri [String]
628
+ # @return [Library, nil]
629
+ def implicit_library_for uri
630
+ filename = uri_to_file(uri)
572
631
  libraries.each do |lib|
573
- return lib if filename.start_with?(lib.workspace.directory)
632
+ # return lib if filename.start_with?(lib.workspace.directory)
633
+ if lib.open?(filename) || filename.start_with?(lib.workspace.directory)
634
+ lib.attach sources.find(uri)
635
+ return lib
636
+ end
574
637
  end
575
- return orphan_library if orphan_library.open?(filename)
576
- raise "No library for #{uri}"
638
+ nil
639
+ end
640
+
641
+ # @param uri [String]
642
+ # @return [Library]
643
+ def generic_library_for uri
644
+ generic_library.attach sources.find(uri)
645
+ generic_library
577
646
  end
578
647
 
579
648
  # @return [Library]
580
- def orphan_library
581
- @orphan_library ||= Solargraph::Library.new
649
+ def generic_library
650
+ @generic_library ||= Solargraph::Library.new
582
651
  end
583
652
 
584
653
  # @return [Diagnoser]
@@ -591,11 +660,6 @@ module Solargraph
591
660
  @cataloger ||= Cataloger.new(self)
592
661
  end
593
662
 
594
- def unsafe_open? uri
595
- library = library_for(uri)
596
- library.open?(uri_to_file(uri))
597
- end
598
-
599
663
  def requests
600
664
  @requests ||= {}
601
665
  end
@@ -0,0 +1,39 @@
1
+ module Solargraph
2
+ module LanguageServer
3
+ class Host
4
+ class Sources
5
+ include UriHelpers
6
+
7
+ def open uri, text, version
8
+ filename = uri_to_file(uri)
9
+ source = Solargraph::Source.new(text, filename, version)
10
+ open_source_hash[uri] = source
11
+ end
12
+
13
+ def update uri, updater
14
+ src = find(uri)
15
+ open_source_hash[uri] = src.synchronize(updater)
16
+ end
17
+
18
+ # @return [Source]
19
+ def find uri
20
+ open_source_hash[uri] || raise(Solargraph::FileNotFoundError, "Host could not find #{uri}")
21
+ end
22
+
23
+ def close uri
24
+ open_source_hash.delete uri
25
+ end
26
+
27
+ def include? uri
28
+ open_source_hash.key? uri
29
+ end
30
+
31
+ private
32
+
33
+ def open_source_hash
34
+ @open_source_hash ||= {}
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,7 +5,6 @@ module Solargraph
5
5
  def process
6
6
  host.configure params['initializationOptions']
7
7
  if support_workspace_folders?
8
- # @todo Prepare multiple folders
9
8
  host.prepare_folders params['workspaceFolders']
10
9
  elsif params['rootUri']
11
10
  host.prepare UriHelpers.uri_to_file(params['rootUri'])
@@ -42,7 +41,8 @@ module Solargraph
42
41
  def support_workspace_folders?
43
42
  params['capabilities'] &&
44
43
  params['capabilities']['workspace'] &&
45
- params['capabilities']['workspace']['workspaceFolders']
44
+ params['capabilities']['workspace']['workspaceFolders'] &&
45
+ params['workspaceFolders']
46
46
  end
47
47
 
48
48
  def static_completion
@@ -113,12 +113,6 @@ module Solargraph
113
113
  }
114
114
  end
115
115
 
116
- def static_references
117
- {
118
- referencesProvider: true
119
- }
120
- end
121
-
122
116
  def static_folding_range
123
117
  {
124
118
  foldingRangeProvider: true
@@ -18,7 +18,7 @@ module Solargraph
18
18
  # @param file [String]
19
19
  # @return [String]
20
20
  def file_to_uri file
21
- "file://#{URI.encode(file.gsub(/^([a-z]\:)/i, '/\1'))}"
21
+ "file://#{URI.encode(file.gsub(/^([a-z]\:)/i, '/\1')).gsub(/\:/, '%3A')}"
22
22
  end
23
23
  end
24
24
  end
@@ -11,6 +11,7 @@ module Solargraph
11
11
  attr_reader :name
12
12
 
13
13
  # @param workspace [Solargraph::Workspace]
14
+ # @param name [String, nil]
14
15
  def initialize workspace = Solargraph::Workspace.new, name = nil
15
16
  @workspace = workspace
16
17
  @name = name
@@ -30,37 +31,52 @@ module Solargraph
30
31
  # Open a file in the library. Opening a file will make it available for
31
32
  # checkout and merge it into the workspace if applicable.
32
33
  #
34
+ # @deprecated The library should not be responsible for this. Instead, it
35
+ # should accept a source and determine whether or not to merge it.
36
+ #
33
37
  # @param filename [String]
34
38
  # @param text [String]
35
39
  # @param version [Integer]
36
40
  # @return [void]
37
41
  def open filename, text, version
38
- mutex.synchronize do
39
- @synchronized = false
40
- source = Solargraph::Source.load_string(text, filename, version)
41
- workspace.merge source
42
- open_file_hash[filename] = source
43
- checkout filename
44
- end
42
+ logger.warn "Library#open is deprecated"
43
+ source = Solargraph::Source.load_string(text, filename, version)
44
+ merge source
45
+ attach source
45
46
  end
46
47
 
48
+ # Open a file from disk and try to merge it into the workspace.
49
+ #
50
+ # @param filename [String]
51
+ # @return [Boolean] True if the file was merged into the source.
47
52
  def open_from_disk filename
53
+ source = Solargraph::Source.load(filename)
54
+ merge source
55
+ end
56
+
57
+ # Attach a source to the library.
58
+ #
59
+ # The attached source does not need to be a part of the workspace. The
60
+ # library will include it in the ApiMap while it's attached. Only one
61
+ # source can be attached to the library at a time.
62
+ #
63
+ # @param source [Source]
64
+ # @return [void]
65
+ def attach source
48
66
  mutex.synchronize do
49
- @synchronized = false
50
- source = Solargraph::Source.load(filename)
51
- workspace.merge source
52
- open_file_hash[filename] = source
53
- checkout filename
67
+ @synchronized = (@current == source) if synchronized?
68
+ @current = source
54
69
  end
55
70
  end
56
71
 
57
- # True if the specified file is currently open.
72
+ # True if the specified file is currently attached.
58
73
  #
59
74
  # @param filename [String]
60
75
  # @return [Boolean]
61
- def open? filename
62
- open_file_hash.has_key? filename
76
+ def attached? filename
77
+ !@current.nil? && @current.filename == filename
63
78
  end
79
+ alias open? attached?
64
80
 
65
81
  # True if the specified file is included in the workspace (but not
66
82
  # necessarily open).
@@ -84,7 +100,6 @@ module Solargraph
84
100
  @synchronized = false
85
101
  source = Solargraph::Source.load_string(text, filename)
86
102
  workspace.merge(source)
87
- catalog
88
103
  result = true
89
104
  end
90
105
  result
@@ -103,9 +118,6 @@ module Solargraph
103
118
  @synchronized = false
104
119
  source = Solargraph::Source.load_string(File.read(filename), filename)
105
120
  workspace.merge(source)
106
- open_file_hash[filename] = source if open_file_hash.key?(filename)
107
- @current = source if @current && @current.filename == source.filename
108
- catalog
109
121
  result = true
110
122
  end
111
123
  result
@@ -120,9 +132,9 @@ module Solargraph
120
132
  def delete filename
121
133
  mutex.synchronize do
122
134
  @synchronized = false
123
- open_file_hash.delete filename
135
+ @current = nil if @current && @current.filename == filename
124
136
  workspace.remove filename
125
- catalog
137
+ # catalog
126
138
  end
127
139
  end
128
140
 
@@ -134,7 +146,7 @@ module Solargraph
134
146
  def close filename
135
147
  mutex.synchronize do
136
148
  @synchronized = false
137
- open_file_hash.delete filename
149
+ @current = nil if @current && @current.filename == filename
138
150
  catalog
139
151
  end
140
152
  end
@@ -194,13 +206,13 @@ module Solargraph
194
206
  return [] if pins.empty?
195
207
  result = []
196
208
  pins.uniq.each do |pin|
197
- (workspace.sources + open_file_hash.values).uniq.each do |source|
209
+ (workspace.sources + (@current ? [@current] : [])).uniq.each do |source|
198
210
  found = source.references(pin.name)
199
211
  found.select! do |loc|
200
212
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
201
213
  referenced.any?{|r| r == pin}
202
214
  end
203
- # HACK for language clients that exclude special characters from the start of variable names
215
+ # HACK: for language clients that exclude special characters from the start of variable names
204
216
  if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
205
217
  found.map! do |loc|
206
218
  Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
@@ -242,7 +254,7 @@ module Solargraph
242
254
  # @return [Source]
243
255
  def checkout filename
244
256
  checked = read(filename)
245
- @synchronized = (checked.filename == @current.filename) if synchronized?
257
+ @synchronized = (checked == @current) if synchronized?
246
258
  @current = checked
247
259
  catalog
248
260
  @current
@@ -281,7 +293,6 @@ module Solargraph
281
293
  # @return [Array<Solargraph::Pin::Base>]
282
294
  def document_symbols filename
283
295
  checkout filename
284
- return [] unless open_file_hash.key?(filename)
285
296
  api_map.document_symbols(filename)
286
297
  end
287
298
 
@@ -297,20 +308,21 @@ module Solargraph
297
308
  # @note This method will not update the library's ApiMap. See
298
309
  # Library#synchronized? and Library#catalog for more information.
299
310
  #
311
+ # @deprecated The library should not be responsible for this. Instead, it
312
+ # should accept a source and determine whether or not to merge it.
300
313
  #
301
314
  # @raise [FileNotFoundError] if the updater's file is not available.
302
315
  # @param updater [Solargraph::Source::Updater]
303
316
  # @return [void]
304
317
  def update updater
318
+ logger.warn 'Library#update is deprecated'
305
319
  mutex.synchronize do
306
320
  if workspace.has_file?(updater.filename)
307
321
  workspace.synchronize!(updater)
308
- open_file_hash[updater.filename] = workspace.source(updater.filename) if open?(updater.filename)
309
- else
310
- raise FileNotFoundError, "Unable to update #{updater.filename}" unless open?(updater.filename)
311
- open_file_hash[updater.filename] = open_file_hash[updater.filename].synchronize(updater)
322
+ @current = workspace.source(updater.filename) if @current && @current.filename == updater.filename
323
+ elsif @current && @current.filename == updater.filename
324
+ @current = @current.synchronize(updater)
312
325
  end
313
- @current = open_file_hash[updater.filename] if @current && @current.filename == updater.filename
314
326
  @synchronized = false
315
327
  end
316
328
  end
@@ -332,7 +344,9 @@ module Solargraph
332
344
  # @todo Only open files get diagnosed. Determine whether anything or
333
345
  # everything in the workspace should get diagnosed, or if there should
334
346
  # be an option to do so.
347
+ #
335
348
  return [] unless open?(filename)
349
+ catalog
336
350
  result = []
337
351
  source = read(filename)
338
352
  workspace.config.reporters.each do |name|
@@ -355,23 +369,45 @@ module Solargraph
355
369
  end
356
370
  end
357
371
 
372
+ # Get an array of foldable ranges for the specified file.
373
+ #
374
+ # @param filename [String]
375
+ # @return [Array<Range>]
358
376
  def folding_ranges filename
359
377
  read(filename).folding_ranges
360
378
  end
361
379
 
380
+ # @deprecated Libraries are no longer responsible for tracking open files.
381
+ #
362
382
  # @return [Array<Source>]
363
383
  def open_sources
364
- open_file_hash.values
384
+ logger.warn 'Library#open_sources is deprecated'
385
+ @current ? [@current] : []
365
386
  end
366
387
 
367
388
  # Create a library from a directory.
368
389
  #
369
390
  # @param directory [String] The path to be used for the workspace
391
+ # @param name [String, nil]
370
392
  # @return [Solargraph::Library]
371
393
  def self.load directory = '', name = nil
372
394
  Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
373
395
  end
374
396
 
397
+ # Try to merge a source into the library's workspace. If the workspace is
398
+ # not configured to include the source, it gets ignored.
399
+ #
400
+ # @param source [Source]
401
+ # @return [Boolean] True if the source was merged into the workspace.
402
+ def merge source
403
+ result = nil
404
+ mutex.synchronize do
405
+ result = workspace.merge(source)
406
+ @synchronized = result if synchronized?
407
+ end
408
+ result
409
+ end
410
+
375
411
  private
376
412
 
377
413
  # @return [Mutex]
@@ -392,14 +428,6 @@ module Solargraph
392
428
  )
393
429
  end
394
430
 
395
- # A collection of files that are currently open in the library. Open
396
- # files do not need to be in the workspace.
397
- #
398
- # @return [Hash{String => Source}]
399
- def open_file_hash
400
- @open_file_hash ||= {}
401
- end
402
-
403
431
  # Get the source for an open file or create a new source if the file
404
432
  # exists on disk. Sources created from disk are not added to the open
405
433
  # workspace files, i.e., the version on disk remains the authoritative
@@ -409,7 +437,7 @@ module Solargraph
409
437
  # @param filename [String]
410
438
  # @return [Solargraph::Source]
411
439
  def read filename
412
- return open_file_hash[filename] if open_file_hash.key?(filename)
440
+ return @current if @current && @current.filename == filename
413
441
  raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
414
442
  workspace.source(filename)
415
443
  end
@@ -71,8 +71,8 @@ module Solargraph
71
71
  def generate_link
72
72
  this_path = path || return_type.tag
73
73
  return nil if this_path.nil? || this_path == 'undefined'
74
- return this_path if comments.empty?
75
- "[#{this_path.gsub('_', '\\\\_')}](solargraph:/document?query=#{URI.encode(this_path)})"
74
+ # return this_path if comments.empty?
75
+ "[#{this_path.gsub('_', '\\\\_')}](solargraph:/document?query=#{URI.escape(this_path)})"
76
76
  end
77
77
  end
78
78
  end
@@ -5,8 +5,20 @@ module Solargraph
5
5
  def process
6
6
  if node.children[0].nil?
7
7
  if [:private, :public, :protected].include?(node.children[1])
8
- # @todo Smelly instance variable access
9
- region.instance_variable_set(:@visibility, node.children[1])
8
+ if (node.children.length > 2)
9
+ node.children[2..-1].each do |child|
10
+ next unless child.is_a?(AST::Node) && (child.type == :sym || child.type == :str)
11
+ name = child.children[0].to_s
12
+ matches = pins.select{ |pin| [Pin::METHOD, Pin::ATTRIBUTE].include?(pin.kind) && pin.name == name && pin.namespace == region.namespace && pin.context.scope == region.scope }
13
+ matches.each do |pin|
14
+ # @todo Smelly instance variable access
15
+ pin.instance_variable_set(:@visibility, node.children[1])
16
+ end
17
+ end
18
+ else
19
+ # @todo Smelly instance variable access
20
+ region.instance_variable_set(:@visibility, node.children[1])
21
+ end
10
22
  elsif node.children[1] == :module_function
11
23
  process_module_function
12
24
  elsif [:attr_reader, :attr_writer, :attr_accessor].include?(node.children[1])
@@ -1,3 +1,3 @@
1
1
  module Solargraph
2
- VERSION = '0.30.1'
2
+ VERSION = '0.30.2'
3
3
  end
@@ -12,7 +12,7 @@ module Solargraph
12
12
 
13
13
  class << self
14
14
  def cache_dir
15
- @cache_dir ||= File.join(Dir.home, '.solargraph', 'cache')
15
+ @cache_dir ||= ENV["SOLARGRAPH_CACHE"] || File.join(Dir.home, '.solargraph', 'cache')
16
16
  end
17
17
 
18
18
  # Ensure installation of minimum documentation.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.1
4
+ version: 0.30.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-28 00:00:00.000000000 Z
11
+ date: 2018-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backport
@@ -227,6 +227,7 @@ files:
227
227
  - lib/solargraph/diagnostics/base.rb
228
228
  - lib/solargraph/diagnostics/require_not_found.rb
229
229
  - lib/solargraph/diagnostics/rubocop.rb
230
+ - lib/solargraph/diagnostics/rubocop_helpers.rb
230
231
  - lib/solargraph/diagnostics/severities.rb
231
232
  - lib/solargraph/diagnostics/type_not_defined.rb
232
233
  - lib/solargraph/diagnostics/update_errors.rb
@@ -236,6 +237,7 @@ files:
236
237
  - lib/solargraph/language_server/host.rb
237
238
  - lib/solargraph/language_server/host/cataloger.rb
238
239
  - lib/solargraph/language_server/host/diagnoser.rb
240
+ - lib/solargraph/language_server/host/sources.rb
239
241
  - lib/solargraph/language_server/message.rb
240
242
  - lib/solargraph/language_server/message/base.rb
241
243
  - lib/solargraph/language_server/message/cancel_request.rb