Skip to content

Commit

Permalink
Pull diagnostics on open
Browse files Browse the repository at this point in the history
  • Loading branch information
SofusA committed Aug 14, 2024
1 parent 96a984c commit 4177ba6
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 77 deletions.
5 changes: 4 additions & 1 deletion helix-term/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use helix_event::{events, register_event};
use helix_view::document::Mode;
use helix_view::events::{DiagnosticsDidChange, DocumentDidChange, SelectionDidChange};
use helix_view::events::{
DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen, SelectionDidChange,
};

use crate::commands;
use crate::keymap::MappableCommand;
Expand All @@ -16,6 +18,7 @@ pub fn register() {
register_event::<PostInsertChar>();
register_event::<PostCommand>();
register_event::<DocumentDidChange>();
register_event::<DocumentDidOpen>();
register_event::<SelectionDidChange>();
register_event::<DiagnosticsDidChange>();
}
11 changes: 8 additions & 3 deletions helix-term/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ 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::diagnostics::PullDiagnosticsForLanguageServersHandler;
use crate::handlers::signature_help::SignatureHelpHandler;

pub use completion::trigger_auto_completion;
pub use helix_view::handlers::Handlers;

use self::diagnostics::PullDiagnosticsForDocumentsHandler;

mod auto_save;
pub mod completion;
mod diagnostics;
Expand All @@ -25,13 +27,16 @@ 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 pull_diagnostics_for_language_servers =
PullDiagnosticsForLanguageServersHandler::new().spawn();
let pull_diagnostics_for_documents = PullDiagnosticsForDocumentsHandler::new().spawn();

let handlers = Handlers {
completions,
signature_hints,
auto_save,
pull_diagnostics,
pull_diagnostics_for_language_servers,
pull_diagnostics_for_documents,
};

completion::register_hooks(&handlers);
Expand Down
206 changes: 136 additions & 70 deletions helix-term/src/handlers/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::time::Duration;

use helix_core::syntax::LanguageServerFeature;
Expand All @@ -7,11 +7,13 @@ 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, DocumentDidChange};
use helix_view::events::{DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen};
use helix_view::handlers::diagnostics::DiagnosticEvent;
use helix_view::handlers::lsp::PullDiagnosticsEvent;
use helix_view::handlers::lsp::{
PullDiagnosticsForDocumentsEvent, PullDiagnosticsForLanguageServersEvent,
};
use helix_view::handlers::Handlers;
use helix_view::Editor;
use helix_view::{DocumentId, Editor};
use tokio::time::Instant;

use crate::events::OnModeSwitch;
Expand All @@ -33,7 +35,7 @@ pub(super) fn register_hooks(handlers: &Handlers) {
Ok(())
});

let tx = handlers.pull_diagnostics.clone();
let tx = handlers.pull_diagnostics_for_language_servers.clone();
register_hook!(move |event: &mut DocumentDidChange<'_>| {
if event
.doc
Expand All @@ -47,117 +49,179 @@ pub(super) fn register_hooks(handlers: &Handlers) {

send_blocking(
&tx,
PullDiagnosticsEvent {
PullDiagnosticsForLanguageServersEvent {
language_server_ids,
},
);
}
Ok(())
});
}

const TIMEOUT: u64 = 120;
let tx = handlers.pull_diagnostics_for_documents.clone();
register_hook!(move |event: &mut DocumentDidOpen<'_>| {
if event
.doc
.has_language_server_with_feature(LanguageServerFeature::PullDiagnostics)
{
send_blocking(
&tx,
PullDiagnosticsForDocumentsEvent {
document_id: event.doc.id(),
},
);
}

Ok(())
});
}

