laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use serde::{
  Deserialize,
  Serialize,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ClientId(u64);

impl ClientId {
  pub const INTERNAL: ClientId = ClientId(0);

  pub(crate) fn from_raw(id: u64) -> Self {
    Self(id)
  }

  #[must_use]
  pub const fn as_u64(self) -> u64 {
    self.0
  }
}

impl std::fmt::Display for ClientId {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "client-{}", self.0)
  }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientKind {
  Ide,
  Cli,
  Mcp,
}

impl std::fmt::Display for ClientKind {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      | ClientKind::Ide => write!(f, "ide"),
      | ClientKind::Cli => write!(f, "cli"),
      | ClientKind::Mcp => write!(f, "mcp"),
    }
  }
}

/// Whether a client of this kind should receive LSP-side notifications
/// (publishDiagnostics, $/progress, $/serverShutdown, etc.).
///
/// MCP clients speak a request/response protocol with no push channel for
/// server-initiated notifications, so the daemon skips them in every
/// broadcast path. They detect daemon shutdown via the IPC channel closing.
pub fn wants_lsp_notifications(kind: ClientKind) -> bool {
  !matches!(kind, ClientKind::Mcp)
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn client_id_from_raw() {
    let id1 = ClientId::from_raw(1);
    let id2 = ClientId::from_raw(2);
    let id3 = ClientId::from_raw(3);

    assert_ne!(id1, id2);
    assert_ne!(id2, id3);
    assert_ne!(id1, id3);
  }

  #[test]
  fn client_id_internal_is_zero() {
    assert_eq!(ClientId::INTERNAL.as_u64(), 0);
  }

  #[test]
  fn client_id_display() {
    let id = ClientId::from_raw(42);
    let display = format!("{}", id);
    assert_eq!(display, "client-42");
  }

  #[test]
  fn client_kind_display() {
    assert_eq!(format!("{}", ClientKind::Ide), "ide");
    assert_eq!(format!("{}", ClientKind::Cli), "cli");
    assert_eq!(format!("{}", ClientKind::Mcp), "mcp");
  }

  #[test]
  fn client_id_serde_transparent() {
    let id = ClientId::from_raw(42);

    let serialized = serde_json::to_string(&id).unwrap();
    assert_eq!(serialized, "42");

    let deserialized: ClientId = serde_json::from_str("42").unwrap();
    assert_eq!(deserialized, id);
  }
}