X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Frust-analyzer%2Fsrc%2Fconfig.rs;h=cac48e9117099074a33638e3137cdc66b32e3d2e;hb=96fc01a30b88d95619b26fd96c58627dd54cb339;hp=a5b1d90b1dc5e1ded204c525e787a2e969a9f836;hpb=18dbb8f5c7714e4c711702232919e482313e1041;p=rust.git diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index a5b1d90b1dc..cac48e91170 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -7,40 +7,48 @@ //! 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, path::PathBuf}; +use std::{ffi::OsString, iter, path::PathBuf}; use flycheck::FlycheckConfig; use hir::PrefixKind; use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; -use ide_db::helpers::insert_use::MergeBehavior; +use ide_db::helpers::{ + insert_use::{InsertUseConfig, 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::enabled_completions_resolve_capabilities, diagnostics::DiagnosticsMapConfig}; +use crate::{ + caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig, + line_index::OffsetEncoding, lsp_ext::supports_utf8, +}; config_data! { struct ConfigData { /// The strategy to use when inserting new imports or merging imports. - assist_importMergeBehaviour: MergeBehaviorDef = "\"full\"", + assist_importMergeBehavior | + 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 = "[]", - /// 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", @@ -51,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 = "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\"", @@ -101,6 +109,8 @@ struct ConfigData { /// Controls file watching implementation. files_watcher: String = "\"client\"", + /// These directories will be ignored by rust-analyzer. + files_excludeDirs: Vec = "[]", /// Whether to show `Debug` action. Only applies when /// `#rust-analyzer.hoverActions.enable#` is set. @@ -143,18 +153,26 @@ 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 = "[]", - /// 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 = "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). + procMacro_server: Option = "null", /// Command to be executed instead of 'cargo' for runnables. runnables_overrideCargo: Option = "null", @@ -162,7 +180,8 @@ struct ConfigData { /// tests or binaries.\nFor example, it may be `--release`. runnables_cargoExtraArgs: Vec = "[]", - /// Path to the rust compiler sources, for usage in rustc_private projects. + /// Path to the rust compiler sources, for usage in rustc_private projects, or "discover" + /// to try to automatically find it. rustcSource : Option = "null", /// Additional arguments to `rustfmt`. @@ -173,34 +192,17 @@ struct ConfigData { } } +impl Default for ConfigData { + fn default() -> Self { + ConfigData::from_json(serde_json::Value::Null) + } +} + #[derive(Debug, Clone)] pub struct Config { - pub client_caps: ClientCapsConfig, - - pub publish_diagnostics: bool, - pub diagnostics: DiagnosticsConfig, - pub diagnostics_map: DiagnosticsMapConfig, - pub lru_capacity: Option, - pub proc_macro_srv: Option<(PathBuf, Vec)>, - pub files: FilesConfig, - pub notifications: NotificationsConfig, - - pub cargo_autoreload: bool, - pub cargo: CargoConfig, - pub rustfmt: RustfmtConfig, - pub flycheck: Option, - pub runnables: RunnablesConfig, - - pub inlay_hints: InlayHintsConfig, - pub completion: CompletionConfig, - pub assist: AssistConfig, - pub call_info_full: bool, - pub lens: LensConfig, - pub hover: HoverConfig, - pub semantic_tokens_refresh: bool, - pub code_lens_refresh: bool, - - pub linked_projects: Vec, + caps: lsp_types::ClientCapabilities, + data: ConfigData, + pub discovered_projects: Option>, pub root_path: AbsPathBuf, } @@ -228,12 +230,7 @@ pub struct LensConfig { pub debug: bool, pub implementations: bool, pub method_refs: bool, -} - -impl Default for LensConfig { - fn default() -> Self { - Self { run: true, debug: true, implementations: true, method_refs: false } - } + pub refs: bool, // for Struct, Enum, Union and Trait } impl LensConfig { @@ -250,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, + pub exclude: Vec, } #[derive(Debug, Clone)] @@ -278,7 +275,7 @@ pub enum RustfmtConfig { } /// Configuration for runnable items, such as `main` function or tests. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct RunnablesConfig { /// Custom command to be executed instead of `cargo` for runnables. pub override_cargo: Option, @@ -286,325 +283,379 @@ pub struct RunnablesConfig { pub cargo_extra_args: Vec, } -#[derive(Debug, Clone, Default)] -pub struct ClientCapsConfig { - pub location_link: bool, - pub line_folding_only: bool, - pub hierarchical_symbols: bool, - pub code_action_literals: bool, - pub work_done_progress: bool, - pub code_action_group: bool, - pub code_action_resolve: bool, - pub hover_actions: bool, - pub status_notification: bool, - pub signature_help_label_offsets: bool, -} - impl Config { - pub fn new(root_path: AbsPathBuf) -> Self { - // Defaults here don't matter, we'll immediately re-write them with - // ConfigData. - let mut res = Config { - client_caps: ClientCapsConfig::default(), - - publish_diagnostics: false, - diagnostics: DiagnosticsConfig::default(), - diagnostics_map: DiagnosticsMapConfig::default(), - lru_capacity: None, - proc_macro_srv: None, - files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, - notifications: NotificationsConfig { cargo_toml_not_found: false }, - - cargo_autoreload: false, - cargo: CargoConfig::default(), - rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, - flycheck: Some(FlycheckConfig::CargoCommand { - command: String::new(), - target_triple: None, - no_default_features: false, - all_targets: false, - all_features: false, - extra_args: Vec::new(), - features: Vec::new(), - }), - runnables: RunnablesConfig::default(), - - inlay_hints: InlayHintsConfig { - type_hints: false, - parameter_hints: false, - chaining_hints: false, - max_length: None, - }, - completion: CompletionConfig::default(), - assist: AssistConfig::default(), - call_info_full: false, - lens: LensConfig::default(), - hover: HoverConfig::default(), - semantic_tokens_refresh: false, - code_lens_refresh: false, - linked_projects: Vec::new(), - root_path, - }; - res.do_update(serde_json::json!({})); - res + pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { + Config { caps, data: ConfigData::default(), discovered_projects: None, root_path } } pub fn update(&mut self, json: serde_json::Value) { log::info!("updating config from JSON: {:#}", json); if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) { return; } - self.do_update(json); - log::info!("updated config: {:#?}", self); + self.data = ConfigData::from_json(json); } - fn do_update(&mut self, json: serde_json::Value) { - let data = ConfigData::from_json(json); - self.publish_diagnostics = data.diagnostics_enable; - self.diagnostics = DiagnosticsConfig { - disable_experimental: !data.diagnostics_enableExperimental, - disabled: data.diagnostics_disabled, - }; - self.diagnostics_map = DiagnosticsMapConfig { - warnings_as_info: data.diagnostics_warningsAsInfo, - warnings_as_hint: data.diagnostics_warningsAsHint, - }; - self.lru_capacity = data.lruCapacity; - self.files.watcher = match data.files_watcher.as_str() { - "notify" => FilesWatcher::Notify, - "client" | _ => FilesWatcher::Client, - }; - self.notifications = - NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound }; - self.cargo_autoreload = data.cargo_autoreload; - - let rustc_source = if let Some(rustc_source) = data.rustcSource { - let rustpath: PathBuf = rustc_source.into(); - AbsPathBuf::try_from(rustpath) - .map_err(|_| { - log::error!("rustc source directory must be an absolute path"); - }) - .ok() - } else { - None - }; - - self.cargo = CargoConfig { - no_default_features: data.cargo_noDefaultFeatures, - all_features: data.cargo_allFeatures, - features: data.cargo_features.clone(), - load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, - target: data.cargo_target.clone(), - rustc_source: rustc_source, - no_sysroot: data.cargo_noSysroot, - }; - self.runnables = RunnablesConfig { - override_cargo: data.runnables_overrideCargo, - cargo_extra_args: data.runnables_cargoExtraArgs, - }; - - self.proc_macro_srv = if data.procMacro_enable { - std::env::current_exe().ok().map(|path| (path, vec!["proc-macro".into()])) - } else { - None - }; + pub fn json_schema() -> serde_json::Value { + ConfigData::json_schema() + } +} - self.rustfmt = match data.rustfmt_overrideCommand { - Some(mut args) if !args.is_empty() => { - let command = args.remove(0); - RustfmtConfig::CustomCommand { command, args } - } - Some(_) | None => RustfmtConfig::Rustfmt { extra_args: data.rustfmt_extraArgs }, - }; +macro_rules! try_ { + ($expr:expr) => { + || -> _ { Some($expr) }() + }; +} +macro_rules! try_or { + ($expr:expr, $or:expr) => { + try_!($expr).unwrap_or($or) + }; +} - self.flycheck = if data.checkOnSave_enable { - let flycheck_config = match data.checkOnSave_overrideCommand { - Some(mut args) if !args.is_empty() => { - let command = args.remove(0); - FlycheckConfig::CustomCommand { command, args } - } - Some(_) | None => FlycheckConfig::CargoCommand { - command: data.checkOnSave_command, - target_triple: data.checkOnSave_target.or(data.cargo_target), - all_targets: data.checkOnSave_allTargets, - no_default_features: data - .checkOnSave_noDefaultFeatures - .unwrap_or(data.cargo_noDefaultFeatures), - all_features: data.checkOnSave_allFeatures.unwrap_or(data.cargo_allFeatures), - features: data.checkOnSave_features.unwrap_or(data.cargo_features), - extra_args: data.checkOnSave_extraArgs, - }, - }; - Some(flycheck_config) +impl Config { + pub fn linked_projects(&self) -> Vec { + if self.data.linkedProjects.is_empty() { + self.discovered_projects + .as_ref() + .into_iter() + .flatten() + .cloned() + .map(LinkedProject::from) + .collect() } else { - None - }; - - self.inlay_hints = InlayHintsConfig { - type_hints: data.inlayHints_typeHints, - parameter_hints: data.inlayHints_parameterHints, - chaining_hints: data.inlayHints_chainingHints, - max_length: data.inlayHints_maxLength, - }; - - self.assist.insert_use.merge = match data.assist_importMergeBehaviour { - MergeBehaviorDef::None => None, - MergeBehaviorDef::Full => Some(MergeBehavior::Full), - MergeBehaviorDef::Last => Some(MergeBehavior::Last), - }; - self.assist.insert_use.prefix_kind = match data.assist_importPrefix { - ImportPrefixDef::Plain => PrefixKind::Plain, - ImportPrefixDef::ByCrate => PrefixKind::ByCrate, - ImportPrefixDef::BySelf => PrefixKind::BySelf, - }; - - self.completion.enable_postfix_completions = data.completion_postfix_enable; - self.completion.enable_autoimport_completions = data.completion_autoimport_enable; - self.completion.add_call_parenthesis = data.completion_addCallParenthesis; - self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets; - self.completion.merge = self.assist.insert_use.merge; - - self.call_info_full = data.callInfo_full; - - self.lens = LensConfig { - run: data.lens_enable && data.lens_run, - debug: data.lens_enable && data.lens_debug, - implementations: data.lens_enable && data.lens_implementations, - method_refs: data.lens_enable && data.lens_methodReferences, - }; - - if !data.linkedProjects.is_empty() { - self.linked_projects.clear(); - for linked_project in data.linkedProjects { - let linked_project = match linked_project { - ManifestOrProjectJson::Manifest(it) => { - let path = self.root_path.join(it); - match ProjectManifest::from_manifest_file(path) { - Ok(it) => it.into(), - Err(e) => { - log::error!("failed to load linked project: {}", e); - continue; - } + self.data + .linkedProjects + .iter() + .filter_map(|linked_project| { + let res = match linked_project { + ManifestOrProjectJson::Manifest(it) => { + let path = self.root_path.join(it); + ProjectManifest::from_manifest_file(path) + .map_err(|e| log::error!("failed to load linked project: {}", e)) + .ok()? + .into() } - } - ManifestOrProjectJson::ProjectJson(it) => { - ProjectJson::new(&self.root_path, it).into() - } - }; - self.linked_projects.push(linked_project); - } + ManifestOrProjectJson::ProjectJson(it) => { + ProjectJson::new(&self.root_path, it.clone()).into() + } + }; + Some(res) + }) + .collect() } + } - self.hover = HoverConfig { - implementations: data.hoverActions_enable && data.hoverActions_implementations, - run: data.hoverActions_enable && data.hoverActions_run, - debug: data.hoverActions_enable && data.hoverActions_debug, - goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, - links_in_hover: data.hoverActions_linksInHover, - markdown: true, - }; + pub fn did_save_text_document_dynamic_registration(&self) -> bool { + let caps = + try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default()); + caps.did_save == Some(true) && caps.dynamic_registration == Some(true) + } + pub fn did_change_watched_files_dynamic_registration(&self) -> bool { + try_or!( + self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?, + false + ) } - pub fn update_caps(&mut self, caps: &ClientCapabilities) { - if let Some(doc_caps) = caps.text_document.as_ref() { - if let Some(value) = doc_caps.hover.as_ref().and_then(|it| it.content_format.as_ref()) { - self.hover.markdown = value.contains(&MarkupKind::Markdown) - } - if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) { - self.client_caps.location_link = value; - } - if let Some(value) = doc_caps.folding_range.as_ref().and_then(|it| it.line_folding_only) - { - self.client_caps.line_folding_only = value - } - if let Some(value) = doc_caps + pub fn location_link(&self) -> bool { + try_or!(self.caps.text_document.as_ref()?.definition?.link_support?, false) + } + pub fn line_folding_only(&self) -> bool { + try_or!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?, false) + } + pub fn hierarchical_symbols(&self) -> bool { + try_or!( + self.caps + .text_document + .as_ref()? .document_symbol - .as_ref() - .and_then(|it| it.hierarchical_document_symbol_support) - { - self.client_caps.hierarchical_symbols = value - } - if let Some(value) = - doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some()) - { - self.client_caps.code_action_literals = value; - } - if let Some(value) = doc_caps + .as_ref()? + .hierarchical_document_symbol_support?, + false + ) + } + pub fn code_action_literals(&self) -> bool { + try_!(self + .caps + .text_document + .as_ref()? + .code_action + .as_ref()? + .code_action_literal_support + .as_ref()?) + .is_some() + } + pub fn work_done_progress(&self) -> bool { + try_or!(self.caps.window.as_ref()?.work_done_progress?, false) + } + pub fn code_action_resolve(&self) -> bool { + try_or!( + self.caps + .text_document + .as_ref()? + .code_action + .as_ref()? + .resolve_support + .as_ref()? + .properties + .as_slice(), + &[] + ) + .iter() + .any(|it| it == "edit") + } + pub fn signature_help_label_offsets(&self) -> bool { + try_or!( + self.caps + .text_document + .as_ref()? .signature_help - .as_ref() - .and_then(|it| it.signature_information.as_ref()) - .and_then(|it| it.parameter_information.as_ref()) - .and_then(|it| it.label_offset_support) - { - self.client_caps.signature_help_label_offsets = value; - } + .as_ref()? + .signature_information + .as_ref()? + .parameter_information + .as_ref()? + .label_offset_support?, + false + ) + } + pub fn offset_encoding(&self) -> OffsetEncoding { + if supports_utf8(&self.caps) { + OffsetEncoding::Utf8 + } else { + OffsetEncoding::Utf16 + } + } - self.completion.allow_snippets(false); - self.completion.active_resolve_capabilities = - enabled_completions_resolve_capabilities(caps).unwrap_or_default(); - if let Some(completion) = &doc_caps.completion { - if let Some(completion_item) = &completion.completion_item { - if let Some(value) = completion_item.snippet_support { - self.completion.allow_snippets(value); - } - } - } + fn experimental(&self, index: &'static str) -> bool { + try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false) + } + pub fn code_action_group(&self) -> bool { + self.experimental("codeActionGroup") + } + pub fn hover_actions(&self) -> bool { + self.experimental("hoverActions") + } + pub fn status_notification(&self) -> bool { + self.experimental("statusNotification") + } - if let Some(code_action) = &doc_caps.code_action { - if let Some(resolve_support) = &code_action.resolve_support { - if resolve_support.properties.iter().any(|it| it == "edit") { - self.client_caps.code_action_resolve = true; - } - } - } + pub fn publish_diagnostics(&self) -> bool { + self.data.diagnostics_enable + } + pub fn diagnostics(&self) -> DiagnosticsConfig { + DiagnosticsConfig { + disable_experimental: !self.data.diagnostics_enableExperimental, + disabled: self.data.diagnostics_disabled.clone(), } - - if let Some(window_caps) = caps.window.as_ref() { - if let Some(value) = window_caps.work_done_progress { - self.client_caps.work_done_progress = value; - } + } + pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { + DiagnosticsMapConfig { + warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(), + warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(), } - - self.assist.allow_snippets(false); - if let Some(experimental) = &caps.experimental { - let get_bool = - |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true); - - let snippet_text_edit = get_bool("snippetTextEdit"); - self.assist.allow_snippets(snippet_text_edit); - - self.client_caps.code_action_group = get_bool("codeActionGroup"); - self.client_caps.hover_actions = get_bool("hoverActions"); - self.client_caps.status_notification = get_bool("statusNotification"); + } + pub fn lru_capacity(&self) -> Option { + self.data.lruCapacity + } + pub fn proc_macro_srv(&self) -> Option<(PathBuf, Vec)> { + if !self.data.procMacro_enable { + return None; } - if let Some(workspace_caps) = caps.workspace.as_ref() { - if let Some(refresh_support) = - workspace_caps.semantic_tokens.as_ref().and_then(|it| it.refresh_support) - { - self.semantic_tokens_refresh = refresh_support; + let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?; + Some((path, vec!["proc-macro".into()])) + } + pub fn files(&self) -> FilesConfig { + FilesConfig { + watcher: match self.data.files_watcher.as_str() { + "notify" => FilesWatcher::Notify, + "client" | _ => FilesWatcher::Client, + }, + exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(), + } + } + pub fn notifications(&self) -> NotificationsConfig { + NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound } + } + 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.as_ref().map(|rustc_src| { + if rustc_src == "discover" { + RustcSource::Discover + } else { + RustcSource::Path(self.root_path.join(rustc_src)) } - - if let Some(refresh_support) = - workspace_caps.code_lens.as_ref().and_then(|it| it.refresh_support) - { - self.code_lens_refresh = refresh_support; + }); + + CargoConfig { + no_default_features: self.data.cargo_noDefaultFeatures, + all_features: self.data.cargo_allFeatures, + features: self.data.cargo_features.clone(), + target: self.data.cargo_target.clone(), + rustc_source, + no_sysroot: self.data.cargo_noSysroot, + } + } + pub fn rustfmt(&self) -> RustfmtConfig { + match &self.data.rustfmt_overrideCommand { + Some(args) if !args.is_empty() => { + let mut args = args.clone(); + let command = args.remove(0); + RustfmtConfig::CustomCommand { command, args } + } + Some(_) | None => { + RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() } } } } - - pub fn json_schema() -> serde_json::Value { - ConfigData::json_schema() + pub fn flycheck(&self) -> Option { + if !self.data.checkOnSave_enable { + return None; + } + let flycheck_config = match &self.data.checkOnSave_overrideCommand { + Some(args) if !args.is_empty() => { + let mut args = args.clone(); + let command = args.remove(0); + FlycheckConfig::CustomCommand { command, args } + } + Some(_) | None => FlycheckConfig::CargoCommand { + command: self.data.checkOnSave_command.clone(), + target_triple: self + .data + .checkOnSave_target + .clone() + .or_else(|| self.data.cargo_target.clone()), + all_targets: self.data.checkOnSave_allTargets, + no_default_features: self + .data + .checkOnSave_noDefaultFeatures + .unwrap_or(self.data.cargo_noDefaultFeatures), + all_features: self + .data + .checkOnSave_allFeatures + .unwrap_or(self.data.cargo_allFeatures), + features: self + .data + .checkOnSave_features + .clone() + .unwrap_or_else(|| self.data.cargo_features.clone()), + extra_args: self.data.checkOnSave_extraArgs.clone(), + }, + }; + Some(flycheck_config) + } + pub fn runnables(&self) -> RunnablesConfig { + RunnablesConfig { + override_cargo: self.data.runnables_overrideCargo.clone(), + cargo_extra_args: self.data.runnables_cargoExtraArgs.clone(), + } + } + pub fn inlay_hints(&self) -> InlayHintsConfig { + InlayHintsConfig { + type_hints: self.data.inlayHints_typeHints, + parameter_hints: self.data.inlayHints_parameterHints, + chaining_hints: self.data.inlayHints_chainingHints, + max_length: self.data.inlayHints_maxLength, + } + } + 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_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, + insert_use: self.insert_use_config(), + snippet_cap: SnippetCap::new(try_or!( + self.caps + .text_document + .as_ref()? + .completion + .as_ref()? + .completion_item + .as_ref()? + .snippet_support?, + false + )), + } + } + pub fn assist(&self) -> AssistConfig { + AssistConfig { + snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), + allowed: None, + insert_use: self.insert_use_config(), + } + } + pub fn call_info_full(&self) -> bool { + self.data.callInfo_full + } + pub fn lens(&self) -> LensConfig { + LensConfig { + run: self.data.lens_enable && self.data.lens_run, + 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 { + HoverConfig { + implementations: self.data.hoverActions_enable + && self.data.hoverActions_implementations, + run: self.data.hoverActions_enable && self.data.hoverActions_run, + debug: self.data.hoverActions_enable && self.data.hoverActions_debug, + goto_type_def: self.data.hoverActions_enable && self.data.hoverActions_gotoTypeDef, + links_in_hover: self.data.hoverActions_linksInHover, + markdown: try_or!( + self.caps + .text_document + .as_ref()? + .hover + .as_ref()? + .content_format + .as_ref()? + .as_slice(), + &[] + ) + .contains(&MarkupKind::Markdown), + } + } + pub fn semantic_tokens_refresh(&self) -> bool { + try_or!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?, false) + } + pub fn code_lens_refresh(&self) -> bool { + try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false) } } -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Clone)] #[serde(untagged)] enum ManifestOrProjectJson { Manifest(PathBuf), ProjectJson(ProjectJsonData), } -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum MergeBehaviorDef { None, @@ -612,7 +663,7 @@ enum MergeBehaviorDef { Last, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportPrefixDef { Plain, @@ -624,15 +675,21 @@ macro_rules! _config_data { (struct $name:ident { $( $(#[doc=$doc:literal])* - $field:ident: $ty:ty = $default:expr, + $field:ident $(| $alias:ident)?: $ty:ty = $default:expr, )* }) => { #[allow(non_snake_case)] + #[derive(Debug, Clone)] struct $name { $($field: $ty,)* } impl $name { fn from_json(mut json: serde_json::Value) -> $name { $name {$( - $field: get_field(&mut json, stringify!($field), $default), + $field: get_field( + &mut json, + stringify!($field), + None$(.or(Some(stringify!($alias))))?, + $default, + ), )*} } @@ -664,21 +721,28 @@ fn manual() -> String { fn get_field( json: &mut serde_json::Value, field: &'static str, + alias: Option<&'static str>, default: &str, ) -> T { let default = serde_json::from_str(default).unwrap(); - let mut pointer = field.replace('_', "/"); - pointer.insert(0, '/'); - json.pointer_mut(&pointer) - .and_then(|it| serde_json::from_value(it.take()).ok()) + // XXX: check alias first, to work-around the VS Code where it pre-fills the + // defaults instead of sending an empty object. + alias + .into_iter() + .chain(iter::once(field)) + .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()) + }) .unwrap_or(default) } 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); } @@ -721,6 +785,10 @@ macro_rules! set { "type": "array", "items": { "type": "string" }, }, + "Vec" => set! { + "type": "array", + "items": { "type": "string" }, + }, "FxHashSet" => set! { "type": "array", "items": { "type": "string" }, @@ -733,6 +801,9 @@ macro_rules! set { "Option" => set! { "type": ["null", "string"], }, + "Option" => set! { + "type": ["null", "string"], + }, "Option" => set! { "type": ["null", "boolean"], }, @@ -795,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]