Class: Solargraph::Library

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/solargraph/library.rb

Overview

A Library handles coordination between a Workspace and an ApiMap.

Constant Summary

Constants included from Logging

Solargraph::Logging::DEFAULT_LOG_LEVEL, Solargraph::Logging::LOG_LEVELS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Logging

logger

Constructor Details

#initialize(workspace = Solargraph::Workspace.new, name = nil) ⇒ Library

Returns a new instance of Library.

Parameters:

  • workspace (Solargraph::Workspace) (defaults to: Solargraph::Workspace.new)
  • name (String, nil) (defaults to: nil)


22
23
24
25
26
# File 'lib/solargraph/library.rb', line 22

def initialize workspace = Solargraph::Workspace.new, name = nil
  @workspace = workspace
  @name = name
  @synchronized = false
end

Instance Attribute Details

#currentSource? (readonly)

Returns:



18
19
20
# File 'lib/solargraph/library.rb', line 18

def current
  @current
end

#nameString? (readonly)

Returns:

  • (String, nil)


15
16
17
# File 'lib/solargraph/library.rb', line 15

def name
  @name
end

#workspaceSolargraph::Workspace (readonly)



12
13
14
# File 'lib/solargraph/library.rb', line 12

def workspace
  @workspace
end

Class Method Details

.load(directory = '', name = nil) ⇒ Solargraph::Library

Create a library from a directory.

Parameters:

  • directory (String) (defaults to: '')

    The path to be used for the workspace

  • name (String, nil) (defaults to: nil)

Returns:



454
455
456
# File 'lib/solargraph/library.rb', line 454

def self.load directory = '', name = nil
  Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
end

Instance Method Details

#attach(source) ⇒ void

This method returns an undefined value.

Attach a source to the library.

The attached source does not need to be a part of the workspace. The library will include it in the ApiMap while it’s attached. Only one source can be attached to the library at a time.

Parameters:



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/solargraph/library.rb', line 49

def attach source
  mutex.synchronize do
    if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
      source_map_hash.delete @current.filename
      source_map_external_require_hash.delete @current.filename
      @external_requires = nil
      @synchronized = false
    end
    @current = source
    maybe_map @current
    catalog_inlock
  end
end

#attached?(filename) ⇒ Boolean Also known as: open?

True if the specified file is currently attached.

Parameters:

  • filename (String)

Returns:

  • (Boolean)


67
68
69
# File 'lib/solargraph/library.rb', line 67

def attached? filename
  !@current.nil? && @current.filename == filename
end

#benchBench

Returns:



430
431
432
433
434
435
436
# File 'lib/solargraph/library.rb', line 430

def bench
  Bench.new(
    source_maps: source_map_hash.values,
    workspace: workspace,
    external_requires: external_requires
  )
end

#catalogvoid

This method returns an undefined value.

Update the ApiMap from the library’s workspace and open files.



411
412
413
414
415
# File 'lib/solargraph/library.rb', line 411

def catalog
  mutex.synchronize do
    catalog_inlock
  end
end

#close(filename) ⇒ void

This method returns an undefined value.

Close a file in the library. Closing a file will make it unavailable for checkout although it may still exist in the workspace.

Parameters:

  • filename (String)


149
150
151
152
153
154
155
# File 'lib/solargraph/library.rb', line 149

def close filename
  mutex.synchronize do
    @synchronized = false
    @current = nil if @current && @current.filename == filename
    catalog
  end
end

#completions_at(filename, line, column) ⇒ SourceMap::Completion?

TODO:

Take a Location instead of filename/line/column

Get completion suggestions at the specified file and location.

Parameters:

  • filename (String)

    The file to analyze

  • line (Integer)

    The zero-based line number

  • column (Integer)

    The zero-based column number

Returns:



164
165
166
167
168
169
170
# File 'lib/solargraph/library.rb', line 164

def completions_at filename, line, column
  position = Position.new(line, column)
  cursor = Source::Cursor.new(read(filename), position)
  api_map.clip(cursor).complete
rescue FileNotFoundError => e
  handle_file_not_found filename, e
end

#contain?(filename) ⇒ Boolean

True if the specified file is included in the workspace (but not necessarily open).

Parameters:

  • filename (String)

Returns:

  • (Boolean)


87
88
89
# File 'lib/solargraph/library.rb', line 87

