]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/config.rs
Replaced fold with for loop
[rust.git] / crates / rust-analyzer / src / config.rs
index 81f4ccd365c3aca1288d0b942aa247f1813367ef..76b72707974cf1795bfd6c51e19f7fecd32e57f3 100644 (file)
@@ -11,8 +11,8 @@
 
 use flycheck::FlycheckConfig;
 use ide::{
-    AssistConfig, CompletionConfig, DiagnosticsConfig, HighlightRelatedConfig, HoverConfig,
-    HoverDocFormat, InlayHintsConfig, JoinLinesConfig, Snippet, SnippetScope,
+    AssistConfig, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, HighlightRelatedConfig,
+    HoverConfig, HoverDocFormat, InlayHintsConfig, JoinLinesConfig, Snippet, SnippetScope,
 };
 use ide_db::helpers::{
     insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -45,6 +45,8 @@
 // parsing the old name.
 config_data! {
     struct ConfigData {
+        /// Placeholder for missing expressions in assists.
+        assist_exprFillDefault: ExprFillDefaultDef              = "\"todo\"",
         /// How imports should be grouped into use statements.
         assist_importGranularity |
         assist_importMergeBehavior |
@@ -58,6 +60,9 @@ struct ConfigData {
         /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
         assist_allowMergingIntoGlobImports: bool           = "true",
 
+        /// Warm up caches on project load.
+        cache_warmup: bool = "true",
+
         /// Show function name and docs in parameter hints.
         callInfo_full: bool                                = "true",
 
@@ -113,7 +118,48 @@ struct ConfigData {
         /// Whether to add parenthesis when completing functions.
         completion_addCallParenthesis: bool      = "true",
         /// Custom completion snippets.
-        completion_snippets: FxHashMap<String, SnippetDef> = "{}",
+        // NOTE: Keep this list in sync with the feature docs of user snippets.
+        completion_snippets: FxHashMap<String, SnippetDef> = r#"{
+            "Arc::new": {
+                "postfix": "arc",
+                "body": "Arc::new(${receiver})",
+                "requires": "std::sync::Arc",
+                "description": "Put the expression into an `Arc`",
+                "scope": "expr"
+            },
+            "Rc::new": {
+                "postfix": "rc",
+                "body": "Rc::new(${receiver})",
+                "requires": "std::rc::Rc",
+                "description": "Put the expression into an `Rc`",
+                "scope": "expr"
+            },
+            "Box::pin": {
+                "postfix": "pinbox",
+                "body": "Box::pin(${receiver})",
+                "requires": "std::boxed::Box",
+                "description": "Put the expression into a pinned `Box`",
+                "scope": "expr"
+            },
+            "Ok": {
+                "postfix": "ok",
+                "body": "Ok(${receiver})",
+                "description": "Wrap the expression in a `Result::Ok`",
+                "scope": "expr"
+            },
+            "Err": {
+                "postfix": "err",
+                "body": "Err(${receiver})",
+                "description": "Wrap the expression in a `Result::Err`",
+                "scope": "expr"
+            },
+            "Some": {
+                "postfix": "some",
+                "body": "Some(${receiver})",
+                "description": "Wrap the expression in an `Option::Some`",
+                "scope": "expr"
+            }
+        }"#,
         /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
         completion_postfix_enable: bool          = "true",
         /// Toggles the additional completions that automatically add imports when completed.
@@ -195,14 +241,16 @@ struct ConfigData {
         hoverActions_run: bool             = "true",
 
         /// Whether to show inlay type hints for method chains.
-        inlayHints_chainingHints: bool      = "true",
+        inlayHints_chainingHints: bool              = "true",
         /// Maximum length for inlay hints. Set to null to have an unlimited length.
-        inlayHints_maxLength: Option<usize> = "25",
+        inlayHints_maxLength: Option<usize>         = "25",
         /// Whether to show function parameter name inlay hints at the call
         /// site.
-        inlayHints_parameterHints: bool     = "true",
+        inlayHints_parameterHints: bool             = "true",
         /// Whether to show inlay type hints for variables.
-        inlayHints_typeHints: bool          = "true",
+        inlayHints_typeHints: bool                  = "true",
+        /// Whether to hide inlay hints for constructors.
+        inlayHints_hideNamedConstructorHints: bool  = "false",
 
         /// Join lines inserts else between consecutive ifs.
         joinLines_joinElseIf: bool = "true",
@@ -250,11 +298,18 @@ struct ConfigData {
         /// Whether to show `can't find Cargo.toml` error message.
         notifications_cargoTomlNotFound: bool      = "true",
 
+        /// How many worker threads to to handle priming caches. The default `0` means to pick automatically.
+        primeCaches_numThreads: ParallelPrimeCachesNumThreads = "0",
+
         /// Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScripts#`.
         procMacro_enable: bool                     = "true",
         /// Internal config, path to proc-macro server executable (typically,
         /// this is rust-analyzer itself, but we override this in tests).
         procMacro_server: Option<PathBuf>          = "null",
+        /// These proc-macros will be ignored when trying to expand them.
+        ///
+        /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
+        procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>>          = "{}",
 
         /// Command to be executed instead of 'cargo' for runnables.
         runnables_overrideCargo: Option<String> = "null",
@@ -291,7 +346,7 @@ struct ConfigData {
 
 impl Default for ConfigData {
     fn default() -> Self {
-        ConfigData::from_json(serde_json::Value::Null)
+        ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
     }
 }
 
@@ -442,16 +497,21 @@ pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
             snippets: Default::default(),
         }
     }
-    pub fn update(&mut self, mut json: serde_json::Value) {
+    pub fn update(
+        &mut self,
+        mut json: serde_json::Value,
+    ) -> Result<(), Vec<(String, serde_json::Error)>> {
         tracing::info!("updating config from JSON: {:#}", json);
         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
-            return;
+            return Ok(());
         }
-        self.detached_files = get_field::<Vec<PathBuf>>(&mut json, "detachedFiles", None, "[]")
-            .into_iter()
-            .map(AbsPathBuf::assert)
-            .collect();
-        self.data = ConfigData::from_json(json);
+        let mut errors = Vec::new();
+        self.detached_files =
+            get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
+                .into_iter()
+                .map(AbsPathBuf::assert)
+                .collect();
+        self.data = ConfigData::from_json(json, &mut errors);
         self.snippets.clear();
         for (name, def) in self.data.completion_snippets.iter() {
             if def.prefix.is_empty() && def.postfix.is_empty() {
@@ -474,6 +534,11 @@ pub fn update(&mut self, mut json: serde_json::Value) {
                 None => tracing::info!("Invalid snippet {}", name),
             }
         }
+        if errors.is_empty() {
+            Ok(())
+        } else {
+            Err(errors)
+        }
     }
 
     pub fn json_schema() -> serde_json::Value {
@@ -543,6 +608,10 @@ pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
         )
     }
 
+    pub fn prefill_caches(&self) -> bool {
+        self.data.cache_warmup
+    }
+
     pub fn location_link(&self) -> bool {
         try_or!(self.caps.text_document.as_ref()?.definition?.link_support?, false)
     }
@@ -644,6 +713,10 @@ pub fn diagnostics(&self) -> DiagnosticsConfig {
         DiagnosticsConfig {
             disable_experimental: !self.data.diagnostics_enableExperimental,
             disabled: self.data.diagnostics_disabled.clone(),
+            expr_fill_default: match self.data.assist_exprFillDefault {
+                ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
+                ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
+            },
         }
     }
     pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
@@ -666,6 +739,9 @@ pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
         };
         Some((path, vec!["proc-macro".into()]))
     }
+    pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
+        &self.data.procMacro_ignored
+    }
     pub fn expand_proc_attr_macros(&self) -> bool {
         self.data.experimental_procAttrMacros
     }
@@ -673,7 +749,10 @@ pub fn files(&self) -> FilesConfig {
         FilesConfig {
             watcher: match self.data.files_watcher.as_str() {
                 "notify" => FilesWatcher::Notify,
-                "client" | _ => FilesWatcher::Client,
+                "client" if self.did_change_watched_files_dynamic_registration() => {
+                    FilesWatcher::Client
+                }
+                _ => FilesWatcher::Notify,
             },
             exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
         }
@@ -768,6 +847,7 @@ pub fn inlay_hints(&self) -> InlayHintsConfig {
             type_hints: self.data.inlayHints_typeHints,
             parameter_hints: self.data.inlayHints_parameterHints,
             chaining_hints: self.data.inlayHints_chainingHints,
+            hide_named_constructor_hints: self.data.inlayHints_hideNamedConstructorHints,
             max_length: self.data.inlayHints_maxLength,
         }
     }
@@ -939,6 +1019,13 @@ pub fn highlight_related(&self) -> HighlightRelatedConfig {
             yield_points: self.data.highlightRelated_yieldPoints,
         }
     }
+
+    pub fn prime_caches_num_threads(&self) -> u8 {
+        match self.data.primeCaches_numThreads {
+            0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
+            n => n,
+        }
+    }
 }
 
 #[derive(Deserialize, Debug, Clone, Copy)]
