Merge #3809
3809: Less config r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
f39b51d025
@ -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<String> },
|
||||
CustomCommand { command: String, args: Vec<String> },
|
||||
|
@ -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<Target> 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<CargoWorkspace> {
|
||||
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<ExternResources> {
|
||||
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::<cargo_metadata::Message>(&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);
|
||||
|
@ -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<ProjectWorkspace> {
|
||||
pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
|
||||
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<ProjectWorkspace> {
|
||||
match find_rust_project_json(path) {
|
||||
Some(json_path) => {
|
||||
|
@ -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::<ServerConfig>("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()?;
|
||||
|
@ -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();
|
||||
|
@ -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<FlycheckConfig>,
|
||||
pub vscode_lldb: bool,
|
||||
pub proc_macro_srv: Option<String>,
|
||||
pub lru_capacity: Option<usize>,
|
||||
pub use_client_watching: bool,
|
||||
pub exclude_globs: Vec<String>,
|
||||
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<String>,
|
||||
#[serde(deserialize_with = "nullable_bool_false")]
|
||||
pub use_client_watching: bool,
|
||||
|
||||
pub lru_capacity: Option<usize>,
|
||||
|
||||
#[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<usize>,
|
||||
|
||||
pub cargo_watch_enable: bool,
|
||||
pub cargo_watch_args: Vec<String>,
|
||||
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<String, bool>,
|
||||
|
||||
pub rustfmt_args: Vec<String>,
|
||||
|
||||
/// 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<bool, D::Error>
|
||||
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<bool, D::Error>
|
||||
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<T> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -579,7 +579,7 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>)
|
||||
.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<Location> = links
|
||||
|
@ -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<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),
|
||||
("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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
pub use crate::{
|
||||
caps::server_capabilities,
|
||||
config::ServerConfig,
|
||||
main_loop::LspError,
|
||||
main_loop::{main_loop, show_message},
|
||||
};
|
||||
|
@ -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<PathBuf>,
|
||||
client_caps: ClientCapabilities,
|
||||
config: ServerConfig,
|
||||
connection: Connection,
|
||||
) -> Result<()> {
|
||||
log::info!("server_config: {:#?}", config);
|
||||
pub fn main_loop(ws_roots: Vec<PathBuf>, 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<Task>,
|
||||
libdata_sender: &Sender<LibraryData>,
|
||||
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::<Vec<ServerConfig>>(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<FileId>,
|
||||
) {
|
||||
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 {
|
||||
|
@ -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));
|
||||
|
@ -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<Flycheck> {
|
||||
let check_config = config.check.as_ref()?;
|
||||
|
||||
fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
|
||||
// FIXME: Figure out the multi-workspace situation
|
||||
workspaces
|
||||
.iter()
|
||||
@ -43,7 +40,7 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<F
|
||||
})
|
||||
.map(|cargo| {
|
||||
let cargo_project_root = cargo.workspace_root().to_path_buf();
|
||||
Some(Flycheck::new(check_config.clone(), cargo_project_root))
|
||||
Some(Flycheck::new(config.clone(), cargo_project_root))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
|
||||
@ -59,7 +56,6 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<F
|
||||
#[derive(Debug)]
|
||||
pub struct WorldState {
|
||||
pub config: Config,
|
||||
pub feature_flags: Arc<FeatureFlags>,
|
||||
pub roots: Vec<PathBuf>,
|
||||
pub workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
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<FeatureFlags>,
|
||||
pub workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
pub analysis: Analysis,
|
||||
pub latest_requests: Arc<RwLock<LatestRequests>>,
|
||||
@ -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<usize>,
|
||||
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),
|
||||
|
@ -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();
|
||||
|
@ -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<TempDir>,
|
||||
roots: Vec<PathBuf>,
|
||||
config: Option<Box<dyn Fn(&mut ServerConfig)>>,
|
||||
config: Option<Box<dyn Fn(&mut Config)>>,
|
||||
}
|
||||
|
||||
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<PathBuf>,
|
||||
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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user