1 //! Config used by the language server.
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.
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.
10 use std::{ffi::OsString, path::PathBuf};
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;
18 #[derive(Debug, Clone)]
20 pub client_caps: ClientCapsConfig,
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,
28 pub cargo: CargoConfig,
29 pub rustfmt: RustfmtConfig,
30 pub check: Option<FlycheckConfig>,
32 pub inlay_hints: InlayHintsConfig,
33 pub completion: CompletionConfig,
34 pub assist: AssistConfig,
35 pub call_info_full: bool,
37 pub hover: HoverConfig,
39 pub with_sysroot: bool,
40 pub linked_projects: Vec<LinkedProject>,
43 #[derive(Debug, Clone)]
44 pub enum LinkedProject {
45 ProjectManifest(ProjectManifest),
46 JsonProject(JsonProject),
49 impl From<ProjectManifest> for LinkedProject {
50 fn from(v: ProjectManifest) -> Self {
51 LinkedProject::ProjectManifest(v)
55 impl From<JsonProject> for LinkedProject {
56 fn from(v: JsonProject) -> Self {
57 LinkedProject::JsonProject(v)
61 #[derive(Clone, Debug, PartialEq, Eq)]
62 pub struct LensConfig {
65 pub impementations: bool,
68 impl Default for LensConfig {
69 fn default() -> Self {
70 Self { run: true, debug: true, impementations: true }
75 pub const NO_LENS: LensConfig = Self { run: false, debug: false, impementations: false };
77 pub fn any(&self) -> bool {
78 self.impementations || self.runnable()
81 pub fn none(&self) -> bool {
85 pub fn runnable(&self) -> bool {
86 self.run || self.debug
90 #[derive(Debug, Clone)]
91 pub struct FilesConfig {
92 pub watcher: FilesWatcher,
93 pub exclude: Vec<String>,
96 #[derive(Debug, Clone)]
97 pub enum FilesWatcher {
102 #[derive(Debug, Clone)]
103 pub struct NotificationsConfig {
104 pub cargo_toml_not_found: bool,
107 #[derive(Debug, Clone)]
108 pub enum RustfmtConfig {
110 extra_args: Vec<String>,
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,
131 impl Default for Config {
132 fn default() -> Self {
134 client_caps: ClientCapsConfig::default(),
137 publish_diagnostics: true,
139 proc_macro_srv: None,
140 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
141 notifications: NotificationsConfig { cargo_toml_not_found: true },
143 cargo: CargoConfig::default(),
144 rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
145 check: Some(FlycheckConfig::CargoCommand {
146 command: "check".to_string(),
149 extra_args: Vec::new(),
152 inlay_hints: InlayHintsConfig {
154 parameter_hints: true,
155 chaining_hints: true,
158 completion: CompletionConfig {
159 enable_postfix_completions: true,
160 add_call_parenthesis: true,
161 add_call_argument_snippets: true,
162 ..CompletionConfig::default()
164 assist: AssistConfig::default(),
165 call_info_full: true,
166 lens: LensConfig::default(),
167 hover: HoverConfig::default(),
168 linked_projects: Vec::new(),
175 pub fn update(&mut self, value: &serde_json::Value) {
176 log::info!("Config::update({:#})", value);
178 let client_caps = self.client_caps.clone();
179 *self = Default::default();
180 self.client_caps = client_caps;
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
189 set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found);
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);
197 match get(value, "/procMacro/enable") {
199 if let Ok(path) = std::env::current_exe() {
200 self.proc_macro_srv = Some((path, vec!["proc-macro".into()]));
203 _ => self.proc_macro_srv = None,
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 {
215 if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt {
216 set(value, "/rustfmt/extraArgs", extra_args);
221 if let Some(false) = get(value, "/checkOnSave/enable") {
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 {
235 // otherwise configure command customizations
237 if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features })
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);
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);
258 let mut lens_enabled = true;
259 set(value, "/lens/enable", &mut 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);
265 self.lens = LensConfig::NO_LENS;
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) {
277 ManifestOrJsonProject::JsonProject(it) => it.into(),
279 self.linked_projects.push(linked_project);
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);
289 self.hover = HoverConfig::NO_ACTIONS;
292 log::info!("Config::update() = {:#?}", self);
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())
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) {
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;
310 if let Some(value) = doc_caps.folding_range.as_ref().and_then(|it| it.line_folding_only)
312 self.client_caps.line_folding_only = value
314 if let Some(value) = doc_caps
317 .and_then(|it| it.hierarchical_document_symbol_support)
319 self.client_caps.hierarchical_symbols = value
322 doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some())
324 self.client_caps.code_action_literals = value;
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);
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;
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);
347 let snippet_text_edit = get_bool("snippetTextEdit");
348 self.assist.allow_snippets(snippet_text_edit);
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");
357 #[derive(Deserialize)]
359 enum ManifestOrJsonProject {
361 JsonProject(JsonProject),