Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/diagnostic.rs')
-rw-r--r--helix-view/src/diagnostic.rs218
1 files changed, 218 insertions, 0 deletions
diff --git a/helix-view/src/diagnostic.rs b/helix-view/src/diagnostic.rs
new file mode 100644
index 00000000..414eb202
--- /dev/null
+++ b/helix-view/src/diagnostic.rs
@@ -0,0 +1,218 @@
+use helix_core::{diagnostic::Severity, Rope};
+use helix_lsp::{lsp, LanguageServerId, OffsetEncoding};
+
+use std::{borrow::Cow, fmt, sync::Arc};
+
+use crate::Range;
+
+#[derive(Debug, Eq, Hash, PartialEq, Clone)]
+pub enum NumberOrString {
+ Number(i32),
+ String(String),
+}
+
+impl NumberOrString {
+ pub fn as_string(&self) -> Cow<'_, str> {
+ match self {
+ Self::Number(n) => Cow::Owned(n.to_string()),
+ Self::String(s) => Cow::Borrowed(s.as_str()),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum DiagnosticTag {
+ Unnecessary,
+ Deprecated,
+}
+
+/// The source of a diagnostic.
+///
+/// This type is cheap to clone: all data is either `Copy` or wrapped in an `Arc`.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub enum DiagnosticProvider {
+ Lsp {
+ /// The ID of the language server which sent the diagnostic.
+ server_id: LanguageServerId,
+ /// An optional identifier under which diagnostics are managed by the client.
+ ///
+ /// `identifier` is a field from the LSP "Pull Diagnostics" feature meant to provide an
+ /// optional "namespace" for diagnostics: a language server can respond to a diagnostics
+ /// pull request with an identifier and these diagnostics should be treated as separate
+ /// from push diagnostics. Rust-analyzer uses this feature for example to provide Cargo
+ /// diagnostics with push and internal diagnostics with pull. The push diagnostics should
+ /// not clear the pull diagnostics and vice-versa.
+ identifier: Option<Arc<str>>,
+ },
+ // Future internal features can go here...
+}
+
+impl DiagnosticProvider {
+ pub fn language_server_id(&self) -> Option<LanguageServerId> {
+ match self {
+ Self::Lsp { server_id, .. } => Some(*server_id),
+ // _ => None,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Diagnostic {
+ pub message: String,
+ pub severity: Option<Severity>,
+ pub code: Option<NumberOrString>,
+ pub tags: Vec<DiagnosticTag>,
+ pub source: Option<String>,
+ pub range: Range,
+ pub provider: DiagnosticProvider,
+ pub data: Option<serde_json::Value>,
+}
+
+impl fmt::Debug for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Diagnostic")
+ .field("message", &self.message)
+ .field("severity", &self.severity)
+ .field("code", &self.code)
+ .field("tags", &self.tags)
+ .field("source", &self.source)
+ .field("range", &self.range)
+ .field("provider", &self.provider)
+ .finish_non_exhaustive()
+ }
+}
+
+impl Diagnostic {
+ pub fn lsp(
+ provider: DiagnosticProvider,
+ offset_encoding: OffsetEncoding,
+ diagnostic: lsp::Diagnostic,
+ ) -> Self {
+ let severity = diagnostic.severity.and_then(|severity| match severity {
+ lsp::DiagnosticSeverity::ERROR => Some(Severity::Error),
+ lsp::DiagnosticSeverity::WARNING => Some(Severity::Warning),
+ lsp::DiagnosticSeverity::INFORMATION => Some(Severity::Info),
+ lsp::DiagnosticSeverity::HINT => Some(Severity::Hint),
+ severity => {
+ log::error!("unrecognized diagnostic severity: {:?}", severity);
+ None
+ }
+ });
+ let code = match diagnostic.code {
+ Some(x) => match x {
+ lsp::NumberOrString::Number(x) => Some(NumberOrString::Number(x)),
+ lsp::NumberOrString::String(x) => Some(NumberOrString::String(x)),
+ },
+ None => None,
+ };
+ let tags = if let Some(tags) = diagnostic.tags {
+ tags.into_iter()
+ .filter_map(|tag| match tag {
+ lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated),
+ lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary),
+ _ => None,
+ })
+ .collect()
+ } else {
+ Vec::new()
+ };
+
+ Self {
+ message: diagnostic.message,
+ severity,
+ code,
+ tags,
+ source: diagnostic.source,
+ range: Range::Lsp {
+ range: diagnostic.range,
+ offset_encoding,
+ },
+ provider,
+ data: diagnostic.data,
+ }
+ }
+
+ /// Converts the diagnostic to a [lsp::Diagnostic].
+ pub fn to_lsp_diagnostic(
+ &self,
+ text: &Rope,
+ offset_encoding: OffsetEncoding,
+ ) -> lsp::Diagnostic {
+ let range = match self.range {
+ Range::Document(range) => helix_lsp::util::range_to_lsp_range(
+ text,
+ helix_core::Range::new(range.start, range.end),
+ offset_encoding,
+ ),
+ Range::Lsp { range, .. } => range,
+ };
+ let severity = self.severity.map(|severity| match severity {
+ Severity::Hint => lsp::DiagnosticSeverity::HINT,
+ Severity::Info => lsp::DiagnosticSeverity::INFORMATION,
+ Severity::Warning => lsp::DiagnosticSeverity::WARNING,
+ Severity::Error => lsp::DiagnosticSeverity::ERROR,
+ });
+ let code = match self.code.clone() {
+ Some(x) => match x {
+ NumberOrString::Number(x) => Some(lsp::NumberOrString::Number(x)),
+ NumberOrString::String(x) => Some(lsp::NumberOrString::String(x)),
+ },
+ None => None,
+ };
+ let new_tags: Vec<_> = self
+ .tags
+ .iter()
+ .map(|tag| match tag {
+ DiagnosticTag::Unnecessary => lsp::DiagnosticTag::UNNECESSARY,
+ DiagnosticTag::Deprecated => lsp::DiagnosticTag::DEPRECATED,
+ })
+ .collect();
+ let tags = if !new_tags.is_empty() {
+ Some(new_tags)
+ } else {
+ None
+ };
+
+ lsp::Diagnostic {
+ range,
+ severity,
+ code,
+ source: self.source.clone(),
+ message: self.message.clone(),
+ tags,
+ data: self.data.clone(),
+ ..Default::default()
+ }
+ }
+}
+
+impl PartialEq for Diagnostic {
+ fn eq(&self, other: &Self) -> bool {
+ self.message == other.message
+ && self.severity == other.severity
+ && self.code == other.code
+ && self.tags == other.tags
+ && self.source == other.source
+ && self.range == other.range
+ && self.provider == other.provider
+ && self.data == other.data
+ }
+}
+
+impl Eq for Diagnostic {}
+
+impl PartialOrd for Diagnostic {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Diagnostic {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ (self.range, self.severity, self.provider.clone()).cmp(&(
+ other.range,
+ other.severity,
+ other.provider.clone(),
+ ))
+ }
+}