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 {
  crate::{
    TRACER,
    database::{
      DynPartition,
      PartitionKey,
      PartitionWriteContextRef,
    },
    partitions::{
      DocumentFoldingRange,
      document_folding_range::FoldingRangeSortKey,
    },
    protocol::{
      jsonrpc,
      lsp::{
        PartialResultParams,
        StaticTextDocumentColorProviderOptions,
        TextDocumentIdentifier,
        WorkDoneProgressParams,
      },
    },
    record::LaburnumRecordRef,
    scheduler::task::TaskContext,
  },
  opentelemetry::trace::FutureExt,
  serde::{
    Deserialize,
    Serialize,
  },
};

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FoldingRangeParams {
  /// The text document.
  pub text_document: TextDocumentIdentifier,

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

  #[serde(flatten)]
  pub partial_result_params: PartialResultParams,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum FoldingRangeProviderCapability {
  Simple(bool),
  FoldingProvider(FoldingProviderOptions),
  Options(StaticTextDocumentColorProviderOptions),
}

impl From<StaticTextDocumentColorProviderOptions>
  for FoldingRangeProviderCapability
{
  fn from(from: StaticTextDocumentColorProviderOptions) -> Self {
    Self::Options(from)
  }
}

impl From<FoldingProviderOptions> for FoldingRangeProviderCapability {
  fn from(from: FoldingProviderOptions) -> Self {
    Self::FoldingProvider(from)
  }
}

impl From<bool> for FoldingRangeProviderCapability {
  fn from(from: bool) -> Self {
    Self::Simple(from)
  }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct FoldingProviderOptions {}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FoldingRangeKindCapability {
  /// The folding range kind values the client supports. When this property
  /// exists the client also guarantees that it will handle values outside its
  /// set gracefully and falls back to a default value when unknown.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub value_set: Option<Vec<FoldingRangeKind>>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FoldingRangeCapability {
  /// If set, the client signals that it supports setting collapsedText on
  /// folding ranges to display custom labels instead of the default text.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  pub collapsed_text: Option<bool>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FoldingRangeClientCapabilities {
  /// Whether implementation supports dynamic registration for folding
  /// range providers. If this is set to `true` the client supports the
  /// new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions &
  /// StaticRegistrationOptions)` return value for the corresponding server
  /// capability as well.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub dynamic_registration: Option<bool>,

  /// The maximum number of folding ranges that the client prefers to receive
  /// per document. The value serves as a hint, servers are free to follow
  /// the limit.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub range_limit: Option<u32>,

  /// If set, the client signals that it only supports folding complete lines.
  /// If set, client will ignore specified `startCharacter` and `endCharacter`
  /// properties in a `FoldingRange`.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub line_folding_only: Option<bool>,

  /// Specific options for the folding range kind.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  pub folding_range_kind: Option<FoldingRangeKindCapability>,

  /// Specific options for the folding range.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  pub folding_range: Option<FoldingRangeCapability>,
}

/// Enum of known range kinds
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum FoldingRangeKind {
  /// Folding range for a comment
  Comment,
  /// Folding range for a imports or includes
  Imports,
  /// Folding range for a region (e.g. `#region`)
  Region,
}

/// Represents a folding range.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FoldingRange {
  /// The zero-based start line of the range to fold. The folded area starts
  /// after the line's last character. To be valid, the end must be zero or
  /// larger and smaller than the number of lines in the document.
  pub start_line: u32,

  /// The zero-based character offset from where the folded range starts. If
  /// not defined, defaults to the length of the start line.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub start_character: Option<u32>,

  /// The zero-based end line of the range to fold. The folded area ends with
  /// the line's last character. To be valid, the end must be zero or larger
  /// and smaller than the number of lines in the document.
  pub end_line: u32,

  /// The zero-based character offset before the folded range ends. If not
  /// defined, defaults to the length of the end line.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub end_character: Option<u32>,

  /// Describes the kind of the folding range such as `comment` or `region`.
  /// The kind is used to categorize folding ranges and used by commands like
  /// `Fold all comments`. See [`FoldingRangeKind`] for an enumeration of
  /// standardized kinds.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub kind: Option<FoldingRangeKind>,

  /// The text that the client should show when the specified range is
  /// collapsed. If not defined or not supported by the client, a default
  /// will be chosen by the client.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  pub collapsed_text: Option<String>,
}

pub trait FoldingRangeService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`textDocument/foldingRange`] request is sent from the client to the
  /// server to return all folding ranges found in a given text document.
  ///
  /// [`textDocument/foldingRange`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange
  ///
  /// # Compatibility
  ///
  /// This request was introduced in specification version 3.10.0.
  fn folding_range(
    &self,
    params: FoldingRangeParams,
    ctx: &mut TaskContext<P, T>,
    _writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<
    Output = jsonrpc::Result<Option<Vec<FoldingRange>>>,
  > + Send {
    let cx = otel::span!(
      ^"laburnum.lsp.folding_range",
      "document.uri" = params.text_document.uri.to_string()
    );
    async move {
      let uri = params.text_document.uri;

      let source_key = {
        let cache = ctx.source_cache();
        let guard = cache.read();
        match guard.latest_key(&uri) {
          | Some(key) => key,
          | None => return Ok(None),
        }
      };

      let file_prefix =
        FoldingRangeSortKey::FilePrefix { source_key }.to_string();

      let query_results = ctx
        .query_client()
        .prefix_internal(DocumentFoldingRange::KEY, file_prefix)
        .await;

      let mut ranges: Vec<FoldingRange> = Vec::new();

      for record_meta in query_results.records().iter() {
        if let Some(record_ref) = query_results.get(record_meta)
          && let Some(folding_range_record) = record_ref.as_folding_range()
        {
          let range = folding_range_record.to_folding_range();

          if range.start_line == range.end_line {
            continue;
          }

          ranges.push(range);
        }
      }

      if ranges.is_empty() {
        Ok(None)
      } else {
        Ok(Some(ranges))
      }
    }
    .with_context(cx)
  }

  const FOLDING_RANGE_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
}