aboutsummaryrefslogtreecommitdiff
path: root/helix-view
diff options
context:
space:
mode:
authorPhilipp Mildenberger2024-01-09 01:01:04 +0000
committerGitHub2024-01-09 01:01:04 +0000
commit41ca46cf8ca65dbd98df7e038fc12a272f0c9e2a (patch)
treee222eececda605353414b18921de63a35d8acd1c /helix-view
parent46ecc102ba6d6c2e6e707b551e24faa240752c87 (diff)
Initialize diagnostics when opening a document (#8873)
Diffstat (limited to 'helix-view')
-rw-r--r--helix-view/src/document.rs116
-rw-r--r--helix-view/src/editor.rs60
2 files changed, 155 insertions, 21 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 51668ab1..0de0cd17 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -4,10 +4,12 @@ use arc_swap::ArcSwap;
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use helix_core::auto_pairs::AutoPairs;
+use helix_core::chars::char_is_word;
use helix_core::doc_formatter::TextFormat;
use helix_core::encoding::Encoding;
use helix_core::syntax::{Highlight, LanguageServerFeature};
use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
+use helix_lsp::util::lsp_pos_to_pos;
use helix_vcs::{DiffHandle, DiffProviderRegistry};
use ::parking_lot::Mutex;
@@ -1075,14 +1077,6 @@ impl Document {
};
}
- /// Set the programming language for the file if you know the name (scope) but don't have the
- /// [`syntax::LanguageConfiguration`] for it.
- pub fn set_language2(&mut self, scope: &str, config_loader: Arc<syntax::Loader>) {
- let language_config = config_loader.language_config_for_scope(scope);
-
- self.set_language(language_config, Some(config_loader));
- }
-
/// Set the programming language for the file if you know the language but don't have the
/// [`syntax::LanguageConfiguration`] for it.
pub fn set_language_by_language_id(
@@ -1714,29 +1708,107 @@ impl Document {
)
}
+ pub fn lsp_diagnostic_to_diagnostic(
+ text: &Rope,
+ language_config: Option<&LanguageConfiguration>,
+ diagnostic: &helix_lsp::lsp::Diagnostic,
+ language_server_id: usize,
+ offset_encoding: helix_lsp::OffsetEncoding,
+ ) -> Option<Diagnostic> {
+ use helix_core::diagnostic::{Range, Severity::*};
+
+ // TODO: convert inside server
+ let start =
+ if let Some(start) = lsp_pos_to_pos(text, diagnostic.range.start, offset_encoding) {
+ start
+ } else {
+ log::warn!("lsp position out of bounds - {:?}", diagnostic);
+ return None;
+ };
+
+ let end = if let Some(end) = lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding) {
+ end
+ } else {
+ log::warn!("lsp position out of bounds - {:?}", diagnostic);
+ return None;
+ };
+
+ let severity = diagnostic.severity.map(|severity| match severity {
+ lsp::DiagnosticSeverity::ERROR => Error,
+ lsp::DiagnosticSeverity::WARNING => Warning,
+ lsp::DiagnosticSeverity::INFORMATION => Info,
+ lsp::DiagnosticSeverity::HINT => Hint,
+ severity => unreachable!("unrecognized diagnostic severity: {:?}", severity),
+ });
+
+ if let Some(lang_conf) = language_config {
+ if let Some(severity) = severity {
+ if severity < lang_conf.diagnostic_severity {
+ return None;
+ }
+ }
+ };
+ use helix_core::diagnostic::{DiagnosticTag, NumberOrString};
+
+ let code = match diagnostic.code.clone() {
+ 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 {
+ let new_tags = tags
+ .iter()
+ .filter_map(|tag| match *tag {
+ lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated),
+ lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary),
+ _ => None,
+ })
+ .collect();
+
+ new_tags
+ } else {
+ Vec::new()
+ };
+
+ let ends_at_word =
+ start != end && end != 0 && text.get_char(end - 1).map_or(false, char_is_word);
+ let starts_at_word = start != end && text.get_char(start).map_or(false, char_is_word);
+
+ Some(Diagnostic {
+ range: Range { start, end },
+ ends_at_word,
+ starts_at_word,
+ zero_width: start == end,
+ line: diagnostic.range.start.line as usize,
+ message: diagnostic.message.clone(),
+ severity,
+ code,
+ tags,
+ source: diagnostic.source.clone(),
+ data: diagnostic.data.clone(),
+ language_server_id,
+ })
+ }
+
#[inline]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
- pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEndedIterator {
- self.diagnostics.iter().filter(|d| {
- self.language_servers_with_feature(LanguageServerFeature::Diagnostics)
- .any(|ls| ls.id() == d.language_server_id)
- })
- }
-
pub fn replace_diagnostics(
&mut self,
diagnostics: impl IntoIterator<Item = Diagnostic>,
unchanged_sources: &[String],
- language_server_id: usize,
+ language_server_id: Option<usize>,
) {
if unchanged_sources.is_empty() {
self.clear_diagnostics(language_server_id);
} else {
self.diagnostics.retain(|d| {
- if d.language_server_id != language_server_id {
+ if language_server_id.map_or(false, |id| id != d.language_server_id) {
return true;
}
@@ -1757,9 +1829,13 @@ impl Document {
});
}
- pub fn clear_diagnostics(&mut self, language_server_id: usize) {
- self.diagnostics
- .retain(|d| d.language_server_id != language_server_id);
+ /// clears diagnostics for a given language server id if set, otherwise all diagnostics are cleared
+ pub fn clear_diagnostics(&mut self, language_server_id: Option<usize>) {
+ if let Some(id) = language_server_id {
+ self.diagnostics.retain(|d| d.language_server_id != id);
+ } else {
+ self.diagnostics.clear();
+ }
}
/// Get the document's auto pairs. If the document has a recognized
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 76429a87..c018668c 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -42,7 +42,7 @@ use anyhow::{anyhow, bail, Error};
pub use helix_core::diagnostic::Severity;
use helix_core::{
auto_pairs::AutoPairs,
- syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
+ syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap},
Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
};
use helix_dap as dap;
@@ -1477,6 +1477,10 @@ impl Editor {
self.config.clone(),
)?;
+ let diagnostics =
+ Editor::doc_diagnostics(&self.language_servers, &self.diagnostics, &doc);
+ doc.replace_diagnostics(diagnostics, &[], None);
+
if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
doc.set_diff_base(diff_base);
}
@@ -1706,6 +1710,60 @@ impl Editor {
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
}
+ /// Returns all supported diagnostics for the document
+ pub fn doc_diagnostics<'a>(
+ language_servers: &'a helix_lsp::Registry,
+ diagnostics: &'a BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
+ document: &Document,
+ ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a {
+ Editor::doc_diagnostics_with_filter(language_servers, diagnostics, document, |_, _| true)
+ }
+
+ /// Returns all supported diagnostics for the document
+ /// filtered by `filter` which is invocated with the raw `lsp::Diagnostic` and the language server id it came from
+ pub fn doc_diagnostics_with_filter<'a>(
+ language_servers: &'a helix_lsp::Registry,
+ diagnostics: &'a BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
+
+ document: &Document,
+ filter: impl Fn(&lsp::Diagnostic, usize) -> bool + 'a,
+ ) -> impl Iterator<Item = helix_core::Diagnostic> + 'a {
+ let text = document.text().clone();
+ let language_config = document.language.clone();
+ document
+ .path()
+ .and_then(|path| url::Url::from_file_path(path).ok()) // TODO log error?
+ .and_then(|uri| diagnostics.get(&uri))
+ .map(|diags| {
+ diags.iter().filter_map(move |(diagnostic, lsp_id)| {
+ let ls = language_servers.get_by_id(*lsp_id)?;
+ language_config
+ .as_ref()
+ .and_then(|c| {
+ c.language_servers.iter().find(|features| {
+ features.name == ls.name()
+ && features.has_feature(LanguageServerFeature::Diagnostics)
+ })
+ })
+ .and_then(|_| {
+ if filter(diagnostic, *lsp_id) {
+ Document::lsp_diagnostic_to_diagnostic(
+ &text,
+ language_config.as_deref(),
+ diagnostic,
+ *lsp_id,
+ ls.offset_encoding(),
+ )
+ } else {
+ None
+ }
+ })
+ })
+ })
+ .into_iter()
+ .flatten()
+ }
+
/// Gets the primary cursor position in screen coordinates,
/// or `None` if the primary cursor is not visible on screen.
pub fn cursor(&self) -> (Option<Position>, CursorKind) {