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::{
    TRACER,
    Uri,
    database::{
      PartitionKey,
      PartitionWriteContextRef,
    },
    protocol::{
      jsonrpc,
      lsp::{
        FullDocumentDiagnosticReport,
        PartialResultParams,
        UnchangedDocumentDiagnosticReport,
        WorkDoneProgressParams,
      },
    },
    record::LaburnumRecordRef,
    scheduler::task::TaskContext,
  },
  opentelemetry::trace::FutureExt,
  serde::{
    Deserialize,
    Serialize,
  },
};

/// Workspace client capabilities specific to diagnostic pull requests.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticWorkspaceClientCapabilities {
  /// 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
  /// pulled diagnostics currently shown. It should be used with absolute care
  /// and is useful for situation where a server for example detects a project
  /// wide change that requires such a calculation.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub refresh_support: Option<bool>,
}

/// A previous result ID in a workspace pull request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PreviousResultId {
  /// The URI for which the client knows a result ID.
  pub uri: Uri,

  /// The value of the previous result ID.
  pub value: String,
}

/// Parameters of the workspace diagnostic request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceDiagnosticParams {
  /// The additional identifier provided during registration.
  pub identifier: Option<String>,

  /// The currently known diagnostic reports with their
  /// previous result ids.
  pub previous_result_ids: Vec<PreviousResultId>,

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

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

/// A full document diagnostic report for a workspace diagnostic result.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceFullDocumentDiagnosticReport {
  /// The URI for which diagnostic information is reported.
  pub uri: Uri,

  /// The version number for which the diagnostics are reported.
  ///
  /// If the document is not marked as open, `None` can be provided.
  pub version: Option<i64>,

  #[serde(flatten)]
  pub full_document_diagnostic_report: FullDocumentDiagnosticReport,
}

/// An unchanged document diagnostic report for a workspace diagnostic result.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceUnchangedDocumentDiagnosticReport {
  /// The URI for which diagnostic information is reported.
  pub uri: Uri,

  /// The version number for which the diagnostics are reported.
  ///
  /// If the document is not marked as open, `None` can be provided.
  pub version: Option<i64>,

  #[serde(flatten)]
  pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport,
}

/// A workspace diagnostic document report.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum WorkspaceDocumentDiagnosticReport {
  Full(WorkspaceFullDocumentDiagnosticReport),
  Unchanged(WorkspaceUnchangedDocumentDiagnosticReport),
}

impl From<WorkspaceFullDocumentDiagnosticReport>
  for WorkspaceDocumentDiagnosticReport
{
  fn from(from: WorkspaceFullDocumentDiagnosticReport) -> Self {
    Self::Full(from)
  }
}

impl From<WorkspaceUnchangedDocumentDiagnosticReport>
  for WorkspaceDocumentDiagnosticReport
{
  fn from(from: WorkspaceUnchangedDocumentDiagnosticReport) -> Self {
    Self::Unchanged(from)
  }
}

/// A workspace diagnostic report.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceDiagnosticReport {
  pub items: Vec<WorkspaceDocumentDiagnosticReport>,
}

/// A partial result for a workspace diagnostic report.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct WorkspaceDiagnosticReportPartialResult {
  pub items: Vec<WorkspaceDocumentDiagnosticReport>,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum WorkspaceDiagnosticReportResult {
  Report(WorkspaceDiagnosticReport),
  Partial(WorkspaceDiagnosticReportPartialResult),
}

impl From<WorkspaceDiagnosticReport> for WorkspaceDiagnosticReportResult {
  fn from(from: WorkspaceDiagnosticReport) -> Self {
    Self::Report(from)
  }
}

impl From<WorkspaceDiagnosticReportPartialResult>
  for WorkspaceDiagnosticReportResult
{
  fn from(from: WorkspaceDiagnosticReportPartialResult) -> Self {
    Self::Partial(from)
  }
}

pub trait WorkspaceDiagnosticService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`workspace/diagnostic`] request is sent from the client to the server
  /// to ask the server to compute workspace wide diagnostics which previously
  /// where pushed from the server to the client.
  ///
  /// In contrast to the [`textDocument/diagnostic`] request, the workspace
  /// request can be long-running and is not bound to a specific workspace or
  /// document state. If the client supports streaming for the workspace
  /// diagnostic pull, it is legal to provide a `textDocument/diagnostic`
  /// report multiple times for the same document URI. The last one
  /// reported will win over previous reports.
  ///
  /// [`textDocument/diagnostic`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_diagnostic
  ///
  /// If a client receives a diagnostic report for a document in a workspace
  /// diagnostic request for which the client also issues individual document
  /// diagnostic pull requests, the client needs to decide which diagnostics
  /// win and should be presented. In general:
  ///
  /// * Diagnostics for a higher document version should win over those from a
  ///   lower document version (e.g. note that document versions are steadily
  ///   increasing).
  /// * Diagnostics from a document pull should win over diagnostics from a
  ///   workspace pull.
  ///
  /// The request doesn't define its own client and server capabilities. It is
  /// only issued if a server registers for the [`workspace/diagnostic`]
  /// request.
  ///
  /// [`workspace/diagnostic`]: https://microsoft.github.io/language-server-protocol/specification#workspace_diagnostic
  ///
  /// # Compatibility
  ///
  /// This request was introduced in specification version 3.17.0.
  fn workspace_diagnostic(
    &self,
    _params: WorkspaceDiagnosticParams,
    ctx: &mut TaskContext<P, T>,
    _writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<
    Output = jsonrpc::Result<WorkspaceDiagnosticReportResult>,
  > + Send {
    let cx = otel::span!(^
      "laburnum.lsp.workspace_diagnostic",
      "identifier" = _params.identifier.as_ref().map(|s| s.to_string()).unwrap_or_default()
    );
    async move {
      use crate::database::DynPartition;
      let partition_key = crate::partitions::Diagnostics::KEY;

      let query_client = ctx.query_client();
      let query_results = query_client
        .prefix_internal(partition_key, String::new())
        .await;

      let reader = ctx.source_cache_reader();

      let encoding = ctx.position_encoding();

      let mut diagnostics_by_file: std::collections::HashMap<
        crate::Uri,
        (crate::SourceKey, Vec<crate::protocol::lsp::Diagnostic>),
      > = std::collections::HashMap::new();

      for record_meta in query_results.records().iter() {
        if let Some(record_ref) = query_results.get(record_meta)
          && let Some(diag) = record_ref.as_dyn_diagnostic()
          && let Some(source_key) = diag.source_key()
          && let Some(uri) = reader.get_uri(source_key.file_id())
        {
          let lsp_diag = diag.to_lsp_diagnostic(&reader, &encoding);
          diagnostics_by_file
            .entry(uri.clone())
            .or_insert_with(|| (source_key, Vec::new()))
            .1
            .push(lsp_diag);
        }
      }

      let items: Vec<WorkspaceDocumentDiagnosticReport> = diagnostics_by_file
        .into_iter()
        .map(|(uri, (source_key, items))| {
          let version = reader
            .current_version(source_key.file_id())
            .map(|v| v as i64);

          WorkspaceFullDocumentDiagnosticReport {
            uri,
            version,
            full_document_diagnostic_report:
              crate::protocol::lsp::FullDocumentDiagnosticReport {
                result_id: None,
                items,
              },
          }
          .into()
        })
        .collect();

      Ok(WorkspaceDiagnosticReportResult::Report(
        WorkspaceDiagnosticReport { items },
      ))
    }
    .with_context(cx)
  }
  const WORKSPACE_DIAGNOSTIC_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
}