From 94c28ba14afb812d748409ed93f930911b56d2f7 Mon Sep 17 00:00:00 2001 From: Daniel Wargh <55995125+ogdakke@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:22:07 +0300 Subject: [PATCH] Improve TS and JS symbol outline (#39797) Added more granular symbols for ts and js in outline panel. This is a bit closer to what vscode offers.
Screenshots of current vs new

New: image Current: image Current vscode (cursor): image

I have never touched scheme before, and pair-programmed this with ai, so please let me know if there's any glaring issues with the implementation. I just miss the outline panel in vscode very much, and would love to see this land. Happy to help with tsx/jsx as well if this is the direction you guys were thinking of taking the outline impl. Doesn't fully close https://github.com/zed-industries/zed/issues/20964 as there is no support for chained class method callbacks or `Class.prototype.method = ...` as mentioned in https://github.com/zed-industries/zed/issues/21243, but this is a step forward. Release Notes: - Improved typescript and javascript symbol outline panel --- crates/languages/src/javascript/outline.scm | 146 +++++++++++-- crates/languages/src/typescript.rs | 220 +++++++++++++++++++- crates/languages/src/typescript/outline.scm | 145 +++++++++++-- 3 files changed, 468 insertions(+), 43 deletions(-) diff --git a/crates/languages/src/javascript/outline.scm b/crates/languages/src/javascript/outline.scm index ca16c27a27be3e1e09ced16cd2eef7aa28345f9e..5f72103bc63bdfab73f7b858c01abe8d34317b22 100644 --- a/crates/languages/src/javascript/outline.scm +++ b/crates/languages/src/javascript/outline.scm @@ -31,38 +31,103 @@ (export_statement (lexical_declaration ["let" "const"] @context - ; Multiple names may be exported - @item is on the declarator to keep - ; ranges distinct. (variable_declarator - name: (_) @name) @item))) + name: (identifier) @name) @item))) + +; Exported array destructuring +(program + (export_statement + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ]))))) + +; Exported object destructuring +(program + (export_statement + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern + value: (identifier) @name @item) + (pair_pattern + value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)]))))) (program (lexical_declaration ["let" "const"] @context - ; Multiple names may be defined - @item is on the declarator to keep - ; ranges distinct. (variable_declarator - name: (_) @name) @item)) + name: (identifier) @name) @item)) + +; Top-level array destructuring +(program + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ])))) + +; Top-level object destructuring +(program + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern + value: (identifier) @name @item) + (pair_pattern + value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)])))) (class_declaration "class" @context name: (_) @name) @item -(method_definition - [ - "get" - "set" - "async" - "*" - "readonly" - "static" - (override_modifier) - (accessibility_modifier) - ]* @context - name: (_) @name - parameters: (formal_parameters - "(" @context - ")" @context)) @item +; Method definitions in classes (not in object literals) +(class_body + (method_definition + [ + "get" + "set" + "async" + "*" + "readonly" + "static" + (override_modifier) + (accessibility_modifier) + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item) + +; Object literal methods +(variable_declarator + value: (object + (method_definition + [ + "get" + "set" + "async" + "*" + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item)) (public_field_definition [ @@ -116,4 +181,43 @@ ) ) @item +; Object properties +(pair + key: [ + (property_identifier) @name + (string (string_fragment) @name) + (number) @name + (computed_property_name) @name + ]) @item + +; Nested variables in function bodies +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (identifier) @name) @item)) + +; Nested array destructuring in functions +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ])))) + +; Nested object destructuring in functions +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern value: (identifier) @name @item) + (pair_pattern value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)])))) + (comment) @annotation diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index a9a1104c8c6cfa2b6eaa7083d18316cee4978fc8..334fd4c4a717d2b0a9890611ff5cc21f3d898aeb 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -1110,7 +1110,7 @@ mod tests { let text = r#" function a() { - // local variables are omitted + // local variables are included let a1 = 1; // all functions are included async function a2() {} @@ -1133,6 +1133,7 @@ mod tests { .collect::>(), &[ ("function a()", 0), + ("let a1", 1), ("async function a2()", 1), ("let b", 0), ("function getB()", 0), @@ -1141,6 +1142,223 @@ mod tests { ); } + #[gpui::test] + async fn test_outline_with_destructuring(cx: &mut TestAppContext) { + let language = crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ); + + let text = r#" + // Top-level destructuring + const { a1, a2 } = a; + const [b1, b2] = b; + + // Defaults and rest + const [c1 = 1, , c2, ...rest1] = c; + const { d1, d2: e1, f1 = 2, g1: h1 = 3, ...rest2 } = d; + + function processData() { + // Nested object destructuring + const { c1, c2 } = c; + // Nested array destructuring + const [d1, d2, d3] = d; + // Destructuring with renaming + const { f1: g1 } = f; + // With defaults + const [x = 10, y] = xy; + } + + class DataHandler { + method() { + // Destructuring in class method + const { a1, a2 } = a; + const [b1, ...b2] = b; + } + } + "# + .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)); + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[ + ("const a1", 0), + ("const a2", 0), + ("const b1", 0), + ("const b2", 0), + ("const c1", 0), + ("const c2", 0), + ("const rest1", 0), + ("const d1", 0), + ("const e1", 0), + ("const h1", 0), + ("const rest2", 0), + ("function processData()", 0), + ("const c1", 1), + ("const c2", 1), + ("const d1", 1), + ("const d2", 1), + ("const d3", 1), + ("const g1", 1), + ("const x", 1), + ("const y", 1), + ("class DataHandler", 0), + ("method()", 1), + ("const a1", 2), + ("const a2", 2), + ("const b1", 2), + ("const b2", 2), + ] + ); + } + + #[gpui::test] + async fn test_outline_with_object_properties(cx: &mut TestAppContext) { + let language = crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ); + + let text = r#" + // Object with function properties + const o = { m() {}, async n() {}, g: function* () {}, h: () => {}, k: function () {} }; + + // Object with primitive properties + const p = { p1: 1, p2: "hello", p3: true }; + + // Nested objects + const q = { + r: { + // won't be included due to one-level depth limit + s: 1 + }, + t: 2 + }; + + function getData() { + const local = { x: 1, y: 2 }; + return local; + } + "# + .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)); + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[ + ("const o", 0), + ("m()", 1), + ("async n()", 1), + ("g", 1), + ("h", 1), + ("k", 1), + ("const p", 0), + ("p1", 1), + ("p2", 1), + ("p3", 1), + ("const q", 0), + ("r", 1), + ("s", 2), + ("t", 1), + ("function getData()", 0), + ("const local", 1), + ("x", 2), + ("y", 2), + ] + ); + } + + #[gpui::test] + async fn test_outline_with_computed_property_names(cx: &mut TestAppContext) { + let language = crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ); + + let text = r#" + // Symbols as object keys + const sym = Symbol("test"); + const obj1 = { + [sym]: 1, + [Symbol("inline")]: 2, + normalKey: 3 + }; + + // Enums as object keys + enum Color { Red, Blue, Green } + + const obj2 = { + [Color.Red]: "red value", + [Color.Blue]: "blue value", + regularProp: "normal" + }; + + // Mixed computed properties + const key = "dynamic"; + const obj3 = { + [key]: 1, + ["string" + "concat"]: 2, + [1 + 1]: 3, + static: 4 + }; + + // Nested objects with computed properties + const obj4 = { + [sym]: { + nested: 1 + }, + regular: { + [key]: 2 + } + }; + "# + .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)); + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[ + ("const sym", 0), + ("const obj1", 0), + ("[sym]", 1), + ("[Symbol(\"inline\")]", 1), + ("normalKey", 1), + ("enum Color", 0), + ("const obj2", 0), + ("[Color.Red]", 1), + ("[Color.Blue]", 1), + ("regularProp", 1), + ("const key", 0), + ("const obj3", 0), + ("[key]", 1), + ("[\"string\" + \"concat\"]", 1), + ("[1 + 1]", 1), + ("static", 1), + ("const obj4", 0), + ("[sym]", 1), + ("nested", 2), + ("regular", 1), + ("[key]", 2), + ] + ); + } + #[gpui::test] async fn test_generator_function_outline(cx: &mut TestAppContext) { let language = crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()); diff --git a/crates/languages/src/typescript/outline.scm b/crates/languages/src/typescript/outline.scm index f4261b9697d376f517b717bc942387190e0b6dde..54d29007c7b7eb57c0bcaefc2c1e0ab75e4d9a6c 100644 --- a/crates/languages/src/typescript/outline.scm +++ b/crates/languages/src/typescript/outline.scm @@ -34,18 +34,64 @@ (export_statement (lexical_declaration ["let" "const"] @context - ; Multiple names may be exported - @item is on the declarator to keep - ; ranges distinct. (variable_declarator - name: (_) @name) @item)) + name: (identifier) @name) @item)) +; Exported array destructuring +(export_statement + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ])))) + +; Exported object destructuring +(export_statement + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern + value: (identifier) @name @item) + (pair_pattern + value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)])))) + +(program + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (identifier) @name) @item)) + +; Top-level array destructuring (program (lexical_declaration ["let" "const"] @context - ; Multiple names may be defined - @item is on the declarator to keep - ; ranges distinct. (variable_declarator - name: (_) @name) @item)) + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ])))) + +; Top-level object destructuring +(program + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern + value: (identifier) @name @item) + (pair_pattern + value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)])))) (class_declaration "class" @context @@ -56,21 +102,38 @@ "class" @context name: (_) @name) @item -(method_definition - [ - "get" - "set" - "async" - "*" - "readonly" - "static" - (override_modifier) - (accessibility_modifier) - ]* @context - name: (_) @name - parameters: (formal_parameters - "(" @context - ")" @context)) @item +; Method definitions in classes (not in object literals) +(class_body + (method_definition + [ + "get" + "set" + "async" + "*" + "readonly" + "static" + (override_modifier) + (accessibility_modifier) + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item) + +; Object literal methods +(variable_declarator + value: (object + (method_definition + [ + "get" + "set" + "async" + "*" + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item)) (public_field_definition [ @@ -124,4 +187,44 @@ ) ) @item +; Object properties +(pair + key: [ + (property_identifier) @name + (string (string_fragment) @name) + (number) @name + (computed_property_name) @name + ]) @item + + +; Nested variables in function bodies +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (identifier) @name) @item)) + +; Nested array destructuring in functions +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (array_pattern + [ + (identifier) @name @item + (assignment_pattern left: (identifier) @name @item) + (rest_pattern (identifier) @name @item) + ])))) + +; Nested object destructuring in functions +(statement_block + (lexical_declaration + ["let" "const"] @context + (variable_declarator + name: (object_pattern + [(shorthand_property_identifier_pattern) @name @item + (pair_pattern value: (identifier) @name @item) + (pair_pattern value: (assignment_pattern left: (identifier) @name @item)) + (rest_pattern (identifier) @name @item)])))) + (comment) @annotation