mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 14:26:58 -04:00
a9752fb2a4
Docs for adding keybindings Only expose Vim from keybindings
144 lines
4.8 KiB
JavaScript
144 lines
4.8 KiB
JavaScript
import { defaultKeymap } from '@codemirror/commands';
|
|
import { Prec, EditorState } from '@codemirror/state';
|
|
import { keymap, ViewPlugin } from '@codemirror/view';
|
|
// import { searchKeymap } from '@codemirror/search';
|
|
import { emacs } from '@replit/codemirror-emacs';
|
|
import { vim, Vim } from '@replit/codemirror-vim';
|
|
// import { vim } from './vim_test.mjs';
|
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
|
import { logger } from '@strudel/core';
|
|
|
|
const vscodePlugin = ViewPlugin.fromClass(
|
|
class {
|
|
constructor() {}
|
|
},
|
|
{
|
|
provide: () => {
|
|
return Prec.highest(keymap.of([...vscodeKeymap]));
|
|
},
|
|
},
|
|
);
|
|
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
|
|
|
// Map Vim :w to trigger the same action as evaluation. We dispatch a custom
|
|
// event 'repl-evaluate' that the editor listens for, and also simulate
|
|
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
|
|
// appears in the Console panel.
|
|
try {
|
|
if (Vim && typeof Vim.defineEx === 'function') {
|
|
// Map gc to toggle line comments by dispatching a custom event that our
|
|
// CodeMirror integration listens to. This avoids depending on Vim's
|
|
// internal actions and works with current selections/visual mode.
|
|
try {
|
|
Vim.defineAction('strudelToggleComment', (cm) => {
|
|
const view = cm?.view || cm;
|
|
try {
|
|
const ev = new CustomEvent('repl-toggle-comment', { detail: { source: 'vim', view }, cancelable: true });
|
|
document.dispatchEvent(ev);
|
|
} catch (e) {
|
|
console.error('strudelToggleComment dispatch failed', e);
|
|
}
|
|
});
|
|
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'normal' });
|
|
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'visual' });
|
|
} catch (e) {
|
|
console.error('Vim gc mapping failed', e);
|
|
}
|
|
|
|
// :q to pause/stop
|
|
Vim.defineEx('quit', 'q', (cm) => {
|
|
try {
|
|
const view = cm?.view || cm;
|
|
// First try dispatching our custom stop event, then fallback to Alt+.
|
|
let handled = false;
|
|
try {
|
|
const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true });
|
|
handled = document.dispatchEvent(ev) === false;
|
|
} catch (e) {
|
|
console.error('Error dispatching repl-stop event', e);
|
|
}
|
|
if (!handled) {
|
|
const altDot = new KeyboardEvent('keydown', {
|
|
key: '.',
|
|
code: 'Period',
|
|
altKey: true,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
view?.dom?.dispatchEvent?.(altDot);
|
|
}
|
|
} catch (e) {
|
|
console.error('Error dispatching :q stop event', e);
|
|
}
|
|
});
|
|
|
|
// :w to evaluate
|
|
Vim.defineEx('write', 'w', (cm) => {
|
|
const view = cm?.view || cm; // CM6 Vim passes either an object with view or the view itself
|
|
try {
|
|
view?.focus?.();
|
|
// Let the app know this came from Vim :w
|
|
try {
|
|
logger('[vim] :w — evaluating code');
|
|
} catch (e) {
|
|
console.error('Error logging Vim :w evaluation', e);
|
|
}
|
|
// Dispatch a dedicated evaluate event first
|
|
let handled = false;
|
|
try {
|
|
const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true });
|
|
handled = document.dispatchEvent(ev) === false; // false means preventDefault was called
|
|
} catch (e) {
|
|
console.error('Error dispatching repl-evaluate event', e);
|
|
}
|
|
if (handled) {
|
|
return;
|
|
}
|
|
// Try Ctrl+Enter first if not handled by custom event
|
|
const ctrlEnter = new KeyboardEvent('keydown', {
|
|
key: 'Enter',
|
|
code: 'Enter',
|
|
ctrlKey: true,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
view?.dom?.dispatchEvent?.(ctrlEnter);
|
|
// If not handled (no handler called preventDefault), try Alt+Enter as
|
|
// fallback
|
|
if (!ctrlEnter.defaultPrevented) {
|
|
const altEnter = new KeyboardEvent('keydown', {
|
|
key: 'Enter',
|
|
code: 'Enter',
|
|
altKey: true,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
});
|
|
view?.dom?.dispatchEvent?.(altEnter);
|
|
}
|
|
} catch (e) {
|
|
console.error('Error dispatching :w evaluation event', e);
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
|
}
|
|
|
|
const keymaps = {
|
|
vim,
|
|
emacs,
|
|
codemirror: () => keymap.of(defaultKeymap),
|
|
vscode: vscodeExtension,
|
|
};
|
|
|
|
export { Vim } from '@replit/codemirror-vim';
|
|
|
|
export function keybindings(name) {
|
|
const active = keymaps[name];
|
|
const extensions = active ? [Prec.high(active())] : [];
|
|
if (name === 'vim') {
|
|
extensions.push(EditorState.allowMultipleSelections.of(true));
|
|
}
|
|
return extensions;
|
|
}
|