Add outlines for CSS (#8706)

Ivan Buryak created

Add outlines for .css files

⚠️I also added `workspace = { workspace = true, features =
["test-support"] }` to dev deps of 'languages' crate to make unit tests
work.

![Css
outlines](https://github.com/zed-industries/zed/assets/4057095/0cade407-79e7-4d0f-9b80-3502509e373d)

Change summary

Cargo.lock                           |  1 
crates/languages/Cargo.toml          |  1 
crates/languages/src/css.rs          | 59 ++++++++++++++++++++++++++++++
crates/languages/src/css/outline.scm | 18 +++++++++
4 files changed, 79 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -5337,6 +5337,7 @@ dependencies = [
  "tree-sitter-zig",
  "unindent",
  "util",
+ "workspace",
 ]
 
 [[package]]

crates/languages/Cargo.toml 🔗

@@ -86,3 +86,4 @@ util.workspace = true
 text.workspace = true
 theme.workspace = true
 unindent.workspace = true
+workspace = { workspace = true, features = ["test-support"] }

crates/languages/src/css.rs 🔗

@@ -126,3 +126,62 @@ async fn get_cached_server_binary(
     .await
     .log_err()
 }
+
+#[cfg(test)]
+mod tests {
+    use gpui::{Context, TestAppContext};
+    use text::BufferId;
+    use unindent::Unindent;
+
+    #[gpui::test]
+    async fn test_outline(cx: &mut TestAppContext) {
+        let language = crate::language("css", tree_sitter_css::language());
+
+        let text = r#"
+            /* Import statement */
+            @import './fonts.css';
+
+            /* multiline list of selectors with nesting */
+            .test-class,
+            div {
+                .nested-class {
+                    color: red;
+                }
+            }
+
+            /* descendant selectors */
+            .test .descendant {}
+
+            /* pseudo */
+            .test:not(:hover) {}
+
+            /* media queries */
+            @media screen and (min-width: 3000px) {
+                .desktop-class {}
+            }
+        "#
+        .unindent();
+
+        let buffer = cx.new_model(|cx| {
+            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+                .with_language(language, cx)
+        });
+        let outline = buffer.update(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
+        assert_eq!(
+            outline
+                .items
+                .iter()
+                .map(|item| (item.text.as_str(), item.depth))
+                .collect::<Vec<_>>(),
+            &[
+                ("@import './fonts.css'", 0),
+                (".test-class, div", 0),
+                (".nested-class", 1),
+                (".test .descendant", 0),
+                (".test:not(:hover)", 0),
+                ("@media screen and (min-width: 3000px)", 0),
+                (".desktop-class", 1),
+            ]
+        );
+    }
+}

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

@@ -0,0 +1,18 @@
+(stylesheet
+    (import_statement
+        "@import" @context
+        ((string_value) @name)) @item)
+
+
+(rule_set
+    (selectors
+      .
+      (_) @name
+      ("," @name (_) @name)*
+    )) @item
+
+(media_statement
+    "@media" @context
+    (_) @name
+    (block)
+) @item