class RubyLanguageServer::ProjectManager

Constants

ROOT_PATH_MUTEX

GoodCop wants to know where to find its config. So here we are.

Attributes

uri_code_file_hash[R]

Public Class Methods

new(path, uri = nil) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 45
def initialize(path, uri = nil)
  # Should probably lock for read, but I'm feeling crazy!
  self.class.root_path = path if self.class.root_path.nil?
  self.class.root_uri = uri if uri

  @root_uri = "file://#{path}"
  # This is {uri: code_file} where content stuff is like
  @additional_gems_installed = false
  @additional_gem_mutex = Mutex.new
end
root_path() click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 22
def root_path
  # I'm torn about this.  Should this be set in the Server?  Or is this right.
  # Rather than worry too much, I'll just do this here and change it later if it feels wrong.
  path = ENV['RUBY_LANGUAGE_SERVER_PROJECT_ROOT'] || @_root_path
  return path if path.nil?

  path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
end
root_path=(path) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 16
def root_path=(path)
  ROOT_PATH_MUTEX.synchronize do
    @_root_path = path
  end
end
root_uri() click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 40
def root_uri
  @_root_uri || "file://#{root_path}"
end
root_uri=(uri) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 31
def root_uri=(uri)
  ROOT_PATH_MUTEX.synchronize do
    if uri
      uri = "#{uri}/" unless uri.end_with?('/')
      @_root_uri = uri
    end
  end
end

Public Instance Methods

all_scopes() click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 87
def all_scopes
  RubyLanguageServer::ScopeData::Scope.all
end
completion_at(uri, position) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 98
def completion_at(uri, position)
  context = context_at_location(uri, position)
  return {} if context.blank?

  RubyLanguageServer.logger.debug("scopes_at(uri, position) #{scopes_at(uri, position).map(&:name)}")
  position_scopes = scopes_at(uri, position) || RubyLanguageServer::ScopeData::Scope.where(id: root_scope_for(uri).id)
  context_scope = position_scopes.first
  RubyLanguageServer::Completion.completion(context, context_scope, position_scopes)
end
context_at_location(uri, position) click to toggle source

Returns the context of what is being typed in the given line

# File lib/ruby_language_server/project_manager.rb, line 227
def context_at_location(uri, position)
  code_file = code_file_for_uri(uri)
  code_file&.context_at_location(position)
end
diagnostics_ready?() click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 56
def diagnostics_ready?
  @additional_gem_mutex.synchronize { @additional_gems_installed }
end
install_additional_gems(gem_names) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 60
def install_additional_gems(gem_names)
  Thread.new do
    RubyLanguageServer::GemInstaller.install_gems(gem_names)
    @additional_gem_mutex.synchronize { @additional_gems_installed = true }
  rescue StandardError => e
    RubyLanguageServer.logger.error("Issue installing rubocop gems: #{e} #{e.backtrace}")
  end
end
possible_definitions(uri, position) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 236
def possible_definitions(uri, position)
  name = word_at_location(uri, position)
  return {} if name.blank?

  name = 'initialize' if name == 'new'
  scope = scopes_at(uri, position).first
  results = scope_definitions_for(name, scope, uri)
  return results unless results.empty?

  project_definitions_for(name)
end
project_definitions_for(name) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 263
def project_definitions_for(name)
  scopes = RubyLanguageServer::ScopeData::Scope.where(name: name)
  variables = RubyLanguageServer::ScopeData::Variable.constant_variables.where(name: name)
  (scopes + variables).reject { |scope| scope.code_file.nil? }.map do |scope|
    Location.hash(scope.code_file.uri, scope.top_line, 1)
  end
end
root_scope_for(uri) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 81
def root_scope_for(uri)
  code_file = code_file_for_uri(uri)
  RubyLanguageServer.logger.error('code_file.nil?!!!!!!!!!!!!!!') if code_file.nil?
  code_file&.root_scope
end
scan_all_project_files(mutex) click to toggle source

interface CompletionItem {

/**
 * The label of this completion item. By default
 * also the text that is inserted when selecting
 * this completion.
 */
label: string;
/**
 * The kind of this completion item. Based of the kind
 * an icon is chosen by the editor.
 */
kind?: number;
/**
 * A human-readable string with additional information
 * about this item, like type or symbol information.
 */
detail?: string;
/**
 * A human-readable string that represents a doc-comment.
 */
documentation?: string;
/**
 * A string that shoud be used when comparing this item
 * with other items. When `falsy` the label is used.
 */
sortText?: string;
/**
 * A string that should be used when filtering a set of
 * completion items. When `falsy` the label is used.
 */
filterText?: string;
/**
 * A string that should be inserted a document when selecting
 * this completion. When `falsy` the label is used.
 */
insertText?: string;
/**
 * The format of the insert text. The format applies to both the `insertText` property
 * and the `newText` property of a provided `textEdit`.
 */
insertTextFormat?: InsertTextFormat;
/**
 * An edit which is applied to a document when selecting this completion. When an edit is provided the value of
 * `insertText` is ignored.
 *
 * *Note:* The range of the edit must be a single line range and it must contain the position at which completion
 * has been requested.
 */
textEdit?: TextEdit;
/**
 * An optional array of additional text edits that are applied when
 * selecting this completion. Edits must not overlap with the main edit
 * nor with themselves.
 */
additionalTextEdits?: TextEdit[];
/**
 * An optional set of characters that when pressed while this completion is active will accept it first and
 * then type that character. *Note* that all commit characters should have `length=1` and that superfluous
 * characters will be ignored.
 */
commitCharacters?: string[];
/**
 * An optional command that is executed *after* inserting this completion. *Note* that
 * additional modifications to the current document should be described with the
 * additionalTextEdits-property.
 */
command?: Command;
/**
 * An data entry field that is preserved on a completion item between
 * a completion and a completion resolve request.
 */
data?: any

}

