From 7f7a715d797d8450eed4f17ae8002b5427d01d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 19 Oct 2026 19:10:43 -0400 Subject: [PATCH] Lists: ListItem::editor draws a field in the title's place, for a row renamed in place; the title stays the row's name --- AGENTS.md | 1 + src/lists/item.rs | 19 +++++++++++---- src/lists/tests/editor.rs | 49 +++++++++++++++++++++++++++++++++++++++ src/lists/tests/mod.rs | 1 + 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/lists/tests/editor.rs diff --git a/AGENTS.md b/AGENTS.md index 5430892..e9e176d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -276,3 +276,4 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - 2026-10-05: `SelectableList` is a list in the AX tree, named by `label`; each `ListItem` is a list item with its title as name, its description, its selection, and, in a selectable list, its place and count (`ListItem::place`). Before, the rows had no role and were missing from the AX tree. - 2026-10-05: `NumberInput::blank` shows a placeholder, not a number, as for a mixed selection; Enter or blur on the empty field commits nothing, a typed number commits as usual. Before, a mixed field held a real number the owner had to invent, and Enter sent it. `ScrubInput` moved to `number/scrub.rs`. - 2026-10-07: gpui comes from crates.io's `gpui-pre` at the exact snapshot GPUI Kit pins, not from Zed's git. Two sources build two gpui crates whose types do not mix, so Ely and Kit could not share a window. The code is still Zed's; the publisher is not. This replaces the 2026-09-28 choice of Zed's git, and the pin moves when Kit's does. +- 2026-10-19: `ListItem::editor` draws a field in the title's place, for a row renamed in place, as ELYTerminal's session rows rename; the title stays the row's name in the AX tree, and the row's press, selection and description stay as they were. diff --git a/src/lists/item.rs b/src/lists/item.rs index bdb7d55..92d1ce8 100644 --- a/src/lists/item.rs +++ b/src/lists/item.rs @@ -19,6 +19,8 @@ type OnClick = Rc; pub struct ListItem { id: ElementId, title: SharedString, + /// A field drawn in the title's place. + editor: Option, description: Option, detail: Option, leading: Option, @@ -39,6 +41,7 @@ impl ListItem { Self { id: id.into(), title: title.into(), + editor: None, description: None, detail: None, leading: None, @@ -72,6 +75,12 @@ impl ListItem { self } + /// A field in place of the title, as a row renamed in place has it; the title stays the row's name. + pub fn editor(mut self, editor: impl IntoElement) -> Self { + self.editor = Some(editor.into_any_element()); + self + } + /// A second line, quieter than the title. pub fn description(mut self, text: impl Into) -> Self { self.description = Some(text.into()); @@ -191,8 +200,9 @@ impl RenderOnce for ListItem { .min_w_0() .flex() .flex_col() - .child( - line(self.title) + .child(match self.editor { + Some(editor) => div().min_w_0().child(editor).into_any_element(), + None => line(self.title) .text_size(theme.text_size(TextSize::Base)) .when(self.strong, |title| title.font_weight(FontWeight::SEMIBOLD)) .when(self.struck, |title| title.line_through()) @@ -200,8 +210,9 @@ impl RenderOnce for ListItem { colors.fg_muted } else { colors.fg - }), - ) + }) + .into_any_element(), + }) .children(self.description.map(|text| { line(text.clone()) .debug_selector(|| format!("item-description-{text}")) diff --git a/src/lists/tests/editor.rs b/src/lists/tests/editor.rs new file mode 100644 index 0000000..741089e --- /dev/null +++ b/src/lists/tests/editor.rs @@ -0,0 +1,49 @@ +use gpui::{ + Context, InteractiveElement, IntoElement, ParentElement, Render, Styled, TestAppContext, + Window, div, px, +}; + +use super::{settle, setup}; +use crate::lists::{List, ListItem}; + +/// Two rows, the first renamed in place. +struct Renaming; + +impl Render for Renaming { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + let field = div() + .debug_selector(|| "row-editor".into()) + .h(px(12.0)) + .w_full(); + div().w(px(240.0)).child( + List::new() + .child( + ListItem::new("renamed", "Old name") + .description("~/work") + .selected(true) + .editor(field), + ) + .child(ListItem::new("other", "Other").description("~/other")), + ) + } +} + +#[gpui::test] +fn an_editor_takes_the_title_s_place_in_its_row(cx: &mut TestAppContext) { + setup(cx); + let (_, cx) = cx.add_window_view(|_, _| Renaming); + settle(cx); + let row = cx + .debug_bounds("item-selected-Old name") + .expect("the renamed row"); + let field = cx.debug_bounds("row-editor").expect("its editor"); + let line = cx + .debug_bounds("item-description-~/work") + .expect("its description"); + assert!(row.contains(&field.center()), "{field:?} in {row:?}"); + assert!( + field.bottom() <= line.top(), + "the editor stands over the description" + ); + assert!(cx.debug_bounds("item-description-~/other").is_some()); +} diff --git a/src/lists/tests/mod.rs b/src/lists/tests/mod.rs index 7022331..1ca2217 100644 --- a/src/lists/tests/mod.rs +++ b/src/lists/tests/mod.rs @@ -18,6 +18,7 @@ use crate::{ theme::Theme, }; +mod editor; mod keys; fn setup(cx: &mut TestAppContext) {