Centralize all config

This commit is contained in:
Aleksey Kladov 2020-04-01 18:41:43 +02:00
parent 1e012eb991
commit a97e5eb85d
9 changed files with 153 additions and 330 deletions

View file

@ -22,12 +22,22 @@ use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
pub use crate::conv::url_from_path_with_drive_lowercasing; pub use crate::conv::url_from_path_with_drive_lowercasing;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig { pub enum FlycheckConfig {
CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> }, CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> },
CustomCommand { command: String, args: Vec<String> }, CustomCommand { command: String, args: Vec<String> },
} }
impl Default for FlycheckConfig {
fn default() -> Self {
FlycheckConfig::CargoCommand {
command: "check".to_string(),
all_targets: true,
extra_args: Vec::new(),
}
}
}
/// Flycheck wraps the shared state and communication machinery used for /// Flycheck wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing /// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output. /// diagnostics based on the output.

View file

@ -5,7 +5,7 @@ mod args;
use lsp_server::Connection; use lsp_server::Connection;
use rust_analyzer::{cli, from_json, show_message, Result, ServerConfig}; use rust_analyzer::{cli, from_json, Config, Result};
use crate::args::HelpPrinted; use crate::args::HelpPrinted;
@ -78,24 +78,18 @@ fn run_server() -> Result<()> {
.filter(|workspaces| !workspaces.is_empty()) .filter(|workspaces| !workspaces.is_empty())
.unwrap_or_else(|| vec![root]); .unwrap_or_else(|| vec![root]);
let server_config = initialize_params let config = {
.initialization_options let mut config = Config::default();
.and_then(|v| { if let Some(value) = &initialize_params.initialization_options {
from_json::<ServerConfig>("config", v) config.update(value);
.map_err(|e| { }
log::error!("{}", e); if let Some(caps) = &initialize_params.capabilities.text_document {
show_message(lsp_types::MessageType::Error, e.to_string(), &connection.sender); config.update_caps(caps);
}) }
.ok() config
}) };
.unwrap_or_default();
rust_analyzer::main_loop( rust_analyzer::main_loop(workspace_roots, config, connection)?;
workspace_roots,
initialize_params.capabilities,
server_config,
connection,
)?;
log::info!("shutting down IO..."); log::info!("shutting down IO...");
io_threads.join()?; io_threads.join()?;

View file

@ -7,14 +7,11 @@
//! configure the server itself, feature flags are passed into analysis, and //! configure the server itself, feature flags are passed into analysis, and
//! tweak things like automatic insertion of `()` in completions. //! tweak things like automatic insertion of `()` in completions.
use rustc_hash::FxHashMap;
use crate::feature_flags::FeatureFlags;
use lsp_types::TextDocumentClientCapabilities; use lsp_types::TextDocumentClientCapabilities;
use ra_flycheck::FlycheckConfig; use ra_flycheck::FlycheckConfig;
use ra_ide::{CompletionConfig, InlayHintsConfig}; use ra_ide::{CompletionConfig, InlayHintsConfig};
use ra_project_model::CargoFeatures; use ra_project_model::CargoFeatures;
use serde::{Deserialize, Deserializer}; use serde::Deserialize;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
@ -61,171 +58,109 @@ impl Default for RustfmtConfig {
} }
} }
pub(crate) fn get_config( impl Default for Config {
config: &ServerConfig, fn default() -> Self {
text_document_caps: Option<&TextDocumentClientCapabilities>, Config {
) -> Config {
let feature_flags = get_feature_flags(config);
Config {
publish_decorations: config.publish_decorations,
publish_diagnostics: feature_flags.get("lsp.diagnostics"),
notifications: NotificationsConfig {
workspace_loaded: feature_flags.get("notifications.workspace-loaded"),
cargo_toml_not_found: feature_flags.get("notifications.cargo-toml-not-found"),
},
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,
},
completion: CompletionConfig {
enable_postfix_completions: feature_flags.get("completion.enable-postfix"),
add_call_parenthesis: feature_flags.get("completion.insertion.add-call-parenthesis"),
add_call_argument_snippets: feature_flags
.get("completion.insertion.add-argument-snippets"),
},
call_info_full: feature_flags.get("call-info.full"),
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
lru_capacity: config.lru_capacity,
use_client_watching: config.use_client_watching,
exclude_globs: config.exclude_globs.clone(),
cargo: config.cargo_features.clone(),
with_sysroot: config.with_sysroot,
}
}
fn get_feature_flags(config: &ServerConfig) -> 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);
}
}
log::info!("feature_flags: {:#?}", ff);
ff
}
/// 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 {
publish_decorations: false, publish_decorations: false,
exclude_globs: Vec::new(), publish_diagnostics: true,
use_client_watching: false, notifications: NotificationsConfig {
lru_capacity: None, workspace_loaded: true,
inlay_hints_type: true, cargo_toml_not_found: true,
inlay_hints_parameter: true, },
inlay_hints_chaining: true, supports_location_link: false,
inlay_hints_max_length: None, line_folding_only: false,
cargo_watch_enable: true, inlay_hints: InlayHintsConfig {
cargo_watch_args: Vec::new(), type_hints: true,
cargo_watch_command: "check".to_string(), parameter_hints: true,
cargo_watch_all_targets: true, chaining_hints: true,
with_sysroot: true, max_length: None,
feature_flags: FxHashMap::default(), },
cargo_features: Default::default(), completion: CompletionConfig {
rustfmt_args: Vec::new(), enable_postfix_completions: true,
add_call_parenthesis: true,
add_call_argument_snippets: true,
},
call_info_full: true,
rustfmt: RustfmtConfig::default(),
check: Some(FlycheckConfig::default()),
vscode_lldb: false, vscode_lldb: false,
proc_macro_srv: None,
lru_capacity: None,
use_client_watching: false,
exclude_globs: Vec::new(),
cargo: CargoFeatures::default(),
with_sysroot: true,
} }
} }
} }
/// Deserializes a null value to a bool false by default impl Config {
fn nullable_bool_false<'de, D>(deserializer: D) -> Result<bool, D::Error> #[rustfmt::skip]
where pub fn update(&mut self, value: &serde_json::Value) {
D: Deserializer<'de>, let line_folding_only = self.line_folding_only;
{ let supports_location_link = self.supports_location_link;
let opt = Option::deserialize(deserializer)?; *self = Default::default();
Ok(opt.unwrap_or(false)) self.line_folding_only = line_folding_only;
} self.supports_location_link = supports_location_link;
/// Deserializes a null value to a bool true by default set(value, "publishDecorations", &mut self.publish_decorations);
fn nullable_bool_true<'de, D>(deserializer: D) -> Result<bool, D::Error> set(value, "excludeGlobs", &mut self.exclude_globs);
where set(value, "useClientWatching", &mut self.use_client_watching);
D: Deserializer<'de>, set(value, "lruCapacity", &mut self.lru_capacity);
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or(true))
}
#[cfg(test)] set(value, "inlayHintsType", &mut self.inlay_hints.type_hints);
mod test { set(value, "inlayHintsParameter", &mut self.inlay_hints.parameter_hints);
use super::*; set(value, "inlayHintsChaining", &mut self.inlay_hints.chaining_hints);
set(value, "inlayHintsMaxLength", &mut self.inlay_hints.max_length);
#[test] if let Some(false) = get(value, "cargo_watch_enable") {
fn deserialize_init_options_defaults() { self.check = None
// check that null == default for both fields } else {
let default = ServerConfig::default(); if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check
assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap()); {
assert_eq!( set(value, "cargoWatchArgs", extra_args);
default, set(value, "cargoWatchCommand", command);
serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap() 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);
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>>(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.supports_location_link = value;
}
if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
self.line_folding_only = value
}
} }
} }

