cpp_ls 0.2.0

A C, C++ and CUDA language server.
Documentation
//! # LSP handlers — one module per capability
//!
//! Every module here has the same two jobs:
//!
//! ```text
//! 1. a handler function with the signature the dispatch table calls:
//!      request:      async fn(ServerContextSnapshot, Params, CancellationToken) -> RequestOutcome<Result>
//!      notification: async fn(ServerContextSnapshot, Params) -> Option<()>
//! 2. a `RegisterCapabilities` implementation, which is what the server advertises in `initialize`
//! ```
//!
//! Both are wired in two places and nowhere else: [`request_handler`] / [`notification_handler`] for the
//! dispatch, and the `capabilities!` table at the bottom of this file for what the client is told. A module
//! that is in one and not the other is a handler the client will never call (or a capability with no
//! implementation), so **the two lists are read together**
//!
//! The table below is deliberately short: it is the set of capabilities this server answers *today*. Adding
//! one is three edits — the module, the dispatch row, the capability row — and the modules that were removed
//! from the Lua skeleton (`semantic_token`, `signature_help`, …) come back the same way: `completion`,
//! `document_symbol`, `folding_range`, `references`, `rename`, `selection_range`, `workspace_symbol` and
//! `inlay_hint` all came back that way.

pub mod call_hierarchy;
mod code_action;
mod completion;
mod configuration;
mod definition;
pub mod diagnostic;
mod document_highlight;
mod document_link;
mod document_symbol;
mod folding_range;
mod format;
mod formatting;
pub mod hover;
pub mod implementation;
mod initialized;
mod inlay_hint;
mod notification_handler;
pub mod references;
mod rename;
mod request_handler;
mod response_handler;
mod selection_range;
mod semantic_token;
mod signature_help;
mod switch_source_header;
mod text_document;
mod type_definition;
mod workspace_symbol;

use crate::context::ServerContextSnapshot;
pub use initialized::{ClientConfig, initialized_handler, start_analysis};
use lsp_types::{ClientCapabilities, ServerCapabilities};
pub use notification_handler::on_notification_handler;
pub use request_handler::on_request_handler;
pub use response_handler::on_response_handler;
pub use text_document::register_files_watch;
pub use text_document::{
    process_did_change_text_document, process_did_change_watched_files, process_did_close_document,
    process_did_open_text_document, process_did_save_text_document,
};

