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::{
    Uri,
    database::{
      PartitionKey,
      PartitionWriteContextRef,
    },
    protocol::{
      jsonrpc,
      lsp::{
        Diagnostic,
        PartialResultParams,
        StaticRegistrationOptions,
        TextDocumentIdentifier,
        TextDocumentRegistrationOptions,
        WorkDoneProgressOptions,
        WorkDoneProgressParams,
      },
    },
    record::LaburnumRecordRef,
    scheduler::task::TaskContext,
  },
  serde::{
    Deserialize,
    Serialize,
  },
  std::collections::HashMap,
};

/// 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 DiagnosticClientCapabilities {
  /// Whether implementation supports dynamic registration.
  ///
  /// If this is set to `true` the client supports the new
  /// `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` return
  /// value for the corresponding server capability as well.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub dynamic_registration: Option<bool>,

  /// Whether the clients supports related documents for document diagnostic
  /// pulls.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub related_document_support: Option<bool>,
}

/// Diagnostic options.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticOptions {
  /// An optional identifier under which the diagnostics are
  /// managed by the client.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub identifier: Option<String>,

  /// Whether the language has inter file dependencies, meaning that editing
  /// code in one file can result in a different diagnostic set in another
  /// file. Inter file dependencies are common for most programming languages
  /// and typically uncommon for linters.
  pub inter_file_dependencies: bool,

  /// The server provides support for workspace diagnostics as well.
  pub workspace_diagnostics: bool,

  #[serde(flatten)]
  pub work_done_progress_options: WorkDoneProgressOptions,
}

/// Diagnostic registration options.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticRegistrationOptions {
  #[serde(flatten)]
  pub text_document_registration_options: TextDocumentRegistrationOptions,

  #[serde(flatten)]
  pub diagnostic_options: DiagnosticOptions,

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

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum DiagnosticServerCapabilities {
  Options(DiagnosticOptions),
  RegistrationOptions(DiagnosticRegistrationOptions),
}

/// Parameters of the document diagnostic request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentDiagnosticParams {
  /// The text document.
  pub text_document: TextDocumentIdentifier,

  /// The additional identifier provided during registration.
  pub identifier: Option<String>,

  /// The result ID of a previous response if provided.
  pub previous_result_id: Option<String>,

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

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

/// A diagnostic report with a full set of problems.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FullDocumentDiagnosticReport {
  /// An optional result ID. If provided it will be sent on the next diagnostic
  /// request for the same document.
  #[serde(skip_serializing_if = "Option::is_none")]
  pub result_id: Option<String>,

  /// The actual items.
  pub items: Vec<Diagnostic>,
}

/// A diagnostic report indicating that the last returned report is still
/// accurate.
///
/// A server can only return `unchanged` if result ids are provided.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UnchangedDocumentDiagnosticReport {
  /// A result ID which will be sent on the next diagnostic request for the
  /// same document.
  pub result_id: String,
}

/// The document diagnostic report kinds.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum DocumentDiagnosticReportKind {
  /// A diagnostic report with a full set of problems.
  Full(FullDocumentDiagnosticReport),
  /// A report indicating that the last returned report is still accurate.
  Unchanged(UnchangedDocumentDiagnosticReport),
}

impl From<FullDocumentDiagnosticReport> for DocumentDiagnosticReportKind {
  fn from(from: FullDocumentDiagnosticReport) -> Self {
    Self::Full(from)
  }
}

impl From<UnchangedDocumentDiagnosticReport> for DocumentDiagnosticReportKind {
  fn from(from: UnchangedDocumentDiagnosticReport) -> Self {
    Self::Unchanged(from)
  }
}

/// A full diagnostic report with a set of related documents.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RelatedFullDocumentDiagnosticReport {
  /// Diagnostics of related documents.
  ///
  /// This information is useful in programming languages where code in a file
  /// A can generate diagnostics in a file B which A depends on. An example
  /// of such a language is C/C++ where macro definitions in a file `a.cpp`
  /// result in errors in a header file `b.hpp`.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  #[serde(default)]
  pub related_documents: Option<HashMap<Uri, DocumentDiagnosticReportKind>>,
  // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport |
  // UnchangedDocumentDiagnosticReport; };
  #[serde(flatten)]
  pub full_document_diagnostic_report: FullDocumentDiagnosticReport,
}

/// An unchanged diagnostic report with a set of related documents.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RelatedUnchangedDocumentDiagnosticReport {
  /// Diagnostics of related documents.
  ///
  /// This information is useful in programming languages where code in a file
  /// A can generate diagnostics in a file B which A depends on. An example
  /// of such a language is C/C++ where macro definitions in a file `a.cpp`
  /// result in errors in a header file `b.hpp`.
  ///
  /// @since 3.17.0
  #[serde(skip_serializing_if = "Option::is_none")]
  #[serde(default)]
  pub related_documents: Option<HashMap<Uri, DocumentDiagnosticReportKind>>,
  // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport |
  // UnchangedDocumentDiagnosticReport; };
  #[serde(flatten)]
  pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport,
}

