From 69bbe79c5037eb3cd00744593d1836e45a6f56e1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 22 Aug 2019 14:44:16 +0300 Subject: implement feature flags --- crates/ra_batch/src/lib.rs | 4 +- crates/ra_ide_api/src/completion/presentation.rs | 5 +- crates/ra_ide_api/src/db.rs | 9 ++-- crates/ra_ide_api/src/feature_flags.rs | 67 ++++++++++++++++++++++++ crates/ra_ide_api/src/lib.rs | 16 ++++-- crates/ra_lsp_server/src/config.rs | 13 +++-- crates/ra_lsp_server/src/main_loop.rs | 42 ++++++++++----- crates/ra_lsp_server/src/world.rs | 15 ++++-- editors/code/package.json | 8 +-- editors/code/src/config.ts | 4 ++ editors/code/src/server.ts | 3 +- 11 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 crates/ra_ide_api/src/feature_flags.rs diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs index 0db751465..f458ea300 100644 --- a/crates/ra_batch/src/lib.rs +++ b/crates/ra_batch/src/lib.rs @@ -3,7 +3,7 @@ use std::{collections::HashSet, error::Error, path::Path}; use rustc_hash::FxHashMap; use ra_db::{CrateGraph, FileId, SourceRootId}; -use ra_ide_api::{AnalysisChange, AnalysisHost}; +use ra_ide_api::{AnalysisChange, AnalysisHost, FeatureFlags}; use ra_project_model::{PackageRoot, ProjectWorkspace}; use ra_vfs::{RootEntry, Vfs, VfsChange}; use ra_vfs_glob::RustPackageFilterBuilder; @@ -63,7 +63,7 @@ pub fn load( vfs: &mut Vfs, ) -> AnalysisHost { let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::().ok()); - let mut host = AnalysisHost::new(lru_cap); + let mut host = AnalysisHost::new(lru_cap, FeatureFlags::default()); let mut analysis_change = AnalysisChange::new(); analysis_change.set_crate_graph(crate_graph); diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index 6878008d3..2b3f98482 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -118,7 +118,10 @@ impl Completions { .set_documentation(func.docs(ctx.db)) .detail(detail); // If not an import, add parenthesis automatically. - if ctx.use_item_syntax.is_none() && !ctx.is_call { + if ctx.use_item_syntax.is_none() + && !ctx.is_call + && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") + { tested_by!(inserts_parens_for_function_calls); let snippet = if data.params().is_empty() || data.has_self_param() && data.params().len() == 1 { diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs index fc8252e4b..f2e6b8f12 100644 --- a/crates/ra_ide_api/src/db.rs +++ b/crates/ra_ide_api/src/db.rs @@ -7,7 +7,7 @@ use ra_db::{ use crate::{ symbol_index::{self, SymbolsDatabase}, - LineIndex, + FeatureFlags, LineIndex, }; #[salsa::database( @@ -22,6 +22,7 @@ use crate::{ #[derive(Debug)] pub(crate) struct RootDatabase { runtime: salsa::Runtime, + pub(crate) feature_flags: Arc, pub(crate) last_gc: time::Instant, pub(crate) last_gc_check: time::Instant, } @@ -46,16 +47,17 @@ impl salsa::Database for RootDatabase { impl Default for RootDatabase { fn default() -> RootDatabase { - RootDatabase::new(None) + RootDatabase::new(None, FeatureFlags::default()) } } impl RootDatabase { - pub fn new(lru_capacity: Option) -> RootDatabase { + pub fn new(lru_capacity: Option, feature_flags: FeatureFlags) -> RootDatabase { let mut db = RootDatabase { runtime: salsa::Runtime::default(), last_gc: time::Instant::now(), last_gc_check: time::Instant::now(), + feature_flags: Arc::new(feature_flags), }; db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); db.set_local_roots_with_durability(Default::default(), Durability::HIGH); @@ -74,6 +76,7 @@ impl salsa::ParallelDatabase for RootDatabase { runtime: self.runtime.snapshot(self), last_gc: self.last_gc, last_gc_check: self.last_gc_check, + feature_flags: Arc::clone(&self.feature_flags), }) } } diff --git a/crates/ra_ide_api/src/feature_flags.rs b/crates/ra_ide_api/src/feature_flags.rs new file mode 100644 index 000000000..9f82ac71c --- /dev/null +++ b/crates/ra_ide_api/src/feature_flags.rs @@ -0,0 +1,67 @@ +use rustc_hash::FxHashMap; + +/// 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 +/// `ra_lsp_server`. 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), + ("notifications.workspace-loaded", 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/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index fa4ae4379..514dcaf96 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -14,6 +14,7 @@ mod db; pub mod mock_analysis; mod symbol_index; mod change; +mod feature_flags; mod status; mod completion; @@ -63,6 +64,7 @@ pub use crate::{ completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, diagnostics::Severity, display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, + feature_flags::FeatureFlags, folding_ranges::{Fold, FoldKind}, hover::HoverResult, inlay_hints::{InlayHint, InlayKind}, @@ -247,13 +249,13 @@ pub struct AnalysisHost { impl Default for AnalysisHost { fn default() -> AnalysisHost { - AnalysisHost::new(None) + AnalysisHost::new(None, FeatureFlags::default()) } } impl AnalysisHost { - pub fn new(lru_capcity: Option) -> AnalysisHost { - AnalysisHost { db: db::RootDatabase::new(lru_capcity) } + pub fn new(lru_capcity: Option, feature_flags: FeatureFlags) -> AnalysisHost { + AnalysisHost { db: db::RootDatabase::new(lru_capcity, feature_flags) } } /// Returns a snapshot of the current state, which you can query for /// semantic information. @@ -261,6 +263,10 @@ impl AnalysisHost { Analysis { db: self.db.snapshot() } } + pub fn feature_flags(&self) -> &FeatureFlags { + &self.db.feature_flags + } + /// Applies changes to the current state of the world. If there are /// outstanding snapshots, they will be canceled. pub fn apply_change(&mut self, change: AnalysisChange) { @@ -319,6 +325,10 @@ impl Analysis { (host.analysis(), file_id) } + pub fn feature_flags(&self) -> &FeatureFlags { + &self.db.feature_flags + } + /// Debug info about the current state of the analysis pub fn status(&self) -> Cancelable { self.with_db(|db| status::status(&*db)) diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs index 71838b89c..5c5ae3e18 100644 --- a/crates/ra_lsp_server/src/config.rs +++ b/crates/ra_lsp_server/src/config.rs @@ -1,3 +1,5 @@ +use rustc_hash::FxHashMap; + use serde::{Deserialize, Deserializer}; /// Client provided initialization options @@ -12,12 +14,6 @@ pub struct ServerConfig { #[serde(deserialize_with = "nullable_bool_false")] pub publish_decorations: bool, - /// Whether or not the workspace loaded notification should be sent - /// - /// Defaults to `true` - #[serde(deserialize_with = "nullable_bool_true")] - pub show_workspace_loaded: bool, - pub exclude_globs: Vec, pub lru_capacity: Option, @@ -25,16 +21,19 @@ pub struct ServerConfig { /// 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, } impl Default for ServerConfig { fn default() -> ServerConfig { ServerConfig { publish_decorations: false, - show_workspace_loaded: true, exclude_globs: Vec::new(), lru_capacity: None, with_sysroot: true, + feature_flags: FxHashMap::default(), } } } diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index c0395c6d8..ce25ff162 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -9,7 +9,7 @@ use gen_lsp_server::{ handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, }; use lsp_types::{ClientCapabilities, NumberOrString}; -use ra_ide_api::{Canceled, FileId, LibraryData}; +use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData}; use ra_prof::profile; use ra_vfs::VfsTask; use serde::{de::DeserializeOwned, Serialize}; @@ -56,7 +56,7 @@ pub fn main_loop( msg_receiver: &Receiver, msg_sender: &Sender, ) -> Result<()> { - log::debug!("server_config: {:?}", config); + log::info!("server_config: {:#?}", config); // FIXME: support dynamic workspace loading. let workspaces = { let ws_worker = workspace_loader(config.with_sysroot); @@ -83,6 +83,21 @@ pub fn main_loop( .iter() .map(|glob| ra_vfs_glob::Glob::new(glob)) .collect::, _>>()?; + let feature_flags = { + let mut ff = FeatureFlags::default(); + for (flag, value) in config.feature_flags { + if let Err(_) = ff.set(flag.as_str(), value) { + log::error!("unknown feature flag: {:?}", flag); + show_message( + req::MessageType::Error, + format!("unknown feature flag: {:?}", flag), + msg_sender, + ); + } + } + ff + }; + log::info!("feature_flags: {:#?}", feature_flags); let mut state = WorldState::new( ws_roots, workspaces, @@ -90,13 +105,13 @@ pub fn main_loop( &globs, Options { publish_decorations: config.publish_decorations, - show_workspace_loaded: config.show_workspace_loaded, supports_location_link: client_caps .text_document .and_then(|it| it.definition) .and_then(|it| it.link_support) .unwrap_or(false), }, + feature_flags, ); let pool = ThreadPool::new(THREADPOOL_SIZE); @@ -276,7 +291,7 @@ fn main_loop_inner( && in_flight_libraries == 0 { let n_packages: usize = state.workspaces.iter().map(|it| it.n_packages()).sum(); - if state.options.show_workspace_loaded { + if state.feature_flags().get("notifications.workspace-loaded") { let msg = format!("workspace loaded, {} rust packages", n_packages); show_message(req::MessageType::Info, msg, msg_sender); } @@ -587,17 +602,20 @@ fn update_file_notifications_on_threadpool( subscriptions: Vec, ) { log::trace!("updating notifications for {:?}", subscriptions); + let publish_diagnostics = world.feature_flags().get("lsp.diagnostics"); pool.execute(move || { for file_id in subscriptions { - match handlers::publish_diagnostics(&world, file_id) { - Err(e) => { - if !is_canceled(&e) { - log::error!("failed to compute diagnostics: {:?}", e); + if publish_diagnostics { + match handlers::publish_diagnostics(&world, file_id) { + Err(e) => { + if !is_canceled(&e) { + log::error!("failed to compute diagnostics: {:?}", e); + } + } + Ok(params) => { + let not = RawNotification::new::(¶ms); + sender.send(Task::Notify(not)).unwrap(); } - } - Ok(params) => { - let not = RawNotification::new::(¶ms); - sender.send(Task::Notify(not)).unwrap(); } } if publish_decorations { diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 10f96812f..6696dff71 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -7,7 +7,8 @@ use gen_lsp_server::ErrorCode; use lsp_types::Url; use parking_lot::RwLock; use ra_ide_api::{ - Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, + Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, + SourceRootId, }; use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot}; use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; @@ -22,7 +23,6 @@ use crate::{ #[derive(Debug, Clone)] pub struct Options { pub publish_decorations: bool, - pub show_workspace_loaded: bool, pub supports_location_link: bool, } @@ -58,6 +58,7 @@ impl WorldState { lru_capacity: Option, exclude_globs: &[Glob], options: Options, + feature_flags: FeatureFlags, ) -> WorldState { let mut change = AnalysisChange::new(); @@ -99,7 +100,7 @@ impl WorldState { } change.set_crate_graph(crate_graph); - let mut analysis_host = AnalysisHost::new(lru_capacity); + let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); analysis_host.apply_change(change); WorldState { options, @@ -184,6 +185,10 @@ impl WorldState { pub fn complete_request(&mut self, request: CompletedRequest) { self.latest_requests.write().record(request) } + + pub fn feature_flags(&self) -> &FeatureFlags { + self.analysis_host.feature_flags() + } } impl WorldSnapshot { @@ -246,4 +251,8 @@ impl WorldSnapshot { let path = self.vfs.read().file2path(VfsFile(file_id.0)); self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) } + + pub fn feature_flags(&self) -> &FeatureFlags { + self.analysis.feature_flags() + } } diff --git a/editors/code/package.json b/editors/code/package.json index 98faf538f..95ec6cff6 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -165,10 +165,10 @@ "default": false, "description": "When highlighting Rust code, use a unique color per identifier" }, - "rust-analyzer.showWorkspaceLoadedNotification": { - "type": "boolean", - "default": true, - "description": "Show notification when workspace was loaded" + "rust-analyzer.featureFlags": { + "type": "object", + "default": {}, + "description": "Fine grained feature flags to disable annoying features" }, "rust-analyzer.enableEnhancedTyping": { "type": "boolean", diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 4df6b50ef..570ddca46 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -23,6 +23,7 @@ export class Config { public lruCapacity: null | number = null; public displayInlayHints = true; public excludeGlobs = []; + public featureFlags = {}; public cargoWatchOptions: CargoWatchOptions = { enableOnStartup: 'ask', trace: 'off', @@ -132,5 +133,8 @@ export class Config { if (config.has('excludeGlobs')) { this.excludeGlobs = config.get('excludeGlobs') || []; } + if (config.has('featureFlags')) { + this.featureFlags = config.get('featureFlags') || {}; + } } } diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts index 3273d8749..0d2a99c70 100644 --- a/editors/code/src/server.ts +++ b/editors/code/src/server.ts @@ -45,7 +45,8 @@ export class Server { showWorkspaceLoaded: Server.config.showWorkspaceLoadedNotification, lruCapacity: Server.config.lruCapacity, - excludeGlobs: Server.config.excludeGlobs + excludeGlobs: Server.config.excludeGlobs, + featureFlags: Server.config.featureFlags }, traceOutputChannel }; -- cgit v1.2.3