laburnum 1.17.0

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 {
  super::{
    ClientId,
    ClientKind,
    Subscriptions,
  },
  crate::{connect::ipc::Connection, protocol::lsp::PositionEncodingKind},
  std::{
    collections::HashMap,
    time::{
      Instant,
      SystemTime,
      UNIX_EPOCH,
    },
  },
};

pub struct ConnectedClient {
  id:                ClientId,
  kind:              ClientKind,
  connection:        Connection,
  subscriptions:     Subscriptions,
  connected_at:      Instant,
  connected_at_unix: u64,
  client_name:       Option<String>,
  client_version:    Option<String>,
  metadata:          HashMap<String, String>,
  position_encoding: PositionEncodingKind,
}

impl ConnectedClient {
  pub(super) fn with_id(
    id: ClientId,
    kind: ClientKind,
    connection: Connection,
    metadata: HashMap<String, String>,
  ) -> Self {
    let client_name = metadata.get("client_name").cloned();
    let client_version = metadata.get("client_version").cloned();
    let connected_at_unix = SystemTime::now()
      .duration_since(UNIX_EPOCH)
      .map(|d| d.as_secs())
      .unwrap_or(0);

    Self {
      id,
      kind,
      connection,
      subscriptions: Subscriptions::new(),
      connected_at: Instant::now(),
      connected_at_unix,
      client_name,
      client_version,
      metadata,
      position_encoding: PositionEncodingKind::DEFAULT,
    }
  }

  #[must_use]
  pub const fn id(&self) -> ClientId {
    self.id
  }

  #[must_use]
  pub const fn kind(&self) -> ClientKind {
    self.kind
  }

  #[must_use]
  pub fn connection(&self) -> &Connection {
    &self.connection
  }

  #[must_use]
  pub fn subscriptions(&self) -> &Subscriptions {
    &self.subscriptions
  }

  pub fn subscriptions_mut(&mut self) -> &mut Subscriptions {
    &mut self.subscriptions
  }

  pub fn subscribe(&mut self, topic: impl super::Topic) {
    self.subscriptions.subscribe(topic);
  }

  pub fn unsubscribe(&mut self, topic: impl super::Topic) {
    self.subscriptions.unsubscribe(topic);
  }

  pub fn is_subscribed(&self, topic: impl super::Topic) -> bool {
    self.subscriptions.is_subscribed(topic)
  }

  #[must_use]
  pub const fn connected_at(&self) -> Instant {
    self.connected_at
  }

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

  #[must_use]
  pub fn client_name(&self) -> Option<&str> {
    self.client_name.as_deref()
  }

  #[must_use]
  pub fn client_version(&self) -> Option<&str> {
    self.client_version.as_deref()
  }

  #[must_use]
  pub fn metadata(&self) -> &HashMap<String, String> {
    &self.metadata
  }

  pub fn metadata_mut(&mut self) -> &mut HashMap<String, String> {
    &mut self.metadata
  }

  #[must_use]
  pub fn position_encoding(&self) -> &PositionEncodingKind {
    &self.position_encoding
  }

  pub fn set_position_encoding(&mut self, encoding: PositionEncodingKind) {
    self.position_encoding = encoding;
  }
}

impl std::fmt::Debug for ConnectedClient {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("ConnectedClient")
      .field("id", &self.id)
      .field("kind", &self.kind)
      .field("client_name", &self.client_name)
      .field("client_version", &self.client_version)
      .field("subscriptions", &self.subscriptions)
      .field("connected_at", &self.connected_at)
      .field("metadata", &self.metadata)
      .field("position_encoding", &self.position_encoding)
      .finish_non_exhaustive()
  }
}

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

  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  enum TestTopic {
    Diagnostics,
  }

  impl super::super::Topic for TestTopic {
    fn name(&self) -> &'static str {
      match self {
        | TestTopic::Diagnostics => "diagnostics",
      }
    }
  }

  #[test]
  fn client_new_via_registry() {
    let registry = ClientRegistry::new();
    let (server_conn, _client_conn) = Connection::memory();
    let id = registry.register(ClientKind::Cli, server_conn, HashMap::new());

    let client = registry.get(id).unwrap();
    assert_eq!(client.kind(), ClientKind::Cli);
    assert!(!client.is_subscribed(TestTopic::Diagnostics));
    assert!(client.metadata().is_empty());
  }

  #[test]
  fn client_connected_at() {
    let start = Instant::now();
    let registry = ClientRegistry::new();
    let (server_conn, _client_conn) = Connection::memory();
    let id = registry.register(ClientKind::Ide, server_conn, HashMap::new());

    let client = registry.get(id).unwrap();
    assert!(client.connected_at() >= start);
    assert!(client.connected_at() <= Instant::now());
  }

  #[test]
  fn client_metadata() {
    let registry = ClientRegistry::new();
    let (server_conn, _client_conn) = Connection::memory();
    let mut metadata = HashMap::new();
    metadata.insert("client_name".to_string(), "vscode".to_string());
    metadata.insert("version".to_string(), "1.85.0".to_string());

    let id = registry.register(ClientKind::Ide, server_conn, metadata);

    let client = registry.get(id).unwrap();
    assert_eq!(client.metadata().get("client_name"), Some(&"vscode".to_string()));
    assert_eq!(
      client.metadata().get("version"),
      Some(&"1.85.0".to_string())
    );
  }

  #[test]
  fn client_metadata_mut() {
    let registry = ClientRegistry::new();
    let (server_conn, _client_conn) = Connection::memory();
    let id = registry.register(ClientKind::Cli, server_conn, HashMap::new());

    {
      let mut client = registry.get_mut(id).unwrap();
      client
        .metadata_mut()
        .insert("key".to_string(), "value".to_string());
    }

    let client = registry.get(id).unwrap();
    assert_eq!(client.metadata().get("key"), Some(&"value".to_string()));
  }
}