Ensure command keybindings work reliably in any vim-mode (#1821)
When the vim extension is enabled and in "normal" mode, some default bindings collide with Silverbullets commands (eg: `ctrl-p` - Share, `ctrl-o` - Open Document, `ctrl-e` - Export, `ctrl-g h` - Home). These vim bindings are not always functional (they may appear to do nothing depending on vims state). This gives the impression that Silverbullet is bugged and wont trigger commands. In "insert" mode, most bindings are passed through correctly with some exceptions (eg: `ctrl-o`). By splitting the command keybindings into their own keymap extension and loading it before the vim extension, we can ensure Silverbullet commands always trigger. This does mean some vim bindings are overshadowed, but if a binding triggers a Silverbullet command, its generally obvious that *something* happened and the user should be able to recognise why the vim binding failed and then rebind the command or vim mapping.
This commit is contained in:
+1
-1
@@ -121,7 +121,7 @@ export class Client {
|
||||
|
||||
// CodeMirror editor
|
||||
editorView!: EditorView;
|
||||
keyHandlerCompartment?: Compartment;
|
||||
commandKeyHandlerCompartment?: Compartment;
|
||||
indentUnitCompartment?: Compartment;
|
||||
undoHistoryCompartment?: Compartment;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import { languageSyscalls } from "./plugos/syscalls/language.ts";
|
||||
import { codeWidgetSyscalls } from "./plugos/syscalls/code_widget.ts";
|
||||
import { clientCodeWidgetSyscalls } from "./plugos/syscalls/client_code_widget.ts";
|
||||
import { KVPrimitivesManifestCache } from "./plugos/manifest_cache.ts";
|
||||
import { createKeyBindings } from "./codemirror/editor_state.ts";
|
||||
import { createCommandKeyBindings } from "./codemirror/editor_state.ts";
|
||||
import type { DataStoreMQ } from "./data/mq.datastore.ts";
|
||||
import { jsonschemaSyscalls } from "./plugos/syscalls/jsonschema.ts";
|
||||
import { luaSyscalls } from "./plugos/syscalls/lua.ts";
|
||||
@@ -125,8 +125,8 @@ export class ClientSystem {
|
||||
});
|
||||
// Replace the key mapping compartment (keybindings)
|
||||
this.client.editorView.dispatch({
|
||||
effects: this.client.keyHandlerCompartment?.reconfigure(
|
||||
createKeyBindings(this.client),
|
||||
effects: this.client.commandKeyHandlerCompartment?.reconfigure(
|
||||
createCommandKeyBindings(this.client),
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -60,11 +60,15 @@ export function createEditorState(
|
||||
): EditorState {
|
||||
let touchCount = 0;
|
||||
|
||||
// Ugly: keep the keyhandler compartment in the client, to be replaced later once more commands are loaded
|
||||
client.keyHandlerCompartment = new Compartment();
|
||||
const keyBindings = client.keyHandlerCompartment.of(
|
||||
createKeyBindings(client),
|
||||
// Ugly: keep the commandKeyHandler compartment in the client, to be replaced
|
||||
// later once more commands are loaded
|
||||
client.commandKeyHandlerCompartment = new Compartment();
|
||||
const commandKeyBindings = client.commandKeyHandlerCompartment.of(
|
||||
createCommandKeyBindings(client),
|
||||
);
|
||||
// Regular key bindings are not dynamically updated and do not require a
|
||||
// compartment.
|
||||
const regularKeyBindings = createRegularKeyBindings(client);
|
||||
|
||||
client.indentUnitCompartment = new Compartment();
|
||||
const indentUnits = client.indentUnitCompartment.of(
|
||||
@@ -82,6 +86,12 @@ export function createEditorState(
|
||||
dark: client.ui.viewState.uiOptions.darkMode,
|
||||
}),
|
||||
|
||||
// Insert our command key bindings *before* vim mode. Vim in normal-mode is
|
||||
// greedy and captures all key events, preventing them from reaching our
|
||||
// own handlers to trigger commands. This will mean some vim-mode
|
||||
// bindings wont trigger if they have the same keys.
|
||||
commandKeyBindings,
|
||||
|
||||
// Enable vim mode, or not
|
||||
[
|
||||
...client.ui.viewState.uiOptions.vimMode
|
||||
@@ -183,7 +193,7 @@ export function createEditorState(
|
||||
},
|
||||
]),
|
||||
disableSpellcheck(["InlineCode", "CodeText", "CodeInfo", "FrontMatter"]),
|
||||
keyBindings,
|
||||
regularKeyBindings,
|
||||
EditorView.domEventHandlers({
|
||||
// This may result in duplicated touch events on mobile devices
|
||||
touchmove: () => {
|
||||
@@ -331,7 +341,7 @@ export function isValidEditor(
|
||||
(currentEditor !== undefined && requiredEditor === "notpage");
|
||||
}
|
||||
|
||||
export function createCommandKeyBindings(client: Client): KeyBinding[] {
|
||||
export function createCommandKeyBindings(client: Client): Extension {
|
||||
const commandKeyBindings: KeyBinding[] = [];
|
||||
|
||||
// Then add bindings for plug commands
|
||||
@@ -386,17 +396,16 @@ export function createCommandKeyBindings(client: Client): KeyBinding[] {
|
||||
}
|
||||
}
|
||||
|
||||
return commandKeyBindings;
|
||||
return keymap.of([
|
||||
...commandKeyBindings,
|
||||
]);
|
||||
}
|
||||
|
||||
export function createKeyBindings(client: Client): Extension {
|
||||
export function createRegularKeyBindings(client: Client): Extension {
|
||||
if (client.isDocumentEditor()) {
|
||||
return keymap.of([
|
||||
...createCommandKeyBindings(client),
|
||||
]);
|
||||
return keymap.of([]);
|
||||
} else {
|
||||
return keymap.of([
|
||||
...createCommandKeyBindings(client),
|
||||
...createSmartQuoteKeyBindings(client),
|
||||
...closeBracketsKeymap,
|
||||
...client.ui.viewState.uiOptions.vimMode
|
||||
|
||||
@@ -148,6 +148,9 @@ export function MiniEditor(
|
||||
doc: text,
|
||||
extensions: [
|
||||
EditorView.theme({}, { dark: darkMode }),
|
||||
// Insert command bindings before vim-mode to ensure they're available
|
||||
// in normal mode. See editor_state.ts for more details.
|
||||
createCommandKeyBindings(globalThis.client),
|
||||
// Enable vim mode, or not
|
||||
[...vimMode ? [vim()] : []],
|
||||
[
|
||||
@@ -182,7 +185,6 @@ export function MiniEditor(
|
||||
},
|
||||
...standardKeymap,
|
||||
...historyKeymap,
|
||||
...createCommandKeyBindings(globalThis.client),
|
||||
]),
|
||||
EditorView.domEventHandlers({
|
||||
click: (e) => {
|
||||
|
||||
Reference in New Issue
Block a user