/// **The module interface units this file imports, read in** — the half of "read the file in first" that a project
/// scan cannot do.
///
/// `import mathlib;` reaches a file because the project indexed it. `import std;` (C++23) does not: MSVC ships the
/// source of `std` as `<VC>/Tools/MSVC/<version>/modules/std.ixx`, **outside the project**, so until something reads
/// that file there is no summary of it anywhere and every `std::` name in a file that imports it answers nothing —
/// silently, which is the failure mode this server keeps having to remove. [`Analysis::prepare`] handles the other
/// half (the file the request is about); this is the half that lives outside the workspace.
///
/// # When it is called, and why the order matters
///
/// **After** the file's summary is current — `prepare` is not enough, because an edit drops a file's summary and the
/// pump rebuilds it later, and a file with no summary has no imports to read. A handler that has an edit waiting calls
/// [`Session::catch_up`] first for exactly that reason, and passing `caught_up` says so rather than leaving a reader
/// to work out whether this call belongs before or after it.
///
/// # What it costs
///
/// Measured on the module fixture: **6 151 ms the first time on a machine** (401 files — the whole standard library,
/// which is what `std.ixx` includes) and **547.7 ms** once the summaries are on disk. Asking a second time reads
/// nothing in and takes **0.004 ms**, so the cost is per session-machine pair rather than per keystroke. A handler
/// that answers a name query is the right place to pay it and the pump is not: nobody should pay for the standard
/// library who is not asking a question about it.
///
/// [`Analysis::prepare`]: crate::context::AnalysisState::prepare
/// [`Session::catch_up`]: cpp_code_analysis::Session::catch_up
pub async fn read_the_modules(
    context: &ServerContextSnapshot,
    path: &std::path::Path,
    caught_up: bool,
) {
    // **Nothing to read, so no lock to take.** Both halves of this call are conditional and both are asked here
    // first: the summary is current (`caught_up` says the caller already did it, `needs_catching_up` asks the
    // session), and no module in view is one the index cannot point at a file for — which is the ordinary answer for
    // a file that imports nothing, and the answer for every call after the first on a file that does.
    //
    // The check is a **read**, and a read does not wait for the pump's parse; the write would. Measured over the
    // wire on the latency fixture, this call was the last **151–1648 ms** of a completion's wait once the same
    // shortcut was in place for `prepare` and `catch_up`.
    let nothing_to_read = context
        .analysis()
        .with_snapshot(|session| {
            (caught_up || !session.needs_catching_up(path))
                && !session.imports_a_module_that_is_not_read(path)
                && !session.a_module_unit_is_waiting_for_its_program()
        })
        .unwrap_or(false);
    if nothing_to_read {
        return;
    }

    let read = path.to_path_buf();

    // **A request queues the module work; it does not drain the index for it.**
    //
    // This closure used to do three things: catch the file up (a parse, milliseconds), queue the interface unit the
    // file imports, and then **drain the whole index queue** — `while session.pending() > 0 { session.advance(256) }`,
    // up to four hundred rounds, then up to sixteen module units, three times over. Measured on the first hover of a
    // session: **1 468 ms** of a 1 862 ms request, spent indexing the project under the write lock, for a `.cpp` that
    // imports no module at all.
    //
    // What a request owes the answer is the part that makes the *reading* current — `catch_up` — and the queueing.
    // The draining is the pump's (`handlers::initialized`'s slice reads the module units the queue names), which is
    // the same split as the second pass: requests ask, the background pays.
    context
        .analysis()
        .update_session("a request queued the modules", move |session| {
            if !caught_up {
                session.catch_up(&read);
            }
            session.queue_the_modules_a_file_imports(&read)
        })
        .await;

    // The pump is what reads them; it is told there is work.
    context.analysis().wake();
}

/// What a module has to answer to be advertised in `initialize`.
///
/// One trait rather than a match over method names, because the *decision* belongs to the module that knows
/// what it can do, and the client's own capabilities are half of that decision — a client that cannot show a
/// popup at all is one a hover provider does not have to be announced to, and the flag that says so is read by
/// the module that would have to honour it rather than by a table here.
pub trait RegisterCapabilities {
    fn register_capabilities(
        server_capabilities: &mut ServerCapabilities,
        client_capabilities: &ClientCapabilities,
    );
}

macro_rules! capabilities {
    // module name => capability type mapping
    (modules: {
        $($module:ident => $capability:ident),* $(,)?
    }) => {
        pub fn server_capabilities(client_capabilities: &ClientCapabilities) -> ServerCapabilities {
            let mut server_capabilities = ServerCapabilities::default();

            $(
                $module::$capability::register_capabilities(&mut server_capabilities, client_capabilities);
            )*

            server_capabilities
        }
    };
}

capabilities!(modules: {
    // The document lifecycle is not optional: without it no request ever has a file to answer about.
    text_document => TextDocumentCapabilities,
    // The capabilities the C++ analysis can already answer, or is being built to answer first.
    definition => DefinitionCapabilities,
    hover => HoverCapabilities,
    call_hierarchy => CallHierarchyCapabilities,
    code_action => CodeActionCapabilities,
    completion => CompletionCapabilities,
    document_highlight => DocumentHighlightCapabilities,
    document_link => DocumentLinkCapabilities,
    document_symbol => DocumentSymbolCapabilities,
    formatting => FormattingCapabilities,
    folding_range => FoldingRangeCapabilities,
    implementation => ImplementationCapabilities,
    type_definition => TypeDefinitionCapabilities,
    inlay_hint => InlayHintCapabilities,
    references => ReferencesCapabilities,
    rename => RenameCapabilities,
    selection_range => SelectionRangeCapabilities,
signature_help => SignatureHelpCapabilities,
semantic_token => SemanticTokenCapabilities,
    switch_source_header => SwitchSourceHeaderCapabilities,
    workspace_symbol => WorkspaceSymbolCapabilities,
    diagnostic => DiagnosticCapabilities,
    configuration => ConfigurationCapabilities,
});