From 41bf2c53d4001ec9333cb5e548a9152d070af642 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 5 Sep 2026 16:58:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E4=BF=9D=E7=95=99=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E4=BB=A3=E7=A0=81=E8=AF=AD=E8=A8=80=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=B8=8E=E5=85=BC=E5=AE=B9=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/VisualMarkdownEditor.spec.ts | 6 ++++++ .../features/editor/VisualMarkdownEditor.vue | 2 +- .../features/editor/shikiCodeMirror.spec.ts | 14 +++++++++++++ .../src/features/editor/shikiCodeMirror.ts | 20 +++++++++++++++++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/editor/VisualMarkdownEditor.spec.ts b/frontend/src/features/editor/VisualMarkdownEditor.spec.ts index a89bdbc..5d83950 100644 --- a/frontend/src/features/editor/VisualMarkdownEditor.spec.ts +++ b/frontend/src/features/editor/VisualMarkdownEditor.spec.ts @@ -56,6 +56,12 @@ describe('VisualMarkdownEditor formatting toolbars', () => { const config = editor.action(ctx => ctx.get(codeBlockConfig.key)) const matching = config.languages.filter(item => item.alias.includes('python')) expect(matching).toHaveLength(1) + for (const name of ['Java', 'Go', 'Rust']) { + const language = config.languages.find(item => item.name === name) + expect(language, `${name} remains available`).toBeDefined() + const support = await language!.load() + expect(support.language.parser.parse('class Example {}').length).toBe(16) + } const cm = new CodeMirror({ doc: 'print("Hello")', extensions: [...config.extensions, await matching[0]!.load()] }) try { const string = [...cm.dom.querySelectorAll('.shiki-token')].find(el => el.textContent?.includes('Hello')) diff --git a/frontend/src/features/editor/VisualMarkdownEditor.vue b/frontend/src/features/editor/VisualMarkdownEditor.vue index 7acad31..8cc0951 100644 --- a/frontend/src/features/editor/VisualMarkdownEditor.vue +++ b/frontend/src/features/editor/VisualMarkdownEditor.vue @@ -179,7 +179,7 @@ onMounted(async () => { // Replace both AFTER feature configuration to avoid default grammar collisions. crepe.editor.config(ctx => ctx.update(codeBlockConfig.key, config => ({ ...config, - languages: shikiLanguages(themeStore.resolvedCodeBlockTheme), + languages: shikiLanguages(themeStore.resolvedCodeBlockTheme, config.languages), extensions: [basicSetup, keymap.of([indentWithTab]), shikiEditorTheme(themeStore.resolvedCodeBlockTheme)], }))) crepe.editor.use(fontSizeMarkdownPlugin) diff --git a/frontend/src/features/editor/shikiCodeMirror.spec.ts b/frontend/src/features/editor/shikiCodeMirror.spec.ts index 2b0d114..59017b0 100644 --- a/frontend/src/features/editor/shikiCodeMirror.spec.ts +++ b/frontend/src/features/editor/shikiCodeMirror.spec.ts @@ -1,6 +1,7 @@ // @vitest-environment happy-dom import { afterEach, expect, it } from 'vitest' import { Compartment } from '@codemirror/state' +import { LanguageDescription } from '@codemirror/language' import { EditorView } from '@codemirror/view' import { shikiLanguage, shikiLanguages } from './shikiCodeMirror' import { getCodeTokenizer } from '@/utils/markdown' @@ -41,3 +42,16 @@ it('offers fenced-code aliases and retains the LaTeX selector', () => { expect(languages.find(item => item.name === 'Python')?.alias).toContain('py') expect(languages.find(item => item.name === 'LaTeX')?.alias).toContain('latex') }) + +it('preserves original loaders and metadata while replacing supported languages', async () => { + const originalPython = LanguageDescription.of({ name: 'Python', alias: ['py', 'custom-python'], extensions: ['py'], filename: /^SConstruct$/, load: () => shikiLanguage('text', 'github-light') }) + const originalRust = LanguageDescription.of({ name: 'Rust', alias: ['rs'], extensions: ['rs'], load: () => shikiLanguage('text', 'github-light') }) + const languages = shikiLanguages('github-dark', [originalPython, originalRust]) + expect(languages.find(item => item.name === 'Rust')).toBe(originalRust) + const python = languages.find(item => item.name === 'Python')! + expect(languages.filter(item => item.alias.includes('python'))).toHaveLength(1) + expect(python.alias).toContain('custom-python') + expect(python.extensions).toEqual(['py']) + expect(python.filename).toBe(originalPython.filename) + expect(await python.load()).not.toBe(await originalPython.load()) +}) diff --git a/frontend/src/features/editor/shikiCodeMirror.ts b/frontend/src/features/editor/shikiCodeMirror.ts index 02e1081..bd56212 100644 --- a/frontend/src/features/editor/shikiCodeMirror.ts +++ b/frontend/src/features/editor/shikiCodeMirror.ts @@ -39,8 +39,8 @@ export async function shikiLanguage(language: string, theme: CodeTheme): Promise return new LanguageSupport(parser, highlights) } -export function shikiLanguages(theme: CodeTheme): LanguageDescription[] { - return [ +export function shikiLanguages(theme: CodeTheme, originalLanguages: readonly LanguageDescription[] = []): LanguageDescription[] { + const overrides = [ { name: 'C', alias: ['c'] }, { name: 'C++', alias: ['cpp', 'c++'] }, { name: 'Python', alias: ['python', 'py'] }, @@ -57,6 +57,22 @@ export function shikiLanguages(theme: CodeTheme): LanguageDescription[] { ].map(({ name, alias }) => LanguageDescription.of({ name, alias, load: () => shikiLanguage(alias[0]!, theme), })) + // Keep Crepe's full registry and lazy loaders for languages without Shiki grammars. + const remaining = new Map(overrides.map(language => [language.name.toLowerCase(), language])) + const languages = originalLanguages.map(original => { + const key = original.name.toLowerCase() + const replacement = remaining.get(key) + if (!replacement) return original + remaining.delete(key) + return LanguageDescription.of({ + name: original.name, + alias: [...new Set([...original.alias, ...replacement.alias])], + extensions: original.extensions, + filename: original.filename, + load: () => replacement.load(), + }) + }) + return [...languages, ...remaining.values()] } export function shikiEditorTheme(theme: CodeTheme) {