svelte: Migrate to `tree-sitter-grammars/tree-sitter-svelte` (#17529)

Albert Marashi and Marshall Bowers created

> [!NOTE]
> The https://github.com/tree-sitter-grammars/tree-sitter-svelte
repository seems to be more well maintained, with higher quality code,
and as per https://github.com/zed-extensions/svelte/issues/1 it was
suggested that we swap to this repository for Svelte grammars

- Closes https://github.com/zed-industries/zed/issues/17310
- Closes https://github.com/zed-industries/zed/issues/10893
- Closes https://github.com/zed-industries/zed/issues/12833
- Closes https://github.com/zed-extensions/svelte/issues/1
- Closes https://github.com/zed-industries/zed/issues/14943
- Closes https://github.com/zed-extensions/svelte/issues/2

- Added: buffer/file symbol outlines for `.svelte` (`outlines.scm`)
- Improved: Attribute directives & modifiers in `.svelte` files can be
styled independently.
- Fixed: issue where svelte expression inside quotes failed parsing
- Improved: Svelte components in Markup are styled differently from
tags.
- Added: Support for Svelte 5 syntax (`{#snippet children()}`, `{@render
foo()`)
- Change: Svelte now using
[tree-sitter-grammars/tree-sitter-svelte](https://github.com/tree-sitter-grammars/tree-sitter-svelte)
for language highlighting
- Added: Support for typescript syntax in svelte expressions


![image](https://github.com/user-attachments/assets/49d199ee-7550-49a7-912d-070cf691b029)

![image](https://github.com/user-attachments/assets/848ac5b6-62da-4c42-8e24-b7023504f8af)

Release Notes:

- N/A

---

**tree-sitter-grammar things to improve**
- [ ] snippet functions aren't being treated as JS code
- [ ] we should be able to detect @component comments and treat them as
markdown
- [x] `foo:bar` style/class/prop directives
- [x] `--foo="..."` var fields
- [ ] snippet/if blocks's children may need to be indented a little
further

Will implement some of the rest of these in a separate PR

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>

Change summary

docs/src/languages/svelte.md                      |  23 ++
extensions/svelte/extension.toml                  |   4 
extensions/svelte/languages/svelte/brackets.scm   |   7 
extensions/svelte/languages/svelte/config.toml    |  12 
extensions/svelte/languages/svelte/highlights.scm | 121 ++++++++++---
extensions/svelte/languages/svelte/injections.scm | 142 +++++++++-------
extensions/svelte/languages/svelte/outline.scm    |  69 ++++++++
7 files changed, 268 insertions(+), 110 deletions(-)

Detailed changes

docs/src/languages/svelte.md 🔗

@@ -2,16 +2,29 @@
 
 Svelte support is available through the [Svelte extension](https://github.com/zed-industries/zed/tree/main/extensions/svelte).
 
-- Tree Sitter: [Himujjal/tree-sitter-svelte](https://github.com/Himujjal/tree-sitter-svelte)
+- Tree Sitter: [tree-sitter-grammars/tree-sitter-svelte](https://github.com/tree-sitter-grammars/tree-sitter-svelte)
 - Language Server: [sveltejs/language-tools](https://github.com/sveltejs/language-tools)
 
-<!--
-TBD: Rewrite Svelte docs so it doesn't begin with a json block assuming you know what inlayHints are.
--->
+## Extra theme styling configuration
+
+You can modify how certain styles such as directives and modifiers appear in attributes:
+
+```json
+"syntax": {
+  // Styling for directives (e.g., `class:foo` or `on:click`) (the `on` or `class` part of the attribute).
+  "attribute.function": {
+    "color": "#ff0000"
+  },
+  // Styling for modifiers at the end of attributes, e.g. `on:<click|preventDefault|stopPropagation>`
+  "attribute.special": {
+    "color": "#00ff00"
+  }
+}
+```
 
 ## Inlay Hints
 
-Zed sets the following initialization options for inlay Hints:
+Zed sets the following initialization options for inlay hints:
 
 ```json
 "inlayHints": {

extensions/svelte/extension.toml 🔗

@@ -11,5 +11,5 @@ name = "Svelte Language Server"
 language = "Svelte"
 
 [grammars.svelte]
-repository = "https://github.com/Himujjal/tree-sitter-svelte"
-commit = "b08d070e303d2a385d6d0ab3add500f8fa514443"
+repository = "https://github.com/tree-sitter-grammars/tree-sitter-svelte"
+commit = "3f06f705410683adb17d146b5eca28c62fe81ba6"

extensions/svelte/languages/svelte/config.toml 🔗

@@ -2,16 +2,16 @@ name = "Svelte"
 grammar = "svelte"
 path_suffixes = ["svelte"]
 block_comment = ["<!-- ", " -->"]
-autoclose_before = ";:.,=}])>"
+autoclose_before = ":\"'}]>"
 brackets = [
     { start = "{", end = "}", close = true, newline = true },
+    { start = "<", end = ">", close = true, newline = true, not_in = ["string"] },
     { start = "[", end = "]", close = true, newline = true },
     { start = "(", end = ")", close = true, newline = true },
-    { start = "<", end = ">", close = false, newline = true, not_in = ["string", "comment"] },
-    { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
-    { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
-    { start = "`", end = "`", close = true, newline = false, not_in = ["string"] },
-    { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+    { start = "!--", end = " --", close = true, newline = true },
+    { start = "\"", end = "\"", close = true, newline = true, not_in = ["string"] },
+    { start = "'", end = "'", close = true, newline = true, not_in = ["string"] },
+    { start = "`", end = "`", close = true, newline = true, not_in = ["string"] },
 ]
 scope_opt_in_language_servers = ["tailwindcss-language-server"]
 prettier_parser_name = "svelte"

extensions/svelte/languages/svelte/highlights.scm 🔗

@@ -1,50 +1,107 @@
-; Special identifiers
-;--------------------
 
-; Treat capitalized tag names as constructors and types
-((tag_name) @type
- (#match? @type "^[A-Z]"))
+; comments
+(comment) @comment
 
-; Regular (lowercase) tag names
-((tag_name) @tag
- (#match? @tag "^[a-z]"))
+; property attribute
+(attribute_directive) @attribute.function
+(attribute_identifier) @attribute
+(attribute_modifier) @attribute.special
 
-; TODO:
-(attribute_name) @property
-(erroneous_end_tag_name) @keyword
-(comment) @comment
+; Style component attributes as @property
+(start_tag
+    (
+        (tag_name) @_tag_name
+        (#match? @_tag_name "^[A-Z]")
+    )
+    (attribute
+        (attribute_name
+            (attribute_identifier) @tag.property
+        )
+    )
+)
 
-[
-  (attribute_value)
-  (quoted_attribute_value)
-] @string
+(self_closing_tag
+    (
+        (tag_name) @_tag_name
+        (#match? @_tag_name "^[A-Z]")
+    )
+    (attribute
+        (attribute_name
+            (attribute_identifier) @tag.property
+        )
+    )
+)
 
-[
-  (text)
-  (raw_text_expr)
-  (raw_text_each)
-] @none
+
+; style elements starting with lowercase letters as tags
+(
+    (tag_name) @tag
+    (#match? @tag "^[a-z]")
+)
+
+; style elements starting with uppercase letters as components (types)
+; Also valid might be to treat them as constructors
+(
+    (tag_name) @tag @tag.component.type.constructor
+    (#match? @tag "^[A-Z]")
+)
 
 [
-  (special_block_keyword)
-  (then)
-  (as)
-] @keyword
+  "<"
+  ">"
+  "</"
+  "/>"
+] @tag.punctuation.bracket
+
 
 [
   "{"
   "}"
 ] @punctuation.bracket
 
-"=" @operator
+[
+    "|"
+] @punctuation.delimiter
+
 
 [
-  "<"
-  ">"
-  "</"
-  "/>"
+  "@"
   "#"
   ":"
   "/"
-  "@"
-] @tag.delimiter
+] @tag.punctuation.special
+
+"=" @operator
+
+
+; Treating (if, each, ...) as a keyword inside of blocks
+; like {#if ...} or {#each ...}
+(block_start_tag
+    tag: _ @tag.keyword
+)
+
+(block_tag
+    tag: _ @tag.keyword
+)
+
+(block_end_tag
+    tag: _ @tag.keyword
+)
+
+(expression_tag
+    tag: _ @tag.keyword
+)
+
+; Style quoted string attribute values
+(quoted_attribute_value) @string
+
+
+; Highlight the `as` keyword in each blocks
+(each_start
+    ("as") @tag.keyword
+)
+
+
+; Highlight the snippet name as a function
+; (e.g. {#snippet foo(bar)}
+(snippet_name) @function

extensions/svelte/languages/svelte/injections.scm 🔗

@@ -1,74 +1,86 @@
-; injections.scm
-; --------------
+; ; injections.scm
+; ; --------------
 
-; match script tags without a lang tag
-((script_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name)*)
-    (raw_text) @content)
-  (#not-eq? @_name "lang")
-  (#set! "language" "javascript"))
+; Match script tags with a lang attribute
+(script_element
+    (start_tag
+        (attribute
+            (attribute_name) @_attr_name
+            (#eq? @_attr_name "lang")
+            (quoted_attribute_value
+                (attribute_value) @language
+            )
+        )
+    )
+    (raw_text) @content
+)
 
-; match javascript
-((script_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name
-      (quoted_attribute_value (attribute_value) @_value)))
-    (raw_text) @content)
-  (#eq? @_name "lang")
-  (#eq? @_value "js")
-  (#set! "language" "javascript"))
+; Match script tags without a lang attribute
+(script_element
+    (start_tag
+        (attribute
+            (attribute_name) @_attr_name
+        )*
+    )
+    (raw_text) @content
+    (#not-any-of? @_attr_name "lang")
+    (#set! language "javascript")
+)
 
-; match typescript
-((script_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name
-      (quoted_attribute_value (attribute_value) @_value)))
-    (raw_text) @content)
-  (#eq? @_name "lang")
-  (#eq? @_value "ts")
-  (#set! "language" "typescript"))
+; Match the contents of the script's generics="T extends string" as typescript code
+;
+; Disabled for the time-being because tree-sitter is treating the generics
+; attribute as a top-level typescript statement, where `T extends string` is
+; not a valid top-level typescript statement.
+;
+; (script_element
+;     (start_tag
+;         (attribute
+;             (attribute_name) @_attr_name
+;             (#eq? @_attr_name "generics")
+;             (quoted_attribute_value
+;                 (attribute_value) @content
+;             )
+;         )
+;     )
+; 	(#set! language "typescript")
+; )
 
-(style_element
-  (raw_text) @content
-  (#set! "language" "css"))
 
-; match style tags without a lang tag
-((style_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name)*)
-    (raw_text) @content)
-  (#not-eq? @_name "lang")
-  (#set! "language" "css"))
+; Mark everything as typescript because it's
+; a more generic superset of javascript
+; Not sure if it's possible to somehow refer to the
+; script's language attribute here.
+((svelte_raw_text) @content
+    (#set! "language" "ts")
+)
 
-; match css
-((style_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name
-      (quoted_attribute_value (attribute_value) @_value)))
-    (raw_text) @content)
-  (#eq? @_name "lang")
-  (#eq? @_value "css")
-  (#set! "language" "css"))
+; Match style tags with a lang attribute
+(style_element
+    (start_tag
+        (attribute
+            (attribute_name) @_attr_name
+            (#eq? @_attr_name "lang")
+            (quoted_attribute_value
+                (attribute_value) @language
+            )
+        )
+    )
+    (raw_text) @content
+)
 
-; match scss
-((style_element
-  (start_tag
-    (attribute
-      (attribute_name) @_name
-      (quoted_attribute_value (attribute_value) @_value)))
-    (raw_text) @content)
-  (#eq? @_name "lang")
-  (#eq? @_value "scss")
-  (#set! "language" "scss"))
+; Match style tags without a lang attribute
+(style_element
+    (start_tag
+        (attribute
+            (attribute_name) @_attr_name
+        )*
+    )
+    (raw_text) @content
+    (#not-any-of? @_attr_name "lang")
+    (#set! language "css")
+)
 
-((raw_text_expr) @content
-  (#set! "language" "javascript"))
 
-((raw_text_each) @content
-  (#set! "language" "javascript"))
+; Downstream TODO: Style highlighting for `style:background="red"` and `style="background: red"` strings
+; Downstream TODO: Style component comments as markdown

extensions/svelte/languages/svelte/outline.scm 🔗

@@ -0,0 +1,69 @@
+
+(script_element
+    (start_tag) @name
+    (raw_text) @context @item
+)
+
+(script_element
+    (end_tag) @name @item
+)
+
+(style_element
+    (start_tag) @name
+    (raw_text) @context
+) @item
+
+
+(document) @item
+
+(comment) @annotation
+
+(if_statement
+    (if_start) @name
+) @item
+
+(else_block
+    (else_start) @name
+) @item
+
+(else_if_block
+    (else_if_start) @name
+) @item
+
+(element
+    (start_tag) @name
+) @item
+
+(element
+    (self_closing_tag) @name
+) @item
+
+
+; (if_end) @name @item
+
+(each_statement
+    (each_start) @name
+) @item
+
+
+(snippet_statement
+    (snippet_start) @name
+) @item
+
+(snippet_end) @name @item
+
+(html_tag) @name @item
+
+(const_tag) @name @item
+
+(await_statement
+    (await_start) @name
+) @item
+
+(then_block
+    (then_start) @name
+) @item
+
+(catch_block
+    (catch_start) @name
+) @item