]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/config.rs
Merge #6514
[rust.git] / crates / rust-analyzer / src / config.rs
1 //! Config used by the language server.
2 //!
3 //! We currently get this config from `initialize` LSP request, which is not the
4 //! best way to do it, but was the simplest thing we could implement.
5 //!
6 //! Of particular interest is the `feature_flags` hash map: while other fields
7 //! configure the server itself, feature flags are passed into analysis, and
8 //! tweak things like automatic insertion of `()` in completions.
9
10 use std::{ffi::OsString, path::PathBuf};
11
12 use flycheck::FlycheckConfig;
13 use hir::PrefixKind;
14 use ide::{
15     AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
16     MergeBehaviour,
17 };
18 use lsp_types::{ClientCapabilities, MarkupKind};
19 use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
20 use rustc_hash::FxHashSet;
21 use serde::Deserialize;
22 use vfs::AbsPathBuf;
23
24 use crate::diagnostics::DiagnosticsMapConfig;
25
26 #[derive(Debug, Clone)]
27 pub struct Config {
28     pub client_caps: ClientCapsConfig,
29
30     pub publish_diagnostics: bool,
31     pub diagnostics: DiagnosticsConfig,
32     pub diagnostics_map: DiagnosticsMapConfig,
33     pub lru_capacity: Option<usize>,
34     pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
35     pub files: FilesConfig,
36     pub notifications: NotificationsConfig,
37
38     pub cargo_autoreload: bool,
39     pub cargo: CargoConfig,
40     pub rustfmt: RustfmtConfig,
41     pub flycheck: Option<FlycheckConfig>,
42     pub runnables: RunnablesConfig,
43
44     pub inlay_hints: InlayHintsConfig,
45     pub completion: CompletionConfig,
46     pub assist: AssistConfig,
47     pub call_info_full: bool,
48     pub lens: LensConfig,
49     pub hover: HoverConfig,
50     pub semantic_tokens_refresh: bool,
51
52     pub with_sysroot: bool,
53     pub linked_projects: Vec<LinkedProject>,
54     pub root_path: AbsPathBuf,
55 }
56
57 #[derive(Debug, Clone, Eq, PartialEq)]
58 pub enum LinkedProject {
59     ProjectManifest(ProjectManifest),
60     InlineJsonProject(ProjectJson),
61 }
62
63 impl From<ProjectManifest> for LinkedProject {
64     fn from(v: ProjectManifest) -> Self {
65         LinkedProject::ProjectManifest(v)
66     }
67 }
68
69 impl From<ProjectJson> for LinkedProject {
70     fn from(v: ProjectJson) -> Self {
71         LinkedProject::InlineJsonProject(v)
72     }
73 }
74
75 #[derive(Clone, Debug, PartialEq, Eq)]
76 pub struct LensConfig {
77     pub run: bool,
78     pub debug: bool,
79     pub implementations: bool,
80     pub method_refs: bool,
81 }
82
83 impl Default for LensConfig {
84     fn default() -> Self {
85         Self { run: true, debug: true, implementations: true, method_refs: false }
86     }
87 }
88
89 impl LensConfig {
90     pub fn any(&self) -> bool {
91         self.implementations || self.runnable() || self.references()
92     }
93
94     pub fn none(&self) -> bool {
95         !self.any()
96     }
97
98     pub fn runnable(&self) -> bool {
99         self.run || self.debug
100     }
101
102     pub fn references(&self) -> bool {
103         self.method_refs
104     }
105 }
106
107 #[derive(Debug, Clone)]
108 pub struct FilesConfig {
109     pub watcher: FilesWatcher,
110     pub exclude: Vec<String>,
111 }
112
113 #[derive(Debug, Clone)]
114 pub enum FilesWatcher {
115     Client,
116     Notify,
117 }
118
119 #[derive(Debug, Clone)]
120 pub struct NotificationsConfig {
121     pub cargo_toml_not_found: bool,
122 }
123
124 #[derive(Debug, Clone)]
125 pub enum RustfmtConfig {
126     Rustfmt { extra_args: Vec<String> },
127     CustomCommand { command: String, args: Vec<String> },
128 }
129
130 /// Configuration for runnable items, such as `main` function or tests.
131 #[derive(Debug, Clone, Default)]
132 pub struct RunnablesConfig {
133     /// Custom command to be executed instead of `cargo` for runnables.
134     pub override_cargo: Option<String>,
135     /// Additional arguments for the `cargo`, e.g. `--release`.
136     pub cargo_extra_args: Vec<String>,
137 }
138
139 #[derive(Debug, Clone, Default)]
140 pub struct ClientCapsConfig {
141     pub location_link: bool,
142     pub line_folding_only: bool,
143     pub hierarchical_symbols: bool,
144     pub code_action_literals: bool,
145     pub work_done_progress: bool,
146     pub code_action_group: bool,
147     pub code_action_resolve: bool,
148     pub hover_actions: bool,
149     pub status_notification: bool,
150     pub signature_help_label_offsets: bool,
151 }
152
153 impl Config {
154     pub fn new(root_path: AbsPathBuf) -> Self {
155         Config {
156             client_caps: ClientCapsConfig::default(),
157
158             with_sysroot: true,
159             publish_diagnostics: true,
160             diagnostics: DiagnosticsConfig::default(),
161             diagnostics_map: DiagnosticsMapConfig::default(),
162             lru_capacity: None,
163             proc_macro_srv: None,
164             files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
165             notifications: NotificationsConfig { cargo_toml_not_found: true },
166
167             cargo_autoreload: true,
168             cargo: CargoConfig::default(),
169             rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
170             flycheck: Some(FlycheckConfig::CargoCommand {
171                 command: "check".to_string(),
172                 target_triple: None,
173                 no_default_features: false,
174                 all_targets: true,
175                 all_features: false,
176                 extra_args: Vec::new(),
177                 features: Vec::new(),
178             }),
179             runnables: RunnablesConfig::default(),
180
181             inlay_hints: InlayHintsConfig {
182                 type_hints: true,
183                 parameter_hints: true,
184                 chaining_hints: true,
185                 max_length: None,
186             },
187             completion: CompletionConfig {
188                 enable_postfix_completions: true,
189                 add_call_parenthesis: true,
190                 add_call_argument_snippets: true,
191                 ..CompletionConfig::default()
192             },
193             assist: AssistConfig::default(),
194             call_info_full: true,
195             lens: LensConfig::default(),
196             hover: HoverConfig::default(),
197             semantic_tokens_refresh: false,
198             linked_projects: Vec::new(),
199             root_path,
200         }
201     }
202
203     pub fn update(&mut self, json: serde_json::Value) {
204         log::info!("Config::update({:#})", json);
205
206         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
207             return;
208         }
209
210         let data = ConfigData::from_json(json);
211
212         self.with_sysroot = data.withSysroot;
213         self.publish_diagnostics = data.diagnostics_enable;
214         self.diagnostics = DiagnosticsConfig {
215             disable_experimental: !data.diagnostics_enableExperimental,
216             disabled: data.diagnostics_disabled,
217         };
218         self.diagnostics_map = DiagnosticsMapConfig {
219             warnings_as_info: data.diagnostics_warningsAsInfo,
220             warnings_as_hint: data.diagnostics_warningsAsHint,
221         };
222         self.lru_capacity = data.lruCapacity;
223         self.files.watcher = match data.files_watcher.as_str() {
224             "notify" => FilesWatcher::Notify,
225             "client" | _ => FilesWatcher::Client,
226         };
227         self.notifications =
228             NotificationsConfig { cargo_toml_not_found: data.notifications_cargoTomlNotFound };
229         self.cargo_autoreload = data.cargo_autoreload;
230         self.cargo = CargoConfig {
231             no_default_features: data.cargo_noDefaultFeatures,
232             all_features: data.cargo_allFeatures,
233             features: data.cargo_features.clone(),
234             load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
235             target: data.cargo_target.clone(),
236         };
237         self.runnables = RunnablesConfig {
238             override_cargo: data.runnables_overrideCargo,
239             cargo_extra_args: data.runnables_cargoExtraArgs,
240         };
241
242         self.proc_macro_srv = if data.procMacro_enable {
243             std::env::current_exe().ok().map(|path| (path, vec!["proc-macro".into()]))
244         } else {
245             None
246         };
247
248         self.rustfmt = match data.rustfmt_overrideCommand {
249             Some(mut args) if !args.is_empty() => {
250                 let command = args.remove(0);
251                 RustfmtConfig::CustomCommand { command, args }
252             }
253             Some(_) | None => RustfmtConfig::Rustfmt { extra_args: data.rustfmt_extraArgs },
254         };
255
256         self.flycheck = if data.checkOnSave_enable {
257             let flycheck_config = match data.checkOnSave_overrideCommand {
258                 Some(mut args) if !args.is_empty() => {
259                     let command = args.remove(0);
260                     FlycheckConfig::CustomCommand { command, args }
261                 }
262                 Some(_) | None => FlycheckConfig::CargoCommand {
263                     command: data.checkOnSave_command,
264                     target_triple: data.checkOnSave_target.or(data.cargo_target),
265                     all_targets: data.checkOnSave_allTargets,
266                     no_default_features: data
267                         .checkOnSave_noDefaultFeatures
268                         .unwrap_or(data.cargo_noDefaultFeatures),
269                     all_features: data.checkOnSave_allFeatures.unwrap_or(data.cargo_allFeatures),
270                     features: data.checkOnSave_features.unwrap_or(data.cargo_features),
271                     extra_args: data.checkOnSave_extraArgs,
272                 },
273             };
274             Some(flycheck_config)
275         } else {
276             None
277         };
278
279         self.inlay_hints = InlayHintsConfig {
280             type_hints: data.inlayHints_typeHints,
281             parameter_hints: data.inlayHints_parameterHints,
282             chaining_hints: data.inlayHints_chainingHints,
283             max_length: data.inlayHints_maxLength,
284         };
285
286         self.completion.enable_postfix_completions = data.completion_postfix_enable;
287         self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
288         self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
289
290         self.assist.insert_use.merge = match data.assist_importMergeBehaviour {
291             MergeBehaviourDef::None => None,
292             MergeBehaviourDef::Full => Some(MergeBehaviour::Full),
293             MergeBehaviourDef::Last => Some(MergeBehaviour::Last),
294         };
295         self.assist.insert_use.prefix_kind = match data.assist_importPrefix {
296             ImportPrefixDef::Plain => PrefixKind::Plain,
297             ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
298             ImportPrefixDef::BySelf => PrefixKind::BySelf,
299         };
300
301         self.call_info_full = data.callInfo_full;
302
303         self.lens = LensConfig {
304             run: data.lens_enable && data.lens_run,
305             debug: data.lens_enable && data.lens_debug,
306             implementations: data.lens_enable && data.lens_implementations,
307             method_refs: data.lens_enable && data.lens_methodReferences,
308         };
309
310         if !data.linkedProjects.is_empty() {
311             self.linked_projects.clear();
312             for linked_project in data.linkedProjects {
313                 let linked_project = match linked_project {
314                     ManifestOrProjectJson::Manifest(it) => {
315                         let path = self.root_path.join(it);
316                         match ProjectManifest::from_manifest_file(path) {
317                             Ok(it) => it.into(),
318                             Err(e) => {
319                                 log::error!("failed to load linked project: {}", e);
320                                 continue;
321                             }
322                         }
323                     }
324                     ManifestOrProjectJson::ProjectJson(it) => {
325                         ProjectJson::new(&self.root_path, it).into()
326                     }
327                 };
328                 self.linked_projects.push(linked_project);
329             }
330         }
331
332         self.hover = HoverConfig {
333             implementations: data.hoverActions_enable && data.hoverActions_implementations,
334             run: data.hoverActions_enable && data.hoverActions_run,
335             debug: data.hoverActions_enable && data.hoverActions_debug,
336             goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
337             links_in_hover: data.hoverActions_linksInHover,
338             markdown: true,
339         };
340
341         log::info!("Config::update() = {:#?}", self);
342     }
343
344     pub fn update_caps(&mut self, caps: &ClientCapabilities) {
345         if let Some(doc_caps) = caps.text_document.as_ref() {
346             if let Some(value) = doc_caps.hover.as_ref().and_then(|it| it.content_format.as_ref()) {
347                 self.hover.markdown = value.contains(&MarkupKind::Markdown)
348             }
349             if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) {
350                 self.client_caps.location_link = value;
351             }
352             if let Some(value) = doc_caps.folding_range.as_ref().and_then(|it| it.line_folding_only)
353             {
354                 self.client_caps.line_folding_only = value
355             }
356             if let Some(value) = doc_caps
357                 .document_symbol
358                 .as_ref()
359                 .and_then(|it| it.hierarchical_document_symbol_support)
360             {
361                 self.client_caps.hierarchical_symbols = value
362             }
363             if let Some(value) =
364                 doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some())
365             {
366                 self.client_caps.code_action_literals = value;
367             }
368             if let Some(value) = doc_caps
369                 .signature_help
370                 .as_ref()
371                 .and_then(|it| it.signature_information.as_ref())
372                 .and_then(|it| it.parameter_information.as_ref())
373                 .and_then(|it| it.label_offset_support)
374             {
375                 self.client_caps.signature_help_label_offsets = value;
376             }
377
378             self.completion.allow_snippets(false);
379             if let Some(completion) = &doc_caps.completion {
380                 if let Some(completion_item) = &completion.completion_item {
381                     if let Some(value) = completion_item.snippet_support {
382                         self.completion.allow_snippets(value);
383                     }
384                 }
385             }
386
387             if let Some(code_action) = &doc_caps.code_action {
388                 match (code_action.data_support, &code_action.resolve_support) {
389                     (Some(true), Some(resolve_support)) => {
390                         if resolve_support.properties.iter().any(|it| it == "edit") {
391                             self.client_caps.code_action_resolve = true;
392                         }
393                     }
394                     _ => (),
395                 }
396             }
397         }
398
399         if let Some(window_caps) = caps.window.as_ref() {
400             if let Some(value) = window_caps.work_done_progress {
401                 self.client_caps.work_done_progress = value;
402             }
403         }
404
405         self.assist.allow_snippets(false);
406         if let Some(experimental) = &caps.experimental {
407             let get_bool =
408                 |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
409
410             let snippet_text_edit = get_bool("snippetTextEdit");
411             self.assist.allow_snippets(snippet_text_edit);
412
413             self.client_caps.code_action_group = get_bool("codeActionGroup");
414             self.client_caps.hover_actions = get_bool("hoverActions");
415             self.client_caps.status_notification = get_bool("statusNotification");
416         }
417
418         if let Some(workspace_caps) = caps.workspace.as_ref() {
419             if let Some(refresh_support) =
420                 workspace_caps.semantic_tokens.as_ref().and_then(|it| it.refresh_support)
421             {
422                 self.semantic_tokens_refresh = refresh_support;
423             }
424         }
425     }
426 }
427
428 #[derive(Deserialize)]
429 #[serde(untagged)]
430 enum ManifestOrProjectJson {
431     Manifest(PathBuf),
432     ProjectJson(ProjectJsonData),
433 }
434
435 #[derive(Deserialize)]
436 #[serde(rename_all = "snake_case")]
437 enum MergeBehaviourDef {
438     None,
439     Full,
440     Last,
441 }
442
443 #[derive(Deserialize)]
444 #[serde(rename_all = "snake_case")]
445 enum ImportPrefixDef {
446     Plain,
447     BySelf,
448     ByCrate,
449 }
450
451 macro_rules! config_data {
452     (struct $name:ident { $($field:ident: $ty:ty = $default:expr,)*}) => {
453         #[allow(non_snake_case)]
454         struct $name { $($field: $ty,)* }
455         impl $name {
456             fn from_json(mut json: serde_json::Value) -> $name {
457                 $name {$(
458                     $field: {
459                         let pointer = stringify!($field).replace('_', "/");
460                         let pointer = format!("/{}", pointer);
461                         json.pointer_mut(&pointer)
462                             .and_then(|it| serde_json::from_value(it.take()).ok())
463                             .unwrap_or($default)
464                     },
465                 )*}
466             }
467         }
468
469     };
470 }
471
472 config_data! {
473     struct ConfigData {
474         assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::None,
475         assist_importPrefix: ImportPrefixDef           = ImportPrefixDef::Plain,
476
477         callInfo_full: bool = true,
478
479         cargo_autoreload: bool           = true,
480         cargo_allFeatures: bool          = false,
481         cargo_features: Vec<String>      = Vec::new(),
482         cargo_loadOutDirsFromCheck: bool = false,
483         cargo_noDefaultFeatures: bool    = false,
484         cargo_target: Option<String>     = None,
485
486         checkOnSave_enable: bool                         = true,
487         checkOnSave_allFeatures: Option<bool>            = None,
488         checkOnSave_allTargets: bool                     = true,
489         checkOnSave_command: String                      = "check".into(),
490         checkOnSave_noDefaultFeatures: Option<bool>      = None,
491         checkOnSave_target: Option<String>               = None,
492         checkOnSave_extraArgs: Vec<String>               = Vec::new(),
493         checkOnSave_features: Option<Vec<String>>        = None,
494         checkOnSave_overrideCommand: Option<Vec<String>> = None,
495
496         completion_addCallArgumentSnippets: bool = true,
497         completion_addCallParenthesis: bool      = true,
498         completion_postfix_enable: bool          = true,
499
500         diagnostics_enable: bool                = true,
501         diagnostics_enableExperimental: bool    = true,
502         diagnostics_disabled: FxHashSet<String> = FxHashSet::default(),
503         diagnostics_warningsAsHint: Vec<String> = Vec::new(),
504         diagnostics_warningsAsInfo: Vec<String> = Vec::new(),
505
506         files_watcher: String = "client".into(),
507
508         hoverActions_debug: bool           = true,
509         hoverActions_enable: bool          = true,
510         hoverActions_gotoTypeDef: bool     = true,
511         hoverActions_implementations: bool = true,
512         hoverActions_run: bool             = true,
513         hoverActions_linksInHover: bool    = true,
514
515         inlayHints_chainingHints: bool      = true,
516         inlayHints_maxLength: Option<usize> = None,
517         inlayHints_parameterHints: bool     = true,
518         inlayHints_typeHints: bool          = true,
519
520         lens_debug: bool            = true,
521         lens_enable: bool           = true,
522         lens_implementations: bool  = true,
523         lens_run: bool              = true,
524         lens_methodReferences: bool = false,
525
526         linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(),
527         lruCapacity: Option<usize>                 = None,
528         notifications_cargoTomlNotFound: bool      = true,
529         procMacro_enable: bool                     = false,
530
531         runnables_overrideCargo: Option<String> = None,
532         runnables_cargoExtraArgs: Vec<String>   = Vec::new(),
533
534         rustfmt_extraArgs: Vec<String>               = Vec::new(),
535         rustfmt_overrideCommand: Option<Vec<String>> = None,
536
537         withSysroot: bool = true,
538     }
539 }