diff --git a/libs/codemirror-editor/package.json b/libs/codemirror-editor/package.json index 29886e1..927a0c8 100644 --- a/libs/codemirror-editor/package.json +++ b/libs/codemirror-editor/package.json @@ -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", diff --git a/libs/codemirror-editor/src/editor.ts b/libs/codemirror-editor/src/editor.ts index f36af69..7796e85 100644 --- a/libs/codemirror-editor/src/editor.ts +++ b/libs/codemirror-editor/src/editor.ts @@ -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(); } diff --git a/libs/pnpm-lock.yaml b/libs/pnpm-lock.yaml index 673326e..7111867 100644 --- a/libs/pnpm-lock.yaml +++ b/libs/pnpm-lock.yaml @@ -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)': diff --git a/src/codemirror_bridge.rs b/src/codemirror_bridge.rs index 2b4136d..d6d0963 100644 --- a/src/codemirror_bridge.rs +++ b/src/codemirror_bridge.rs @@ -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)。