From f567bb52ff9589ff0c73aaf267a817ae471ec913 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Tue, 10 Jun 2025 13:50:57 -0500 Subject: [PATCH] gpui: Simplify uniform list API by removing entity param (#32480) This PR also introduces `Context::processor`, a sibling of `Context::listener` that takes a strong pointer to entity and allows for a return result. Release Notes: - N/A Co-authored-by: Mikayla --- crates/agent/src/thread_history.rs | 5 ++-- .../src/session/running/breakpoint_list.rs | 6 ++-- .../src/session/running/module_list.rs | 7 +++-- .../src/session/running/variable_list.rs | 5 ++-- crates/editor/src/code_context_menus.rs | 10 +++---- crates/extensions_ui/src/extensions_ui.rs | 14 +++------ crates/git_ui/src/git_panel.rs | 11 ++++--- crates/gpui/examples/data_table.rs | 12 ++++---- crates/gpui/examples/uniform_list.rs | 5 ++-- crates/gpui/src/app/context.rs | 12 ++++++++ crates/gpui/src/elements/uniform_list.rs | 29 ++++++++----------- crates/language_tools/src/syntax_tree_view.rs | 5 ++-- crates/outline_panel/src/outline_panel.rs | 10 ++++--- crates/picker/src/picker.rs | 7 ++--- crates/project_panel/src/project_panel.rs | 6 ++-- .../src/project_index_debug_view.rs | 7 ++--- crates/zed/src/zed/component_preview.rs | 6 ++-- 17 files changed, 80 insertions(+), 77 deletions(-) diff --git a/crates/agent/src/thread_history.rs b/crates/agent/src/thread_history.rs index 7b889cbe59..be2709a535 100644 --- a/crates/agent/src/thread_history.rs +++ b/crates/agent/src/thread_history.rs @@ -594,10 +594,11 @@ impl Render for ThreadHistory { view.pr_5() .child( uniform_list( - cx.entity().clone(), "thread-history", self.list_item_count(), - Self::list_items, + cx.processor(|this, range: Range, window, cx| { + this.list_items(range, window, cx) + }), ) .p_1() .track_scroll(self.scroll_handle.clone()) diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 3b63aa73cd..018536ac52 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -1,4 +1,5 @@ use std::{ + ops::Range, path::{Path, PathBuf}, sync::Arc, time::Duration, @@ -277,10 +278,9 @@ impl BreakpointList { let selected_ix = self.selected_ix; let focus_handle = self.focus_handle.clone(); uniform_list( - cx.entity(), "breakpoint-list", self.breakpoints.len(), - move |this, range, window, cx| { + cx.processor(move |this, range: Range, window, cx| { range .clone() .zip(&mut this.breakpoints[range]) @@ -291,7 +291,7 @@ impl BreakpointList { .into_any_element() }) .collect() - }, + }), ) .track_scroll(self.scroll_handle.clone()) .flex_grow() diff --git a/crates/debugger_ui/src/session/running/module_list.rs b/crates/debugger_ui/src/session/running/module_list.rs index 3ca829fcb2..f132369a71 100644 --- a/crates/debugger_ui/src/session/running/module_list.rs +++ b/crates/debugger_ui/src/session/running/module_list.rs @@ -8,7 +8,7 @@ use project::{ ProjectItem as _, ProjectPath, debugger::session::{Session, SessionEvent}, }; -use std::{path::Path, sync::Arc}; +use std::{ops::Range, path::Path, sync::Arc}; use ui::{Scrollbar, ScrollbarState, prelude::*}; use workspace::Workspace; @@ -281,10 +281,11 @@ impl ModuleList { fn render_list(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { uniform_list( - cx.entity(), "module-list", self.entries.len(), - |this, range, _window, cx| range.map(|ix| this.render_entry(ix, cx)).collect(), + cx.processor(|this, range: Range, _window, cx| { + range.map(|ix| this.render_entry(ix, cx)).collect() + }), ) .track_scroll(self.scroll_handle.clone()) .size_full() diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index 46c6edeefc..220276418d 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -980,10 +980,11 @@ impl Render for VariableList { .on_action(cx.listener(Self::edit_variable)) .child( uniform_list( - cx.entity().clone(), "variable-list", self.entries.len(), - move |this, range, window, cx| this.render_entries(range, window, cx), + cx.processor(move |this, range: Range, window, cx| { + this.render_entries(range, window, cx) + }), ) .track_scroll(self.list_handle.clone()) .gap_1_5() diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index f0f53481c1..2aea86603a 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -722,10 +722,9 @@ impl CompletionsMenu { let last_rendered_range = self.last_rendered_range.clone(); let style = style.clone(); let list = uniform_list( - cx.entity().clone(), "completions", self.entries.borrow().len(), - move |_editor, range, _window, cx| { + cx.processor(move |_editor, range: Range, _window, cx| { last_rendered_range.borrow_mut().replace(range.clone()); let start_ix = range.start; let completions_guard = completions.borrow_mut(); @@ -837,7 +836,7 @@ impl CompletionsMenu { ) }) .collect() - }, + }), ) .occlude() .max_h(max_height_in_lines as f32 * window.line_height()) @@ -1452,10 +1451,9 @@ impl CodeActionsMenu { let actions = self.actions.clone(); let selected_item = self.selected_item; let list = uniform_list( - cx.entity().clone(), "code_actions_menu", self.actions.len(), - move |_this, range, _, cx| { + cx.processor(move |_this, range: Range, _, cx| { actions .iter() .skip(range.start) @@ -1518,7 +1516,7 @@ impl CodeActionsMenu { ) }) .collect() - }, + }), ) .occlude() .max_h(max_height_in_lines as f32 * window.line_height()) diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index db13de95b1..fe556e22c9 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1477,18 +1477,12 @@ impl Render for ExtensionsPage { return this.py_4().child(self.render_empty_state(cx)); } - let extensions_page = cx.entity().clone(); let scroll_handle = self.list.clone(); this.child( - uniform_list( - extensions_page, - "entries", - count, - Self::render_extensions, - ) - .flex_grow() - .pb_4() - .track_scroll(scroll_handle), + uniform_list("entries", count, cx.processor(Self::render_extensions)) + .flex_grow() + .pb_4() + .track_scroll(scroll_handle), ) .child( div() diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 0dedfa1667..33418fd483 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -55,6 +55,7 @@ use project::{ use serde::{Deserialize, Serialize}; use settings::{Settings as _, SettingsStore}; use std::future::Future; +use std::ops::Range; use std::path::{Path, PathBuf}; use std::{collections::HashSet, sync::Arc, time::Duration, usize}; use strum::{IntoEnumIterator, VariantNames}; @@ -3710,8 +3711,10 @@ impl GitPanel { .relative() .overflow_hidden() .child( - uniform_list(cx.entity().clone(), "entries", entry_count, { - move |this, range, window, cx| { + uniform_list( + "entries", + entry_count, + cx.processor(move |this, range: Range, window, cx| { let mut items = Vec::with_capacity(range.end - range.start); for ix in range { @@ -3739,8 +3742,8 @@ impl GitPanel { } items - } - }) + }), + ) .when( !self.horizontal_scrollbar.show_track && self.horizontal_scrollbar.show_scrollbar, diff --git a/crates/gpui/examples/data_table.rs b/crates/gpui/examples/data_table.rs index 3f3286680b..f8561f2e9f 100644 --- a/crates/gpui/examples/data_table.rs +++ b/crates/gpui/examples/data_table.rs @@ -378,8 +378,6 @@ impl DataTable { impl Render for DataTable { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let entity = cx.entity(); - div() .font_family(".SystemUIFont") .bg(gpui::white()) @@ -431,8 +429,10 @@ impl Render for DataTable { .relative() .size_full() .child( - uniform_list(entity, "items", self.quotes.len(), { - move |this, range, _, _| { + uniform_list( + "items", + self.quotes.len(), + cx.processor(move |this, range: Range, _, _| { this.visible_range = range.clone(); let mut items = Vec::with_capacity(range.end - range.start); for i in range { @@ -441,8 +441,8 @@ impl Render for DataTable { } } items - } - }) + }), + ) .size_full() .track_scroll(self.scroll_handle.clone()), ) diff --git a/crates/gpui/examples/uniform_list.rs b/crates/gpui/examples/uniform_list.rs index 221de44bd9..fc5d92bdd4 100644 --- a/crates/gpui/examples/uniform_list.rs +++ b/crates/gpui/examples/uniform_list.rs @@ -9,10 +9,9 @@ impl Render for UniformListExample { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { div().size_full().bg(rgb(0xffffff)).child( uniform_list( - cx.entity().clone(), "entries", 50, - |_this, range, _window, _cx| { + cx.processor(|_this, range, _window, _cx| { let mut items = Vec::new(); for ix in range { let item = ix + 1; @@ -29,7 +28,7 @@ impl Render for UniformListExample { ); } items - }, + }), ) .h_full(), ) diff --git a/crates/gpui/src/app/context.rs b/crates/gpui/src/app/context.rs index fcdc6ca97d..2d90ff35b1 100644 --- a/crates/gpui/src/app/context.rs +++ b/crates/gpui/src/app/context.rs @@ -225,6 +225,18 @@ impl<'a, T: 'static> Context<'a, T> { } } + /// Convenience method for producing view state in a closure. + /// See `listener` for more details. + pub fn processor( + &self, + f: impl Fn(&mut T, E, &mut Window, &mut Context) -> R + 'static, + ) -> impl Fn(E, &mut Window, &mut App) -> R + 'static { + let view = self.entity(); + move |e: E, window: &mut Window, cx: &mut App| { + view.update(cx, |view, cx| f(view, e, window, cx)) + } + } + /// Run something using this entity and cx, when the returned struct is dropped pub fn on_drop( &self, diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 859c8f9552..c85f71eae8 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,10 +5,10 @@ //! elements with uniform height. use crate::{ - AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, Element, ElementId, Entity, - GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, - IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Render, ScrollHandle, Size, - StyleRefinement, Styled, Window, point, size, + AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId, + Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId, + ListSizingBehavior, Overflow, Pixels, ScrollHandle, Size, StyleRefinement, Styled, Window, + point, size, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -19,28 +19,23 @@ use super::ListHorizontalSizingBehavior; /// When rendered into a container with overflow-y: hidden and a fixed (or max) height, /// uniform_list will only render the visible subset of items. #[track_caller] -pub fn uniform_list( - view: Entity, - id: I, +pub fn uniform_list( + id: impl Into, item_count: usize, - f: impl 'static + Fn(&mut V, Range, &mut Window, &mut Context) -> Vec, + f: impl 'static + Fn(Range, &mut Window, &mut App) -> Vec, ) -> UniformList where - I: Into, R: IntoElement, - V: Render, { let id = id.into(); let mut base_style = StyleRefinement::default(); base_style.overflow.y = Some(Overflow::Scroll); - let render_range = move |range, window: &mut Window, cx: &mut App| { - view.update(cx, |this, cx| { - f(this, range, window, cx) - .into_iter() - .map(|component| component.into_any_element()) - .collect() - }) + let render_range = move |range: Range, window: &mut Window, cx: &mut App| { + f(range, window, cx) + .into_iter() + .map(|component| component.into_any_element()) + .collect() }; UniformList { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 03fe485179..e88b3a825b 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -303,10 +303,9 @@ impl Render for SyntaxTreeView { { let layer = layer.clone(); rendered = rendered.child(uniform_list( - cx.entity().clone(), "SyntaxTreeView", layer.node().descendant_count(), - move |this, range, _, cx| { + cx.processor(move |this, range: Range, _, cx| { let mut items = Vec::new(); let mut cursor = layer.node().walk(); let mut descendant_ix = range.start; @@ -377,7 +376,7 @@ impl Render for SyntaxTreeView { } } items - }, + }), ) .size_full() .track_scroll(self.list_scroll_handle.clone()) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index cea592e9ee..046ffd1284 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4497,8 +4497,10 @@ impl OutlinePanel { let multi_buffer_snapshot = self .active_editor() .map(|editor| editor.read(cx).buffer().read(cx).snapshot(cx)); - uniform_list(cx.entity().clone(), "entries", items_len, { - move |outline_panel, range, window, cx| { + uniform_list( + "entries", + items_len, + cx.processor(move |outline_panel, range: Range, window, cx| { let entries = outline_panel.cached_entries.get(range); entries .map(|entries| entries.to_vec()) @@ -4555,8 +4557,8 @@ impl OutlinePanel { ), }) .collect() - } - }) + }), + ) .with_sizing_behavior(ListSizingBehavior::Infer) .with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained) .with_width_from_item(self.max_width_item_index) diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index c6013fbdf6..e8b4e6a15f 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -17,7 +17,7 @@ use gpui::{ use head::Head; use schemars::JsonSchema; use serde::Deserialize; -use std::{sync::Arc, time::Duration}; +use std::{ops::Range, sync::Arc, time::Duration}; use ui::{ Color, Divider, Label, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, prelude::*, v_flex, }; @@ -760,14 +760,13 @@ impl Picker { match &self.element_container { ElementContainer::UniformList(scroll_handle) => uniform_list( - cx.entity().clone(), "candidates", self.delegate.match_count(), - move |picker, visible_range, window, cx| { + cx.processor(move |picker, visible_range: Range, window, cx| { visible_range .map(|ix| picker.render_element(window, cx, ix)) .collect() - }, + }), ) .with_sizing_behavior(sizing_behavior) .when_some(self.widest_item, |el, widest_item| { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7effb96ac0..1ead1c2a9c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4914,8 +4914,8 @@ impl Render for ProjectPanel { ) .track_focus(&self.focus_handle(cx)) .child( - uniform_list(cx.entity().clone(), "entries", item_count, { - |this, range, window, cx| { + uniform_list("entries", item_count, { + cx.processor(|this, range: Range, window, cx| { let mut items = Vec::with_capacity(range.end - range.start); this.for_each_visible_entry( range, @@ -4926,7 +4926,7 @@ impl Render for ProjectPanel { }, ); items - } + }) }) .when(show_indent_guides, |list| { list.with_decoration( diff --git a/crates/semantic_index/src/project_index_debug_view.rs b/crates/semantic_index/src/project_index_debug_view.rs index 15b86c3f77..1b0d87fca0 100644 --- a/crates/semantic_index/src/project_index_debug_view.rs +++ b/crates/semantic_index/src/project_index_debug_view.rs @@ -6,7 +6,7 @@ use gpui::{ }; use project::WorktreeId; use settings::Settings; -use std::{path::Path, sync::Arc}; +use std::{ops::Range, path::Path, sync::Arc}; use theme::ThemeSettings; use ui::prelude::*; use workspace::item::Item; @@ -224,10 +224,9 @@ impl Render for ProjectIndexDebugView { .into_any_element() } else { let mut list = uniform_list( - cx.entity().clone(), "ProjectIndexDebugView", self.rows.len(), - move |this, range, _, cx| { + cx.processor(move |this, range: Range, _, cx| { this.rows[range] .iter() .enumerate() @@ -262,7 +261,7 @@ impl Render for ProjectIndexDebugView { })), }) .collect() - }, + }), ) .track_scroll(self.list_scroll_handle.clone()) .size_full() diff --git a/crates/zed/src/zed/component_preview.rs b/crates/zed/src/zed/component_preview.rs index 32bbc30619..d77076aff8 100644 --- a/crates/zed/src/zed/component_preview.rs +++ b/crates/zed/src/zed/component_preview.rs @@ -5,6 +5,7 @@ mod persistence; mod preview_support; +use std::ops::Range; use std::sync::Arc; use std::iter::Iterator; @@ -780,10 +781,9 @@ impl Render for ComponentPreview { .h_full() .child( gpui::uniform_list( - cx.entity().clone(), "component-nav", sidebar_entries.len(), - move |this, range, _window, cx| { + cx.processor(move |this, range: Range, _window, cx| { range .filter_map(|ix| { if ix < sidebar_entries.len() { @@ -797,7 +797,7 @@ impl Render for ComponentPreview { } }) .collect() - }, + }), ) .track_scroll(self.nav_scroll_handle.clone()) .pt_4()