aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_flycheck/src/lib.rs12
-rw-r--r--crates/rust-analyzer/src/bin/main.rs32
-rw-r--r--crates/rust-analyzer/src/config.rs251
-rw-r--r--crates/rust-analyzer/src/feature_flags.rs77
-rw-r--r--crates/rust-analyzer/src/lib.rs3
-rw-r--r--crates/rust-analyzer/src/main_loop.rs43
-rw-r--r--crates/rust-analyzer/src/world.rs20
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs2
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs39
9 files changed, 151 insertions, 328 deletions
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index 13494a731..f3d6f8f5f 100644
--- a/crates/ra_flycheck/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -22,12 +22,22 @@ use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
22 22
23pub use crate::conv::url_from_path_with_drive_lowercasing; 23pub use crate::conv::url_from_path_with_drive_lowercasing;
24 24
25#[derive(Clone, Debug)] 25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum FlycheckConfig { 26pub enum FlycheckConfig {
27 CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> }, 27 CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> },
28 CustomCommand { command: String, args: Vec<String> }, 28 CustomCommand { command: String, args: Vec<String> },
29} 29}
30 30
31impl Default for FlycheckConfig {
32 fn default() -> Self {
33 FlycheckConfig::CargoCommand {
34 command: "check".to_string(),
35 all_targets: true,
36 extra_args: Vec::new(),
37 }
38 }
39}
40
31/// Flycheck wraps the shared state and communication machinery used for 41/// Flycheck wraps the shared state and communication machinery used for
32/// running `cargo check` (or other compatible command) and providing 42/// running `cargo check` (or other compatible command) and providing
33/// diagnostics based on the output. 43/// diagnostics based on the output.
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index a744a6695..483f50ce6 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -5,7 +5,7 @@ mod args;
5 5
6use lsp_server::Connection; 6use lsp_server::Connection;
7 7
8use rust_analyzer::{cli, from_json, show_message, Result, ServerConfig}; 8use rust_analyzer::{cli, from_json, Config, Result};
9 9
10use crate::args::HelpPrinted; 10use crate::args::HelpPrinted;
11 11
@@ -78,24 +78,18 @@ fn run_server() -> Result<()> {
78 .filter(|workspaces| !workspaces.is_empty()) 78 .filter(|workspaces| !workspaces.is_empty())
79 .unwrap_or_else(|| vec![root]); 79 .unwrap_or_else(|| vec![root]);
80 80
81 let server_config = initialize_params 81 let config = {
82 .initialization_options 82 let mut config = Config::default();
83 .and_then(|v| { 83 if let Some(value) = &initialize_params.initialization_options {
84 from_json::<ServerConfig>("config", v) 84 config.update(value);
85 .map_err(|e| { 85 }
86 log::error!("{}", e); 86 if let Some(caps) = &initialize_params.capabilities.text_document {
87 show_message(lsp_types::MessageType::Error, e.to_string(), &connection.sender); 87 config.update_caps(caps);
88 }) 88 }
89 .ok() 89 config
90 }) 90 };
91 .unwrap_or_default(); 91
92 92 rust_analyzer::main_loop(workspace_roots, config, connection)?;
93 rust_analyzer::main_loop(
94 workspace_roots,
95 initialize_params.capabilities,
96 server_config,
97 connection,
98 )?;
99 93
100 log::info!("shutting down IO..."); 94 log::info!("shutting down IO...");
101 io_threads.join()?; 95 io_threads.join()?;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8a8e42ed8..c07626e5c 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,14 +7,11 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use rustc_hash::FxHashMap;
11
12use crate::feature_flags::FeatureFlags;
13use lsp_types::TextDocumentClientCapabilities; 10use lsp_types::TextDocumentClientCapabilities;
14use ra_flycheck::FlycheckConfig; 11use ra_flycheck::FlycheckConfig;
15use ra_ide::{CompletionConfig, InlayHintsConfig}; 12use ra_ide::{CompletionConfig, InlayHintsConfig};
16use ra_project_model::CargoFeatures; 13use ra_project_model::CargoFeatures;
17use serde::{Deserialize, Deserializer}; 14use serde::Deserialize;
18 15
19#[derive(Debug, Clone)] 16#[derive(Debug, Clone)]
20pub struct Config { 17pub struct Config {
@@ -61,171 +58,109 @@ impl Default for RustfmtConfig {
61 } 58 }
62} 59}
63 60
64pub(crate) fn get_config( 61impl Default for Config {
65 config: &ServerConfig, 62 fn default() -> Self {
66 text_document_caps: Option<&TextDocumentClientCapabilities>, 63 Config {
67) -> Config { 64 publish_decorations: false,
68 let feature_flags = get_feature_flags(config); 65 publish_diagnostics: true,
69 Config { 66 notifications: NotificationsConfig {
70 publish_decorations: config.publish_decorations, 67 workspace_loaded: true,
71 publish_diagnostics: feature_flags.get("lsp.diagnostics"), 68 cargo_toml_not_found: true,
72 notifications: NotificationsConfig { 69 },
73 workspace_loaded: feature_flags.get("notifications.workspace-loaded"), 70 supports_location_link: false,
74 cargo_toml_not_found: feature_flags.get("notifications.cargo-toml-not-found"), 71 line_folding_only: false,
75 }, 72 inlay_hints: InlayHintsConfig {
76 supports_location_link: text_document_caps 73 type_hints: true,
77 .and_then(|it| it.definition) 74 parameter_hints: true,
78 .and_then(|it| it.link_support) 75 chaining_hints: true,
79 .unwrap_or(false), 76 max_length: None,
80 line_folding_only: text_document_caps 77 },
81 .and_then(|it| it.folding_range.as_ref()) 78 completion: CompletionConfig {
82 .and_then(|it| it.line_folding_only) 79 enable_postfix_completions: true,
83 .unwrap_or(false), 80 add_call_parenthesis: true,
84 inlay_hints: InlayHintsConfig { 81 add_call_argument_snippets: true,
85 type_hints: config.inlay_hints_type, 82 },
86 parameter_hints: config.inlay_hints_parameter, 83 call_info_full: true,
87 chaining_hints: config.inlay_hints_chaining, 84 rustfmt: RustfmtConfig::default(),
88 max_length: config.inlay_hints_max_length, 85 check: Some(FlycheckConfig::default()),
89 }, 86 vscode_lldb: false,
90 completion: CompletionConfig { 87 proc_macro_srv: None,
91 enable_postfix_completions: feature_flags.get("completion.enable-postfix"), 88 lru_capacity: None,
92 add_call_parenthesis: feature_flags.get("completion.insertion.add-call-parenthesis"), 89 use_client_watching: false,
93 add_call_argument_snippets: feature_flags 90 exclude_globs: Vec::new(),
94 .get("completion.insertion.add-argument-snippets"), 91 cargo: CargoFeatures::default(),
95 }, 92 with_sysroot: true,
96 call_info_full: feature_flags.get("call-info.full"),
97 check: if config.cargo_watch_enable {
98 Some(FlycheckConfig::CargoCommand {
99 command: config.cargo_watch_command.clone(),
100 all_targets: config.cargo_watch_all_targets,
101 extra_args: config.cargo_watch_args.clone(),
102 })
103 } else {
104 None
105 },
106 rustfmt: RustfmtConfig::Rustfmt { extra_args: config.rustfmt_args.clone() },
107 vscode_lldb: config.vscode_lldb,
108 proc_macro_srv: None, // FIXME: get this from config
109 lru_capacity: config.lru_capacity,
110 use_client_watching: config.use_client_watching,
111 exclude_globs: config.exclude_globs.clone(),
112 cargo: config.cargo_features.clone(),
113 with_sysroot: config.with_sysroot,
114 }
115}
116
117fn get_feature_flags(config: &ServerConfig) -> FeatureFlags {
118 let mut ff = FeatureFlags::default();
119 for (flag, &value) in &config.feature_flags {
120 if ff.set(flag.as_str(), value).is_err() {
121 log::error!("unknown feature flag: {:?}", flag);
122 } 93 }
123 } 94 }
124 log::info!("feature_flags: {:#?}", ff);
125 ff
126} 95}
127 96
128/// Client provided initialization options 97impl Config {
129#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] 98 #[rustfmt::skip]
130#[serde(rename_all = "camelCase", default)] 99 pub fn update(&mut self, value: &serde_json::Value) {
131pub struct ServerConfig { 100 let line_folding_only = self.line_folding_only;
132 /// Whether the client supports our custom highlighting publishing decorations. 101 let supports_location_link = self.supports_location_link;
133 /// This is different to the highlightingOn setting, which is whether the user 102 *self = Default::default();
134 /// wants our custom highlighting to be used. 103 self.line_folding_only = line_folding_only;
135 /// 104 self.supports_location_link = supports_location_link;
136 /// Defaults to `false` 105
137 #[serde(deserialize_with = "nullable_bool_false")] 106 set(value, "publishDecorations", &mut self.publish_decorations);
138 pub publish_decorations: bool, 107 set(value, "excludeGlobs", &mut self.exclude_globs);
139 108 set(value, "useClientWatching", &mut self.use_client_watching);
140 pub exclude_globs: Vec<String>, 109 set(value, "lruCapacity", &mut self.lru_capacity);
141 #[serde(deserialize_with = "nullable_bool_false")] 110
142 pub use_client_watching: bool, 111 set(value, "inlayHintsType", &mut self.inlay_hints.type_hints);
143 112 set(value, "inlayHintsParameter", &mut self.inlay_hints.parameter_hints);
144 pub lru_capacity: Option<usize>, 113 set(value, "inlayHintsChaining", &mut self.inlay_hints.chaining_hints);
145 114 set(value, "inlayHintsMaxLength", &mut self.inlay_hints.max_length);
146 #[serde(deserialize_with = "nullable_bool_true")] 115
147 pub inlay_hints_type: bool, 116 if let Some(false) = get(value, "cargo_watch_enable") {
148 #[serde(deserialize_with = "nullable_bool_true")] 117 self.check = None
149 pub inlay_hints_parameter: bool, 118 } else {
150 #[serde(deserialize_with = "nullable_bool_true")] 119 if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check
151 pub inlay_hints_chaining: bool, 120 {
152 pub inlay_hints_max_length: Option<usize>, 121 set(value, "cargoWatchArgs", extra_args);
153 122 set(value, "cargoWatchCommand", command);
154 pub cargo_watch_enable: bool, 123 set(value, "cargoWatchAllTargets", all_targets);
155 pub cargo_watch_args: Vec<String>, 124 }
156 pub cargo_watch_command: String, 125 };
157 pub cargo_watch_all_targets: bool, 126
158 127 set(value, "withSysroot", &mut self.with_sysroot);
159 /// For internal usage to make integrated tests faster. 128 if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt {
160 #[serde(deserialize_with = "nullable_bool_true")] 129 set(value, "rustfmtArgs", extra_args);
161 pub with_sysroot: bool, 130 }
162 131
163 /// Fine grained feature flags to disable specific features. 132 set(value, "cargoFeatures/noDefaultFeatures", &mut self.cargo.no_default_features);
164 pub feature_flags: FxHashMap<String, bool>, 133 set(value, "cargoFeatures/allFeatures", &mut self.cargo.all_features);
134 set(value, "cargoFeatures/features", &mut self.cargo.features);
135 set(value, "cargoFeatures/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
165 136
166 pub rustfmt_args: Vec<String>, 137 set(value, "vscodeLldb", &mut self.vscode_lldb);
167 138
168 /// Cargo feature configurations. 139 set(value, "featureFlags/lsp.diagnostics", &mut self.publish_diagnostics);
169 pub cargo_features: CargoFeatures, 140 set(value, "featureFlags/notifications.workspace-loaded", &mut self.notifications.workspace_loaded);
141 set(value, "featureFlags/notifications.cargo-toml-not-found", &mut self.notifications.cargo_toml_not_found);
142 set(value, "featureFlags/completion.enable-postfix", &mut self.completion.enable_postfix_completions);
143 set(value, "featureFlags/completion.insertion.add-call-parenthesis", &mut self.completion.add_call_parenthesis);
144 set(value, "featureFlags/completion.insertion.add-argument-snippets", &mut self.completion.add_call_argument_snippets);
145 set(value, "featureFlags/call-info.full", &mut self.call_info_full);
170 146
171 /// Enabled if the vscode_lldb extension is available. 147 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
172 pub vscode_lldb: bool, 148 value.pointer(pointer).and_then(|it| T::deserialize(it).ok())
173} 149 }
174 150
175impl Default for ServerConfig { 151 fn set<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str, slot: &mut T) {
176 fn default() -> ServerConfig { 152 if let Some(new_value) = get(value, pointer) {
177 ServerConfig { 153 *slot = new_value
178 publish_decorations: false, 154 }
179 exclude_globs: Vec::new(),
180 use_client_watching: false,
181 lru_capacity: None,
182 inlay_hints_type: true,
183 inlay_hints_parameter: true,
184 inlay_hints_chaining: true,
185 inlay_hints_max_length: None,
186 cargo_watch_enable: true,
187 cargo_watch_args: Vec::new(),
188 cargo_watch_command: "check".to_string(),
189 cargo_watch_all_targets: true,
190 with_sysroot: true,
191 feature_flags: FxHashMap::default(),
192 cargo_features: Default::default(),
193 rustfmt_args: Vec::new(),
194 vscode_lldb: false,
195 } 155 }
196 } 156 }
197}
198
199/// Deserializes a null value to a bool false by default
200fn nullable_bool_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
201where
202 D: Deserializer<'de>,
203{
204 let opt = Option::deserialize(deserializer)?;
205 Ok(opt.unwrap_or(false))
206}
207 157
208/// Deserializes a null value to a bool true by default 158 pub fn update_caps(&mut self, caps: &TextDocumentClientCapabilities) {
209fn nullable_bool_true<'de, D>(deserializer: D) -> Result<bool, D::Error> 159 if let Some(value) = caps.definition.as_ref().and_then(|it| it.link_support) {
210where 160 self.supports_location_link = value;
211 D: Deserializer<'de>, 161 }
212{ 162 if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
213 let opt = Option::deserialize(deserializer)?; 163 self.line_folding_only = value
214 Ok(opt.unwrap_or(true)) 164 }
215}
216
217#[cfg(test)]
218mod test {
219 use super::*;
220
221 #[test]
222 fn deserialize_init_options_defaults() {
223 // check that null == default for both fields
224 let default = ServerConfig::default();
225 assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap());
226 assert_eq!(
227 default,
228 serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap()
229 );
230 } 165 }
231} 166}
diff --git a/crates/rust-analyzer/src/feature_flags.rs b/crates/rust-analyzer/src/feature_flags.rs
deleted file mode 100644
index dbb3f50a0..000000000
--- a/crates/rust-analyzer/src/feature_flags.rs
+++ /dev/null
@@ -1,77 +0,0 @@
1//! See docs for `FeatureFlags`.
2
3use rustc_hash::FxHashMap;
4
5// FIXME: looks like a much better design is to pass options to each call,
6// rather than to have a global ambient feature flags -- that way, the clients
7// can issue two successive calls with different options.
8
9/// Feature flags hold fine-grained toggles for all *user-visible* features of
10/// rust-analyzer.
11///
12/// The exists such that users are able to disable any annoying feature (and,
13/// with many users and many features, some features are bound to be annoying
14/// for some users)
15///
16/// Note that we purposefully use run-time checked strings, and not something
17/// checked at compile time, to keep things simple and flexible.
18///
19/// Also note that, at the moment, `FeatureFlags` also store features for
20/// `rust-analyzer`. This should be benign layering violation.
21#[derive(Debug)]
22pub struct FeatureFlags {
23 flags: FxHashMap<String, bool>,
24}
25
26impl FeatureFlags {
27 fn new(flags: &[(&str, bool)]) -> FeatureFlags {
28 let flags = flags
29 .iter()
30 .map(|&(name, value)| {
31 check_flag_name(name);
32 (name.to_string(), value)
33 })
34 .collect();
35 FeatureFlags { flags }
36 }
37
38 pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> {
39 match self.flags.get_mut(flag) {
40 None => Err(()),
41 Some(slot) => {
42 *slot = value;
43 Ok(())
44 }
45 }
46 }
47
48 pub fn get(&self, flag: &str) -> bool {
49 match self.flags.get(flag) {
50 None => panic!("unknown flag: {:?}", flag),
51 Some(value) => *value,
52 }
53 }
54}
55
56impl Default for FeatureFlags {
57 fn default() -> FeatureFlags {
58 FeatureFlags::new(&[
59 ("lsp.diagnostics", true),
60 ("completion.insertion.add-call-parenthesis", true),
61 ("completion.insertion.add-argument-snippets", true),
62 ("completion.enable-postfix", true),
63 ("call-info.full", true),
64 ("notifications.workspace-loaded", true),
65 ("notifications.cargo-toml-not-found", true),
66 ])
67 }
68}
69
70fn check_flag_name(flag: &str) {
71 for c in flag.bytes() {
72 match c {
73 b'a'..=b'z' | b'-' | b'.' => (),
74 _ => panic!("flag name does not match conventions: {:?}", flag),
75 }
76 }
77}
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index e50e47b19..6062d0984 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -37,14 +37,13 @@ mod config;
37mod world; 37mod world;
38mod diagnostics; 38mod diagnostics;
39mod semantic_tokens; 39mod semantic_tokens;
40mod feature_flags;
41 40
42use serde::de::DeserializeOwned; 41use serde::de::DeserializeOwned;
43 42
44pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; 43pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
45pub use crate::{ 44pub use crate::{
45 config::Config,
46 caps::server_capabilities, 46 caps::server_capabilities,
47 config::ServerConfig,
48 main_loop::LspError, 47 main_loop::LspError,
49 main_loop::{main_loop, show_message}, 48 main_loop::{main_loop, show_message},
50}; 49};
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c973d43fa..00f92dbd5 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -17,9 +17,8 @@ use std::{
17use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 19use lsp_types::{
20 ClientCapabilities, NumberOrString, TextDocumentClientCapabilities, WorkDoneProgress, 20 NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
21 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, 21 WorkDoneProgressEnd, WorkDoneProgressReport,
22 WorkDoneProgressReport,
23}; 22};
24use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; 23use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
25use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; 24use ra_ide::{Canceled, FileId, LibraryData, SourceRootId};
@@ -31,7 +30,7 @@ use serde::{de::DeserializeOwned, Serialize};
31use threadpool::ThreadPool; 30use threadpool::ThreadPool;
32 31
33use crate::{ 32use crate::{
34 config::get_config, 33 config::Config,
35 diagnostics::DiagnosticTask, 34 diagnostics::DiagnosticTask,
36 main_loop::{ 35 main_loop::{
37 pending_requests::{PendingRequest, PendingRequests}, 36 pending_requests::{PendingRequest, PendingRequests},
@@ -39,7 +38,7 @@ use crate::{
39 }, 38 },
40 req, 39 req,
41 world::{WorldSnapshot, WorldState}, 40 world::{WorldSnapshot, WorldState},
42 Result, ServerConfig, 41 Result,
43}; 42};
44use req::ConfigurationParams; 43use req::ConfigurationParams;
45 44
@@ -65,14 +64,7 @@ impl fmt::Display for LspError {
65 64
66impl Error for LspError {} 65impl Error for LspError {}
67 66
68pub fn main_loop( 67pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> {
69 ws_roots: Vec<PathBuf>,
70 client_caps: ClientCapabilities,
71 config: ServerConfig,
72 connection: Connection,
73) -> Result<()> {
74 let text_document_caps = client_caps.text_document.as_ref();
75 let config = get_config(&config, text_document_caps);
76 log::info!("initial config: {:#?}", config); 68 log::info!("initial config: {:#?}", config);
77 69
78 // Windows scheduler implements priority boosts: if thread waits for an 70 // Windows scheduler implements priority boosts: if thread waits for an
@@ -205,7 +197,6 @@ pub fn main_loop(
205 &task_sender, 197 &task_sender,
206 &libdata_sender, 198 &libdata_sender,
207 &connection, 199 &connection,
208 text_document_caps,
209 &mut world_state, 200 &mut world_state,
210 &mut loop_state, 201 &mut loop_state,
211 event, 202 event,
@@ -316,7 +307,6 @@ fn loop_turn(
316 task_sender: &Sender<Task>, 307 task_sender: &Sender<Task>,
317 libdata_sender: &Sender<LibraryData>, 308 libdata_sender: &Sender<LibraryData>,
318 connection: &Connection, 309 connection: &Connection,
319 text_document_caps: Option<&TextDocumentClientCapabilities>,
320 world_state: &mut WorldState, 310 world_state: &mut WorldState,
321 loop_state: &mut LoopState, 311 loop_state: &mut LoopState,
322 event: Event, 312 event: Event,
@@ -370,27 +360,14 @@ fn loop_turn(
370 log::debug!("config update response: '{:?}", resp); 360 log::debug!("config update response: '{:?}", resp);
371 let Response { error, result, .. } = resp; 361 let Response { error, result, .. } = resp;
372 362
373 match ( 363 match (error, result) {
374 error,
375 result.map(|result| serde_json::from_value::<Vec<ServerConfig>>(result)),
376 ) {
377 (Some(err), _) => { 364 (Some(err), _) => {
378 log::error!("failed to fetch the server settings: {:?}", err) 365 log::error!("failed to fetch the server settings: {:?}", err)
379 } 366 }
380 (None, Some(Ok(new_config))) => { 367 (None, Some(new_config)) => {
381 let new_config = new_config 368 let mut config = world_state.config.clone();
382 .first() 369 config.update(&new_config);
383 .expect( 370 world_state.update_configuration(config);
384 "the client is expected to always send a non-empty config data",
385 )
386 .to_owned();
387 world_state.update_configuration(
388 new_config.lru_capacity,
389 get_config(&new_config, text_document_caps),
390 );
391 }
392 (None, Some(Err(e))) => {
393 log::error!("failed to parse client config response: {}", e)
394 } 371 }
395 (None, None) => { 372 (None, None) => {
396 log::error!("received empty server settings response from the client") 373 log::error!("received empty server settings response from the client")
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index df1b7ceeb..5674f42ef 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -11,7 +11,7 @@ use std::{
11use crossbeam_channel::{unbounded, Receiver}; 11use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 12use lsp_types::Url;
13use parking_lot::RwLock; 13use parking_lot::RwLock;
14use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck}; 14use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig};
15use ra_ide::{ 15use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, 16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
17}; 17};
@@ -30,9 +30,7 @@ use crate::{
30use ra_db::ExternSourceId; 30use ra_db::ExternSourceId;
31use rustc_hash::{FxHashMap, FxHashSet}; 31use rustc_hash::{FxHashMap, FxHashSet};
32 32
33fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<Flycheck> { 33fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
34 let check_config = config.check.as_ref()?;
35
36 // FIXME: Figure out the multi-workspace situation 34 // FIXME: Figure out the multi-workspace situation
37 workspaces 35 workspaces
38 .iter() 36 .iter()
@@ -42,7 +40,7 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<F
42 }) 40 })
43 .map(|cargo| { 41 .map(|cargo| {
44 let cargo_project_root = cargo.workspace_root().to_path_buf(); 42 let cargo_project_root = cargo.workspace_root().to_path_buf();
45 Some(Flycheck::new(check_config.clone(), cargo_project_root)) 43 Some(Flycheck::new(config.clone(), cargo_project_root))
46 }) 44 })
47 .unwrap_or_else(|| { 45 .unwrap_or_else(|| {
48 log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); 46 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
@@ -187,7 +185,7 @@ impl WorldState {
187 }); 185 });
188 change.set_crate_graph(crate_graph); 186 change.set_crate_graph(crate_graph);
189 187
190 let flycheck = create_flycheck(&workspaces, &config); 188 let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
191 189
192 let mut analysis_host = AnalysisHost::new(lru_capacity); 190 let mut analysis_host = AnalysisHost::new(lru_capacity);
193 analysis_host.apply_change(change); 191 analysis_host.apply_change(change);
@@ -204,9 +202,13 @@ impl WorldState {
204 } 202 }
205 } 203 }
206 204
207 pub fn update_configuration(&mut self, lru_capacity: Option<usize>, config: Config) { 205 pub fn update_configuration(&mut self, config: Config) {
208 self.analysis_host.update_lru_capacity(lru_capacity); 206 self.analysis_host.update_lru_capacity(config.lru_capacity);
209 self.flycheck = create_flycheck(&self.workspaces, &config); 207 if config.check != self.config.check {
208 self.flycheck =
209 config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
210 }
211
210 self.config = config; 212 self.config = config;
211 } 213 }
212 214
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 5af5eaad2..638813311 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -615,7 +615,7 @@ fn main() { message(); }
615"###, 615"###,
616 ) 616 )
617 .with_config(|config| { 617 .with_config(|config| {
618 config.cargo_features.load_out_dirs_from_check = true; 618 config.cargo.load_out_dirs_from_check = true;
619 }) 619 })
620 .server(); 620 .server();
621 server.wait_until_workspace_is_loaded(); 621 server.wait_until_workspace_is_loaded();
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index d8bed6d7f..c83cb8adb 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -11,8 +11,7 @@ use lsp_server::{Connection, Message, Notification, Request};
11use lsp_types::{ 11use lsp_types::{
12 notification::{DidOpenTextDocument, Exit}, 12 notification::{DidOpenTextDocument, Exit},
13 request::Shutdown, 13 request::Shutdown,
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, 14 DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
15 TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
16}; 15};
17use serde::Serialize; 16use serde::Serialize;
18use serde_json::{to_string_pretty, Value}; 17use serde_json::{to_string_pretty, Value};
@@ -20,14 +19,14 @@ use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture}; 19use test_utils::{find_mismatch, parse_fixture};
21 20
22use req::{ProgressParams, ProgressParamsValue}; 21use req::{ProgressParams, ProgressParamsValue};
23use rust_analyzer::{main_loop, req, ServerConfig}; 22use rust_analyzer::{main_loop, req, Config};
24 23
25pub struct Project<'a> { 24pub struct Project<'a> {
26 fixture: &'a str, 25 fixture: &'a str,
27 with_sysroot: bool, 26 with_sysroot: bool,
28 tmp_dir: Option<TempDir>, 27 tmp_dir: Option<TempDir>,
29 roots: Vec<PathBuf>, 28 roots: Vec<PathBuf>,
30 config: Option<Box<dyn Fn(&mut ServerConfig)>>, 29 config: Option<Box<dyn Fn(&mut Config)>>,
31} 30}
32 31
33impl<'a> Project<'a> { 32impl<'a> Project<'a> {
@@ -50,7 +49,7 @@ impl<'a> Project<'a> {
50 self 49 self
51 } 50 }
52 51
53 pub fn with_config(mut self, config: impl Fn(&mut ServerConfig) + 'static) -> Project<'a> { 52 pub fn with_config(mut self, config: impl Fn(&mut Config) + 'static) -> Project<'a> {
54 self.config = Some(Box::new(config)); 53 self.config = Some(Box::new(config));
55 self 54 self
56 } 55 }
@@ -78,8 +77,11 @@ impl<'a> Project<'a> {
78 77
79 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); 78 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
80 79
81 let mut config = 80 let mut config = Config {
82 ServerConfig { with_sysroot: self.with_sysroot, ..ServerConfig::default() }; 81 supports_location_link: true,
82 with_sysroot: self.with_sysroot,
83 ..Config::default()
84 };
83 85
84 if let Some(f) = &self.config { 86 if let Some(f) = &self.config {
85 f(&mut config) 87 f(&mut config)
@@ -105,7 +107,7 @@ pub struct Server {
105impl Server { 107impl Server {
106 fn new( 108 fn new(
107 dir: TempDir, 109 dir: TempDir,
108 config: ServerConfig, 110 config: Config,
109 roots: Vec<PathBuf>, 111 roots: Vec<PathBuf>,
110 files: Vec<(PathBuf, String)>, 112 files: Vec<(PathBuf, String)>,
111 ) -> Server { 113 ) -> Server {
@@ -116,26 +118,7 @@ impl Server {
116 118
117 let _thread = jod_thread::Builder::new() 119 let _thread = jod_thread::Builder::new()
118 .name("test server".to_string()) 120 .name("test server".to_string())
119 .spawn(move || { 121 .spawn(move || main_loop(roots, config, connection).unwrap())
120 main_loop(
121 roots,
122 ClientCapabilities {
123 workspace: None,
124 text_document: Some(TextDocumentClientCapabilities {
125 definition: Some(GotoCapability {
126 dynamic_registration: None,
127 link_support: Some(true),
128 }),
129 ..Default::default()
130 }),
131 window: None,
132 experimental: None,
133 },
134 config,
135 connection,
136 )
137 .unwrap()
138 })
139 .expect("failed to spawn a thread"); 122 .expect("failed to spawn a thread");
140 123
141 let res = 124 let res =