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 <mikayla@zed.dev>
This commit is contained in:
Ben Kunkle 2025-06-10 13:50:57 -05:00 committed by GitHub
parent c55630889a
commit f567bb52ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 80 additions and 77 deletions

View file

@ -594,10 +594,11 @@ impl Render for ThreadHistory {
view.pr_5() view.pr_5()
.child( .child(
uniform_list( uniform_list(
cx.entity().clone(),
"thread-history", "thread-history",
self.list_item_count(), self.list_item_count(),
Self::list_items, cx.processor(|this, range: Range<usize>, window, cx| {
this.list_items(range, window, cx)
}),
) )
.p_1() .p_1()
.track_scroll(self.scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())

View file

@ -1,4 +1,5 @@
use std::{ use std::{
ops::Range,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
time::Duration, time::Duration,
@ -277,10 +278,9 @@ impl BreakpointList {
let selected_ix = self.selected_ix; let selected_ix = self.selected_ix;
let focus_handle = self.focus_handle.clone(); let focus_handle = self.focus_handle.clone();
uniform_list( uniform_list(
cx.entity(),
"breakpoint-list", "breakpoint-list",
self.breakpoints.len(), self.breakpoints.len(),
move |this, range, window, cx| { cx.processor(move |this, range: Range<usize>, window, cx| {
range range
.clone() .clone()
.zip(&mut this.breakpoints[range]) .zip(&mut this.breakpoints[range])
@ -291,7 +291,7 @@ impl BreakpointList {
.into_any_element() .into_any_element()
}) })
.collect() .collect()
}, }),
) )
.track_scroll(self.scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())
.flex_grow() .flex_grow()

View file

@ -8,7 +8,7 @@ use project::{
ProjectItem as _, ProjectPath, ProjectItem as _, ProjectPath,
debugger::session::{Session, SessionEvent}, debugger::session::{Session, SessionEvent},
}; };
use std::{path::Path, sync::Arc}; use std::{ops::Range, path::Path, sync::Arc};
use ui::{Scrollbar, ScrollbarState, prelude::*}; use ui::{Scrollbar, ScrollbarState, prelude::*};
use workspace::Workspace; use workspace::Workspace;
@ -281,10 +281,11 @@ impl ModuleList {
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
uniform_list( uniform_list(
cx.entity(),
"module-list", "module-list",
self.entries.len(), self.entries.len(),
|this, range, _window, cx| range.map(|ix| this.render_entry(ix, cx)).collect(), cx.processor(|this, range: Range<usize>, _window, cx| {
range.map(|ix| this.render_entry(ix, cx)).collect()
}),
) )
.track_scroll(self.scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())
.size_full() .size_full()

View file

@ -980,10 +980,11 @@ impl Render for VariableList {
.on_action(cx.listener(Self::edit_variable)) .on_action(cx.listener(Self::edit_variable))
.child( .child(
uniform_list( uniform_list(
cx.entity().clone(),
"variable-list", "variable-list",
self.entries.len(), self.entries.len(),
move |this, range, window, cx| this.render_entries(range, window, cx), cx.processor(move |this, range: Range<usize>, window, cx| {
this.render_entries(range, window, cx)
}),
) )
.track_scroll(self.list_handle.clone()) .track_scroll(self.list_handle.clone())
.gap_1_5() .gap_1_5()

View file

@ -722,10 +722,9 @@ impl CompletionsMenu {
let last_rendered_range = self.last_rendered_range.clone(); let last_rendered_range = self.last_rendered_range.clone();
let style = style.clone(); let style = style.clone();
let list = uniform_list( let list = uniform_list(
cx.entity().clone(),
"completions", "completions",
self.entries.borrow().len(), self.entries.borrow().len(),
move |_editor, range, _window, cx| { cx.processor(move |_editor, range: Range<usize>, _window, cx| {
last_rendered_range.borrow_mut().replace(range.clone()); last_rendered_range.borrow_mut().replace(range.clone());
let start_ix = range.start; let start_ix = range.start;
let completions_guard = completions.borrow_mut(); let completions_guard = completions.borrow_mut();
@ -837,7 +836,7 @@ impl CompletionsMenu {
) )
}) })
.collect() .collect()
}, }),
) )
.occlude() .occlude()
.max_h(max_height_in_lines as f32 * window.line_height()) .max_h(max_height_in_lines as f32 * window.line_height())
@ -1452,10 +1451,9 @@ impl CodeActionsMenu {
let actions = self.actions.clone(); let actions = self.actions.clone();
let selected_item = self.selected_item; let selected_item = self.selected_item;
let list = uniform_list( let list = uniform_list(
cx.entity().clone(),
"code_actions_menu", "code_actions_menu",
self.actions.len(), self.actions.len(),
move |_this, range, _, cx| { cx.processor(move |_this, range: Range<usize>, _, cx| {
actions actions
.iter() .iter()
.skip(range.start) .skip(range.start)
@ -1518,7 +1516,7 @@ impl CodeActionsMenu {
) )
}) })
.collect() .collect()
}, }),
) )
.occlude() .occlude()
.max_h(max_height_in_lines as f32 * window.line_height()) .max_h(max_height_in_lines as f32 * window.line_height())

