|
| 1 | +" Vim syntax file |
| 2 | +" Language: Svelte |
| 3 | +" Maintainer: 231tr0n |
| 4 | +" Last Change: 2026 Sep 03 |
| 5 | + |
| 6 | +" Quit if a syntax file was already loaded. |
| 7 | +if exists("b:current_syntax") |
| 8 | + finish |
| 9 | +endif |
| 10 | + |
| 11 | +" Svelte is HTML-ish markup with embedded { } expressions, CSS and |
| 12 | +" JavaScript/TypeScript. Rather than reusing html.vim's keyword-based tag and |
| 13 | +" attribute lists (which would also make <class>/<style> keywords that shadow |
| 14 | +" the class:/style: directives), the markup below is defined generically with |
| 15 | +" matches, like typescriptreact.vim does for TSX. |
| 16 | + |
| 17 | +" Embedded scripting languages: JavaScript for <script>, TypeScript for |
| 18 | +" <script lang="ts"> and CSS for <style>. Defining them here makes the file |
| 19 | +" self-contained; nothing is pulled in from html.vim. |
| 20 | +" TypeScript must be included before JavaScript: the include scripts set |
| 21 | +" b:current_syntax and bail out early when it is already set, so clear it |
| 22 | +" after each include. |
| 23 | +syntax include @svelteTypeScript syntax/typescript.vim |
| 24 | +unlet! b:current_syntax |
| 25 | +syntax include @svelteJavaScript syntax/javascript.vim |
| 26 | +unlet! b:current_syntax |
| 27 | +syntax include @svelteCSS syntax/css.vim |
| 28 | +unlet! b:current_syntax |
| 29 | + |
| 30 | +" Generic markup: tags, end tags, attribute names, values and strings. The |
| 31 | +" tag and attribute name groups are matches, not keywords, so they never |
| 32 | +" shadow the svelteDirective (which is longer and therefore wins). |
| 33 | +syntax region svelteTag start=+<[^/!?]+ end=+>+ fold |
| 34 | + \ contains=svelteTagN,svelteString,svelteArg,svelteValue,svelteComponent |
| 35 | +syntax region svelteEndTag start=+</+ end=+>+ fold |
| 36 | + \ contains=svelteTagN |
| 37 | +syntax match svelteTagN contained +<\s*[-a-zA-Z0-9]\++hs=s+1 |
| 38 | + \ contains=svelteTagName |
| 39 | +syntax match svelteTagN contained +</\s*[-a-zA-Z0-9]\++hs=s+2 |
| 40 | + \ contains=svelteTagName |
| 41 | +syntax match svelteTagName contained "\%#=1\<[a-zA-Z_][a-zA-Z0-9:_-]*\>" |
| 42 | +syntax match svelteArg contained "\%#=1\<[a-zA-Z_][a-zA-Z0-9_-]*\>" |
| 43 | +syntax match svelteArg contained "\%#=1\<data-\h\%(\w\|[-.]\)*\>" |
| 44 | +syntax match svelteArg contained "\%#=1\<aria-\h\%(\w\|[-.]\)*\>" |
| 45 | +syntax match svelteEqual contained "=" |
| 46 | +syntax match svelteValue contained "=[\t ]*[^'" \t>][^ \t>]*"hs=s+1 |
| 47 | +syntax region svelteString contained start=+"+ skip=+\\"+ end=+"+ contains=@sveltePreProc |
| 48 | +syntax region svelteString contained start=+'+ skip=+\\'+ end=+'+ contains=@sveltePreProc |
| 49 | + |
| 50 | +" <script> holds JavaScript, <script lang="ts"> holds TypeScript and |
| 51 | +" <style> holds CSS. They are defined after the generic tag so that they win |
| 52 | +" over svelteTag and the whole region (including its content) is recognized. |
| 53 | +syntax region svelteScriptJS matchgroup=svelteScriptTag |
| 54 | + \ start=+<script\>\_[^>]*>+ keepend |
| 55 | + \ end=+</script\_[^>]*>+me=s-1 |
| 56 | + \ contains=@svelteJavaScript,sveltePreProc,svelteScriptTag,@sveltePreProc |
| 57 | +syntax region svelteScriptTS matchgroup=svelteScriptTag |
| 58 | + \ start=+<script\_[^>]*lang=["']ts["']>+ keepend |
| 59 | + \ end=+</script\_[^>]*>+me=s-1 |
| 60 | + \ contains=@svelteTypeScript,sveltePreProc,svelteScriptTag,@sveltePreProc |
| 61 | +syntax region svelteStyle matchgroup=svelteStyleTag |
| 62 | + \ start=+<style\>\_[^>]*>+ keepend |
| 63 | + \ end=+</style\_[^>]*>+me=s-1 |
| 64 | + \ contains=@svelteCSS,svelteStyleTag,@sveltePreProc |
| 65 | + |
| 66 | +" <script> / <style> opening tags. These also count as svelteTag so that any |
| 67 | +" svelte-specific markup inside the tag is recognized. |
| 68 | +syntax region svelteScriptTag contained start=+<script+ end=+>+ fold |
| 69 | + \ contains=svelteTagN,svelteString,svelteArg,svelteValue |
| 70 | +syntax region svelteStyleTag contained start=+<style+ end=+>+ fold |
| 71 | + \ contains=svelteTagN,svelteString,svelteArg,svelteValue |
| 72 | + |
| 73 | +" HTML-style comment markers inside <script> / <style> and expression blocks. |
| 74 | +syntax match sveltePreProc contained "\%(<!--\|-->\)" |
| 75 | + |
| 76 | +" Svelte special component elements: <svelte:head>, <svelte:window>, ... |
| 77 | +syntax match svelteComponent "\v<svelte:(head|body|window|document|options|element|boundary|component|fragment|self)>" containedin=svelteTagN |
| 78 | + |
| 79 | +" Svelte 5 runes (and store auto-subscriptions) inside <script> blocks. |
| 80 | +" Also matches dot-notation rune variants: $state.raw, $derived.by, |
| 81 | +" $effect.pre, $effect.tracking, $effect.pending, $effect.root, $props.id |
| 82 | +syntax match svelteRune "\$\w\+\%(\.\w\+\)\?" containedin=svelteScriptJS,svelteScriptTS |
| 83 | + |
| 84 | +" Interpolation and expressions: { expr } |
| 85 | +" Only match inside tags or attribute values, not inside <script> blocks, where |
| 86 | +" { are JavaScript block delimiters. |
| 87 | +syntax region svelteMustache matchgroup=svelteBraces |
| 88 | + \ start=+\v\{+ end=+\v\}+ |
| 89 | + \ containedin=svelteTag,svelteValue,svelteString keepend |
| 90 | + |
| 91 | +" Control-flow and special blocks. Covers both legacy Svelte and Svelte 5: |
| 92 | +" {#if} {:else} {/if} {#each} {:then} {:catch} {/each} |
| 93 | +" {#await} {#key} {#snippet} {@html} {@const} {@debug} {@render} |
| 94 | +syntax region svelteBlock matchgroup=svelteKeyword |
| 95 | + \ start=+\v\{[#/:@]\w*+ end=+\v\}+ containedin=svelteTag,svelteValue keepend |
| 96 | + |
| 97 | +" Element directives: |
| 98 | +" on:click bind:value class:active use:foo transition:fade in:/out:/animate: |
| 99 | +" let:item slot: style: (modifiers via "|": on:click|once) |
| 100 | +syntax match svelteDirective |
| 101 | + \ "\v(on|bind|class|use|transition|in|out|animate|let|slot|style):[a-zA-Z0-9_-]+(\|[a-zA-Z0-9_-]+)*" |
| 102 | + \ containedin=svelteTag contained |
| 103 | + |
| 104 | +highlight default link svelteTag Function |
| 105 | +highlight default link svelteEndTag Identifier |
| 106 | +highlight default link svelteTagName Statement |
| 107 | +highlight default link svelteArg Type |
| 108 | +highlight default link svelteEqual NONE |
| 109 | +highlight default link svelteValue String |
| 110 | +highlight default link svelteString String |
| 111 | +highlight default link svelteScriptTag Function |
| 112 | +highlight default link svelteStyleTag Function |
| 113 | +highlight default link sveltePreProc Comment |
| 114 | +highlight default link svelteComponent svelteTagName |
| 115 | +highlight default link svelteRune Statement |
| 116 | +highlight default link svelteBraces Delimiter |
| 117 | +highlight default link svelteKeyword Statement |
| 118 | +highlight default link svelteDirective Type |
| 119 | + |
| 120 | +let b:current_syntax = "svelte" |
0 commit comments