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:



414
415
416
# File 'lib/solargraph/library.rb', line 414

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

#benchObject



390
391
392
393
394
395
396
# File 'lib/solargraph/library.rb', line 390

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.



376
377
378
379
380
# File 'lib/solargraph/library.rb', line 376

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) || workspace.would_merge?(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>)


343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/solargraph/library.rb', line 343

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) ⇒ Array<YARD::CodeObjects::Base>

Parameters:

  • query (String)

Returns:

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


290
291
292
# File 'lib/solargraph/library.rb', line 290

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:



316
317
318
# File 'lib/solargraph/library.rb', line 316

def document_symbols filename
  api_map.document_symbols(filename)
end

#external_requiresObject



470
471
472
# File 'lib/solargraph/library.rb', line 470

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:



405
406
407
# File 'lib/solargraph/library.rb', line 405

def folding_ranges filename
  read(filename).folding_ranges
end

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

Get an array of pins that match a path.

Parameters:

  • path (String)

Returns:



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

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:



257
258
259
# File 'lib/solargraph/library.rb', line 257

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:



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/solargraph/library.rb', line 265

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?
  workspace.require_paths.each do |path|
    full = Pathname.new(path).join("#{pin.name}.rb").to_s
    next unless source_map_hash.key?(full)
    return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
  end
  nil
rescue FileNotFoundError
  nil
end

#map!Object



458
459
460
461
462
463
464
# File 'lib/solargraph/library.rb', line 458

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)


438
439
440
# File 'lib/solargraph/library.rb', line 438

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.



423
424
425
426
427
428
429
430
431
432
# File 'lib/solargraph/library.rb', line 423

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_mapObject



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/solargraph/library.rb', line 442

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) ⇒ Array<Solargraph::Pin::Base>

Parameters:

  • path (String)

Returns:



322
323
324
# File 'lib/solargraph/library.rb', line 322

def path_pins path
  api_map.get_path_suggestions(path)
end

#pinsObject



466
467
468
# File 'lib/solargraph/library.rb', line 466

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:



304
305
306
# File 'lib/solargraph/library.rb', line 304

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)


334
335
336
337
# File 'lib/solargraph/library.rb', line 334

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:



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/solargraph/library.rb', line 223

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 && referenced.path == pin.path
    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>)


296
297
298
# File 'lib/solargraph/library.rb', line 296

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:



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

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_hashObject



434
435
436
# File 'lib/solargraph/library.rb', line 434

def source_map_hash
  @source_map_hash ||= {}
end

#source_mapsObject



326
327
328
# File 'lib/solargraph/library.rb', line 326

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