yggdrasil/bridges/codemirror.rs
1//! CodeMirror 编辑器的 wasm-bindgen 绑定层。
2//!
3//! 封装与 `window.CodeMirrorEditor`(IIFE 暴露的全局对象字面量)的全部交互,
4//! 严格镜像 [`crate::bridges::tiptap`] 的结构:wasm-bindgen extern +
5//! `EditorHandle` 仅在 WASM 前端编译(server 构建无 window)。SQL 补全用的
6//! `SqlSchema`/`SqlTable` 是 server function 返回值 DTO,定义在
7//! [`crate::models::sql_schema`](不属于本文件——避免 API 层反向依赖桥接层)。
8//!
9//! 与 tiptap 一样,`create` 以 js_namespace 绑定为成员调用,调用时才解析全局;
10//! 脚本未加载时返回 Err。
11//!
12//! 用法(Leptos):挂载点用 `NodeRef` 取元素,先用 `use_browser_library(BrowserLibrary::CodeMirror, ..)` 等库就绪,
13//! 再在 `Effect` 内构造 `EditorOptions` 并 `create(&element, &opts)`;`EditorHandle` 含 JS 引用
14//! (非 `Send + Sync`),放进 `StoredValue::new_local`/`RwSignal::new_local`,`on_cleanup` 里 `take`
15//! 出来丢弃即触发 `Drop` → `destroy`。
16
17// ============================================================================
18// 以下全部仅在 WASM 前端编译:wasm-bindgen extern + EditorHandle + 闭包。
19// 放在 #[cfg] 子模块内,避免 server 构建尝试编译引用 JS 对象的 extern。
20// ============================================================================
21#[cfg(target_arch = "wasm32")]
22pub mod wasm {
23 use wasm_bindgen::prelude::*;
24 use wasm_bindgen::JsCast;
25
26 // —— window.CodeMirrorEditor.create ——
27 //
28 // `js_namespace` 把 `create` 绑成 `CodeMirrorEditor.create(...)` 的成员调用,
29 // 全局在调用时才解析:脚本未加载时返回 Err(TypeError),而不是在绑定阶段 panic。
30 #[wasm_bindgen]
31 extern "C" {
32 /// `CodeMirrorEditor.create(container, options)`:在元素上创建实例,替换该元素上的旧实例。
33 /// 构造失败抛异常(被 catch 捕获)。
34 #[wasm_bindgen(js_namespace = CodeMirrorEditor, js_name = create, catch)]
35 pub fn create(
36 container: &web_sys::HtmlElement,
37 options: &EditorOptions,
38 ) -> Result<EditorInstance, JsValue>;
39 }
40
41 // —— 编辑器实例(CodeMirrorInstance)——
42 #[wasm_bindgen]
43 extern "C" {
44 /// `CodeMirrorEditor.create` 返回的编辑器实例对象,承载 CodeMirror EditorView。
45 pub type EditorInstance;
46
47 /// 返回当前文档全文。
48 #[wasm_bindgen(method, js_name = getValue)]
49 pub fn get_value(this: &EditorInstance) -> String;
50
51 /// 替换整个文档内容(dispatch changes,触发 onChange)。
52 #[wasm_bindgen(method, js_name = setValue)]
53 pub fn set_value(this: &EditorInstance, s: &str);
54
55 /// 热切换主题(Compartment.reconfigure,不重建实例)。
56 #[wasm_bindgen(method, js_name = setTheme)]
57 pub fn set_theme(this: &EditorInstance, theme: &str);
58
59 /// 热切换 Vim 模式(Compartment.reconfigure,不重建实例)。
60 #[wasm_bindgen(method, js_name = setVim)]
61 pub fn set_vim(this: &EditorInstance, v: bool);
62
63 /// 热切换语言(go/rust/python/node/javascript/sql,Compartment.reconfigure)。
64 /// 由 CodeRunner 组件在挂载时按 data-lang 调用。
65 #[wasm_bindgen(method, js_name = setLanguage)]
66 pub fn set_language(this: &EditorInstance, lang: &str);
67
68 /// 更新 SQL 补全 schema(Compartment.reconfigure)。
69 /// 参数为 serde_wasm_bindgen::to_value 序列化后的 JsValue
70 ///(SqlSchema 是 serde 类型,非 wasm-bindgen 类型,故不能直接传 &SqlSchema)。
71 #[wasm_bindgen(method, js_name = setSchema)]
72 pub fn set_schema(this: &EditorInstance, schema: &wasm_bindgen::JsValue);
73
74 /// 让编辑器获取焦点。
75 #[wasm_bindgen(method)]
76 pub fn focus(this: &EditorInstance);
77
78 /// 销毁编辑器,释放 JS 侧资源。
79 #[wasm_bindgen(method)]
80 pub fn destroy(this: &EditorInstance);
81 }
82
83 // —— EditorOptions:用 builder 模式(setter)构造 JS 对象 ——
84 #[wasm_bindgen]
85 extern "C" {
86 /// 传给 `CodeMirrorEditor.create` 的配置对象,对应 JS 侧的 EditorOptions。
87 /// 用 `default()` 创建空对象后通过 setter 设置字段。
88 pub type EditorOptions;
89
90 /// 语言(默认 'sql')。
91 #[wasm_bindgen(method, setter, js_name = language)]
92 pub fn set_language(this: &EditorOptions, v: &str);
93
94 /// 主题:'light' 或 'dark'(配色取自 `CODE_PALETTES`)。
95 #[wasm_bindgen(method, setter, js_name = theme)]
96 pub fn set_theme(this: &EditorOptions, v: &str);
97
98 /// 是否启用 Vim keymap。
99 #[wasm_bindgen(method, setter, js_name = vim)]
100 pub fn set_vim(this: &EditorOptions, v: bool);
101
102 /// SQL 补全 schema(表/列数据)。v 为 serde_wasm_bindgen::to_value 序列化结果。
103 #[wasm_bindgen(method, setter, js_name = schema)]
104 pub fn set_schema(this: &EditorOptions, v: &wasm_bindgen::JsValue);
105
106 /// 初始文档内容。
107 #[wasm_bindgen(method, setter, js_name = value)]
108 pub fn set_value(this: &EditorOptions, v: &str);
109
110 /// 文档变更回调(参数为最新全文)。
111 #[wasm_bindgen(method, setter, js_name = onChange)]
112 pub fn set_on_change(this: &EditorOptions, cb: &Closure<dyn FnMut(String)>);
113
114 /// 编辑器就绪回调(构造末尾同步触发一次)。
115 #[wasm_bindgen(method, setter, js_name = onReady)]
116 pub fn set_on_ready(this: &EditorOptions, cb: &Closure<dyn FnMut()>);
117
118 /// Ctrl/Cmd + Enter 快捷键回调(SQL 控制台、可运行代码块触发执行)。
119 #[wasm_bindgen(method, setter, js_name = onRunShortcut)]
120 pub fn set_on_run_shortcut(this: &EditorOptions, cb: &Closure<dyn FnMut()>);
121
122 /// Vim 模式变化回调(normal / insert / visual / replace)。
123 #[wasm_bindgen(method, setter, js_name = onVimModeChange)]
124 pub fn set_on_vim_mode_change(this: &EditorOptions, cb: &Closure<dyn FnMut(String)>);
125 }
126
127 impl Default for EditorOptions {
128 /// 空 JS 对象;字段只经上面的 setter 写入,JS 端按同名 interface 读取。
129 fn default() -> Self {
130 js_sys::Object::new().unchecked_into()
131 }
132 }
133
134 /// 编辑器实例句柄:持有 instance + 所有 Closure,Drop 时销毁实例并释放闭包。
135 ///
136 /// 闭包字段 `_` 前缀表示仅用于保持生命周期——它们被注入 JS 后,JS 侧持有
137 /// 函数引用;只要 [`EditorHandle`] 存活,闭包就不会被回收。Drop 时随结构释放。
138 pub struct EditorHandle {
139 instance: EditorInstance,
140 _on_change: Closure<dyn FnMut(String)>,
141 _on_ready: Closure<dyn FnMut()>,
142 _on_run_shortcut: Closure<dyn FnMut()>,
143 _on_vim_mode_change: Option<Closure<dyn FnMut(String)>>,
144 }
145
146 impl EditorHandle {
147 /// 调用方须先把各 closure set 进 EditorOptions,再 create,
148 /// 然后把返回的 instance + 同名 closure 一起传入 new。
149 /// `on_run_shortcut` 对应 Ctrl/Cmd+Enter 回调;不用该功能时传 no-op 闭包。
150 pub fn new(
151 instance: EditorInstance,
152 on_change: Closure<dyn FnMut(String)>,
153 on_ready: Closure<dyn FnMut()>,
154 on_run_shortcut: Closure<dyn FnMut()>,
155 ) -> Self {
156 Self {
157 instance,
158 _on_change: on_change,
159 _on_ready: on_ready,
160 _on_run_shortcut: on_run_shortcut,
161 _on_vim_mode_change: None,
162 }
163 }
164
165 /// 一并持有已注入 EditorOptions 的 Vim 模式回调。
166 pub fn with_vim_mode(mut self, on_vim_mode_change: Closure<dyn FnMut(String)>) -> Self {
167 self._on_vim_mode_change = Some(on_vim_mode_change);
168 self
169 }
170
171 /// 借用底层实例,供宿主调 getValue/setTheme/setSchema 等。
172 pub fn instance(&self) -> &EditorInstance {
173 &self.instance
174 }
175 }
176
177 impl Drop for EditorHandle {
178 fn drop(&mut self) {
179 // 销毁 JS 侧编辑器;随后 _on_change/_on_ready 字段按声明顺序释放,
180 // 释放 wasm-bindgen 函数表槽位。
181 self.instance.destroy();
182 }
183 }
184}
185
186#[cfg(target_arch = "wasm32")]
187pub use wasm::*;