Improve CSS syntax highlighting (#25326)

chbk created

Release Notes:

  - Improved CSS syntax highlighting

| Zed 0.174.6 | With this PR |
| --- | --- |
| ![css_0 174
6](https://github.com/user-attachments/assets/d069f20e-5f1f-4d03-a010-81ba4b61b3a0)
|
![css_pr](https://github.com/user-attachments/assets/36463ef1-2ead-421d-9825-bd359e7677ab)
|

- `|`: `operator`
- `and`, `or`, `not`, `only`: `operator` -> `keyword.operator`, as
defined in other languages
- `id_name`, `class_name`: `property`/`attribute` -> `selector`, not a
property name. [CSS
reference](https://www.w3.org/TR/selectors-3/#class-html)
- `namespace_name`: `property` -> `namespace`, not a property name
- `property_name`: `constant` -> `property`, like `feature_name` already
defined
- `(keyword_query)`: `property`, similar to `feature_name`. [CSS
reference](https://www.w3.org/TR/mediaqueries-3/#media1)
- `keyword_query`: `constant.builtin`, [CSS
reference](https://www.w3.org/TR/mediaqueries-3/#media0)
- `plain_value`, `keyframes_name`: `constant.builtin`, [CSS
reference](https://www.w3.org/TR/css-values-3/#value-defs)
- `unit`: `type` -> `type.unit`,
[Atom](https://github.com/atom/language-css/blob/9e4afce058b4593edf03ed1dec6033b163c678f0/grammars/tree-sitter-css.cson#L73)
and [VS
Code](https://github.com/microsoft/vscode/blob/336801752dd09afa76f5429fba846e533bcdb7d9/extensions/css/syntaxes/css.tmLanguage.json#L1393)
also have a `unit` scope for this. [CSS
reference](https://www.w3.org/TR/css3-values/#dimensions)


```css
@media (keyword_query) and keyword_query {}
@supports (feature_name: plain_value) {}
@namespace namespace_name url("string");
namespace_name|tag_name {}
@keyframes keyframes_name {
  to {
    top: 200unit;
    color: #c01045;
  }
}
tag_name::before,
#id_name:nth-child(even),
.class_name[attribute_name=plain_value] {
  property_name: 2em 1.2em;
  --variable: rgb(250, 0, 0);
  color: var(--variable);
  animation: keyframes_name 5s plain_value;
}
```

Change summary

assets/themes/ayu/ayu.json              | 45 +++++++++++++
assets/themes/gruvbox/gruvbox.json      | 90 +++++++++++++++++++++++++++
assets/themes/one/one.json              | 30 +++++++++
crates/languages/src/css/highlights.scm | 45 +++++++++----
4 files changed, 196 insertions(+), 14 deletions(-)

Detailed changes

assets/themes/ayu/ayu.json 🔗

@@ -261,6 +261,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#bfbdb6ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#d2a6ffff",
             "font_style": null,
@@ -316,6 +321,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#d2a6ffff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#5ac1feff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#a9d94bff",
             "font_style": null,
@@ -632,6 +647,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#5c6166ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#a37accff",
             "font_style": null,
@@ -687,6 +707,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#a37accff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#3b9ee5ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#86b300ff",
             "font_style": null,
@@ -1003,6 +1033,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#cccac2ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#dfbfffff",
             "font_style": null,
@@ -1058,6 +1093,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#dfbfffff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#72cffeff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#d4fe7fff",
             "font_style": null,

assets/themes/gruvbox/gruvbox.json 🔗

@@ -270,6 +270,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#d3869bff",
             "font_style": null,
@@ -325,6 +330,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#fabd2eff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#b8bb25ff",
             "font_style": null,
@@ -655,6 +670,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#d3869bff",
             "font_style": null,
@@ -710,6 +730,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#fabd2eff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#b8bb25ff",
             "font_style": null,
@@ -1040,6 +1070,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#d3869bff",
             "font_style": null,
@@ -1095,6 +1130,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#fabd2eff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#83a598ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#b8bb25ff",
             "font_style": null,
@@ -1425,6 +1470,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#066578ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#8f3e71ff",
             "font_style": null,
@@ -1480,6 +1530,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#b57613ff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#0b6678ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#79740eff",
             "font_style": null,
@@ -1810,6 +1870,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#066578ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#8f3e71ff",
             "font_style": null,
@@ -1865,6 +1930,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#b57613ff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#0b6678ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#79740eff",
             "font_style": null,
@@ -2195,6 +2270,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#066578ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#8f3e71ff",
             "font_style": null,
@@ -2250,6 +2330,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#b57613ff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#0b6678ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#79740eff",
             "font_style": null,

assets/themes/one/one.json 🔗

@@ -264,6 +264,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#dce0e5ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#bf956aff",
             "font_style": null,
@@ -319,6 +324,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#dfc184ff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#74ade8ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#a1c181ff",
             "font_style": null,
@@ -643,6 +658,11 @@
             "font_style": null,
             "font_weight": null
           },
+          "namespace": {
+            "color": "#242529ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "number": {
             "color": "#ad6e25ff",
             "font_style": null,
@@ -698,6 +718,16 @@
             "font_style": null,
             "font_weight": null
           },
+          "selector": {
+            "color": "#669f59ff",
+            "font_style": null,
+            "font_weight": null
+          },
+          "selector.pseudo": {
+            "color": "#5c78e2ff",
+            "font_style": null,
+            "font_weight": null
+          },
           "string": {
             "color": "#649f57ff",
             "font_style": null,

crates/languages/src/css/highlights.scm 🔗

@@ -11,6 +11,7 @@
   ">"
   "+"
   "-"
+  "|"
   "*"
   "/"
   "="
@@ -19,35 +20,50 @@
   "~="
   "$="
   "*="
+] @operator
+
+[
   "and"
   "or"
   "not"
   "only"
-] @operator
+] @keyword.operator
+
+(id_name) @selector.id
+(class_name) @selector.class
 
-(attribute_selector (plain_value) @string)
+(namespace_name) @namespace
+(namespace_selector (tag_name) @namespace "|")
 
 (attribute_name) @attribute
-(pseudo_element_selector (tag_name) @attribute)
-(pseudo_class_selector (class_name) @attribute)
+(pseudo_element_selector "::" (tag_name) @selector.pseudo)
+(pseudo_class_selector ":" (class_name) @selector.pseudo)
 
 [
-  (class_name)
-  (id_name)
-  (namespace_name)
   (feature_name)
+  (property_name)
 ] @property
 
-(property_name) @constant
-
 (function_name) @function
 
+[
+  (plain_value)
+  (keyframes_name)
+  (keyword_query)
+] @constant.builtin
+
+(attribute_selector
+  (plain_value) @string)
+
+(parenthesized_query
+  (keyword_query) @property)
+
 (
   [
     (property_name)
     (plain_value)
-  ] @variable.special
-  (#match? @variable.special "^--")
+  ] @variable
+  (#match? @variable "^--")
 )
 
 [
@@ -61,7 +77,7 @@
   (to)
   (from)
   (important)
-]  @keyword
+] @keyword
 
 (string_value) @string
 (color_value) @string.special
@@ -71,7 +87,7 @@
   (float_value)
 ] @number
 
-(unit) @type
+(unit) @type.unit
 
 [
   ","
@@ -79,9 +95,10 @@
   "."
   "::"
   ";"
-  "#"
 ] @punctuation.delimiter
 
+(id_selector "#" @punctuation.delimiter)
+
 [
   "{"
   ")"