diff --git a/src/documents/document/edit.rs b/src/documents/document/edit.rs index 0b3ba85..3dcd3f0 100644 --- a/src/documents/document/edit.rs +++ b/src/documents/document/edit.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use gpui::{AppContext as _, Context, Focusable as _, Window}; +use gpui::{App, AppContext as _, Context, Focusable as _, Window}; use super::{DocumentEvent, MarkdownDocument, Open}; use crate::{ @@ -31,6 +31,24 @@ impl MarkdownDocument { } } + /// The open block still holds what it opened with. + pub(super) fn untouched(&self, cx: &App) -> bool { + self.open.as_ref().is_some_and(|open| { + open.input.read(cx).text() == &self.editor.read(cx).text()[open.range.clone()] + }) + } + + /// Undo or redo in the document's one history. + pub(super) fn history(&mut self, redo: bool, window: &mut Window, cx: &mut Context) { + self.commit(cx); + self.editor.update(cx, |editor, cx| match redo { + true => editor.redo(cx), + false => editor.undo(cx), + }); + window.focus(&self.focus, cx); + self.focused(window, cx); + } + /// Opens block `ix` as shown, or one past the end. pub(super) fn open( &mut self, diff --git a/src/documents/document/mod.rs b/src/documents/document/mod.rs index f83ad16..6f4b0af 100644 --- a/src/documents/document/mod.rs +++ b/src/documents/document/mod.rs @@ -170,6 +170,8 @@ impl MarkdownDocument { } let focus = self.focus_handle(cx); window.focus(&focus, cx); + // Preview to visual keeps one handle: no focus event. + self.focused(window, cx); cx.notify(); } diff --git a/src/documents/document/tests/mod.rs b/src/documents/document/tests/mod.rs index ff36656..bcbf851 100644 --- a/src/documents/document/tests/mod.rs +++ b/src/documents/document/tests/mod.rs @@ -425,3 +425,50 @@ fn focus_opens_no_block_in_a_written_document(cx: &mut TestAppContext) { cx.simulate_input("x"); assert_eq!(text(&document, cx), TEXT); } + +fn mode(document: &Entity, mode: MarkdownMode, cx: &mut VisualTestContext) { + document.update_in(cx, |document, window, cx| { + document.set_mode(mode, window, cx) + }); + cx.run_until_parked(); +} + +#[gpui::test] +fn preview_and_back_still_take_typing_in_an_empty_document(cx: &mut TestAppContext) { + let (document, cx) = open("", MarkdownMode::Visual, cx); + focus(&document, cx); + cx.simulate_keystrokes("escape"); + mode(&document, MarkdownMode::Preview, cx); + mode(&document, MarkdownMode::Visual, cx); + cx.simulate_input("Hi"); + assert_eq!(text(&document, cx), "Hi"); +} + +#[gpui::test] +fn undo_in_an_untouched_block_steps_the_document(cx: &mut TestAppContext) { + let (document, cx) = open("Seed", MarkdownMode::Source, cx); + document.update(cx, |document, cx| document.set_text("", cx)); + mode(&document, MarkdownMode::Visual, cx); + cx.simulate_keystrokes("secondary-z"); + cx.run_until_parked(); + assert_eq!(text(&document, cx), "Seed", "the source edit is undone"); + cx.simulate_keystrokes("secondary-shift-z"); + cx.run_until_parked(); + assert_eq!(text(&document, cx), "", "and redone"); +} + +#[gpui::test] +fn undo_in_a_typed_block_stays_in_the_block(cx: &mut TestAppContext) { + let (document, cx) = open("Seed", MarkdownMode::Source, cx); + document.update(cx, |document, cx| document.set_text(TEXT, cx)); + mode(&document, MarkdownMode::Visual, cx); + press(1, Modifiers::none(), cx); + cx.simulate_input(" x"); + cx.simulate_keystrokes("secondary-z"); + cx.run_until_parked(); + assert_eq!( + text(&document, cx), + TEXT, + "the typing went, not the edit before" + ); +} diff --git a/src/documents/document/visual.rs b/src/documents/document/visual.rs index b3a796e..00c4b0e 100644 --- a/src/documents/document/visual.rs +++ b/src/documents/document/visual.rs @@ -7,7 +7,7 @@ use gpui::{ use super::MarkdownDocument; use crate::{ documents::{MarkdownMode, render::draw_nodes}, - forms::{Down, Up}, + forms::{self, Down, Up}, primitives::FocusRing as _, theme::{ActiveTheme, Radius, TextSize}, }; @@ -158,13 +158,24 @@ impl Render for MarkdownDocument { MarkdownMode::Visual => root .key_context(CONTEXT) .track_focus(&self.focus) - .on_action(cx.listener(|document, _: &Undo, _, cx| { - document.commit(cx); - document.editor.update(cx, |editor, cx| editor.undo(cx)); + .on_action(cx.listener(|document, _: &Undo, window, cx| { + document.history(false, window, cx); })) - .on_action(cx.listener(|document, _: &Redo, _, cx| { - document.commit(cx); - document.editor.update(cx, |editor, cx| editor.redo(cx)); + .on_action(cx.listener(|document, _: &Redo, window, cx| { + document.history(true, window, cx); + })) + // An untouched block leaves undo to the document. + .capture_action(cx.listener(|document, _: &forms::Undo, window, cx| { + if document.untouched(cx) { + cx.stop_propagation(); + document.history(false, window, cx); + } + })) + .capture_action(cx.listener(|document, _: &forms::Redo, window, cx| { + if document.untouched(cx) { + cx.stop_propagation(); + document.history(true, window, cx); + } })) .capture_action(cx.listener(|document, _: &Up, window, cx| { if document.step(false, window, cx) {