Pull diagnostics
This commit is contained in:
parent
64b38d1a28
commit
69025dde28
13 changed files with 392 additions and 100 deletions
|
@ -332,6 +332,7 @@ pub enum LanguageServerFeature {
|
|||
WorkspaceSymbols,
|
||||
// Symbols, use bitflags, see above?
|
||||
Diagnostics,
|
||||
PullDiagnostics,
|
||||
RenameSymbol,
|
||||
InlayHints,
|
||||
}
|
||||
|
@ -355,6 +356,7 @@ impl Display for LanguageServerFeature {
|
|||
DocumentSymbols => "document-symbols",
|
||||
WorkspaceSymbols => "workspace-symbols",
|
||||
Diagnostics => "diagnostics",
|
||||
PullDiagnostics => "pull-diagnostics",
|
||||
RenameSymbol => "rename-symbol",
|
||||
InlayHints => "inlay-hints",
|
||||
};
|
||||
|
|
|
@ -345,6 +345,7 @@ impl Client {
|
|||
Some(OneOf::Left(true) | OneOf::Right(_))
|
||||
),
|
||||
LanguageServerFeature::Diagnostics => true, // there's no extra server capability
|
||||
LanguageServerFeature::PullDiagnostics => capabilities.diagnostic_provider.is_some(),
|
||||
LanguageServerFeature::RenameSymbol => matches!(
|
||||
capabilities.rename_provider,
|
||||
Some(OneOf::Left(true)) | Some(OneOf::Right(_))
|
||||
|
@ -570,6 +571,9 @@ impl Client {
|
|||
did_rename: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
diagnostic: Some(lsp::DiagnosticWorkspaceClientCapabilities {
|
||||
refresh_support: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
text_document: Some(lsp::TextDocumentClientCapabilities {
|
||||
|
@ -647,6 +651,10 @@ impl Client {
|
|||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
diagnostic: Some(lsp::DiagnosticClientCapabilities {
|
||||
dynamic_registration: Some(false),
|
||||
related_document_support: Some(true),
|
||||
}),
|
||||
publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
|
||||
version_support: Some(true),
|
||||
tag_support: Some(lsp::TagSupport {
|
||||
|
@ -1223,6 +1231,32 @@ impl Client {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn text_document_diagnostic(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
previous_result_id: Option<String>,
|
||||
) -> Option<impl Future<Output = Result<Value>>> {
|
||||
let capabilities = self.capabilities();
|
||||
|
||||
// Return early if the server does not support pull diagnostic.
|
||||
let identifier = match capabilities.diagnostic_provider.as_ref()? {
|
||||
lsp::DiagnosticServerCapabilities::Options(cap) => cap.identifier.clone(),
|
||||
lsp::DiagnosticServerCapabilities::RegistrationOptions(cap) => {
|
||||
cap.diagnostic_options.identifier.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let params = lsp::DocumentDiagnosticParams {
|
||||
text_document,
|
||||
identifier,
|
||||
previous_result_id,
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
|
||||
partial_result_params: lsp::PartialResultParams::default(),
|
||||
};
|
||||
|
||||
Some(self.call::<lsp::request::DocumentDiagnosticRequest>(params))
|
||||
}
|
||||
|
||||
pub fn text_document_document_highlight(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
|
|
|
@ -463,6 +463,7 @@ pub enum MethodCall {
|
|||
RegisterCapability(lsp::RegistrationParams),
|
||||
UnregisterCapability(lsp::UnregistrationParams),
|
||||
ShowDocument(lsp::ShowDocumentParams),
|
||||
WorkspaceDiagnosticRefresh,
|
||||
}
|
||||
|
||||
impl MethodCall {
|
||||
|
@ -494,6 +495,7 @@ impl MethodCall {
|
|||
let params: lsp::ShowDocumentParams = params.parse()?;
|
||||
Self::ShowDocument(params)
|
||||
}
|
||||
lsp::request::WorkspaceDiagnosticRefresh::METHOD => Self::WorkspaceDiagnosticRefresh,
|
||||
_ => {
|
||||
return Err(Error::Unhandled);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ use helix_view::{
|
|||
align_view,
|
||||
document::{DocumentOpenError, DocumentSavedEventResult},
|
||||
editor::{ConfigEvent, EditorEvent},
|
||||
events::DiagnosticsDidChange,
|
||||
graphics::Rect,
|
||||
theme,
|
||||
tree::Layout,
|
||||
|
@ -33,7 +32,7 @@ use crate::{
|
|||
use log::{debug, error, info, warn};
|
||||
#[cfg(not(feature = "integration"))]
|
||||
use std::io::stdout;
|
||||
use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
|
||||
use std::{io::stdin, path::Path, sync::Arc};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use anyhow::Context;
|
||||
|
@ -741,9 +740,14 @@ impl Application {
|
|||
doc.text(),
|
||||
language_id,
|
||||
));
|
||||
|
||||
handlers::diagnostics::pull_diagnostics_for_document(
|
||||
doc,
|
||||
language_server,
|
||||
);
|
||||
}
|
||||
}
|
||||
Notification::PublishDiagnostics(mut params) => {
|
||||
Notification::PublishDiagnostics(params) => {
|
||||
let uri = match helix_core::Uri::try_from(params.uri) {
|
||||
Ok(uri) => uri,
|
||||
Err(err) => {
|
||||
|
@ -756,100 +760,20 @@ impl Application {
|
|||
log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name());
|
||||
return;
|
||||
}
|
||||
// have to inline the function because of borrow checking...
|
||||
let doc = self.editor.documents.values_mut()
|
||||
.find(|doc| doc.uri().is_some_and(|u| u == uri))
|
||||
.filter(|doc| {
|
||||
if let Some(version) = params.version {
|
||||
if version != doc.version() {
|
||||
log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
let mut unchanged_diag_sources = Vec::new();
|
||||
if let Some(doc) = &doc {
|
||||
let lang_conf = doc.language.clone();
|
||||
|
||||
if let Some(lang_conf) = &lang_conf {
|
||||
if let Some(old_diagnostics) = self.editor.diagnostics.get(&uri) {
|
||||
if !lang_conf.persistent_diagnostic_sources.is_empty() {
|
||||
// Sort diagnostics first by severity and then by line numbers.
|
||||
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
||||
params
|
||||
let diagnostics: Vec<(lsp::Diagnostic, LanguageServerId)> = params
|
||||
.diagnostics
|
||||
.sort_by_key(|d| (d.severity, d.range.start));
|
||||
}
|
||||
for source in &lang_conf.persistent_diagnostic_sources {
|
||||
let new_diagnostics = params
|
||||
.diagnostics
|
||||
.iter()
|
||||
.filter(|d| d.source.as_ref() == Some(source));
|
||||
let old_diagnostics = old_diagnostics
|
||||
.iter()
|
||||
.filter(|(d, d_server)| {
|
||||
*d_server == server_id
|
||||
&& d.source.as_ref() == Some(source)
|
||||
})
|
||||
.map(|(d, _)| d);
|
||||
if new_diagnostics.eq(old_diagnostics) {
|
||||
unchanged_diag_sources.push(source.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.into_iter()
|
||||
.map(|d| (d, server_id))
|
||||
.collect();
|
||||
|
||||
let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
|
||||
|
||||
// Insert the original lsp::Diagnostics here because we may have no open document
|
||||
// for diagnosic message and so we can't calculate the exact position.
|
||||
// When using them later in the diagnostics picker, we calculate them on-demand.
|
||||
let diagnostics = match self.editor.diagnostics.entry(uri) {
|
||||
Entry::Occupied(o) => {
|
||||
let current_diagnostics = o.into_mut();
|
||||
// there may entries of other language servers, which is why we can't overwrite the whole entry
|
||||
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
|
||||
current_diagnostics.extend(diagnostics);
|
||||
current_diagnostics
|
||||
// Sort diagnostics first by severity and then by line numbers.
|
||||
}
|
||||
Entry::Vacant(v) => v.insert(diagnostics.collect()),
|
||||
};
|
||||
|
||||
// Sort diagnostics first by severity and then by line numbers.
|
||||
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
||||
diagnostics
|
||||
.sort_by_key(|(d, server_id)| (d.severity, d.range.start, *server_id));
|
||||
|
||||
if let Some(doc) = doc {
|
||||
let diagnostic_of_language_server_and_not_in_unchanged_sources =
|
||||
|diagnostic: &lsp::Diagnostic, ls_id| {
|
||||
ls_id == server_id
|
||||
&& diagnostic.source.as_ref().map_or(true, |source| {
|
||||
!unchanged_diag_sources.contains(source)
|
||||
})
|
||||
};
|
||||
let diagnostics = Editor::doc_diagnostics_with_filter(
|
||||
&self.editor.language_servers,
|
||||
&self.editor.diagnostics,
|
||||
doc,
|
||||
diagnostic_of_language_server_and_not_in_unchanged_sources,
|
||||
);
|
||||
doc.replace_diagnostics(
|
||||
self.editor.add_diagnostics(
|
||||
diagnostics,
|
||||
&unchanged_diag_sources,
|
||||
Some(server_id),
|
||||
server_id,
|
||||
uri,
|
||||
params.version,
|
||||
None,
|
||||
);
|
||||
|
||||
let doc = doc.id();
|
||||
helix_event::dispatch(DiagnosticsDidChange {
|
||||
editor: &mut self.editor,
|
||||
doc,
|
||||
});
|
||||
}
|
||||
}
|
||||
Notification::ShowMessage(params) => {
|
||||
if self.config.load().editor.lsp.display_messages {
|
||||
|
@ -1124,6 +1048,16 @@ impl Application {
|
|||
let result = self.handle_show_document(params, offset_encoding);
|
||||
Ok(json!(result))
|
||||
}
|
||||
Ok(MethodCall::WorkspaceDiagnosticRefresh) => {
|
||||
for document in self.editor.documents() {
|
||||
let language_server = language_server!();
|
||||
handlers::diagnostics::pull_diagnostics_for_document(
|
||||
document,
|
||||
language_server,
|
||||
);
|
||||
}
|
||||
Ok(serde_json::Value::Null)
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(language_server!().reply(id, reply));
|
||||
|
|
|
@ -903,6 +903,10 @@ fn goto_buffer(editor: &mut Editor, direction: Direction, count: usize) {
|
|||
|
||||
let id = *id;
|
||||
|
||||
if let Some(doc) = editor.document(id) {
|
||||
helix_event::dispatch(helix_view::events::DocumentDidOpen { doc });
|
||||
};
|
||||
|
||||
editor.switch(id, Action::Replace);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use helix_event::{events, register_event};
|
||||
use helix_view::document::Mode;
|
||||
use helix_view::events::{
|
||||
DiagnosticsDidChange, DocumentDidChange, DocumentFocusLost, SelectionDidChange,
|
||||
DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen, DocumentFocusLost, SelectionDidChange,
|
||||
};
|
||||
|
||||
use crate::commands;
|
||||
|
@ -18,6 +18,7 @@ pub fn register() {
|
|||
register_event::<PostInsertChar>();
|
||||
register_event::<PostCommand>();
|
||||
register_event::<DocumentDidChange>();
|
||||
register_event::<DocumentDidOpen>();
|
||||
register_event::<DocumentFocusLost>();
|
||||
register_event::<SelectionDidChange>();
|
||||
register_event::<DiagnosticsDidChange>();
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::config::Config;
|
|||
use crate::events;
|
||||
use crate::handlers::auto_save::AutoSaveHandler;
|
||||
use crate::handlers::completion::CompletionHandler;
|
||||
use crate::handlers::diagnostics::PullDiagnosticsHandler;
|
||||
use crate::handlers::signature_help::SignatureHelpHandler;
|
||||
|
||||
pub use completion::trigger_auto_completion;
|
||||
|
@ -14,7 +15,7 @@ pub use helix_view::handlers::Handlers;
|
|||
|
||||
mod auto_save;
|
||||
pub mod completion;
|
||||
mod diagnostics;
|
||||
pub mod diagnostics;
|
||||
mod signature_help;
|
||||
mod snippet;
|
||||
|
||||
|
@ -24,11 +25,13 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
|
|||
let completions = CompletionHandler::new(config).spawn();
|
||||
let signature_hints = SignatureHelpHandler::new().spawn();
|
||||
let auto_save = AutoSaveHandler::new().spawn();
|
||||
let pull_diagnostics = PullDiagnosticsHandler::new().spawn();
|
||||
|
||||
let handlers = Handlers {
|
||||
completions,
|
||||
signature_hints,
|
||||
auto_save,
|
||||
pull_diagnostics,
|
||||
};
|
||||
|
||||
completion::register_hooks(&handlers);
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
|
||||
use helix_core::syntax::LanguageServerFeature;
|
||||
use helix_core::Uri;
|
||||
use helix_event::{register_hook, send_blocking};
|
||||
use helix_lsp::lsp::{self, Diagnostic};
|
||||
use helix_lsp::LanguageServerId;
|
||||
use helix_view::document::Mode;
|
||||
use helix_view::events::DiagnosticsDidChange;
|
||||
use helix_view::events::{DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen};
|
||||
use helix_view::handlers::diagnostics::DiagnosticEvent;
|
||||
use helix_view::handlers::lsp::PullDiagnosticsEvent;
|
||||
use helix_view::handlers::Handlers;
|
||||
use helix_view::{DocumentId, Editor};
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::events::OnModeSwitch;
|
||||
use crate::job;
|
||||
|
||||
pub(super) fn register_hooks(_handlers: &Handlers) {
|
||||
pub(super) fn register_hooks(handlers: &Handlers) {
|
||||
register_hook!(move |event: &mut DiagnosticsDidChange<'_>| {
|
||||
if event.editor.mode != Mode::Insert {
|
||||
for (view, _) in event.editor.tree.views_mut() {
|
||||
|
@ -21,4 +32,200 @@ pub(super) fn register_hooks(_handlers: &Handlers) {
|
|||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let tx = handlers.pull_diagnostics.clone();
|
||||
register_hook!(move |event: &mut DocumentDidChange<'_>| {
|
||||
if event
|
||||
.doc
|
||||
.has_language_server_with_feature(LanguageServerFeature::PullDiagnostics)
|
||||
{
|
||||
let document_id = event.doc.id();
|
||||
send_blocking(&tx, PullDiagnosticsEvent { document_id });
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
|
||||
if event
|
||||
.doc
|
||||
.has_language_server_with_feature(LanguageServerFeature::PullDiagnostics)
|
||||
{
|
||||
let document_id = event.doc.id();
|
||||
job::dispatch_blocking(move |editor, _| {
|
||||
let Some(doc) = editor.document(document_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let language_servers =
|
||||
doc.language_servers_with_feature(LanguageServerFeature::PullDiagnostics);
|
||||
|
||||
for language_server in language_servers {
|
||||
pull_diagnostics_for_document(doc, language_server);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct PullDiagnosticsHandler {
|
||||
document_ids: HashSet<DocumentId>,
|
||||
}
|
||||
|
||||
impl PullDiagnosticsHandler {
|
||||
pub fn new() -> PullDiagnosticsHandler {
|
||||
PullDiagnosticsHandler {
|
||||
document_ids: [].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl helix_event::AsyncHook for PullDiagnosticsHandler {
|
||||
type Event = PullDiagnosticsEvent;
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: Self::Event,
|
||||
_: Option<tokio::time::Instant>,
|
||||
) -> Option<tokio::time::Instant> {
|
||||
self.document_ids.insert(event.document_id);
|
||||
Some(Instant::now() + Duration::from_millis(120))
|
||||
}
|
||||
|
||||
fn finish_debounce(&mut self) {
|
||||
for document_id in self.document_ids.clone() {
|
||||
job::dispatch_blocking(move |editor, _| {
|
||||
let doc = editor.document(document_id);
|
||||
let Some(doc) = doc else {
|
||||
return;
|
||||
};
|
||||
|
||||
let language_servers =
|
||||
doc.language_servers_with_feature(LanguageServerFeature::PullDiagnostics);
|
||||
|
||||
for language_server in language_servers {
|
||||
pull_diagnostics_for_document(doc, language_server);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pull_diagnostics_for_document(
|
||||
doc: &helix_view::Document,
|
||||
language_server: &helix_lsp::Client,
|
||||
) {
|
||||
let Some(future) = language_server
|
||||
.text_document_diagnostic(doc.identifier(), doc.previous_diagnostic_id.clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(uri) = doc.uri() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let server_id = language_server.id();
|
||||
let document_id = doc.id();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match future.await {
|
||||
Ok(res) => {
|
||||
job::dispatch(move |editor, _| {
|
||||
let response = match serde_json::from_value(res) {
|
||||
Ok(result) => result,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
handle_pull_diagnostics_response(editor, response, server_id, uri, document_id)
|
||||
})
|
||||
.await
|
||||
}
|
||||
Err(err) => log::error!("Pull diagnostic request failed: {err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_pull_diagnostics_response(
|
||||
editor: &mut Editor,
|
||||
response: lsp::DocumentDiagnosticReport,
|
||||
server_id: LanguageServerId,
|
||||
uri: Uri,
|
||||
document_id: DocumentId,
|
||||
) {
|
||||
let Some(doc) = editor.document_mut(document_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match response {
|
||||
lsp::DocumentDiagnosticReport::Full(report) => {
|
||||
// Original file diagnostic
|
||||
add_diagnostics_to_editor(
|
||||
editor,
|
||||
uri,
|
||||
report.full_document_diagnostic_report.items,
|
||||
report.full_document_diagnostic_report.result_id,
|
||||
server_id,
|
||||
);
|
||||
|
||||
// Related files diagnostic
|
||||
handle_document_diagnostic_report_kind(
|
||||
editor,
|
||||
document_id,
|
||||
report.related_documents,
|
||||
server_id,
|
||||
);
|
||||
}
|
||||
lsp::DocumentDiagnosticReport::Unchanged(report) => {
|
||||
doc.previous_diagnostic_id =
|
||||
Some(report.unchanged_document_diagnostic_report.result_id);
|
||||
|
||||
handle_document_diagnostic_report_kind(
|
||||
editor,
|
||||
document_id,
|
||||
report.related_documents,
|
||||
server_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_diagnostics_to_editor(
|
||||
editor: &mut Editor,
|
||||
uri: Uri,
|
||||
report: Vec<lsp::Diagnostic>,
|
||||
result_id: Option<String>,
|
||||
server_id: LanguageServerId,
|
||||
) {
|
||||
let diagnostics: Vec<(Diagnostic, LanguageServerId)> =
|
||||
report.into_iter().map(|d| (d, server_id)).collect();
|
||||
|
||||
editor.add_diagnostics(diagnostics, server_id, uri, None, result_id);
|
||||
}
|
||||
|
||||
fn handle_document_diagnostic_report_kind(
|
||||
editor: &mut Editor,
|
||||
document_id: DocumentId,
|
||||
report: Option<HashMap<lsp::Url, lsp::DocumentDiagnosticReportKind>>,
|
||||
server_id: LanguageServerId,
|
||||
) {
|
||||
for (url, report) in report.into_iter().flatten() {
|
||||
match report {
|
||||
lsp::DocumentDiagnosticReportKind::Full(report) => {
|
||||
let Ok(uri) = Uri::try_from(url) else {
|
||||
return;
|
||||
};
|
||||
|
||||
add_diagnostics_to_editor(editor, uri, report.items, report.result_id, server_id);
|
||||
}
|
||||
lsp::DocumentDiagnosticReportKind::Unchanged(report) => {
|
||||
let Some(doc) = editor.document_mut(document_id) else {
|
||||
return;
|
||||
};
|
||||
doc.previous_diagnostic_id = Some(report.result_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,6 +194,8 @@ pub struct Document {
|
|||
pub focused_at: std::time::Instant,
|
||||
|
||||
pub readonly: bool,
|
||||
|
||||
pub previous_diagnostic_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Inlay hints for a single `(Document, View)` combo.
|
||||
|
@ -687,6 +689,7 @@ impl Document {
|
|||
focused_at: std::time::Instant::now(),
|
||||
readonly: false,
|
||||
jump_labels: HashMap::new(),
|
||||
previous_diagnostic_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2153,6 +2156,10 @@ impl Document {
|
|||
pub fn reset_all_inlay_hints(&mut self) {
|
||||
self.inlay_hints = Default::default();
|
||||
}
|
||||
|
||||
pub fn has_language_server_with_feature(&self, feature: LanguageServerFeature) -> bool {
|
||||
self.language_servers_with_feature(feature).next().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
document::{
|
||||
DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint,
|
||||
},
|
||||
events::DocumentFocusLost,
|
||||
events::{DocumentDidOpen, DocumentFocusLost},
|
||||
graphics::{CursorKind, Rect},
|
||||
handlers::Handlers,
|
||||
info::Info,
|
||||
|
@ -1772,6 +1772,11 @@ impl Editor {
|
|||
};
|
||||
|
||||
self.switch(id, action);
|
||||
|
||||
if let Some(doc) = self.document_mut(id) {
|
||||
helix_event::dispatch(DocumentDidOpen { doc });
|
||||
};
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
@ -2209,6 +2214,93 @@ impl Editor {
|
|||
current_view.id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_diagnostics(
|
||||
&mut self,
|
||||
diagnostics: Vec<(lsp::Diagnostic, LanguageServerId)>,
|
||||
server_id: LanguageServerId,
|
||||
uri: helix_core::Uri,
|
||||
document_version: Option<i32>,
|
||||
result_id: Option<String>,
|
||||
) {
|
||||
let Some(doc) = self
|
||||
.documents
|
||||
.values_mut()
|
||||
.find(|doc| doc.uri().is_some_and(|u| u == uri))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(version) = document_version {
|
||||
if version != doc.version() {
|
||||
log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut unchanged_diag_sources = Vec::new();
|
||||
if let Some(old_diagnostics) = self.diagnostics.get(&uri) {
|
||||
if let Some(lang_conf) = doc.language_config() {
|
||||
for source in &lang_conf.persistent_diagnostic_sources {
|
||||
let new_diagnostics = diagnostics
|
||||
.iter()
|
||||
.filter(|d| d.0.source.as_ref() == Some(source));
|
||||
let old_diagnostics = old_diagnostics
|
||||
.iter()
|
||||
.filter(|(d, d_server)| {
|
||||
*d_server == server_id && d.source.as_ref() == Some(source)
|
||||
})
|
||||
.map(|(d, _)| d);
|
||||
if new_diagnostics.map(|x| &x.0).eq(old_diagnostics) {
|
||||
unchanged_diag_sources.push(source.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the original lsp::Diagnostics here because we may have no open document
|
||||
// for diagnosic message and so we can't calculate the exact position.
|
||||
// When using them later in the diagnostics picker, we calculate them on-demand.
|
||||
let diagnostics = match self.diagnostics.entry(uri) {
|
||||
std::collections::btree_map::Entry::Occupied(o) => {
|
||||
let current_diagnostics = o.into_mut();
|
||||
// there may entries of other language servers, which is why we can't overwrite the whole entry
|
||||
current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
|
||||
current_diagnostics.extend(diagnostics);
|
||||
current_diagnostics
|
||||
// Sort diagnostics first by severity and then by line numbers.
|
||||
}
|
||||
std::collections::btree_map::Entry::Vacant(v) => v.insert(diagnostics),
|
||||
};
|
||||
|
||||
// Sort diagnostics first by severity and then by line numbers.
|
||||
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
|
||||
diagnostics.sort_by_key(|(d, server_id)| (d.severity, d.range.start, *server_id));
|
||||
|
||||
let diagnostic_of_language_server_and_not_in_unchanged_sources =
|
||||
|diagnostic: &lsp::Diagnostic, ls_id| {
|
||||
ls_id == server_id
|
||||
&& diagnostic
|
||||
.source
|
||||
.as_ref()
|
||||
.map_or(true, |source| !unchanged_diag_sources.contains(source))
|
||||
};
|
||||
let diagnostics = Editor::doc_diagnostics_with_filter(
|
||||
&self.language_servers,
|
||||
&self.diagnostics,
|
||||
doc,
|
||||
diagnostic_of_language_server_and_not_in_unchanged_sources,
|
||||
);
|
||||
doc.replace_diagnostics(diagnostics, &unchanged_diag_sources, Some(server_id));
|
||||
|
||||
if result_id.is_some() {
|
||||
doc.previous_diagnostic_id = result_id;
|
||||
}
|
||||
|
||||
let doc = doc.id();
|
||||
|
||||
helix_event::dispatch(crate::events::DiagnosticsDidChange { editor: self, doc });
|
||||
}
|
||||
}
|
||||
|
||||
fn try_restore_indent(doc: &mut Document, view: &mut View) {
|
||||
|
|
|
@ -11,6 +11,7 @@ events! {
|
|||
changes: &'a ChangeSet,
|
||||
ghost_transaction: bool
|
||||
}
|
||||
DocumentDidOpen<'a> { doc: &'a Document}
|
||||
SelectionDidChange<'a> { doc: &'a mut Document, view: ViewId }
|
||||
DiagnosticsDidChange<'a> { editor: &'a mut Editor, doc: DocumentId }
|
||||
// called **after** a document loses focus (but not when its closed)
|
||||
|
|
|
@ -19,6 +19,7 @@ pub struct Handlers {
|
|||
pub completions: Sender<lsp::CompletionEvent>,
|
||||
pub signature_hints: Sender<lsp::SignatureHelpEvent>,
|
||||
pub auto_save: Sender<AutoSaveEvent>,
|
||||
pub pull_diagnostics: Sender<lsp::PullDiagnosticsEvent>,
|
||||
}
|
||||
|
||||
impl Handlers {
|
||||
|
|
|
@ -47,6 +47,10 @@ pub enum SignatureHelpEvent {
|
|||
RequestComplete { open: bool },
|
||||
}
|
||||
|
||||
pub struct PullDiagnosticsEvent {
|
||||
pub document_id: DocumentId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApplyEditError {
|
||||
pub kind: ApplyEditErrorKind,
|
||||
|
|
Loading…
Reference in a new issue