Repository: rust-analyzer/rust-analyzer
Pull request #7170 (2021-01-05 14:35:52)

More maintainable caps config
bors r+
🤖

bors[bot]: Build failed:
  * [Rust (macos-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/1650528950)
matklad: bors r+
bors[bot]: Build failed:
  * [Rust (ubuntu-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/1650565224)

Patch:
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index a5b1d90b1d..a80652e839 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -175,7 +175,7 @@ config_data! {
 
 #[derive(Debug, Clone)]
 pub struct Config {
-    pub client_caps: ClientCapsConfig,
+    pub caps: lsp_types::ClientCapabilities,
 
     pub publish_diagnostics: bool,
     pub diagnostics: DiagnosticsConfig,
@@ -286,26 +286,12 @@ pub struct RunnablesConfig {
     pub cargo_extra_args: Vec<String>,
 }
 
-#[derive(Debug, Clone, Default)]
-pub struct ClientCapsConfig {
-    pub location_link: bool,
-    pub line_folding_only: bool,
-    pub hierarchical_symbols: bool,
-    pub code_action_literals: bool,
-    pub work_done_progress: bool,
-    pub code_action_group: bool,
-    pub code_action_resolve: bool,
-    pub hover_actions: bool,
-    pub status_notification: bool,
-    pub signature_help_label_offsets: bool,
-}
-
 impl Config {
     pub fn new(root_path: AbsPathBuf) -> Self {
         // Defaults here don't matter, we'll immediately re-write them with
         // ConfigData.
         let mut res = Config {
-            client_caps: ClientCapsConfig::default(),
+            caps: lsp_types::ClientCapabilities::default(),
 
             publish_diagnostics: false,
             diagnostics: DiagnosticsConfig::default(),
@@ -505,38 +491,11 @@ impl Config {
     }
 
     pub fn update_caps(&mut self, caps: &ClientCapabilities) {
+        self.caps = caps.clone();
         if let Some(doc_caps) = caps.text_document.as_ref() {
             if let Some(value) = doc_caps.hover.as_ref().and_then(|it| it.content_format.as_ref()) {
                 self.hover.markdown = value.contains(&MarkupKind::Markdown)
             }
-            if let Some(value) = doc_caps.definition.as_ref().and_then(|it| it.link_support) {
-                self.client_caps.location_link = value;
-            }
-            if let Some(value) = doc_caps.folding_range.as_ref().and_then(|it| it.line_folding_only)
-            {
-                self.client_caps.line_folding_only = value
-            }
-            if let Some(value) = doc_caps
-                .document_symbol
-                .as_ref()
-                .and_then(|it| it.hierarchical_document_symbol_support)
-            {
-                self.client_caps.hierarchical_symbols = value
-            }
-            if let Some(value) =
-                doc_caps.code_action.as_ref().map(|it| it.code_action_literal_support.is_some())
-            {
-                self.client_caps.code_action_literals = value;
-            }
-            if let Some(value) = doc_caps
-                .signature_help
-                .as_ref()
-                .and_then(|it| it.signature_information.as_ref())
-                .and_then(|it| it.parameter_information.as_ref())
-                .and_then(|it| it.label_offset_support)
-            {
-                self.client_caps.signature_help_label_offsets = value;
-            }
 
             self.completion.allow_snippets(false);
             self.completion.active_resolve_capabilities =
@@ -548,20 +507,6 @@ impl Config {
                     }
                 }
             }
-
-            if let Some(code_action) = &doc_caps.code_action {
-                if let Some(resolve_support) = &code_action.resolve_support {
-                    if resolve_support.properties.iter().any(|it| it == "edit") {
-                        self.client_caps.code_action_resolve = true;
-                    }
-                }
-            }
-        }
-
-        if let Some(window_caps) = caps.window.as_ref() {
-            if let Some(value) = window_caps.work_done_progress {
-                self.client_caps.work_done_progress = value;
-            }
         }
 
         self.assist.allow_snippets(false);
@@ -571,10 +516,6 @@ impl Config {
 
             let snippet_text_edit = get_bool("snippetTextEdit");
             self.assist.allow_snippets(snippet_text_edit);
-
-            self.client_caps.code_action_group = get_bool("codeActionGroup");
-            self.client_caps.hover_actions = get_bool("hoverActions");
-            self.client_caps.status_notification = get_bool("statusNotification");
         }
 
         if let Some(workspace_caps) = caps.workspace.as_ref() {
@@ -597,6 +538,95 @@ impl Config {
     }
 }
 
+macro_rules! try_ {
+    ($expr:expr) => {
+        || -> _ { Some($expr) }()
+    };
+}
+macro_rules! try_or {
+    ($expr:expr, $or:expr) => {
+        try_!($expr).unwrap_or($or)
+    };
+}
+
+impl Config {
+    pub fn location_link(&self) -> bool {
+        try_or!(self.caps.text_document.as_ref()?.definition?.link_support?, false)
+    }
+    pub fn line_folding_only(&self) -> bool {
+        try_or!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?, false)
+    }
+    pub fn hierarchical_symbols(&self) -> bool {
+        try_or!(
+            self.caps
+                .text_document
+                .as_ref()?
+                .document_symbol
+                .as_ref()?
+                .hierarchical_document_symbol_support?,
+            false
+        )
+    }
+    pub fn code_action_literals(&self) -> bool {
+        try_!(self
+            .caps
+            .text_document
+            .as_ref()?
+            .code_action
+            .as_ref()?
+            .code_action_literal_support
+            .as_ref()?)
+        .is_some()
+    }
+    pub fn work_done_progress(&self) -> bool {
+        try_or!(self.caps.window.as_ref()?.work_done_progress?, false)
+    }
+    pub fn code_action_resolve(&self) -> bool {
+        try_or!(
+            self.caps
+                .text_document
+                .as_ref()?
+                .code_action
+                .as_ref()?
+                .resolve_support
+                .as_ref()?
+                .properties
+                .as_slice(),
+            &[]
+        )
+        .iter()
+        .any(|it| it == "edit")
+    }
+    pub fn signature_help_label_offsets(&self) -> bool {
+        try_or!(
+            self.caps
+                .text_document
+                .as_ref()?
+                .signature_help
+                .as_ref()?
+                .signature_information
+                .as_ref()?
+                .parameter_information
+                .as_ref()?
+                .label_offset_support?,
+            false
+        )
+    }
+
+    fn experimental(&self, index: &'static str) -> bool {
+        try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false)
+    }
+    pub fn code_action_group(&self) -> bool {
+        self.experimental("codeActionGroup")
+    }
+    pub fn hover_actions(&self) -> bool {
+        self.experimental("hoverActions")
+    }
+    pub fn status_notification(&self) -> bool {
+        self.experimental("statusNotification")
+    }
+}
+
 #[derive(Deserialize)]
 #[serde(untagged)]
 enum ManifestOrProjectJson {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index c21ca044ae..c13cdc4e38 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -320,7 +320,7 @@ pub(crate) fn handle_document_symbol(
         acc
     };
 
-    let res = if snap.config.client_caps.hierarchical_symbols {
+    let res = if snap.config.hierarchical_symbols() {
         document_symbols.into()
     } else {
         let url = to_proto::url(&snap, file_id);
@@ -727,7 +727,7 @@ pub(crate) fn handle_folding_range(
     let folds = snap.analysis.folding_ranges(file_id)?;
     let text = snap.analysis.file_text(file_id)?;
     let line_index = snap.analysis.file_line_index(file_id)?;
-    let line_folding_only = snap.config.client_caps.line_folding_only;
+    let line_folding_only = snap.config.line_folding_only();
     let res = folds
         .into_iter()
         .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it))
@@ -746,11 +746,8 @@ pub(crate) fn handle_signature_help(
         None => return Ok(None),
     };
     let concise = !snap.config.call_info_full;
-    let res = to_proto::signature_help(
-        call_info,
-        concise,
-        snap.config.client_caps.signature_help_label_offsets,
-    );
+    let res =
+        to_proto::signature_help(call_info, concise, snap.config.signature_help_label_offsets());
     Ok(Some(res))
 }
 
@@ -929,7 +926,7 @@ pub(crate) fn handle_code_action(
     // We intentionally don't support command-based actions, as those either
     // requires custom client-code anyway, or requires server-initiated edits.
     // Server initiated edits break causality, so we avoid those as well.
-    if !snap.config.client_caps.code_action_literals {
+    if !snap.config.code_action_literals() {
         return Ok(None);
     }
 
@@ -959,7 +956,7 @@ pub(crate) fn handle_code_action(
         add_quick_fixes(&snap, frange, &line_index, &mut res)?;
     }
 
-    if snap.config.client_caps.code_action_resolve {
+    if snap.config.code_action_resolve() {
         for (index, assist) in
             snap.analysis.assists(&assists_config, false, frange)?.into_iter().enumerate()
         {
@@ -1542,7 +1539,7 @@ fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command {
 }
 
 fn goto_location_command(snap: &GlobalStateSnapshot, nav: &NavigationTarget) -> Option<Command> {
-    let value = if snap.config.client_caps.location_link {
+    let value = if snap.config.location_link() {
         let link = to_proto::location_link(snap, None, nav.clone()).ok()?;
         to_value(link).ok()?
     } else {
@@ -1641,7 +1638,7 @@ fn prepare_hover_actions(
     file_id: FileId,
     actions: &[HoverAction],
 ) -> Vec<lsp_ext::CommandLinkGroup> {
-    if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
+    if snap.config.hover.none() || !snap.config.hover_actions() {
         return Vec::new();
     }
 
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 60c12e4e2e..40de56dadc 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -46,7 +46,7 @@ impl GlobalState {
         message: Option<String>,
         fraction: Option<f64>,
     ) {
-        if !self.config.client_caps.work_done_progress {
+        if !self.config.work_done_progress() {
             return;
         }
         let percentage = fraction.map(|f| {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 79e39e3a59..ce5cedeb34 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -79,7 +79,7 @@ impl GlobalState {
     }
     pub(crate) fn transition(&mut self, new_status: Status) {
         self.status = new_status;
-        if self.config.client_caps.status_notification {
+        if self.config.status_notification() {
             let lsp_status = match new_status {
                 Status::Loading => lsp_ext::Status::Loading,
                 Status::Ready => lsp_ext::Status::Ready,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 999b183515..e0413ec06e 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -605,7 +605,7 @@ pub(crate) fn goto_definition_response(
     src: Option<FileRange>,
     targets: Vec<NavigationTarget>,
 ) -> Result<lsp_types::GotoDefinitionResponse> {
-    if snap.config.client_caps.location_link {
+    if snap.config.location_link() {
         let links = targets
             .into_iter()
             .map(|nav| location_link(snap, src, nav))
@@ -785,7 +785,7 @@ pub(crate) fn unresolved_code_action(
     assert!(assist.source_change.is_none());
     let res = lsp_ext::CodeAction {
         title: assist.label.to_string(),
-        group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
+        group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
         kind: Some(code_action_kind(assist.id.1)),
         edit: None,
         is_preferred: None,
@@ -805,7 +805,7 @@ pub(crate) fn resolved_code_action(
     let res = lsp_ext::CodeAction {
         edit: Some(snippet_workspace_edit(snap, change)?),
         title: assist.label.to_string(),
-        group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
+        group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
         kind: Some(code_action_kind(assist.id.1)),
         is_preferred: None,
         data: None,


Test patch:
diff --git a/crates/rust-analyzer/tests/rust-analyzer/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index 4561257893..aac7dbccec 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
@@ -14,7 +14,7 @@ use lsp_types::{
 use lsp_types::{ProgressParams, ProgressParamsValue};
 use project_model::{CargoConfig, ProjectManifest};
 use rust_analyzer::{
-    config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject},
+    config::{Config, FilesConfig, FilesWatcher, LinkedProject},
     main_loop,
 };
 use serde::Serialize;
@@ -84,10 +84,24 @@ impl<'a> Project<'a> {
             .collect::<Vec<_>>();
 
         let mut config = Config {
-            client_caps: ClientCapsConfig {
-                location_link: true,
-                code_action_literals: true,
-                work_done_progress: true,
+            caps: lsp_types::ClientCapabilities {
+                text_document: Some(lsp_types::TextDocumentClientCapabilities {
+                    definition: Some(lsp_types::GotoCapability {
+                        link_support: Some(true),
+                        ..Default::default()
+                    }),
+                    code_action: Some(lsp_types::CodeActionClientCapabilities {
+                        code_action_literal_support: Some(
+                            lsp_types::CodeActionLiteralSupport::default(),
+                        ),
+                        ..Default::default()
+                    }),
+                    ..Default::default()
+                }),
+                window: Some(lsp_types::WindowClientCapabilities {
+                    work_done_progress: Some(true),
+                    ..Default::default()
+                }),
                 ..Default::default()
             },
             cargo: CargoConfig { no_sysroot: !self.with_sysroot, ..Default::default() },