solargraph 0.30.1 → 0.30.2

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.
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