From 6f9e052edb347d6af5fc98015657928ba4300790 Mon Sep 17 00:00:00 2001
From: Brian Donovan <1938+eventualbuddha@users.noreply.github.com>
Date: Mon, 14 Jul 2025 05:26:17 -0700
Subject: [PATCH] languages: Add JS/TS generator functions to outline (#34388)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Functions like `function* iterateElements() {}` would not show up in the
editor's navigation outline. With this change, they do.
| **Before** | **After**
|-|-|
|
|
|
Note that I decided to use Zed's agent assistance features to do this PR
as a sort of test run. I don't normally code with an AI assistant, but
figured it might be good in this case since I'm unfamiliar with the
codebase. I must say I was fairly impressed. All the changes in this PR
were done by Claude Sonnet 4, though I have done a manual review to
ensure the changes look sane and tested the changes by running the
re-built `zed` binary with a toy project.
Closes #21631
Release Notes:
- Fixed JS/TS outlines to show generator functions.
---
crates/languages/src/javascript/outline.scm | 9 ++++
crates/languages/src/tsx/outline.scm | 9 ++++
crates/languages/src/typescript.rs | 56 +++++++++++++++++++++
crates/languages/src/typescript/outline.scm | 9 ++++
4 files changed, 83 insertions(+)
diff --git a/crates/languages/src/javascript/outline.scm b/crates/languages/src/javascript/outline.scm
index 99aa4bdfd5ad530505ebb90dc075e5ca405a5451..026c71e1f91d323ff2370828f330e4a4944e74db 100644
--- a/crates/languages/src/javascript/outline.scm
+++ b/crates/languages/src/javascript/outline.scm
@@ -14,6 +14,15 @@
"(" @context
")" @context)) @item
+(generator_function_declaration
+ "async"? @context
+ "function" @context
+ "*" @context
+ name: (_) @name
+ parameters: (formal_parameters
+ "(" @context
+ ")" @context)) @item
+
(interface_declaration
"interface" @context
name: (_) @name) @item
diff --git a/crates/languages/src/tsx/outline.scm b/crates/languages/src/tsx/outline.scm
index df6ffa5aec8aa1b23b8179d0c341231feea5c0b5..5dafe791e493d03f6a73fa7c155ebb03072dc4d5 100644
--- a/crates/languages/src/tsx/outline.scm
+++ b/crates/languages/src/tsx/outline.scm
@@ -18,6 +18,15 @@
"(" @context
")" @context)) @item
+(generator_function_declaration
+ "async"? @context
+ "function" @context
+ "*" @context
+ name: (_) @name
+ parameters: (formal_parameters
+ "(" @context
+ ")" @context)) @item
+
(interface_declaration
"interface" @context
name: (_) @name) @item
diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs
index 3c1ecdcd5c51ee0d10a886327bac488b44621f6d..34b9c3224eecf9c86dd61cf64cb0d5e33572a810 100644
--- a/crates/languages/src/typescript.rs
+++ b/crates/languages/src/typescript.rs
@@ -1075,6 +1075,62 @@ mod tests {
);
}
+ #[gpui::test]
+ async fn test_generator_function_outline(cx: &mut TestAppContext) {
+ let language = crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into());
+
+ let text = r#"
+ function normalFunction() {
+ console.log("normal");
+ }
+
+ function* simpleGenerator() {
+ yield 1;
+ yield 2;
+ }
+
+ async function* asyncGenerator() {
+ yield await Promise.resolve(1);
+ }
+
+ function* generatorWithParams(start, end) {
+ for (let i = start; i <= end; i++) {
+ yield i;
+ }
+ }
+
+ class TestClass {
+ *methodGenerator() {
+ yield "method";
+ }
+
+ async *asyncMethodGenerator() {
+ yield "async method";
+ }
+ }
+ "#
+ .unindent();
+
+ let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+ let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
+ assert_eq!(
+ outline
+ .items
+ .iter()
+ .map(|item| (item.text.as_str(), item.depth))
+ .collect::>(),
+ &[
+ ("function normalFunction()", 0),
+ ("function* simpleGenerator()", 0),
+ ("async function* asyncGenerator()", 0),
+ ("function* generatorWithParams( )", 0),
+ ("class TestClass", 0),
+ ("*methodGenerator()", 1),
+ ("async *asyncMethodGenerator()", 1),
+ ]
+ );
+ }
+
#[gpui::test]
async fn test_package_json_discovery(executor: BackgroundExecutor, cx: &mut TestAppContext) {
cx.update(|cx| {
diff --git a/crates/languages/src/typescript/outline.scm b/crates/languages/src/typescript/outline.scm
index df6ffa5aec8aa1b23b8179d0c341231feea5c0b5..5dafe791e493d03f6a73fa7c155ebb03072dc4d5 100644
--- a/crates/languages/src/typescript/outline.scm
+++ b/crates/languages/src/typescript/outline.scm
@@ -18,6 +18,15 @@
"(" @context
")" @context)) @item
+(generator_function_declaration
+ "async"? @context
+ "function" @context
+ "*" @context
+ name: (_) @name
+ parameters: (formal_parameters
+ "(" @context
+ ")" @context)) @item
+
(interface_declaration
"interface" @context
name: (_) @name) @item