@@ -1008,6 +1095,15 @@ enum ManifestOrProjectJson {
     ProjectJson(ProjectJsonData),
 }
 
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum ExprFillDefaultDef {
+    #[serde(alias = "todo")]
+    Todo,
+    #[serde(alias = "default")]
+    Default,
+}
+
 #[derive(Deserialize, Debug, Clone)]
 #[serde(rename_all = "snake_case")]
 enum ImportGranularityDef {
@@ -1044,6 +1140,8 @@ enum WorkspaceSymbolSearchKindDef {
     AllSymbols,
 }
 
+type ParallelPrimeCachesNumThreads = u8;
+
 macro_rules! _config_data {
     (struct $name:ident {
         $(
@@ -1055,10 +1153,11 @@ macro_rules! _config_data {
         #[derive(Debug, Clone)]
         struct $name { $($field: $ty,)* }
         impl $name {
-            fn from_json(mut json: serde_json::Value) -> $name {
+            fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
                 $name {$(
                     $field: get_field(
                         &mut json,
+                        error_sink,
                         stringify!($field),
                         None$(.or(Some(stringify!($alias))))*,
                         $default,
@@ -1095,6 +1194,7 @@ fn manual() -> String {
 
 fn get_field<T: DeserializeOwned>(
     json: &mut serde_json::Value,
+    error_sink: &mut Vec<(String, serde_json::Error)>,
     field: &'static str,
     alias: Option<&'static str>,
     default: &str,
@@ -1109,7 +1209,14 @@ fn get_field<T: DeserializeOwned>(
         .find_map(move |field| {
             let mut pointer = field.replace('_', "/");
             pointer.insert(0, '/');
-            json.pointer_mut(&pointer).and_then(|it| serde_json::from_value(it.take()).ok())
+            json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
+                Ok(it) => Some(it),
+                Err(e) => {
+                    tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
+                    error_sink.push((pointer, e));
+                    None
+                }
+            })
         })
         .unwrap_or(default)
 }
@@ -1170,6 +1277,9 @@ macro_rules! set {
             "items": { "type": "string" },
             "uniqueItems": true,
         },
+        "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
+            "type": "object",
+        },
         "FxHashMap<String, SnippetDef>" => set! {
             "type": "object",
         },
@@ -1202,6 +1312,14 @@ macro_rules! set {
                 "Merge imports from the same module into a single `use` statement."
             ],
         },
+        "ExprFillDefaultDef" => set! {
+            "type": "string",
+            "enum": ["todo", "default"],
+            "enumDescriptions": [
+                "Fill missing expressions with the `todo` macro",
+                "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
+            ],
+        },
         "ImportGranularityDef" => set! {
             "type": "string",
             "enum": ["preserve", "crate", "module", "item"],
@@ -1245,6 +1363,11 @@ macro_rules! set {
                 "Search for all symbols kinds"
             ],
         },
+        "ParallelPrimeCachesNumThreads" => set! {
+            "type": "number",
+            "minimum": 0,
+            "maximum": 255
+        },
         _ => panic!("{}: {}", ty, default),
     }
 
@@ -1258,7 +1381,23 @@ fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
         .map(|(field, _ty, doc, default)| {
             let name = format!("rust-analyzer.{}", field.replace("_", "."));
             let doc = doc_comment_to_string(*doc);
-            format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
+            if default.contains('\n') {
+                format!(
+                    r#"[[{}]]{}::
++
+--
+Default:
+----
+{}
+----
+{}
+--
+"#,
+                    name, name, default, doc
+                )
+            } else {
+                format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
+            }
         })
         .collect::<String>()
 }