refactor DocumentDidChange events

in the past DocumentDidChange and SelectionDidChange events were implemented in
a simplistic manner to get a simple prototype out. However, if you want to use
these events in more complex scenarios with interdependencies between the two
handlers the system fell short.

The `SelectionDidChange` event was dispatched before the DocumentDidChange (and
not at all if the selection wasn't manually set) so any handlers that wants to
track selection was not able to map their ranges yet.

The reason for this was actually the way that apply_impl was structured. The
function was slightly refactored to address these problems and enable moving
other range mappings to event handlers.
pull/9801/head
Pascal Kuthe 4 months ago
parent 26a190db62
commit 0d7857bfc4
No known key found for this signature in database
GPG Key ID: D715E8655AE166A6

@ -341,7 +341,7 @@ pub(super) fn register_hooks(handlers: &Handlers) {
let tx = handlers.signature_hints.clone(); let tx = handlers.signature_hints.clone();
register_hook!(move |event: &mut DocumentDidChange<'_>| { register_hook!(move |event: &mut DocumentDidChange<'_>| {
if event.doc.config.load().lsp.auto_signature_help { if event.doc.config.load().lsp.auto_signature_help && !event.ghost_transaction {
send_blocking(&tx, SignatureHelpEvent::ReTrigger); send_blocking(&tx, SignatureHelpEvent::ReTrigger);
} }
Ok(()) Ok(())

@ -1189,28 +1189,12 @@ impl Document {
use helix_core::Assoc; use helix_core::Assoc;
let old_doc = self.text().clone(); let old_doc = self.text().clone();
let changes = transaction.changes();
if !changes.apply(&mut self.text) {
return false;
}
let success = transaction.changes().apply(&mut self.text); if changes.is_empty() {
if success {
if emit_lsp_notification {
helix_event::dispatch(DocumentDidChange {
doc: self,
view: view_id,
old_text: &old_doc,
});
}
for selection in self.selections.values_mut() {
*selection = selection
.clone()
// Map through changes
.map(transaction.changes())
// Ensure all selections across all views still adhere to invariants.
.ensure_invariants(self.text.slice(..));
}
// if specified, the current selection should instead be replaced by transaction.selection
if let Some(selection) = transaction.selection() { if let Some(selection) = transaction.selection() {
self.selections.insert( self.selections.insert(
view_id, view_id,
@ -1221,129 +1205,155 @@ impl Document {
view: view_id, view: view_id,
}); });
} }
return true;
}
self.modified_since_accessed = true; self.modified_since_accessed = true;
self.version += 1;
for selection in self.selections.values_mut() {
*selection = selection
.clone()
// Map through changes
.map(transaction.changes())
// Ensure all selections across all views still adhere to invariants.
.ensure_invariants(self.text.slice(..));
} }
if !transaction.changes().is_empty() { // generate revert to savepoint
self.version += 1; if !self.savepoints.is_empty() {
// start computing the diff in parallel let revert = transaction.invert(&old_doc);
if let Some(diff_handle) = &self.diff_handle { self.savepoints
diff_handle.update_document(self.text.clone(), false); .retain_mut(|save_point| match save_point.upgrade() {
} Some(savepoint) => {
let mut revert_to_savepoint = savepoint.revert.lock();
*revert_to_savepoint =
revert.clone().compose(mem::take(&mut revert_to_savepoint));
true
}
None => false,
})
}
// generate revert to savepoint // update tree-sitter syntax tree
if !self.savepoints.is_empty() { if let Some(syntax) = &mut self.syntax {
let revert = transaction.invert(&old_doc); // TODO: no unwrap
self.savepoints let res = syntax.update(
.retain_mut(|save_point| match save_point.upgrade() { old_doc.slice(..),
Some(savepoint) => { self.text.slice(..),
let mut revert_to_savepoint = savepoint.revert.lock(); transaction.changes(),
*revert_to_savepoint = );
revert.clone().compose(mem::take(&mut revert_to_savepoint)); if res.is_err() {
true log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
} self.syntax = None;
None => false,
})
} }
}
// update tree-sitter syntax tree // TODO: all of that should likely just be hooks
if let Some(syntax) = &mut self.syntax { // start computing the diff in parallel
// TODO: no unwrap if let Some(diff_handle) = &self.diff_handle {
let res = syntax.update( diff_handle.update_document(self.text.clone(), false);
old_doc.slice(..), }
self.text.slice(..),
transaction.changes(),
);
if res.is_err() {
log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
self.syntax = None;
}
}
let changes = transaction.changes(); // map diagnostics over changes too
changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
let assoc = if diagnostic.starts_at_word {
Assoc::BeforeWord
} else {
Assoc::After
};
(&mut diagnostic.range.start, assoc)
}));
changes.update_positions(self.diagnostics.iter_mut().filter_map(|diagnostic| {
if diagnostic.zero_width {
// for zero width diagnostics treat the diagnostic as a point
// rather than a range
return None;
}
let assoc = if diagnostic.ends_at_word {
Assoc::AfterWord
} else {
Assoc::Before
};
Some((&mut diagnostic.range.end, assoc))
}));
self.diagnostics.retain_mut(|diagnostic| {
if diagnostic.zero_width {
diagnostic.range.end = diagnostic.range.start
} else if diagnostic.range.start >= diagnostic.range.end {
return false;
}
diagnostic.line = self.text.char_to_line(diagnostic.range.start);
true
});
// map diagnostics over changes too self.diagnostics.sort_unstable_by_key(|diagnostic| {
changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| { (diagnostic.range, diagnostic.severity, diagnostic.provider)
let assoc = if diagnostic.starts_at_word { });
Assoc::BeforeWord
} else {
Assoc::After
};
(&mut diagnostic.range.start, assoc)
}));
changes.update_positions(self.diagnostics.iter_mut().filter_map(|diagnostic| {
if diagnostic.zero_width {
// for zero width diagnostics treat the diagnostic as a point
// rather than a range
return None;
}
let assoc = if diagnostic.ends_at_word {
Assoc::AfterWord
} else {
Assoc::Before
};
Some((&mut diagnostic.range.end, assoc))
}));
self.diagnostics.retain_mut(|diagnostic| {
if diagnostic.zero_width {
diagnostic.range.end = diagnostic.range.start
} else if diagnostic.range.start >= diagnostic.range.end {
return false;
}
diagnostic.line = self.text.char_to_line(diagnostic.range.start);
true
});
self.diagnostics.sort_unstable_by_key(|diagnostic| { // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
(diagnostic.range, diagnostic.severity, diagnostic.provider) let apply_inlay_hint_changes = |annotations: &mut Vec<InlineAnnotation>| {
}); changes.update_positions(
annotations
.iter_mut()
.map(|annotation| (&mut annotation.char_idx, Assoc::After)),
);
};
// Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place self.inlay_hints_oudated = true;
let apply_inlay_hint_changes = |annotations: &mut Vec<InlineAnnotation>| { for text_annotation in self.inlay_hints.values_mut() {
changes.update_positions( let DocumentInlayHints {
annotations id: _,
.iter_mut() type_inlay_hints,
.map(|annotation| (&mut annotation.char_idx, Assoc::After)), parameter_inlay_hints,
); other_inlay_hints,
}; padding_before_inlay_hints,
padding_after_inlay_hints,
} = text_annotation;
apply_inlay_hint_changes(padding_before_inlay_hints);
apply_inlay_hint_changes(type_inlay_hints);
apply_inlay_hint_changes(parameter_inlay_hints);
apply_inlay_hint_changes(other_inlay_hints);
apply_inlay_hint_changes(padding_after_inlay_hints);
}
self.inlay_hints_oudated = true; helix_event::dispatch(DocumentDidChange {
for text_annotation in self.inlay_hints.values_mut() { doc: self,
let DocumentInlayHints { view: view_id,
id: _, old_text: &old_doc,
type_inlay_hints, changes,
parameter_inlay_hints, ghost_transaction: !emit_lsp_notification,
other_inlay_hints, });
padding_before_inlay_hints,
padding_after_inlay_hints,
} = text_annotation;
apply_inlay_hint_changes(padding_before_inlay_hints);
apply_inlay_hint_changes(type_inlay_hints);
apply_inlay_hint_changes(parameter_inlay_hints);
apply_inlay_hint_changes(other_inlay_hints);
apply_inlay_hint_changes(padding_after_inlay_hints);
}
if emit_lsp_notification { if emit_lsp_notification {
// TODO: move to hook // TODO: move to hook
// emit lsp notification // emit lsp notification
for language_server in self.language_servers() { for language_server in self.language_servers() {
let notify = language_server.text_document_did_change( let notify = language_server.text_document_did_change(
self.versioned_identifier(), self.versioned_identifier(),
&old_doc, &old_doc,
self.text(), self.text(),
changes, changes,
); );
if let Some(notify) = notify { if let Some(notify) = notify {
tokio::spawn(notify); tokio::spawn(notify);
}
} }
} }
} }
success
if let Some(selection) = transaction.selection() {
self.selections.insert(
view_id,
selection.clone().ensure_invariants(self.text.slice(..)),
);
helix_event::dispatch(SelectionDidChange {
doc: self,
view: view_id,
});
}
true
} }
fn apply_inner( fn apply_inner(

@ -1,9 +1,15 @@
use helix_core::Rope; use helix_core::{ChangeSet, Rope};
use helix_event::events; use helix_event::events;
use crate::{Document, ViewId}; use crate::{Document, ViewId};
events! { events! {
DocumentDidChange<'a> { doc: &'a mut Document, view: ViewId, old_text: &'a Rope } DocumentDidChange<'a> {
doc: &'a mut Document,
view: ViewId,
old_text: &'a Rope,
changes: &'a ChangeSet,
ghost_transaction: bool
}
SelectionDidChange<'a> { doc: &'a mut Document, view: ViewId } SelectionDidChange<'a> { doc: &'a mut Document, view: ViewId }
} }

Loading…
Cancel
Save