Improve Vue template token captures for theme consistency#119
Improve Vue template token captures for theme consistency#119ryuhei373 wants to merge 6 commits into
Conversation
The "=" between attribute name and value is currently captured as @operator. This causes themes to color it like a programming operator (e.g., the "+" in arithmetic), which differs from the HTML extension where the same character is captured as @punctuation.delimiter.html. Aligning Vue with HTML produces more consistent rendering across themes that distinguish operators from punctuation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The argument of a directive ("click" in "@click", "value" in
":value", "default" in "v-slot:default") is the attribute or event
name being targeted. It is more accurately treated as an attribute
name than as a constant.
Vue's official TextMate grammar (used by VSCode) scopes the same
token as entity.other.attribute-name, which is the same scope used
for plain HTML attribute names. This change brings tree-sitter Vue
in line with that convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In the Vue grammar, shorthand prefixes (":", "@", "#") and longhand
directive names ("v-bind", "v-on", "v-if", ...) are both surfaced
under the same node alias (directive_name). Previously the
highlights query painted both with @keyword.directive, which
produced a "keyword" color for shorthand prefixes that are
syntactically punctuation.
Use #match? predicates on the node text to split the two:
- single-character shorthand prefixes -> @punctuation.delimiter
- "v-*" longhand names -> @keyword.directive (unchanged)
Vue's official TextMate grammar treats shorthand prefixes as
punctuation as well.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iter
In longhand directive syntax ("v-bind:click", "v-on:keydown",
"v-slot:default", ...), the ":" between the directive name and the
argument is a separator. Previously this anonymous token was not
captured explicitly and inherited @Property from the surrounding
directive_attribute, rendering it as an attribute color.
Vue's official TextMate grammar scopes the same character as
punctuation.separator.key-value.html.vue, treating it as
punctuation. Match that intent by capturing the literal ":" as
@punctuation.delimiter.
In the Vue grammar, ":" only appears as an anonymous token in this
position (the shorthand ":" is a directive_shorthand node, handled
separately), so this rule should not affect other locations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MrSubidubi
left a comment
There was a problem hiding this comment.
Thank you for this, overall looks good! Left some comments.
|
|
||
| (directive_name) @keyword.directive | ||
| ((directive_name) @punctuation.delimiter | ||
| (#match? @punctuation.delimiter "^[:@#]$")) |
There was a problem hiding this comment.
Can we not use any-of here instead?
| "=" @punctuation.delimiter | ||
|
|
||
| ":" @punctuation.delimiter | ||
|
|
There was a problem hiding this comment.
Let's make this a list, please
|
|
||
| (directive_argument) @constant | ||
| ((directive_name) @keyword.directive | ||
| (#match? @keyword.directive "^v-")) |
There was a problem hiding this comment.
Do we intentionally not color some directives anymore? Fine with this, but just wanted to check.
There was a problem hiding this comment.
@MrSubidubi
I didn't intend to stop coloring them entirely. directive_name still resolves to either a v-* token or one of the shorthand prefixes (:, @, or #), and both are still being captured—the former as @keyword.directive and the latter as @punctuation.delimiter.
If you're referring to the shorthands losing their @keyword.directive highlighting, that part is indeed intentional. It aligns with Vue's official TextMate grammar, which scopes them under punctuation.attribute-shorthand.* rather than as keywords. If I've missed your point, please let me know which specific cases you had in mind so I can take another look.
Replace the regex-based #match? predicate with #any-of? for the
shorthand prefix check. The capture matches a fixed finite set of
literals (":", "@", "#"), so #any-of? expresses the intent
directly without going through a regex character class and anchors.
Addresses review feedback on zed-extensions#119.
Combine the two single-literal captures for "=" and ":" into a list
form so the shared @punctuation.delimiter capture name only appears
once. This matches the style already used in the same file for the
bracket tokens ("<", ">", "</", "/>") and makes the intent
"these literals share the same role" explicit.
Addresses review feedback on zed-extensions#119.
Summary
This PR refines several token captures in
languages/vue/highlights.scmso that themes can render Vue templates more consistently with how the HTML extension renders plain HTML, and with how Vue's official TextMate grammar (used by VSCode) scopes the same characters.The motivation comes from a theme distinguishing operator vs. punctuation vs. constant vs. attribute name colors. Today the Vue grammar surfaces several characters under a category that doesn't match their syntactic role:
=between attribute and value@operator@punctuation.delimiter:(e.g.v-bind:click)@property@punctuation.delimiter:,@,#@keyword.directive@punctuation.delimiterdirective_argument(e.g.click,class,default)@constant@attributeLonghand directive names (
v-bind,v-on,v-if, ...) remain@keyword.directive. Each change is its own commit with rationale.Visual Comparison
Reference: VSCode (Vue official TextMate grammar)
For comparison, the equivalent scopes in
vuejs/language-tools'sextensions/vscode/syntaxes/vue.tmLanguage.json(pinned to commit9e97191):=in directive expression (quoted) →punctuation.separator.key-value.html.vue(L1298)=in directive expression (unquoted) →punctuation.separator.key-value.html.vue(L1327):separator (v-bind:click) →punctuation.separator.key-value.html.vue(L1228):(v-bind) →punctuation.attribute-shorthand.bind.html.vue(L1231)@(v-on) →punctuation.attribute-shorthand.event.html.vue(L1234)#(v-slot) →punctuation.attribute-shorthand.slot.html.vue(L1237)click,class) →entity.other.attribute-name.html.vue(L1254)Each shorthand prefix has its own dedicated punctuation scope in the TextMate grammar; since user themes typically style
punctuation.*uniformly, treating them all as@punctuation.delimiterin the tree-sitter highlights captures the same intent.