diff --git a/package.json b/package.json index 274bd5e..3e3c495 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "license": "MIT", "dependencies": { + "@codemirror/commands": "6.10.2", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/language": "^6.12.2", "@codemirror/state": "^6.5.2", @@ -31,6 +32,11 @@ "@xterm/xterm": "^6.0.0", "mermaid": "^11.13.0" }, + "pnpm": { + "overrides": { + "@codemirror/state": "6.5.4" + } + }, "devDependencies": { "@playwright/test": "^1.58.2", "@sveltejs/vite-plugin-svelte": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88c9c53..854e751 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,10 +4,16 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@codemirror/state': 6.5.4 + importers: .: dependencies: + '@codemirror/commands': + specifier: 6.10.2 + version: 6.10.2 '@codemirror/lang-markdown': specifier: ^6.5.0 version: 6.5.0 @@ -15,7 +21,7 @@ importers: specifier: ^6.12.2 version: 6.12.2 '@codemirror/state': - specifier: ^6.5.2 + specifier: 6.5.4 version: 6.5.4 '@codemirror/view': specifier: ^6.39.17 @@ -444,7 +450,7 @@ packages: '@codemirror/commands': 6.x.x '@codemirror/language': 6.x.x '@codemirror/search': 6.x.x - '@codemirror/state': 6.x.x + '@codemirror/state': 6.5.4 '@codemirror/view': 6.x.x '@rollup/rollup-android-arm-eabi@4.59.0': diff --git a/src/lib/CodeMirrorNoteEditor.svelte b/src/lib/CodeMirrorNoteEditor.svelte index 01ff52e..2543d82 100644 --- a/src/lib/CodeMirrorNoteEditor.svelte +++ b/src/lib/CodeMirrorNoteEditor.svelte @@ -2,6 +2,7 @@ import { untrack } from "svelte"; import { EditorState } from "@codemirror/state"; import { EditorView, drawSelection } from "@codemirror/view"; + import { history } from "@codemirror/commands"; import { markdown } from "@codemirror/lang-markdown"; import { Vim, getCM, vim } from "@replit/codemirror-vim"; import { markdownLivePreview } from "./markdownLivePreview"; @@ -95,6 +96,7 @@ doc, extensions: [ vim(), + history(), drawSelection(), markdown(), markdownLivePreview({ resolveImageSrc: untrack(() => resolveImageSrc) }), diff --git a/src/lib/NotesEditor.test.ts b/src/lib/NotesEditor.test.ts index 7af9b23..04efd30 100644 --- a/src/lib/NotesEditor.test.ts +++ b/src/lib/NotesEditor.test.ts @@ -138,6 +138,46 @@ describe("NotesEditor", () => { expect(get(focusTarget)).toEqual({ type: "note", filename: "a.md", folder: "Project Alpha" }); }); + it("supports vim undo (u) to revert typed text", async () => { + vi.mocked(command).mockImplementation((commandName: string) => { + if (commandName === "read_note") { + return Promise.resolve("original"); + } + + if (commandName === "write_note") { + return Promise.resolve(undefined); + } + + return Promise.resolve(undefined); + }); + + focusTarget.set({ type: "notes-editor", folder: "Project Alpha" }); + + render(NotesEditor); + const user = userEvent.setup(); + + const editor = await screen.findByTestId("note-code-editor"); + const textbox = within(editor).getByRole("textbox"); + textbox.focus(); + + // Enter insert mode, type text, return to normal mode + await user.keyboard("A"); // append at end of line + await user.keyboard(" added"); + await user.keyboard("{Escape}"); + + await waitFor(() => { + expect(editor).toHaveTextContent("original added"); + }); + + // Press 'u' to undo + await user.keyboard("u"); + + await waitFor(() => { + expect(editor).toHaveTextContent("original"); + expect(editor).not.toHaveTextContent("original added"); + }); + }); + it("keeps the latest note content when read_note resolves out of order", async () => { const noteARequest = deferred(); const noteBRequest = deferred();