{{ msg }}
"; + 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 的 /])' + + ###[ TEMPLATE ]############################################################# + + template-tag-open: + - meta_scope: meta.tag.structure.template.begin.html + - include: tag-attributes + - match: '/>' + scope: punctuation.definition.tag.end.html + pop: 1 + - match: '>' + scope: punctuation.definition.tag.end.html + push: template-content + + template-content: + - meta_include_prototype: false + - meta_content_scope: meta.template.vue + # 闭合 :结束段 + - match: '(?=])' + pop: 1 + # Vue 插值 {{ ... }}:embed JS 表达式 + - match: '\{\{' + scope: punctuation.section.interpolation.begin.html + embed: scope:source.js + escape: '\}\}' + escape_captures: + 0: punctuation.section.interpolation.end.html + # 其余:按 HTML 处理(标签、文本、属性),同时识别 Vue 指令 + - include: scope:text.html.basic + + ###[ SCRIPT ]############################################################### + # 分流在 main 完成:含 lang="ts|tsx|typescript" 的 ])' + + ###[ STYLE ]################################################################ + + style-tag-open: + - meta_scope: meta.tag.structure.style.begin.html + - include: tag-attributes + - match: '/>' + scope: punctuation.definition.tag.end.html + pop: 1 + - match: '>' + scope: punctuation.definition.tag.end.html + embed: scope:source.css + escape: '(?=])' + + ###[ SHARED ]############################################################### + + # 标签属性扫描:Vue 指令 + 普通属性 + 值。供 template/script/style 的 open context 复用。 + tag-attributes: + # Vue 指令(v-if / @click / :prop / #slot)优先,标为属性名 + - match: '{{vue_directive}}\s*(=)?' + scope: entity.other.attribute-name.html + # 普通属性名 = 值 + - match: '\b[A-Za-z-][\w:-]*\s*(=)' + scope: entity.other.attribute-name.html + push: maybe-attr-value + # 布尔属性 + - match: '\b[A-Za-z-][\w:-]*' + scope: entity.other.attribute-name.html + - include: whitespace + + maybe-attr-value: + - include: whitespace + - match: '"' + scope: punctuation.definition.string.begin.html + set: double-quoted-attr-value + - match: "'" + scope: punctuation.definition.string.begin.html + set: single-quoted-attr-value + - match: '(?=\S)' + pop: 1 + + double-quoted-attr-value: + - meta_scope: string.quoted.double.html + - match: '"' + scope: punctuation.definition.string.end.html + pop: 1 + - include: attr-value-escapes + + single-quoted-attr-value: + - meta_scope: string.quoted.single.html + - match: "'" + scope: punctuation.definition.string.end.html + pop: 1 + - include: attr-value-escapes + + attr-value-escapes: + - match: '&[a-zA-Z]+;|\d+;|[0-9A-Fa-f]+;' + scope: constant.character.entity.html + + whitespace: + - match: '\s+'