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)


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

def initialize workspace = Solargraph::Workspace.new, name = nil
  @workspace = workspace
  @name = name
  api_map.catalog bench
  @synchronized = true
  @catalog_mutex = Mutex.new
end

Instance Attribute Details

#currentSource? (readonly)

Returns:



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

def current
  @current
end

#nameString? (readonly)

Returns:

  • (String, nil)


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

def name
  @name
end

#workspaceSolargraph::Workspace (readonly)



10
11
12
# File 'lib/solargraph/library.rb', line 10

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:



374
375
376
# File 'lib/solargraph/library.rb', line 374

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
# File 'lib/solargraph/library.rb', line 49

def attach source
  mutex.synchronize do
    @synchronized = (@current == source) if synchronized?
    @current = source
    catalog
  end
end

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

True if the specified file is currently attached.

Parameters:

  • filename (String)

Returns:

  • (Boolean)


61
62
63
# File 'lib/solargraph/library.rb', line 61

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

#catalogvoid

This method returns an undefined value.

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



348
349
350
351
352
353
354
355
356
# File 'lib/solargraph/library.rb', line 348

def catalog
  @catalog_mutex.synchronize do
    break if synchronized?
    logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
    api_map.catalog bench
    @synchronized = true
    logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" if logger.info?
  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)


142
143
144
145
146
147
148
# File 'lib/solargraph/library.rb', line 142

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:



157
158
159
160
161
# File 'lib/solargraph/library.rb', line 157

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

#contain?(filename) ⇒ Boolean

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

Parameters:

  • filename (String)

Returns:

  • (Boolean)


81
82
83
# File 'lib/solargraph/library.rb', line 81

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.



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/solargraph/library.rb', line 91

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(filename) ⇒ Boolean

Create a file source from a file on disk. The file is ignored if it is neither open in the library nor included in the workspace.

Parameters:

  • filename (String)

Returns:

  • (Boolean)

    True if the file was added to the workspace.



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/solargraph/library.rb', line 108

def create_from_disk filename
  result = false
  mutex.synchronize do
    next if File.directory?(filename) || !File.exist?(filename)
    next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
    @synchronized = false
    source = Solargraph::Source.load_string(File.read(filename), filename)
    workspace.merge(source)
    result = true
  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:



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/solargraph/library.rb', line 171

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
end

#delete(filename) ⇒ Boolean

Delete a file 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:

  • filename (String)

Returns:

  • (Boolean)

    True if the file was deleted



127
128
129
130
131
132
133
134
135
# File 'lib/solargraph/library.rb', line 127

def delete filename
  detach filename
  result = false
  mutex.synchronize do
    result = workspace.remove(filename)
    @synchronized = !result if synchronized?
  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



70
71
72
73
74
# File 'lib/solargraph/library.rb', line 70

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:



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/solargraph/library.rb', line 315

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)
  catalog
  result = []
  source = read(filename)
  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>)


262
263
264
265
# File 'lib/solargraph/library.rb', line 262

def document query
  catalog
  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:



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

def document_symbols filename
  api_map.document_symbols(filename)
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:



365
366
367
# File 'lib/solargraph/library.rb', line 365

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:



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

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:



244
245
246
# File 'lib/solargraph/library.rb', line 244

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

#locate_ref(location) ⇒ Object



248
249
250
# File 'lib/solargraph/library.rb', line 248

def locate_ref location
  api_map.require_reference_at location
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.



383
384
385
386
387
388
389
390
# File 'lib/solargraph/library.rb', line 383

def merge source
  result = false
  mutex.synchronize do
    result = workspace.merge(source)
    @synchronized = !result if synchronized?
  end
  result
end

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

Parameters:

  • path (String)

Returns:



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

def path_pins path
  catalog
  api_map.get_path_suggestions(path)
end

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

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

Parameters:

  • query (String)

Returns:



278
279
280
281
# File 'lib/solargraph/library.rb', line 278

def query_symbols query
  catalog
  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)


306
307
308
309
# File 'lib/solargraph/library.rb', line 306

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

#references_from(filename, line, column, strip: 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

Returns:



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/solargraph/library.rb', line 211

def references_from filename, line, column, strip: false
  cursor = api_map.cursor_at(filename, Position.new(line, column))
  clip = api_map.clip(cursor)
  pins = clip.define
  return [] if pins.empty?
  result = []
  pins.uniq.each do |pin|
    (workspace.sources + (@current ? [@current] : [])).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)
        # HACK: The additional location comparison is necessary because
        # Clip#define can return proxies for parameter pins
        referenced.any? { |r| r == pin || r.location == pin.location }
      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
  end
  result.uniq
end

#search(query) ⇒ Array<String>

Parameters:

  • query (String)

Returns:



269
270
271
272
# File 'lib/solargraph/library.rb', line 269

def search query
  catalog
  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:



199
200
201
202
203
# File 'lib/solargraph/library.rb', line 199

def signatures_at filename, line, column
  position = Position.new(line, column)
  cursor = Source::Cursor.new(read(filename), position)
  api_map.clip(cursor).signify
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