diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index fe326ae1b..da03da0dc 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, Drawer } from '@strudel/draw'; import { isAutoCompletionEnabled } from './autocomplete.mjs'; @@ -257,39 +257,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)); @@ -317,18 +284,6 @@ export class StrudelMirror { this.repl.scheduler.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(); @@ -415,9 +370,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 9b217cfed..afe8d8814 100644 --- a/packages/codemirror/keybindings.mjs +++ b/packages/codemirror/keybindings.mjs @@ -3,10 +3,9 @@ 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 { logger } from '@strudel/core'; const vscodePlugin = ViewPlugin.fromClass( class { @@ -20,110 +19,6 @@ const vscodePlugin = ViewPlugin.fromClass( ); 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.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) => { - 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.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); - } - // 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, 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 6d76e61a8..477154a4a 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 = @@ -350,11 +350,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,