Class: RubyLanguageServer::ProjectManager
- Inherits:
-
Object
- Object
- RubyLanguageServer::ProjectManager
- Defined in:
- lib/ruby_language_server/project_manager.rb
Constant Summary collapse
- ROOT_PATH_MUTEX =
GoodCop wants to know where to find its config. So here we are.
Mutex.new
Instance Attribute Summary collapse
-
#uri_code_file_hash ⇒ Object
readonly
Returns the value of attribute uri_code_file_hash.
Class Method Summary collapse
Instance Method Summary collapse
- #all_scopes ⇒ Object
- #completion_at(uri, position) ⇒ Object
-
#context_at_location(uri, position) ⇒ Object
Returns the context of what is being typed in the given line.
-
#initialize(path, uri = nil) ⇒ ProjectManager
constructor
A new instance of ProjectManager.
- #possible_definitions(uri, position) ⇒ Object
- #project_definitions_for(name) ⇒ Object
- #root_scope_for(uri) ⇒ Object
-
#scan_all_project_files ⇒ Object
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 }.
-
#scope_definitions_for(name, scope, uri) ⇒ Object
Return variables found in the current scope.
-
#scopes_at(uri, position) ⇒ Object
Return the list of scopes [deepest, parent, …, Object].
- #tags_for_uri(uri) ⇒ Object
-
#text_for_uri(uri) ⇒ Object
def diagnostics_ready? @additional_gem_mutex.synchronize { @additional_gems_installed } end.
-
#update_document_content(uri, text) ⇒ Object
returns diagnostic info (if possible).
- #word_at_location(uri, position) ⇒ Object
Constructor Details
#initialize(path, uri = nil) ⇒ ProjectManager
Returns a new instance of ProjectManager.
45 46 47 48 49 50 51 52 53 54 |
# 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 |
Instance Attribute Details
#uri_code_file_hash ⇒ Object (readonly)
Returns the value of attribute uri_code_file_hash.
9 10 11 |
# File 'lib/ruby_language_server/project_manager.rb', line 9 def uri_code_file_hash @uri_code_file_hash end |
Class Method Details
.root_path ⇒ Object
22 23 24 25 26 27 28 29 |
# 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.fetch('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) ⇒ Object
16 17 18 19 20 |
# 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 ⇒ Object
40 41 42 |
# File 'lib/ruby_language_server/project_manager.rb', line 40 def root_uri @_root_uri || "file://#{root_path}" end |
.root_uri=(uri) ⇒ Object
31 32 33 34 35 36 37 38 |
# 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 |
Instance Method Details
#all_scopes ⇒ Object
78 79 80 |
# File 'lib/ruby_language_server/project_manager.rb', line 78 def all_scopes RubyLanguageServer::ScopeData::Scope.all end |
#completion_at(uri, position) ⇒ Object
89 90 91 92 93 94 95 96 97 |
# File 'lib/ruby_language_server/project_manager.rb', line 89 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) ⇒ Object
Returns the context of what is being typed in the given line
226 227 228 229 |
# File 'lib/ruby_language_server/project_manager.rb', line 226 def context_at_location(uri, position) code_file = code_file_for_uri(uri) code_file&.context_at_location(position) end |
#possible_definitions(uri, position) ⇒ Object
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/ruby_language_server/project_manager.rb', line 235 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) ⇒ Object
262 263 264 265 266 267 268 |
# File 'lib/ruby_language_server/project_manager.rb', line 262 def project_definitions_for(name) scopes = RubyLanguageServer::ScopeData::Scope.where(name:) variables = RubyLanguageServer::ScopeData::Variable.constant_variables.where(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) ⇒ Object
72 73 74 75 76 |
# File 'lib/ruby_language_server/project_manager.rb', line 72 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 ⇒ Object
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
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/ruby_language_server/project_manager.rb', line 173 def scan_all_project_files project_ruby_files = Dir.glob("#{self.class.root_path}**/*.rb") RubyLanguageServer.logger.debug('Threading up!') root_uri = @root_uri root_uri += '/' unless root_uri.end_with? '/' # Using fork because this is run in a docker container that has fork. # If you want to run this on some platform without fork, fork the code and PR it :-) fork_id = fork do project_ruby_files.each do |container_path| # Let's not preload spec/test files or vendor - yet.. next if container_path.match?(/(spec\.rb|test\.rb|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("Threading #{host_uri}") begin ActiveRecord::Base.connection_pool.with_connection do |_connection| update_document_content(host_uri, text) code_file_for_uri(host_uri).refresh_scopes_if_needed(shallow: true) end rescue StandardError => e RubyLanguageServer.logger.warn("Error updating: #{e}\n#{e.backtrace * "\n"}") sleep 5 retry end end end RubyLanguageServer.logger.debug("Forked process id to look at other files: #{fork_id}") Process.detach(fork_id) end |
#scope_definitions_for(name, scope, uri) ⇒ Object
Return variables found in the current scope. After all, those are the important ones. Should probably be private…
249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/ruby_language_server/project_manager.rb', line 249 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) ⇒ Object
Return the list of scopes [deepest, parent, …, Object]
83 84 85 86 87 |
# File 'lib/ruby_language_server/project_manager.rb', line 83 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) ⇒ Object
65 66 67 68 69 70 |
# File 'lib/ruby_language_server/project_manager.rb', line 65 def (uri) code_file = code_file_for_uri(uri) return {} if code_file.nil? code_file. end |
#text_for_uri(uri) ⇒ Object
def diagnostics_ready?
@additional_gem_mutex.synchronize { @additional_gems_installed }
end
60 61 62 63 |
# File 'lib/ruby_language_server/project_manager.rb', line 60 def text_for_uri(uri) code_file = code_file_for_uri(uri) code_file&.text || '' end |
#update_document_content(uri, text) ⇒ Object
returns diagnostic info (if possible)
206 207 208 209 210 211 212 213 214 |
# File 'lib/ruby_language_server/project_manager.rb', line 206 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 |
#word_at_location(uri, position) ⇒ Object
231 232 233 |
# File 'lib/ruby_language_server/project_manager.rb', line 231 def word_at_location(uri, position) context_at_location(uri, position)&.last end |