]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/config.rs
Make group imports configurable
[rust.git] / crates / rust-analyzer / src / config.rs
index 27b92a5a9bf47478e06488758518761e0a7712f5..cac48e9117099074a33638e3137cdc66b32e3d2e 100644 (file)
@@ -7,23 +7,26 @@
 //! configure the server itself, feature flags are passed into analysis, and
 //! tweak things like automatic insertion of `()` in completions.
 
-use std::{convert::TryFrom, ffi::OsString, iter, path::PathBuf};
+use std::{ffi::OsString, iter, path::PathBuf};
 
 use flycheck::FlycheckConfig;
 use hir::PrefixKind;
-use ide::{
-    AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
-    InsertUseConfig,
+use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
+use ide_db::helpers::{
+    insert_use::{InsertUseConfig, MergeBehavior},
+    SnippetCap,
 };
-use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
 use itertools::Itertools;
 use lsp_types::{ClientCapabilities, MarkupKind};
-use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
+use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource};
 use rustc_hash::FxHashSet;
 use serde::{de::DeserializeOwned, Deserialize};
 use vfs::AbsPathBuf;
 
-use crate::{caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig};
+use crate::{
+    caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig,
+    line_index::OffsetEncoding, lsp_ext::supports_utf8,
+};
 
 config_data! {
     struct ConfigData {
@@ -32,19 +35,20 @@ struct ConfigData {
         assist_importMergeBehaviour: MergeBehaviorDef  = "\"full\"",
         /// The path structure for newly inserted paths to use.
         assist_importPrefix: ImportPrefixDef           = "\"plain\"",
-
+        /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
+        assist_importGroup: bool                       = "true",
         /// Show function name and docs in parameter hints.
         callInfo_full: bool = "true",
 
         /// Automatically refresh project info via `cargo metadata` on
         /// `Cargo.toml` changes.
         cargo_autoreload: bool           = "true",
-        /// Activate all available features.
+        /// Activate all available features (`--all-features`).
         cargo_allFeatures: bool          = "false",
         /// List of features to activate.
         cargo_features: Vec<String>      = "[]",
-        /// Run `cargo check` on startup to get the correct value for package
-        /// OUT_DIRs.
+        /// Run build scripts (`build.rs`) for more precise code analysis.
+        cargo_runBuildScripts |
         cargo_loadOutDirsFromCheck: bool = "false",
         /// Do not activate the `default` feature.
         cargo_noDefaultFeatures: bool    = "false",
@@ -55,10 +59,10 @@ struct ConfigData {
 
         /// Run specified `cargo check` command for diagnostics on save.
         checkOnSave_enable: bool                         = "true",
-        /// Check with all features (will be passed as `--all-features`).
+        /// Check with all features (`--all-features`).
         /// Defaults to `#rust-analyzer.cargo.allFeatures#`.
         checkOnSave_allFeatures: Option<bool>            = "null",
-        /// Check all targets and tests (will be passed as `--all-targets`).
+        /// Check all targets and tests (`--all-targets`).
         checkOnSave_allTargets: bool                     = "true",
         /// Cargo command to use for `cargo check`.
         checkOnSave_command: String                      = "\"check\"",
@@ -105,6 +109,8 @@ struct ConfigData {
 
         /// Controls file watching implementation.
         files_watcher: String = "\"client\"",
+        /// These directories will be ignored by rust-analyzer.
+        files_excludeDirs: Vec<PathBuf> = "[]",
 
         /// Whether to show `Debug` action. Only applies when
         /// `#rust-analyzer.hoverActions.enable#` is set.
@@ -147,20 +153,22 @@ struct ConfigData {
         /// Whether to show `Method References` lens. Only applies when
         /// `#rust-analyzer.lens.enable#` is set.
         lens_methodReferences: bool = "false",
+        /// Whether to show `References` lens. Only applies when
+        /// `#rust-analyzer.lens.enable#` is set.
+        lens_references: bool = "false",
 
         /// Disable project auto-discovery in favor of explicitly specified set
         /// of projects.\n\nElements must be paths pointing to `Cargo.toml`,
         /// `rust-project.json`, or JSON objects in `rust-project.json` format.
         linkedProjects: Vec<ManifestOrProjectJson> = "[]",
 
-        /// Number of syntax trees rust-analyzer keeps in memory.  Defaults to 128.
+        /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
         lruCapacity: Option<usize>                 = "null",
 
         /// Whether to show `can't find Cargo.toml` error message.
         notifications_cargoTomlNotFound: bool      = "true",
 
-        /// Enable Proc macro support, `#rust-analyzer.cargo.loadOutDirsFromCheck#` must be
-        /// enabled.
+        /// Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScripts#`.
         procMacro_enable: bool                     = "false",
         /// Internal config, path to proc-macro server executable (typically,
         /// this is rust-analyzer itself, but we override this in tests).
@@ -172,8 +180,9 @@ struct ConfigData {
         /// tests or binaries.\nFor example, it may be `--release`.
         runnables_cargoExtraArgs: Vec<String>   = "[]",
 
-        /// Path to the rust compiler sources, for usage in rustc_private projects.
-        rustcSource : Option<PathBuf> = "null",
+        /// Path to the rust compiler sources, for usage in rustc_private projects, or "discover"
+        /// to try to automatically find it.
+        rustcSource : Option<String> = "null",
 
         /// Additional arguments to `rustfmt`.
         rustfmt_extraArgs: Vec<String>               = "[]",
@@ -221,6 +230,7 @@ pub struct LensConfig {
     pub debug: bool,
     pub implementations: bool,
     pub method_refs: bool,
+    pub refs: bool, // for Struct, Enum, Union and Trait
 }
 
 impl LensConfig {
@@ -237,14 +247,14 @@ pub fn runnable(&self) -> bool {
     }
 
     pub fn references(&self) -> bool {
-        self.method_refs
+        self.method_refs || self.refs
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct FilesConfig {
     pub watcher: FilesWatcher,
-    pub exclude: Vec<String>,
+    pub exclude: Vec<AbsPathBuf>,
 }
 
 #[derive(Debug, Clone)]
@@ -408,6 +418,13 @@ pub fn signature_help_label_offsets(&self) -> bool {
             false
         )
     }
+    pub fn offset_encoding(&self) -> OffsetEncoding {
+        if supports_utf8(&self.caps) {
+            OffsetEncoding::Utf8
+        } else {
+            OffsetEncoding::Utf16
+        }
+    }
 
     fn experimental(&self, index: &'static str) -> bool {
         try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false)
@@ -454,7 +471,7 @@ pub fn files(&self) -> FilesConfig {
                 "notify" => FilesWatcher::Notify,
                 "client" | _ => FilesWatcher::Client,
             },
-            exclude: Vec::new(),
+            exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
         }
     }
     pub fn notifications(&self) -> NotificationsConfig {
@@ -463,18 +480,22 @@ pub fn notifications(&self) -> NotificationsConfig {
     pub fn cargo_autoreload(&self) -> bool {
         self.data.cargo_autoreload
     }
+    pub fn run_build_scripts(&self) -> bool {
+        self.data.cargo_runBuildScripts || self.data.procMacro_enable
+    }
     pub fn cargo(&self) -> CargoConfig {
-        let rustc_source = self.data.rustcSource.clone().and_then(|it| {
-            AbsPathBuf::try_from(it)
-                .map_err(|_| log::error!("rustc source directory must be an absolute path"))
-                .ok()
+        let rustc_source = self.data.rustcSource.as_ref().map(|rustc_src| {
+            if rustc_src == "discover" {
+                RustcSource::Discover
+            } else {
+                RustcSource::Path(self.root_path.join(rustc_src))
+            }
         });
 
         CargoConfig {
             no_default_features: self.data.cargo_noDefaultFeatures,
             all_features: self.data.cargo_allFeatures,
             features: self.data.cargo_features.clone(),
-            load_out_dirs_from_check: self.data.cargo_loadOutDirsFromCheck,
             target: self.data.cargo_target.clone(),
             rustc_source,
             no_sysroot: self.data.cargo_noSysroot,
@@ -508,7 +529,7 @@ pub fn flycheck(&self) -> Option<FlycheckConfig> {
                     .data
                     .checkOnSave_target
                     .clone()
-                    .or(self.data.cargo_target.clone()),
+                    .or_else(|| self.data.cargo_target.clone()),
                 all_targets: self.data.checkOnSave_allTargets,
                 no_default_features: self
                     .data
@@ -522,7 +543,7 @@ pub fn flycheck(&self) -> Option<FlycheckConfig> {
                     .data
                     .checkOnSave_features
                     .clone()
-                    .unwrap_or(self.data.cargo_features.clone()),
+                    .unwrap_or_else(|| self.data.cargo_features.clone()),
                 extra_args: self.data.checkOnSave_extraArgs.clone(),
             },
         };
@@ -542,21 +563,29 @@ pub fn inlay_hints(&self) -> InlayHintsConfig {
             max_length: self.data.inlayHints_maxLength,
         }
     }
-    fn merge_behavior(&self) -> Option<MergeBehavior> {
-        match self.data.assist_importMergeBehavior {
-            MergeBehaviorDef::None => None,
-            MergeBehaviorDef::Full => Some(MergeBehavior::Full),
-            MergeBehaviorDef::Last => Some(MergeBehavior::Last),
+    fn insert_use_config(&self) -> InsertUseConfig {
+        InsertUseConfig {
+            merge: match self.data.assist_importMergeBehavior {
+                MergeBehaviorDef::None => None,
+                MergeBehaviorDef::Full => Some(MergeBehavior::Full),
+                MergeBehaviorDef::Last => Some(MergeBehavior::Last),
+            },
+            prefix_kind: match self.data.assist_importPrefix {
+                ImportPrefixDef::Plain => PrefixKind::Plain,
+                ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
+                ImportPrefixDef::BySelf => PrefixKind::BySelf,
+            },
+            group: self.data.assist_importGroup,
         }
     }
     pub fn completion(&self) -> CompletionConfig {
         CompletionConfig {
             enable_postfix_completions: self.data.completion_postfix_enable,
-            enable_autoimport_completions: self.data.completion_autoimport_enable
+            enable_imports_on_the_fly: self.data.completion_autoimport_enable
                 && completion_item_edit_resolve(&self.caps),
             add_call_parenthesis: self.data.completion_addCallParenthesis,
             add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
-            merge: self.merge_behavior(),
+            insert_use: self.insert_use_config(),
             snippet_cap: SnippetCap::new(try_or!(
                 self.caps
                     .text_document
@@ -574,14 +603,7 @@ pub fn assist(&self) -> AssistConfig {
         AssistConfig {
             snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
             allowed: None,
-            insert_use: InsertUseConfig {
-                merge: self.merge_behavior(),
-                prefix_kind: match self.data.assist_importPrefix {
-                    ImportPrefixDef::Plain => PrefixKind::Plain,
-                    ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
-                    ImportPrefixDef::BySelf => PrefixKind::BySelf,
-                },
-            },
+            insert_use: self.insert_use_config(),
         }
     }
     pub fn call_info_full(&self) -> bool {
@@ -593,6 +615,7 @@ pub fn lens(&self) -> LensConfig {
             debug: self.data.lens_enable && self.data.lens_debug,
             implementations: self.data.lens_enable && self.data.lens_implementations,
             method_refs: self.data.lens_enable && self.data.lens_methodReferences,
+            refs: self.data.lens_enable && self.data.lens_references,
         }
     }
     pub fn hover(&self) -> HoverConfig {
@@ -719,7 +742,7 @@ fn get_field<T: DeserializeOwned>(
 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
     for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
         fn key(f: &str) -> &str {
-            f.splitn(2, "_").next().unwrap()
+            f.splitn(2, '_').next().unwrap()
         }
         assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
     }
@@ -762,6 +785,10 @@ macro_rules! set {
             "type": "array",
             "items": { "type": "string" },
         },
+        "Vec<PathBuf>" => set! {
+            "type": "array",
+            "items": { "type": "string" },
+        },
         "FxHashSet<String>" => set! {
             "type": "array",
             "items": { "type": "string" },
@@ -839,15 +866,32 @@ mod tests {
     fn schema_in_sync_with_package_json() {
         let s = Config::json_schema();
         let schema = format!("{:#}", s);
-        let schema = schema.trim_start_matches('{').trim_end_matches('}');
-
-        let package_json = project_dir().join("editors/code/package.json");
-        let package_json = fs::read_to_string(&package_json).unwrap();
-
-        let p = remove_ws(&package_json);
+        let mut schema = schema
+            .trim_start_matches('{')
+            .trim_end_matches('}')
+            .replace("  ", "    ")
+            .replace("\n", "\n            ")
+            .trim_start_matches('\n')
+            .trim_end()
+            .to_string();
+        schema.push_str(",\n");
+
+        let package_json_path = project_dir().join("editors/code/package.json");
+        let mut package_json = fs::read_to_string(&package_json_path).unwrap();
+
+        let start_marker = "                \"$generated-start\": false,\n";
+        let end_marker = "                \"$generated-end\": false\n";
+
+        let start = package_json.find(start_marker).unwrap() + start_marker.len();
+        let end = package_json.find(end_marker).unwrap();
+        let p = remove_ws(&package_json[start..end]);
         let s = remove_ws(&schema);
 
-        assert!(p.contains(&s), "update config in package.json. New config:\n{:#}", schema);
+        if !p.contains(&s) {
+            package_json.replace_range(start..end, &schema);
+            fs::write(&package_json_path, &mut package_json).unwrap();
+            panic!("new config, updating package.json")
+        }
     }
 
     #[test]