/// The result of a document diagnostic pull request.
///
/// A report can either be a full report containing all diagnostics for the
/// requested document or an unchanged report indicating that nothing has
/// changed in terms of diagnostics in comparison to the last pull request.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum DocumentDiagnosticReport {
  /// A diagnostic report with a full set of problems.
  Full(RelatedFullDocumentDiagnosticReport),
  /// A report indicating that the last returned report is still accurate.
  Unchanged(RelatedUnchangedDocumentDiagnosticReport),
}

impl From<RelatedFullDocumentDiagnosticReport> for DocumentDiagnosticReport {
  fn from(from: RelatedFullDocumentDiagnosticReport) -> Self {
    Self::Full(from)
  }
}

impl From<RelatedUnchangedDocumentDiagnosticReport>
  for DocumentDiagnosticReport
{
  fn from(from: RelatedUnchangedDocumentDiagnosticReport) -> Self {
    Self::Unchanged(from)
  }
}

/// A partial result for a document diagnostic report.
///
/// @since 3.17.0
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentDiagnosticReportPartialResult {
  #[serde(skip_serializing_if = "Option::is_none")]
  #[serde(default)]
  pub related_documents: Option<HashMap<Uri, DocumentDiagnosticReportKind>>,
  // relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport |
  // UnchangedDocumentDiagnosticReport; };
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum DocumentDiagnosticReportResult {
  Report(DocumentDiagnosticReport),
  Partial(DocumentDiagnosticReportPartialResult),
}

impl From<DocumentDiagnosticReport> for DocumentDiagnosticReportResult {
  fn from(from: DocumentDiagnosticReport) -> Self {
    Self::Report(from)
  }
}

impl From<DocumentDiagnosticReportPartialResult>
  for DocumentDiagnosticReportResult
{
  fn from(from: DocumentDiagnosticReportPartialResult) -> Self {
    Self::Partial(from)
  }
}

/// Cancellation data returned from a diagnostic request.
///
/// If no data is provided, it defaults to `{ retrigger_request: true }`.
///
/// @since 3.17.0
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticServerCancellationData {
  pub retrigger_request: bool,
}

impl Default for DiagnosticServerCancellationData {
  fn default() -> Self {
    Self {
      retrigger_request: true,
    }
  }
}

pub trait DiagnosticService<
  P: crate::database::storage::Partitions,
  T: crate::protocol::lsp::LanguageServer<P>,
>: Send + Sync + 'static
{
  /// The [`textDocument/diagnostic`] request is sent from the client to the
  /// server to ask the server to compute the diagnostics for a given
  /// document.
  ///
  /// As with other pull requests, the server is asked to compute the
  /// diagnostics for the currently synced version of the document.
  ///
  /// The request doesn't define its own client and server capabilities. It is
  /// only issued if a server registers for the [`textDocument/diagnostic`]
  /// request.
  ///
  /// [`textDocument/diagnostic`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_diagnostic
  ///
  /// # Compatibility
  ///
  /// This request was introduced in specification version 3.17.0.
  fn diagnostic(
    &self,
    params: DocumentDiagnosticParams,
    ctx: &mut TaskContext<P, T>,
    writer: &mut PartitionWriteContextRef<'_, P>,
  ) -> impl std::future::Future<
    Output = jsonrpc::Result<DocumentDiagnosticReportResult>,
  > + Send {
    let _ = (self, writer);
    let uri = params.text_document.uri.clone();

    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 requested_file_id = reader.get_file_id(&uri);

      let encoding = ctx.position_encoding();

      let lsp_diagnostics: Vec<Diagnostic> = query_results
        .records()
        .iter()
        .filter_map(|record_meta| {
          let record_ref = query_results.get(record_meta)?;
          let diag = record_ref.as_dyn_diagnostic()?;
          let source_key = diag.source_key()?;

          if Some(source_key.file_id()) != requested_file_id {
            return None;
          }

          Some(diag.to_lsp_diagnostic(&reader, &encoding))
        })
        .collect();

      let report = RelatedFullDocumentDiagnosticReport {
        related_documents:               None,
        full_document_diagnostic_report: FullDocumentDiagnosticReport {
          result_id: None,
          items:     lsp_diagnostics,
        },
      };

      Ok(DocumentDiagnosticReportResult::Report(
        DocumentDiagnosticReport::Full(report),
      ))
    }
  }
  const DIAGNOSTIC_LANE: crate::scheduler::lanes::Lane =
    crate::scheduler::lanes::DEFAULT_LANE;
}