541 lines
15 KiB
TypeScript
541 lines
15 KiB
TypeScript
import {
|
|
cursorCharLeft,
|
|
cursorCharRight,
|
|
cursorDocEnd,
|
|
cursorDocStart,
|
|
cursorGroupLeft,
|
|
cursorGroupRight,
|
|
cursorLineBoundaryLeft,
|
|
cursorLineBoundaryRight,
|
|
cursorLineDown,
|
|
cursorLineEnd,
|
|
cursorLineStart,
|
|
cursorLineUp,
|
|
cursorPageDown,
|
|
cursorPageUp,
|
|
deleteCharBackward,
|
|
deleteCharForward,
|
|
deleteGroupBackward,
|
|
deleteGroupForward,
|
|
deleteLine,
|
|
deleteLineBoundaryBackward,
|
|
deleteLineBoundaryForward,
|
|
indentLess,
|
|
indentMore,
|
|
insertNewlineAndIndent,
|
|
redo,
|
|
selectAll,
|
|
selectCharLeft,
|
|
selectCharRight,
|
|
selectDocEnd,
|
|
selectDocStart,
|
|
selectGroupLeft,
|
|
selectGroupRight,
|
|
selectLineBoundaryLeft,
|
|
selectLineBoundaryRight,
|
|
selectLineDown,
|
|
selectLineEnd,
|
|
selectLineStart,
|
|
selectLineUp,
|
|
selectPageDown,
|
|
selectPageUp,
|
|
transposeChars,
|
|
undo,
|
|
} from "@codemirror/commands";
|
|
import {
|
|
acceptCompletion,
|
|
closeCompletion,
|
|
moveCompletionSelection,
|
|
startCompletion,
|
|
} from "@codemirror/autocomplete";
|
|
import { openSearchPanel } from "@codemirror/search";
|
|
import { reloadAllWidgets } from "./codemirror/code_widget.ts";
|
|
import { broadcastReload } from "./components/widget_sandbox_iframe.ts";
|
|
import type { Client } from "./client.ts";
|
|
import type { CommandHook } from "./plugos/hooks/command.ts";
|
|
|
|
/**
|
|
* Registers client-side editor commands with the CommandHook. These were
|
|
* previously defined in the editor plug; moving them into the client makes
|
|
* them synchronous (no async round-trip through the plug Web Worker) and
|
|
* avoids losing key events when the user types faster than the worker
|
|
* responds. They are still exposed through the official command mechanism
|
|
* so Lua scripts can rebind them.
|
|
*
|
|
* The `hook` is passed explicitly because this runs from inside the
|
|
* `ClientSystem` constructor, before `client.clientSystem` has been assigned.
|
|
*/
|
|
export function registerEditorCommands(
|
|
client: Client,
|
|
hook: CommandHook,
|
|
): void {
|
|
const view = () => client.editorView;
|
|
|
|
// Enter: accept completion if popup is open, else newline-and-indent
|
|
hook.registerCommand({
|
|
name: "Editor: Insert Newline",
|
|
key: "Enter",
|
|
mac: "Enter",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
if (acceptCompletion(v)) return true;
|
|
return insertNewlineAndIndent(v);
|
|
},
|
|
});
|
|
|
|
// Delete
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Char Backward",
|
|
key: ["Backspace", "Ctrl-h"],
|
|
mac: ["Backspace", "Ctrl-h"],
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteCharBackward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Char Forward",
|
|
key: "Delete",
|
|
mac: "Delete",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteCharForward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Group Backward",
|
|
key: "Ctrl-Backspace",
|
|
mac: "Alt-Backspace",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteGroupBackward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Group Forward",
|
|
key: "Ctrl-Delete",
|
|
mac: "Alt-Delete",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteGroupForward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Line Boundary Backward",
|
|
mac: "Cmd-Backspace",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteLineBoundaryBackward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Delete Line Boundary Forward",
|
|
mac: "Cmd-Delete",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => deleteLineBoundaryForward(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Transpose Chars",
|
|
key: "Ctrl-t",
|
|
mac: "Ctrl-t",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => transposeChars(view()),
|
|
});
|
|
|
|
// Cursor motions
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Char Left",
|
|
key: "ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorCharLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Char Right",
|
|
key: "ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorCharRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Group Left",
|
|
key: "Ctrl-ArrowLeft",
|
|
mac: "Alt-ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorGroupLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Group Right",
|
|
key: "Ctrl-ArrowRight",
|
|
mac: "Alt-ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorGroupRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line Boundary Left",
|
|
mac: "Cmd-ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorLineBoundaryLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line Boundary Right",
|
|
mac: "Cmd-ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorLineBoundaryRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line Start",
|
|
key: "Home",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorLineStart(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line End",
|
|
key: ["End", "Ctrl-e"],
|
|
mac: ["End", "Ctrl-e"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorLineEnd(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Doc Start",
|
|
key: "Ctrl-Home",
|
|
mac: ["Ctrl-Home", "Cmd-ArrowUp"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorDocStart(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Doc End",
|
|
key: "Ctrl-End",
|
|
mac: ["Ctrl-End", "Cmd-ArrowDown"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => cursorDocEnd(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line Up",
|
|
key: ["ArrowUp", "Ctrl-p"],
|
|
mac: "ArrowUp",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
if (moveCompletionSelection(false)(v)) return true;
|
|
return cursorLineUp(v);
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Line Down",
|
|
key: "ArrowDown",
|
|
mac: "ArrowDown",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
if (moveCompletionSelection(true)(v)) return true;
|
|
return cursorLineDown(v);
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Page Up",
|
|
key: "PageUp",
|
|
mac: ["PageUp", "Ctrl-ArrowUp"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
if (moveCompletionSelection(false, "page")(v)) return true;
|
|
return cursorPageUp(v);
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Cursor Page Down",
|
|
key: "PageDown",
|
|
mac: ["PageDown", "Ctrl-v", "Ctrl-ArrowDown"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
if (moveCompletionSelection(true, "page")(v)) return true;
|
|
return cursorPageDown(v);
|
|
},
|
|
});
|
|
|
|
// Selection-extending motions
|
|
hook.registerCommand({
|
|
name: "Editor: Select Char Left",
|
|
key: "Shift-ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectCharLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Char Right",
|
|
key: "Shift-ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectCharRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Group Left",
|
|
key: "Shift-Ctrl-ArrowLeft",
|
|
mac: "Shift-Alt-ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectGroupLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Group Right",
|
|
key: "Shift-Ctrl-ArrowRight",
|
|
mac: "Shift-Alt-ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectGroupRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line Boundary Left",
|
|
mac: "Shift-Cmd-ArrowLeft",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineBoundaryLeft(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line Boundary Right",
|
|
mac: "Shift-Cmd-ArrowRight",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineBoundaryRight(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line Start",
|
|
key: "Shift-Home",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineStart(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line End",
|
|
key: ["Shift-End", "Shift-Ctrl-e"],
|
|
mac: ["Shift-End", "Shift-Ctrl-e"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineEnd(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Doc Start",
|
|
key: "Shift-Ctrl-Home",
|
|
mac: ["Shift-Ctrl-Home", "Shift-Cmd-ArrowUp"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectDocStart(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Doc End",
|
|
key: "Shift-Ctrl-End",
|
|
mac: ["Shift-Ctrl-End", "Shift-Cmd-ArrowDown"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectDocEnd(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line Up",
|
|
key: ["Shift-ArrowUp", "Shift-Ctrl-p"],
|
|
mac: "Shift-ArrowUp",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineUp(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Line Down",
|
|
key: "Shift-ArrowDown",
|
|
mac: "Shift-ArrowDown",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectLineDown(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Page Up",
|
|
key: "Shift-PageUp",
|
|
mac: ["Shift-PageUp", "Shift-Ctrl-ArrowUp"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectPageUp(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Select Page Down",
|
|
key: "Shift-PageDown",
|
|
mac: ["Shift-PageDown", "Shift-Ctrl-ArrowDown"],
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => selectPageDown(view()),
|
|
});
|
|
|
|
// Selection / indentation
|
|
hook.registerCommand({
|
|
name: "Editor: Select All",
|
|
key: "Ctrl-a",
|
|
mac: "Cmd-a",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => {
|
|
const v = view();
|
|
return selectAll({ state: v.state, dispatch: v.dispatch });
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Indent",
|
|
key: "Tab",
|
|
mac: "Tab",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
run: async () => {
|
|
const v = view();
|
|
// Accept completion popup suggestion if open, else indent
|
|
if (acceptCompletion(v)) return true;
|
|
return indentMore({ state: v.state, dispatch: v.dispatch });
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Outdent",
|
|
key: "Shift-Tab",
|
|
mac: "Shift-Tab",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
run: async () => {
|
|
const v = view();
|
|
return indentLess({ state: v.state, dispatch: v.dispatch });
|
|
},
|
|
});
|
|
|
|
// Undo / redo
|
|
hook.registerCommand({
|
|
name: "Editor: Undo",
|
|
key: "Ctrl-z",
|
|
mac: "Cmd-z",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
menu: { location: "edit", group: "1_undo", order: 1, label: "Undo" },
|
|
run: async () => undo(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Redo",
|
|
key: "Ctrl-y",
|
|
mac: "Cmd-Shift-z",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
menu: { location: "edit", group: "1_undo", order: 2, label: "Redo" },
|
|
run: async () => redo(view()),
|
|
});
|
|
|
|
// Delete line
|
|
hook.registerCommand({
|
|
name: "Delete Line",
|
|
key: "Ctrl-d",
|
|
requireMode: "rw",
|
|
requireEditor: "page",
|
|
run: async () => deleteLine(view()),
|
|
});
|
|
|
|
// Completion popup
|
|
hook.registerCommand({
|
|
name: "Editor: Start Completion",
|
|
key: "Ctrl-Space",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => startCompletion(view()),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Close Completion",
|
|
key: "Escape",
|
|
requireEditor: "page",
|
|
disableInVim: true,
|
|
run: async () => closeCompletion(view()),
|
|
});
|
|
|
|
// Openers (modal UI — not in the typing hot path, but moving them out of
|
|
// the plug removes an unnecessary worker round-trip).
|
|
hook.registerCommand({
|
|
name: "Open Command Palette",
|
|
key: "Ctrl-/",
|
|
mac: "Cmd-/",
|
|
menu: { location: "file", group: "3_palette", label: "Command Palette..." },
|
|
run: async () => client.startCommandPalette(),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Navigate: Page Picker",
|
|
key: "Ctrl-k",
|
|
mac: "Cmd-k",
|
|
menu: [
|
|
{ location: "file", group: "1_new", order: 2, label: "Open Page..." },
|
|
{ location: "navigate", group: "2_picker", order: 1, label: "Page..." },
|
|
],
|
|
run: async () => client.startPageNavigate("page"),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Navigate: Meta Picker",
|
|
key: "Ctrl-Shift-k",
|
|
mac: "Cmd-Shift-k",
|
|
menu: { location: "navigate", group: "2_picker", order: 4, label: "Meta Page..." },
|
|
run: async () => client.startPageNavigate("meta"),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Navigate: Document Picker",
|
|
key: "Ctrl-o",
|
|
mac: "Cmd-o",
|
|
menu: [
|
|
{ location: "file", group: "1_new", order: 3, label: "Open Document..." },
|
|
{ location: "navigate", group: "2_picker", order: 2, label: "Document..." },
|
|
],
|
|
run: async () => client.startPageNavigate("document"),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Navigate: Anything Picker",
|
|
run: async () => client.startPageNavigate("all"),
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: Find in Page",
|
|
key: "Ctrl-f",
|
|
mac: "Cmd-f",
|
|
requireEditor: "page",
|
|
menu: { location: "edit", group: "3_find", label: "Find in Page..." },
|
|
run: async () => {
|
|
openSearchPanel(view());
|
|
return false; // keep focus on search panel, not the editor
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Editor: New Window",
|
|
key: "Ctrl-n",
|
|
mac: "Cmd-n",
|
|
run: async () => {
|
|
globalThis.open(
|
|
location.href,
|
|
`rnd${Math.random()}`,
|
|
`width=${globalThis.innerWidth},heigh=${globalThis.innerHeight}`,
|
|
);
|
|
},
|
|
});
|
|
hook.registerCommand({
|
|
name: "Widgets: Refresh All",
|
|
requireEditor: "page",
|
|
run: async () => {
|
|
client.widgetCache.clearPrewarm();
|
|
broadcastReload();
|
|
return reloadAllWidgets();
|
|
},
|
|
});
|
|
}
|