def contain? filename
  workspace.has_file?(filename)
end

#create(filename, text) ⇒ Boolean

Create a source to be added to the workspace. The file is ignored if it is neither open in the library nor included in the workspace.

Parameters:

  • filename (String)
  • text (String)

    The contents of the file

Returns:

  • (Boolean)

    True if the file was added to the workspace.



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/solargraph/library.rb', line 97

def create filename, text
  result = false
  mutex.synchronize do
    next unless contain?(filename) || open?(filename)
    @synchronized = false
    source = Solargraph::Source.load_string(text, filename)
    workspace.merge(source)
    result = true
  end
  result
end

#create_from_disk(*filenames) ⇒ Boolean

Create file sources from files on disk. A file is ignored if it is neither open in the library nor included in the workspace.

Parameters:

  • filenames (Array<String>)

Returns:

  • (Boolean)

    True if at least one file was added to the workspace.



114
115
116
117
118
119
120
121
122
123
124
# File 'lib/solargraph/library.rb', line 114

def create_from_disk *filenames
  result = false
  mutex.synchronize do
    sources = filenames
      .reject { |filename| File.directory?(filename) || !File.exist?(filename) }
      .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) }
    result = workspace.merge(*sources)
    sources.each { |source| maybe_map source }
  end
  result
end

#definitions_at(filename, line, column) ⇒ Array<Solargraph::Pin::Base>?

TODO:

Take filename/position instead of filename/line/column

Get definition suggestions for the expression at the specified file and location.

Parameters:

  • filename (String)

    The file to analyze

  • line (Integer)

    The zero-based line number

  • column (Integer)

    The zero-based column number

Returns:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/solargraph/library.rb', line 180

def definitions_at filename, line, column
  position = Position.new(line, column)
  cursor = Source::Cursor.new(read(filename), position)
  if cursor.comment?
    source = read(filename)
    offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
    lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i)
    rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
    if lft && rgt
      tag = (lft[1] + rgt[1]).sub(/:+$/, '')
      clip = api_map.clip(cursor)
      clip.translate tag
    else
      []
    end
  else
    api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
  end
rescue FileNotFoundError => e
  handle_file_not_found(filename, e)
end

#delete(*filenames) ⇒ Boolean

Delete files from the library. Deleting a file will make it unavailable for checkout and optionally remove it from the workspace unless the workspace configuration determines that it should still exist.

Parameters:

  • filenames (Array<String>)

Returns:

  • (Boolean)

    True if any file was deleted



132
133
134
135
136
137
138
139
140
141
142
# File 'lib/solargraph/library.rb', line 132

def delete *filenames
  result = false
  filenames.each do |filename|
    detach filename
    mutex.synchronize do
      result ||= workspace.remove(filename)
      @synchronized = !result if synchronized?
    end
  end
  result
end

#detach(filename) ⇒ Boolean

Detach the specified file if it is currently attached to the library.

Parameters:

  • filename (String)

Returns:

  • (Boolean)

    True if the specified file was detached



76
77
78
79
80
# File 'lib/solargraph/library.rb', line 76

def detach filename
  return false if @current.nil? || @current.filename != filename
  attach nil
  true
end

#diagnose(filename) ⇒ Array<Hash>

Get diagnostics about a file.

Parameters:

  • filename (String)

Returns:

  • (Array<Hash>)


378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/solargraph/library.rb', line 378

def diagnose filename
  # @todo Only open files get diagnosed. Determine whether anything or
  #   everything in the workspace should get diagnosed, or if there should
  #   be an option to do so.
  #
  return [] unless open?(filename)
  result = []
  source = read(filename)
  catalog
  repargs = {}
  workspace.config.reporters.each do |line|
    if line == 'all!'
      Diagnostics.reporters.each do |reporter|
        repargs[reporter] ||= []
      end
    else
      args = line.split(':').map(&:strip)
      name = args.shift
      reporter = Diagnostics.reporter(name)
      raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
      repargs[reporter] ||= []
      repargs[reporter].concat args
    end
  end
  repargs.each_pair do |reporter, args|
    result.concat reporter.new(*args.uniq).diagnose(source, api_map)
  end
  result
end

#document(query) ⇒ Enumerable<YARD::CodeObjects::Base>

Parameters:

  • query (String)

Returns:

  • (Enumerable<YARD::CodeObjects::Base>)