#[derive(Debug)]
pub(super) struct PullDiagnosticsHandler {
language_server_ids: Vec<LanguageServerId>,
pub(super) struct PullDiagnosticsForLanguageServersHandler {
language_server_ids: HashSet<LanguageServerId>,
}

impl PullDiagnosticsHandler {
pub fn new() -> PullDiagnosticsHandler {
PullDiagnosticsHandler {
language_server_ids: vec![],
impl PullDiagnosticsForLanguageServersHandler {
pub fn new() -> PullDiagnosticsForLanguageServersHandler {
PullDiagnosticsForLanguageServersHandler {
language_server_ids: [].into(),
}
}
}
pub(super) struct PullDiagnosticsForDocumentsHandler {
document_ids: HashSet<DocumentId>,
}

impl helix_event::AsyncHook for PullDiagnosticsHandler {
type Event = PullDiagnosticsEvent;
impl PullDiagnosticsForDocumentsHandler {
pub fn new() -> PullDiagnosticsForDocumentsHandler {
PullDiagnosticsForDocumentsHandler {
document_ids: [].into(),
}
}
}

impl helix_event::AsyncHook for PullDiagnosticsForLanguageServersHandler {
type Event = PullDiagnosticsForLanguageServersEvent;

fn handle_event(
&mut self,
event: Self::Event,
_: Option<tokio::time::Instant>,
) -> Option<tokio::time::Instant> {
self.language_server_ids = event.language_server_ids;
Some(Instant::now() + Duration::from_millis(TIMEOUT))
Some(Instant::now() + Duration::from_millis(120))
}

fn finish_debounce(&mut self) {
let language_servers = self.language_server_ids.clone();
job::dispatch_blocking(move |editor, _| {
pull_diagnostic_for_document(
editor,
language_servers,
editor.documents().map(|x| x.id()).collect(),
)
pull_diagnostic_for_language_servers(editor, language_servers)
})
}
}

fn pull_diagnostic_for_document(
impl helix_event::AsyncHook for PullDiagnosticsForDocumentsHandler {
type Event = PullDiagnosticsForDocumentsEvent;

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(50))
}

fn finish_debounce(&mut self) {
let document_ids = self.document_ids.clone();
job::dispatch_blocking(move |editor, _| {
pull_diagnostics_for_documents(editor, document_ids)
})
}
}

fn pull_diagnostics_for_documents(editor: &mut Editor, document_ids: HashSet<DocumentId>) {
for document_id in document_ids {
let Some(doc) = editor.document_mut(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);
}
}
}

fn pull_diagnostic_for_language_servers(
editor: &mut Editor,
language_server_ids: Vec<LanguageServerId>,
document_ids: Vec<helix_view::DocumentId>,
language_server_ids: HashSet<LanguageServerId>,
) {
for document_id in document_ids.clone() {
let doc = doc_mut!(editor, &document_id);
let document_ids: Vec<_> = editor.documents().map(|x| x.id()).collect();

for document_id in document_ids {
let Some(doc) = editor.document_mut(document_id) else {
return;
};

let language_servers = doc
.language_servers()
.filter(|x| language_server_ids.contains(&x.id()));

for language_server in language_servers {
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();

tokio::spawn(async move {
match future.await {
Ok(res) => {
job::dispatch(move |editor, _| {
log::error!("{}", res);

let parsed_response: Option<lsp::DocumentDiagnosticReport> =
match serde_json::from_value(res) {
Ok(result) => Some(result),
Err(_) => None,
};

let Some(response) = parsed_response else {
return;
};

show_pull_diagnostics(editor, response, server_id, uri, &document_id)
})
.await
}
Err(err) => log::error!("signature help request failed: {err}"),
}
});
pull_diagnostics_for_document(doc, language_server);
}
}
}

fn show_pull_diagnostics(
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: &helix_view::DocumentId,
document_id: helix_view::DocumentId,
) {
let doc = doc_mut!(editor, document_id);
let Some(doc) = editor.document_mut(document_id) else {
return;
};

match response {
lsp::DocumentDiagnosticReport::Full(report) => {
// Original file diagnostic
parse_diagnostic(
add_diagnostics_to_editor(
editor,
uri,
report.full_document_diagnostic_report.items,
Expand Down Expand Up @@ -187,7 +251,7 @@ fn show_pull_diagnostics(
}
}

fn parse_diagnostic(
fn add_diagnostics_to_editor(
editor: &mut Editor,
uri: Uri,
report: Vec<lsp::Diagnostic>,
Expand All @@ -202,7 +266,7 @@ fn parse_diagnostic(

fn handle_document_diagnostic_report_kind(
editor: &mut Editor,
document_id: &helix_view::DocumentId,
document_id: helix_view::DocumentId,
report: Option<HashMap<lsp::Url, lsp::DocumentDiagnosticReportKind>>,
server_id: LanguageServerId,
) {
Expand All @@ -213,10 +277,12 @@ fn handle_document_diagnostic_report_kind(
return;
};

parse_diagnostic(editor, uri, report.items, report.result_id, server_id);
add_diagnostics_to_editor(editor, uri, report.items, report.result_id, server_id);
}
lsp::DocumentDiagnosticReportKind::Unchanged(report) => {
let doc = doc_mut!(editor, &document_id);
let Some(doc) = editor.document_mut(document_id) else {
return;
};
doc.previous_diagnostic_id = Some(report.result_id);
}
}
Expand Down
6 changes: 6 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
document::{
DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint,
},
events::DocumentDidOpen,
graphics::{CursorKind, Rect},
handlers::Handlers,
info::Info,
Expand Down Expand Up @@ -1721,6 +1722,11 @@ impl Editor {
};

self.switch(id, action);

if let Some(doc) = self.document_mut(id) {
helix_event::dispatch(DocumentDidOpen { doc });
};

Ok(id)
}

Expand Down
1 change: 1 addition & 0 deletions helix-view/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{Document, DocumentId, Editor, ViewId};

events! {
DocumentDidChange<'a> { doc: &'a mut Document, view: ViewId, old_text: &'a Rope }
DocumentDidOpen<'a> { doc: &'a mut Document}
SelectionDidChange<'a> { doc: &'a mut Document, view: ViewId }
DiagnosticsDidChange<'a> { editor: &'a mut Editor, doc: DocumentId }
}
3 changes: 2 additions & 1 deletion helix-view/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ 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>,
pub pull_diagnostics_for_language_servers: Sender<lsp::PullDiagnosticsForLanguageServersEvent>,
pub pull_diagnostics_for_documents: Sender<lsp::PullDiagnosticsForDocumentsEvent>,
}

impl Handlers {
Expand Down
Loading

0 comments on commit 4177ba6

Please sign in to comment.