feat: Refactor Vim/Helix keybindings with shared helper functions
Factor out the repl-eval and repl-stop event dispatching code into replEval() and replStop() helper functions that are shared between Vim and Helix keybindings. This refactoring: - Reduces code duplication between Vim :w/:q and Helix :w/:q commands - Makes the keybinding handlers more maintainable - Properly implements Helix commands using the commands.of() API - Ensures consistent behavior across both keybinding modes The Helix keybindings now properly integrate with the REPL using the same event dispatch mechanism as Vim, dispatching custom repl-evaluate and repl-stop events with fallbacks to keyboard events. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a6b3b9ef5e
commit
055f009357
1 changed files with 106 additions and 57 deletions
|
|
@ -6,7 +6,7 @@ import { emacs } from '@replit/codemirror-emacs';
|
||||||
import { vim, Vim } from '@replit/codemirror-vim';
|
import { vim, Vim } from '@replit/codemirror-vim';
|
||||||
// import { vim } from './vim_test.mjs';
|
// import { vim } from './vim_test.mjs';
|
||||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||||
import { helix } from 'codemirror-helix';
|
import { helix, commands } from 'codemirror-helix';
|
||||||
import { logger } from '@strudel/core';
|
import { logger } from '@strudel/core';
|
||||||
|
|
||||||
const vscodePlugin = ViewPlugin.fromClass(
|
const vscodePlugin = ViewPlugin.fromClass(
|
||||||
|
|
@ -21,6 +21,70 @@ const vscodePlugin = ViewPlugin.fromClass(
|
||||||
);
|
);
|
||||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||||
|
|
||||||
|
function replEval(view) {
|
||||||
|
try {
|
||||||
|
// 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 repl evaluation event', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replStop(view) {
|
||||||
|
try {
|
||||||
|
// 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 repl stop event', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map Vim :w to trigger the same action as evaluation. We dispatch a custom
|
// 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
|
// 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
|
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
|
||||||
|
|
@ -48,29 +112,8 @@ try {
|
||||||
|
|
||||||
// :q to pause/stop
|
// :q to pause/stop
|
||||||
Vim.defineEx('quit', 'q', (cm) => {
|
Vim.defineEx('quit', 'q', (cm) => {
|
||||||
try {
|
const view = cm?.view || cm;
|
||||||
const view = cm?.view || cm;
|
replStop(view);
|
||||||
// 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
|
// :w to evaluate
|
||||||
|
|
@ -84,38 +127,7 @@ try {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error logging Vim :w evaluation', e);
|
console.error('Error logging Vim :w evaluation', e);
|
||||||
}
|
}
|
||||||
// Dispatch a dedicated evaluate event first
|
replEval(view);
|
||||||
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) {
|
} catch (e) {
|
||||||
console.error('Error dispatching :w evaluation event', e);
|
console.error('Error dispatching :w evaluation event', e);
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +137,49 @@ try {
|
||||||
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map Helix :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.
|
||||||
|
const helixCommands = commands.of([
|
||||||
|
{
|
||||||
|
// :w to evaluate
|
||||||
|
name: 'write',
|
||||||
|
aliases: ['w'],
|
||||||
|
help: 'Repl-eval',
|
||||||
|
handler(view, args) {
|
||||||
|
try {
|
||||||
|
view?.focus?.(); // Let the app know this came from Helix :w
|
||||||
|
logger('[helix] :w — evaluating code');
|
||||||
|
replEval(view);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error dispatching helix :w evaluation event', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// :q to pause/stop
|
||||||
|
name: 'quit',
|
||||||
|
aliases: ['q'],
|
||||||
|
help: 'Repl-stop',
|
||||||
|
handler(view, args) {
|
||||||
|
try {
|
||||||
|
view?.focus?.(); // Let the app know this came from Helix :q
|
||||||
|
logger('[helix] :q — stopping repl');
|
||||||
|
replStop(view);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error dispatching helix :q stop event', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const keymaps = {
|
const keymaps = {
|
||||||
vim,
|
vim,
|
||||||
emacs,
|
emacs,
|
||||||
helix,
|
|
||||||
codemirror: () => keymap.of(defaultKeymap),
|
codemirror: () => keymap.of(defaultKeymap),
|
||||||
vscode: vscodeExtension,
|
vscode: vscodeExtension,
|
||||||
|
helix: () => [helix(), helixCommands],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function keybindings(name) {
|
export function keybindings(name) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue