xfy 156b51d697
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
feat(highlight): 文章页代码块支持 Vue SFC 语法高亮
阅读侧(syntect)此前对 ```vue 回退纯文本:syntect 默认集无 Vue
语法、syntaxes/ 无定义、别名表无 vue 条目。

官方 Vue Component.sublime-syntax 因 extends: Packages/HTML/...
用 Sublime Text 包路径,syntect 的 add_from_folder 解析不了(实测
panic),无法直接采用。改为自包含实现:

- syntaxes/Vue.sublime-syntax:自包含 Vue SFC 语法。<template>/
  <script>/<style> 三段在 main 入口按标签名分流(syntect context
  切换后无法回看标签名)。script 按 lang 属性经前瞻分流
  ts/tsx→source.ts、其余→source.js;style 统一 embed source.css
  (scss/less 无对应 scope,因 CSS 是超集兼容);template 内 {{ }}
  插值 embed source.js,其余走内置 HTML 语法。
- 关键约束(syntect 5.3 实测):embed 规则必须用纯净 match+embed+
  escape 写法,附加 pop/embed_scope 会破坏 embed 使其静默回退纯文本;
  escape 用 lookahead 不消费闭合标签。

编辑器侧(lowlight@3.3.0)与阅读侧是独立链路。lowlight 依赖的
highlight.js 11 早已移除 vue grammar,common/all 均不含;
highlightjs-vue 包停在 2019 年 1.0.0 且用旧版 hljsDefineVue 全局
API,与 lowlight@3 的 ESM register 不兼容。故编辑器侧用 registerAlias
把 vue 注册为 xml 别名兜底(template 段按 XML 着色,script/style 段
不精确但不抛 Unknown language);读者看到的阅读侧仍是完整高亮。

src/highlight.rs 别名表加 vue 条目(兜底大写 ```Vue 边界),新增 3 个
Vue 高亮单测(SFC 三段 / lang=ts 走 TS / 大小写别名)。
2026-07-17 11:42:55 +08:00

54 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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小写化
*
* 与后端 parse_fence_infosrc/api/code_runner/languages.rs:85对齐
* info string 形如 `python runnable {"timeout_secs":10}`,语言是首个空白分隔的 token
* 再 to_lowercaselowlight 注册名均为小写,大写会导致 Unknown language 抛错)。
*/
export function extractLang(info: string): string {
const first = info.trim().split(/\s+/)[0];
return first ? first.toLowerCase() : '';
}
/**
* 从完整 fence info string 提取 overrides JSON 字符串。
*
* 与后端 parse_fence_infosrc/api/code_runner/languages.rs:93-97对齐
* info string 形如 `python runnable {"timeout_secs":10}`,提取以 `{` 开头的 token。
* 无 overrides 时返回空串Rust 侧空串视为 None
*/
export function extractOverridesJson(info: string): string {
const token = info
.trim()
.split(/\s+/)
.find((t) => t.startsWith('{'));
return token ?? '';
}
/**
* lowlight 实例,包装 highlight 方法以处理完整 info string。
*
* CodeBlockLowlight 取 `block.node.attrs.language`(如 `python runnable {...}`
* 直接调 `lowlight.highlight(language, code)`,但 lowlight 只认 `python` 这个 token。
* wrapper 先 extractLang 提取首 token再高亮使 runnable 块也能按正确语言着色。
*
* 普通 code blocklanguage='python'也兼容extractLang('python') → 'python'。
*/
export const lowlight = {
...base,
highlight(language: string, value: string) {
return base.highlight(extractLang(language), value);
},
};