Add document and LSP lifecycle events, move some callbacks into hooks

This adds events for:

* a document being opened
* a document being closed
* a language server sending the initialized notification
* a language server exiting

and also moves some handling done for these scenarios into hooks,
generally moving more into helix-view. A hook is also added on
`DocumentDidChange` which sends the `text_document_did_change`
notification - this resolves a TODO in `document`.
This commit is contained in:
Michael Davis 2025-03-02 12:50:24 -05:00
parent 2cc33b5c47
commit 6da1a79d80
No known key found for this signature in database
8 changed files with 101 additions and 45 deletions

View file

@ -704,28 +704,10 @@ impl Application {
language_server.did_change_configuration(config.clone());
}
let docs = self
.editor
.documents()
.filter(|doc| doc.supports_language_server(server_id));
// trigger textDocument/didOpen for docs that are already open
for doc in docs {
let url = match doc.url() {
Some(url) => url,
None => continue, // skip documents with no path
};
let language_id =
doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
language_server.text_document_did_open(
url,
doc.version(),
doc.text(),
language_id,
);
}
helix_event::dispatch(helix_view::events::LanguageServerInitialized {
editor: &mut self.editor,
server_id,
});
}
Notification::PublishDiagnostics(params) => {
let uri = match helix_core::Uri::try_from(params.uri) {
@ -870,6 +852,11 @@ impl Application {
doc.clear_diagnostics_for_language_server(server_id);
}
helix_event::dispatch(helix_view::events::LanguageServerExited {
editor: &mut self.editor,
server_id,
});
// Remove the language server from the registry.
self.editor.language_servers.remove_by_id(server_id);
}

View file

@ -1,7 +1,8 @@
use helix_event::{events, register_event};
use helix_view::document::Mode;
use helix_view::events::{
DiagnosticsDidChange, DocumentDidChange, DocumentFocusLost, SelectionDidChange,
DiagnosticsDidChange, DocumentDidChange, DocumentDidClose, DocumentDidOpen, DocumentFocusLost,
LanguageServerExited, LanguageServerInitialized, SelectionDidChange,
};
use crate::commands;
@ -17,8 +18,12 @@ pub fn register() {
register_event::<OnModeSwitch>();
register_event::<PostInsertChar>();
register_event::<PostCommand>();
register_event::<DocumentDidOpen>();
register_event::<DocumentDidChange>();
register_event::<DocumentDidClose>();
register_event::<DocumentFocusLost>();
register_event::<SelectionDidChange>();
register_event::<DiagnosticsDidChange>();
register_event::<LanguageServerInitialized>();
register_event::<LanguageServerExited>();
}

View file

@ -29,6 +29,7 @@ pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
auto_save,
};
helix_view::handlers::register_hooks(&handlers);
completion::register_hooks(&handlers);
signature_help::register_hooks(&handlers);
auto_save::register_hooks(&handlers);

View file

@ -1489,19 +1489,6 @@ impl Document {
});
}
if emit_lsp_notification {
// TODO: move to hook
// emit lsp notification
for language_server in self.language_servers() {
let _ = language_server.text_document_did_change(
self.versioned_identifier(),
&old_doc,
self.text(),
changes,
);
}
}
true
}

View file

@ -4,7 +4,7 @@ use crate::{
document::{
DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint,
},
events::DocumentFocusLost,
events::{DocumentDidClose, DocumentDidOpen, DocumentFocusLost},
graphics::{CursorKind, Rect},
handlers::Handlers,
info::Info,
@ -1783,10 +1783,16 @@ impl Editor {
let id = self.new_document(doc);
self.launch_language_servers(id);
helix_event::dispatch(DocumentDidOpen {
editor: self,
doc: id,
});
id
};
self.switch(id, action);
Ok(id)
}
@ -1800,7 +1806,7 @@ impl Editor {
}
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
let doc = match self.documents.get_mut(&doc_id) {
let doc = match self.documents.remove(&doc_id) {
Some(doc) => doc,
None => return Err(CloseError::DoesNotExist),
};
@ -1811,10 +1817,6 @@ impl Editor {
// This will also disallow any follow-up writes
self.saves.remove(&doc_id);
for language_server in doc.language_servers() {
language_server.text_document_did_close(doc.identifier());
}
enum Action {
Close(ViewId),
ReplaceDoc(ViewId, DocumentId),
@ -1851,8 +1853,6 @@ impl Editor {
}
}
self.documents.remove(&doc_id);
// If the document we removed was visible in all views, we will have no more views. We don't
// want to close the editor just for a simple buffer close, so we need to create a new view
// containing either an existing document, or a brand new document.
@ -1872,6 +1872,8 @@ impl Editor {
self._refresh();
helix_event::dispatch(DocumentDidClose { editor: self, doc });
Ok(())
}

View file

@ -1,9 +1,14 @@
use helix_core::{ChangeSet, Rope};
use helix_event::events;
use helix_lsp::LanguageServerId;
use crate::{Document, DocumentId, Editor, ViewId};
events! {
DocumentDidOpen<'a> {
editor: &'a mut Editor,
doc: DocumentId
}
DocumentDidChange<'a> {
doc: &'a mut Document,
view: ViewId,
@ -11,8 +16,21 @@ events! {
changes: &'a ChangeSet,
ghost_transaction: bool
}
DocumentDidClose<'a> {
editor: &'a mut Editor,
doc: 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)
DocumentFocusLost<'a> { editor: &'a mut Editor, doc: DocumentId }
LanguageServerInitialized<'a> {
editor: &'a mut Editor,
server_id: LanguageServerId
}
LanguageServerExited<'a> {
editor: &'a mut Editor,
server_id: LanguageServerId
}
}

View file

@ -46,3 +46,7 @@ impl Handlers {
send_blocking(&self.signature_hints, event)
}
}
pub fn register_hooks(handlers: &Handlers) {
lsp::register_hooks(handlers);
}

View file

@ -2,13 +2,18 @@ use std::collections::btree_map::Entry;
use std::fmt::Display;
use crate::editor::Action;
use crate::events::DiagnosticsDidChange;
use crate::events::{
DiagnosticsDidChange, DocumentDidChange, DocumentDidClose, LanguageServerInitialized,
};
use crate::Editor;
use helix_core::diagnostic::DiagnosticProvider;
use helix_core::Uri;
use helix_event::register_hook;
use helix_lsp::util::generate_transaction_from_edits;
use helix_lsp::{lsp, LanguageServerId, OffsetEncoding};
use super::Handlers;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SignatureHelpInvoked {
Automatic,
@ -381,3 +386,50 @@ impl Editor {
});
}
}
pub fn register_hooks(_handlers: &Handlers) {
register_hook!(move |event: &mut LanguageServerInitialized<'_>| {
let language_server = event.editor.language_server_by_id(event.server_id).unwrap();
for doc in event
.editor
.documents()
.filter(|doc| doc.supports_language_server(event.server_id))
{
let Some(url) = doc.url() else {
continue;
};
let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
language_server.text_document_did_open(url, doc.version(), doc.text(), language_id);
}
Ok(())
});
register_hook!(move |event: &mut DocumentDidChange<'_>| {
// Send textDocument/didChange notifications.
if !event.ghost_transaction {
for language_server in event.doc.language_servers() {
language_server.text_document_did_change(
event.doc.versioned_identifier(),
event.old_text,
event.doc.text(),
event.changes,
);
}
}
Ok(())
});
register_hook!(move |event: &mut DocumentDidClose<'_>| {
// Send textDocument/didClose notifications.
for language_server in event.doc.language_servers() {
language_server.text_document_did_close(event.doc.identifier());
}
Ok(())
});
}