mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 01:17:27 +00:00
Merge #1715
1715: Feature flags r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
0a612fad02
13 changed files with 172 additions and 44 deletions
|
@ -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::<usize>().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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<RootDatabase>,
|
||||
pub(crate) feature_flags: Arc<FeatureFlags>,
|
||||
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<usize>) -> RootDatabase {
|
||||
pub fn new(lru_capacity: Option<usize>, 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
67
crates/ra_ide_api/src/feature_flags.rs
Normal file
67
crates/ra_ide_api/src/feature_flags.rs
Normal file
|
@ -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<String, bool>,
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<usize>) -> AnalysisHost {
|
||||
AnalysisHost { db: db::RootDatabase::new(lru_capcity) }
|
||||
pub fn new(lru_capcity: Option<usize>, 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<String> {
|
||||
self.with_db(|db| status::status(&*db))
|
||||
|
|
|
@ -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<String>,
|
||||
|
||||
pub lru_capacity: Option<usize>,
|
||||
|
@ -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<String, bool>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,8 @@ mod world;
|
|||
|
||||
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
pub use crate::{
|
||||
caps::server_capabilities, config::ServerConfig, main_loop::main_loop, main_loop::LspError,
|
||||
caps::server_capabilities,
|
||||
config::ServerConfig,
|
||||
main_loop::LspError,
|
||||
main_loop::{main_loop, show_message},
|
||||
};
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use flexi_logger::{Duplicate, Logger};
|
||||
use gen_lsp_server::{run_server, stdio_transport};
|
||||
use serde::Deserialize;
|
||||
|
||||
use ra_lsp_server::{Result, ServerConfig};
|
||||
use ra_lsp_server::{show_message, Result, ServerConfig};
|
||||
use ra_prof;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -46,15 +45,23 @@ fn main_inner() -> Result<()> {
|
|||
.filter(|workspaces| !workspaces.is_empty())
|
||||
.unwrap_or_else(|| vec![root]);
|
||||
|
||||
let opts = params
|
||||
let server_config: ServerConfig = params
|
||||
.initialization_options
|
||||
.and_then(|v| {
|
||||
ServerConfig::deserialize(v)
|
||||
.map_err(|e| log::error!("failed to deserialize config: {}", e))
|
||||
serde_json::from_value(v)
|
||||
.map_err(|e| {
|
||||
log::error!("failed to deserialize config: {}", e);
|
||||
show_message(
|
||||
lsp_types::MessageType::Error,
|
||||
format!("failed to deserialize config: {}", e),
|
||||
s,
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
ra_lsp_server::main_loop(workspace_roots, params.capabilities, opts, r, s)
|
||||
|
||||
ra_lsp_server::main_loop(workspace_roots, params.capabilities, server_config, r, s)
|
||||
})?;
|
||||
log::info!("shutting down IO...");
|
||||
threads.join()?;
|
||||
|
|
|
@ -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<RawMessage>,
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
) -> 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::<std::result::Result<Vec<_>, _>>()?;
|
||||
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<FileId>,
|
||||
) {
|
||||
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::<req::PublishDiagnostics>(¶ms);
|
||||
sender.send(Task::Notify(not)).unwrap();
|
||||
}
|
||||
}
|
||||
Ok(params) => {
|
||||
let not = RawNotification::new::<req::PublishDiagnostics>(¶ms);
|
||||
sender.send(Task::Notify(not)).unwrap();
|
||||
}
|
||||
}
|
||||
if publish_decorations {
|
||||
|
@ -617,7 +635,11 @@ fn update_file_notifications_on_threadpool(
|
|||
});
|
||||
}
|
||||
|
||||
fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<RawMessage>) {
|
||||
pub fn show_message(
|
||||
typ: req::MessageType,
|
||||
message: impl Into<String>,
|
||||
sender: &Sender<RawMessage>,
|
||||
) {
|
||||
let message = message.into();
|
||||
let params = req::ShowMessageParams { typ, message };
|
||||
let not = RawNotification::new::<req::ShowMessage>(¶ms);
|
||||
|
|
|
@ -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<usize>,
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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') || {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue