Skip to main content

gpui_kit_grid/state/
edit.rs

1//! Editing cells: the types of an edit, the editor of a column, what the
2//! delegate's edit hooks are told, where the grid keeps the editors and the
3//! edit seams, and the edit session.
4//!
5//! A column takes edits once the application gives it an editor
6//! (`with_editor`). An edit starts with F2, Backspace or Delete, typing, a
7//! double click or `start_edit`; the editor opens a field in the cell (a
8//! host in the body's row, `element.rs`), commits at once, or refuses. One
9//! path commits every value (`commit_value`): the unchanged check,
10//! `is_editable`, `validate_edit`, then `EditCommit`. A press outside the
11//! field and a focus leaving it end the edit as the edit policy decides.
12//! The keys of the field are Actions bound in `LiveGridEditor`. The session
13//! follows live data too (`follow_edit_rows`, `end_hidden_edit`).
14
15use std::{cell::Cell, rc::Rc};
16
17use gpui_kit::base::{Align, POPUP_PRIORITY, Placement, Positioner, actions::Cancel};
18use gpui_kit::component::{ActiveTheme as _, Icon, IconName, Size as ComponentSize};
19use gpui_kit::{
20    Anchor, AnyElement, AnyWindowHandle, App, Bounds, Context, Div, FocusHandle,
21    InteractiveElement, IntoElement, KeyDownEvent, Modifiers, MouseButton, MouseDownEvent,
22    MouseUpEvent, ParentElement as _, Pixels, Point, Role, SharedString, Stateful,
23    StatefulInteractiveElement as _, Styled as _, Subscription, Window, anchored, deferred, div,
24    point, prelude::FluentBuilder as _, px, size,
25};
26
27use rust_i18n::t;
28
29use super::{
30    ColumnOf, KeyOf, LiveGridDelegate, LiveGridEvent, LiveGridState, ViewColumnIx,
31    a11y::data_row_id_name,
32    columns::column_of,
33    editors::editor_size,
34    frame::{HostCell, RowPlanner},
35    key_hash,
36    selection::{Navigation, is_past_drag_threshold},
37};
38use crate::{
39    actions::{
40        CancelEdit, CommitEdit, CommitEditDown, CommitEditNext, CommitEditPrev, CommitEditUp,
41        EDITOR_KEY_CONTEXT, StartEdit, StartEditEmpty,
42    },
43    element::{CellHost, HostLayer, HostPlan},
44    model::{CellValue, ColumnIx, ColumnSet, GridColumns, GridRow, OwnedValue, display_text},
45    policy::{
46        AfterCommit, CellMove, DraftAction, EditCommitKey, EditEnd, EditGesture,
47        EditGestureContext, EditPolicy, EditPolicyContext, StandardEditGesture, StandardEditPolicy,
48    },
49    store::ChangeSet,
50};
51
52/// One commit, until `LiveGridState::resolve_edit` answers it. Opaque,
53/// unique within its grid.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub struct EditId(u64);
56
57impl EditId {
58    #[cfg_attr(not(test), expect(dead_code, reason = "the edit slot numbers commits"))]
59    pub(crate) const fn new(id: u64) -> Self {
60        Self(id)
61    }
62
63    /// The id as a number, unique within its grid.
64    pub const fn get(self) -> u64 {
65        self.0
66    }
67}
68
69/// Why a value is refused: the editor could not parse it, the delegate's
70/// `validate_edit` refused it, or the application rejected the commit.
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct EditError {
73    message: SharedString,
74}
75
76impl EditError {
77    /// A refusal with the message shown to the user: a sentence that says
78    /// what is wrong and how to fix it.
79    pub fn new(message: impl Into<SharedString>) -> Self {
80        Self {
81            message: message.into(),
82        }
83    }
84
85    /// The message.
86    pub fn message(&self) -> &SharedString {
87        &self.message
88    }
89}
90
91/// Who started an edit.
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
93#[non_exhaustive]
94pub enum EditOrigin {
95    /// A key, a press or a menu command.
96    User,
97    /// A call of the grid API.
98    Programmatic,
99}
100
101/// What started an edit, as [`EditGesture`] and [`CellEditor`] see it.
102#[derive(Clone, Debug, PartialEq, Eq)]
103#[non_exhaustive]
104pub enum EditInput {
105    /// `StartEdit` (F2) or a menu command: the editor starts from the
106    /// value.
107    Command,
108    /// `StartEditEmpty` (Backspace, Delete): the editor starts empty.
109    Clear,
110    /// Typing on a focused cell: the editor starts from the typed text.
111    Typing(SharedString),
112    /// A press on the cell; `click_count` 2 for a double click.
113    Press {
114        /// The press's place in a series.
115        click_count: usize,
116    },
117    /// `LiveGridState::start_edit`, or a commit through a
118    /// `CellEditHandle`.
119    Programmatic,
120}
121
122/// Why an editor closed without `EditCommit`.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
124#[non_exhaustive]
125pub enum EditCancelReason {
126    /// `CancelEdit` (Escape).
127    Escape,
128    /// The focus left with a draft [`EditPolicy::on_blur`] cancels.
129    Blur,
130    /// The value is the one the edit started from
131    /// ([`EditPolicy::commit_unchanged`]).
132    Unchanged,
133    /// The row left the store.
134    RowRemoved,
135    /// The row left the shown lines without a user action
136    /// ([`EditPolicy::on_hidden`]).
137    RowHidden,
138    /// `LiveGridState::cancel_edit`, or another edit replaced it.
139    Programmatic,
140}
141
142/// The mark of an open editor: what its frame and its message show.
143#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
144#[non_exhaustive]
145pub enum EditorMark {
146    /// Nothing to show.
147    #[default]
148    None,
149    /// The draft is refused: it does not parse, or `validate_edit` refused
150    /// it.
151    Invalid,
152    /// The store changed the cell while its draft is open.
153    Conflict,
154}
155
156/// What a cell's commits look like now.
157#[derive(Clone, Debug, PartialEq, Eq)]
158#[non_exhaustive]
159pub enum EditState {
160    /// The commit went out; no answer yet.
161    Pending,
162    /// The answer was `Ok`; the cell waits for the store to have the value.
163    Accepted,
164    /// The answer was `Err`: the cell shows the store's value and this
165    /// error until it is edited again.
166    Failed(EditError),
167}
168
169/// Edits the cells of a column ([`LiveGridState::with_editor`]). Asked
170/// once per edit start, on the UI thread, outside prepaint.
171pub trait CellEditor<R: GridRow>: 'static {
172    /// What to do with a start: open a field, commit at once, or refuse.
173    fn open(&self, start: &EditorStart<'_, R>, window: &mut Window, cx: &mut App) -> EditorOpen;
174}
175
176/// What an editor does with a start.
177#[non_exhaustive]
178pub enum EditorOpen {
179    /// A field in the cell until a commit or a cancel.
180    Field(Box<dyn EditorField>),
181    /// Commits this value at once, with no field: a checkbox toggles.
182    Commit(OwnedValue),
183    /// Takes no edit from this input: a select editor ignores typing. The
184    /// input goes on as if the column had no editor.
185    Refuse,
186}
187
188/// One open editor: its element in the cell, its draft and its focus.
189pub trait EditorField: 'static {
190    /// The element in the cell, built in prepaint every frame while the
191    /// editor is open. Must not read, update or notify the grid entity.
192    fn render(&self, cell: &EditorCell<'_>, window: &mut Window, cx: &mut App) -> AnyElement;
193
194    /// Moves the keyboard focus into the editor; once, when it opens.
195    fn focus(&self, window: &mut Window, cx: &mut App);
196
197    /// The draft as a value of the column; `Err` if it is not one: the
198    /// editor stays open and shows the message.
199    fn value(&self, cx: &App) -> Result<OwnedValue, EditError>;
200
201    /// Replaces the draft (`DraftAction::Replace`).
202    fn set_value(&self, value: &OwnedValue, window: &mut Window, cx: &mut App);
203
204    /// Returns true if the user changed the draft since the editor opened.
205    fn is_dirty(&self, cx: &App) -> bool;
206}
207
208/// What a [`CellEditor`] knows about a start.
209pub struct EditorStart<'a, R: GridRow> {
210    row: &'a R,
211    column: R::Column,
212    value: CellValue<'a>,
213    input: &'a EditInput,
214    size: ComponentSize,
215    title: &'a SharedString,
216}
217
218impl<'a, R: GridRow> EditorStart<'a, R> {
219    /// The row.
220    pub fn row(&self) -> &'a R {
221        self.row
222    }
223
224    /// The row's key.
225    pub fn key(&self) -> &'a R::Key {
226        self.row.key()
227    }
228
229    /// The column.
230    pub fn column(&self) -> R::Column {
231        self.column
232    }
233
234    /// The cell's value: the store's, or the value a commit of the cell
235    /// still waits with.
236    pub fn value(&self) -> CellValue<'a> {
237        self.value
238    }
239
240    /// What started the edit.
241    pub fn input(&self) -> &'a EditInput {
242        self.input
243    }
244
245    /// The size of a gpui-kit field that fits the grid's rows.
246    pub fn size(&self) -> ComponentSize {
247        self.size
248    }
249
250    /// The column's title: the field's accessible name.
251    pub fn title(&self) -> &'a SharedString {
252        self.title
253    }
254}
255
256/// What an [`EditorField`] knows about its cell when it renders.
257pub struct EditorCell<'a> {
258    bounds: Bounds<Pixels>,
259    size: ComponentSize,
260    mark: EditorMark,
261    message: Option<&'a SharedString>,
262    title: &'a SharedString,
263    popup: &'a FieldPopup,
264}
265
266impl<'a> EditorCell<'a> {
267    /// The cell in window coordinates, without a tree row's indent.
268    pub fn bounds(&self) -> Bounds<Pixels> {
269        self.bounds
270    }
271
272    /// The size of a gpui-kit field that fits the grid's rows.
273    pub fn size(&self) -> ComponentSize {
274        self.size
275    }
276
277    /// The editor's mark.
278    pub fn mark(&self) -> EditorMark {
279        self.mark
280    }
281
282    /// The message of the mark, if any.
283    pub fn message(&self) -> Option<&'a SharedString> {
284        self.message
285    }
286
287    /// The column's title: the field's accessible name.
288    pub fn title(&self) -> &'a SharedString {
289        self.title
290    }
291
292    /// Places `content` under the cell on a popover surface, as the
293    /// built-in list and calendar are: above the grid, on the other side of
294    /// the cell when there is no room below, and the field's own for
295    /// presses, which do not end the edit. The message of the mark moves
296    /// above the cell meanwhile. One per frame.
297    pub fn popup(&self, content: impl IntoElement, cx: &App) -> AnyElement {
298        use gpui_kit::component::ThemeStyled as _;
299
300        self.popup.shown.set(true);
301        let bounds = self.popup.bounds.clone();
302        deferred(
303            Positioner::side(self.bounds)
304                .placement(Placement::Bottom)
305                .align(Align::Start)
306                .offset(px(2.))
307                .margin(px(8.))
308                .on_position(move |resolved| bounds.set(Some(resolved.bounds)))
309                .child(div().occlude().popover_style(cx).child(content)),
310        )
311        .with_priority(POPUP_PRIORITY)
312        .into_any_element()
313    }
314}
315
316/// The layer a field shows under its cell ([`EditorCell::popup`]): whether
317/// it is shown this frame, and where it was in the last one.
318#[derive(Default)]
319struct FieldPopup {
320    shown: Cell<bool>,
321    bounds: Rc<Cell<Option<Bounds<Pixels>>>>,
322}
323
324/// What [`LiveGridDelegate::is_editable`] knows about a cell.
325pub struct EditableContext<'a, R: GridRow> {
326    row: &'a R,
327    column: R::Column,
328    value: CellValue<'a>,
329    input: &'a EditInput,
330}
331
332impl<'a, R: GridRow> EditableContext<'a, R> {
333    /// The row.
334    pub fn row(&self) -> &'a R {
335        self.row
336    }
337
338    /// The row's key.
339    pub fn key(&self) -> &'a R::Key {
340        self.row.key()
341    }
342
343    /// The column.
344    pub fn column(&self) -> R::Column {
345        self.column
346    }
347
348    /// The cell's value.
349    pub fn value(&self) -> CellValue<'a> {
350        self.value
351    }
352
353    /// What would start the edit.
354    pub fn input(&self) -> &'a EditInput {
355        self.input
356    }
357}
358
359/// What [`LiveGridDelegate::validate_edit`] knows about a commit.
360pub struct EditValidateContext<'a, R: GridRow> {
361    row: &'a R,
362    column: R::Column,
363    value: &'a OwnedValue,
364    base: &'a OwnedValue,
365    current: &'a OwnedValue,
366}
367
368impl<'a, R: GridRow> EditValidateContext<'a, R> {
369    /// The row, as the store has it now.
370    pub fn row(&self) -> &'a R {
371        self.row
372    }
373
374    /// The row's key.
375    pub fn key(&self) -> &'a R::Key {
376        self.row.key()
377    }
378
379    /// The column.
380    pub fn column(&self) -> R::Column {
381        self.column
382    }
383
384    /// The value to commit.
385    pub fn value(&self) -> &'a OwnedValue {
386        self.value
387    }
388
389    /// The cell's value when the edit started.
390    pub fn base(&self) -> &'a OwnedValue {
391        self.base
392    }
393
394    /// The cell's value in the store now: it differs from
395    /// [`Self::base`] after a concurrent change.
396    pub fn current(&self) -> &'a OwnedValue {
397        self.current
398    }
399}
400
401/// What [`LiveGridDelegate::render_edit_message`] knows about the open
402/// editor's message.
403pub struct EditMessageContext<'a> {
404    mark: EditorMark,
405    message: &'a SharedString,
406    cell_bounds: Bounds<Pixels>,
407    size: ComponentSize,
408}
409
410impl<'a> EditMessageContext<'a> {
411    /// What the message is about: a refused draft or a conflict.
412    pub fn mark(&self) -> EditorMark {
413        self.mark
414    }
415
416    /// The message.
417    pub fn message(&self) -> &'a SharedString {
418        self.message
419    }
420
421    /// The edited cell in window coordinates.
422    pub fn cell_bounds(&self) -> Bounds<Pixels> {
423        self.cell_bounds
424    }
425
426    /// The grid's size.
427    pub fn size(&self) -> ComponentSize {
428        self.size
429    }
430
431    /// The default presentation: one line with an icon, `circle-alert` in
432    /// the danger colour for a refused draft, `triangle-alert` in the
433    /// warning colour for a conflict, on a popover surface. The grid places
434    /// it under the cell.
435    pub fn default_popup(&self, _window: &mut Window, cx: &mut App) -> AnyElement {
436        use gpui_kit::component::ThemeStyled as _;
437
438        let theme = cx.theme();
439        let (icon, color) = match self.mark {
440            EditorMark::Conflict => (IconName::TriangleAlert, theme.warning),
441            _ => (IconName::CircleAlert, theme.danger),
442        };
443        div()
444            .flex()
445            .items_center()
446            .gap(px(4.))
447            .px(px(6.))
448            .py(px(2.))
449            .text_xs()
450            .popover_style(cx)
451            .child(Icon::new(icon).size_3().text_color(color))
452            .child(self.message.clone())
453            .into_any_element()
454    }
455}
456
457/// An open edit: the cell, the value it started from, the editor's field
458/// and its mark, and the focus handle of the field's host.
459struct EditSession<K, C> {
460    key: K,
461    column: C,
462    /// The value the edit started from.
463    base: OwnedValue,
464    /// The store's value of the cell at the last snapshot: a change asks
465    /// the edit policy once.
466    seen: OwnedValue,
467    /// What started it: `is_editable` is asked again with it at the commit.
468    input: EditInput,
469    field: Box<dyn EditorField>,
470    /// The column's title: the field's accessible name.
471    title: SharedString,
472    mark: EditorMark,
473    message: Option<SharedString>,
474    /// The handle the host tracks: the field inside is its descendant.
475    editor_focus: FocusHandle,
476    /// The host's visible part in the last frame: a press inside it is the
477    /// field's own.
478    host_bounds: Cell<Option<Bounds<Pixels>>>,
479    /// The message's layer in the last frame: a press on it is the field's
480    /// too.
481    popup_bounds: Rc<Cell<Option<Bounds<Pixels>>>>,
482    /// The field's own layer, such as a list.
483    field_popup: FieldPopup,
484    /// Brings the edited cell back on screen; any key in the host calls it.
485    reveal: Rc<dyn Fn(&mut App)>,
486    /// The window of the field, to give the grid the focus back when the
487    /// edited row leaves the store.
488    window: AnyWindowHandle,
489    _focus_out: Subscription,
490}
491
492/// The open edit of a grid, and the number of the next commit.
493pub(super) struct EditSlot<K, C> {
494    session: Option<EditSession<K, C>>,
495    next_edit: u64,
496    /// A single press on a data cell, and where it went down: released
497    /// there, it is offered to the edit gesture (`Press { click_count: 1 }`).
498    single_press: Option<(K, C, Point<Pixels>)>,
499    /// The columns with an editor: their elements may commit (`CellEditHandle`).
500    edited_columns: ColumnSet,
501}
502
503impl<K, C> Default for EditSlot<K, C> {
504    fn default() -> Self {
505        Self {
506            session: None,
507            next_edit: 0,
508            single_press: None,
509            edited_columns: ColumnSet::EMPTY,
510        }
511    }
512}
513
514impl<K: PartialEq, C: Copy> EditSlot<K, C> {
515    /// Returns true if `column` has an editor.
516    pub(super) fn has_editor(&self, column: ColumnIx) -> bool {
517        self.edited_columns.contains(column)
518    }
519
520    /// Returns true while an editor is open.
521    pub(super) fn is_open(&self) -> bool {
522        self.session.is_some()
523    }
524
525    /// The edited column, if the open editor is in the row of `key`.
526    pub(super) fn column_in(&self, key: &K) -> Option<C> {
527        self.session
528            .as_ref()
529            .and_then(|session| (session.key == *key).then_some(session.column))
530    }
531
532    /// Returns true if the keyboard focus is in the open editor.
533    pub(super) fn contains_focus(&self, window: &Window, cx: &App) -> bool {
534        self.session
535            .as_ref()
536            .is_some_and(|session| session.editor_focus.contains_focused(window, cx))
537    }
538
539    /// A new commit's id.
540    fn next_id(&mut self) -> EditId {
541        self.next_edit += 1;
542        EditId(self.next_edit)
543    }
544}
545
546/// How an attempt to commit a value ended.
547#[derive(Clone, Copy, Debug, PartialEq, Eq)]
548enum CommitOutcome {
549    /// `EditCommit` went out.
550    Committed,
551    /// The editor closed without a commit (`EditCancel`): the value was
552    /// unchanged, the cell is no longer editable, or the row is gone.
553    Closed,
554    /// The value was refused: the field stays open with the message.
555    Refused,
556}
557
558/// The editors of a grid's columns and its edit seams.
559pub(super) struct EditSetup<R: GridRow> {
560    /// By schema index; `None`: the column is read-only.
561    editors: Vec<Option<Rc<dyn CellEditor<R>>>>,
562    gesture: Box<dyn EditGesture>,
563    policy: Box<dyn EditPolicy>,
564}
565
566impl<R: GridRow> Default for EditSetup<R> {
567    fn default() -> Self {
568        Self {
569            editors: Vec::new(),
570            gesture: Box::new(StandardEditGesture),
571            policy: Box::new(StandardEditPolicy),
572        }
573    }
574}
575
576impl<R: GridRow> EditSetup<R> {
577    fn editor(&self, column: ColumnIx) -> Option<&Rc<dyn CellEditor<R>>> {
578        self.editors.get(column.get())?.as_ref()
579    }
580
581    fn set_editor(&mut self, column: ColumnIx, editor: Option<Rc<dyn CellEditor<R>>>) {
582        if self.editors.len() <= column.get() {
583            self.editors.resize_with(column.get() + 1, || None);
584        }
585        self.editors[column.get()] = editor;
586    }
587
588    /// The columns with an editor.
589    fn edited_columns(&self) -> ColumnSet {
590        let mut columns = ColumnSet::EMPTY;
591        for (ix, editor) in self.editors.iter().enumerate() {
592            if editor.is_some() {
593                columns.insert(ColumnIx::new(ix as u16));
594            }
595        }
596        columns
597    }
598
599    /// Which input opens an editor, and where the focus goes after a commit.
600    pub(super) fn gesture(&self) -> &dyn EditGesture {
601        &*self.gesture
602    }
603
604    /// How an edit behaves around its commit and under live data.
605    pub(super) fn policy(&self) -> &dyn EditPolicy {
606        &*self.policy
607    }
608}
609
610impl<D: LiveGridDelegate> LiveGridState<D> {
611    /// Gives `column`'s cells an editor; a column without one is read-only.
612    /// The delegate's `is_editable` can still refuse a cell.
613    pub fn with_editor(mut self, column: ColumnOf<D>, editor: impl CellEditor<D::Row>) -> Self {
614        self.edit_setup
615            .set_editor(column.ix(), Some(Rc::new(editor)));
616        self.edit.edited_columns = self.edit_setup.edited_columns();
617        self
618    }
619
620    /// Sets or removes the editor of `column`'s cells.
621    pub fn set_editor(
622        &mut self,
623        column: ColumnOf<D>,
624        editor: Option<Rc<dyn CellEditor<D::Row>>>,
625        cx: &mut Context<Self>,
626    ) {
627        self.edit_setup.set_editor(column.ix(), editor);
628        self.edit.edited_columns = self.edit_setup.edited_columns();
629        cx.notify();
630    }
631
632    /// Returns true if `column`'s cells have an editor.
633    pub fn has_editor(&self, column: ColumnOf<D>) -> bool {
634        self.edit_setup.editor(column.ix()).is_some()
635    }
636
637    /// Sets which input opens an editor and where the focus goes after a
638    /// commit (default [`StandardEditGesture`], Excel's).
639    pub fn with_edit_gesture(mut self, gesture: impl EditGesture) -> Self {
640        self.edit_setup.gesture = Box::new(gesture);
641        self
642    }
643
644    /// Sets how an edit behaves around its commit and under live data
645    /// (default [`StandardEditPolicy`]).
646    pub fn with_edit_policy(mut self, policy: impl EditPolicy) -> Self {
647        self.edit_setup.policy = Box::new(policy);
648        self
649    }
650
651    /// Opens the editor of a cell (`EditInput::Programmatic`,
652    /// `EditOrigin::Programmatic`): the cell takes the focus and scrolls
653    /// into view. An open editor elsewhere ends first, as a press outside
654    /// it would end it. Returns false, doing nothing, if the cell is not
655    /// shown, has no editor, or `is_editable` refuses it.
656    pub fn start_edit(
657        &mut self,
658        key: &KeyOf<D>,
659        column: ColumnOf<D>,
660        window: &mut Window,
661        cx: &mut Context<Self>,
662    ) -> bool {
663        self.begin_edit(
664            key.clone(),
665            column,
666            EditInput::Programmatic,
667            EditOrigin::Programmatic,
668            Modifiers::default(),
669            window,
670            cx,
671        )
672    }
673
674    /// Commits the open editor as Enter would, without moving the focus.
675    /// Returns false if no editor is open or its draft is refused: it stays
676    /// open with the message.
677    pub fn commit_edit(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
678        self.edit.is_open() && self.commit_field(Some(window), cx) != CommitOutcome::Refused
679    }
680
681    /// Closes the open editor without a commit
682    /// (`EditCancelReason::Programmatic`). Returns false if none is open.
683    pub fn cancel_edit(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
684        self.cancel_field(EditCancelReason::Programmatic, Some(window), cx)
685    }
686
687    /// The cell being edited: its key and column.
688    pub fn editing_cell(&self) -> Option<(&KeyOf<D>, ColumnOf<D>)> {
689        let session = self.edit.session.as_ref()?;
690        Some((&session.key, session.column))
691    }
692
693    /// Returns true while an editor is open.
694    pub fn is_editing(&self) -> bool {
695        self.edit.is_open()
696    }
697
698    /// The open editor's message and its mark: a refused draft
699    /// (`EditorMark::Invalid`), for an application that shows it itself
700    /// (`LiveGridDelegate::render_edit_message` returning `None`).
701    pub fn edit_message(&self) -> Option<(EditorMark, &SharedString)> {
702        let session = self.edit.session.as_ref()?;
703        Some((session.mark, session.message.as_ref()?))
704    }
705
706    /// Returns true if the keyboard focus is on the grid's rows: on the
707    /// root, or in the open editor's host. The root's focus ring and the
708    /// focused cell's outline follow it. The group panel is outside.
709    pub(crate) fn is_body_focused(&self, window: &Window, cx: &App) -> bool {
710        self.focus_handle.is_focused(window) || self.edit.contains_focus(window, cx)
711    }
712
713    /// The focused data cell: its key and column.
714    fn focused_data_cell(&self) -> Option<(KeyOf<D>, ColumnOf<D>)> {
715        let focus = self.selection.focus()?;
716        Some((focus.row()?.clone(), focus.column()))
717    }
718
719    /// Opens the editor of a cell for `input`. The one path of every start:
720    /// the cell must be a shown cell of a data row in a visible column with
721    /// an editor; the gesture must agree to typing and presses
722    /// (`EditGesture::starts_edit`) and the delegate to the cell
723    /// (`is_editable`). The editor then opens a field, commits at once, or
724    /// refuses. Returns true if it did not refuse.
725    #[allow(clippy::too_many_arguments)]
726    fn begin_edit(
727        &mut self,
728        key: KeyOf<D>,
729        column: ColumnOf<D>,
730        input: EditInput,
731        origin: EditOrigin,
732        modifiers: Modifiers,
733        window: &mut Window,
734        cx: &mut Context<Self>,
735    ) -> bool {
736        let snapshot = self.snapshot.clone();
737        let Some(id) = snapshot.row_id(&key) else {
738            return false;
739        };
740        let Some(row) = snapshot.row(id).cloned() else {
741            return false;
742        };
743        if self.projection.display_ix(id).is_none()
744            || self.columns.view_column_ix(column.ix()).is_none()
745        {
746            return false;
747        }
748        let Some(editor) = self.edit_setup.editor(column.ix()).cloned() else {
749            return false;
750        };
751        let content = self.columns.column(column.ix()).content;
752        if matches!(input, EditInput::Typing(_) | EditInput::Press { .. })
753            && !self
754                .edit_setup
755                .gesture()
756                .starts_edit(&EditGestureContext::new(input.clone(), modifiers, content))
757        {
758            return false;
759        }
760        // The value the cell shows: a waiting commit's, or the store's.
761        let shown = self.pending_edits.overlay(&key, column).cloned();
762        let value = shown
763            .as_ref()
764            .map_or_else(|| row.value(column), OwnedValue::as_cell);
765        let editable = self.delegate.is_editable(
766            &EditableContext {
767                row: &*row,
768                column,
769                value,
770                input: &input,
771            },
772            cx,
773        );
774        if !editable {
775            return false;
776        }
777        // An open editor elsewhere ends first, as a press outside it.
778        if self.edit.is_open() {
779            self.end_edit_on_blur(window, cx);
780        }
781        let base = OwnedValue::from_cell(value);
782        let title = self.columns.column(column.ix()).title.clone();
783        let opened = editor.open(
784            &EditorStart {
785                row: &*row,
786                column,
787                value,
788                input: &input,
789                size: editor_size(self.row_height),
790                title: &title,
791            },
792            window,
793            cx,
794        );
795        match opened {
796            EditorOpen::Refuse => false,
797            EditorOpen::Commit(value) => {
798                self.focus_edited_cell(&key, column, cx);
799                self.pending_edits.clear_failed(&key, column);
800                self.commit_value(key, column, value, base, &input, Some(window), cx);
801                true
802            }
803            EditorOpen::Field(field) => {
804                self.focus_edited_cell(&key, column, cx);
805                let editor_focus = cx.focus_handle();
806                let focus_out = cx.on_focus_out(&editor_focus, window, |this, _, window, cx| {
807                    this.on_editor_focus_out(window, cx);
808                });
809                let reveal: Rc<dyn Fn(&mut App)> = {
810                    let grid = cx.weak_entity();
811                    Rc::new(move |cx: &mut App| {
812                        let _ = grid.update(cx, |this, cx| {
813                            if this.reveal_edited_cell() {
814                                cx.notify();
815                            }
816                        });
817                    })
818                };
819                field.focus(window, cx);
820                let (grid, refocus) = (cx.weak_entity(), editor_focus.clone());
821                window.defer(cx, move |window, cx| {
822                    let _ = grid.update(cx, |this, cx| this.refocus_field(&refocus, window, cx));
823                });
824                self.pending_edits.clear_failed(&key, column);
825                self.edit.session = Some(EditSession {
826                    key: key.clone(),
827                    column,
828                    seen: OwnedValue::from_cell(row.value(column)),
829                    base,
830                    input,
831                    field,
832                    title,
833                    mark: EditorMark::None,
834                    message: None,
835                    editor_focus,
836                    host_bounds: Cell::new(None),
837                    popup_bounds: Rc::default(),
838                    field_popup: FieldPopup::default(),
839                    reveal,
840                    window: window.window_handle(),
841                    _focus_out: focus_out,
842                });
843                cx.emit(LiveGridEvent::EditStart {
844                    key,
845                    column,
846                    origin,
847                });
848                cx.notify();
849                true
850            }
851        }
852    }
853
854    /// Gives the edited cell the focus, if it lacks it, and scrolls it into
855    /// view.
856    fn focus_edited_cell(&mut self, key: &KeyOf<D>, column: ColumnOf<D>, cx: &mut Context<Self>) {
857        if self.focused_data_cell().as_ref() != Some(&(key.clone(), column)) {
858            self.set_focused_cell(key, Some(column), cx);
859        }
860        self.reveal_focus();
861    }
862
863    /// Scrolls the edited cell into view. Returns true if it scrolled.
864    fn reveal_edited_cell(&mut self) -> bool {
865        let Some(session) = &self.edit.session else {
866            return false;
867        };
868        let Some(display_ix) = self
869            .snapshot
870            .row_id(&session.key)
871            .and_then(|id| self.projection.display_ix(id))
872        else {
873            return false;
874        };
875        let column = session.column.ix();
876        let before = (self.scroll_row, self.scroll_offset, self.scroll_x);
877        self.reveal_row(display_ix);
878        let mut columns = ColumnSet::EMPTY;
879        columns.insert(column);
880        self.reveal_columns(columns);
881        self.clamp_scroll();
882        before != (self.scroll_row, self.scroll_offset, self.scroll_x)
883    }
884
885    /// The store's value of a cell now; `None` if its row is gone.
886    fn store_value(&self, key: &KeyOf<D>, column: ColumnOf<D>) -> Option<OwnedValue> {
887        let row = self.snapshot.row(self.snapshot.row_id(key)?)?;
888        Some(OwnedValue::from_cell(row.value(column)))
889    }
890
891    /// Closes the open field. The grid's root takes the keyboard focus back
892    /// if the field had it; without a window at hand (a snapshot, a new
893    /// order), once the update is done. The order held for the field goes
894    /// to the reorder policy again, as when the context menu closes.
895    fn close_field(
896        &mut self,
897        window: Option<&mut Window>,
898        cx: &mut Context<Self>,
899    ) -> Option<EditSession<KeyOf<D>, ColumnOf<D>>> {
900        let session = self.edit.session.take()?;
901        match window {
902            Some(window) => {
903                if session.editor_focus.contains_focused(window, cx) {
904                    window.focus(&self.focus_handle, cx);
905                }
906            }
907            None => {
908                let (root, window) = (self.focus_handle.clone(), session.window);
909                cx.defer(move |cx| {
910                    let _ = window.update(cx, |_, window, cx| {
911                        if window.focused(cx).is_none() || root.contains_focused(window, cx) {
912                            window.focus(&root, cx);
913                        }
914                    });
915                });
916            }
917        }
918        // After the close's own events.
919        let grid = cx.weak_entity();
920        cx.defer(move |cx| {
921            let _ = grid.update(cx, |this, cx| this.reconsider_held(cx));
922        });
923        cx.notify();
924        Some(session)
925    }
926
927    /// The one path of every commit: a field's draft and a commit with no
928    /// field. The value is checked against the value the edit started from
929    /// (`EditPolicy::commit_unchanged`), the cell against `is_editable`, the
930    /// value against `validate_edit`; then the field closes and
931    /// `EditCommit` goes out.
932    #[allow(clippy::too_many_arguments)]
933    fn commit_value(
934        &mut self,
935        key: KeyOf<D>,
936        column: ColumnOf<D>,
937        value: OwnedValue,
938        base: OwnedValue,
939        input: &EditInput,
940        window: Option<&mut Window>,
941        cx: &mut Context<Self>,
942    ) -> CommitOutcome {
943        let close = |this: &mut Self,
944                     reason: EditCancelReason,
945                     draft: Option<OwnedValue>,
946                     window: Option<&mut Window>,
947                     cx: &mut Context<Self>| {
948            this.close_field(window, cx);
949            cx.emit(LiveGridEvent::EditCancel {
950                key: key.clone(),
951                column,
952                reason,
953                draft,
954            });
955            CommitOutcome::Closed
956        };
957        if value.cmp_total(&base).is_eq() && !self.edit_setup.policy().commit_unchanged() {
958            return close(self, EditCancelReason::Unchanged, None, window, cx);
959        }
960        let snapshot = self.snapshot.clone();
961        let Some(row) = snapshot.row_id(&key).and_then(|id| snapshot.row(id)) else {
962            return close(self, EditCancelReason::RowRemoved, Some(value), window, cx);
963        };
964        let current = OwnedValue::from_cell(row.value(column));
965        let editable = self.delegate.is_editable(
966            &EditableContext {
967                row: &**row,
968                column,
969                value: current.as_cell(),
970                input,
971            },
972            cx,
973        );
974        if !editable {
975            return close(
976                self,
977                EditCancelReason::Programmatic,
978                Some(value),
979                window,
980                cx,
981            );
982        }
983        let valid = self.delegate.validate_edit(
984            &EditValidateContext {
985                row: &**row,
986                column,
987                value: &value,
988                base: &base,
989                current: &current,
990            },
991            cx,
992        );
993        if let Err(error) = valid {
994            if let Some(session) = &mut self.edit.session {
995                session.mark = EditorMark::Invalid;
996                session.message = Some(error.message().clone());
997                cx.notify();
998            }
999            return CommitOutcome::Refused;
1000        }
1001        let edit = self.edit.next_id();
1002        self.close_field(window, cx);
1003        cx.emit(LiveGridEvent::EditCommit {
1004            edit,
1005            key: key.clone(),
1006            column,
1007            value: value.clone(),
1008            base,
1009            current,
1010        });
1011        // The cell shows the value until the commit settles.
1012        self.begin_pending_edit(edit, key, column, value, cx);
1013        CommitOutcome::Committed
1014    }
1015
1016    /// A commit from a cell's element (`CellEditHandle::commit`): as an
1017    /// editor's commit with no field, started by a call. The row must be in
1018    /// the store, the column must have an editor, and `is_editable` must
1019    /// take the cell now; a refused cell or value emits nothing. An editor
1020    /// open elsewhere ends first. Returns true if `EditCommit` went out.
1021    pub(super) fn commit_from_element(
1022        &mut self,
1023        key: &KeyOf<D>,
1024        column: ColumnOf<D>,
1025        value: OwnedValue,
1026        window: &mut Window,
1027        cx: &mut Context<Self>,
1028    ) -> bool {
1029        if self.edit_setup.editor(column.ix()).is_none() {
1030            return false;
1031        }
1032        let snapshot = self.snapshot.clone();
1033        let Some(row) = snapshot.get(key) else {
1034            return false;
1035        };
1036        let input = EditInput::Programmatic;
1037        let shown = self.pending_edits.overlay(key, column).cloned();
1038        let now = shown
1039            .as_ref()
1040            .map_or_else(|| row.value(column), OwnedValue::as_cell);
1041        let editable = self.delegate.is_editable(
1042            &EditableContext {
1043                row: &**row,
1044                column,
1045                value: now,
1046                input: &input,
1047            },
1048            cx,
1049        );
1050        if !editable {
1051            return false;
1052        }
1053        if self.edit.is_open() {
1054            self.end_edit_on_blur(window, cx);
1055        }
1056        let base = OwnedValue::from_cell(now);
1057        self.pending_edits.clear_failed(key, column);
1058        self.commit_value(key.clone(), column, value, base, &input, Some(window), cx)
1059            == CommitOutcome::Committed
1060    }
1061
1062    /// Commits the open field's draft: a draft that does not parse keeps
1063    /// the field open with its message.
1064    fn commit_field(
1065        &mut self,
1066        window: Option<&mut Window>,
1067        cx: &mut Context<Self>,
1068    ) -> CommitOutcome {
1069        let Some(session) = &mut self.edit.session else {
1070            return CommitOutcome::Closed;
1071        };
1072        let draft = session.field.value(cx);
1073        let value = match draft {
1074            Ok(value) => value,
1075            Err(error) => {
1076                session.mark = EditorMark::Invalid;
1077                session.message = Some(error.message().clone());
1078                cx.notify();
1079                return CommitOutcome::Refused;
1080            }
1081        };
1082        let (key, column, base, input) = (
1083            session.key.clone(),
1084            session.column,
1085            session.base.clone(),
1086            session.input.clone(),
1087        );
1088        self.commit_value(key, column, value, base, &input, window, cx)
1089    }
1090
1091    /// Closes the open field without a commit. Returns false if none is
1092    /// open.
1093    fn cancel_field(
1094        &mut self,
1095        reason: EditCancelReason,
1096        window: Option<&mut Window>,
1097        cx: &mut Context<Self>,
1098    ) -> bool {
1099        let Some(session) = &self.edit.session else {
1100            return false;
1101        };
1102        let draft = session
1103            .field
1104            .value(cx)
1105            .ok()
1106            .filter(|draft| !draft.cmp_total(&session.base).is_eq());
1107        let Some(session) = self.close_field(window, cx) else {
1108            return false;
1109        };
1110        cx.emit(LiveGridEvent::EditCancel {
1111            key: session.key,
1112            column: session.column,
1113            reason,
1114            draft,
1115        });
1116        true
1117    }
1118
1119    /// The focus left the open field, or a press landed outside it: it
1120    /// ends as [`EditPolicy::on_blur`] decides, with no move. A commit the
1121    /// delegate refuses then cancels: the press goes on elsewhere.
1122    fn end_edit_on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1123        let Some(session) = &self.edit.session else {
1124            return;
1125        };
1126        let draft = session.field.value(cx).ok();
1127        let current = self
1128            .store_value(&session.key, session.column)
1129            .unwrap_or_else(|| session.base.clone());
1130        let end = self.edit_setup.policy().on_blur(&EditPolicyContext::new(
1131            &session.base,
1132            &current,
1133            draft.as_ref(),
1134            session.field.is_dirty(cx),
1135        ));
1136        if end == EditEnd::Commit
1137            && self.commit_field(Some(&mut *window), cx) != CommitOutcome::Refused
1138        {
1139            return;
1140        }
1141        self.cancel_field(EditCancelReason::Blur, Some(window), cx);
1142    }
1143
1144    /// The focus left the editor's host without a press: to another element
1145    /// of the window. Leaving the window keeps the edit, as Excel does: GPUI
1146    /// reports a focus-out when the window turns inactive, which is ignored.
1147    fn on_editor_focus_out(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1148        if window.is_window_active() && self.edit.is_open() && !self.edit.contains_focus(window, cx)
1149        {
1150            self.end_edit_on_blur(window, cx);
1151        }
1152    }
1153
1154    /// Gives the open field the keyboard focus again after the event that
1155    /// opened it: a press focuses the grid's root once its own handlers
1156    /// ran.
1157    fn refocus_field(&mut self, editor_focus: &FocusHandle, window: &mut Window, cx: &mut App) {
1158        if let Some(session) = &self.edit.session
1159            && session.editor_focus == *editor_focus
1160            && !session.editor_focus.contains_focused(window, cx)
1161        {
1162            session.field.focus(window, cx);
1163        }
1164    }
1165
1166    /// A commit Action: commits the field and moves the focus as the edit
1167    /// gesture says for `key`.
1168    fn commit_and_move(&mut self, key: EditCommitKey, window: &mut Window, cx: &mut Context<Self>) {
1169        if !self.edit.is_open()
1170            || self.commit_field(Some(&mut *window), cx) == CommitOutcome::Refused
1171        {
1172            return;
1173        }
1174        match self.edit_setup.gesture().after_commit(key) {
1175            AfterCommit::Move(to) => {
1176                self.move_focus(to, cx);
1177            }
1178            AfterCommit::MoveAndEdit(to) => {
1179                if self.move_focus(to, cx)
1180                    && let Some((key, column)) = self.focused_data_cell()
1181                {
1182                    self.begin_edit(
1183                        key,
1184                        column,
1185                        EditInput::Command,
1186                        EditOrigin::User,
1187                        Modifiers::default(),
1188                        window,
1189                        cx,
1190                    );
1191                }
1192            }
1193            _ => {}
1194        }
1195    }
1196
1197    /// Moves the focus after a commit; returns true if it moved. The
1198    /// arrows' moves are the arrows' navigation; `NextEditable` and
1199    /// `PrevEditable` stay in the row.
1200    fn move_focus(&mut self, to: CellMove, cx: &mut Context<Self>) -> bool {
1201        let before = self.focused_data_cell();
1202        let navigation = match to {
1203            CellMove::Up => Navigation::Up,
1204            CellMove::Down => Navigation::Down,
1205            CellMove::Left => Navigation::PrevColumn,
1206            CellMove::Right => Navigation::NextColumn,
1207            CellMove::NextEditable | CellMove::PrevEditable => {
1208                return self.focus_editable_neighbour(to == CellMove::NextEditable, cx);
1209            }
1210        };
1211        self.navigate(navigation, false, cx);
1212        self.focused_data_cell() != before
1213    }
1214
1215    /// Focuses the next (`forward`) or previous visible cell of the focused
1216    /// row that takes an edit. Returns true if there is one.
1217    fn focus_editable_neighbour(&mut self, forward: bool, cx: &mut Context<Self>) -> bool {
1218        let Some((key, column)) = self.focused_data_cell() else {
1219            return false;
1220        };
1221        let Some(view) = self.columns.view_column_ix(column.ix()) else {
1222            return false;
1223        };
1224        let snapshot = self.snapshot.clone();
1225        let Some(row) = snapshot.row_id(&key).and_then(|id| snapshot.row(id)) else {
1226            return false;
1227        };
1228        let count = self.columns.visible_set().len();
1229        let views: Vec<usize> = if forward {
1230            (view.get() + 1..count).collect()
1231        } else {
1232            (0..view.get()).rev().collect()
1233        };
1234        let next = views.into_iter().find_map(|at| {
1235            let ix = self.columns.column_at(ViewColumnIx::new(at as u16))?;
1236            let column: ColumnOf<D> = column_of(ix);
1237            let editable = self.edit_setup.editor(ix).is_some()
1238                && self.delegate.is_editable(
1239                    &EditableContext {
1240                        row: &**row,
1241                        column,
1242                        value: row.value(column),
1243                        input: &EditInput::Command,
1244                    },
1245                    cx,
1246                );
1247            editable.then_some(column)
1248        });
1249        match next {
1250            Some(column) => self.set_focused_cell(&key, Some(column), cx),
1251            None => false,
1252        }
1253    }
1254
1255    // The call sites in the grid's other files.
1256
1257    /// A button went down anywhere in the window, in the capture phase:
1258    /// before the press does anything, an open editor outside it ends as
1259    /// [`EditPolicy::on_blur`] decides.
1260    pub(super) fn commit_edit_on_press(
1261        &mut self,
1262        position: Point<Pixels>,
1263        window: &mut Window,
1264        cx: &mut Context<Self>,
1265    ) {
1266        let Some(session) = &self.edit.session else {
1267            return;
1268        };
1269        let inside = |bounds: Option<Bounds<Pixels>>| bounds.is_some_and(|b| b.contains(&position));
1270        if inside(session.host_bounds.get())
1271            || inside(session.popup_bounds.get())
1272            || inside(session.field_popup.bounds.get())
1273        {
1274            return;
1275        }
1276        self.end_edit_on_blur(window, cx);
1277    }
1278
1279    /// The second press of a series on a data cell: opens its editor if
1280    /// the column has one and the gesture agrees. Returns true if an editor
1281    /// opened; false keeps the double click a `Confirm`.
1282    pub(super) fn start_edit_by_press(
1283        &mut self,
1284        click_count: usize,
1285        window: &mut Window,
1286        cx: &mut Context<Self>,
1287    ) -> bool {
1288        let Some(pointer) = self
1289            .pointer_target
1290            .as_ref()
1291            .filter(|pointer| pointer.pinned().is_none())
1292        else {
1293            return false;
1294        };
1295        let (Some(key), Some(column)) = (pointer.row().cloned(), pointer.column()) else {
1296            return false;
1297        };
1298        self.begin_edit(
1299            key,
1300            column_of(column),
1301            EditInput::Press { click_count },
1302            EditOrigin::User,
1303            Modifiers::default(),
1304            window,
1305            cx,
1306        )
1307    }
1308
1309    /// A new snapshot was taken: the waiting commits settle what the store
1310    /// decided; an edited row that left the store ends its edit
1311    /// (`EditCancel { RowRemoved }`); a change of the edited cell's value
1312    /// asks `EditPolicy::on_value_change` once. O(1) for the edit: one key
1313    /// lookup and one value.
1314    pub(super) fn follow_edit_rows(&mut self, _changes: &ChangeSet, cx: &mut Context<Self>) {
1315        self.follow_pending_edits(cx);
1316        let Some(session) = &self.edit.session else {
1317            return;
1318        };
1319        let Some(current) = self.store_value(&session.key, session.column) else {
1320            self.cancel_field(EditCancelReason::RowRemoved, None, cx);
1321            return;
1322        };
1323        if current.cmp_total(&session.base).is_eq() {
1324            // Back at the value the edit started from: no conflict.
1325            if let Some(session) = &mut self.edit.session
1326                && session.mark == EditorMark::Conflict
1327            {
1328                session.mark = EditorMark::None;
1329                session.message = None;
1330                session.seen = current;
1331                cx.notify();
1332            }
1333            return;
1334        }
1335        if current.cmp_total(&session.seen).is_eq() {
1336            return;
1337        }
1338        let draft = session.field.value(cx).ok();
1339        let action = self
1340            .edit_setup
1341            .policy()
1342            .on_value_change(&EditPolicyContext::new(
1343                &session.base,
1344                &current,
1345                draft.as_ref(),
1346                session.field.is_dirty(cx),
1347            ));
1348        match action {
1349            DraftAction::Replace => {
1350                let Some(session) = &mut self.edit.session else {
1351                    return;
1352                };
1353                session.seen = current.clone();
1354                session.base = current.clone();
1355                session.mark = EditorMark::None;
1356                session.message = None;
1357                // The field takes the value once a window is at hand.
1358                let (grid, window, editor_focus) = (
1359                    cx.weak_entity(),
1360                    session.window,
1361                    session.editor_focus.clone(),
1362                );
1363                cx.defer(move |cx| {
1364                    let _ = window.update(cx, |_, window, cx| {
1365                        let _ = grid.update(cx, |this, cx| {
1366                            if let Some(session) = &this.edit.session
1367                                && session.editor_focus == editor_focus
1368                            {
1369                                session.field.set_value(&current, window, cx);
1370                                cx.notify();
1371                            }
1372                        });
1373                    });
1374                });
1375                cx.notify();
1376            }
1377            DraftAction::Cancel => {
1378                self.cancel_field(EditCancelReason::Programmatic, None, cx);
1379            }
1380            _ => {
1381                let mut text = String::new();
1382                let shown = display_text(current.as_cell(), &mut text).to_owned();
1383                let Some(session) = &mut self.edit.session else {
1384                    return;
1385                };
1386                session.mark = EditorMark::Conflict;
1387                session.message = Some(t!("live_grid.edit.conflict", value = shown).into());
1388                session.seen = current;
1389                cx.notify();
1390            }
1391        }
1392    }
1393
1394    /// The edited row or column is no longer shown, though no press closed
1395    /// the field: a filter, a collapsed group, a hidden column. The edit
1396    /// ends as `EditPolicy::on_hidden` decides: a commit, or a cancel
1397    /// (`EditCancel { RowHidden }`), which a refused commit becomes too.
1398    pub(super) fn end_hidden_edit(&mut self, cx: &mut Context<Self>) {
1399        let Some(session) = &self.edit.session else {
1400            return;
1401        };
1402        let shown = self
1403            .snapshot
1404            .row_id(&session.key)
1405            .and_then(|id| self.projection.display_ix(id))
1406            .is_some()
1407            && self.columns.view_column_ix(session.column.ix()).is_some();
1408        if shown {
1409            return;
1410        }
1411        let draft = session.field.value(cx).ok();
1412        let current = self
1413            .store_value(&session.key, session.column)
1414            .unwrap_or_else(|| session.base.clone());
1415        let end = self.edit_setup.policy().on_hidden(&EditPolicyContext::new(
1416            &session.base,
1417            &current,
1418            draft.as_ref(),
1419            session.field.is_dirty(cx),
1420        ));
1421        if end == EditEnd::Commit && self.commit_field(None, cx) != CommitOutcome::Refused {
1422            return;
1423        }
1424        self.cancel_field(EditCancelReason::RowHidden, None, cx);
1425    }
1426
1427    /// Plans the open editor's host when its row is not painted this frame
1428    /// (scrolled away): it keeps the focus and the keys, with a mask of no
1429    /// area, at the row's place by the projection.
1430    pub(super) fn plan_hidden_editor_host(
1431        &mut self,
1432        hosts: &mut HostPlan,
1433        painted: bool,
1434        window: &mut Window,
1435        cx: &mut Context<Self>,
1436    ) {
1437        let Some(session) = &self.edit.session else {
1438            return;
1439        };
1440        // A hidden column takes the field out of every row: the edit ends
1441        // after this frame.
1442        if self.columns.view_column_ix(session.column.ix()).is_none() {
1443            let grid = cx.weak_entity();
1444            cx.defer(move |cx| {
1445                let _ = grid.update(cx, |this, cx| this.end_hidden_edit(cx));
1446            });
1447        }
1448        if painted {
1449            return;
1450        }
1451        session.host_bounds.set(None);
1452        let rows = self.body_bands().rows();
1453        let line = self
1454            .snapshot
1455            .row_id(&session.key)
1456            .and_then(|id| self.projection.display_ix(id))
1457            .map_or(rows.origin.y, |display_ix| {
1458                rows.origin.y + self.row_height * (display_ix as f32 - self.scroll_row as f32)
1459                    - self.scroll_offset
1460            });
1461        let line = Bounds::new(
1462            point(rows.origin.x, line),
1463            size(rows.size.width, self.row_height),
1464        );
1465        let width = self.columns.column(session.column.ix()).width;
1466        let bounds = Bounds::new(line.origin, size(width, self.row_height));
1467        let first = hosts.len();
1468        let element = editor_host(
1469            session,
1470            bounds,
1471            editor_size(self.row_height),
1472            false,
1473            None,
1474            window,
1475            cx,
1476        );
1477        hosts.push(CellHost {
1478            layer: HostLayer::Editor,
1479            column: session.column.ix(),
1480            zone: self.columns.column(session.column.ix()).zone,
1481            bounds,
1482            element,
1483        });
1484        hosts.end_row(
1485            first,
1486            data_row_id_name(None),
1487            key_hash(&session.key),
1488            line,
1489            Bounds::default(),
1490        );
1491    }
1492
1493    /// Binds the editing Actions (`StartEdit`, `StartEditEmpty`, the
1494    /// commits and `CancelEdit`) and typing on the root.
1495    pub(super) fn bind_edit_actions<E: InteractiveElement>(root: E, cx: &Context<Self>) -> E {
1496        root.on_action(cx.listener(|this, _: &StartEdit, window, cx| {
1497            this.start_edit_by_key(EditInput::Command, window, cx)
1498        }))
1499        .on_action(cx.listener(|this, _: &StartEditEmpty, window, cx| {
1500            this.start_edit_by_key(EditInput::Clear, window, cx)
1501        }))
1502        .on_action(cx.listener(|this, _: &CommitEdit, window, cx| {
1503            this.commit_and_move(EditCommitKey::Enter, window, cx)
1504        }))
1505        .on_action(cx.listener(|this, _: &CommitEditUp, window, cx| {
1506            this.commit_and_move(EditCommitKey::Up, window, cx)
1507        }))
1508        .on_action(cx.listener(|this, _: &CommitEditDown, window, cx| {
1509            this.commit_and_move(EditCommitKey::Down, window, cx)
1510        }))
1511        .on_action(cx.listener(|this, _: &CommitEditNext, window, cx| {
1512            this.commit_and_move(EditCommitKey::Next, window, cx)
1513        }))
1514        .on_action(cx.listener(|this, _: &CommitEditPrev, window, cx| {
1515            this.commit_and_move(EditCommitKey::Prev, window, cx)
1516        }))
1517        .on_action(cx.listener(|this, _: &CancelEdit, window, cx| {
1518            this.cancel_field(EditCancelReason::Escape, Some(window), cx);
1519        }))
1520        .on_key_down(cx.listener(Self::type_to_edit))
1521        .on_mouse_down(MouseButton::Left, cx.listener(Self::note_single_press))
1522        .on_mouse_up(MouseButton::Left, cx.listener(Self::edit_on_release))
1523    }
1524
1525    /// A left press bubbled up to the root: if the body took it as the
1526    /// first press on a data cell, its release may edit the cell.
1527    fn note_single_press(&mut self, event: &MouseDownEvent, _: &mut Window, _: &mut Context<Self>) {
1528        self.edit.single_press = self.pointer_target.as_ref().and_then(|pointer| {
1529            let fresh = pointer.position() == event.position && event.click_count == 1;
1530            let cell = (pointer.row().cloned()?, column_of(pointer.column()?));
1531            (fresh && pointer.pinned().is_none()).then_some((cell.0, cell.1, event.position))
1532        });
1533    }
1534
1535    /// A left release on the root after a single press on a data cell,
1536    /// without a drag: a gesture of the application may edit on a single
1537    /// click (`Press { click_count: 1 }`, AG Grid's `singleClickEdit`). The
1538    /// standard gesture does not.
1539    fn edit_on_release(
1540        &mut self,
1541        event: &MouseUpEvent,
1542        window: &mut Window,
1543        cx: &mut Context<Self>,
1544    ) {
1545        let Some((key, column, origin)) = self.edit.single_press.take() else {
1546            return;
1547        };
1548        if self.edit.is_open() || is_past_drag_threshold(origin, event.position) {
1549            return;
1550        }
1551        self.begin_edit(
1552            key,
1553            column,
1554            EditInput::Press { click_count: 1 },
1555            EditOrigin::User,
1556            event.modifiers,
1557            window,
1558            cx,
1559        );
1560    }
1561
1562    /// `StartEdit` and `StartEditEmpty` on the focused cell; a cell that
1563    /// takes no edit lets the key go on.
1564    fn start_edit_by_key(&mut self, input: EditInput, window: &mut Window, cx: &mut Context<Self>) {
1565        let started = self.focus_handle.is_focused(window)
1566            && self.focused_data_cell().is_some_and(|(key, column)| {
1567                self.begin_edit(
1568                    key,
1569                    column,
1570                    input,
1571                    EditOrigin::User,
1572                    Modifiers::default(),
1573                    window,
1574                    cx,
1575                )
1576            });
1577        if !started {
1578            cx.propagate();
1579        }
1580    }
1581
1582    /// Typing on the focused cell opens its editor with the typed text: a
1583    /// key with a character, without Ctrl, Cmd or Fn, that no binding took,
1584    /// while the root itself has the focus. An input method's text does not
1585    /// start an edit: the root has no text input handler.
1586    fn type_to_edit(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context<Self>) {
1587        let keystroke = &event.keystroke;
1588        let modifiers = keystroke.modifiers;
1589        if self.edit.is_open()
1590            || !self.focus_handle.is_focused(window)
1591            || modifiers.control
1592            || modifiers.platform
1593            || modifiers.function
1594        {
1595            return;
1596        }
1597        let Some(text) = keystroke
1598            .key_char
1599            .as_ref()
1600            .filter(|text| !text.is_empty() && !text.chars().any(char::is_control))
1601        else {
1602            return;
1603        };
1604        let Some((key, column)) = self.focused_data_cell() else {
1605            return;
1606        };
1607        let input = EditInput::Typing(text.clone().into());
1608        if self.begin_edit(key, column, input, EditOrigin::User, modifiers, window, cx) {
1609            cx.stop_propagation();
1610        }
1611    }
1612
1613    /// Returns true if typing `text` on the focused cell would edit it: a
1614    /// data cell with an editor that the delegate and the gesture let take
1615    /// typing. `ExpandRow` and `CollapseRow` then let `+` and `-`
1616    /// go on to typing.
1617    pub(super) fn would_type_into_focus(&self, text: &str, cx: &App) -> bool {
1618        let Some((key, column)) = self.focused_data_cell() else {
1619            return false;
1620        };
1621        let Some(row) = self
1622            .snapshot
1623            .row_id(&key)
1624            .and_then(|id| self.snapshot.row(id))
1625        else {
1626            return false;
1627        };
1628        let input = EditInput::Typing(SharedString::new(text));
1629        self.edit_setup.editor(column.ix()).is_some()
1630            && self.columns.view_column_ix(column.ix()).is_some()
1631            && self
1632                .edit_setup
1633                .gesture()
1634                .starts_edit(&EditGestureContext::new(
1635                    input.clone(),
1636                    Modifiers::default(),
1637                    self.columns.column(column.ix()).content,
1638                ))
1639            && self.delegate.is_editable(
1640                &EditableContext {
1641                    row: &**row,
1642                    column,
1643                    value: row.value(column),
1644                    input: &input,
1645                },
1646                cx,
1647            )
1648    }
1649}
1650
1651/// The open editor's host over `bounds`: the field, a frame inside the cell
1652/// in the colour of its mark, the editor's key context and its focus, and
1653/// the message's layer under the cell. A press on it is the field's, not
1654/// the body's; any key brings the cell back on screen.
1655fn editor_host<K, C>(
1656    session: &EditSession<K, C>,
1657    bounds: Bounds<Pixels>,
1658    size: ComponentSize,
1659    a11y: bool,
1660    message: Option<AnyElement>,
1661    window: &mut Window,
1662    cx: &mut App,
1663) -> Stateful<Div>
1664where
1665    C: GridColumns,
1666{
1667    session.field_popup.shown.set(false);
1668    session.field_popup.bounds.set(None);
1669    let field = session.field.render(
1670        &EditorCell {
1671            bounds,
1672            size,
1673            mark: session.mark,
1674            message: session.message.as_ref(),
1675            title: &session.title,
1676            popup: &session.field_popup,
1677        },
1678        window,
1679        cx,
1680    );
1681    // Over a field's own layer under the cell, the message goes above it.
1682    let (message_anchor, message_at) = if session.field_popup.shown.get() {
1683        (Anchor::BottomLeft, point(bounds.left(), bounds.top()))
1684    } else {
1685        (Anchor::TopLeft, point(bounds.left(), bounds.bottom()))
1686    };
1687    let theme = cx.theme();
1688    let frame = match session.mark {
1689        EditorMark::Invalid => theme.danger,
1690        EditorMark::Conflict => theme.warning,
1691        _ => theme.ring,
1692    };
1693    let reveal = session.reveal.clone();
1694    let popup_bounds = session.popup_bounds.clone();
1695    popup_bounds.set(None);
1696    CellHost::host_div(HostLayer::Editor, session.column.id())
1697        .track_focus(&session.editor_focus)
1698        .key_context(EDITOR_KEY_CONTEXT)
1699        .capture_key_down(move |_, _, cx| reveal(cx))
1700        // A field's own Escape that it lets go (gpui-kit's `Cancel`, as a
1701        // list does) cancels the edit, not the grid's selection.
1702        .on_action(|_: &Cancel, window, cx| {
1703            window.dispatch_action(Box::new(CancelEdit), cx);
1704        })
1705        .block_mouse_except_scroll()
1706        .flex()
1707        .items_center()
1708        .px(px(2.))
1709        .bg(theme.background)
1710        .border_2()
1711        .border_color(frame)
1712        .when(a11y, |this| this.role(Role::GridCell))
1713        .when_some(session.message.clone(), |this, message| {
1714            this.aria_description(message)
1715        })
1716        .child(field)
1717        .when_some(message, |this, message| {
1718            this.child(
1719                deferred(
1720                    anchored()
1721                        .anchor(message_anchor)
1722                        .position(message_at)
1723                        .snap_to_window_with_margin(px(4.))
1724                        .child(
1725                            div()
1726                                .on_children_prepainted(move |children, _, _| {
1727                                    popup_bounds.set(children.first().copied());
1728                                })
1729                                .child(div().occlude().child(message)),
1730                        ),
1731                )
1732                .with_priority(1),
1733            )
1734        })
1735}
1736
1737impl<D: LiveGridDelegate> RowPlanner<'_, D> {
1738    /// Plans the open editor's host in its cell: the cell without a tree
1739    /// row's indent, the field over it, the message under it. Returns true:
1740    /// the body paints no text there.
1741    pub(super) fn plan_editor_host(
1742        &mut self,
1743        cell: &HostCell<'_, D::Row>,
1744        window: &mut Window,
1745    ) -> bool {
1746        let Some(session) = &self.edit.session else {
1747            return false;
1748        };
1749        let bounds = Bounds::new(
1750            point(cell.bounds.origin.x + cell.indent, cell.bounds.origin.y),
1751            size(
1752                (cell.bounds.size.width - cell.indent).max(px(0.)),
1753                cell.bounds.size.height,
1754            ),
1755        );
1756        let clip = self.band_clips[1];
1757        session.host_bounds.set(Some(bounds.intersect(&clip)));
1758        let size = editor_size(self.row_height);
1759        let message = session.message.as_ref().and_then(|message| {
1760            self.delegate.render_edit_message(
1761                &EditMessageContext {
1762                    mark: session.mark,
1763                    message,
1764                    cell_bounds: bounds,
1765                    size: grid_size(self.row_height),
1766                },
1767                window,
1768                self.app,
1769            )
1770        });
1771        let element = editor_host(
1772            session,
1773            bounds,
1774            size,
1775            self.a11y.is_some(),
1776            message,
1777            window,
1778            self.app,
1779        );
1780        self.hosts.push(CellHost {
1781            layer: HostLayer::Editor,
1782            column: cell.column.ix(),
1783            zone: cell.zone,
1784            bounds,
1785            element,
1786        });
1787        true
1788    }
1789}
1790
1791/// The gpui-kit size of a grid with rows `row_height` high: 26, 30, 32 and
1792/// 40 px are `xsmall`, `small`, `medium` and `large`.
1793fn grid_size(row_height: Pixels) -> ComponentSize {
1794    [
1795        ComponentSize::XSmall,
1796        ComponentSize::Small,
1797        ComponentSize::Medium,
1798        ComponentSize::Large,
1799    ]
1800    .into_iter()
1801    .find(|size| size.table_row_height() == row_height)
1802    .unwrap_or(ComponentSize::Size(row_height))
1803}
1804
1805#[cfg(test)]
1806mod tests {
1807    use std::collections::HashSet;
1808
1809    use gpui_kit::Modifiers;
1810
1811    use super::*;
1812    use crate::{
1813        CellContent,
1814        policy::{
1815            AfterCommit, CellMove, DraftAction, EditCommitKey, EditEnd, EditGestureContext,
1816            EditPolicyContext, ReorderContext,
1817        },
1818    };
1819
1820    #[test]
1821    fn test_edit_types() {
1822        // Ids are unique and read back.
1823        let ids: HashSet<EditId> = (0..4).map(EditId::new).collect();
1824        assert_eq!(ids.len(), 4);
1825        assert_eq!(EditId::new(7).get(), 7);
1826        let error = EditError::new("Enter a number");
1827        assert_eq!(error.message().as_ref(), "Enter a number");
1828        assert_eq!(EditorMark::default(), EditorMark::None);
1829
1830        // Excel's gesture: every input but a single press, and no press on
1831        // a control; Enter down, Tab right.
1832        let gesture = StandardEditGesture;
1833        let starts = |input: EditInput, content: CellContent| {
1834            gesture.starts_edit(&EditGestureContext::new(
1835                input,
1836                Modifiers::default(),
1837                content,
1838            ))
1839        };
1840        for input in [
1841            EditInput::Command,
1842            EditInput::Clear,
1843            EditInput::Typing("4".into()),
1844            EditInput::Press { click_count: 2 },
1845            EditInput::Programmatic,
1846        ] {
1847            assert!(starts(input.clone(), CellContent::Text), "{input:?}");
1848            assert!(starts(input.clone(), CellContent::Element), "{input:?}");
1849        }
1850        assert!(!starts(
1851            EditInput::Press { click_count: 1 },
1852            CellContent::Text
1853        ));
1854        assert!(!starts(
1855            EditInput::Press { click_count: 2 },
1856            CellContent::Control
1857        ));
1858        assert!(starts(EditInput::Typing("x".into()), CellContent::Control));
1859        for (key, expected) in [
1860            (EditCommitKey::Enter, CellMove::Down),
1861            (EditCommitKey::Down, CellMove::Down),
1862            (EditCommitKey::Up, CellMove::Up),
1863            (EditCommitKey::Next, CellMove::Right),
1864            (EditCommitKey::Prev, CellMove::Left),
1865        ] {
1866            assert_eq!(gesture.after_commit(key), AfterCommit::Move(expected));
1867        }
1868
1869        // The standard policy: a valid draft commits on blur, an invalid one
1870        // cancels; the draft is kept under a store change; a hidden row
1871        // cancels; 5 s to settle; the error mark stays.
1872        let policy = StandardEditPolicy;
1873        let (base, current, draft) = (OwnedValue::Int(1), OwnedValue::Int(2), OwnedValue::Int(3));
1874        let valid = EditPolicyContext::new(&base, &current, Some(&draft), true);
1875        let invalid = EditPolicyContext::new(&base, &current, None, true);
1876        assert_eq!(policy.on_blur(&valid), EditEnd::Commit);
1877        assert_eq!(policy.on_blur(&invalid), EditEnd::Cancel);
1878        assert!(!policy.commit_unchanged());
1879        assert_eq!(policy.on_value_change(&valid), DraftAction::Keep);
1880        assert_eq!(policy.on_hidden(&valid), EditEnd::Cancel);
1881        assert_eq!(policy.settle_timeout(), std::time::Duration::from_secs(5));
1882        assert_eq!(policy.error_mark_timeout(), None);
1883        assert_eq!(
1884            (valid.base(), valid.current(), valid.is_dirty()),
1885            (&base, &current, true)
1886        );
1887
1888        // A new context sets none of the edit, drag and chooser inputs.
1889        let now = web_time::Instant::now();
1890        let context = ReorderContext::new(now, now, false, false);
1891        assert!(!context.is_editing());
1892        assert!(!context.has_pending_edits());
1893        assert!(!context.is_drag_over());
1894        assert!(!context.is_column_chooser_open());
1895        assert!(context.with_editing(true).is_editing());
1896
1897        // Nothing is open; the setup reads back what it was given.
1898        let slot = EditSlot::<u32, u16>::default();
1899        assert!(!slot.is_open());
1900        assert_eq!(slot.column_in(&1), None);
1901        let setup = EditSetup::<TestRow>::default();
1902        assert!(setup.gesture().starts_edit(&EditGestureContext::new(
1903            EditInput::Command,
1904            Modifiers::default(),
1905            CellContent::Text,
1906        )));
1907        assert_eq!(setup.policy().on_blur(&invalid), EditEnd::Cancel);
1908    }
1909
1910    struct TestRow;
1911
1912    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1913    struct TestColumn;
1914
1915    impl crate::model::GridColumns for TestColumn {
1916        fn all() -> &'static [Self] {
1917            &[Self]
1918        }
1919
1920        fn id(self) -> &'static str {
1921            "test"
1922        }
1923
1924        fn ix(self) -> ColumnIx {
1925            ColumnIx::new(0)
1926        }
1927    }
1928
1929    impl GridRow for TestRow {
1930        type Key = u32;
1931        type Column = TestColumn;
1932
1933        fn key(&self) -> &u32 {
1934            &0
1935        }
1936
1937        fn value(&self, _: TestColumn) -> CellValue<'_> {
1938            CellValue::Empty
1939        }
1940    }
1941}