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::{
      PartitionKey,
      PartitionWriteContextRef,
    },
    protocol::{
      jsonrpc,
      lsp::{
        MarkedString,
        MarkupContent,
        MarkupKind,
        Range,
        TextDocumentPositionParams,
        TextDocumentRegistrationOptions,
        WorkDoneProgressOptions,
        WorkDoneProgressParams,
      },
    },
    partitions::DocumentSymbols,
    record::LaburnumRecordRef,
    scheduler::task::TaskContext,
    source::line_ops::LineOps,
  },
  opentelemetry::trace::FutureExt,
  serde::{
    Deserialize,
    Serialize,
  },
};

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

  /// Client supports the follow content formats for the content
  /// property. The order describes the preferred format of the client.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub content_format: Option<Vec<MarkupKind>>,
}

/// Hover options.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HoverOptions {
  #[serde(flatten)]
  pub work_done_progress_options: WorkDoneProgressOptions,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HoverRegistrationOptions {
  #[serde(flatten)]
  pub text_document_registration_options: TextDocumentRegistrationOptions,

  #[serde(flatten)]
  pub hover_options: HoverOptions,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum HoverProviderCapability {
  Simple(bool),
  Options(HoverOptions),
}

impl From<HoverOptions> for HoverProviderCapability {
  fn from(from: HoverOptions) -> Self {
    Self::Options(from)
  }
}

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

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HoverParams {
  #[serde(flatten)]
  pub text_document_position_params: TextDocumentPositionParams,

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

/// The result of a hover request.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Hover {
  /// The hover's content
  pub contents: HoverContents,
  /// An optional range is a range inside a text document
  /// that is used to visualize a hover, e.g. by changing the background color.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub range:    Option<Range>,
}

/// Hover contents could be single entry or multiple entries.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum HoverContents {
  Scalar(MarkedString),
  Array(Vec<MarkedString>),
  Markup(MarkupContent),
}

pub trait HoverService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`textDocument/hover`] request asks the server for hover information
  /// at a given text document position.
  ///
  /// [`textDocument/hover`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
  ///
  /// Such hover information typically includes type signature information and
  /// inline documentation for the symbol at the given text document position.
  fn hover(
    &self,
    params: HoverParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<Output = jsonrpc::Result<Option<Hover>>> + Send
  {
    let _ = writer;
    let cx = otel::span!(^
      "laburnum.lsp.hover",
      "document.uri" = params.text_document_position_params.text_document.uri.to_string(),
      "position.line" = params.text_document_position_params.position.line as i64,
      "position.character" = params.text_document_position_params.position.character as i64
    );
    async move {
      let uri = params.text_document_position_params.text_document.uri;
      let position = params.text_document_position_params.position;

      let source_key = {
        let cache = ctx.source_cache();
        let guard = cache.read();
        match guard.latest_key(&uri) {
          | Some(key) => key,
          | None => {
            otel::event!("hover.source_key_not_found", "uri" = uri.to_string());
            return Ok(None);
          },
        }
      };

      {
        use crate::partitions::{
          DocumentSymbols,
          document_symbols::DocumentSymbolSortKey,
        };

        let file_prefix = DocumentSymbolSortKey::FilePrefix { source_key };
        let results = ctx
          .query_client()
          .query_partition(DocumentSymbols)
          .sort_key_begins_with(file_prefix)
          .execute()
          .await;

        otel::event!(
          "hover.document_symbols",
          "source_key" = source_key.to_string(),
          "symbol_count" = results.records.len() as i64
        );
      }

      use crate::partitions::TextDocumentPosition;

      let encoding = ctx.position_encoding();
      let source_cache = ctx.source_cache_reader();

      let byte_offset = {
        let source = match source_cache.get_source(source_key) {
          Some(s) => s,
          None => return Ok(None),
        };
        match source.line_col_to_byte(position.line, position.character, &encoding) {
          Some(o) => o as u64,
          None => return Ok(None),
        }
      };

      let symbol_hash = ctx
        .query_client()
        .span_index_get::<TextDocumentPosition>(&uri, byte_offset)
        .and_then(|record| {
          record.as_text_document_position().map(|r| r.symbol_hash())
        });

      if let Some(hover) = symbol_hash.and_then(|hash| {
        ctx
          .query_client()
          .get_by_hash::<DocumentSymbols>(hash)
          .and_then(|r| {
            r.as_document_symbol()
              .and_then(|sym| sym.hover_info(&source_cache, &encoding))
          })
      }) {
        otel::event!("hover.found");
        return Ok(Some(hover));
      }

      otel::event!("hover.returning_none");
      Ok(None)
    }
    .with_context(cx)
  }
  const HOVER_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
}