View file

@ -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),
}
}
}

View file

@ -37,14 +37,13 @@ mod config;
mod world; mod world;
mod diagnostics; mod diagnostics;
mod semantic_tokens; mod semantic_tokens;
mod feature_flags;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub use crate::{ pub use crate::{
config::Config,
caps::server_capabilities, caps::server_capabilities,
config::ServerConfig,
main_loop::LspError, main_loop::LspError,
main_loop::{main_loop, show_message}, main_loop::{main_loop, show_message},
}; };

View file

@ -17,9 +17,8 @@ use std::{
use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
use lsp_types::{ use lsp_types::{
ClientCapabilities, NumberOrString, TextDocumentClientCapabilities, WorkDoneProgress, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressEnd, WorkDoneProgressReport,
WorkDoneProgressReport,
}; };
use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; use ra_ide::{Canceled, FileId, LibraryData, SourceRootId};
@ -31,7 +30,7 @@ use serde::{de::DeserializeOwned, Serialize};
use threadpool::ThreadPool; use threadpool::ThreadPool;
use crate::{ use crate::{
config::get_config, config::Config,
diagnostics::DiagnosticTask, diagnostics::DiagnosticTask,
main_loop::{ main_loop::{
pending_requests::{PendingRequest, PendingRequests}, pending_requests::{PendingRequest, PendingRequests},
@ -39,7 +38,7 @@ use crate::{
}, },
req, req,
world::{WorldSnapshot, WorldState}, world::{WorldSnapshot, WorldState},
Result, ServerConfig, Result,
}; };
use req::ConfigurationParams; use req::ConfigurationParams;
@ -65,14 +64,7 @@ impl fmt::Display for LspError {
impl Error for LspError {} impl Error for LspError {}
pub fn main_loop( pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> {
ws_roots: Vec<PathBuf>,
client_caps: ClientCapabilities,
config: ServerConfig,
connection: Connection,
) -> Result<()> {
let text_document_caps = client_caps.text_document.as_ref();
let config = get_config(&config, text_document_caps);
log::info!("initial config: {:#?}", config); log::info!("initial config: {:#?}", config);
// Windows scheduler implements priority boosts: if thread waits for an // Windows scheduler implements priority boosts: if thread waits for an
@ -205,7 +197,6 @@ pub fn main_loop(
&task_sender, &task_sender,
&libdata_sender, &libdata_sender,
&connection, &connection,
text_document_caps,
&mut world_state, &mut world_state,
&mut loop_state, &mut loop_state,
event, event,
@ -316,7 +307,6 @@ fn loop_turn(
task_sender: &Sender<Task>, task_sender: &Sender<Task>,
libdata_sender: &Sender<LibraryData>, libdata_sender: &Sender<LibraryData>,
connection: &Connection, connection: &Connection,
text_document_caps: Option<&TextDocumentClientCapabilities>,
world_state: &mut WorldState, world_state: &mut WorldState,
loop_state: &mut LoopState, loop_state: &mut LoopState,
event: Event, event: Event,
@ -370,27 +360,14 @@ fn loop_turn(
log::debug!("config update response: '{:?}", resp); log::debug!("config update response: '{:?}", resp);
let Response { error, result, .. } = resp; let Response { error, result, .. } = resp;
match ( match (error, result) {
error,
result.map(|result| serde_json::from_value::<Vec<ServerConfig>>(result)),
) {
(Some(err), _) => { (Some(err), _) => {
log::error!("failed to fetch the server settings: {:?}", err) log::error!("failed to fetch the server settings: {:?}", err)
} }
(None, Some(Ok(new_config))) => { (None, Some(new_config)) => {
let new_config = new_config let mut config = world_state.config.clone();
.first() config.update(&new_config);
.expect( world_state.update_configuration(config);
"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),
);
}
(None, Some(Err(e))) => {
log::error!("failed to parse client config response: {}", e)
} }
(None, None) => { (None, None) => {
log::error!("received empty server settings response from the client") log::error!("received empty server settings response from the client")

View file

@ -11,7 +11,7 @@ use std::{
use crossbeam_channel::{unbounded, Receiver}; use crossbeam_channel::{unbounded, Receiver};
use lsp_types::Url; use lsp_types::Url;
use parking_lot::RwLock; 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::{ use ra_ide::{
Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
}; };
@ -30,9 +30,7 @@ use crate::{
use ra_db::ExternSourceId; use ra_db::ExternSourceId;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<Flycheck> { fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
let check_config = config.check.as_ref()?;
// FIXME: Figure out the multi-workspace situation // FIXME: Figure out the multi-workspace situation
workspaces workspaces
.iter() .iter()
@ -42,7 +40,7 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<F
}) })
.map(|cargo| { .map(|cargo| {
let cargo_project_root = cargo.workspace_root().to_path_buf(); 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(|| { .unwrap_or_else(|| {
log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
@ -187,7 +185,7 @@ impl WorldState {
}); });
change.set_crate_graph(crate_graph); 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); let mut analysis_host = AnalysisHost::new(lru_capacity);
analysis_host.apply_change(change); analysis_host.apply_change(change);
@ -204,9 +202,13 @@ impl WorldState {
} }
} }
pub fn update_configuration(&mut self, lru_capacity: Option<usize>, config: Config) { pub fn update_configuration(&mut self, config: Config) {
self.analysis_host.update_lru_capacity(lru_capacity); self.analysis_host.update_lru_capacity(config.lru_capacity);
self.flycheck = create_flycheck(&self.workspaces, &config); if config.check != self.config.check {
self.flycheck =
config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
}
self.config = config; self.config = config;
} }

View file

@ -615,7 +615,7 @@ fn main() { message(); }
"###, "###,
) )
.with_config(|config| { .with_config(|config| {
config.cargo_features.load_out_dirs_from_check = true; config.cargo.load_out_dirs_from_check = true;
}) })
.server(); .server();
server.wait_until_workspace_is_loaded(); server.wait_until_workspace_is_loaded();

View file

@ -11,8 +11,7 @@ use lsp_server::{Connection, Message, Notification, Request};
use lsp_types::{ use lsp_types::{
notification::{DidOpenTextDocument, Exit}, notification::{DidOpenTextDocument, Exit},
request::Shutdown, request::Shutdown,
ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
}; };
use serde::Serialize; use serde::Serialize;
use serde_json::{to_string_pretty, Value}; use serde_json::{to_string_pretty, Value};
@ -20,14 +19,14 @@ use tempfile::TempDir;
use test_utils::{find_mismatch, parse_fixture}; use test_utils::{find_mismatch, parse_fixture};
use req::{ProgressParams, ProgressParamsValue}; use req::{ProgressParams, ProgressParamsValue};
use rust_analyzer::{main_loop, req, ServerConfig}; use rust_analyzer::{main_loop, req, Config};
pub struct Project<'a> { pub struct Project<'a> {
fixture: &'a str, fixture: &'a str,
with_sysroot: bool, with_sysroot: bool,
tmp_dir: Option<TempDir>, tmp_dir: Option<TempDir>,
roots: Vec<PathBuf>, roots: Vec<PathBuf>,
config: Option<Box<dyn Fn(&mut ServerConfig)>>, config: Option<Box<dyn Fn(&mut Config)>>,
} }
impl<'a> Project<'a> { impl<'a> Project<'a> {
@ -50,7 +49,7 @@ impl<'a> Project<'a> {
self 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.config = Some(Box::new(config));
self self
} }
@ -78,8 +77,11 @@ impl<'a> Project<'a> {
let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
let mut config = let mut config = Config {
ServerConfig { with_sysroot: self.with_sysroot, ..ServerConfig::default() }; supports_location_link: true,
with_sysroot: self.with_sysroot,
..Config::default()
};
if let Some(f) = &self.config { if let Some(f) = &self.config {
f(&mut config) f(&mut config)
@ -105,7 +107,7 @@ pub struct Server {
impl Server { impl Server {
fn new( fn new(
dir: TempDir, dir: TempDir,
config: ServerConfig, config: Config,
roots: Vec<PathBuf>, roots: Vec<PathBuf>,
files: Vec<(PathBuf, String)>, files: Vec<(PathBuf, String)>,
) -> Server { ) -> Server {
@ -116,26 +118,7 @@ impl Server {
let _thread = jod_thread::Builder::new() let _thread = jod_thread::Builder::new()
.name("test server".to_string()) .name("test server".to_string())
.spawn(move || { .spawn(move || main_loop(roots, config, connection).unwrap())
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()
})
.expect("failed to spawn a thread"); .expect("failed to spawn a thread");
let res = let res =