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.
- #diagnostics_ready? ⇒ Boolean
-
#initialize(path, uri = nil) ⇒ ProjectManager
constructor
A new instance of ProjectManager.
- #install_additional_gems(gem_names) ⇒ Object
- #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
-
#update_document_content(uri, text) ⇒ Object
returns diagnostic info (if possible).
- #updated_diagnostics_for_codefile(code_file) ⇒ Object
- #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
87 88 89 |
# File 'lib/ruby_language_server/project_manager.rb', line 87 def all_scopes RubyLanguageServer::ScopeData::Scope.all end |
#completion_at(uri, position) ⇒ Object
98 99 100 101 102 103 104 105 106 |
# 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) ⇒ Object
Returns the context of what is being typed in the given line
235 236 237 238 |
# File 'lib/ruby_language_server/project_manager.rb', line 235 def context_at_location(uri, position) code_file = code_file_for_uri(uri) code_file&.context_at_location(position) end |
#diagnostics_ready? ⇒ Boolean
56 57 58 |
# 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) ⇒ Object
60 61 62 63 64 65 66 67 |
# 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) ⇒ Object
244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/ruby_language_server/project_manager.rb', line 244 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
271 272 273 274 275 276 277 |
# File 'lib/ruby_language_server/project_manager.rb', line 271 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
81 82 83 84 85 |
# 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 ⇒ 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
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/ruby_language_server/project_manager.rb', line 182 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…
258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/ruby_language_server/project_manager.rb', line 258 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]
92 93 94 95 96 |
# 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) ⇒ Object
74 75 76 77 78 79 |
# File 'lib/ruby_language_server/project_manager.rb', line 74 def (uri) code_file = code_file_for_uri(uri) return {} if code_file.nil? code_file. end |
#text_for_uri(uri) ⇒ Object
69 70 71 72 |
# 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) ⇒ Object
returns diagnostic info (if possible)
215 216 217 218 219 220 221 222 223 |
# File 'lib/ruby_language_server/project_manager.rb', line 215 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) ⇒ Object
225 226 227 228 229 230 231 232 |
# File 'lib/ruby_language_server/project_manager.rb', line 225 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) ⇒ Object
240 241 242 |
# File 'lib/ruby_language_server/project_manager.rb', line 240 def word_at_location(uri, position) context_at_location(uri, position)&.last end |