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 {
  crate::{
    Partitions,
    TRACER,
    Uri,
    database::{
      DynPartition,
      PartitionKey,
      PartitionWriteContextRef,
    },
    partitions::WorkspaceSymbols,
    protocol::{
      jsonrpc,
      lsp::{
        LSPAny,
        Location,
        OneOf,
        PartialResultParams,
        SymbolInformation,
        SymbolKind,
        SymbolKindCapability,
        SymbolTag,
        TagSupport,
        WorkDoneProgressParams,
      },
    },
    scheduler::task::TaskContext,
  },
  opentelemetry::trace::FutureExt,
  serde::{
    Deserialize,
    Serialize,
  },
};

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceSymbolClientCapabilities {
  /// This capability supports dynamic registration.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub dynamic_registration: Option<bool>,

  /// Specific capabilities for the `SymbolKind` in the `workspace/symbol`
  /// request.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub symbol_kind: Option<SymbolKindCapability>,

  /// The client supports tags on `SymbolInformation`.
  /// Clients supporting tags have to handle unknown tags gracefully.
  ///
  /// @since 3.16.0
  #[serde(
    default,
    skip_serializing_if = "Option::is_none",
    deserialize_with = "TagSupport::deserialize_compat"
  )]
  pub tag_support: Option<TagSupport<SymbolTag>>,

  /// The client support partial workspace symbols. The client will send the
  /// request `workspaceSymbol/resolve` to the server to resolve additional
  /// properties.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  pub resolve_support: Option<WorkspaceSymbolResolveSupportCapability>,
}

/// The parameters of a Workspace Symbol Request.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceSymbolParams {
  #[serde(flatten)]
  pub partial_result_params: PartialResultParams,

  #[serde(flatten)]
  pub work_done_progress_params: WorkDoneProgressParams,

  /// A non-empty query string
  pub query: String,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceSymbolResolveSupportCapability {
  /// The properties that a client can resolve lazily. Usually
  /// `location.range`
  pub properties: Vec<String>,
}

/// A special workspace symbol that supports locations without a range
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceSymbol {
  /// The name of this symbol.
  pub name: String,

  /// The kind of this symbol.
  pub kind: SymbolKind,

  /// Tags for this completion item.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub tags: Option<Vec<SymbolTag>>,

  /// The name of the symbol containing this symbol. This information is for
  /// user interface purposes (e.g. to render a qualifier in the user interface
  /// if necessary). It can't be used to re-infer a hierarchy for the document
  /// symbols.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub container_name: Option<String>,

  /// The location of this symbol. Whether a server is allowed to
  /// return a location without a range depends on the client
  /// capability `workspace.symbol.resolveSupport`.
  ///
  /// See also `SymbolInformation.location`.
  pub location: OneOf<Location, WorkspaceLocation>,

  /// A data entry field that is preserved on a workspace symbol between a
  /// workspace symbol request and a workspace symbol resolve request.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub data: Option<LSPAny>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct WorkspaceLocation {
  pub uri: Uri,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum WorkspaceSymbolResponse {
  Flat(Vec<SymbolInformation>),
  Nested(Vec<WorkspaceSymbol>),
}

impl From<Vec<SymbolInformation>> for WorkspaceSymbolResponse {
  fn from(info: Vec<SymbolInformation>) -> Self {
    Self::Flat(info)
  }
}

impl From<Vec<WorkspaceSymbol>> for WorkspaceSymbolResponse {
  fn from(symbols: Vec<WorkspaceSymbol>) -> Self {
    Self::Nested(symbols)
  }
}

pub trait SymbolService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`workspace/symbol`] request is sent from the client to the server to
  /// list project-wide symbols matching the given query string.
  ///
  /// [`workspace/symbol`]: https://microsoft.github.io/language-server-protocol/specification#workspace_symbol
  ///
  /// # Compatibility
  ///
  /// Since 3.17.0, servers can also provider a handler for
  /// [`workspaceSymbol/resolve`] requests. This allows servers to return
  /// workspace symbols without a range for a `workspace/symbol`
  /// request. Clients then need to resolve the range when necessary using the
  /// `workspaceSymbol/resolve` request.
  ///
  /// [`workspaceSymbol/resolve`]: Self::symbol_resolve
  ///
  /// Servers can only use this new model if clients advertise support for it
  /// via the `workspace.symbol.resolve_support` capability.
  fn symbol(
    &self,
    params: WorkspaceSymbolParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<
    Output = jsonrpc::Result<
      Option<OneOf<Vec<SymbolInformation>, Vec<WorkspaceSymbol>>>,
    >,
  > + Send {
    let cx = otel::span!(^
      "laburnum.lsp.workspace_symbol",
      "query" = params.query.to_string()
    );
    async move {
      use crate::record::LaburnumRecordRef;

      if params.query.is_empty() {
        return Ok(Some(OneOf::Left(vec![])));
      }

      let query_lower = params.query.to_lowercase();

      let query_results = ctx
        .query_client()
        .query_partition(WorkspaceSymbols)
        .sort_key_begins_with(crate::partitions::workspace_symbols::WorkspaceSymbolSortKey::NamePrefix { name_lowercase: query_lower })
        .execute()
        .await;

      let mut results = Vec::new();

      for record_ref in query_results.iter() {
        if let Some(symbol) = record_ref.as_workspace_symbol() {
          let location = match &symbol.location {
            | OneOf::Left(loc) => loc.clone(),
            | OneOf::Right(_) => continue,
          };

          results.push(SymbolInformation {
            name: symbol.name.clone(),
            kind: symbol.kind,
            tags: symbol.tags.clone(),
            #[allow(deprecated)]
            deprecated: None,
            location,
            container_name: symbol.container_name.clone(),
          });
        }
      }

      let response = OneOf::Left(results);

      Ok(Some(response))
    }.with_context(cx)
  }

  /// The [`workspaceSymbol/resolve`] request is sent from the client to the
  /// server to resolve additional information for a given workspace symbol.
  ///
  /// [`workspaceSymbol/resolve`]: https://microsoft.github.io/language-server-protocol/specification#workspace_symbolResolve
  ///
  /// See the [`symbol`](Self::symbol) documentation for more details.
  ///
  /// # Compatibility
  ///
  /// This request was introduced in specification version 3.17.0.
  fn symbol_resolve(
    &self,
    params: WorkspaceSymbol,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<Output = jsonrpc::Result<WorkspaceSymbol>> + Send
  {
    async move {
      otel::span!("laburnum.lsp.workspace_symbol_resolve");
      let _ = params;

      Err(jsonrpc::Error::method_not_found())
    }
  }
  const SYMBOL_RESOLVE_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
  const SYMBOL_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
}