feat(codemirror): 支持 Ctrl/Cmd+Enter 运行快捷键
按钮一直写着「执行 (Ctrl+Enter)」但快捷键从未接线——basicSetup 不含
自定义 keymap。从底层往上打通 Mod-Enter 运行通道:
editor.ts:
- EditorOptions 加 onRunShortcut 可选回调
- constructor 条件注入 keymap.of([{ key: "Mod-Enter", ... }])
- 用 Prec.highest 包裹:@replit/codemirror-vim 的 vimPlugin 在
ViewPlugin 层拦截按键,普通 keymap 会被吞;vim 自己也用
Prec.highest,跟随此惯例保证 Mod-Enter 在 vim 之前命中
- 放在 vimCompartment 之前,满足「vim 必须在 keymap 最前」约束
- Prec 从 @codemirror/state 引入(不在 @codemirror/view)
codemirror_bridge.rs:
- EditorOptions extern 加 set_on_run_shortcut
- EditorHandle 加 _on_run_shortcut 字段保生命周期,new() 签名增加
该参数(Drop 随实例释放)
调用点适配:
- CodeRunner(runner.rs)传 no-op 闭包(不用该功能但签名要满足)
- SQL 控制台的接线在后续 system.rs 改动中完成
This commit is contained in:
parent
1dc02561a3
commit
4239602f3d
@ -64,4 +64,12 @@ describe('CodeMirrorInstance', () => {
|
|||||||
expect(captured).toBe('hello');
|
expect(captured).toBe('hello');
|
||||||
inst.destroy();
|
inst.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('onRunShortcut 注入则构造不抛错', () => {
|
||||||
|
const opts = new EditorOptions();
|
||||||
|
opts.onRunShortcut = () => {};
|
||||||
|
const inst = new CodeMirrorInstance(container, opts);
|
||||||
|
inst.destroy();
|
||||||
|
// happy-dom 无法触发真实按键,仅验证 keymap 扩展加载不抛错
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { python } from '@codemirror/lang-python';
|
import { python } from '@codemirror/lang-python';
|
||||||
import { PostgreSQL, sql } from '@codemirror/lang-sql';
|
import { PostgreSQL, sql } from '@codemirror/lang-sql';
|
||||||
import { Compartment, EditorState, type Extension } from '@codemirror/state';
|
import { Compartment, EditorState, type Extension, Prec } from '@codemirror/state';
|
||||||
import { EditorView } from '@codemirror/view';
|
import { EditorView, keymap } from '@codemirror/view';
|
||||||
import { vim } from '@replit/codemirror-vim';
|
import { vim } from '@replit/codemirror-vim';
|
||||||
import { basicSetup } from 'codemirror';
|
import { basicSetup } from 'codemirror';
|
||||||
import { type ThemeName, themeExtension } from './themes';
|
import { type ThemeName, themeExtension } from './themes';
|
||||||
@ -25,6 +25,8 @@ export class EditorOptions {
|
|||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onReady?: () => void;
|
onReady?: () => void;
|
||||||
|
/** Ctrl/Cmd + Enter 快捷键回调(SQL 控制台触发执行)。 */
|
||||||
|
onRunShortcut?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,6 +77,24 @@ export class CodeMirrorInstance {
|
|||||||
doc: options.value ?? '',
|
doc: options.value ?? '',
|
||||||
extensions: [
|
extensions: [
|
||||||
basicSetup,
|
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 仓库要求)
|
// vim 必须在 keymap 最前(@replit/codemirror-vim 仓库要求)
|
||||||
this.vimCompartment.of(options.vim ? [vim()] : []),
|
this.vimCompartment.of(options.vim ? [vim()] : []),
|
||||||
this.themeCompartment.of(themeExtension(theme)),
|
this.themeCompartment.of(themeExtension(theme)),
|
||||||
|
|||||||
@ -147,6 +147,10 @@ pub mod wasm {
|
|||||||
/// 编辑器就绪回调(构造末尾同步触发一次)。
|
/// 编辑器就绪回调(构造末尾同步触发一次)。
|
||||||
#[wasm_bindgen(method, setter, js_name = onReady)]
|
#[wasm_bindgen(method, setter, js_name = onReady)]
|
||||||
pub fn set_on_ready(this: &EditorOptions, cb: &Closure<dyn FnMut()>);
|
pub fn set_on_ready(this: &EditorOptions, cb: &Closure<dyn FnMut()>);
|
||||||
|
|
||||||
|
/// Ctrl/Cmd + Enter 快捷键回调(SQL 控制台触发执行)。
|
||||||
|
#[wasm_bindgen(method, setter, js_name = onRunShortcut)]
|
||||||
|
pub fn set_on_run_shortcut(this: &EditorOptions, cb: &Closure<dyn FnMut()>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 编辑器实例句柄:持有 instance + 所有 Closure,Drop 时销毁实例并释放闭包。
|
/// 编辑器实例句柄:持有 instance + 所有 Closure,Drop 时销毁实例并释放闭包。
|
||||||
@ -157,20 +161,24 @@ pub mod wasm {
|
|||||||
instance: EditorInstance,
|
instance: EditorInstance,
|
||||||
_on_change: Closure<dyn FnMut(String)>,
|
_on_change: Closure<dyn FnMut(String)>,
|
||||||
_on_ready: Closure<dyn FnMut()>,
|
_on_ready: Closure<dyn FnMut()>,
|
||||||
|
_on_run_shortcut: Closure<dyn FnMut()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorHandle {
|
impl EditorHandle {
|
||||||
/// 调用方须先把各 closure set 进 EditorOptions,再 create,
|
/// 调用方须先把各 closure set 进 EditorOptions,再 create,
|
||||||
/// 然后把返回的 instance + 同名 closure 一起传入 new。
|
/// 然后把返回的 instance + 同名 closure 一起传入 new。
|
||||||
|
/// `on_run_shortcut` 对应 Ctrl/Cmd+Enter 回调;不用该功能时传 no-op 闭包。
|
||||||
pub fn new(
|
pub fn new(
|
||||||
instance: EditorInstance,
|
instance: EditorInstance,
|
||||||
on_change: Closure<dyn FnMut(String)>,
|
on_change: Closure<dyn FnMut(String)>,
|
||||||
on_ready: Closure<dyn FnMut()>,
|
on_ready: Closure<dyn FnMut()>,
|
||||||
|
on_run_shortcut: Closure<dyn FnMut()>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
instance,
|
instance,
|
||||||
_on_change: on_change,
|
_on_change: on_change,
|
||||||
_on_ready: on_ready,
|
_on_ready: on_ready,
|
||||||
|
_on_run_shortcut: on_run_shortcut,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,9 @@ pub fn CodeRunner(
|
|||||||
move |v: String| sig.set(v)
|
move |v: String| sig.set(v)
|
||||||
});
|
});
|
||||||
let on_ready = Closure::new(|| {});
|
let on_ready = Closure::new(|| {});
|
||||||
|
// CodeRunner 不使用 Ctrl+Enter 运行快捷键(它有自己的运行按钮),
|
||||||
|
// 但 EditorHandle 签名要求该闭包,传 no-op 满足生命周期。
|
||||||
|
let on_run_shortcut = Closure::new(|| {});
|
||||||
|
|
||||||
let resolved = use_resolved_theme();
|
let resolved = use_resolved_theme();
|
||||||
let theme_name = if resolved() == ResolvedTheme::Dark {
|
let theme_name = if resolved() == ResolvedTheme::Dark {
|
||||||
@ -87,12 +90,17 @@ pub fn CodeRunner(
|
|||||||
opts.set_value(&source_signal.read());
|
opts.set_value(&source_signal.read());
|
||||||
opts.set_on_change(&on_change);
|
opts.set_on_change(&on_change);
|
||||||
opts.set_on_ready(&on_ready);
|
opts.set_on_ready(&on_ready);
|
||||||
|
opts.set_on_run_shortcut(&on_run_shortcut);
|
||||||
|
|
||||||
if let Ok(Some(inst)) =
|
if let Ok(Some(inst)) =
|
||||||
codemirror_bridge::get_module().create(&mount_container_id, &opts)
|
codemirror_bridge::get_module().create(&mount_container_id, &opts)
|
||||||
{
|
{
|
||||||
let handle =
|
let handle = codemirror_bridge::EditorHandle::new(
|
||||||
codemirror_bridge::EditorHandle::new(inst, on_change, on_ready);
|
inst,
|
||||||
|
on_change,
|
||||||
|
on_ready,
|
||||||
|
on_run_shortcut,
|
||||||
|
);
|
||||||
editor_handle.set(Some(handle));
|
editor_handle.set(Some(handle));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user