Skip to main content

gpui_kit_grid/state/
grouping.rs

1//! Grouping and expansion in the grid state: the grouping levels and
2//! aggregates of the view, the expansion the grid owns, the one path each
3//! change takes and the events it emits.
4//!
5//! The grid owns the expansion as it owns the quick filter: it lives in the
6//! view ([`ViewSpec::expansion`]) and survives every new view. Toggles are
7//! kept by [`GroupPath`] and by row key, so a change of grouping silences
8//! them and going back brings them back; only a new default depth clears
9//! them.
10
11use std::{mem, sync::Arc};
12
13use gpui_kit::Context;
14
15use super::{
16    ColumnOf, KeyOf, LiveGridDelegate, LiveGridEvent, LiveGridState,
17    events::{ExpandTarget, GroupOrigin},
18    projection::PendingWork,
19};
20use crate::{
21    engine::{
22        Aggregate, DisplayRow, ExpandDepth, Expansion, GroupBy, GroupPath, Structure, TreeSpec,
23        ViewSpec,
24    },
25    model::GridRow,
26};
27
28/// Counts the edits of the view's expansion. A job and a held order
29/// remember the revision their lines are laid out for, and lines of an
30/// older one are laid out again when shown.
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
32pub(super) struct ExpansionRevision(u64);
33
34impl ExpansionRevision {
35    fn next(self) -> Self {
36        Self(self.0 + 1)
37    }
38}
39
40impl<D: LiveGridDelegate> LiveGridState<D> {
41    /// The grouping levels, the top level first; empty when not grouped.
42    pub fn group_by(&self) -> &[GroupBy<D::Row>] {
43        self.view.group_by()
44    }
45
46    /// Replaces the grouping and rebuilds at once (`GroupChange` with
47    /// `GroupOrigin::Programmatic`). The new order is shown from the top,
48    /// unless the focused line (a row or a group caption) is on screen and
49    /// still shown: it keeps its screen line. Repeated levels are dropped,
50    /// the first one stays; the same grouping again (the same level ids and
51    /// directions) does nothing. Works for columns the group panel does not
52    /// accept. A grouping replaces a tree; no levels turn grouping off and
53    /// leave a tree alone.
54    ///
55    /// The expansion stays: toggles are kept by group path, so those of
56    /// another grouping come back with it.
57    pub fn set_group_by(
58        &mut self,
59        levels: impl IntoIterator<Item = GroupBy<D::Row>>,
60        cx: &mut Context<Self>,
61    ) {
62        self.apply_group_by(levels.into_iter().collect(), GroupOrigin::Programmatic, cx);
63    }
64
65    /// The one path of every grouping change: the group panel and its menu,
66    /// grouping Actions, a layout, `set_group_by`. Drops repeated levels
67    /// (the first one stays) and returns false, doing nothing, if the
68    /// grouping is unchanged. Otherwise releases a held order
69    /// (`OrderHeldChange { held: false }`), emits `GroupChange { origin }`
70    /// and rebuilds at once, as a user action. The new order is shown from
71    /// the top, unless the focused line is on screen and still shown: it
72    /// keeps its screen line (`restore_line_anchor`).
73    pub(crate) fn apply_group_by(
74        &mut self,
75        levels: Vec<GroupBy<D::Row>>,
76        origin: GroupOrigin,
77        cx: &mut Context<Self>,
78    ) -> bool {
79        let mut normalized: Vec<GroupBy<D::Row>> = Vec::with_capacity(levels.len());
80        for level in levels {
81            if !normalized.iter().any(|kept| kept.id() == level.id()) {
82                normalized.push(level);
83            }
84        }
85        if same_levels(&normalized, self.view.group_by()) {
86            return false;
87        }
88        self.view = mem::take(&mut self.view).with_group_by(normalized);
89        self.view_changed(None, Some(origin), cx);
90        true
91    }
92
93    /// The tree of the view, if it builds one; `None` when flat or grouped.
94    pub fn tree(&self) -> Option<&TreeSpec<D::Row>> {
95        self.view.tree()
96    }
97
98    /// The aggregates of group captions, group footers and the totals, by
99    /// column.
100    pub fn aggregates(&self) -> &[(ColumnOf<D>, Aggregate<D::Row>)] {
101        self.view.aggregates()
102    }
103
104    /// Replaces the aggregates of group captions, group footers and the
105    /// totals; a repeated column keeps its last aggregate. The same
106    /// aggregates again (custom ones by identity, in any order) do nothing;
107    /// otherwise the view rebuilds at once, as a user action, keeping the
108    /// scroll position (an aggregate order may move groups), and a held
109    /// order is dropped (`OrderHeldChange { held: false }`). No event of its
110    /// own: the application sets the aggregates, and a layout does not keep
111    /// them.
112    pub fn set_aggregates(
113        &mut self,
114        aggregates: impl IntoIterator<Item = (ColumnOf<D>, Aggregate<D::Row>)>,
115        cx: &mut Context<Self>,
116    ) {
117        let view = self.view.clone().with_aggregates(aggregates);
118        if same_aggregates(view.aggregates(), self.view.aggregates()) {
119            return;
120        }
121        self.view = view;
122        self.restart_projection(cx);
123        cx.notify();
124    }
125
126    /// Sets how deep groups and tree rows are expanded by default
127    /// ([`ExpandDepth::All`] unless set), with nothing toggled. For a grid
128    /// being built; [`Self::set_expand_depth`] changes it later. The
129    /// expansion of a layout given to [`Self::with_layout`], before or after,
130    /// replaces it.
131    pub fn with_expand_depth(mut self, depth: ExpandDepth) -> Self {
132        self.view = mem::take(&mut self.view).with_expansion(Expansion::new(depth));
133        self.expansion_revision = self.expansion_revision.next();
134        // The default layout keeps this depth.
135        self.layout.set_view_depth(depth);
136        self.apply_initial_layout()
137    }
138
139    /// How deep groups and tree rows are expanded by default.
140    pub fn expand_depth(&self) -> ExpandDepth {
141        self.view.expansion().depth()
142    }
143
144    /// Sets the default depth and clears every toggle: expand all
145    /// (`ExpandDepth::All`), collapse all (`Levels(0)`), captions only
146    /// (`Captions`). Emits `ExpansionChange { target: None }` with
147    /// `GroupOrigin::Programmatic`, unless the depth is the same and nothing
148    /// is toggled.
149    pub fn set_expand_depth(&mut self, depth: ExpandDepth, cx: &mut Context<Self>) {
150        self.apply_expand_depth(depth, GroupOrigin::Programmatic, cx);
151    }
152
153    /// [`Self::set_expand_depth`] for `origin`: the Actions and menu items
154    /// of expand all, collapse all and captions only are `User`. Returns
155    /// false if nothing changed.
156    pub(crate) fn apply_expand_depth(
157        &mut self,
158        depth: ExpandDepth,
159        origin: GroupOrigin,
160        cx: &mut Context<Self>,
161    ) -> bool {
162        self.apply_expansion(Expansion::new(depth), None, origin, cx)
163    }
164
165    /// Whether a group or a tree row is expanded now: the view's expansion,
166    /// not the projection shown, which may lag one job behind. The root
167    /// path is expanded. A tree row's depth comes from the shown projection:
168    /// a row it does not hold, or one of a projection that is not a tree,
169    /// is not expanded.
170    pub fn is_expanded(&self, target: &ExpandTarget<KeyOf<D>>) -> bool {
171        let expansion = self.view.expansion();
172        match target {
173            ExpandTarget::Group(path) => {
174                path.is_root() || expansion.is_group_expanded(path, self.group_levels(path))
175            }
176            ExpandTarget::Row(key) => self
177                .tree_row_depth(key)
178                .is_some_and(|depth| expansion.is_row_expanded(key, depth)),
179        }
180    }
181
182    /// Expands or collapses one group or tree row. Returns false, doing
183    /// nothing, if it was so already, for the root path, and for a tree row
184    /// the shown projection does not hold as a tree. Otherwise emits
185    /// `ExpansionChange` with `GroupOrigin::Programmatic` at once; the lines
186    /// are laid out again in the background, without the throttle and the
187    /// reorder hold, and a `ProjectionChange` follows.
188    ///
189    /// A group of another grouping may be toggled too: the toggle shows when
190    /// that grouping comes back. A new row never expands its group: it
191    /// arrives in the group's state.
192    pub fn set_expanded(
193        &mut self,
194        target: ExpandTarget<KeyOf<D>>,
195        expanded: bool,
196        cx: &mut Context<Self>,
197    ) -> bool {
198        self.apply_expanded(target, expanded, GroupOrigin::Programmatic, cx)
199    }
200
201    /// [`Self::set_expanded`] for `origin`: chevrons, keys and menu items
202    /// are `User`.
203    pub(crate) fn apply_expanded(
204        &mut self,
205        target: ExpandTarget<KeyOf<D>>,
206        expanded: bool,
207        origin: GroupOrigin,
208        cx: &mut Context<Self>,
209    ) -> bool {
210        let expansion = self.view.expansion().clone();
211        let expansion = match &target {
212            ExpandTarget::Group(path) => {
213                if path.is_root() {
214                    return false;
215                }
216                expansion.with_group(path.clone(), expanded, self.group_levels(path))
217            }
218            ExpandTarget::Row(key) => {
219                let Some(depth) = self.tree_row_depth(key) else {
220                    return false;
221                };
222                expansion.with_row(key.clone(), expanded, depth)
223            }
224        };
225        self.apply_expansion(expansion, Some(target), origin, cx)
226    }
227
228    /// The one path of every expansion change: puts `expansion` into the
229    /// view and emits `ExpansionChange { target, origin }` at once, before
230    /// the lines are laid out; returns false, doing nothing, if it equals
231    /// the view's. A grouped or tree view lays its lines out again as a user
232    /// action (`expand`): no throttle, no reorder hold, ahead of a
233    /// data-driven rebuild that waits; a held order stays held and is laid
234    /// out when shown. A flat view has nothing to lay out.
235    ///
236    /// A deep toggle (a line and every descendant) builds the whole
237    /// expansion and passes the line as `target`: one event.
238    ///
239    /// A focused line the new expansion hides moves at once to the caption
240    /// or tree row of the outermost collapsed line above it: `FocusChange`
241    /// after `ExpansionChange`, with the same origin.
242    pub(crate) fn apply_expansion(
243        &mut self,
244        expansion: Expansion<KeyOf<D>>,
245        target: Option<ExpandTarget<KeyOf<D>>>,
246        origin: GroupOrigin,
247        cx: &mut Context<Self>,
248    ) -> bool {
249        if expansion == *self.view.expansion() {
250            return false;
251        }
252        self.view = mem::take(&mut self.view).with_expansion(expansion);
253        self.expansion_revision = self.expansion_revision.next();
254        if self.has_structure() {
255            self.pending = self.pending.with(PendingWork::EXPAND);
256        }
257        cx.emit(LiveGridEvent::ExpansionChange { target, origin });
258        self.focus_off_collapsed(origin, cx);
259        // The chevrons follow the view's expansion in the next frame, which
260        // also starts the layout.
261        cx.notify();
262        true
263    }
264
265    /// The levels a group's default expansion is decided by: the view's
266    /// grouping, or the path's own depth for a group of a deeper one.
267    pub(super) fn group_levels(&self, path: &GroupPath) -> usize {
268        self.view
269            .group_by()
270            .len()
271            .max(usize::from(path.depth()) + 1)
272    }
273
274    /// The depth of a tree row in the shown projection; `None` if the
275    /// projection is not a tree or does not hold the row. A row under a
276    /// collapsed ancestor is not on screen: its depth counts the ancestors
277    /// the projection holds.
278    fn tree_row_depth(&self, key: &KeyOf<D>) -> Option<u16> {
279        let (projection, snapshot) = (&self.projection, &self.snapshot);
280        if !projection.is_tree() {
281            return None;
282        }
283        let id = snapshot.row_id(key)?;
284        if let Some(display_ix) = projection.display_ix(id) {
285            return projection.line(display_ix).map(DisplayRow::depth);
286        }
287        if !projection.contains(id) {
288            return None;
289        }
290        let mut row = snapshot.row(id)?;
291        let mut depth = 0u16;
292        // A depth fits `u16`: the bound also ends a cycle of parents.
293        while depth < u16::MAX {
294            let Some(parent) = row
295                .parent_key()
296                .and_then(|parent| snapshot.row_id(parent))
297                .filter(|parent| projection.contains(*parent))
298            else {
299                break;
300            };
301            depth += 1;
302            row = snapshot.row(parent)?;
303        }
304        Some(depth)
305    }
306}
307
308/// Returns true if two lists of levels group alike: the same level ids and
309/// directions, in order. A group key is known by its id.
310fn same_levels<R: GridRow>(a: &[GroupBy<R>], b: &[GroupBy<R>]) -> bool {
311    a.len() == b.len()
312        && a.iter()
313            .zip(b)
314            .all(|(a, b)| a.id() == b.id() && a.direction() == b.direction())
315}
316
317/// Returns true if two views arrange rows alike: both flat, grouped by the
318/// same levels, or trees of the same column. What a `GroupChange` reports.
319pub(super) fn same_structure<R: GridRow>(a: &ViewSpec<R>, b: &ViewSpec<R>) -> bool {
320    match (a.structure(), b.structure()) {
321        (Structure::Flat, Structure::Flat) => true,
322        (Structure::Groups(_), Structure::Groups(_)) => same_levels(a.group_by(), b.group_by()),
323        (Structure::Tree(a), Structure::Tree(b)) => a.column() == b.column(),
324        _ => false,
325    }
326}
327
328/// Returns true if two sets of aggregates are the same, in any order: built-in
329/// ones by kind, custom ones by identity. Columns are unique in each.
330fn same_aggregates<R: GridRow>(
331    a: &[(R::Column, Aggregate<R>)],
332    b: &[(R::Column, Aggregate<R>)],
333) -> bool {
334    a.len() == b.len()
335        && a.iter().all(|(column, aggregate)| {
336            b.iter()
337                .any(|(other, kept)| other == column && same_aggregate(aggregate, kept))
338        })
339}
340
341fn same_aggregate<R: GridRow>(a: &Aggregate<R>, b: &Aggregate<R>) -> bool {
342    match (a, b) {
343        (Aggregate::Custom(a), Aggregate::Custom(b)) => Arc::ptr_eq(a, b),
344        (Aggregate::Custom(_), _) | (_, Aggregate::Custom(_)) => false,
345        _ => mem::discriminant(a) == mem::discriminant(b),
346    }
347}