View file

@ -1477,18 +1477,12 @@ impl Render for ExtensionsPage {
return this.py_4().child(self.render_empty_state(cx)); return this.py_4().child(self.render_empty_state(cx));
} }
let extensions_page = cx.entity().clone();
let scroll_handle = self.list.clone(); let scroll_handle = self.list.clone();
this.child( this.child(
uniform_list( uniform_list("entries", count, cx.processor(Self::render_extensions))
extensions_page, .flex_grow()
"entries", .pb_4()
count, .track_scroll(scroll_handle),
Self::render_extensions,
)
.flex_grow()
.pb_4()
.track_scroll(scroll_handle),
) )
.child( .child(
div() div()

View file

@ -55,6 +55,7 @@ use project::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsStore}; use settings::{Settings as _, SettingsStore};
use std::future::Future; use std::future::Future;
use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{collections::HashSet, sync::Arc, time::Duration, usize}; use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames}; use strum::{IntoEnumIterator, VariantNames};
@ -3710,8 +3711,10 @@ impl GitPanel {
.relative() .relative()
.overflow_hidden() .overflow_hidden()
.child( .child(
uniform_list(cx.entity().clone(), "entries", entry_count, { uniform_list(
move |this, range, window, cx| { "entries",
entry_count,
cx.processor(move |this, range: Range<usize>, window, cx| {
let mut items = Vec::with_capacity(range.end - range.start); let mut items = Vec::with_capacity(range.end - range.start);
for ix in range { for ix in range {
@ -3739,8 +3742,8 @@ impl GitPanel {
} }
items items
} }),
}) )
.when( .when(
!self.horizontal_scrollbar.show_track !self.horizontal_scrollbar.show_track
&& self.horizontal_scrollbar.show_scrollbar, && self.horizontal_scrollbar.show_scrollbar,

View file

@ -378,8 +378,6 @@ impl DataTable {
impl Render for DataTable { impl Render for DataTable {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let entity = cx.entity();
div() div()
.font_family(".SystemUIFont") .font_family(".SystemUIFont")
.bg(gpui::white()) .bg(gpui::white())
@ -431,8 +429,10 @@ impl Render for DataTable {
.relative() .relative()
.size_full() .size_full()
.child( .child(
uniform_list(entity, "items", self.quotes.len(), { uniform_list(
move |this, range, _, _| { "items",
self.quotes.len(),
cx.processor(move |this, range: Range<usize>, _, _| {
this.visible_range = range.clone(); this.visible_range = range.clone();
let mut items = Vec::with_capacity(range.end - range.start); let mut items = Vec::with_capacity(range.end - range.start);
for i in range { for i in range {
@ -441,8 +441,8 @@ impl Render for DataTable {
} }
} }
items items
} }),
}) )
.size_full() .size_full()
.track_scroll(self.scroll_handle.clone()), .track_scroll(self.scroll_handle.clone()),
) )

View file

@ -9,10 +9,9 @@ impl Render for UniformListExample {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().size_full().bg(rgb(0xffffff)).child( div().size_full().bg(rgb(0xffffff)).child(
uniform_list( uniform_list(
cx.entity().clone(),
"entries", "entries",
50, 50,
|_this, range, _window, _cx| { cx.processor(|_this, range, _window, _cx| {
let mut items = Vec::new(); let mut items = Vec::new();
for ix in range { for ix in range {
let item = ix + 1; let item = ix + 1;
@ -29,7 +28,7 @@ impl Render for UniformListExample {
); );
} }
items items
}, }),
) )
.h_full(), .h_full(),
) )

View file

@ -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<E, R>(
&self,
f: impl Fn(&mut T, E, &mut Window, &mut Context<T>) -> 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 /// Run something using this entity and cx, when the returned struct is dropped
pub fn on_drop( pub fn on_drop(
&self, &self,

View file

@ -5,10 +5,10 @@
//! elements with uniform height. //! elements with uniform height.
use crate::{ use crate::{
AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, Element, ElementId, Entity, AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Render, ScrollHandle, Size, ListSizingBehavior, Overflow, Pixels, ScrollHandle, Size, StyleRefinement, Styled, Window,
StyleRefinement, Styled, Window, point, size, point, size,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; 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, /// 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. /// uniform_list will only render the visible subset of items.
#[track_caller] #[track_caller]
pub fn uniform_list<I, R, V>( pub fn uniform_list<R>(
view: Entity<V>, id: impl Into<ElementId>,
id: I,
item_count: usize, item_count: usize,
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>, f: impl 'static + Fn(Range<usize>, &mut Window, &mut App) -> Vec<R>,
) -> UniformList ) -> UniformList
where where
I: Into<ElementId>,
R: IntoElement, R: IntoElement,
V: Render,
{ {
let id = id.into(); let id = id.into();
let mut base_style = StyleRefinement::default(); let mut base_style = StyleRefinement::default();
base_style.overflow.y = Some(Overflow::Scroll); base_style.overflow.y = Some(Overflow::Scroll);
let render_range = move |range, window: &mut Window, cx: &mut App| { let render_range = move |range: Range<usize>, window: &mut Window, cx: &mut App| {
view.update(cx, |this, cx| { f(range, window, cx)
f(this, range, window, cx) .into_iter()
.into_iter() .map(|component| component.into_any_element())
.map(|component| component.into_any_element()) .collect()
.collect()
})
}; };
UniformList { UniformList {

View file

@ -303,10 +303,9 @@ impl Render for SyntaxTreeView {
{ {
let layer = layer.clone(); let layer = layer.clone();
rendered = rendered.child(uniform_list( rendered = rendered.child(uniform_list(
cx.entity().clone(),
"SyntaxTreeView", "SyntaxTreeView",
layer.node().descendant_count(), layer.node().descendant_count(),
move |this, range, _, cx| { cx.processor(move |this, range: Range<usize>, _, cx| {
let mut items = Vec::new(); let mut items = Vec::new();
let mut cursor = layer.node().walk(); let mut cursor = layer.node().walk();
let mut descendant_ix = range.start; let mut descendant_ix = range.start;
@ -377,7 +376,7 @@ impl Render for SyntaxTreeView {
} }
} }
items items
}, }),
) )
.size_full() .size_full()
.track_scroll(self.list_scroll_handle.clone()) .track_scroll(self.list_scroll_handle.clone())

View file

@ -4497,8 +4497,10 @@ impl OutlinePanel {
let multi_buffer_snapshot = self let multi_buffer_snapshot = self
.active_editor() .active_editor()
.map(|editor| editor.read(cx).buffer().read(cx).snapshot(cx)); .map(|editor| editor.read(cx).buffer().read(cx).snapshot(cx));
uniform_list(cx.entity().clone(), "entries", items_len, { uniform_list(
move |outline_panel, range, window, cx| { "entries",
items_len,
cx.processor(move |outline_panel, range: Range<usize>, window, cx| {
let entries = outline_panel.cached_entries.get(range); let entries = outline_panel.cached_entries.get(range);
entries entries
.map(|entries| entries.to_vec()) .map(|entries| entries.to_vec())
@ -4555,8 +4557,8 @@ impl OutlinePanel {
), ),
}) })
.collect() .collect()
} }),
}) )
.with_sizing_behavior(ListSizingBehavior::Infer) .with_sizing_behavior(ListSizingBehavior::Infer)
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained) .with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
.with_width_from_item(self.max_width_item_index) .with_width_from_item(self.max_width_item_index)

View file

@ -17,7 +17,7 @@ use gpui::{
use head::Head; use head::Head;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::{sync::Arc, time::Duration}; use std::{ops::Range, sync::Arc, time::Duration};
use ui::{ use ui::{
Color, Divider, Label, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, prelude::*, v_flex,
}; };
@ -760,14 +760,13 @@ impl<D: PickerDelegate> Picker<D> {
match &self.element_container { match &self.element_container {
ElementContainer::UniformList(scroll_handle) => uniform_list( ElementContainer::UniformList(scroll_handle) => uniform_list(
cx.entity().clone(),
"candidates", "candidates",
self.delegate.match_count(), self.delegate.match_count(),
move |picker, visible_range, window, cx| { cx.processor(move |picker, visible_range: Range<usize>, window, cx| {
visible_range visible_range
.map(|ix| picker.render_element(window, cx, ix)) .map(|ix| picker.render_element(window, cx, ix))
.collect() .collect()
}, }),
) )
.with_sizing_behavior(sizing_behavior) .with_sizing_behavior(sizing_behavior)
.when_some(self.widest_item, |el, widest_item| { .when_some(self.widest_item, |el, widest_item| {

View file

@ -4914,8 +4914,8 @@ impl Render for ProjectPanel {
) )
.track_focus(&self.focus_handle(cx)) .track_focus(&self.focus_handle(cx))
.child( .child(
uniform_list(cx.entity().clone(), "entries", item_count, { uniform_list("entries", item_count, {
|this, range, window, cx| { cx.processor(|this, range: Range<usize>, window, cx| {
let mut items = Vec::with_capacity(range.end - range.start); let mut items = Vec::with_capacity(range.end - range.start);
this.for_each_visible_entry( this.for_each_visible_entry(
range, range,
@ -4926,7 +4926,7 @@ impl Render for ProjectPanel {
}, },
); );
items items
} })
}) })
.when(show_indent_guides, |list| { .when(show_indent_guides, |list| {
list.with_decoration( list.with_decoration(

View file

@ -6,7 +6,7 @@ use gpui::{
}; };
use project::WorktreeId; use project::WorktreeId;
use settings::Settings; use settings::Settings;
use std::{path::Path, sync::Arc}; use std::{ops::Range, path::Path, sync::Arc};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::prelude::*; use ui::prelude::*;
use workspace::item::Item; use workspace::item::Item;
@ -224,10 +224,9 @@ impl Render for ProjectIndexDebugView {
.into_any_element() .into_any_element()
} else { } else {
let mut list = uniform_list( let mut list = uniform_list(
cx.entity().clone(),
"ProjectIndexDebugView", "ProjectIndexDebugView",
self.rows.len(), self.rows.len(),
move |this, range, _, cx| { cx.processor(move |this, range: Range<usize>, _, cx| {
this.rows[range] this.rows[range]
.iter() .iter()
.enumerate() .enumerate()
@ -262,7 +261,7 @@ impl Render for ProjectIndexDebugView {
})), })),
}) })
.collect() .collect()
}, }),
) )
.track_scroll(self.list_scroll_handle.clone()) .track_scroll(self.list_scroll_handle.clone())
.size_full() .size_full()

View file

@ -5,6 +5,7 @@
mod persistence; mod persistence;
mod preview_support; mod preview_support;
use std::ops::Range;
use std::sync::Arc; use std::sync::Arc;
use std::iter::Iterator; use std::iter::Iterator;
@ -780,10 +781,9 @@ impl Render for ComponentPreview {
.h_full() .h_full()
.child( .child(
gpui::uniform_list( gpui::uniform_list(
cx.entity().clone(),
"component-nav", "component-nav",
sidebar_entries.len(), sidebar_entries.len(),
move |this, range, _window, cx| { cx.processor(move |this, range: Range<usize>, _window, cx| {
range range
.filter_map(|ix| { .filter_map(|ix| {
if ix < sidebar_entries.len() { if ix < sidebar_entries.len() {
@ -797,7 +797,7 @@ impl Render for ComponentPreview {
} }
}) })
.collect() .collect()
}, }),
) )
.track_scroll(self.nav_scroll_handle.clone()) .track_scroll(self.nav_scroll_handle.clone())
.pt_4() .pt_4()