Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/ruby_indexer/lib/ruby_indexer/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def initialize

# We start the included patterns with only the non excluded directories so that we can avoid paying the price of
# traversing large directories that don't include Ruby files like `node_modules`
@included_patterns = ["{#{top_level_directories.join(",")}}/**/*.rb", "*.rb"] #: Array[String]
@included_patterns = ["*.rb", *top_level_directories.map {|name| "#{name}/**/*.rb"}] #: Array[String]
@excluded_magic_comments = [
"frozen_string_literal:",
"typed:",
Expand All @@ -56,7 +56,7 @@ def initialize
end

#: -> Array[URI::Generic]
def indexable_uris
def indexable_uris(&logging)
excluded_gems = @excluded_gems - @included_gems
locked_gems = Bundler.locked_gems&.specs

Expand All @@ -68,7 +68,16 @@ def indexable_uris
uris = @included_patterns.flat_map do |pattern|
load_path_entry = nil #: String?

Dir.glob(File.join(@workspace_path, pattern), flags).map! do |path|
begin
glob_pattern = File.join(@workspace_path, pattern)
glob_results = Dir.glob(glob_pattern, flags)
rescue StandardError => error
message = "Indexation error on included_pattern: '#{pattern}': (#{error.class.name} - #{error.message})"
logging.call(error: message)
next
end

glob_results.map! do |path|
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the current
Expand All @@ -79,7 +88,7 @@ def indexable_uris

URI::Generic.from_path(path: path, load_path_entry: load_path_entry)
end
end
end.compact

# If the patterns are relative, we make it relative to the workspace path. If they are absolute, then we shouldn't
# concatenate anything
Expand Down Expand Up @@ -177,8 +186,11 @@ def apply_config(config)

@excluded_gems.concat(config["excluded_gems"]) if config["excluded_gems"]
@included_gems.concat(config["included_gems"]) if config["included_gems"]
@excluded_patterns.concat(config["excluded_patterns"]) if config["excluded_patterns"]
@included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
if config["excluded_patterns"]
@excluded_patterns.concat(config["excluded_patterns"])
@included_patterns -= config["excluded_patterns"]
end
@excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
end

Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,9 @@ def resolve(name, nesting, seen_names = [])
# indexing progress. That block is invoked with the current progress percentage and should return `true` to continue
# indexing or `false` to stop indexing.
#: (?uris: Array[URI::Generic]) ?{ (Integer progress) -> bool } -> void
def index_all(uris: @configuration.indexable_uris, &block)
def index_all(uris: [], &block)
uris = @configuration.indexable_uris(&block) if uris.empty?

# When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
# existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
# behavior and can take appropriate action.
Expand Down
29 changes: 27 additions & 2 deletions lib/ruby_lsp/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,27 @@ def inherited(child_class)

# Discovers and loads all add-ons. Returns a list of errors when trying to require add-ons
#: (GlobalState global_state, Thread::Queue outgoing_queue, ?include_project_addons: bool) -> Array[StandardError]
def load_addons(global_state, outgoing_queue, include_project_addons: true)
def load_addons(global_state, outgoing_queue, include_project_addons: true, &logging)
# Require all add-ons entry points, which should be placed under
# `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` or in the workspace under
# `your_project/ruby_lsp/project_name/addon.rb`
addon_files = Gem.find_files("ruby_lsp/**/addon.rb")

if include_project_addons
project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
workspace_path = global_state.workspace_path
spaces = top_level_directories.map do |space|
"#{workspace_path}/#{space}/**/ruby_lsp/**/addon.rb"
end
spaces << "#{workspace_path}/ruby_lsp/**/addon.rb"

project_addons = spaces.flat_map do |pattern|
Dir.glob(pattern)
rescue StandardError => error
message = "Addon discovery failed with pattern: '#{pattern}': (#{error.class.name} - #{error.message})"
logging.call(error: message)
next
end.compact

bundle_path = Bundler.bundle_path.to_s
gems_dir = Bundler.bundle_path.join("gems")

Expand Down Expand Up @@ -162,6 +175,18 @@ def depend_on_ruby_lsp!(*version_constraints)
"Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
end
end

private

#: -> Array[String]
def top_level_directories
Dir.glob("#{Dir.pwd}/*").filter_map do |path|
dir_name = File.basename(path)
next unless File.directory?(path)

dir_name
end
end
end

#: -> void
Expand Down
26 changes: 23 additions & 3 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,18 @@ def load_addons(include_project_addons: true)
# different versions of the same files. We cannot load add-ons if Bundler.setup failed
return if @setup_error

errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
errors = Addon.load_addons(
@global_state,
@outgoing_queue,
include_project_addons: include_project_addons
) do |log: nil, error: nil|
send_log_message(log) if log
if error
send_log_message(error, type: Constant::MessageType::ERROR)
send_message(Notification.window_show_message(error, type: Constant::MessageType::ERROR))
end
end

return if test_mode?

if errors.any?
Expand Down Expand Up @@ -1232,8 +1243,15 @@ def perform_initial_indexing
# stuck indexing files
Thread.new do
begin
@global_state.index.index_all do |percentage|
progress("indexing-progress", percentage)
@global_state.index.index_all do |percentage = nil, log: nil, error: nil|
send_log_message(log) if log
if error
send_log_message(error, type: Constant::MessageType::ERROR)
send_message(Notification.window_show_message(error, type: Constant::MessageType::ERROR))
end

progress("indexing-progress", percentage) unless percentage.nil?

true
rescue ClosedQueueError
# Since we run indexing on a separate thread, it's possible to kill the server before indexing is complete.
Expand All @@ -1244,6 +1262,8 @@ def perform_initial_indexing
rescue StandardError => error
message = "Error while indexing (see [troubleshooting steps]" \
"(https://shopify.github.io/ruby-lsp/troubleshooting#indexing)): #{error.message}"

send_log_message("#{message}\n\n#{error.backtrace.join("\n")}", type: Constant::MessageType::ERROR)
send_message(Notification.window_show_message(message, type: Constant::MessageType::ERROR))
end

Expand Down
Loading