From d2725ac047b5465188cbaac1b0653dc7c5e85dd7 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sun, 13 Nov 2022 11:59:23 -0600 Subject: [PATCH] Apply transactions to all views Previously, transactions were only applied to the currently focused view. This could lead to jumplist selections not being updated or panics if a jumplist selection pointed past the end of the document. This change moves the application of transactions to the Editor which can apply the transaction to the document and all views, ensuring that jumplist entries are updated even if a document is open in multiple windows. This complicates most callers of the removed `helix_view::apply_transaction` helper function. Previously, callers could take mutable borrows of the view and doc at the beginning of a function and then pass them to the helper. Now they must take immutable borrows or refresh the mutable borrows after calling `Editor::apply_transaction` to avoid taking multiple mutable borrows of Editor. A new macro `current_ids` has been added to support a new common case where we only care about the currently focused document's and view's IDs. --- helix-term/src/commands.rs | 211 +++++++++++++++++-------------- helix-term/src/commands/lsp.rs | 7 +- helix-term/src/commands/typed.rs | 74 +++++------ helix-term/src/ui/completion.rs | 23 ++-- helix-term/src/ui/editor.rs | 9 +- helix-term/tests/test/helpers.rs | 12 +- helix-view/src/document.rs | 74 ++++++----- helix-view/src/editor.rs | 99 ++++++++++++++- helix-view/src/lib.rs | 13 -- helix-view/src/macros.rs | 12 +- helix-view/src/view.rs | 7 +- 11 files changed, 326 insertions(+), 215 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1b0c557e6eb4b..c98c911e5cbc7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,3 +1,8 @@ +// TODO: remove this when the MSRV is 1.62.0. +// This warning is benign and is removed in 1.62.0. +// See https://github.com/rust-lang/rust/pull/96268 +#![allow(mutable_borrow_reservation_conflict)] + pub(crate) mod dap; pub(crate) mod lsp; pub(crate) mod typed; @@ -27,7 +32,6 @@ use helix_core::{ SmallVec, Tendril, Transaction, }; use helix_view::{ - apply_transaction, clipboard::ClipboardType, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, editor::{Action, Motion}, @@ -698,7 +702,7 @@ fn extend_to_line_start(cx: &mut Context) { } fn kill_to_line_start(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id).clone().transform(|range| { @@ -722,13 +726,13 @@ fn kill_to_line_start(cx: &mut Context) { }; Range::new(head, anchor) }); - delete_selection_insert_mode(doc, view, &selection); + delete_selection_insert_mode(cx.editor, &selection); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } fn kill_to_line_end(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id).clone().transform(|range| { @@ -743,7 +747,7 @@ fn kill_to_line_end(cx: &mut Context) { } new_range }); - delete_selection_insert_mode(doc, view, &selection); + delete_selection_insert_mode(cx.editor, &selection); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } @@ -799,9 +803,10 @@ fn trim_selections(cx: &mut Context) { // align text in selection fn align_selections(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let (view_id, doc_id) = current_ids!(cx.editor); + let doc = doc!(cx.editor, &doc_id); let text = doc.text().slice(..); - let selection = doc.selection(view.id); + let selection = doc.selection(view_id); let tab_width = doc.tab_width(); let mut column_widths: Vec> = Vec::new(); @@ -859,7 +864,7 @@ fn align_selections(cx: &mut Context) { changes.sort_unstable_by_key(|(from, _, _)| *from); let transaction = Transaction::change(doc.text(), changes.into_iter()); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc_id, view_id); } fn goto_window(cx: &mut Context, align: Align) { @@ -1271,7 +1276,7 @@ fn replace(cx: &mut Context) { // need to wait for next key cx.on_next_key(move |cx, event| { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let ch: Option<&str> = match event { KeyEvent { code: KeyCode::Char(ch), @@ -1311,7 +1316,7 @@ fn replace(cx: &mut Context) { } }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); exit_select_mode(cx); } }) @@ -1321,7 +1326,7 @@ fn switch_case_impl(cx: &mut Context, change_fn: F) where F: Fn(RopeSlice) -> Tendril, { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { let text: Tendril = change_fn(range.slice(doc.text().slice(..))); @@ -1329,7 +1334,7 @@ where (range.from(), range.to(), Some(text)) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn switch_case(cx: &mut Context) { @@ -2140,7 +2145,7 @@ enum Operation { } fn delete_selection_impl(cx: &mut Context, op: Operation) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); @@ -2156,7 +2161,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { (range.from(), range.to(), None) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); match op { Operation::Delete => { @@ -2170,11 +2175,12 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { } #[inline] -fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) { +fn delete_selection_insert_mode(editor: &mut Editor, selection: &Selection) { + let (view, doc) = current_ref!(editor); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { (range.from(), range.to(), None) }); - apply_transaction(&transaction, doc, view); + editor.apply_transaction(&transaction, doc.id(), view.id); } fn delete_selection(cx: &mut Context) { @@ -2253,8 +2259,8 @@ fn insert_mode(cx: &mut Context) { // inserts at the end of each selection fn append_mode(cx: &mut Context) { enter_insert_mode(cx); - let (view, doc) = current!(cx.editor); - doc.restore_cursor = true; + doc_mut!(cx.editor).restore_cursor = true; + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); // Make sure there's room at the end of the document if the last @@ -2270,9 +2276,10 @@ fn append_mode(cx: &mut Context) { doc.text(), [(end, end, Some(doc.line_ending.as_str().into()))].into_iter(), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } + let (view, doc) = current!(cx.editor); let selection = doc.selection(view.id).clone().transform(|range| { Range::new( range.from(), @@ -2559,12 +2566,11 @@ async fn make_format_callback( } let scrolloff = editor.config().scrolloff; - let doc = doc_mut!(editor, &doc_id); - let view = view_mut!(editor, view_id); if let Ok(format) = format { - if doc.version() == doc_version { - apply_transaction(&format, doc, view); + if doc!(editor, &doc_id).version() == doc_version { + editor.apply_transaction(&format, doc_id, view_id); + let (view, doc) = current!(editor); doc.append_changes_to_history(view.id); doc.detect_indent_and_line_ending(); view.ensure_cursor_in_view(doc, scrolloff); @@ -2574,8 +2580,7 @@ async fn make_format_callback( } if let Some((path, force)) = write { - let id = doc.id(); - if let Err(err) = editor.save(id, path, force) { + if let Err(err) = editor.save(doc_id, path, force) { editor.set_error(format!("Error saving: {}", err)); } } @@ -2593,7 +2598,7 @@ pub enum Open { fn open(cx: &mut Context, open: Open) { let count = cx.count(); enter_insert_mode(cx); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let contents = doc.text(); @@ -2657,7 +2662,7 @@ fn open(cx: &mut Context, open: Open) { transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } // o inserts a new line after each line with a selection @@ -2676,9 +2681,10 @@ fn normal_mode(cx: &mut Context) { } cx.editor.mode = Mode::Normal; - let (view, doc) = current!(cx.editor); - try_restore_indent(doc, view); + try_restore_indent(cx.editor); + + let (view, doc) = current!(cx.editor); // if leaving append mode, move cursor back by 1 if doc.restore_cursor { @@ -2695,7 +2701,7 @@ fn normal_mode(cx: &mut Context) { } } -fn try_restore_indent(doc: &mut Document, view: &mut View) { +fn try_restore_indent(editor: &mut Editor) { use helix_core::chars::char_is_whitespace; use helix_core::Operation; @@ -2712,6 +2718,8 @@ fn try_restore_indent(doc: &mut Document, view: &mut View) { } } + let (view, doc) = current_ref!(editor); + let doc_changes = doc.changes().changes(); let text = doc.text().slice(..); let range = doc.selection(view.id).primary(); @@ -2725,7 +2733,7 @@ fn try_restore_indent(doc: &mut Document, view: &mut View) { let line_start_pos = text.line_to_char(range.cursor_line(text)); (line_start_pos, pos, None) }); - apply_transaction(&transaction, doc, view); + editor.apply_transaction(&transaction, doc.id(), view.id); } } @@ -3037,9 +3045,8 @@ pub mod insert { .and_then(|ap| auto_pairs::hook(text, selection, c, ap)) .or_else(|| insert(text, selection, c)); - let (view, doc) = current!(cx.editor); - if let Some(t) = transaction { - apply_transaction(&t, doc, view); + if let Some(tx) = transaction { + cx.editor.apply_transaction(&tx, doc.id(), view.id); } // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) @@ -3051,7 +3058,7 @@ pub mod insert { } pub fn insert_tab(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); // TODO: round out to nearest indentation level (for example a line with 3 spaces should // indent by one to reach 4 spaces). @@ -3061,7 +3068,7 @@ pub mod insert { &doc.selection(view.id).clone().cursors(doc.text().slice(..)), indent, ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } pub fn insert_newline(cx: &mut Context) { @@ -3147,8 +3154,7 @@ pub mod insert { transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); - let (view, doc) = current!(cx.editor); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } pub fn delete_char_backward(cx: &mut Context) { @@ -3242,15 +3248,15 @@ pub mod insert { } } }); - let (view, doc) = current!(cx.editor); - apply_transaction(&transaction, doc, view); + let (view, doc) = current_ref!(cx.editor); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } pub fn delete_char_forward(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let transaction = Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { @@ -3261,14 +3267,14 @@ pub mod insert { None, ) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } pub fn delete_word_backward(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id).clone().transform(|range| { @@ -3276,14 +3282,14 @@ pub mod insert { let next = Range::new(anchor, range.cursor(text)); exclude_cursor(text, next, range) }); - delete_selection_insert_mode(doc, view, &selection); + delete_selection_insert_mode(cx.editor, &selection); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } pub fn delete_word_forward(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id).clone().transform(|range| { @@ -3291,7 +3297,7 @@ pub mod insert { Range::new(range.cursor(text), head) }); - delete_selection_insert_mode(doc, view, &selection); + delete_selection_insert_mode(cx.editor, &selection); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); } @@ -3301,9 +3307,9 @@ pub mod insert { fn undo(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view_id, doc_id) = current_ids!(cx.editor); for _ in 0..count { - if !doc.undo(view) { + if !cx.editor.undo(doc_id, view_id) { cx.editor.set_status("Already at oldest change"); break; } @@ -3312,9 +3318,9 @@ fn undo(cx: &mut Context) { fn redo(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view_id, doc_id) = current_ids!(cx.editor); for _ in 0..count { - if !doc.redo(view) { + if !cx.editor.redo(doc_id, view_id) { cx.editor.set_status("Already at newest change"); break; } @@ -3323,10 +3329,10 @@ fn redo(cx: &mut Context) { fn earlier(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view_id, doc_id) = current_ids!(cx.editor); for _ in 0..count { // rather than doing in batch we do this so get error halfway - if !doc.earlier(view, UndoKind::Steps(1)) { + if !cx.editor.earlier(doc_id, view_id, UndoKind::Steps(1)) { cx.editor.set_status("Already at oldest change"); break; } @@ -3335,10 +3341,10 @@ fn earlier(cx: &mut Context) { fn later(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view_id, doc_id) = current_ids!(cx.editor); for _ in 0..count { // rather than doing in batch we do this so get error halfway - if !doc.later(view, UndoKind::Steps(1)) { + if !cx.editor.later(doc_id, view_id, UndoKind::Steps(1)) { cx.editor.set_status("Already at newest change"); break; } @@ -3467,9 +3473,15 @@ enum Paste { Cursor, } -fn paste_impl(values: &[String], doc: &mut Document, view: &mut View, action: Paste, count: usize) { +fn paste_impl( + values: &[String], + doc: &Document, + view: &View, + action: Paste, + count: usize, +) -> Transaction { if values.is_empty() { - return; + return Transaction::new(doc.text()); } let repeat = std::iter::repeat( @@ -3531,9 +3543,7 @@ fn paste_impl(values: &[String], doc: &mut Document, view: &mut View, action: Pa (pos, pos, value) }); - let transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); - - apply_transaction(&transaction, doc, view); + transaction.with_selection(Selection::new(ranges, selection.primary_index())) } pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { @@ -3542,8 +3552,9 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { Mode::Insert | Mode::Select => Paste::Cursor, Mode::Normal => Paste::Before, }; - let (view, doc) = current!(cx.editor); - paste_impl(&[contents], doc, view, paste, count); + let (view, doc) = current_ref!(cx.editor); + let transaction = paste_impl(&[contents], doc, view, paste, count); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn paste_clipboard_impl( @@ -3552,10 +3563,11 @@ fn paste_clipboard_impl( clipboard_type: ClipboardType, count: usize, ) -> anyhow::Result<()> { - let (view, doc) = current!(editor); match editor.clipboard_provider.get_contents(clipboard_type) { Ok(contents) => { - paste_impl(&[contents], doc, view, action, count); + let (view, doc) = current_ref!(editor); + let transaction = paste_impl(&[contents], doc, view, action, count); + editor.apply_transaction(&transaction, doc.id(), view.id); Ok(()) } Err(e) => Err(e.context("Couldn't get system clipboard contents")), @@ -3601,7 +3613,7 @@ fn paste_primary_clipboard_before(cx: &mut Context) { fn replace_with_yanked(cx: &mut Context) { let count = cx.count(); let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let registers = &mut cx.editor.registers; if let Some(values) = registers.read(reg_name) { @@ -3625,7 +3637,7 @@ fn replace_with_yanked(cx: &mut Context) { } }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); exit_select_mode(cx); } } @@ -3636,7 +3648,7 @@ fn replace_selections_with_clipboard_impl( clipboard_type: ClipboardType, ) -> anyhow::Result<()> { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); match cx.editor.clipboard_provider.get_contents(clipboard_type) { Ok(contents) => { @@ -3649,7 +3661,8 @@ fn replace_selections_with_clipboard_impl( ) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); } Err(e) => return Err(e.context("Couldn't get system clipboard contents")), @@ -3670,12 +3683,14 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) { fn paste(cx: &mut Context, pos: Paste) { let count = cx.count(); let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; + let (view, doc) = current_ref!(cx.editor); + let values = match cx.editor.registers.read(reg_name) { + Some(values) => values, + None => return, + }; - if let Some(values) = registers.read(reg_name) { - paste_impl(values, doc, view, pos, count); - } + let transaction = paste_impl(values, doc, view, pos, count); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn paste_after(cx: &mut Context) { @@ -3704,7 +3719,7 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec { fn indent(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let lines = get_lines(doc, view.id); // Indent by one level @@ -3721,12 +3736,12 @@ fn indent(cx: &mut Context) { Some((pos, pos, Some(indent.clone()))) }), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn unindent(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let lines = get_lines(doc, view.id); let mut changes = Vec::with_capacity(lines.len()); let tab_width = doc.tab_width(); @@ -3760,13 +3775,13 @@ fn unindent(cx: &mut Context) { let transaction = Transaction::change(doc.text(), changes.into_iter()); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn format_selections(cx: &mut Context) { use helix_lsp::{lsp, util::range_to_lsp_range}; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); // via lsp if available // TODO: else via tree-sitter indentation calculations @@ -3809,12 +3824,12 @@ fn format_selections(cx: &mut Context) { language_server.offset_encoding(), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn join_selections_impl(cx: &mut Context, select_space: bool) { use movement::skip_while; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text(); let slice = doc.text().slice(..); @@ -3863,7 +3878,7 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) { Transaction::change(doc.text(), changes.into_iter()) }; - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) { @@ -3996,14 +4011,14 @@ pub fn completion(cx: &mut Context) { // comments fn toggle_comments(cx: &mut Context) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let token = doc .language_config() .and_then(|lc| lc.comment_token.as_ref()) .map(|tc| tc.as_ref()); let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id), token); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); exit_select_mode(cx); } @@ -4028,7 +4043,7 @@ fn rotate_selections_backward(cx: &mut Context) { fn rotate_selection_contents(cx: &mut Context, direction: Direction) { let count = cx.count; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -4059,7 +4074,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) { .map(|(range, fragment)| (range.from(), range.to(), Some(fragment))), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } fn rotate_selection_contents_forward(cx: &mut Context) { @@ -4534,7 +4549,7 @@ fn surround_add(cx: &mut Context) { Some(ch) => ch, None => return, }; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); let (open, close) = surround::get_pair(ch); // The number of chars in get_pair @@ -4564,7 +4579,7 @@ fn surround_add(cx: &mut Context) { let transaction = Transaction::change(doc.text(), changes.into_iter()) .with_selection(Selection::new(ranges, selection.primary_index())); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); }) } @@ -4576,7 +4591,7 @@ fn surround_replace(cx: &mut Context) { Some(ch) => Some(ch), None => return, }; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -4589,7 +4604,7 @@ fn surround_replace(cx: &mut Context) { }; cx.on_next_key(move |cx, event| { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let to = match event.char() { Some(to) => to, None => return, @@ -4603,7 +4618,7 @@ fn surround_replace(cx: &mut Context) { (pos, pos + 1, Some(t)) }), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); }); }) } @@ -4616,7 +4631,7 @@ fn surround_delete(cx: &mut Context) { Some(ch) => Some(ch), None => return, }; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -4630,7 +4645,7 @@ fn surround_delete(cx: &mut Context) { let transaction = Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None))); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); }) } @@ -4788,7 +4803,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { let config = cx.editor.config(); let shell = &config.shell; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); let mut changes = Vec::with_capacity(selection.len()); @@ -4835,12 +4850,14 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { if behavior != &ShellBehavior::Ignore { let transaction = Transaction::change(doc.text(), changes.into_iter()) .with_selection(Selection::new(ranges, selection.primary_index())); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); } // after replace cursor may be out of bounds, do this to // make sure cursor is in view and update scroll as well + let (view, doc) = current!(cx.editor); view.ensure_cursor_in_view(doc, config.scrolloff); } @@ -4878,7 +4895,7 @@ fn add_newline_below(cx: &mut Context) { fn add_newline_impl(cx: &mut Context, open: Open) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); let text = doc.text(); let slice = text.slice(..); @@ -4898,7 +4915,7 @@ fn add_newline_impl(cx: &mut Context, open: Open) { }); let transaction = Transaction::change(text, changes); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } enum IncrementDirection { @@ -4959,7 +4976,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) { // If the register is `#` then increase or decrease the `amount` by 1 per element let increase_by = if cx.register == Some('#') { sign } else { 0 }; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let selection = doc.selection(view.id); let text = doc.text().slice(..); @@ -5012,7 +5029,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) { let transaction = Transaction::change(doc.text(), changes.into_iter()); let transaction = transaction.with_selection(selection.clone()); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); } } diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 33d33440cacd4..fd01dfcf0f85d 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -9,7 +9,7 @@ use tui::text::{Span, Spans}; use super::{align_view, push_jump, Align, Context, Editor, Open}; use helix_core::{path, Selection}; -use helix_view::{apply_transaction, document::Mode, editor::Action, theme::Style}; +use helix_view::{document::Mode, editor::Action, theme::Style}; use crate::{ compositor::{self, Compositor}, @@ -711,7 +711,7 @@ pub fn apply_workspace_edit( } }; - let doc = doc_mut!(editor, &doc_id); + let doc = doc!(editor, &doc_id); // Need to determine a view for apply/append_changes_to_history let selections = doc.selections(); @@ -732,7 +732,8 @@ pub fn apply_workspace_edit( text_edits, offset_encoding, ); - apply_transaction(&transaction, doc, view_mut!(editor, view_id)); + editor.apply_transaction(&transaction, doc_id, view_id); + let doc = doc_mut!(editor, &doc_id); doc.append_changes_to_history(view_id); }; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b8f99ff369d23..ec7678cea52fd 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -4,10 +4,7 @@ use crate::job::Job; use super::*; -use helix_view::{ - apply_transaction, - editor::{Action, CloseError, ConfigEvent}, -}; +use helix_view::editor::{Action, CloseError, ConfigEvent}; use ui::completers::{self, Completer}; #[derive(Clone)] @@ -445,8 +442,8 @@ fn set_line_ending( arg if arg.starts_with("nel") => Nel, _ => bail!("invalid line ending"), }; - let (view, doc) = current!(cx.editor); - doc.line_ending = line_ending; + doc_mut!(cx.editor).line_ending = line_ending; + let (view, doc) = current_ref!(cx.editor); let mut pos = 0; let transaction = Transaction::change( @@ -463,7 +460,8 @@ fn set_line_ending( } }), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); Ok(()) @@ -480,9 +478,8 @@ fn earlier( let uk = args.join(" ").parse::().map_err(|s| anyhow!(s))?; - let (view, doc) = current!(cx.editor); - let success = doc.earlier(view, uk); - if !success { + let (view_id, doc_id) = current_ids!(cx.editor); + if !cx.editor.earlier(doc_id, view_id, uk) { cx.editor.set_status("Already at oldest change"); } @@ -499,9 +496,9 @@ fn later( } let uk = args.join(" ").parse::().map_err(|s| anyhow!(s))?; - let (view, doc) = current!(cx.editor); - let success = doc.later(view, uk); - if !success { + + let (view_id, doc_id) = current_ids!(cx.editor); + if !cx.editor.later(doc_id, view_id, uk) { cx.editor.set_status("Already at newest change"); } @@ -899,7 +896,7 @@ fn replace_selections_with_clipboard_impl( cx: &mut compositor::Context, clipboard_type: ClipboardType, ) -> anyhow::Result<()> { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); match cx.editor.clipboard_provider.get_contents(clipboard_type) { Ok(contents) => { @@ -908,7 +905,8 @@ fn replace_selections_with_clipboard_impl( (range.from(), range.to(), Some(contents.as_str().into())) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); Ok(()) } @@ -1027,11 +1025,8 @@ fn reload( return Ok(()); } - let scrolloff = cx.editor.config().scrolloff; - let (view, doc) = current!(cx.editor); - doc.reload(view).map(|_| { - view.ensure_cursor_in_view(doc, scrolloff); - }) + let (view_id, doc_id) = current_ids!(cx.editor); + cx.editor.reload(doc_id, view_id) } fn reload_all( @@ -1062,11 +1057,10 @@ fn reload_all( .collect(); for (doc_id, view_ids) in docs_view_ids { - let doc = doc_mut!(cx.editor, &doc_id); - // Every doc is guaranteed to have at least 1 view at this point. - let view = view_mut!(cx.editor, view_ids[0]); - doc.reload(view)?; + cx.editor.reload(doc_id, view_ids[0])?; + + let doc = doc!(cx.editor, &doc_id); for view_id in view_ids { let view = view_mut!(cx.editor, view_id); @@ -1088,8 +1082,7 @@ fn update( return Ok(()); } - let (_view, doc) = current!(cx.editor); - if doc.is_modified() { + if doc!(cx.editor).is_modified() { write(cx, args, event) } else { Ok(()) @@ -1105,9 +1098,7 @@ fn lsp_workspace_command( return Ok(()); } - let (_, doc) = current!(cx.editor); - - let language_server = match doc.language_server() { + let language_server = match doc!(cx.editor).language_server() { Some(language_server) => language_server, None => { cx.editor @@ -1176,7 +1167,8 @@ fn lsp_restart( return Ok(()); } - let (_view, doc) = current!(cx.editor); + let doc = doc!(cx.editor); + let config = doc .language_config() .context("LSP not defined for the current document")?; @@ -1210,7 +1202,7 @@ fn tree_sitter_scopes( return Ok(()); } - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let pos = doc.selection(view.id).primary().cursor(text); @@ -1243,10 +1235,10 @@ fn vsplit( return Ok(()); } - let id = view!(cx.editor).doc; + let (_, doc_id) = current_ids!(cx.editor); if args.is_empty() { - cx.editor.switch(id, Action::VerticalSplit); + cx.editor.switch(doc_id, Action::VerticalSplit); } else { for arg in args { cx.editor @@ -1266,10 +1258,10 @@ fn hsplit( return Ok(()); } - let id = view!(cx.editor).doc; + let (_, doc_id) = current_ids!(cx.editor); if args.is_empty() { - cx.editor.switch(id, Action::HorizontalSplit); + cx.editor.switch(doc_id, Action::HorizontalSplit); } else { for arg in args { cx.editor @@ -1548,7 +1540,7 @@ fn sort_impl( _args: &[Cow], reverse: bool, ) -> anyhow::Result<()> { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -1571,7 +1563,8 @@ fn sort_impl( .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))), ); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); Ok(()) @@ -1587,7 +1580,7 @@ fn reflow( } let scrolloff = cx.editor.config().scrolloff; - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); const DEFAULT_MAX_LEN: usize = 79; @@ -1615,7 +1608,8 @@ fn reflow( (range.from(), range.to(), Some(reflowed_text)) }); - apply_transaction(&transaction, doc, view); + cx.editor.apply_transaction(&transaction, doc.id(), view.id); + let (view, doc) = current!(cx.editor); doc.append_changes_to_history(view.id); view.ensure_cursor_in_view(doc, scrolloff); @@ -1631,7 +1625,7 @@ fn tree_sitter_subtree( return Ok(()); } - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); if let Some(syntax) = doc.syntax() { let primary_selection = doc.selection(view.id).primary(); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 4e6ee4246775e..d1d8aa58a3e37 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,5 +1,5 @@ use crate::compositor::{Component, Context, Event, EventResult}; -use helix_view::{apply_transaction, editor::CompleteAction}; +use helix_view::editor::CompleteAction; use tui::buffer::Buffer as Surface; use tui::text::Spans; @@ -148,14 +148,14 @@ impl Completion { .collect() } - let (view, doc) = current!(editor); + let (view_id, doc_id) = current_ids!(editor); // if more text was entered, remove it - doc.restore(view); + editor.restore(doc_id, view_id); match event { PromptEvent::Abort => { - doc.restore(view); + editor.restore(doc_id, view_id); editor.last_completion = None; } PromptEvent::Update => { @@ -163,7 +163,7 @@ impl Completion { let item = item.unwrap(); let transaction = item_to_transaction( - doc, + doc!(editor, &doc_id), item, offset_encoding, start_offset, @@ -171,8 +171,8 @@ impl Completion { ); // initialize a savepoint - doc.savepoint(); - apply_transaction(&transaction, doc, view); + doc_mut!(editor, &doc_id).savepoint(); + editor.apply_transaction(&transaction, doc_id, view_id); editor.last_completion = Some(CompleteAction { trigger_offset, @@ -184,14 +184,14 @@ impl Completion { let item = item.unwrap(); let transaction = item_to_transaction( - doc, + doc!(editor, &doc_id), item, offset_encoding, start_offset, trigger_offset, ); - apply_transaction(&transaction, doc, view); + editor.apply_transaction(&transaction, doc_id, view_id); editor.last_completion = Some(CompleteAction { trigger_offset, @@ -207,7 +207,7 @@ impl Completion { { None } else { - Self::resolve_completion_item(doc, item.clone()) + Self::resolve_completion_item(doc!(editor, &doc_id), item.clone()) }; if let Some(additional_edits) = resolved_item @@ -216,12 +216,13 @@ impl Completion { .or(item.additional_text_edits.as_ref()) { if !additional_edits.is_empty() { + let doc = doc!(editor); let transaction = util::generate_transaction_from_edits( doc.text(), additional_edits.clone(), offset_encoding, // TODO: should probably transcode in Client ); - apply_transaction(&transaction, doc, view); + editor.apply_transaction(&transaction, doc_id, view_id); } } } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 6c8ee2d95e9c2..622329b51c35a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,6 @@ use helix_core::{ visual_coords_at_pos, LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ - apply_transaction, document::{Mode, SCRATCH_BUFFER_NAME}, editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, @@ -1010,10 +1009,10 @@ impl EditorView { match key { InsertEvent::Key(key) => self.insert_mode(cxt, key), InsertEvent::CompletionApply(compl) => { - let (view, doc) = current!(cxt.editor); - - doc.restore(view); + let (view_id, doc_id) = current_ids!(cxt.editor); + cxt.editor.restore(doc_id, view_id); + let (view, doc) = current_ref!(cxt.editor); let text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(text); @@ -1026,7 +1025,7 @@ impl EditorView { (shift_position(start), shift_position(end), t) }), ); - apply_transaction(&tx, doc, view); + cxt.editor.apply_transaction(&tx, doc_id, view_id); } InsertEvent::TriggerCompletion => { let (_, doc) = current!(cxt.editor); diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 2c5043d68cf23..ef94ea39c8731 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -1,3 +1,6 @@ +// TODO: remove this when the MSRV is 1.62.0 +#![allow(mutable_borrow_reservation_conflict)] + use std::{ fs::File, io::{Read, Write}, @@ -121,7 +124,7 @@ pub async fn test_key_sequence_with_input_text>( None => Application::new(Args::default(), Config::default(), test_syntax_conf(None))?, }; - let (view, doc) = helix_view::current!(app.editor); + let (view, doc) = helix_view::current_ref!(app.editor); let sel = doc.selection(view.id).clone(); // replace the initial text with the input text @@ -130,7 +133,8 @@ pub async fn test_key_sequence_with_input_text>( }) .with_selection(test_case.in_selection.clone()); - helix_view::apply_transaction(&transaction, doc, view); + app.editor + .apply_transaction(&transaction, doc.id(), view.id); test_key_sequence( &mut app, @@ -307,7 +311,7 @@ impl AppBuilder { let mut app = Application::new(self.args, self.config, self.syn_conf)?; if let Some((text, selection)) = self.input { - let (view, doc) = helix_view::current!(app.editor); + let (view, doc) = helix_view::current_ref!(app.editor); let sel = doc.selection(view.id).clone(); let trans = Transaction::change_by_selection(doc.text(), &sel, |_| { (0, doc.text().len_chars(), Some((text.clone()).into())) @@ -315,7 +319,7 @@ impl AppBuilder { .with_selection(selection); // replace the initial text with the input text - helix_view::apply_transaction(&trans, doc, view); + app.editor.apply_transaction(&trans, doc.id(), view.id); } Ok(app) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 0870852833918..f1b555e4c153d 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -24,7 +24,7 @@ use helix_core::{ DEFAULT_LINE_ENDING, }; -use crate::{apply_transaction, DocumentId, Editor, View, ViewId}; +use crate::{DocumentId, Editor, ViewId}; /// 8kB of buffer space for encoding and decoding `Rope`s. const BUF_SIZE: usize = 8192; @@ -624,7 +624,7 @@ impl Document { } /// Reload the document from its path. - pub fn reload(&mut self, view: &mut View) -> Result<(), Error> { + pub fn reload(&self) -> Result { let encoding = &self.encoding; let path = self.path().filter(|path| path.exists()); @@ -639,14 +639,7 @@ impl Document { // Calculate the difference between the buffer and source text, and apply it. // This is not considered a modification of the contents of the file regardless // of the encoding. - let transaction = helix_core::diff::compare_ropes(self.text(), &rope); - apply_transaction(&transaction, self, view); - self.append_changes_to_history(view.id); - self.reset_modified(); - - self.detect_indent_and_line_ending(); - - Ok(()) + Ok(helix_core::diff::compare_ropes(self.text(), &rope)) } /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`. @@ -833,8 +826,8 @@ impl Document { } /// Apply a [`Transaction`] to the [`Document`] to change its text. - /// Instead of calling this function directly, use [crate::apply_transaction] - /// to ensure that the transaction is applied to the appropriate [`View`] as + /// Instead of calling this function directly, use [Editor::apply_transaction] + /// to ensure that the transaction is applied to the appropriate [`crate::view::View`] as /// well. pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool { // store the state just before any changes are made. This allows us to undo to the @@ -857,11 +850,11 @@ impl Document { success } - fn undo_redo_impl(&mut self, view: &mut View, undo: bool) -> bool { + fn undo_redo_impl(&mut self, view_id: ViewId, undo: bool) -> Option { let mut history = self.history.take(); - let txn = if undo { history.undo() } else { history.redo() }; - let success = if let Some(txn) = txn { - self.apply_impl(txn, view.id) && view.apply(txn, self) + let transaction = if undo { history.undo() } else { history.redo() }.cloned(); + let success = if let Some(ref txn) = transaction { + self.apply_impl(txn, view_id) } else { false }; @@ -870,57 +863,68 @@ impl Document { if success { // reset changeset to fix len self.changes = ChangeSet::new(self.text()); + transaction + } else { + None } - success } - /// Undo the last modification to the [`Document`]. Returns whether the undo was successful. - pub fn undo(&mut self, view: &mut View) -> bool { - self.undo_redo_impl(view, true) + /// Undo the last modification to the [`Document`]. + /// Use [Editor::undo] instead of calling this method directly. + pub fn undo(&mut self, view_id: ViewId) -> Option { + self.undo_redo_impl(view_id, true) } - /// Redo the last modification to the [`Document`]. Returns whether the redo was successful. - pub fn redo(&mut self, view: &mut View) -> bool { - self.undo_redo_impl(view, false) + /// Redo the last modification to the [`Document`]. + /// Use [Editor::redo] instead of calling this method directly. + pub fn redo(&mut self, view_id: ViewId) -> Option { + self.undo_redo_impl(view_id, false) } pub fn savepoint(&mut self) { self.savepoint = Some(Transaction::new(self.text())); } - pub fn restore(&mut self, view: &mut View) { - if let Some(revert) = self.savepoint.take() { - apply_transaction(&revert, self, view); - } + pub fn restore(&mut self) -> Option { + self.savepoint.take() } - fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool { + fn earlier_later_impl( + &mut self, + view_id: ViewId, + uk: UndoKind, + earlier: bool, + ) -> Vec { let txns = if earlier { self.history.get_mut().earlier(uk) } else { self.history.get_mut().later(uk) }; let mut success = false; - for txn in txns { - if self.apply_impl(&txn, view.id) && view.apply(&txn, self) { + for txn in txns.clone() { + if self.apply_impl(&txn, view_id) { success = true; } } if success { // reset changeset to fix len self.changes = ChangeSet::new(self.text()); + txns + } else { + Vec::new() } - success } /// Undo modifications to the [`Document`] according to `uk`. - pub fn earlier(&mut self, view: &mut View, uk: UndoKind) -> bool { - self.earlier_later_impl(view, uk, true) + /// Use [Editor::earlier] instead of calling this method directly. + pub fn earlier(&mut self, view_id: ViewId, uk: UndoKind) -> Vec { + self.earlier_later_impl(view_id, uk, true) } /// Redo modifications to the [`Document`] according to `uk`. - pub fn later(&mut self, view: &mut View, uk: UndoKind) -> bool { - self.earlier_later_impl(view, uk, false) + /// Use [Editor::later] instead of calling this method directly. + pub fn later(&mut self, view_id: ViewId, uk: UndoKind) -> Vec { + self.earlier_later_impl(view_id, uk, false) } /// Commit pending changes to history diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 23e0a4976a534..cb0da7e59ccc1 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -34,11 +34,11 @@ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; -use helix_core::Position; use helix_core::{ auto_pairs::AutoPairs, + history::UndoKind, syntax::{self, AutoPairConfig}, - Change, + Change, Position, }; use helix_dap as dap; use helix_lsp::lsp; @@ -1225,6 +1225,101 @@ impl Editor { Ok(()) } + pub fn reload(&mut self, doc_id: DocumentId, view_id: ViewId) -> anyhow::Result<()> { + let scrolloff = self.config().scrolloff; + let doc = doc_mut!(self, &doc_id); + let transaction = doc.reload()?; + self.apply_transaction(&transaction, doc_id, view_id); + let doc = doc_mut!(self, &doc_id); + doc.append_changes_to_history(view_id); + doc.reset_modified(); + doc.detect_indent_and_line_ending(); + view_mut!(self, view_id).ensure_cursor_in_view(doc, scrolloff); + Ok(()) + } + + pub fn restore(&mut self, doc_id: DocumentId, view_id: ViewId) { + let doc = doc_mut!(self, &doc_id); + if let Some(transaction) = doc.restore() { + self.apply_transaction(&transaction, doc_id, view_id); + } + } + + pub fn undo(&mut self, doc_id: DocumentId, view_id: ViewId) -> bool { + let doc = doc_mut!(self, &doc_id); + let transaction = match doc.undo(view_id) { + Some(transaction) => transaction, + None => return false, + }; + self.apply_transaction_to_views(&transaction, doc_id); + true + } + + pub fn redo(&mut self, doc_id: DocumentId, view_id: ViewId) -> bool { + let doc = doc_mut!(self, &doc_id); + let transaction = match doc.redo(view_id) { + Some(transaction) => transaction, + None => return false, + }; + self.apply_transaction_to_views(&transaction, doc_id); + true + } + + pub fn earlier(&mut self, doc_id: DocumentId, view_id: ViewId, kind: UndoKind) -> bool { + let doc = doc_mut!(self, &doc_id); + let transactions = doc.earlier(view_id, kind); + if transactions.is_empty() { + return false; + } + for transaction in transactions { + self.apply_transaction_to_views(&transaction, doc_id); + } + true + } + + pub fn later(&mut self, doc_id: DocumentId, view_id: ViewId, kind: UndoKind) -> bool { + let doc = doc_mut!(self, &doc_id); + let transactions = doc.later(view_id, kind); + if transactions.is_empty() { + return false; + } + for transaction in transactions { + self.apply_transaction_to_views(&transaction, doc_id); + } + true + } + + /// Applies a given [helix_core::Transaction] to the given Document and the focused + /// ViewId. + /// + /// The transaction is also applied to all views which contain the open document so + /// that all jumplist selections are updated by the transaction. + pub fn apply_transaction( + &mut self, + transaction: &helix_core::Transaction, + doc_id: DocumentId, + view_id: ViewId, + ) -> bool { + let doc = doc_mut!(self, &doc_id); + if doc.apply(transaction, view_id) { + self.apply_transaction_to_views(transaction, doc_id); + true + } else { + false + } + } + + fn apply_transaction_to_views( + &mut self, + transaction: &helix_core::Transaction, + doc_id: DocumentId, + ) { + let doc = doc!(self, &doc_id); + for (view, _focused) in self.tree.views_mut() { + view.apply(transaction, doc) + } + } + pub fn resize(&mut self, area: Rect) { if self.tree.resize(area) { self._refresh(); diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 4c32b356b9eaa..9a98044639531 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -66,19 +66,6 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) { view.offset.row = line.saturating_sub(relative); } -/// Applies a [`helix_core::Transaction`] to the given [`Document`] -/// and [`View`]. -pub fn apply_transaction( - transaction: &helix_core::Transaction, - doc: &mut Document, - view: &mut 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) -} - pub use document::Document; pub use editor::Editor; pub use theme::Theme; diff --git a/helix-view/src/macros.rs b/helix-view/src/macros.rs index 53ab434622d44..ee4969b882fab 100644 --- a/helix-view/src/macros.rs +++ b/helix-view/src/macros.rs @@ -28,6 +28,16 @@ macro_rules! current_ref { }}; } +/// Get the IDs of the current View and Document immutably. +/// Returns `(ViewId, DocumentId)` +#[macro_export] +macro_rules! current_ids { + ($editor:expr) => {{ + let view = $crate::view!($editor); + (view.id, view.doc) + }}; +} + /// Get the current document mutably. /// Returns `&mut Document` #[macro_export] @@ -67,7 +77,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 diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 6da4df1f0f031..33e5d6046c038 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -341,11 +341,10 @@ impl View { // } /// Applies a [`Transaction`] to the view. - /// Instead of calling this function directly, use [crate::apply_transaction] - /// which applies a transaction to the [`Document`] and view together. - pub fn apply(&mut self, transaction: &Transaction, doc: &Document) -> bool { + /// Instead of calling this function directly, use [crate::Editor::apply_transaction] + /// which applies a transaction to the [`Document`] and views together. + pub fn apply(&mut self, transaction: &Transaction, doc: &Document) { self.jumps.apply(transaction, doc); - true } }