]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/config.rs
Add hover actions as LSP extension
[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 lsp_types::ClientCapabilities;
13 use ra_flycheck::FlycheckConfig;
14 use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig, HoverConfig};
15 use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
16 use serde::Deserialize;
17
18 #[derive(Debug, Clone)]
19 pub struct Config {
20     pub client_caps: ClientCapsConfig,
21
22     pub publish_diagnostics: bool,
23     pub lru_capacity: Option<usize>,
24     pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
25     pub files: FilesConfig,
26     pub notifications: NotificationsConfig,
27
28     pub cargo: CargoConfig,
29     pub rustfmt: RustfmtConfig,
30     pub check: Option<FlycheckConfig>,
31
32     pub inlay_hints: InlayHintsConfig,
33     pub completion: CompletionConfig,
34     pub assist: AssistConfig,
35     pub call_info_full: bool,
36     pub lens: LensConfig,
37     pub hover: HoverConfig,
38
39     pub with_sysroot: bool,
40     pub linked_projects: Vec<LinkedProject>,
41 }
42
43 #[derive(Debug, Clone)]
44 pub enum LinkedProject {
45     ProjectManifest(ProjectManifest),
46     JsonProject(JsonProject),
47 }
48
49 impl From<ProjectManifest> for LinkedProject {
50     fn from(v: ProjectManifest) -> Self {
51         LinkedProject::ProjectManifest(v)
52     }
53 }
54
55 impl From<JsonProject> for LinkedProject {
56     fn from(v: JsonProject) -> Self {
57         LinkedProject::JsonProject(v)
58     }
59 }
60
61 #[derive(Clone, Debug, PartialEq, Eq)]
62 pub struct LensConfig {
63     pub run: bool,
64     pub debug: bool,
65     pub impementations: bool,
66 }
67
68 impl Default for LensConfig {
69     fn default() -> Self {
70         Self { run: true, debug: true, impementations: true }
71     }
72 }
73
74 impl LensConfig {
75     pub const NO_LENS: LensConfig = Self { run: false, debug: false, impementations: false };
76
77     pub fn any(&self) -> bool {
78         self.impementations || self.runnable()
79     }
80
81     pub fn none(&self) -> bool {
82         !self.any()
83     }
84
85     pub fn runnable(&self) -> bool {
86         self.run || self.debug
87     }
88 }
89
90 #[derive(Debug, Clone)]
91 pub struct FilesConfig {
92     pub watcher: FilesWatcher,
93     pub exclude: Vec<String>,
94 }
95
96 #[derive(Debug, Clone)]
97 pub enum FilesWatcher {
98     Client,
99     Notify,
100 }
101
102 #[derive(Debug, Clone)]
103 pub struct NotificationsConfig {
104     pub cargo_toml_not_found: bool,
105 }
106
107 #[derive(Debug, Clone)]
108 pub enum RustfmtConfig {
109     Rustfmt {
110         extra_args: Vec<String>,
111     },
112     #[allow(unused)]
113     CustomCommand {
114         command: String,
115         args: Vec<String>,
116     },
117 }
118
119 #[derive(Debug, Clone, Default)]
120 pub struct ClientCapsConfig {
121     pub location_link: bool,
122     pub line_folding_only: bool,
123     pub hierarchical_symbols: bool,
124     pub code_action_literals: bool,
125     pub work_done_progress: bool,
126     pub code_action_group: bool,
127     pub resolve_code_action: bool,
128     pub hover_actions: bool,
129 }
130
131 impl Default for Config {
132     fn default() -> Self {
133         Config {
134             client_caps: ClientCapsConfig::default(),
135
136             with_sysroot: true,
137             publish_diagnostics: true,
138             lru_capacity: None,
139             proc_macro_srv: None,
140             files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
141             notifications: NotificationsConfig { cargo_toml_not_found: true },
142
143             cargo: CargoConfig::default(),
144             rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
145             check: Some(FlycheckConfig::CargoCommand {
146                 command: "check".to_string(),
147                 all_targets: true,
148                 all_features: false,
149                 extra_args: Vec::new(),
150             }),
151
152             inlay_hints: InlayHintsConfig {
153                 type_hints: true,
154                 parameter_hints: true,
155                 chaining_hints: true,
156                 max_length: None,
157             },
158             completion: CompletionConfig {
159                 enable_postfix_completions: true,
160                 add_call_parenthesis: true,
161                 add_call_argument_snippets: true,
162                 ..CompletionConfig::default()
163             },
164             assist: AssistConfig::default(),
165             call_info_full: true,
166             lens: LensConfig::default(),
167             hover: HoverConfig::default(),
168             linked_projects: Vec::new(),
169         }
170     }
171 }
172
173 impl Config {
174     #[rustfmt::skip]
175     pub fn update(&mut self, value: &serde_json::Value) {
176         log::info!("Config::update({:#})", value);
177
178         let client_caps = self.client_caps.clone();
179         *self = Default::default();
180         self.client_caps = client_caps;
181
182         set(value, "/withSysroot", &mut self.with_sysroot);
183         set(value, "/diagnostics/enable", &mut self.publish_diagnostics);
184         set(value, "/lruCapacity", &mut self.lru_capacity);
185         self.files.watcher = match get(value, "/files/watcher") {
186             Some("client") => FilesWatcher::Client,
187             Some("notify") | _ => FilesWatcher::Notify
188         };
189         set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found);
190
191         set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features);
192         set(value, "/cargo/allFeatures", &mut self.cargo.all_features);
193         set(value, "/cargo/features", &mut self.cargo.features);
194         set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
195         set(value, "/cargo/target", &mut self.cargo.target);
196
197         match get(value, "/procMacro/enable") {
198             Some(true) => {
199                 if let Ok(path) = std::env::current_exe() {
200                     self.proc_macro_srv = Some((path, vec!["proc-macro".into()]));
201                 }
202             }
203             _ => self.proc_macro_srv = None,
204         }
205
206         match get::<Vec<String>>(value, "/rustfmt/overrideCommand") {
207             Some(mut args) if !args.is_empty() => {
208                 let command = args.remove(0);
209                 self.rustfmt = RustfmtConfig::CustomCommand {
210                     command,
211                     args,
212                 }
213             }
214             _ => {
215                 if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt {
216                     set(value, "/rustfmt/extraArgs", extra_args);
217                 }
218             }
219         };
220
221         if let Some(false) = get(value, "/checkOnSave/enable") {
222             // check is disabled
223             self.check = None;
224         } else {
225             // check is enabled
226             match get::<Vec<String>>(value, "/checkOnSave/overrideCommand") {
227                 // first see if the user has completely overridden the command
228                 Some(mut args) if !args.is_empty() => {
229                     let command = args.remove(0);
230                     self.check = Some(FlycheckConfig::CustomCommand {
231                         command,
232                         args,
233                     });
234                 }
235                 // otherwise configure command customizations
236                 _ => {
237                     if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features })
238                         = &mut self.check
239                     {
240                         set(value, "/checkOnSave/extraArgs", extra_args);
241                         set(value, "/checkOnSave/command", command);
242                         set(value, "/checkOnSave/allTargets", all_targets);
243                         set(value, "/checkOnSave/allFeatures", all_features);
244                     }
245                 }
246             };
247         }
248
249         set(value, "/inlayHints/typeHints", &mut self.inlay_hints.type_hints);
250         set(value, "/inlayHints/parameterHints", &mut self.inlay_hints.parameter_hints);
251         set(value, "/inlayHints/chainingHints", &mut self.inlay_hints.chaining_hints);
252         set(value, "/inlayHints/maxLength", &mut self.inlay_hints.max_length);
253         set(value, "/completion/postfix/enable", &mut self.completion.enable_postfix_completions);
254         set(value, "/completion/addCallParenthesis", &mut self.completion.add_call_parenthesis);
255         set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets);
256         set(value, "/callInfo/full", &mut self.call_info_full);
257
258         let mut lens_enabled = true;
259         set(value, "/lens/enable", &mut lens_enabled);
260         if lens_enabled {
261             set(value, "/lens/run", &mut self.lens.run);
262             set(value, "/lens/debug", &mut self.lens.debug);
263             set(value, "/lens/implementations", &mut self.lens.impementations);
264         } else {
265             self.lens = LensConfig::NO_LENS;
266         }
267
268         if let Some(linked_projects) = get::<Vec<ManifestOrJsonProject>>(value, "/linkedProjects") {
269             if !linked_projects.is_empty() {
270                 self.linked_projects.clear();
271                 for linked_project in linked_projects {
272                     let linked_project = match linked_project {
273                         ManifestOrJsonProject::Manifest(it) => match ProjectManifest::from_manifest_file(it) {
274                             Ok(it) => it.into(),
275                             Err(_) => continue,
276                         }
277                         ManifestOrJsonProject::JsonProject(it) => it.into(),
278                     };
279                     self.linked_projects.push(linked_project);
280                 }
281             }
282         }
283
284         let mut use_hover_actions = false;
285         set(value, "/hoverActions/enable", &mut use_hover_actions);
286         if use_hover_actions {
287             set(value, "/hoverActions/implementations", &mut self.hover.implementations);
288         } else {
289             self.hover = HoverConfig::NO_ACTIONS;
290         }
291
292         log::info!("Config::update() = {:#?}", self);
293
294         fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
295             value.pointer(pointer).and_then(|it| T::deserialize(it).ok())
296         }
297
298         fn set<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str, slot: &mut T) {
299             if let Some(new_value) = get(value, pointer) {
300                 *slot = new_value
301             }
302         }
303     }
304
305     pub fn update_caps(&mut self, caps: &ClientCapabilities) {
306         if let Some(doc_caps) = caps.text_document.as_ref() {
307             if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) {
308                 self.client_caps.location_link = value;
309             }
310             if let Some(value) = doc_caps.folding_range.as_ref().and_then(|it| it.line_folding_only)
311             {
312                 self.client_caps.line_folding_only = value
313             }
314             if let Some(value) = doc_caps
315                 .document_symbol
316                 .as_ref()
317                 .and_then(|it| it.hierarchical_document_symbol_support)
318             {
319                 self.client_caps.hierarchical_symbols = value
320             }
321             if let Some(value) =
322                 doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some())
323             {
324                 self.client_caps.code_action_literals = value;
325             }
326
327             self.completion.allow_snippets(false);
328             if let Some(completion) = &doc_caps.completion {
329                 if let Some(completion_item) = &completion.completion_item {
330                     if let Some(value) = completion_item.snippet_support {
331                         self.completion.allow_snippets(value);
332                     }
333                 }
334             }
335         }
336
337         if let Some(window_caps) = caps.window.as_ref() {
338             if let Some(value) = window_caps.work_done_progress {
339                 self.client_caps.work_done_progress = value;
340             }
341         }
342
343         self.assist.allow_snippets(false);
344         if let Some(experimental) = &caps.experimental {
345             let get_bool = |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
346
347             let snippet_text_edit = get_bool("snippetTextEdit");
348             self.assist.allow_snippets(snippet_text_edit);
349
350             self.client_caps.code_action_group = get_bool("codeActionGroup");
351                         self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
352             self.client_caps.hover_actions = get_bool("hoverActions");
353         }
354     }
355 }
356
357 #[derive(Deserialize)]
358 #[serde(untagged)]
359 enum ManifestOrJsonProject {
360     Manifest(PathBuf),
361     JsonProject(JsonProject),
362 }