# File lib/ruby_language_server/project_manager.rb, line 182
def scan_all_project_files(mutex)
  project_ruby_files = Dir.glob("#{self.class.root_path}**/*.rb")
  Thread.new do
    RubyLanguageServer.logger.debug('Threading up!')
    root_uri = @root_uri
    root_uri += '/' unless root_uri.end_with? '/'
    project_ruby_files.each do |container_path|
      # Let's not preload spec/test or vendor - yet..
      next if container_path.match?(/^(.?spec|test|vendor)/)

      text = File.read(container_path)
      relative_path = container_path.delete_prefix(self.class.root_path)
      host_uri = root_uri + relative_path
      RubyLanguageServer.logger.debug "Locking scan for #{container_path}"
      mutex.synchronize do
        RubyLanguageServer.logger.debug("Threading #{host_uri}")
        update_document_content(host_uri, text)
        code_file_for_uri(host_uri).refresh_scopes_if_needed
      end
      RubyLanguageServer.logger.debug "Unlocking scan for #{container_path}"
    end
  end
end
scope_definitions_for(name, scope, uri) click to toggle source

Return variables found in the current scope. After all, those are the important ones. Should probably be private…

# File lib/ruby_language_server/project_manager.rb, line 250
def scope_definitions_for(name, scope, uri)
  check_scope = scope
  return_array = []
  while check_scope
    scope.variables.each do |variable|
      return_array << Location.hash(uri, variable.line, 1) if variable.name == name
    end
    check_scope = check_scope.parent
  end
  RubyLanguageServer.logger.debug("==============>> scope_definitions_for(#{name}, #{scope.to_json}, #{uri}: #{return_array.uniq})")
  return_array.uniq
end
scopes_at(uri, position) click to toggle source

Return the list of scopes [deepest, parent, …, Object]

# File lib/ruby_language_server/project_manager.rb, line 92
def scopes_at(uri, position)
  code_file = code_file_for_uri(uri)
  code_file.refresh_scopes_if_needed
  code_file.scopes.for_line(position.line).where.not(path: nil).by_path_length
end
tags_for_uri(uri) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 74
def tags_for_uri(uri)
  code_file = code_file_for_uri(uri)
  return {} if code_file.nil?

  code_file.tags
end
text_for_uri(uri) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 69
def text_for_uri(uri)
  code_file = code_file_for_uri(uri)
  code_file&.text || ''
end
update_document_content(uri, text) click to toggle source

returns diagnostic info (if possible)

# File lib/ruby_language_server/project_manager.rb, line 207
def update_document_content(uri, text)
  RubyLanguageServer.logger.debug("update_document_content: #{uri}")
  # RubyLanguageServer.logger.error("@root_path: #{@root_path}")
  code_file = code_file_for_uri(uri)
  return code_file.diagnostics if code_file.text == text

  code_file.update_text(text)
  diagnostics_ready? ? updated_diagnostics_for_codefile(code_file) : []
end
updated_diagnostics_for_codefile(code_file) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 217
def updated_diagnostics_for_codefile(code_file)
  # Maybe we should be sharing this GoodCop across instances
  RubyLanguageServer.logger.debug("updated_diagnostics_for_codefile: #{code_file.uri}")
  project_relative_filename = filename_relative_to_project(code_file.uri)
  code_file.diagnostics = GoodCop.instance.diagnostics(code_file.text, project_relative_filename)
  RubyLanguageServer.logger.debug("code_file.diagnostics: #{code_file.diagnostics}")
  code_file.diagnostics
end
word_at_location(uri, position) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 232
def word_at_location(uri, position)
  context_at_location(uri, position)&.last
end

Private Instance Methods

code_file_for_uri(uri) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 273
def code_file_for_uri(uri)
  CodeFile.find_by_uri(uri) || CodeFile.build(uri, nil)
end
filename_relative_to_project(filename) click to toggle source
# File lib/ruby_language_server/project_manager.rb, line 277
def filename_relative_to_project(filename)
  filename.gsub(self.class.root_uri, self.class.root_path)
end