//! 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<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",
/// 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\"",
/// 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.
/// 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).
+ procMacro_server: Option<PathBuf> = "null",
/// Command to be executed instead of 'cargo' for runnables.
runnables_overrideCargo: Option<String> = "null",
/// 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.
+ /// 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`.
}
}
+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<usize>,
- pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
- pub files: FilesConfig,
- pub notifications: NotificationsConfig,
-
- pub cargo_autoreload: bool,
- pub cargo: CargoConfig,
- pub rustfmt: RustfmtConfig,
- pub flycheck: Option<FlycheckConfig>,
- 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<LinkedProject>,
+ caps: lsp_types::ClientCapabilities,
+ data: ConfigData,
+ pub discovered_projects: Option<Vec<ProjectManifest>>,
pub root_path: AbsPathBuf,
}
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 {
}
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)]
}
/// 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<String>,
pub cargo_extra_args: Vec<String>,
}
-#[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<LinkedProject> {
+ 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<usize> {
+ self.data.lruCapacity
+ }
+ pub fn proc_macro_srv(&self) -> Option<(PathBuf, Vec<OsString>)> {
+ 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<FlycheckConfig> {
+ 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,
Last,
}
-#[derive(Deserialize)]
+#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum ImportPrefixDef {
Plain,
(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,
+ ),
)*}
}
fn get_field<T: DeserializeOwned>(
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);
}
"type": "array",
"items": { "type": "string" },
},
+ "Vec<PathBuf>" => set! {
+ "type": "array",
+ "items": { "type": "string" },
+ },
"FxHashSet<String>" => set! {
"type": "array",
"items": { "type": "string" },
"Option<String>" => set! {
"type": ["null", "string"],
},
+ "Option<PathBuf>" => set! {
+ "type": ["null", "string"],
+ },
"Option<bool>" => set! {
"type": ["null", "boolean"],
},
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]