diff --git a/libs/tiptap-editor/src/__tests__/highlight.test.ts b/libs/tiptap-editor/src/__tests__/highlight.test.ts index 67d2f91..d5504dc 100644 --- a/libs/tiptap-editor/src/__tests__/highlight.test.ts +++ b/libs/tiptap-editor/src/__tests__/highlight.test.ts @@ -66,4 +66,12 @@ describe('lowlight wrapper', () => { // CodeBlockLowlight 源码用 try/catch 兜底,这里只验 wrapper 不改变此行为。 expect(() => lowlight.highlight('totally-unknown-lang', 'code')).toThrow(); }); + + it('vue 作为 xml 别名高亮(不抛 Unknown language)', () => { + // lowlight 依赖的 highlight.js 11 已移除 vue grammar, + // highlight.ts 把 vue 注册为 xml 别名兜底。 + // 验证:vue 代码能高亮(走 xml grammar,输出含 hljs class)且不抛错。 + const result = lowlight.highlight('vue', ''); + expect(JSON.stringify(result)).toContain('hljs-'); + }); }); diff --git a/libs/tiptap-editor/src/highlight.ts b/libs/tiptap-editor/src/highlight.ts index 356c5b0..647eb61 100644 --- a/libs/tiptap-editor/src/highlight.ts +++ b/libs/tiptap-editor/src/highlight.ts @@ -2,6 +2,13 @@ import { common, createLowlight } from 'lowlight'; const base = createLowlight(common); +// Vue SFC 别名:lowlight 依赖的 highlight.js 11 早已移除 vue grammar, +// common/all 均不含 vue。这里把 vue 注册为 xml 的别名,使 ```vue 代码块 +// 在编辑器写作时按 XML/HTML 着色(template 段正常,script/style 段不精确 +// 但不抛 Unknown language)。阅读侧(读者看到的)走 syntect 自包含 Vue +// 语法(syntaxes/Vue.sublime-syntax),三段都有完整高亮,两侧独立互不影响。 +base.registerAlias({ xml: ['vue'] }); + /** * 从完整 fence info string 提取语言名(首个 token,小写化)。 * diff --git a/src/highlight.rs b/src/highlight.rs index 9aed29a..dfe3485 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -80,6 +80,8 @@ pub mod server { ("kotlin", "kt"), ("swift", "swift"), ("golang", "go"), + // Vue SFC:file_extensions 已含 vue,此条兜底大写 ```Vue 等边界。 + ("vue", "vue"), ]; for &(from, to) in aliases { // 别名比较同样不区分大小写,保证 "RUST" 与 "rust" 等价。 @@ -363,4 +365,71 @@ mod tests { assert!(result.contains("string"), "Zig 字符串未被识别: {}", result); assert!(result.contains("numeric"), "Zig 数字未被识别: {}", result); } + + #[test] + fn highlight_code_vue_sfc() { + // Vue SFC 三段(template HTML + script JS + style CSS)都应被识别, + // 不回退到纯文本(text plain)。 + let code = "\ + + + + +"; + let result = highlight_code(code, Some("vue")); + assert!( + !result.contains(r#""#), + "Vue 不应回退到纯文本: {}", + result + ); + assert!( + result.contains("entity name tag"), + "Vue template 标签未被识别: {}", + result + ); + assert!( + result.contains("source js"), + "Vue script 段未嵌入 JS 高亮: {}", + result + ); + assert!( + result.contains("source css"), + "Vue style 段未嵌入 CSS 高亮: {}", + result + ); + assert!( + result.contains("entity other attribute-name"), + "Vue 指令/属性未被识别: {}", + result + ); + } + + #[test] + fn highlight_code_vue_script_lang_ts() { + // "; + let result = highlight_code(code, Some("vue")); + assert!( + result.contains("source ts"), + "Vue lang=ts 应嵌入 TS: {}", + result + ); + } + + #[test] + fn highlight_code_vue_resolves_vue_alias() { + // 别名表 "vue" 与扩展名 "vue" 输出应一致。 + let code = ""; + let by_alias = highlight_code(code, Some("vue")); + let by_upper = highlight_code(code, Some("Vue")); + // 大写标识经别名表 eq_ignore_ascii_case 回退,输出须与小写一致。 + assert_eq!(by_alias, by_upper); + } } diff --git a/syntaxes/Vue.sublime-syntax b/syntaxes/Vue.sublime-syntax new file mode 100644 index 0000000..10f2471 --- /dev/null +++ b/syntaxes/Vue.sublime-syntax @@ -0,0 +1,174 @@ +%YAML 1.2 +--- +# Vue Single-File Component (.vue) 语法高亮。 +# +# 自包含实现:不依赖 `extends: Packages/HTML/...`(syntect 不认 ST 包路径), +# 改用 embed: scope: 引用本项目已有的 HTML/JS/TS/TSX/CSS scope。 +# +# 关键约束(syntect 5.3 实测): +# - embed 规则必须用纯净写法 `match + embed + escape`,不能附加 pop/embed_scope, +# 否则 embed 失效并静默回退纯文本。 +# - escape 用 lookahead `(?=...)` 不消费闭合标签,交给外层 context 处理。 +# +# 分段策略:SFC 的