From a2af0cd5cc15b224e731a863c1e75a3b186d6243 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 14 Oct 2026 23:10:29 +0200 Subject: [PATCH] Revert "Merge pull request 'Repurpose vim shortcuts for usability' (#1624) from dtricks/strudel:feat/vim-shortcuts into main" This reverts commit 12204c9053c1355cdd06a6007b28e21116e88534, reversing changes made to f2009a33aadde8ef4140442afc5f8addee5fdf56. --- packages/codemirror/codemirror.mjs | 52 +----- packages/codemirror/keybindings.mjs | 161 +----------------- website/src/pages/technical-manual/repl.mdx | 4 - website/src/pages/technical-manual/vim.mdx | 58 ------- .../src/repl/components/panel/SettingsTab.jsx | 7 +- website/src/repl/prebakeCodeMirror.mjs | 33 ---- 6 files changed, 6 insertions(+), 309 deletions(-) delete mode 100644 website/src/pages/technical-manual/vim.mdx diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 71e5c49ab..874fee4a4 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -1,5 +1,5 @@ import { closeBrackets } from '@codemirror/autocomplete'; -import { indentWithTab, toggleLineComment } from '@codemirror/commands'; +import { indentWithTab } from '@codemirror/commands'; import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'; import { Compartment, EditorState, Prec } from '@codemirror/state'; @@ -12,7 +12,7 @@ import { lineNumbers, } from '@codemirror/view'; import { persistentAtom } from '@nanostores/persistent'; -import { logger, registerControl, repl } from '@strudel/core'; +import { registerControl, repl } from '@strudel/core'; import { cleanupDraw, cleanupDrawContext, Drawer } from '@strudel/draw'; import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { basicSetup } from './basicSetup.mjs'; @@ -289,39 +289,6 @@ export class StrudelMirror { } }; document.addEventListener('start-repl', this.onStartRepl); - - // Handle global evaluation requests (e.g., from Vim :w) - this.onEvaluateRequest = (e) => { - try { - if (e.detail.view !== this.editor) { - return; // ignore events from other editors - } - // Evaluate current editor on repl-evaluate - logger('[repl] evaluate via event'); - this.evaluate(); - e?.cancelable && e.preventDefault?.(); - } catch (err) { - console.error('Error handling repl-evaluate event', err); - } - }; - document.addEventListener('repl-evaluate', this.onEvaluateRequest); - document.addEventListener('repl-stop', this.onStopRequest); - - // Toggle comments requested from Vim (gc) - this.onToggleComment = (e) => { - try { - if (e.detail.view !== this.editor) { - return; // ignore events from other editors - } - // Honor selections; toggleLineComment handles both selections and - // single line - toggleLineComment(this.editor); - e?.cancelable && e.preventDefault?.(); - } catch (err) { - console.error('Error handling repl-toggle-comment event', err); - } - }; - document.addEventListener('repl-toggle-comment', this.onToggleComment); } draw(haps, time, painters) { painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime)); @@ -350,18 +317,6 @@ export class StrudelMirror { this.repl.stop(); } - // Listen for global stop requests (e.g., from Vim :q) - onStopRequest = (e) => { - try { - if (e.detail.view !== this.editor) { - return; // ignore events from other editors - } - this.stop(); - e?.cancelable && e.preventDefault?.(); - } catch (err) { - console.error('Error handling repl-stop event', err); - } - }; async toggle() { if (this.repl.scheduler.started) { this.repl.stop(); @@ -466,9 +421,6 @@ export class StrudelMirror { } clear() { this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl); - this.onEvaluateRequest && document.removeEventListener('repl-evaluate', this.onEvaluateRequest); - this.onStopRequest && document.removeEventListener('repl-stop', this.onStopRequest); - this.onToggleComment && document.removeEventListener('repl-toggle-comment', this.onToggleComment); } getCursorLocation() { return this.editor.state.selection.main.head; diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs index 16c7ec99e..5c5360a3e 100644 --- a/packages/codemirror/keybindings.mjs +++ b/packages/codemirror/keybindings.mjs @@ -3,11 +3,10 @@ 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 '@replit/codemirror-vim'; // import { vim } from './vim_test.mjs'; import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; -import { helix, commands } from 'codemirror-helix'; -import { logger } from '@strudel/core'; +import { helix } from 'codemirror-helix'; const vscodePlugin = ViewPlugin.fromClass( class { @@ -21,166 +20,12 @@ const vscodePlugin = ViewPlugin.fromClass( ); 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 -// 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.cm6; - 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) => { - const view = cm.cm6; - logger('[vim] :q — stopping repl'); - replStop(view); - }); - - // :w to evaluate - Vim.defineEx('write', 'w', (cm) => { - const view = cm.cm6; - 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); - } - replEval(view); - } 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); -} - -// 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 = { vim, emacs, codemirror: () => keymap.of(defaultKeymap), vscode: vscodeExtension, - helix: () => [helix(), helixCommands], + helix: () => [helix()], }; export { Vim } from '@replit/codemirror-vim'; diff --git a/website/src/pages/technical-manual/repl.mdx b/website/src/pages/technical-manual/repl.mdx index 4135848ed..166b73f27 100644 --- a/website/src/pages/technical-manual/repl.mdx +++ b/website/src/pages/technical-manual/repl.mdx @@ -117,10 +117,6 @@ Here's an example AST for `c3 [e3 g3]` which translates to `seq(c3, seq(e3, g3))` -## Vim Keybindings - -See the separate page on Vim shortcuts for a quick reference: [/technical-manual/vim](/technical-manual/vim) - ## Scheduling Events After an instance of `Pattern` is obtained from the user code, diff --git a/website/src/pages/technical-manual/vim.mdx b/website/src/pages/technical-manual/vim.mdx deleted file mode 100644 index 6ec41d4a2..000000000 --- a/website/src/pages/technical-manual/vim.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Vim Shortcuts -layout: ../../layouts/MainLayout.astro ---- - -# Vim Shortcuts in the REPL - -When the REPL editor (CodeMirror) is configured to use Vim keybindings, the following commands are available: - -- :w — Evaluate the current code - - - Triggers the same evaluation as Ctrl+Enter / Alt+Enter - - You'll see messages in the Console panel such as: - - [vim] :w — evaluating code - - [repl] evaluate via event - - [eval] code updated - -- :q — Stop/pause playback - - - Triggers the same stop action as Alt+. - - Useful to quickly stop scheduling without leaving Vim mode - -- gc — Toggle line comments for the current selection(s) - - - Works in normal and visual mode - - If there's a selection, all selected lines are toggled - -Notes - -- Behavior respects the current language mode in the editor for comment syntax. -- If multiple REPL editors are open, commands target the active editor. The implementation dispatches custom events handled by the editor. -- If you don't see the Console panel, open the right panel in the REPL UI. - -Troubleshooting - -- If :w logs but evaluation doesn't apply, ensure Vim keybindings are active and try again. You can also use Ctrl+Enter as a fallback. -- For :q / gc, ensure focus is inside the editor. If an error occurs, reload the page to reset editor state and try again. - -## Adding custom keybindings - -To add custom keybindings the `Vim` object can be used (either from within a pattern or in a prebake script) - -Example: - -```javascript -// Map 'jk' to Escape in normal mode -Vim.map('jk', '', 'insert'); -// Map 'U' to :w (Evaluate the current code) -Vim.map('U', ':w', 'normal'); -// Map 'Q' to :q — Stop/pause playback -Vim.map('Q', ':q', 'normal'); -// Map 'J' to find next '$' (jump to next label) -Vim.map('J', '/\\$', 'normal'); -// Map 'K' to find previous '$' (jump to previous label) -Vim.map('K', '?\\$', 'normal'); -``` - -For more information on how to use the `Vim` object see [CodeMirror Vim](https://github.com/replit/codemirror-vim) diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index a385fd48a..8cdba09e0 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -9,7 +9,7 @@ import { confirmDialog } from '../../util.mjs'; import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; import { ActionButton } from '../button/action-button.jsx'; import { exportScript, ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import cx from '@src/cx.mjs'; const inputClass = @@ -356,11 +356,6 @@ function MainSettingsContent({ started }) { function PrebakeSettingsContent() { const { fontFamily, includePrebakeScriptInShare, prebakeScript } = useSettings(); const editorRef = useRef(); - useEffect(() => { - return () => { - editorRef.current?.cleanup(); - }; - }); return (
diff --git a/website/src/repl/prebakeCodeMirror.mjs b/website/src/repl/prebakeCodeMirror.mjs index 42b913ae6..972da0062 100644 --- a/website/src/repl/prebakeCodeMirror.mjs +++ b/website/src/repl/prebakeCodeMirror.mjs @@ -1,4 +1,3 @@ -import { toggleLineComment } from '@codemirror/commands'; import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'; import { Compartment, EditorState, Prec } from '@codemirror/state'; @@ -63,28 +62,6 @@ export class PrebakeCodeMirror { state, parent: container, }); - - const handleSaveEvent = async (e) => { - if (e.detail.view !== this.view) { - return; // ignore events from other editors - } - await this.savePrebake(); - e?.cancelable && e.preventDefault?.(); - }; - const handleToggleComment = (e) => { - if (e.detail.view !== this.view) { - return; // ignore events from other editors - } - this.toggleComment(); - e?.cancelable && e.preventDefault?.(); - }; - - document.addEventListener('repl-evaluate', handleSaveEvent); - document.addEventListener('repl-toggle-comment', handleToggleComment); - this.cleanup = () => { - document.removeEventListener('prebake-evaluate', handleSaveEvent); - document.removeEventListener('prebake-toggle-comment', handleToggleComment); - }; } async savePrebake() { @@ -94,16 +71,6 @@ export class PrebakeCodeMirror { logger('[prebake] prebake saved'); } - toggleComment() { - try { - // Honor selections; toggleLineComment handles both selections and - // single line - toggleLineComment(this.view); - } catch (err) { - console.error('Error handling repl-toggle-comment event', err); - } - } - setCode(code) { const changes = { from: 0,