324
325
326
# File 'lib/solargraph/library.rb', line 324

def document query
  api_map.document query
end

#document_symbols(filename) ⇒ Array<Solargraph::Pin::Base>

Get an array of document symbols.

Document symbols are composed of namespace, method, and constant pins. The results of this query are appropriate for building the response to a textDocument/documentSymbol message in the language server protocol.

Parameters:

  • filename (String)

Returns:



350
351
352
# File 'lib/solargraph/library.rb', line 350

def document_symbols filename
  api_map.document_symbols(filename)
end

#external_requiresSet<String>

Returns:

  • (Set<String>)


515
516
517
# File 'lib/solargraph/library.rb', line 515

def external_requires
  @external_requires ||= source_map_external_require_hash.values.flatten.to_set
end

#folding_ranges(filename) ⇒ Array<Range>

Deprecated.

The library should not need to handle folding ranges. The source itself has all the information it needs.

Get an array of foldable ranges for the specified file.

Parameters:

  • filename (String)

Returns:



445
446
447
# File 'lib/solargraph/library.rb', line 445

def folding_ranges filename
  read(filename).folding_ranges
end

#get_path_pins(path) ⇒ Enumerable<Solargraph::Pin::Base>

Get an array of pins that match a path.

Parameters:

  • path (String)

Returns:



318
319
320
# File 'lib/solargraph/library.rb', line 318

def get_path_pins path
  api_map.get_path_suggestions(path)
end

#inspectObject



28
29
30
31
# File 'lib/solargraph/library.rb', line 28

def inspect
  # Let's not deal with insane data dumps in spec failures
  to_s
end

#locate_pins(location) ⇒ Array<Solargraph::Pin::Base>

Get the pins at the specified location or nil if the pin does not exist.

Parameters:

Returns:



285
286
287
# File 'lib/solargraph/library.rb', line 285

def locate_pins location
  api_map.locate_pins(location).map { |pin| pin.realize(api_map) }
end

#locate_ref(location) ⇒ Location?

Match a require reference to a file.

Parameters:

Returns:



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/solargraph/library.rb', line 293

def locate_ref location
  map = source_map_hash[location.filename]
  return if map.nil?
  pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
  return nil if pin.nil?
  # @param full [String]
  return_if_match = proc do |full|
    if source_map_hash.key?(full)
      return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
    end
  end
  workspace.require_paths.each do |path|
    full = File.join path, pin.name
    return_if_match.(full)
    return_if_match.(full << ".rb")
  end
  nil
rescue FileNotFoundError
  nil
end

#map!self

Returns:

  • (self)


501
502
503
504
505
506
507
# File 'lib/solargraph/library.rb', line 501

def map!
  workspace.sources.each do |src|
    source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
    find_external_requires(source_map_hash[src.filename])
  end
  self
end

#mapped?Boolean

Returns:

  • (Boolean)


479
480
481
# File 'lib/solargraph/library.rb', line 479

def mapped?
  (workspace.filenames - source_map_hash.keys).empty?
end

#merge(source) ⇒ Boolean

Try to merge a source into the library’s workspace. If the workspace is not configured to include the source, it gets ignored.

Parameters:

Returns:

  • (Boolean)

    True if the source was merged into the workspace.



463
464
465
466
467
468
469
470
471
472
# File 'lib/solargraph/library.rb', line 463

def merge source
  Logging.logger.debug "Merging source: #{source.filename}"
  result = false
  mutex.synchronize do
    result = workspace.merge(source)
    maybe_map source
  end
  # catalog
  result
end

#next_mapSourceMap, Boolean

Returns:



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/solargraph/library.rb', line 484

def next_map
  return false if mapped?
  mutex.synchronize do
    @synchronized = false
    src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
    if src
      Logging.logger.debug "Mapping #{src.filename}"
      source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
      find_external_requires(source_map_hash[src.filename])
      source_map_hash[src.filename]
    else
      false
    end
  end
end

#path_pins(path) ⇒ Enumerable<Solargraph::Pin::Base>

Parameters:

  • path (String)

Returns:



356
357
358
# File 'lib/solargraph/library.rb', line 356

def path_pins path
  api_map.get_path_suggestions(path)
end

#pinsArray<Solargraph::Pin::Base>

Returns:



510
511
512
# File 'lib/solargraph/library.rb', line 510

def pins
  @pins ||= []
end

#query_symbols(query) ⇒ Array<Pin::Base>

Get an array of all symbols in the workspace that match the query.

Parameters:

  • query (String)

Returns:



338
339
340
# File 'lib/solargraph/library.rb', line 338

def query_symbols query
  api_map.query_symbols query
end

#read_text(filename) ⇒ String

Get the current text of a file in the library.

Parameters:

  • filename (String)

Returns:

  • (String)


369
370
371
372
# File 'lib/solargraph/library.rb', line 369

def read_text filename
  source = read(filename)
  source.code
end

#references_from(filename, line, column, strip: false, only: false) ⇒ Array<Solargraph::Range>

TODO:

Take a Location instead of filename/line/column

Parameters:

  • filename (String)
  • line (Integer)
  • column (Integer)
  • strip (Boolean) (defaults to: false)

    Strip special characters from variable names

  • only (Boolean) (defaults to: false)

    Search for references in the current file only

Returns:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/solargraph/library.rb', line 239

def references_from filename, line, column, strip: false, only: false
  cursor = api_map.cursor_at(filename, Position.new(line, column))
  clip = api_map.clip(cursor)
  pin = clip.define.first
  return [] unless pin
  result = []
  files = if only
    [api_map.source_map(filename)]
  else
    (workspace.sources + (@current ? [@current] : []))
  end
  files.uniq(&:filename).each do |source|
    found = source.references(pin.name)
    found.select! do |loc|
      referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character).first
      referenced&.path == pin.path
    end
    if pin.path == 'Class#new'
      caller = cursor.chain.base.infer(api_map, clip.send(:block), clip.locals).first
      if caller.defined?
        found.select! do |loc|
          clip = api_map.clip_at(loc.filename, loc.range.start)
          other = clip.send(:cursor).chain.base.infer(api_map, clip.send(:block), clip.locals).first
          caller == other
        end
      else
        found.clear
      end
    end
    # HACK: for language clients that exclude special characters from the start of variable names
    if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
      found.map! do |loc|
        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))
      end
    end
    result.concat(found.sort do |a, b|
      a.range.start.line <=> b.range.start.line
    end)
  end
  result.uniq
end

#search(query) ⇒ Array<String>

Parameters:

  • query (String)

Returns:

  • (Array<String>)


330
331
332
# File 'lib/solargraph/library.rb', line 330

def search query
  api_map.search query
end

#signatures_at(filename, line, column) ⇒ Array<Solargraph::Pin::Base>

TODO:

Take filename/position instead of filename/line/column

Get signature suggestions for the method at the specified file and location.

Parameters:

  • filename (String)

    The file to analyze

  • line (Integer)

    The zero-based line number

  • column (Integer)

    The zero-based column number

Returns:



226
227
228
229
230
# File 'lib/solargraph/library.rb', line 226

def signatures_at filename, line, column
  position = Position.new(line, column)
  cursor = Source::Cursor.new(read(filename), position)
  api_map.clip(cursor).signify
end

#source_map_hashHash{String => SourceMap}

Returns:



475
476
477
# File 'lib/solargraph/library.rb', line 475

def source_map_hash
  @source_map_hash ||= {}
end

#source_mapsArray<SourceMap>

Returns:



361
362
363
# File 'lib/solargraph/library.rb', line 361

def source_maps
  source_map_hash.values
end

#synchronized?Boolean

True if the ApiMap is up to date with the library’s workspace and open files.

Returns:

  • (Boolean)


37
38
39
# File 'lib/solargraph/library.rb', line 37

def synchronized?
  @synchronized
end

#type_definitions_at(filename, line, column) ⇒ Array<Solargraph::Pin::Base>?

TODO:

Take filename/position instead of filename/line/column

Get type definition suggestions for the expression at the specified file and location.

Parameters:

  • filename (String)

    The file to analyze

  • line (Integer)

    The zero-based line number

  • column (Integer)

    The zero-based column number

Returns:



210
211
212
213
214
215
216
# File 'lib/solargraph/library.rb', line 210

def type_definitions_at filename, line, column
  position = Position.new(line, column)
  cursor = Source::Cursor.new(read(filename), position)
  api_map.clip(cursor).types
rescue FileNotFoundError => e
  handle_file_not_found filename, e
end