merman-lsp 0.8.0-alpha.3

Language server for Merman diagnostics and completion foundations.
Documentation
use crate::document_store::{DocumentStore, SnapshotBuildRequest, SnapshotContext};
use crate::snapshot::DocumentSnapshot;
use std::sync::Arc;
use tokio::sync::Mutex;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::Url;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SnapshotContextKind {
    SemanticTokens,
    Structure,
    WorkspaceSymbols,
}

impl SnapshotContextKind {
    pub(crate) fn stale_error(self) -> tower_lsp::jsonrpc::Error {
        let mut error = tower_lsp::jsonrpc::Error::content_modified();
        error.message = match self {
            Self::SemanticTokens => "semantic tokens document changed while computing",
            Self::Structure => "structure document changed while computing",
            Self::WorkspaceSymbols => "workspace symbol documents changed while computing",
        }
        .into();
        error
    }
}

pub(crate) async fn snapshot_context_for_uri(
    store: &Arc<Mutex<DocumentStore>>,
    uri: &Url,
    kind: SnapshotContextKind,
) -> Result<Option<SnapshotContext>> {
    let request = {
        let mut store = store.lock().await;
        if store.has_snapshot(uri) {
            return Ok(store.snapshot_context(uri));
        }
        store.snapshot_build_request(uri)
    };
    let Some(request) = request else {
        return Ok(None);
    };

    let snapshot = request.build();
    commit_snapshot_context(store, &request, snapshot, kind).await
}

pub(crate) async fn commit_snapshot_context(
    store: &Arc<Mutex<DocumentStore>>,
    request: &SnapshotBuildRequest,
    snapshot: Arc<DocumentSnapshot>,
    kind: SnapshotContextKind,
) -> Result<Option<SnapshotContext>> {
    let mut store = store.lock().await;
    match store.insert_built_snapshot(request, snapshot) {
        Some(context) => Ok(Some(context)),
        None if store.get(request.uri()).is_some() => Err(kind.stale_error()),
        None => Ok(None),
    }
}

pub(crate) async fn snapshot_result<T>(
    store: &Arc<Mutex<DocumentStore>>,
    uri: &Url,
    kind: SnapshotContextKind,
    compute: impl FnOnce(&DocumentSnapshot) -> Result<Option<T>>,
) -> Result<Option<T>> {
    let Some(context) = snapshot_context_for_uri(store, uri, kind).await? else {
        return Ok(None);
    };

    let result = compute(&context.snapshot);
    ensure_snapshot_current(store, &context, kind).await?;
    result
}

pub(crate) async fn ensure_snapshot_current(
    store: &Arc<Mutex<DocumentStore>>,
    context: &SnapshotContext,
    kind: SnapshotContextKind,
) -> Result<()> {
    let store = store.lock().await;
    if store.is_snapshot_context_current(context) {
        Ok(())
    } else {
        Err(kind.stale_error())
    }
}