Skip to main content

gpui_kit_grid/
lib.rs

1//! gpui-kit-grid: a live-updating data grid for gpui-kit applications.
2//!
3//! The core: a background ingest that owns the rows, background projection
4//! rebuilds, a custom-painted body with a colour-free text cache, and change
5//! highlight on a global tick.
6//!
7//! ```text
8//! GridWriter (any thread) → ingest task → StoreSnapshot → LiveGridState (view)
9//!                                                       → Rebuild (background) → Projection
10//! ```
11
12// GPUI's `Action` derive spells its paths `gpui::`; the facade resolves them
13// through this alias (gpui-kit's `crates/kit/src/lib.rs`).
14extern crate gpui_kit as gpui;
15
16pub mod actions;
17mod element;
18mod engine;
19mod export;
20mod grid;
21mod highlight;
22mod model;
23mod persist;
24mod policy;
25mod quick_filter;
26mod state;
27mod store;
28mod text;
29
30pub use actions::init;
31pub use engine::{
32    Aggregate, AggregateFn, AggregateOrder, AggregateRow, Branch, DisplayRow, EveryGroupFooter,
33    ExpandDepth, Expansion, GroupBy, GroupFooter, GroupId, GroupInfo, GroupKey, GroupOrder,
34    GroupOrderContext, GroupPath, GroupTable, KeyOrder, NoGroupFooter, Projection,
35    ProjectionEngine, Rebuild, SortDirection, TreeFilter, TreeFilterContext, TreeFilterRule,
36    TreeSpec, TreeTotals, TreeTotalsContext, TreeTotalsRule, ViewSpec,
37};
38pub use export::{
39    CellExport, CellExporter, CsvEncoding, CsvFormat, DisplayedCells, ExportCell, ExportColumn,
40    ExportControl, ExportError, ExportFormat, ExportLine, ExportLines, ExportRow, ExportScope,
41    ExportTable, Exporter, LineFilter, NumberHint, NumberPrecision, RawCells, TextFile,
42    TsvExporter,
43};
44/// Derives [`GridColumns`](trait@GridColumns) for an enum of columns (feature
45/// `derive`).
46#[cfg(feature = "derive")]
47pub use gpui_kit_grid_macros::GridColumns;
48pub use grid::LiveGrid;
49pub use highlight::{ChangeDirection, HighlightPolicy, RowChange, RowStamps, Stamp};
50pub use model::{
51    CellValue, ColumnId, ColumnIx, ColumnSet, GridColumns, GridRow, GridValue, IconValue,
52    ImageValue, LinkValue, NumberFormat, OwnedValue,
53};
54pub use persist::{
55    AppendNewColumns, GridLayout, JsonLayoutCodec, LayoutCodec, LayoutColumn, LayoutError,
56    LayoutExpansion, LayoutGroup, LayoutLibrary, LayoutMerge, LayoutMergeContext, LayoutReport,
57    LayoutSort,
58};
59pub use policy::{
60    AfterCommit, AutofitContext, AutofitPolicy, BackgroundRunner, BandedMove, CellMove,
61    ColumnMoveContext, ColumnMovePolicy, ColumnOrder, ContextMenuGesture,
62    ContextMenuGestureContext, DraftAction, DropBetweenRows, DropLine, DropOnRows, EditCommitKey,
63    EditEnd, EditGesture, EditGestureContext, EditPolicy, EditPolicyContext, ExpandArea,
64    ExpandEdit, ExpandGesture, ExpandGestureContext, FocusBranches, FocusCaptions, FollowLiveValue,
65    FreeMove, GestureInput, GridArea, HoldReorder, InlineRunner, LineScopeContext, LiveReorder,
66    ManualReorder, NoRowDrag, NoRowDrop, ProjectionScope, ReorderContext, ReorderDecision,
67    ReorderPolicy, RowDragGesture, RowDragPressContext, RowDropContext, RowDropPolicy,
68    SelectableLine, SelectedRowsDrag, SelectionEdit, SelectionGesture, SelectionGestureContext,
69    SelectionPolicy, SelectionScope, SelectionScopeContext, SortCycle, SortCycleContext,
70    StandardContextMenuGesture, StandardEditGesture, StandardEditPolicy, StandardExpandGesture,
71    StandardSelectionGesture, StandardSortCycle, StoreScope, TaskRunner, VisibleRowsAutofit,
72};
73pub use quick_filter::{
74    CellTexts, ContainsAllTerms, ContainsAllTermsQuery, QuickFilter, QuickFilterMatcher,
75    QuickFilterQuery, QuickFilterRule,
76};
77pub use state::{
78    AggregateLine, AggregateStyleContext, AssetImageSource, BandInfo, CaptionTag, CellChange,
79    CellContent, CellEditHandle, CellEditor, CellElementContext, CellImage, CellImageSource,
80    CellImages, CellStyle, CellStyleContext, CellTooltip, CellTooltipContext, ChangePaint,
81    ChangePainter, ChangePalette, CheckboxEditor, ColumnAlign, ColumnBand, ColumnChooserContext,
82    ColumnChooserFactory, ColumnChooserPanel, ColumnInfo, ColumnZone, DateEditor,
83    DirectionalChanges, DragPayload, DropPlace, DropSource, DropTarget, EditCancelReason,
84    EditError, EditId, EditInput, EditMessageContext, EditOrigin, EditState, EditValidateContext,
85    EditableContext, EditorCell, EditorField, EditorMark, EditorOpen, EditorStart, EmptyExport,
86    ExpandTarget, ExportHeader, ExportJob, ExportOutcome, ExportProgress, ExportRequest,
87    FlushOutcome, FocusedCell, GridColumn, GridHandleEvent, GridMenuContext, GridSchema,
88    GridSelection, GridStats, GridTarget, GridTuning, GroupCaptionContext, GroupOrigin,
89    HeaderLabel, HeaderTooltipContext, ImageFit, LayoutOrigin, LayoutPart, LayoutParts, LinkOpener,
90    LiveGridDelegate, LiveGridEvent, LiveGridHandle, LiveGridState, MIN_COLUMN_WIDTH, NumberEditor,
91    NumberKind, OpenInBrowser, PopoverColumnChooser, ProgressContext, RowDrag, RowDragContext,
92    ScopedSelection, SelectEditor, SelectOption, SelectionMode, SelectionOrigin, SortOrigin,
93    TextEditor, UniformChanges, ViewColumnIx, WeakLiveGridHandle,
94};
95pub use store::{
96    ChangeSet, Clock, GridProgress, GridStore, GridStoreBuilder, GridWriter, Ingest, Op, PAGE_SIZE,
97    PinnedEdge, Reload, ReloadPolicy, ReplayLive, RowId, StoreOptions, StoreSnapshot, StoreStats,
98    StoreSubscription, WriteBatch, WriteCoalescing,
99};
100pub use text::TextStats;
101
102rust_i18n::i18n!("locales", fallback = "en");
103
104#[cfg(test)]
105mod tests {
106    use rust_i18n::t;
107
108    /// Every key of `locales/live_grid.yml` with its English text.
109    const CATALOG: &[(&str, &str)] = &[
110        ("live_grid.menu.copy", "Copy"),
111        ("live_grid.menu.copy_all", "Copy all"),
112        ("live_grid.menu.sort", "Sort"),
113        ("live_grid.menu.sort_ascending", "Sort ascending"),
114        ("live_grid.menu.sort_descending", "Sort descending"),
115        ("live_grid.menu.clear_sort", "Clear sort"),
116        ("live_grid.menu.clear_all_sorting", "Clear all sorting"),
117        ("live_grid.menu.column", "Column"),
118        ("live_grid.menu.autofit_column", "Autofit column"),
119        ("live_grid.menu.autofit_columns", "Autofit all columns"),
120        ("live_grid.menu.hide_column", "Hide column"),
121        ("live_grid.menu.show_all_columns", "Show all columns"),
122        ("live_grid.menu.choose_columns", "Choose columns…"),
123        ("live_grid.menu.freeze_left", "Freeze left"),
124        ("live_grid.menu.freeze_right", "Freeze right"),
125        ("live_grid.menu.unfreeze", "Unfreeze"),
126        ("live_grid.menu.reset_columns", "Reset columns"),
127        ("live_grid.sort.ascending", "Sorted ascending"),
128        ("live_grid.sort.descending", "Sorted descending"),
129        (
130            "live_grid.sort.ascending_priority",
131            "Sorted ascending, priority %{priority}",
132        ),
133        (
134            "live_grid.sort.descending_priority",
135            "Sorted descending, priority %{priority}",
136        ),
137        ("live_grid.progress.loading", "Loading…"),
138        ("live_grid.menu.group_by", "Group by this column"),
139        ("live_grid.menu.ungroup", "Ungroup"),
140        ("live_grid.menu.clear_grouping", "Ungroup all"),
141        ("live_grid.menu.move_left", "Move left"),
142        ("live_grid.menu.move_right", "Move right"),
143        ("live_grid.group_panel.label", "Grouping"),
144        (
145            "live_grid.group_panel.hint",
146            "Drag a column header here to group by it",
147        ),
148        (
149            "live_grid.group_panel.tree",
150            "Grouping is off in the tree view",
151        ),
152        ("live_grid.group_panel.chip", "%{title}, %{direction}"),
153        (
154            "live_grid.group_panel.remove",
155            "Remove grouping by %{title}",
156        ),
157        // Group captions, their menu and aggregates.
158        ("live_grid.group.caption", "%{title}: %{value} (%{count})"),
159        ("live_grid.group.blank", "(Blank)"),
160        ("live_grid.group.true", "Yes"),
161        ("live_grid.group.false", "No"),
162        ("live_grid.menu.expand", "Expand"),
163        ("live_grid.menu.collapse", "Collapse"),
164        ("live_grid.menu.expand_all", "Expand all"),
165        ("live_grid.menu.collapse_all", "Collapse all"),
166        ("live_grid.menu.captions_only", "Captions only"),
167        ("live_grid.aggregate.sum", "Sum"),
168        ("live_grid.aggregate.avg", "Average"),
169        ("live_grid.aggregate.min", "Min"),
170        ("live_grid.aggregate.max", "Max"),
171        ("live_grid.aggregate.count", "Count"),
172        ("live_grid.aggregate.tooltip", "%{name}: %{value}"),
173        // The totals row.
174        ("live_grid.totals.label", "Total"),
175        // Accessibility texts.
176        ("live_grid.a11y.up", "rising"),
177        ("live_grid.a11y.down", "falling"),
178        ("live_grid.a11y.expanded", "Expanded"),
179        ("live_grid.a11y.collapsed", "Collapsed"),
180        // The column chooser.
181        ("live_grid.chooser.label", "Columns"),
182        ("live_grid.chooser.search", "Search columns"),
183        ("live_grid.chooser.frozen_left", "Frozen left"),
184        ("live_grid.chooser.scrolling", "Scrollable"),
185        ("live_grid.chooser.frozen_right", "Frozen right"),
186        ("live_grid.chooser.band_shown", "%{shown} of %{count}"),
187        ("live_grid.chooser.no_match", "No columns match"),
188        ("live_grid.chooser.show_all", "Show all"),
189        ("live_grid.chooser.move", "Move %{title}"),
190        // The messages of the built-in editors.
191        ("live_grid.edit.not_a_number", "Enter a number"),
192        ("live_grid.edit.not_a_whole_number", "Enter a whole number"),
193        (
194            "live_grid.edit.too_many_decimals",
195            "Use at most %{scale} decimals",
196        ),
197        ("live_grid.edit.below_min", "Enter %{min} or more"),
198        ("live_grid.edit.above_max", "Enter %{max} or less"),
199        ("live_grid.edit.too_long", "Use at most %{count} characters"),
200        // The list of a select editor.
201        ("live_grid.edit.search", "Search"),
202        ("live_grid.edit.no_matches", "No matches"),
203        // The ghost of a row drag.
204        ("live_grid.drag.row", "1 row"),
205        ("live_grid.drag.rows", "%{count} rows"),
206        // Editing under live updates.
207        (
208            "live_grid.edit.conflict",
209            "Changed to %{value} while you edited",
210        ),
211        ("live_grid.edit.pending", "Saving…"),
212        ("live_grid.edit.failed", "Not saved: %{message}"),
213        ("live_grid.a11y.saving", "saving"),
214        ("live_grid.a11y.not_saved", "not saved"),
215        ("live_grid.a11y.link", "link"),
216    ];
217
218    #[test]
219    fn test_locale_catalog() {
220        // `ru` is added only if the product turns out to be Russian-speaking.
221        assert_eq!(rust_i18n::available_locales!(), ["en", "zh-CN", "zh-HK"]);
222        for &(key, english) in CATALOG {
223            assert_eq!(t!(key, locale = "en"), english);
224            for locale in ["zh-CN", "zh-HK"] {
225                // A missing translation falls back to English.
226                let text = t!(key, locale = locale);
227                assert_ne!(text, english, "{key} has no {locale} text");
228                for placeholder in [
229                    "%{priority}",
230                    "%{title}",
231                    "%{direction}",
232                    "%{value}",
233                    "%{count}",
234                    "%{name}",
235                    "%{scale}",
236                    "%{min}",
237                    "%{max}",
238                    "%{message}",
239                    "%{shown}",
240                ] {
241                    assert_eq!(
242                        text.contains(placeholder),
243                        english.contains(placeholder),
244                        "{key} in {locale}: {placeholder}"
245                    );
246                }
247            }
248        }
249        for locale in ["en", "zh-CN", "zh-HK"] {
250            let text = t!(
251                "live_grid.sort.descending_priority",
252                locale = locale,
253                priority = 2
254            );
255            assert!(text.ends_with(" 2"), "{locale}: {text}");
256            assert!(!text.contains("..."), "{locale}: {text}");
257        }
258        assert!(!t!("live_grid.progress.loading", locale = "en").contains("..."));
259    }
260}