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
5 changes: 5 additions & 0 deletions lib/ruby_lsp/global_state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class GlobalState
#: String?
attr_reader :telemetry_machine_id

#: Hash[Symbol, untyped]?
attr_reader :indexing_options

#: -> void
def initialize
@workspace_uri = URI::Generic.from_path(path: Dir.pwd) #: URI::Generic
Expand All @@ -71,6 +74,7 @@ def initialize
@enabled_feature_flags = {} #: Hash[Symbol, bool]
@mutex = Mutex.new #: Mutex
@telemetry_machine_id = nil #: String?
@indexing_options = nil #: Hash[Symbol, untyped]?
@feature_configuration = {
inlayHint: RequestConfig.new({
enableAll: false,
Expand Down Expand Up @@ -113,6 +117,7 @@ def active_linters
#: (Hash[Symbol, untyped] options) -> Array[Notification]
def apply_options(options)
notifications = []
@indexing_options = options.dig(:initializationOptions, :indexing)
direct_dependencies = gather_direct_dependencies
all_dependencies = gather_direct_and_indirect_dependencies
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
Expand Down
44 changes: 44 additions & 0 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1464,11 +1464,55 @@ def benchmark(label, &block)
#: () -> void
def load_rubydex_config
@global_state.graph.load_config(Rubydex::Config.load(@global_state.workspace_path))
apply_editor_indexing_exclusions
rescue Rubydex::ConfigError => e
send_message(Notification.window_show_message(
"Error loading rubydex config: #{e.message}",
type: Constant::MessageType::ERROR,
))
end

#: -> void
def apply_editor_indexing_exclusions
options = @global_state.indexing_options
return unless options

excluded_patterns = options[:excludedPatterns]
excluded_patterns = [] unless excluded_patterns.is_a?(Array)

excluded_gems = options[:excludedGems]
excluded_gems = [] unless excluded_gems.is_a?(Array)
included_gems = options[:includedGems]
included_gems = [] unless included_gems.is_a?(Array)

patterns = excluded_patterns.grep(String).map { |pattern| normalize_indexing_pattern(pattern) }
patterns.concat(excluded_gem_patterns(excluded_gems.grep(String) - included_gems.grep(String)))

if patterns.any?
# Rubydex exposes this C API publicly, but its generated RBI marks it private.
@global_state.graph.send(:exclude_patterns, patterns)
end
end

#: (Array[String] gem_names) -> Array[String]
def excluded_gem_patterns(gem_names)
locked_gems = Bundler.locked_gems&.specs
return [] unless locked_gems

gem_names.filter_map do |gem_name|
locked_spec = locked_gems.find { |spec| spec.name == gem_name }
next unless locked_spec

spec = Gem::Specification.find_by_name(locked_spec.name, "= #{locked_spec.version}")
normalize_indexing_pattern(File.join(spec.full_gem_path, "**"))
rescue Gem::MissingSpecError
nil
end
end

#: (String pattern) -> String
def normalize_indexing_pattern(pattern)
File.expand_path(pattern.tr("\\", "/"), @global_state.workspace_path).tr("\\", "/")
end
end
end
33 changes: 33 additions & 0 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,39 @@ def test_server_info_includes_formatter
assert_equal("rubocop_internal", hash.dig("formatter"))
end

def test_applies_editor_indexing_exclusions_with_rubydex
Dir.mktmpdir do |workspace|
excluded_file = File.join(workspace, "excluded.rb")
included_file = File.join(workspace, "included.rb")
File.write(excluded_file, "class ExcludedFromIndex; end\n")
File.write(included_file, "class IncludedInIndex; end\n")
minitest_file = gem_path("minitest").join("lib", "minitest.rb").to_s

@server.process_message({
id: 1,
method: "initialize",
params: {
initializationOptions: {
indexing: {
excludedPatterns: ["excluded.rb"],
excludedGems: ["minitest"],
},
},
workspaceFolders: [{ uri: URI::Generic.from_path(path: workspace).to_s }],
},
})

@server.send(:load_rubydex_config)
graph = @server.global_state.graph
graph.index_all([excluded_file, included_file, minitest_file])
graph.resolve

assert_empty(graph.search("ExcludedFromIndex").to_a)
assert_equal(["IncludedInIndex"], graph.search("IncludedInIndex").map(&:name).to_a)
assert_empty(graph.search("Minitest").to_a)
end
end

def test_formatting_errors_push_window_notification
@server.global_state.expects(:formatter).raises(StandardError, "boom").once

Expand Down
Loading