Порівняти коміти

...
1 Коміти
Автор SHA1 Повідомлення Дата
l0ng-ai 205241c0b3 fix(notebook): Vim u and Ctrl-R undo and redo in notebook cells
Notebook cells have no CodeMirror history: source and structural edits
share the notebook's backend history, which Cmd+Z already uses. Vim's u /
Ctrl-R called CodeMirror's history and did nothing. An editor can now
provide externalHistory, and the Vim engine sends undo / redo there.
2026-11-16 00:11:25 +08:00
4 змінених файлів з 52 додано та 4 видалено

No files matched your search

+14
Переглянути файл
@@ -290,6 +290,20 @@ export const documentSynced = Facet.define<() => Promise<void>, (() => Promise<v
combine: (values) => values[0] ?? null,
});
/**
* Undo / Redo of an editor whose history is kept outside CodeMirror (notebook cells share
* the notebook's backend history). Commands that would call CodeMirror's history (Vim's
* `u` / `Ctrl-R`) go through it instead.
*/
export interface ExternalHistory {
undo(): void;
redo(): void;
}
export const externalHistory = Facet.define<ExternalHistory, ExternalHistory | null>({
combine: (values) => values[0] ?? null,
});
export function whenSynced(view: EditorView): Promise<void> {
return view.state.facet(documentSynced)?.() ?? Promise.resolve();
}
+5 -1
Переглянути файл
@@ -12,7 +12,7 @@ import {
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
import { bracketMatching, indentOnInput, syntaxHighlighting } from "@codemirror/language";
import type { NotebookCellDoc } from "@/bindings";
import { cellEditorExtensions, embeddedEditorExtensions } from "@/editor/api";
import { cellEditorExtensions, embeddedEditorExtensions, externalHistory } from "@/editor/api";
import { openideaTheme } from "@/editor/core/cm/theme";
import { openideaHighlightStyle } from "@/editor/core/cm/highlight";
import { selectionMarks } from "@/editor/core/cm/selectionMarks";
@@ -97,6 +97,10 @@ export function CellEditor({
]),
),
keymap.of([indentWithTab, ...defaultKeymap]),
externalHistory.of({
undo: () => void session.undo().catch((e) => session.report(e)),
redo: () => void session.redo().catch((e) => session.report(e)),
}),
embeddedEditorExtensions(session.path),
language.of([]),
services.current.of(languageServices(type, initialDoc.current)),
+20 -1
Переглянути файл
@@ -2,7 +2,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { EditorState, Prec } from "@codemirror/state";
import { EditorView, keymap } from "@codemirror/view";
import { getCM, Vim } from "@replit/codemirror-vim";
import { embeddedEditorExtensions, transientChange, useEditorRegistry, onEditorEvent } from "@/editor/api";
import {
embeddedEditorExtensions,
externalHistory,
transientChange,
useEditorRegistry,
onEditorEvent,
} from "@/editor/api";
import { executeAction, registerAction } from "@/platform/actions";
import { getContext } from "@/platform/context";
import { _handleEx, applyRcCommands, vimExtension } from "../engine";
@@ -115,6 +121,19 @@ describe("Vim engine", () => {
view.destroy();
});
it("sends u and Ctrl-R to an editor's own history (notebook cells)", () => {
const history = { undo: vi.fn(), redo: vi.fn() };
const view = makeView([vimExtension(), externalHistory.of(history)]);
const cm = getCM(view)!;
Vim.handleKey(cm as never, "u", "user");
Vim.handleKey(cm as never, "<C-r>", "user");
Vim.handleKey(cm as never, "2", "user");
Vim.handleKey(cm as never, "u", "user");
expect(history.undo).toHaveBeenCalledTimes(3);
expect(history.redo).toHaveBeenCalledTimes(1);
view.destroy();
});
it("applies insert-mode mappings, accepting <esc> in any case", () => {
const view = makeView();
const cm = getCM(view)!;
+13 -2
Переглянути файл
@@ -5,8 +5,8 @@
*/
import { EditorState, Prec, StateEffect, StateField, Transaction, type Extension } from "@codemirror/state";
import { EditorView, ViewPlugin, runScopeHandlers } from "@codemirror/view";
import { Vim, getCM, vim } from "@replit/codemirror-vim";
import { transientChange, reportInputMode, type EditorDocContext } from "@/editor/api";
import { CodeMirror, Vim, getCM, vim } from "@replit/codemirror-vim";
import { externalHistory, transientChange, reportInputMode, type EditorDocContext } from "@/editor/api";
import { setContext } from "@/platform/context";
import { executeAction, getAction } from "@/platform/actions";
import { showStatusInfo } from "@/workbench/contributions";
@@ -16,6 +16,17 @@ import { resolveActionId, type RcCommand } from "./rc";
import { mapFunctionTextObjects } from "./textObjects";
/** Insert/Replace state as seen by transactions (mode changes are queued). */
// Vim's `u` / `Ctrl-R` call CodeMirror's history; an editor that keeps its history elsewhere
// (notebook cells) provides `externalHistory` and gets those keys too.
for (const name of ["undo", "redo"] as const) {
const own = CodeMirror.commands[name];
CodeMirror.commands[name] = (cm) => {
const history = cm.cm6.state.facet(externalHistory);
if (history) history[name]();
else own(cm);
};
}
const setVimInsert = StateEffect.define<boolean>();
const vimInsert = StateField.define<boolean>({