diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 13494a731b7..b54a30ab85f 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -22,7 +22,7 @@ use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; pub use crate::conv::url_from_path_with_drive_lowercasing; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { CargoCommand { command: String, all_targets: bool, extra_args: Vec }, CustomCommand { command: String, args: Vec }, diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index f4fd6ad28cf..c1b6e1ddc38 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs @@ -13,7 +13,6 @@ use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId} use ra_arena::{Arena, Idx}; use ra_db::Edition; use rustc_hash::FxHashMap; -use serde::Deserialize; /// `CargoWorkspace` represents the logical structure of, well, a Cargo /// workspace. It pretty closely mirrors `cargo metadata` output. @@ -43,9 +42,8 @@ impl ops::Index for CargoWorkspace { } } -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase", default)] -pub struct CargoFeatures { +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CargoConfig { /// Do not activate the `default` feature. pub no_default_features: bool, @@ -60,9 +58,9 @@ pub struct CargoFeatures { pub load_out_dirs_from_check: bool, } -impl Default for CargoFeatures { +impl Default for CargoConfig { fn default() -> Self { - CargoFeatures { + CargoConfig { no_default_features: false, all_features: true, features: Vec::new(), @@ -141,7 +139,7 @@ impl PackageData { impl CargoWorkspace { pub fn from_cargo_metadata( cargo_toml: &Path, - cargo_features: &CargoFeatures, + cargo_features: &CargoConfig, ) -> Result { let mut meta = MetadataCommand::new(); meta.manifest_path(cargo_toml); @@ -275,7 +273,7 @@ pub struct ExternResources { pub fn load_extern_resources( cargo_toml: &Path, - cargo_features: &CargoFeatures, + cargo_features: &CargoConfig, ) -> Result { let mut cmd = Command::new(cargo_binary()); cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); @@ -293,9 +291,8 @@ pub fn load_extern_resources( let mut res = ExternResources::default(); - let stdout = String::from_utf8(output.stdout)?; - for line in stdout.lines() { - if let Ok(message) = serde_json::from_str::(&line) { + for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { + if let Ok(message) = message { match message { Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { res.out_dirs.insert(package_id, out_dir); diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 444d3bb3f05..dd9c80691f3 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -19,7 +19,7 @@ use rustc_hash::FxHashMap; use serde_json::from_reader; pub use crate::{ - cargo_workspace::{CargoFeatures, CargoWorkspace, Package, Target, TargetKind}, + cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, json_project::JsonProject, sysroot::Sysroot, }; @@ -78,14 +78,14 @@ impl PackageRoot { } impl ProjectWorkspace { - pub fn discover(path: &Path, cargo_features: &CargoFeatures) -> Result { + pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result { ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) } pub fn discover_with_sysroot( path: &Path, with_sysroot: bool, - cargo_features: &CargoFeatures, + cargo_features: &CargoConfig, ) -> Result { match find_rust_project_json(path) { Some(json_path) => { diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index a744a6695dd..608f4f67b2c 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -4,8 +4,7 @@ mod args; use lsp_server::Connection; - -use rust_analyzer::{cli, from_json, show_message, Result, ServerConfig}; +use rust_analyzer::{cli, config::Config, from_json, Result}; use crate::args::HelpPrinted; @@ -78,24 +77,18 @@ fn run_server() -> Result<()> { .filter(|workspaces| !workspaces.is_empty()) .unwrap_or_else(|| vec![root]); - let server_config = initialize_params - .initialization_options - .and_then(|v| { - from_json::("config", v) - .map_err(|e| { - log::error!("{}", e); - show_message(lsp_types::MessageType::Error, e.to_string(), &connection.sender); - }) - .ok() - }) - .unwrap_or_default(); + let config = { + let mut config = Config::default(); + if let Some(value) = &initialize_params.initialization_options { + config.update(value); + } + if let Some(caps) = &initialize_params.capabilities.text_document { + config.update_caps(caps); + } + config + }; - rust_analyzer::main_loop( - workspace_roots, - initialize_params.capabilities, - server_config, - connection, - )?; + rust_analyzer::main_loop(workspace_roots, config, connection)?; log::info!("shutting down IO..."); io_threads.join()?; diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 832f04226c3..2c0bde920be 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver}; use ra_db::{ExternSourceId, FileId, SourceRootId}; use ra_ide::{AnalysisChange, AnalysisHost}; use ra_project_model::{ - get_rustc_cfg_options, CargoFeatures, PackageRoot, ProcMacroClient, ProjectWorkspace, + get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, }; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -29,7 +29,7 @@ pub(crate) fn load_cargo( let root = std::env::current_dir()?.join(root); let ws = ProjectWorkspace::discover( root.as_ref(), - &CargoFeatures { load_out_dirs_from_check, ..Default::default() }, + &CargoConfig { load_out_dirs_from_check, ..Default::default() }, )?; let mut extern_dirs = FxHashSet::default(); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index fb7895ce078..3c8f55f1e49 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -7,24 +7,36 @@ //! configure the server itself, feature flags are passed into analysis, and //! tweak things like automatic insertion of `()` in completions. -use rustc_hash::FxHashMap; - use lsp_types::TextDocumentClientCapabilities; use ra_flycheck::FlycheckConfig; -use ra_ide::InlayHintsConfig; -use ra_project_model::CargoFeatures; -use serde::{Deserialize, Deserializer}; +use ra_ide::{CompletionConfig, InlayHintsConfig}; +use ra_project_model::CargoConfig; +use serde::Deserialize; #[derive(Debug, Clone)] pub struct Config { + pub client_caps: ClientCapsConfig, pub publish_decorations: bool, - pub supports_location_link: bool, - pub line_folding_only: bool, + pub publish_diagnostics: bool, + pub notifications: NotificationsConfig, pub inlay_hints: InlayHintsConfig, + pub completion: CompletionConfig, + pub call_info_full: bool, pub rustfmt: RustfmtConfig, pub check: Option, pub vscode_lldb: bool, pub proc_macro_srv: Option, + pub lru_capacity: Option, + pub use_client_watching: bool, + pub exclude_globs: Vec, + pub cargo: CargoConfig, + pub with_sysroot: bool, +} + +#[derive(Debug, Clone)] +pub struct NotificationsConfig { + pub workspace_loaded: bool, + pub cargo_toml_not_found: bool, } #[derive(Debug, Clone)] @@ -39,148 +51,120 @@ pub enum RustfmtConfig { }, } -impl Default for RustfmtConfig { +#[derive(Debug, Clone, Default)] +pub struct ClientCapsConfig { + pub location_link: bool, + pub line_folding_only: bool, +} + +impl Default for Config { fn default() -> Self { - RustfmtConfig::Rustfmt { extra_args: Vec::new() } - } -} - -pub(crate) fn get_config( - config: &ServerConfig, - text_document_caps: Option<&TextDocumentClientCapabilities>, -) -> Config { - Config { - publish_decorations: config.publish_decorations, - supports_location_link: text_document_caps - .and_then(|it| it.definition) - .and_then(|it| it.link_support) - .unwrap_or(false), - line_folding_only: text_document_caps - .and_then(|it| it.folding_range.as_ref()) - .and_then(|it| it.line_folding_only) - .unwrap_or(false), - inlay_hints: InlayHintsConfig { - type_hints: config.inlay_hints_type, - parameter_hints: config.inlay_hints_parameter, - chaining_hints: config.inlay_hints_chaining, - max_length: config.inlay_hints_max_length, - }, - check: if config.cargo_watch_enable { - Some(FlycheckConfig::CargoCommand { - command: config.cargo_watch_command.clone(), - all_targets: config.cargo_watch_all_targets, - extra_args: config.cargo_watch_args.clone(), - }) - } else { - None - }, - rustfmt: RustfmtConfig::Rustfmt { extra_args: config.rustfmt_args.clone() }, - vscode_lldb: config.vscode_lldb, - proc_macro_srv: None, // FIXME: get this from config - } -} - -/// Client provided initialization options -#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase", default)] -pub struct ServerConfig { - /// Whether the client supports our custom highlighting publishing decorations. - /// This is different to the highlightingOn setting, which is whether the user - /// wants our custom highlighting to be used. - /// - /// Defaults to `false` - #[serde(deserialize_with = "nullable_bool_false")] - pub publish_decorations: bool, - - pub exclude_globs: Vec, - #[serde(deserialize_with = "nullable_bool_false")] - pub use_client_watching: bool, - - pub lru_capacity: Option, - - #[serde(deserialize_with = "nullable_bool_true")] - pub inlay_hints_type: bool, - #[serde(deserialize_with = "nullable_bool_true")] - pub inlay_hints_parameter: bool, - #[serde(deserialize_with = "nullable_bool_true")] - pub inlay_hints_chaining: bool, - pub inlay_hints_max_length: Option, - - pub cargo_watch_enable: bool, - pub cargo_watch_args: Vec, - pub cargo_watch_command: String, - pub cargo_watch_all_targets: bool, - - /// For internal usage to make integrated tests faster. - #[serde(deserialize_with = "nullable_bool_true")] - pub with_sysroot: bool, - - /// Fine grained feature flags to disable specific features. - pub feature_flags: FxHashMap, - - pub rustfmt_args: Vec, - - /// Cargo feature configurations. - pub cargo_features: CargoFeatures, - - /// Enabled if the vscode_lldb extension is available. - pub vscode_lldb: bool, -} - -impl Default for ServerConfig { - fn default() -> ServerConfig { - ServerConfig { + Config { publish_decorations: false, - exclude_globs: Vec::new(), - use_client_watching: false, - lru_capacity: None, - inlay_hints_type: true, - inlay_hints_parameter: true, - inlay_hints_chaining: true, - inlay_hints_max_length: None, - cargo_watch_enable: true, - cargo_watch_args: Vec::new(), - cargo_watch_command: "check".to_string(), - cargo_watch_all_targets: true, - with_sysroot: true, - feature_flags: FxHashMap::default(), - cargo_features: Default::default(), - rustfmt_args: Vec::new(), + publish_diagnostics: true, + notifications: NotificationsConfig { + workspace_loaded: true, + cargo_toml_not_found: true, + }, + client_caps: ClientCapsConfig::default(), + inlay_hints: InlayHintsConfig { + type_hints: true, + parameter_hints: true, + chaining_hints: true, + max_length: None, + }, + completion: CompletionConfig { + enable_postfix_completions: true, + add_call_parenthesis: true, + add_call_argument_snippets: true, + }, + call_info_full: true, + rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, + check: Some(FlycheckConfig::CargoCommand { + command: "check".to_string(), + all_targets: true, + extra_args: Vec::new(), + }), vscode_lldb: false, + proc_macro_srv: None, + lru_capacity: None, + use_client_watching: false, + exclude_globs: Vec::new(), + cargo: CargoConfig::default(), + with_sysroot: true, } } } -/// Deserializes a null value to a bool false by default -fn nullable_bool_false<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let opt = Option::deserialize(deserializer)?; - Ok(opt.unwrap_or(false)) -} +impl Config { + #[rustfmt::skip] + pub fn update(&mut self, value: &serde_json::Value) { + log::info!("Config::update({:#})", value); -/// Deserializes a null value to a bool true by default -fn nullable_bool_true<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let opt = Option::deserialize(deserializer)?; - Ok(opt.unwrap_or(true)) -} + let client_caps = self.client_caps.clone(); + *self = Default::default(); + self.client_caps = client_caps; -#[cfg(test)] -mod test { - use super::*; + set(value, "/publishDecorations", &mut self.publish_decorations); + set(value, "/excludeGlobs", &mut self.exclude_globs); + set(value, "/useClientWatching", &mut self.use_client_watching); + set(value, "/lruCapacity", &mut self.lru_capacity); - #[test] - fn deserialize_init_options_defaults() { - // check that null == default for both fields - let default = ServerConfig::default(); - assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap()); - assert_eq!( - default, - serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap() - ); + set(value, "/inlayHintsType", &mut self.inlay_hints.type_hints); + set(value, "/inlayHintsParameter", &mut self.inlay_hints.parameter_hints); + set(value, "/inlayHintsChaining", &mut self.inlay_hints.chaining_hints); + set(value, "/inlayHintsMaxLength", &mut self.inlay_hints.max_length); + + if let Some(false) = get(value, "cargo_watch_enable") { + self.check = None + } else { + if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check + { + set(value, "/cargoWatchArgs", extra_args); + set(value, "/cargoWatchCommand", command); + set(value, "/cargoWatchAllTargets", all_targets); + } + }; + + set(value, "/withSysroot", &mut self.with_sysroot); + if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt { + set(value, "/rustfmtArgs", extra_args); + } + + set(value, "/cargoFeatures/noDefaultFeatures", &mut self.cargo.no_default_features); + set(value, "/cargoFeatures/allFeatures", &mut self.cargo.all_features); + set(value, "/cargoFeatures/features", &mut self.cargo.features); + set(value, "/cargoFeatures/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check); + + set(value, "/vscodeLldb", &mut self.vscode_lldb); + + set(value, "/featureFlags/lsp.diagnostics", &mut self.publish_diagnostics); + set(value, "/featureFlags/notifications.workspace-loaded", &mut self.notifications.workspace_loaded); + set(value, "/featureFlags/notifications.cargo-toml-not-found", &mut self.notifications.cargo_toml_not_found); + set(value, "/featureFlags/completion.enable-postfix", &mut self.completion.enable_postfix_completions); + set(value, "/featureFlags/completion.insertion.add-call-parenthesis", &mut self.completion.add_call_parenthesis); + set(value, "/featureFlags/completion.insertion.add-argument-snippets", &mut self.completion.add_call_argument_snippets); + set(value, "/featureFlags/call-info.full", &mut self.call_info_full); + + log::info!("Config::update() = {:#?}", self); + + fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option { + value.pointer(pointer).and_then(|it| T::deserialize(it).ok()) + } + + fn set<'a, T: Deserialize<'a> + std::fmt::Debug>(value: &'a serde_json::Value, pointer: &str, slot: &mut T) { + if let Some(new_value) = get(value, pointer) { + *slot = new_value + } + } + } + + pub fn update_caps(&mut self, caps: &TextDocumentClientCapabilities) { + if let Some(value) = caps.definition.as_ref().and_then(|it| it.link_support) { + self.client_caps.location_link = value; + } + if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) { + self.client_caps.line_folding_only = value + } } } diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs index e8dc953c380..57c4c8ce55c 100644 --- a/crates/rust-analyzer/src/conv.rs +++ b/crates/rust-analyzer/src/conv.rs @@ -579,7 +579,7 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo>) .into_iter() .map(|nav| (file_id, RangeInfo::new(range, nav))) .try_conv_with_to_vec(world)?; - if world.config.supports_location_link { + if world.config.client_caps.location_link { Ok(links.into()) } else { let locations: Vec = links diff --git a/crates/rust-analyzer/src/feature_flags.rs b/crates/rust-analyzer/src/feature_flags.rs deleted file mode 100644 index dbb3f50a095..00000000000 --- a/crates/rust-analyzer/src/feature_flags.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! See docs for `FeatureFlags`. - -use rustc_hash::FxHashMap; - -// FIXME: looks like a much better design is to pass options to each call, -// rather than to have a global ambient feature flags -- that way, the clients -// can issue two successive calls with different options. - -/// Feature flags hold fine-grained toggles for all *user-visible* features of -/// rust-analyzer. -/// -/// The exists such that users are able to disable any annoying feature (and, -/// with many users and many features, some features are bound to be annoying -/// for some users) -/// -/// Note that we purposefully use run-time checked strings, and not something -/// checked at compile time, to keep things simple and flexible. -/// -/// Also note that, at the moment, `FeatureFlags` also store features for -/// `rust-analyzer`. This should be benign layering violation. -#[derive(Debug)] -pub struct FeatureFlags { - flags: FxHashMap, -} - -impl FeatureFlags { - fn new(flags: &[(&str, bool)]) -> FeatureFlags { - let flags = flags - .iter() - .map(|&(name, value)| { - check_flag_name(name); - (name.to_string(), value) - }) - .collect(); - FeatureFlags { flags } - } - - pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> { - match self.flags.get_mut(flag) { - None => Err(()), - Some(slot) => { - *slot = value; - Ok(()) - } - } - } - - pub fn get(&self, flag: &str) -> bool { - match self.flags.get(flag) { - None => panic!("unknown flag: {:?}", flag), - Some(value) => *value, - } - } -} - -impl Default for FeatureFlags { - fn default() -> FeatureFlags { - FeatureFlags::new(&[ - ("lsp.diagnostics", true), - ("completion.insertion.add-call-parenthesis", true), - ("completion.insertion.add-argument-snippets", true), - ("completion.enable-postfix", true), - ("call-info.full", true), - ("notifications.workspace-loaded", true), - ("notifications.cargo-toml-not-found", true), - ]) - } -} - -fn check_flag_name(flag: &str) { - for c in flag.bytes() { - match c { - b'a'..=b'z' | b'-' | b'.' => (), - _ => panic!("flag name does not match conventions: {:?}", flag), - } - } -} diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index e50e47b195b..02953be3039 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -33,18 +33,16 @@ mod conv; mod main_loop; mod markdown; pub mod req; -mod config; +pub mod config; mod world; mod diagnostics; mod semantic_tokens; -mod feature_flags; use serde::de::DeserializeOwned; pub type Result = std::result::Result>; pub use crate::{ caps::server_capabilities, - config::ServerConfig, main_loop::LspError, main_loop::{main_loop, show_message}, }; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index de40e2ac2c8..45ae0ad9d17 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -17,9 +17,8 @@ use std::{ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ - ClientCapabilities, NumberOrString, TextDocumentClientCapabilities, WorkDoneProgress, - WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, - WorkDoneProgressReport, + NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, + WorkDoneProgressEnd, WorkDoneProgressReport, }; use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; @@ -31,16 +30,15 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ - config::get_config, + config::Config, diagnostics::DiagnosticTask, - feature_flags::FeatureFlags, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, }, req, world::{WorldSnapshot, WorldState}, - Result, ServerConfig, + Result, }; use req::ConfigurationParams; @@ -66,29 +64,8 @@ impl fmt::Display for LspError { impl Error for LspError {} -fn get_feature_flags(config: &ServerConfig, connection: &Connection) -> FeatureFlags { - let mut ff = FeatureFlags::default(); - for (flag, &value) in &config.feature_flags { - if ff.set(flag.as_str(), value).is_err() { - log::error!("unknown feature flag: {:?}", flag); - show_message( - req::MessageType::Error, - format!("unknown feature flag: {:?}", flag), - &connection.sender, - ); - } - } - log::info!("feature_flags: {:#?}", ff); - ff -} - -pub fn main_loop( - ws_roots: Vec, - client_caps: ClientCapabilities, - config: ServerConfig, - connection: Connection, -) -> Result<()> { - log::info!("server_config: {:#?}", config); +pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) -> Result<()> { + log::info!("initial config: {:#?}", config); // Windows scheduler implements priority boosts: if thread waits for an // event (like a condvar), and event fires, priority of the thread is @@ -109,11 +86,8 @@ pub fn main_loop( SetThreadPriority(thread, thread_priority_above_normal); } - let text_document_caps = client_caps.text_document.as_ref(); let mut loop_state = LoopState::default(); let mut world_state = { - let feature_flags = get_feature_flags(&config, &connection); - // FIXME: support dynamic workspace loading. let workspaces = { let mut loaded_workspaces = Vec::new(); @@ -121,7 +95,7 @@ pub fn main_loop( let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( ws_root.as_path(), config.with_sysroot, - &config.cargo_features, + &config.cargo, ); match workspace { Ok(workspace) => loaded_workspaces.push(workspace), @@ -131,7 +105,7 @@ pub fn main_loop( if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = e.downcast_ref() { - if !feature_flags.get("notifications.cargo-toml-not-found") { + if !config.notifications.cargo_toml_not_found { continue; } } @@ -180,8 +154,7 @@ pub fn main_loop( config.lru_capacity, &globs, Watch(!config.use_client_watching), - get_config(&config, text_document_caps), - feature_flags, + config, ) }; @@ -224,7 +197,6 @@ pub fn main_loop( &task_sender, &libdata_sender, &connection, - text_document_caps, &mut world_state, &mut loop_state, event, @@ -335,7 +307,6 @@ fn loop_turn( task_sender: &Sender, libdata_sender: &Sender, connection: &Connection, - text_document_caps: Option<&TextDocumentClientCapabilities>, world_state: &mut WorldState, loop_state: &mut LoopState, event: Event, @@ -389,28 +360,16 @@ fn loop_turn( log::debug!("config update response: '{:?}", resp); let Response { error, result, .. } = resp; - match ( - error, - result.map(|result| serde_json::from_value::>(result)), - ) { + match (error, result) { (Some(err), _) => { log::error!("failed to fetch the server settings: {:?}", err) } - (None, Some(Ok(new_config))) => { - let new_config = new_config - .first() - .expect( - "the client is expected to always send a non-empty config data", - ) - .to_owned(); - world_state.update_configuration( - new_config.lru_capacity, - get_config(&new_config, text_document_caps), - get_feature_flags(&new_config, connection), - ); - } - (None, Some(Err(e))) => { - log::error!("failed to parse client config response: {}", e) + (None, Some(configs)) => { + if let Some(new_config) = configs.get(0) { + let mut config = world_state.config.clone(); + config.update(&new_config); + world_state.update_configuration(config); + } } (None, None) => { log::error!("received empty server settings response from the client") @@ -441,8 +400,8 @@ fn loop_turn( }); } - let show_progress = !loop_state.workspace_loaded - && world_state.feature_flags.get("notifications.workspace-loaded"); + let show_progress = + !loop_state.workspace_loaded && world_state.config.notifications.workspace_loaded; if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total @@ -930,7 +889,7 @@ fn update_file_notifications_on_threadpool( subscriptions: Vec, ) { log::trace!("updating notifications for {:?}", subscriptions); - let publish_diagnostics = world.feature_flags.get("lsp.diagnostics"); + let publish_diagnostics = world.config.publish_diagnostics; pool.execute(move || { for file_id in subscriptions { if publish_diagnostics { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index bb99b38a8e7..23e48c08966 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -19,8 +19,8 @@ use lsp_types::{ TextEdit, WorkspaceEdit, }; use ra_ide::{ - Assist, AssistId, CompletionConfig, FileId, FilePosition, FileRange, Query, RangeInfo, - Runnable, RunnableKind, SearchScope, + Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, + SearchScope, }; use ra_prof::profile; use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; @@ -426,15 +426,7 @@ pub fn handle_completion( return Ok(None); } - let config = CompletionConfig { - enable_postfix_completions: world.feature_flags.get("completion.enable-postfix"), - add_call_parenthesis: world.feature_flags.get("completion.insertion.add-call-parenthesis"), - add_call_argument_snippets: world - .feature_flags - .get("completion.insertion.add-argument-snippets"), - }; - - let items = match world.analysis().completions(position, &config)? { + let items = match world.analysis().completions(position, &world.config.completion)? { None => return Ok(None), Some(items) => items, }; @@ -458,7 +450,7 @@ pub fn handle_folding_range( let ctx = FoldConvCtx { text: &text, line_index: &line_index, - line_folding_only: world.config.line_folding_only, + line_folding_only: world.config.client_caps.line_folding_only, }; let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); Ok(res) @@ -471,7 +463,7 @@ pub fn handle_signature_help( let _p = profile("handle_signature_help"); let position = params.try_conv_with(&world)?; if let Some(call_info) = world.analysis().call_info(position)? { - let concise = !world.feature_flags.get("call-info.full"); + let concise = !world.config.call_info_full; let mut active_parameter = call_info.active_parameter.map(|it| it as i64); if concise && call_info.signature.has_self_param { active_parameter = active_parameter.map(|it| it.saturating_sub(1)); diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs index 124de4d8ec7..5674f42ef3f 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/world.rs @@ -11,7 +11,7 @@ use std::{ use crossbeam_channel::{unbounded, Receiver}; use lsp_types::Url; use parking_lot::RwLock; -use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck}; +use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig}; use ra_ide::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, }; @@ -23,7 +23,6 @@ use stdx::format_to; use crate::{ config::Config, diagnostics::{CheckFixes, DiagnosticCollection}, - feature_flags::FeatureFlags, main_loop::pending_requests::{CompletedRequest, LatestRequests}, vfs_glob::{Glob, RustPackageFilterBuilder}, LspError, Result, @@ -31,9 +30,7 @@ use crate::{ use ra_db::ExternSourceId; use rustc_hash::{FxHashMap, FxHashSet}; -fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option { - let check_config = config.check.as_ref()?; - +fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option { // FIXME: Figure out the multi-workspace situation workspaces .iter() @@ -43,7 +40,7 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option Option, pub roots: Vec, pub workspaces: Arc>, pub analysis_host: AnalysisHost, @@ -73,7 +69,6 @@ pub struct WorldState { /// An immutable snapshot of the world's state at a point in time. pub struct WorldSnapshot { pub config: Config, - pub feature_flags: Arc, pub workspaces: Arc>, pub analysis: Analysis, pub latest_requests: Arc>, @@ -89,7 +84,6 @@ impl WorldState { exclude_globs: &[Glob], watch: Watch, config: Config, - feature_flags: FeatureFlags, ) -> WorldState { let mut change = AnalysisChange::new(); @@ -191,13 +185,12 @@ impl WorldState { }); change.set_crate_graph(crate_graph); - let flycheck = create_flycheck(&workspaces, &config); + let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); let mut analysis_host = AnalysisHost::new(lru_capacity); analysis_host.apply_change(change); WorldState { config: config, - feature_flags: Arc::new(feature_flags), roots: folder_roots, workspaces: Arc::new(workspaces), analysis_host, @@ -209,15 +202,13 @@ impl WorldState { } } - pub fn update_configuration( - &mut self, - lru_capacity: Option, - config: Config, - feature_flags: FeatureFlags, - ) { - self.feature_flags = Arc::new(feature_flags); - self.analysis_host.update_lru_capacity(lru_capacity); - self.flycheck = create_flycheck(&self.workspaces, &config); + pub fn update_configuration(&mut self, config: Config) { + self.analysis_host.update_lru_capacity(config.lru_capacity); + if config.check != self.config.check { + self.flycheck = + config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); + } + self.config = config; } @@ -275,7 +266,6 @@ impl WorldState { pub fn snapshot(&self) -> WorldSnapshot { WorldSnapshot { config: self.config.clone(), - feature_flags: Arc::clone(&self.feature_flags), workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(), vfs: Arc::clone(&self.vfs), diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 5af5eaad209..63881331123 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -615,7 +615,7 @@ fn main() { message(); } "###, ) .with_config(|config| { - config.cargo_features.load_out_dirs_from_check = true; + config.cargo.load_out_dirs_from_check = true; }) .server(); server.wait_until_workspace_is_loaded(); diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index d8bed6d7f41..7eebedff7ad 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs @@ -11,8 +11,7 @@ use lsp_server::{Connection, Message, Notification, Request}; use lsp_types::{ notification::{DidOpenTextDocument, Exit}, request::Shutdown, - ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, - TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress, + DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress, }; use serde::Serialize; use serde_json::{to_string_pretty, Value}; @@ -20,14 +19,17 @@ use tempfile::TempDir; use test_utils::{find_mismatch, parse_fixture}; use req::{ProgressParams, ProgressParamsValue}; -use rust_analyzer::{main_loop, req, ServerConfig}; +use rust_analyzer::{ + config::{ClientCapsConfig, Config}, + main_loop, req, +}; pub struct Project<'a> { fixture: &'a str, with_sysroot: bool, tmp_dir: Option, roots: Vec, - config: Option>, + config: Option>, } impl<'a> Project<'a> { @@ -50,7 +52,7 @@ impl<'a> Project<'a> { self } - pub fn with_config(mut self, config: impl Fn(&mut ServerConfig) + 'static) -> Project<'a> { + pub fn with_config(mut self, config: impl Fn(&mut Config) + 'static) -> Project<'a> { self.config = Some(Box::new(config)); self } @@ -78,8 +80,11 @@ impl<'a> Project<'a> { let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); - let mut config = - ServerConfig { with_sysroot: self.with_sysroot, ..ServerConfig::default() }; + let mut config = Config { + client_caps: ClientCapsConfig { location_link: true, ..Default::default() }, + with_sysroot: self.with_sysroot, + ..Config::default() + }; if let Some(f) = &self.config { f(&mut config) @@ -105,7 +110,7 @@ pub struct Server { impl Server { fn new( dir: TempDir, - config: ServerConfig, + config: Config, roots: Vec, files: Vec<(PathBuf, String)>, ) -> Server { @@ -116,26 +121,7 @@ impl Server { let _thread = jod_thread::Builder::new() .name("test server".to_string()) - .spawn(move || { - main_loop( - roots, - ClientCapabilities { - workspace: None, - text_document: Some(TextDocumentClientCapabilities { - definition: Some(GotoCapability { - dynamic_registration: None, - link_support: Some(true), - }), - ..Default::default() - }), - window: None, - experimental: None, - }, - config, - connection, - ) - .unwrap() - }) + .spawn(move || main_loop(roots, config, connection).unwrap()) .expect("failed to spawn a thread"); let res =