cpp_ls 0.3.0

A C, C++ and CUDA language server.
Documentation
//! # Telling the client what to watch
//!
//! `workspace/didChangeWatchedFiles` is only useful if the client knows which files to report, and the LSP way to
//! say so is a **dynamic registration**: `client/registerCapability` with a list of globs.
//!
//! ```text
//! **/*.{c,cc,cpp,cxx,h,hh,hpp,hxx,ipp,inl,tpp,tcc}    the sources and headers this analysis reads
//! **/compile_commands.json                          the file that configures it
//! **/*.cppm, **/*.ixx                               module interfaces (a C++20 project's own kind of source)
//! ```
//!
//! Clients with dynamic registration provide the events; otherwise the server uses a native watcher.

use log::info;
use lsp_types::{
    DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher, GlobPattern, Registration,
    RegistrationParams, Unregistration, UnregistrationParams, WatchKind,
};

use crate::context::{ClientProxy, ServerContextSnapshot};

/// The registration id, so that a reload can replace the previous registration rather than add another one.
const WATCH_FILES_REGISTRATION_ID: &str = "cpp_ls_watched_files";

/// The globs a client is asked to report on.
///
/// Braces are part of the LSP glob dialect (`GlobPattern::String` is matched with the client's own implementation,
/// which follows the protocol's grammar), and this is the same extension list the analysis scans a project for —
/// one list, so that "what the client tells us about" and "what the scan picks up" cannot drift apart.
///
/// The last three are the files that **configure** the analysis rather than being part of it
/// (`cpp_code_analysis::CONFIGURATION_FILE_NAMES`): a change to one of them is not an edit to index, it is a reason
/// to work the project out again, and a client that never mentions them would leave a workspace analysing a
/// compilation that no longer exists.
const WATCHED_GLOBS: &[&str] = &[
    "**/*.{c,cc,cpp,cxx,c++,h,hh,hpp,hxx,h++,ipp,inl,tpp,tcc}",
    "**/*.{cppm,ixx}",
    "**/.cppls.toml",
    "**/compile_commands.json",
    "**/CMakeCache.txt",
];

pub async fn register_files_watch(context: ServerContextSnapshot) {
    if !context
        .lsp_features()
        .supports_dynamic_watched_files_registration()
    {
        return;
    }

    if context
        .workspace_manager()
        .lock()
        .await
        .watch_filter()
        .is_none()
    {
        // Nothing to watch yet. `initialized` registers again once a folder is named, which is the only moment the
        // globs mean anything.
        return;
    }

    register(context.client());
    info!("asked the client to watch {} globs", WATCHED_GLOBS.len());
}

fn register(client: &ClientProxy) {
    // Unregister first: a reload registers again, and two registrations of the same globs would report every event
    // twice — which is not a correctness problem for the analysis (the batch is idempotent) but is a doubled cost
    // in every event the user makes.
    unregister(client);

    let options = DidChangeWatchedFilesRegistrationOptions {
        watchers: WATCHED_GLOBS
            .iter()
            .map(|glob| FileSystemWatcher {
                glob_pattern: GlobPattern::String((*glob).to_string()),
                kind: Some(WatchKind::Create | WatchKind::Change | WatchKind::Delete),
            })
            .collect(),
    };

    client.dynamic_register_capability(RegistrationParams {
        registrations: vec![Registration {
            id: WATCH_FILES_REGISTRATION_ID.to_string(),
            method: "workspace/didChangeWatchedFiles".to_string(),
            register_options: Some(
                serde_json::to_value(options).expect("the registration options serialize"),
            ),
        }],
    });
}

fn unregister(client: &ClientProxy) {
    client.dynamic_unregister_capability(UnregistrationParams {
        unregisterations: vec![Unregistration {
            id: WATCH_FILES_REGISTRATION_ID.to_string(),
            method: "workspace/didChangeWatchedFiles".to_string(),
        }],
    });
}