diff --git a/.index.yml b/.index.yml deleted file mode 100644 index 1c4e7b84c..000000000 --- a/.index.yml +++ /dev/null @@ -1,2 +0,0 @@ -excluded_patterns: - - "**/test/fixtures/**/*.rb" diff --git a/.ruby-lsp.yml b/.ruby-lsp.yml new file mode 100644 index 000000000..30f9f985e --- /dev/null +++ b/.ruby-lsp.yml @@ -0,0 +1,3 @@ +indexing: + excluded_patterns: + - "**/test/fixtures/**/*.rb" diff --git a/README.md b/README.md index 855e36ac0..82371c79a 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,10 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting documentation](SEMANTIC_HIGHLIGHTING.md). +## Configuration + +Configuration is read from an optional `.ruby-lsp.yml` file in the root of your project. + ### Configuring code indexing By default, the Ruby LSP indexes all Ruby files defined in the current project and all of its dependencies, including @@ -76,26 +80,30 @@ default gems, except for - Gems that only appear under the `:development` group - All Ruby files under `test/**/*.rb` -By creating a `.index.yml` file, these configurations can be overridden and tuned. Note that indexing dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by the configurations placed here. +Within the `.ruby-lsp.yml` file, these configurations can be overridden and tuned. Note that indexing dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by the configurations placed here. ```yaml # Exclude files based on a given pattern. Often used to exclude test files or fixtures -excluded_patterns: - - "**/spec/**/*.rb" +indexing: + excluded_patterns: + - "**/spec/**/*.rb" # Include files based on a given pattern. Can be used to index Ruby files that use different extensions -included_patterns: +indexing: + included_patterns: - "**/bin/*" # Exclude gems by name. If a gem is never referenced in the project's code and is only used as a tool, excluding it will # speed up indexing and reduce the amount of results in features like definition or completion -excluded_gems: - - rubocop - - pathname +indexing: + excluded_gems: + - rubocop + - pathname # Include gems by name. Normally used to include development gems that are excluded by default -included_gems: - - prism +indexing: + included_gems: + - prism ``` ### Addons diff --git a/lib/ruby_lsp/configuration.rb b/lib/ruby_lsp/configuration.rb new file mode 100644 index 000000000..f22442dbc --- /dev/null +++ b/lib/ruby_lsp/configuration.rb @@ -0,0 +1,32 @@ +# typed: strict +# frozen_string_literal: true + +module RubyLsp + class Configuration + extend T::Sig + + sig { params(workspace_uri: URI::Generic).void } + def initialize(workspace_uri) + @workspace_uri = workspace_uri + end + + sig { returns(T::Hash[String, T.untyped]) } + def indexing + # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink. + index_path = File.join(@workspace_uri.to_standardized_path, ".index.yml") + ruby_lsp_path = File.join(@workspace_uri.to_standardized_path, ".ruby-lsp.yml") + + if File.exist?(index_path) + unless ENV["RUBY_LSP_ENV"] == "test" + $stderr.puts("The .index.yml configuration file is deprecated. Please rename it to .ruby-lsp.yml and " \ + "update the structure as described in the README: https://github.com/Shopify/ruby-lsp#configuration") + end + YAML.parse_file(index_path).to_ruby + elsif File.exist?(ruby_lsp_path) + YAML.parse_file(ruby_lsp_path).to_ruby.fetch("indexing") + else + {} + end + end + end +end diff --git a/lib/ruby_lsp/internal.rb b/lib/ruby_lsp/internal.rb index c38149f9f..4184c9897 100644 --- a/lib/ruby_lsp/internal.rb +++ b/lib/ruby_lsp/internal.rb @@ -20,6 +20,7 @@ require "language_server-protocol" require "ruby-lsp" +require "ruby_lsp/configuration" require "ruby_lsp/base_server" require "ruby_indexer/ruby_indexer" require "core_ext/uri" diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index 45864fdcd..5bb5501fb 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -235,27 +235,21 @@ def run_initialized end RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable) - indexing_config = {} - # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink. - index_path = File.join(@store.workspace_uri.to_standardized_path, ".index.yml") - - if File.exist?(index_path) - begin - indexing_config = YAML.parse_file(index_path).to_ruby - rescue Psych::SyntaxError => e - message = "Syntax error while loading configuration: #{e.message}" - send_message( - Notification.new( - method: "window/showMessage", - params: Interface::ShowMessageParams.new( - type: Constant::MessageType::WARNING, - message: message, - ), + begin + indexing_config = Configuration.new(@store.workspace_uri).indexing + rescue => e + message = "#{e.class} while loading indexing configuration: #{e.message}" + send_message( + Notification.new( + method: "window/showMessage", + params: Interface::ShowMessageParams.new( + type: Constant::MessageType::WARNING, + message: message, ), - ) - end + ), + ) end perform_initial_indexing(indexing_config) diff --git a/test/configuration_test.rb b/test/configuration_test.rb new file mode 100644 index 000000000..11478f7b7 --- /dev/null +++ b/test/configuration_test.rb @@ -0,0 +1,60 @@ +# typed: true +# frozen_string_literal: true + +require "test_helper" + +module RubyLsp + class ConfigurationTest < Minitest::Test + def test_returns_empty_hash_when_no_configuration_files_exist + FileUtils.mv(".ruby-lsp.yml", ".ruby-lsp.yml.tmp") + workspace_uri = URI::Generic.build(scheme: "file", host: nil, path: "/path/to/workspace") + + result = RubyLsp::Configuration.new(workspace_uri).indexing + + assert_empty(result) + ensure + FileUtils.mv(".ruby-lsp.yml.tmp", ".ruby-lsp.yml") + end + + def test_supports_depecated_index_configuration_file + FileUtils.mv(".ruby-lsp.yml", ".ruby-lsp.yml.tmp") + File.write(".index.yml", <<~YAML) + excluded_patterns: + - "**/test/fixtures/**/*.rb" + YAML + workspace_uri = URI::Generic.build(scheme: "file", host: nil, path: Dir.pwd) + + result = RubyLsp::Configuration.new(workspace_uri).indexing + + assert_equal({ "excluded_patterns" => ["**/test/fixtures/**/*.rb"] }, result) + ensure + FileUtils.mv(".ruby-lsp.yml.tmp", ".ruby-lsp.yml") + FileUtils.rm_f(".index.yml") + end + + def test_supports_newer_configuration + workspace_uri = URI::Generic.build(scheme: "file", host: nil, path: Dir.pwd) + + result = RubyLsp::Configuration.new(workspace_uri).indexing + + assert_equal({ "excluded_patterns" => ["**/test/fixtures/**/*.rb"] }, result) + end + + def test_raises_if_indexing_key_is_missing + FileUtils.mv(".ruby-lsp.yml", ".ruby-lsp.yml.tmp") + File.write(".ruby-lsp.yml", <<~YAML) + excluded_patterns: + - "**/test/fixtures/**/*.rb" + YAML + workspace_uri = URI::Generic.build(scheme: "file", host: nil, path: Dir.pwd) + + error = assert_raises do + RubyLsp::Configuration.new(workspace_uri).indexing + end + assert_equal("key not found: \"indexing\"", error.message) + assert_instance_of(KeyError, error) + ensure + FileUtils.mv(".ruby-lsp.yml.tmp", ".ruby-lsp.yml") + end + end +end diff --git a/test/server_test.rb b/test/server_test.rb index 7e5fee08f..3df201bde 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -326,18 +326,19 @@ def test_initialize_features_with_enable_all_configuration end def test_handles_invalid_configuration - FileUtils.mv(".index.yml", ".index.yml.tmp") - File.write(".index.yml", "} invalid yaml") + FileUtils.mv(".ruby-lsp.yml", ".ruby-lsp.yml.tmp") + File.write(".ruby-lsp.yml", "}") @server.process_message({ method: "initialized" }) notification = @server.pop_response assert_equal("window/showMessage", notification.method) assert_match( - /Syntax error while loading configuration/, + /Psych::SyntaxError while loading indexing configuration/, T.cast(notification.params, RubyLsp::Interface::ShowMessageParams).message, + "Psych::SyntaxError while loading indexing configuration:", ) ensure - FileUtils.mv(".index.yml.tmp", ".index.yml") + FileUtils.mv(".ruby-lsp.yml.tmp", ".ruby-lsp.yml") end def test_detects_rubocop_if_direct_dependency