Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply transactions to all views #4733

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions helix-core/src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ impl History {
self.current == 0
}

/// Returns the changes since the given revision composed into a transaction.
/// Returns None if there are no changes between the current and given revisions.
pub fn changes_since(&self, revision: usize) -> Option<Transaction> {
if self.at_root() || self.current >= revision {
return None;
}

let mut transaction = self.revisions[revision].transaction.clone();

// The bounds are checked in the if condition above:
// `revision + 1` is known to be `<= self.current`.
for revision in &self.revisions[revision + 1..self.current] {
transaction = transaction.compose(revision.transaction.clone());
}

Some(transaction)
the-mikedavis marked this conversation as resolved.
Show resolved Hide resolved
}

/// Undo the last edit.
pub fn undo(&mut self) -> Option<&Transaction> {
if self.at_root() {
Expand Down
26 changes: 23 additions & 3 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,9 @@ impl Component for EditorView {
cx.editor.status_msg = None;

let mode = cx.editor.mode();
let (view, _) = current!(cx.editor);
let (view, doc) = current!(cx.editor);
let original_doc_id = doc.id();
let original_doc_revision = doc.get_current_revision();
let focus = view.id;

if let Some(on_next_key) = self.on_next_key.take() {
Expand Down Expand Up @@ -1413,13 +1415,31 @@ impl Component for EditorView {
let view = view_mut!(cx.editor, focus);
let doc = doc_mut!(cx.editor, &view.doc);

view.ensure_cursor_in_view(doc, config.scrolloff);

// Store a history state if not in insert mode. This also takes care of
// committing changes when leaving insert mode.
if mode != Mode::Insert {
doc.append_changes_to_history(view.id);
}

// If the current document has been changed, apply the changes to all views.
// This ensures that selections in jumplists follow changes.
Comment on lines +1424 to +1425
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: mouse clicks can also change view focus and are handled separately further below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, this will apply as soon as changes are added to history, I thought it was when focus is changed 👍🏻

if doc.id() == original_doc_id
&& doc.get_current_revision() > original_doc_revision
{
if let Some(transaction) =
doc.history.get_mut().changes_since(original_doc_revision)
{
let doc = doc!(cx.editor, &original_doc_id);
for (view, _focused) in cx.editor.tree.views_mut() {
view.apply(&transaction, doc);
}
}
}

let view = view_mut!(cx.editor, focus);
let doc = doc_mut!(cx.editor, &view.doc);

view.ensure_cursor_in_view(doc, config.scrolloff);
}

EventResult::Consumed(callback)
Expand Down
26 changes: 26 additions & 0 deletions helix-term/tests/test/splits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,29 @@ async fn test_split_write_quit_same_file() -> anyhow::Result<()> {

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_changes_in_splits_apply_to_all_views() -> anyhow::Result<()> {
// See <https://github.com/helix-editor/helix/issues/4732>.
// Transactions must be applied to any view that has the changed document open.
// This sequence would panic since the jumplist entry would be modified in one
// window but not the other. Attempting to update the changelist in the other
// window would cause a panic since it would point outside of the document.

// The key sequence here:
// * <C-w>v Create a vertical split of the current buffer.
// Both views look at the same doc.
// * [<space> Add a line ending to the beginning of the document.
// The cursor is now at line 2 in window 2.
// * <C-s> Save that selection to the jumplist in window 2.
// * <C-w>w Switch to window 1.
// * kd Delete line 1 in window 1.
// * <C-w>q Close window 1, focusing window 2.
// * d Delete line 1 in window 2.
//
// This panicked in the past because the jumplist entry on line 2 of window 2
// was not updated and after the `kd` step, pointed outside of the document.
test(("#[|]#", "<C-w>v[<space><C-s><C-w>wkd<C-w>qd", "#[|]#")).await?;
Comment on lines +133 to +152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️


Ok(())
}
8 changes: 3 additions & 5 deletions helix-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) {
pub fn apply_transaction(
transaction: &helix_core::Transaction,
doc: &mut Document,
view: &mut View,
view: &View,
) -> bool {
// This is a short function but it's easy to call `Document::apply`
// without calling `View::apply` or in the wrong order. The transaction
// must be applied to the document before the view.
doc.apply(transaction, view.id) && view.apply(transaction, doc)
// TODO remove this helper function. Just call Document::apply everywhere directly.
doc.apply(transaction, view.id)
}

pub use document::Document;
Expand Down
2 changes: 1 addition & 1 deletion helix-view/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ macro_rules! view {
#[macro_export]
macro_rules! doc {
($editor:expr, $id:expr) => {{
$editor.documents[$id]
&$editor.documents[$id]
}};
($editor:expr) => {{
$crate::current_ref!($editor).1
Expand Down
1 change: 1 addition & 0 deletions helix-view/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ impl View {
/// which applies a transaction to the [`Document`] and view together.
pub fn apply(&mut self, transaction: &Transaction, doc: &Document) -> bool {
self.jumps.apply(transaction, doc);
// TODO: remove the boolean return. This is unused.
true
}
}
Expand Down