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::PartitionWriteContextRef,
    protocol::{
      jsonrpc,
      lsp::{
        DocumentSelector,
        DynamicRegistrationClientCapabilities,
        Range,
        TextDocumentIdentifier,
        TextDocumentPositionParams,
        TextEdit,
        WorkDoneProgressParams,
      },
    },
    scheduler::task::TaskContext,
  },
  serde::{
    Deserialize,
    Serialize,
  },
  std::collections::HashMap,
};

pub type DocumentFormattingClientCapabilities =
  DynamicRegistrationClientCapabilities;
pub type DocumentRangeFormattingClientCapabilities =
  DynamicRegistrationClientCapabilities;
pub type DocumentOnTypeFormattingClientCapabilities =
  DynamicRegistrationClientCapabilities;

/// Format document on type options
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentOnTypeFormattingOptions {
  /// A character on which formatting should be triggered, like `}`.
  pub first_trigger_character: String,

  /// More trigger characters.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub more_trigger_character: Option<Vec<String>>,
}

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

  /// The format options.
  pub options: FormattingOptions,

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

/// Value-object describing what options formatting should use.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FormattingOptions {
  /// Size of a tab in spaces.
  pub tab_size: u32,

  /// Prefer spaces over tabs.
  pub insert_spaces: bool,

  /// Signature for further properties.
  #[serde(flatten)]
  pub properties: HashMap<String, FormattingProperty>,

  /// Trim trailing whitespace on a line.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub trim_trailing_whitespace: Option<bool>,

  /// Insert a newline character at the end of the file if one does not exist.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub insert_final_newline: Option<bool>,

  /// Trim all newlines after the final newline at the end of the file.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub trim_final_newlines: Option<bool>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum FormattingProperty {
  Bool(bool),
  Number(i32),
  String(String),
}

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

  /// The range to format
  pub range: Range,

  /// The format options
  pub options: FormattingOptions,

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

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentOnTypeFormattingParams {
  /// Text Document and Position fields.
  #[serde(flatten)]
  pub text_document_position: TextDocumentPositionParams,

  /// The character that has been typed.
  pub ch: String,

  /// The format options.
  pub options: FormattingOptions,
}

/// Extends `TextDocumentRegistrationOptions`
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentOnTypeFormattingRegistrationOptions {
  /// A document selector to identify the scope of the registration. If set to
  /// null the document selector provided on the client side will be used.
  pub document_selector: Option<DocumentSelector>,

  /// A character on which formatting should be triggered, like `}`.
  pub first_trigger_character: String,

  /// More trigger characters.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub more_trigger_character: Option<Vec<String>>,
}

pub trait FormattingService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`textDocument/formatting`] request is sent from the client to the
  /// server to format a whole document.
  ///
  /// [`textDocument/formatting`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
  fn formatting(
    &self,
    params: DocumentFormattingParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<Output = jsonrpc::Result<Option<Vec<TextEdit>>>> + Send
  {
    async move {
      otel::span!(
        "laburnum.lsp.formatting",
        "document.uri" = params.text_document.uri.to_string()
      );

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

  /// The [`textDocument/rangeFormatting`] request is sent from pub(crate) the
  /// client to the server to format a given range in a document.
  ///
  /// [`textDocument/rangeFormatting`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting
  fn range_formatting(
    &self,
    params: DocumentRangeFormattingParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<Output = jsonrpc::Result<Option<Vec<TextEdit>>>> + Send
  {
    async move {
      otel::span!(
        "laburnum.lsp.range_formatting",
        "document.uri" = params.text_document.uri.to_string(),
        "range.start.line" = params.range.start.line as i64,
        "range.end.line" = params.range.end.line as i64
      );

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

  /// The [`textDocument/onTypeFormatting`] request is sent from the client to
  /// the server to format parts of the document during typing.
  ///
  /// [`textDocument/onTypeFormatting`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting
  fn on_type_formatting(
    &self,
    params: DocumentOnTypeFormattingParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<Output = jsonrpc::Result<Option<Vec<TextEdit>>>> + Send
  {
    async move {
      otel::span!(
        "laburnum.lsp.on_type_formatting",
        "document.uri" =
          params.text_document_position.text_document.uri.to_string(),
        "position.line" = params.text_document_position.position.line as i64,
        "character" = params.ch.to_string()
      );

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

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

  #[test]
  fn formatting_options() {
    test_serialization(
      &FormattingOptions {
        tab_size:                 123,
        insert_spaces:            true,
        properties:               HashMap::new(),
        trim_trailing_whitespace: None,
        insert_final_newline:     None,
        trim_final_newlines:      None,
      },
      r#"{"tabSize":123,"insertSpaces":true}"#,
    );

    test_serialization(
      &FormattingOptions {
        tab_size:                 123,
        insert_spaces:            true,
        properties:               vec![(
          "prop".to_string(),
          FormattingProperty::Number(1),
        )]
        .into_iter()
        .collect(),
        trim_trailing_whitespace: None,
        insert_final_newline:     None,
        trim_final_newlines:      None,
      },
      r#"{"tabSize":123,"insertSpaces":true,"prop":1}"#,
    );
  }
}