diff --git a/libs/codemirror-editor/src/__tests__/editor.test.ts b/libs/codemirror-editor/src/__tests__/editor.test.ts index e744aeb..397a563 100644 --- a/libs/codemirror-editor/src/__tests__/editor.test.ts +++ b/libs/codemirror-editor/src/__tests__/editor.test.ts @@ -64,4 +64,12 @@ describe('CodeMirrorInstance', () => { expect(captured).toBe('hello'); inst.destroy(); }); + + it('onRunShortcut 注入则构造不抛错', () => { + const opts = new EditorOptions(); + opts.onRunShortcut = () => {}; + const inst = new CodeMirrorInstance(container, opts); + inst.destroy(); + // happy-dom 无法触发真实按键,仅验证 keymap 扩展加载不抛错 + }); }); diff --git a/libs/codemirror-editor/src/editor.ts b/libs/codemirror-editor/src/editor.ts index fda11f8..c0e06d3 100644 --- a/libs/codemirror-editor/src/editor.ts +++ b/libs/codemirror-editor/src/editor.ts @@ -1,8 +1,8 @@ import { javascript } from '@codemirror/lang-javascript'; import { python } from '@codemirror/lang-python'; import { PostgreSQL, sql } from '@codemirror/lang-sql'; -import { Compartment, EditorState, type Extension } from '@codemirror/state'; -import { EditorView } from '@codemirror/view'; +import { Compartment, EditorState, type Extension, Prec } from '@codemirror/state'; +import { EditorView, keymap } from '@codemirror/view'; import { vim } from '@replit/codemirror-vim'; import { basicSetup } from 'codemirror'; import { type ThemeName, themeExtension } from './themes'; @@ -25,6 +25,8 @@ export class EditorOptions { value?: string; onChange?: (value: string) => void; onReady?: () => void; + /** Ctrl/Cmd + Enter 快捷键回调(SQL 控制台触发执行)。 */ + onRunShortcut?: () => void; } /** @@ -75,6 +77,24 @@ export class CodeMirrorInstance { doc: options.value ?? '', extensions: [ basicSetup, + // Ctrl/Cmd + Enter 运行快捷键:用 Prec.highest 包裹,保证在 vim 的 + // ViewPlugin 按键拦截之前命中(vim 自己也用 Prec.highest,跟随此惯例)。 + ...(options.onRunShortcut + ? [ + Prec.highest( + keymap.of([ + { + key: 'Mod-Enter', + preventDefault: true, + run: () => { + options.onRunShortcut?.(); + return true; + }, + }, + ]), + ), + ] + : []), // vim 必须在 keymap 最前(@replit/codemirror-vim 仓库要求) this.vimCompartment.of(options.vim ? [vim()] : []), this.themeCompartment.of(themeExtension(theme)), diff --git a/src/codemirror_bridge.rs b/src/codemirror_bridge.rs index d6d0963..9562b0a 100644 --- a/src/codemirror_bridge.rs +++ b/src/codemirror_bridge.rs @@ -147,6 +147,10 @@ pub mod wasm { /// 编辑器就绪回调(构造末尾同步触发一次)。 #[wasm_bindgen(method, setter, js_name = onReady)] pub fn set_on_ready(this: &EditorOptions, cb: &Closure); + + /// Ctrl/Cmd + Enter 快捷键回调(SQL 控制台触发执行)。 + #[wasm_bindgen(method, setter, js_name = onRunShortcut)] + pub fn set_on_run_shortcut(this: &EditorOptions, cb: &Closure); } /// 编辑器实例句柄:持有 instance + 所有 Closure,Drop 时销毁实例并释放闭包。 @@ -157,20 +161,24 @@ pub mod wasm { instance: EditorInstance, _on_change: Closure, _on_ready: Closure, + _on_run_shortcut: Closure, } impl EditorHandle { /// 调用方须先把各 closure set 进 EditorOptions,再 create, /// 然后把返回的 instance + 同名 closure 一起传入 new。 + /// `on_run_shortcut` 对应 Ctrl/Cmd+Enter 回调;不用该功能时传 no-op 闭包。 pub fn new( instance: EditorInstance, on_change: Closure, on_ready: Closure, + on_run_shortcut: Closure, ) -> Self { Self { instance, _on_change: on_change, _on_ready: on_ready, + _on_run_shortcut: on_run_shortcut, } } diff --git a/src/components/code_runner/runner.rs b/src/components/code_runner/runner.rs index 58fb7eb..363e40c 100644 --- a/src/components/code_runner/runner.rs +++ b/src/components/code_runner/runner.rs @@ -73,6 +73,9 @@ pub fn CodeRunner( move |v: String| sig.set(v) }); let on_ready = Closure::new(|| {}); + // CodeRunner 不使用 Ctrl+Enter 运行快捷键(它有自己的运行按钮), + // 但 EditorHandle 签名要求该闭包,传 no-op 满足生命周期。 + let on_run_shortcut = Closure::new(|| {}); let resolved = use_resolved_theme(); let theme_name = if resolved() == ResolvedTheme::Dark { @@ -87,12 +90,17 @@ pub fn CodeRunner( opts.set_value(&source_signal.read()); opts.set_on_change(&on_change); opts.set_on_ready(&on_ready); + opts.set_on_run_shortcut(&on_run_shortcut); if let Ok(Some(inst)) = codemirror_bridge::get_module().create(&mount_container_id, &opts) { - let handle = - codemirror_bridge::EditorHandle::new(inst, on_change, on_ready); + let handle = codemirror_bridge::EditorHandle::new( + inst, + on_change, + on_ready, + on_run_shortcut, + ); editor_handle.set(Some(handle)); } });