refactor(editor): support dynamic languages python/javascript in codemirror
- editor.ts: 新增 languageCompartment + buildLanguageExtension(lang, schema) python → lang-python, node/javascript → lang-javascript, sql/缺省 → lang-sql 新增 setLanguage(lang) 热切换; setSchema 保留当前语言不被强制切回 sql - package.json: +@codemirror/lang-python +lang-javascript - codemirror_bridge.rs: EditorInstance 新增 set_language wasm-bindgen extern (产物 public/codemirror/ 被 gitignore,本地已重建)
This commit is contained in:
parent
a77a2e3502
commit
576803302d
@ -14,6 +14,8 @@
|
||||
"@catppuccin/codemirror": "^1.0.3",
|
||||
"@codemirror/autocomplete": "^6.18.0",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/lang-python": "^6.2.0",
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/state": "^6.5.0",
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { PostgreSQL, sql } from '@codemirror/lang-sql';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { Compartment, EditorState, type Extension } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { basicSetup } from 'codemirror';
|
||||
@ -25,20 +27,48 @@ export class EditorOptions {
|
||||
onReady?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把语言标识映射成 CodeMirror 语言 Extension。
|
||||
* - `python` → @codemirror/lang-python
|
||||
* - `node` / `javascript` / `js` → @codemirror/lang-javascript
|
||||
* - `sql` / 缺省 → @codemirror/lang-sql(PostgreSQL 方言 + schema 补全)
|
||||
*
|
||||
* SQL 分支使用 schema 补全;非 SQL 语言忽略 schema(补全仅对 SQL 有意义)。
|
||||
*/
|
||||
function buildLanguageExtension(lang: string, schema: SqlSchema): Extension {
|
||||
const normalized = (lang ?? '').toLowerCase();
|
||||
if (normalized === 'python') {
|
||||
return python();
|
||||
}
|
||||
if (normalized === 'node' || normalized === 'javascript' || normalized === 'js') {
|
||||
return javascript();
|
||||
}
|
||||
// sql / 缺省:保留原有 PostgreSQL + schema 补全行为
|
||||
return sql({
|
||||
dialect: PostgreSQL,
|
||||
schema: schemaToCompletions(schema),
|
||||
upperCaseKeywords: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CodeMirror 实例封装。
|
||||
* create() 时用三个 Compartment 注入 theme/schema/vim,
|
||||
* create() 时用四个 Compartment 注入 theme/language/schema/vim,
|
||||
* 支持后续热切换(reconfigure)而不重建实例——保留 Vim 状态、光标、撤销栈。
|
||||
*/
|
||||
export class CodeMirrorInstance {
|
||||
private view: EditorView;
|
||||
private themeCompartment = new Compartment();
|
||||
private schemaCompartment = new Compartment();
|
||||
private languageCompartment = new Compartment();
|
||||
private vimCompartment = new Compartment();
|
||||
private language: string;
|
||||
private schema: SqlSchema;
|
||||
|
||||
constructor(container: HTMLElement, options: EditorOptions) {
|
||||
const theme: ThemeName = options.theme ?? 'light';
|
||||
const schema = options.schema ?? { tables: [] };
|
||||
this.schema = schema;
|
||||
this.language = options.language ?? 'sql';
|
||||
|
||||
this.view = new EditorView({
|
||||
state: EditorState.create({
|
||||
@ -48,12 +78,8 @@ export class CodeMirrorInstance {
|
||||
// vim 必须在 keymap 最前(@replit/codemirror-vim 仓库要求)
|
||||
this.vimCompartment.of(options.vim ? [vim()] : []),
|
||||
this.themeCompartment.of(themeExtension(theme)),
|
||||
this.schemaCompartment.of(
|
||||
sql({
|
||||
dialect: PostgreSQL,
|
||||
schema: schemaToCompletions(schema),
|
||||
upperCaseKeywords: true,
|
||||
}),
|
||||
this.languageCompartment.of(
|
||||
buildLanguageExtension(this.language, schema),
|
||||
),
|
||||
EditorView.updateListener.of((v) => {
|
||||
if (v.docChanged) {
|
||||
@ -85,19 +111,30 @@ export class CodeMirrorInstance {
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新 SQL 补全 schema(Compartment.reconfigure)。 */
|
||||
setSchema(schema: SqlSchema): void {
|
||||
/** 热切换语言(python/node/javascript/sql),不重建实例(Compartment.reconfigure)。 */
|
||||
setLanguage(lang: string): void {
|
||||
this.language = lang;
|
||||
this.view.dispatch({
|
||||
effects: this.schemaCompartment.reconfigure(
|
||||
sql({
|
||||
dialect: PostgreSQL,
|
||||
schema: schemaToCompletions(schema),
|
||||
upperCaseKeywords: true,
|
||||
}),
|
||||
effects: this.languageCompartment.reconfigure(
|
||||
buildLanguageExtension(lang, this.schema),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新 SQL 补全 schema(Compartment.reconfigure)。
|
||||
* 当前语言为 SQL 时立即生效;非 SQL 语言会缓存 schema,切回 SQL 时生效。 */
|
||||
setSchema(schema: SqlSchema): void {
|
||||
this.schema = schema;
|
||||
// 仅当当前是 SQL 语言时才重配 extension(其它语言不消费 schema)。
|
||||
if ((this.language ?? '').toLowerCase() === 'sql' || !this.language) {
|
||||
this.view.dispatch({
|
||||
effects: this.languageCompartment.reconfigure(
|
||||
buildLanguageExtension('sql', schema),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.view.focus();
|
||||
}
|
||||
|
||||
48
libs/pnpm-lock.yaml
generated
48
libs/pnpm-lock.yaml
generated
@ -35,6 +35,12 @@ importers:
|
||||
'@codemirror/commands':
|
||||
specifier: ^6.8.0
|
||||
version: 6.10.4
|
||||
'@codemirror/lang-javascript':
|
||||
specifier: ^6.2.3
|
||||
version: 6.2.5
|
||||
'@codemirror/lang-python':
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.1
|
||||
'@codemirror/lang-sql':
|
||||
specifier: ^6.10.0
|
||||
version: 6.10.0
|
||||
@ -162,6 +168,12 @@ packages:
|
||||
'@codemirror/commands@6.10.4':
|
||||
resolution: {integrity: sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==}
|
||||
|
||||
'@codemirror/lang-javascript@6.2.5':
|
||||
resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
|
||||
|
||||
'@codemirror/lang-python@6.2.1':
|
||||
resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
|
||||
|
||||
'@codemirror/lang-sql@6.10.0':
|
||||
resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==}
|
||||
|
||||
@ -207,9 +219,15 @@ packages:
|
||||
'@lezer/highlight@1.2.3':
|
||||
resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
|
||||
|
||||
'@lezer/javascript@1.5.4':
|
||||
resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
|
||||
|
||||
'@lezer/lr@1.4.10':
|
||||
resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==}
|
||||
|
||||
'@lezer/python@1.1.19':
|
||||
resolution: {integrity: sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.3':
|
||||
resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==}
|
||||
|
||||
@ -970,6 +988,24 @@ snapshots:
|
||||
'@codemirror/view': 6.43.4
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@codemirror/lang-javascript@6.2.5':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.3
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/lint': 6.9.7
|
||||
'@codemirror/state': 6.7.0
|
||||
'@codemirror/view': 6.43.4
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/javascript': 1.5.4
|
||||
|
||||
'@codemirror/lang-python@6.2.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.3
|
||||
'@codemirror/language': 6.12.4
|
||||
'@codemirror/state': 6.7.0
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/python': 1.1.19
|
||||
|
||||
'@codemirror/lang-sql@6.10.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.3
|
||||
@ -1046,10 +1082,22 @@ snapshots:
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@lezer/javascript@1.5.4':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
||||
'@lezer/lr@1.4.10':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.2
|
||||
|
||||
'@lezer/python@1.1.19':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
||||
'@marijn/find-cluster-break@1.0.3': {}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
|
||||
|
||||
@ -89,6 +89,11 @@ pub mod wasm {
|
||||
#[wasm_bindgen(method, js_name = setTheme)]
|
||||
pub fn set_theme(this: &EditorInstance, theme: &str);
|
||||
|
||||
/// 热切换语言(python/node/javascript/sql,Compartment.reconfigure)。
|
||||
/// 由 CodeRunner 组件在挂载时按 data-lang 调用。
|
||||
#[wasm_bindgen(method, js_name = setLanguage)]
|
||||
pub fn set_language(this: &EditorInstance, lang: &str);
|
||||
|
||||
/// 更新 SQL 补全 schema(Compartment.reconfigure)。
|
||||
/// 参数为 serde_wasm_bindgen::to_value 序列化后的 JsValue
|
||||
///(SqlSchema 是 serde 类型,非 wasm-bindgen 类型,故不能直接传 &SqlSchema)。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user