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::{
        DynamicRegistrationClientCapabilities,
        Range,
        StaticRegistrationOptions,
        TextDocumentIdentifier,
        TextDocumentRegistrationOptions,
        WorkDoneProgressOptions,
        WorkDoneProgressParams,
      },
    },
    scheduler::task::TaskContext,
  },
  serde::{
    Deserialize,
    Serialize,
  },
};

pub type InlineValueClientCapabilities = DynamicRegistrationClientCapabilities;

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum InlineValueServerCapabilities {
  Options(InlineValueOptions),
  RegistrationOptions(InlineValueRegistrationOptions),
}

/// Inline value options used during static registration.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct InlineValueOptions {
  #[serde(flatten)]
  pub work_done_progress_options: WorkDoneProgressOptions,
}

/// Inline value options used during static or dynamic registration.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct InlineValueRegistrationOptions {
  #[serde(flatten)]
  pub inline_value_options: InlineValueOptions,

  #[serde(flatten)]
  pub text_document_registration_options: TextDocumentRegistrationOptions,

  #[serde(flatten)]
  pub static_registration_options: StaticRegistrationOptions,
}

/// A parameter literal used in inline value requests.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineValueParams {
  #[serde(flatten)]
  pub work_done_progress_params: WorkDoneProgressParams,

  /// The text document.
  pub text_document: TextDocumentIdentifier,

  /// The document range for which inline values should be computed.
  pub range: Range,

  /// Additional information about the context in which inline values were
  /// requested.
  pub context: InlineValueContext,
}

/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineValueContext {
  /// The stack frame (as a DAP Id) where the execution has stopped.
  pub frame_id: i32,

  /// The document range where execution has stopped.
  /// Typically the end position of the range denotes the line where the
  /// inline values are shown.
  pub stopped_location: Range,
}

/// Provide inline value as text.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct InlineValueText {
  /// The document range for which the inline value applies.
  pub range: Range,

  /// The text of the inline value.
  pub text: String,
}

/// Provide inline value through a variable lookup.
///
/// If only a range is specified, the variable name will be extracted from
/// the underlying document.
///
/// An optional variable name can be used to override the extracted name.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineValueVariableLookup {
  /// The document range for which the inline value applies.
  /// The range is used to extract the variable name from the underlying
  /// document.
  pub range: Range,

  /// If specified the name of the variable to look up.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub variable_name: Option<String>,

  /// How to perform the lookup.
  pub case_sensitive_lookup: bool,
}

/// Provide an inline value through an expression evaluation.
///
/// If only a range is specified, the expression will be extracted from the
/// underlying document.
///
/// An optional expression can be used to override the extracted expression.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineValueEvaluatableExpression {
  /// The document range for which the inline value applies.
  /// The range is used to extract the evaluatable expression from the
  /// underlying document.
  pub range: Range,

  /// If specified the expression overrides the extracted expression.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub expression: Option<String>,
}

/// Inline value information can be provided by different means:
/// - directly as a text value (class `InlineValueText`).
/// - as a name to use for a variable lookup (class `InlineValueVariableLookup`)
/// - as an evaluatable expression (class `InlineValueEvaluatableExpression`)
///
/// The `InlineValue` types combines all inline value types into one type.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum InlineValue {
  Text(InlineValueText),
  VariableLookup(InlineValueVariableLookup),
  EvaluatableExpression(InlineValueEvaluatableExpression),
}

impl From<InlineValueText> for InlineValue {
  #[inline]
  fn from(from: InlineValueText) -> Self {
    Self::Text(from)
  }
}

impl From<InlineValueVariableLookup> for InlineValue {
  #[inline]
  fn from(from: InlineValueVariableLookup) -> Self {
    Self::VariableLookup(from)
  }
}

impl From<InlineValueEvaluatableExpression> for InlineValue {
  #[inline]
  fn from(from: InlineValueEvaluatableExpression) -> Self {
    Self::EvaluatableExpression(from)
  }
}

/// Client workspace capabilities specific to inline values.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
/// @since 3.17.0
#[serde(rename_all = "camelCase")]
pub struct InlineValueWorkspaceClientCapabilities {
  /// Whether the client implementation supports a refresh request sent from
  /// the server to the client.
  ///
  /// Note that this event is global and will force the client to refresh all
  /// inline values currently shown. It should be used with absolute care and
  /// is useful for situation where a server for example detect a project wide
  /// change that requires such a calculation.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub refresh_support: Option<bool>,
}

pub trait InlineValueService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`textDocument/inlineValue`] request is sent from the client to the
  /// server to compute inline values for a given text document that may be
  /// rendered in the editor at the end of lines.
  ///
  /// [`textDocument/inlineValue`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_inlineValue
  ///
  /// # Compatibility
  ///
  /// This request was introduced in specification version 3.17.0.
  fn inline_value(
    &self,
    params: InlineValueParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl ::std::future::Future<
    Output = jsonrpc::Result<Option<Vec<InlineValue>>>,
  > + Send {
    async move {
      otel::span!(
        "laburnum.lsp.inline_value",
        "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
      );

      let _ = params;

      Err(jsonrpc::Error::method_not_found())
    }
  }

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

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

  #[test]
  fn inline_values() {
    test_serialization(
      &InlineValueText {
        range: Range::new(Position::new(0, 0), Position::new(0, 4)),
        text:  "one".to_owned(),
      },
      r#"{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":4}},"text":"one"}"#,
    );

    test_serialization(
      &InlineValue::VariableLookup(InlineValueVariableLookup {
        range:                 Range::new(
          Position::new(1, 0),
          Position::new(1, 4),
        ),
        variable_name:         None,
        case_sensitive_lookup: false,
      }),
      r#"{"range":{"start":{"line":1,"character":0},"end":{"line":1,"character":4}},"caseSensitiveLookup":false}"#,
    );

    test_serialization(
      &InlineValue::EvaluatableExpression(InlineValueEvaluatableExpression {
        range:      Range::new(Position::new(2, 0), Position::new(2, 4)),
        expression: None,
      }),
      r#"{"range":{"start":{"line":2,"character":0},"end":{"line":2,"character":4}}}"#,
    );
  }
}