cpp_ls 0.1.3

A C, C++ and CUDA language server.
Documentation
use lsp_types::ClientCapabilities;

use super::client_id::ClientId;

#[derive(Debug)]
pub struct LspFeatures {
    client_capabilities: ClientCapabilities,
    /// **Which editor this is**, from `initialize.clientInfo` — the question a *custom* semantic token type turns on,
    /// because whether a client can draw one is not in its capability list. See
    /// [`LspFeatures::client_id`].
    client_id: ClientId,
}

#[allow(unused)]
impl LspFeatures {
    pub fn new(client_capabilities: ClientCapabilities, client_id: ClientId) -> Self {
        Self {
            client_capabilities,
            client_id,
        }
    }

    /// **Which editor asked.** A client's capability list names the *standard* semantic token types it knows, so a
    /// custom one (`formatSpecifier`) is never in it — even for a client that draws it through its own theme
    /// mapping. This is the fact that decides whether a custom type is still worth sending; see
    /// [`crate::handlers::semantic_token`] and `draw.rs`'s note on the fallbacks.
    pub fn client_id(&self) -> ClientId {
        self.client_id
    }

    pub fn supports_multiline_tokens(&self) -> bool {
        self.client_capabilities
            .text_document
            .as_ref()
            .and_then(|text_document| text_document.semantic_tokens.as_ref())
            .and_then(|semantic_tokens| semantic_tokens.multiline_token_support)
            .unwrap_or_default()
    }

    /// **The token types the client says it can draw** — `None` when it does not do semantic tokens at all.
    ///
    /// The server's legend is fixed (the numbers in an answer are indices into it), so this is a *filter* rather
    /// than a legend: a kind the client cannot draw is left out of the answer instead of being sent as a number it
    /// maps to nothing. An empty list is treated as "said nothing" by the caller, because a client that asks for
    /// tokens without naming any type is one this server cannot filter for.
    pub fn semantic_token_types(&self) -> Option<Vec<lsp_types::SemanticTokenType>> {
        self.client_capabilities
            .text_document
            .as_ref()
            .and_then(|text_document| text_document.semantic_tokens.as_ref())
            .map(|semantic_tokens| semantic_tokens.token_types.clone())
    }

    pub fn supports_config_request(&self) -> bool {
        self.client_capabilities
            .workspace
            .as_ref()
            .and_then(|workspace| workspace.configuration)
            .unwrap_or_default()
    }

    pub fn supports_work_done_progress(&self) -> bool {
        self.client_capabilities
            .window
            .as_ref()
            .and_then(|window| window.work_done_progress)
            .unwrap_or_default()
    }

    /// **Can the client interpolate a snippet body?**
    ///
    /// The one capability a completion has to branch on, because the cost of guessing wrong is asymmetric: a client
    /// that supports snippets and is not sent any loses a convenience, while a client that does not and is sent one
    /// writes `${1:condition}` into the file as literal text.
    ///
    /// `snippetSupport` is the protocol's flag for exactly this question, and its absence is a **no**: the field's
    /// own note says a client that does not set it is assumed to take plain text only. That default matters here
    /// more than usual, because the failure is not a missing feature — it is placeholder syntax written into the
    /// user's file, which they then have to delete.
    pub fn supports_snippets(&self) -> bool {
        self.client_capabilities
            .text_document
            .as_ref()
            .and_then(|text_document| text_document.completion.as_ref())
            .and_then(|completion| completion.completion_item.as_ref())
            .and_then(|item| item.snippet_support)
            .unwrap_or(false)
    }

    pub fn supports_pull_diagnostic(&self) -> bool {
        if let Some(text_document) = &self.client_capabilities.text_document {
            return text_document.diagnostic.is_some();
        }
        false
    }

    pub fn supports_workspace_diagnostic(&self) -> bool {
        self.supports_pull_diagnostic()
    }

    /// May the server ask the client to ask again for the inlay hints it is showing?
    ///
    /// The capability that makes a hint answer **repairable**. A request that arrives while the index is still
    /// reading is answered with *fewer* hints than the file deserves — a callee in a file nobody has read yet
    /// resolves to nothing, and a hint naming the wrong parameter would be worse than none — so the answer changes
    /// without the document changing, which is the one case a client cannot notice by itself. See
    /// [`Client::refresh_inlay_hints`], where the measurement behind this lives.
    pub fn supports_refresh_inlay_hint(&self) -> bool {
        self.client_capabilities
            .workspace
            .as_ref()
            .and_then(|workspace| workspace.inlay_hint.as_ref())
            .and_then(|hint| hint.refresh_support)
            .unwrap_or_default()
    }

    /// May the server ask the client to ask again for the **semantic tokens** it is showing?
    ///
    /// The same repair as [`LspFeatures::supports_refresh_inlay_hint`], for the other answer that changes without
    /// the document changing: a name whose declaration lives in a header nobody has read yet is drawn with the
    /// client's own grammar colour rather than the analysis's, and the client has no reason to ask again. A server
    /// that answers a token request while the file's include closure is still unread therefore produces a file
    /// coloured **once, wrongly**, until the next edit — which is what "`std::endl` is not a function until I type
    /// something" was.
    pub fn supports_refresh_semantic_tokens(&self) -> bool {
        self.client_capabilities
            .workspace
            .as_ref()
            .and_then(|workspace| workspace.semantic_tokens.as_ref())
            .and_then(|tokens| tokens.refresh_support)
            .unwrap_or_default()
    }

    pub fn supports_dynamic_watched_files_registration(&self) -> bool {
        self.client_capabilities
            .workspace
            .as_ref()
            .and_then(|ws| ws.did_change_watched_files.as_ref())
            .and_then(|watch| watch.dynamic_registration)
            .unwrap_or_default()
    }
}