Skip to main content

gpui_kit_grid/
model.rs

1//! Rows, columns and cell values. This module has no GPUI types, so it can be
2//! promoted to `gpui-base` later.
3
4#[cfg(feature = "decimal")]
5mod decimal;
6mod float_text;
7mod timestamp;
8
9use std::{
10    any::Any,
11    borrow::{Borrow, Cow},
12    cmp::Ordering,
13    fmt::{self, Debug, Write as _},
14    hash::{Hash, Hasher},
15    sync::Arc,
16};
17
18use chrono::{DateTime, Utc};
19#[cfg(feature = "decimal")]
20use rust_decimal::Decimal;
21use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser};
22
23pub(crate) use self::float_text::push_fixed;
24pub(crate) use self::timestamp::push_timestamp;
25
26use crate::highlight::ChangeDirection;
27
28/// The index of a column in the schema. Stable for the schema's lifetime and
29/// used on the hot path. Persisted layouts use [`ColumnId`] instead.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
31pub struct ColumnIx(u16);
32
33impl ColumnIx {
34    /// Creates a column index.
35    pub const fn new(ix: u16) -> Self {
36        Self(ix)
37    }
38
39    /// Returns the index as `usize`.
40    pub const fn get(self) -> usize {
41        self.0 as usize
42    }
43}
44
45/// A stable column identifier for persisted layouts: the value of
46/// [`GridColumns::id`]. Unlike [`ColumnIx`] it survives schema changes, so
47/// saved layouts and sort keys refer to columns by it. Not for the hot path.
48///
49/// An id from the schema borrows its `&'static str` and does not allocate;
50/// an id read from a saved layout owns its text. Both compare, hash and
51/// order by the text alone, so either can look up the other in a map.
52/// Serialized as a plain string.
53#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
54#[serde(transparent)]
55pub struct ColumnId(Cow<'static, str>);
56
57impl ColumnId {
58    /// An id that borrows a static string, such as a [`GridColumns::id`].
59    pub const fn new_static(id: &'static str) -> Self {
60        Self(Cow::Borrowed(id))
61    }
62
63    /// An id that owns its text, such as one read from a file.
64    pub fn new(id: impl Into<String>) -> Self {
65        Self(Cow::Owned(id.into()))
66    }
67
68    /// The id as text.
69    pub fn as_str(&self) -> &str {
70        &self.0
71    }
72}
73
74impl From<&'static str> for ColumnId {
75    fn from(id: &'static str) -> Self {
76        Self::new_static(id)
77    }
78}
79
80impl From<String> for ColumnId {
81    fn from(id: String) -> Self {
82        Self::new(id)
83    }
84}
85
86impl AsRef<str> for ColumnId {
87    fn as_ref(&self) -> &str {
88        self.as_str()
89    }
90}
91
92impl Borrow<str> for ColumnId {
93    fn borrow(&self) -> &str {
94        self.as_str()
95    }
96}
97
98impl fmt::Display for ColumnId {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.write_str(self.as_str())
101    }
102}
103
104/// The set of columns of a row type, usually an application `enum`.
105///
106/// # Deriving
107///
108/// With the `derive` feature, `#[derive(GridColumns)]` writes this
109/// implementation for an enum of unit variants. Declaration order is the
110/// schema order, and a column's id is its variant name in `snake_case`, as
111/// a hand-written implementation spells it, so moving to the derive keeps
112/// saved layouts. `#[grid_column(id = "…")]` sets one column's id,
113/// `#[grid_columns(rename_all = "…")]` the naming rule, and
114/// `#[grid_columns(crate = "…")]` the path of a re-exported grid crate.
115/// The derive refuses at compile time what a hand-written implementation
116/// can get wrong: a variant missing from `all()`, an index shifted by an
117/// explicit discriminant, a repeated id, more than 128 columns.
118///
119#[cfg_attr(feature = "derive", doc = "```")]
120#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
121/// use gpui_kit_grid::GridColumns;
122///
123/// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, GridColumns)]
124/// enum TradeColumn {
125///     Id,
126///     Instrument,
127///     BidSize, // "bid_size"
128///     #[grid_column(id = "px")]
129///     Price,
130/// }
131///
132/// assert_eq!(TradeColumn::BidSize.id(), "bid_size");
133/// assert_eq!(TradeColumn::Price.id(), "px");
134/// assert_eq!(TradeColumn::Price.ix().get(), 3);
135/// ```
136pub trait GridColumns: Copy + Eq + Hash + Debug + Send + Sync + 'static {
137    /// Every column in schema order. `all()[c.ix().get()] == c`.
138    fn all() -> &'static [Self];
139    /// A stable identifier for persisted layouts and element ids. Unique
140    /// within the schema; changing it orphans the column in saved layouts.
141    fn id(self) -> &'static str;
142    /// The schema index of the column.
143    fn ix(self) -> ColumnIx;
144
145    /// The column's identifier as a [`ColumnId`]. Borrows [`GridColumns::id`]
146    /// without allocating.
147    fn column_id(self) -> ColumnId {
148        ColumnId::new_static(self.id())
149    }
150}
151
152/// A set of columns, up to 128.
153#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
154pub struct ColumnSet(u128);
155
156impl ColumnSet {
157    /// No columns.
158    pub const EMPTY: Self = Self(0);
159
160    /// The first `count` columns.
161    pub fn first(count: usize) -> Self {
162        assert!(count <= 128, "a grid supports at most 128 columns");
163        if count == 128 {
164            Self(u128::MAX)
165        } else {
166            Self((1u128 << count) - 1)
167        }
168    }
169
170    /// Adds a column.
171    pub fn insert(&mut self, column: ColumnIx) {
172        self.0 |= 1u128 << column.get();
173    }
174
175    /// Returns true if the set contains the column.
176    pub fn contains(self, column: ColumnIx) -> bool {
177        self.0 & (1u128 << column.get()) != 0
178    }
179
180    /// Returns true if the set has no columns.
181    pub fn is_empty(self) -> bool {
182        self.0 == 0
183    }
184
185    /// Returns true if both sets share a column.
186    pub fn intersects(self, other: Self) -> bool {
187        self.0 & other.0 != 0
188    }
189
190    /// Adds every column of `other`.
191    pub fn union_with(&mut self, other: Self) {
192        self.0 |= other.0;
193    }
194
195    /// The number of columns in the set.
196    pub fn len(self) -> usize {
197        self.0.count_ones() as usize
198    }
199
200    /// The number of columns of the set before `column`: the place of
201    /// `column` among the set's columns in schema order, if it is one.
202    #[inline]
203    pub(crate) fn rank(self, column: ColumnIx) -> usize {
204        let below = (1u128 << column.get()) - 1;
205        (self.0 & below).count_ones() as usize
206    }
207
208    /// The columns in both sets.
209    pub(crate) fn intersection(self, other: Self) -> Self {
210        Self(self.0 & other.0)
211    }
212
213    /// Removes a column.
214    pub(crate) fn remove(&mut self, column: ColumnIx) {
215        self.0 &= !(1u128 << column.get());
216    }
217
218    /// The columns of the set in schema order.
219    pub(crate) fn iter(self) -> impl Iterator<Item = ColumnIx> {
220        let mut bits = self.0;
221        std::iter::from_fn(move || {
222            if bits == 0 {
223                return None;
224            }
225            let ix = bits.trailing_zeros() as u16;
226            bits &= bits - 1;
227            Some(ColumnIx::new(ix))
228        })
229    }
230
231    /// The set as a bit mask: bit `n` is the column of index `n`.
232    pub(crate) const fn bits(self) -> u128 {
233        self.0
234    }
235
236    /// The set of a bit mask from [`Self::bits`].
237    pub(crate) const fn from_bits(bits: u128) -> Self {
238        Self(bits)
239    }
240}
241
242/// A cell value. Numbers, points in time and borrowed text do not allocate.
243///
244/// `==` compares two values of one variant: floats with `f64::eq` (a NaN is
245/// not equal to itself, -0.0 equals 0.0), decimals by value (`1.0` equals
246/// `1.00`), icons by path and text, custom values by
247/// [`GridValue::cmp_value`]. Values of two variants are never equal, not
248/// even `Int(1)` and `Float(1.0)`. [`GridRow::changed_columns`] diffs rows
249/// with it; [`CellValue::cmp_total`] is the order.
250#[derive(Clone, Copy, Debug)]
251#[non_exhaustive]
252pub enum CellValue<'a> {
253    /// No value.
254    Empty,
255    /// A boolean.
256    Bool(bool),
257    /// An integer.
258    Int(i64),
259    /// A floating point number.
260    Float(f64),
261    /// An exact decimal number with its scale: money, quantities with a
262    /// fixed number of decimals. `12.30` shows as `12.30`. Needs the
263    /// `decimal` feature.
264    #[cfg(feature = "decimal")]
265    Decimal(Decimal),
266    /// Text borrowed from the row.
267    Text(&'a str),
268    /// A point in time. Shown in UTC as `2026-11-04 12:34:56` until a column
269    /// formatter exists; another zone or format is a [`CellValue::Custom`]
270    /// value with its own text. A date without a time is the timestamp of
271    /// its midnight in UTC.
272    Timestamp(DateTime<Utc>),
273    /// An icon with the text that stands for it. The body paints the icon;
274    /// the text is what a screen reader, a tooltip, the quick filter, a copy
275    /// and the sort read.
276    Icon(IconValue<'a>),
277    /// An image with the text that stands for it: a raster or a colour SVG
278    /// that the grid's image source (`LiveGridState::with_image_source`)
279    /// finds by its id. Like an icon, the body paints the image and the text
280    /// is what a screen reader, a tooltip, the quick filter, a copy and the
281    /// sort read.
282    Image(ImageValue<'a>),
283    /// A link: its text, which the cell shows in the theme's link colour,
284    /// underlined, and the address a click on the text or Enter on the
285    /// focused cell opens (`LiveGridState::with_link_opener`). The text is
286    /// the value everywhere else, as an icon's: the sort, the quick filter,
287    /// a copy, an export and a screen reader, which hears "link" after it.
288    /// Painted by the body: no element per cell.
289    Link(LinkValue<'a>),
290    /// A value of the application's own type, borrowed from the row: its own
291    /// order, text and export. See [`GridValue`].
292    Custom(&'a dyn GridValue),
293}
294
295impl PartialEq for CellValue<'_> {
296    fn eq(&self, other: &Self) -> bool {
297        match (self, other) {
298            (Self::Empty, Self::Empty) => true,
299            (Self::Bool(a), Self::Bool(b)) => a == b,
300            (Self::Int(a), Self::Int(b)) => a == b,
301            (Self::Float(a), Self::Float(b)) => a == b,
302            #[cfg(feature = "decimal")]
303            (Self::Decimal(a), Self::Decimal(b)) => a == b,
304            (Self::Text(a), Self::Text(b)) => a == b,
305            (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
306            (Self::Icon(a), Self::Icon(b)) => a == b,
307            (Self::Image(a), Self::Image(b)) => a == b,
308            (Self::Link(a), Self::Link(b)) => a == b,
309            (Self::Custom(a), Self::Custom(b)) => a.cmp_value(*b).is_eq(),
310            _ => false,
311        }
312    }
313}
314
315impl CellValue<'_> {
316    /// A total order across all values: `Empty` first, then `Bool`,
317    /// numbers, `Timestamp`, `Text`, `Icon` and `Image`, and `Custom` last,
318    /// each by value.
319    ///
320    /// Numbers follow `f64::total_cmp`, and an `Int` compares exactly, as
321    /// the float of its value would: `Int(1)` equals `Float(1.0)` and
322    /// `Int(2^53 + 1)` is above `Float(2^53)`. So -0.0 sorts just below 0.0
323    /// and `Int(0)`, a negative NaN before -∞ and a positive NaN after +∞.
324    ///
325    /// A `Decimal` (the `decimal` feature) is a number too and compares
326    /// exactly by its value, whatever its scale: `Decimal(1.00)` equals
327    /// `Int(1)` and `Float(1.0)`, `Decimal(0.1)` is below `Float(0.1)`,
328    /// whose binary value is a little above 0.1, and a zero `Decimal` of any
329    /// sign or scale sorts as `Int(0)`, above -0.0.
330    ///
331    /// Timestamps compare by time. An `Icon`, an `Image` and a `Link` sort
332    /// by their text, as `Text` does: an icon, an image, a link and a text
333    /// with the same text are equal. Custom values compare with
334    /// [`GridValue::cmp_value`], which keeps the order total.
335    ///
336    /// Not the same as `==`, which compares floats with `f64::eq` (a NaN is
337    /// not equal to itself, -0.0 equals 0.0), compares decimals by value
338    /// (`1.0` equals `1.00`) and never calls values of two variants equal.
339    pub fn cmp_total(&self, other: &Self) -> Ordering {
340        fn rank(value: &CellValue) -> u8 {
341            match value {
342                CellValue::Empty => 0,
343                CellValue::Bool(_) => 1,
344                CellValue::Int(_) | CellValue::Float(_) => 2,
345                #[cfg(feature = "decimal")]
346                CellValue::Decimal(_) => 2,
347                CellValue::Timestamp(_) => 3,
348                CellValue::Text(_)
349                | CellValue::Icon(_)
350                | CellValue::Image(_)
351                | CellValue::Link(_) => 4,
352                CellValue::Custom(_) => 5,
353            }
354        }
355        match (self, other) {
356            (CellValue::Bool(a), CellValue::Bool(b)) => a.cmp(b),
357            (CellValue::Int(a), CellValue::Int(b)) => a.cmp(b),
358            (CellValue::Float(a), CellValue::Float(b)) => a.total_cmp(b),
359            (CellValue::Int(a), CellValue::Float(b)) => cmp_int_float(*a, *b),
360            (CellValue::Float(a), CellValue::Int(b)) => cmp_int_float(*b, *a).reverse(),
361            #[cfg(feature = "decimal")]
362            (CellValue::Decimal(a), CellValue::Decimal(b)) => a.cmp(b),
363            // Every `i64` is a `Decimal`: exact.
364            #[cfg(feature = "decimal")]
365            (CellValue::Decimal(a), CellValue::Int(b)) => a.cmp(&Decimal::from(*b)),
366            #[cfg(feature = "decimal")]
367            (CellValue::Int(a), CellValue::Decimal(b)) => Decimal::from(*a).cmp(b),
368            #[cfg(feature = "decimal")]
369            (CellValue::Decimal(a), CellValue::Float(b)) => decimal::cmp_decimal_float(*a, *b),
370            #[cfg(feature = "decimal")]
371            (CellValue::Float(a), CellValue::Decimal(b)) => {
372                decimal::cmp_decimal_float(*b, *a).reverse()
373            }
374            (CellValue::Text(a), CellValue::Text(b)) => a.cmp(b),
375            (CellValue::Timestamp(a), CellValue::Timestamp(b)) => a.cmp(b),
376            (CellValue::Custom(a), CellValue::Custom(b)) => a.cmp_value(*b),
377            // Text, an icon's text and an image's text: by the text.
378            _ => match (self.sort_text(), other.sort_text()) {
379                (Some(a), Some(b)) => a.cmp(b),
380                _ => rank(self).cmp(&rank(other)),
381            },
382        }
383    }
384
385    /// The text a `Text`, `Icon`, `Image` or `Link` sorts by.
386    fn sort_text(&self) -> Option<&str> {
387        match self {
388            CellValue::Text(text) => Some(text),
389            CellValue::Icon(icon) => Some(icon.text),
390            CellValue::Image(image) => Some(image.text),
391            CellValue::Link(link) => Some(link.text),
392            _ => None,
393        }
394    }
395
396    /// Returns true for numbers, which are right-aligned by default.
397    pub fn is_number(&self) -> bool {
398        match self {
399            CellValue::Int(_) | CellValue::Float(_) => true,
400            #[cfg(feature = "decimal")]
401            CellValue::Decimal(_) => true,
402            _ => false,
403        }
404    }
405}
406
407/// An icon in a cell ([`CellValue::Icon`]): the asset path of its SVG (the
408/// `path()` of a gpui-kit `IconNamed`, or the application's own asset) and
409/// the text that stands for it.
410///
411/// The text is the cell's value everywhere but on the screen: a screen
412/// reader, a tooltip, the quick filter, a copy, an export and the sort read
413/// it. Both borrow from the row: no allocation.
414#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
415pub struct IconValue<'a> {
416    path: &'a str,
417    text: &'a str,
418}
419
420impl<'a> IconValue<'a> {
421    /// An icon from its asset path and its text.
422    pub const fn new(path: &'a str, text: &'a str) -> Self {
423        Self { path, text }
424    }
425
426    /// The asset path of the icon's SVG.
427    pub const fn path(&self) -> &'a str {
428        self.path
429    }
430
431    /// The text that stands for the icon.
432    pub const fn text(&self) -> &'a str {
433        self.text
434    }
435}
436
437/// A link in a cell ([`CellValue::Link`]): the text the cell shows and the
438/// address it opens. Both borrow from the row: no allocation.
439#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
440pub struct LinkValue<'a> {
441    text: &'a str,
442    url: &'a str,
443}
444
445impl<'a> LinkValue<'a> {
446    /// A link showing `text` that opens `url`.
447    pub const fn new(text: &'a str, url: &'a str) -> Self {
448        Self { text, url }
449    }
450
451    /// The text the cell shows.
452    pub const fn text(&self) -> &'a str {
453        self.text
454    }
455
456    /// The address the link opens.
457    pub const fn url(&self) -> &'a str {
458        self.url
459    }
460}
461
462/// An image in a cell ([`CellValue::Image`]): its id, which the grid's
463/// image source reads (an asset path, an absolute file path, an
464/// `http(s)://` URI or a key of the application's own), and the text that
465/// stands for it.
466///
467/// The text is the cell's value everywhere but on the screen, as an icon's.
468/// Both borrow from the row: no allocation.
469#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
470pub struct ImageValue<'a> {
471    id: &'a str,
472    text: &'a str,
473}
474
475impl<'a> ImageValue<'a> {
476    /// An image from its id and its text.
477    pub const fn new(id: &'a str, text: &'a str) -> Self {
478        Self { id, text }
479    }
480
481    /// The id the image source finds the image by.
482    pub const fn id(&self) -> &'a str {
483        self.id
484    }
485
486    /// The text that stands for the image.
487    pub const fn text(&self) -> &'a str {
488        self.text
489    }
490}
491
492/// A cell value of the application's own type ([`CellValue::Custom`]): a
493/// severity that sorts by rank, a tenor, a price in ticks, a time in the
494/// local zone. The type decides its order, its text and what an export
495/// writes; the row lends it, so a cell does not allocate.
496///
497/// Called on the UI thread in prepaint for visible cells, and in the
498/// background for every row a rebuild sorts or filters: `cmp_value` and
499/// `write_text` must not allocate (beyond `out` growing) or lock.
500///
501/// ```
502/// use std::{any::TypeId, cmp::Ordering};
503///
504/// use gpui_kit_grid::{CellValue, GridValue};
505///
506/// /// Sorts by rank, worst first; shows its name.
507/// #[derive(Clone, Copy, Debug)]
508/// enum Severity {
509///     Critical,
510///     Warning,
511///     Info,
512/// }
513///
514/// impl GridValue for Severity {
515///     fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
516///         match other.downcast_ref::<Severity>() {
517///             Some(other) => (*self as u8).cmp(&(*other as u8)),
518///             // A value of another type: by type, so the order stays total.
519///             None => TypeId::of::<Self>().cmp(&other.as_any().type_id()),
520///         }
521///     }
522///
523///     fn write_text(&self, out: &mut String) {
524///         out.push_str(match self {
525///             Self::Critical => "Critical",
526///             Self::Warning => "Warning",
527///             Self::Info => "Info",
528///         });
529///     }
530/// }
531///
532/// let (critical, info) = (Severity::Critical, Severity::Info);
533/// let (critical, info) = (CellValue::Custom(&critical), CellValue::Custom(&info));
534/// assert!(critical.cmp_total(&info).is_lt());
535/// // Custom values sort after text.
536/// assert!(CellValue::Text("Critical").cmp_total(&critical).is_lt());
537/// ```
538pub trait GridValue: Any + Debug + Send + Sync {
539    /// The order of this value against `other`, which may be of another
540    /// type: `downcast_ref` tells. Values of two types must order by type,
541    /// the same way every time (for example by the `TypeId` of `as_any`),
542    /// so the order stays total: a sort relies on it. `Equal` also makes two cells `==`,
543    /// which is how [`GridRow::changed_columns`] sees a change.
544    fn cmp_value(&self, other: &dyn GridValue) -> Ordering;
545
546    /// Appends the text shown in the cell, which the quick filter searches
547    /// and a displayed copy writes. Appends with `push_str` or `write!`:
548    /// `out` may already hold text, which must stay.
549    fn write_text(&self, out: &mut String);
550
551    /// What a raw copy ([`RawCells`](crate::RawCells)) and a file export
552    /// write for the cell; `None` (the default) writes the text. A
553    /// `CellValue::Custom` here is written as its text.
554    fn export_value(&self) -> Option<CellValue<'_>> {
555        None
556    }
557
558    /// The owned form of the value: a group key, a `Min` or `Max`
559    /// aggregate, an edited value. The default is [`OwnedValue::Text`] of
560    /// [`GridValue::write_text`]: a group caption shows the text, and an
561    /// order by the aggregate ([`AggregateOrder`](crate::AggregateOrder))
562    /// follows the text.
563    ///
564    /// Return [`OwnedValue::custom`] of a copy to keep the value itself:
565    /// its order, its text, and its data for a caption hook, which
566    /// downcasts [`GroupCaptionContext::value`](crate::GroupCaptionContext::value).
567    /// Groups follow the values' own order either way: the rows are sorted
568    /// by the borrowed values before the groups are found.
569    fn to_owned_value(&self) -> OwnedValue {
570        let mut text = String::new();
571        self.write_text(&mut text);
572        OwnedValue::Text(text.into())
573    }
574}
575
576impl dyn GridValue {
577    /// The value as `Any`, to downcast it.
578    pub fn as_any(&self) -> &dyn Any {
579        self
580    }
581
582    /// The value as `T`, if it is one.
583    pub fn downcast_ref<T: GridValue>(&self) -> Option<&T> {
584        self.as_any().downcast_ref()
585    }
586}
587
588/// An owned cell value: a group key kept across snapshots, an aggregate
589/// result.
590///
591/// Two values are equal exactly when [`CellValue::cmp_total`] calls them
592/// equal, and equal values hash alike: `Int(1) == Float(1.0)`, while -0.0
593/// and 0.0 are two values and a NaN equals only itself, bit for bit. A
594/// `Decimal` equals the numbers of its value: `Decimal(1.00) == Int(1)`,
595/// `Decimal(0.5) == Float(0.5)`.
596///
597/// Serialized as JSON-like data: `Empty` as `null`, `Bool` as a boolean,
598/// `Int` as an integer, `Float` as a number with a fraction or an exponent
599/// (`1.0`, never `1`, so it reads back as a `Float`), `Text` as a string,
600/// `Decimal` as `{"decimal": "12.30"}`, text that keeps the scale,
601/// `Timestamp` as `{"timestamp": "2026-11-04T12:34:56Z"}`, RFC 3339 in UTC
602/// with the fraction it has. A NaN, an infinity or a timestamp outside the
603/// years 0 to 9999 is not serialized: writing one fails. A string reads back
604/// as `Text`, never as a timestamp.
605#[derive(Clone, Debug)]
606#[non_exhaustive]
607pub enum OwnedValue {
608    /// No value.
609    Empty,
610    /// A boolean.
611    Bool(bool),
612    /// An integer.
613    Int(i64),
614    /// A floating point number.
615    Float(f64),
616    /// An exact decimal number with its scale. Needs the `decimal` feature.
617    #[cfg(feature = "decimal")]
618    Decimal(Decimal),
619    /// Text.
620    Text(Arc<str>),
621    /// A point in time.
622    Timestamp(DateTime<Utc>),
623    /// A value of the application's own type, shared: what
624    /// [`GridValue::to_owned_value`] returns to keep its order and text.
625    /// Two are equal when [`GridValue::cmp_value`] says so; it hashes by
626    /// its type. Serialized as its [`GridValue::export_value`], else as
627    /// its text, and read back as that: a restored layout's group of a
628    /// custom key does not find the group again.
629    Custom(Arc<dyn GridValue>),
630}
631
632impl OwnedValue {
633    /// A value of the application's own type, kept as itself: for
634    /// [`GridValue::to_owned_value`], usually `OwnedValue::custom(self.clone())`.
635    /// Allocates once.
636    pub fn custom(value: impl GridValue) -> Self {
637        Self::Custom(Arc::new(value))
638    }
639
640    /// Copies a borrowed value; allocates only for `Text` and `Icon`, and
641    /// for `Custom` as its [`GridValue::to_owned_value`] does. An `Icon`
642    /// becomes the `Text` of its text: the icon is the cell's look, its
643    /// text the value.
644    pub fn from_cell(value: CellValue<'_>) -> Self {
645        match value {
646            CellValue::Empty => Self::Empty,
647            CellValue::Bool(value) => Self::Bool(value),
648            CellValue::Int(value) => Self::Int(value),
649            CellValue::Float(value) => Self::Float(value),
650            #[cfg(feature = "decimal")]
651            CellValue::Decimal(value) => Self::Decimal(value),
652            CellValue::Text(text) => Self::Text(text.into()),
653            CellValue::Timestamp(value) => Self::Timestamp(value),
654            CellValue::Icon(icon) => Self::Text(icon.text().into()),
655            CellValue::Image(image) => Self::Text(image.text().into()),
656            CellValue::Link(link) => Self::Text(link.text().into()),
657            CellValue::Custom(value) => value.to_owned_value(),
658        }
659    }
660
661    /// Borrows the value back, to show it as a cell does or to compare it.
662    pub fn as_cell(&self) -> CellValue<'_> {
663        match self {
664            Self::Empty => CellValue::Empty,
665            Self::Bool(value) => CellValue::Bool(*value),
666            Self::Int(value) => CellValue::Int(*value),
667            Self::Float(value) => CellValue::Float(*value),
668            #[cfg(feature = "decimal")]
669            Self::Decimal(value) => CellValue::Decimal(*value),
670            Self::Text(text) => CellValue::Text(text),
671            Self::Timestamp(value) => CellValue::Timestamp(*value),
672            Self::Custom(value) => CellValue::Custom(&**value),
673        }
674    }
675
676    /// The order of [`CellValue::cmp_total`].
677    pub fn cmp_total(&self, other: &Self) -> Ordering {
678        self.as_cell().cmp_total(&other.as_cell())
679    }
680}
681
682impl PartialEq for OwnedValue {
683    fn eq(&self, other: &Self) -> bool {
684        self.cmp_total(other).is_eq()
685    }
686}
687
688impl Eq for OwnedValue {}
689
690impl Hash for OwnedValue {
691    fn hash<H: Hasher>(&self, state: &mut H) {
692        match self {
693            Self::Empty => state.write_u8(0),
694            Self::Bool(value) => {
695                state.write_u8(1);
696                value.hash(state);
697            }
698            Self::Int(value) => {
699                state.write_u8(2);
700                value.hash(state);
701            }
702            // A float equal to an integer hashes as that integer.
703            Self::Float(value) => match integral(*value) {
704                Some(int) => {
705                    state.write_u8(2);
706                    int.hash(state);
707                }
708                // `cmp_total` calls two floats equal only bit for bit.
709                None => {
710                    state.write_u8(3);
711                    value.to_bits().hash(state);
712                }
713            },
714            // A decimal equal to an integer or a float hashes as that number.
715            #[cfg(feature = "decimal")]
716            Self::Decimal(value) => match decimal::equal_int_or_float(*value) {
717                Some(number) => number.hash(state),
718                None => {
719                    // Without trailing zeros, so `1.50` and `1.5` hash alike.
720                    let normal = value.normalize();
721                    state.write_u8(5);
722                    normal.mantissa().hash(state);
723                    normal.scale().hash(state);
724                }
725            },
726            Self::Text(text) => {
727                state.write_u8(4);
728                text.hash(state);
729            }
730            Self::Timestamp(value) => {
731                state.write_u8(6);
732                value.hash(state);
733            }
734            // Equal custom values are of one type: `cmp_value` orders two
735            // types by type.
736            Self::Custom(value) => {
737                state.write_u8(7);
738                value.as_any().type_id().hash(state);
739            }
740        }
741    }
742}
743
744/// The integer `cmp_total` calls equal to `float`, if there is one: an
745/// integral value in the `i64` range, but not -0.0 (it sorts below 0).
746fn integral(float: f64) -> Option<i64> {
747    let in_range = (-9.223_372_036_854_775_808e18..9.223_372_036_854_775_808e18).contains(&float);
748    (in_range && float.fract() == 0.0 && !(float == 0.0 && float.is_sign_negative()))
749        .then_some(float as i64)
750}
751
752impl Serialize for OwnedValue {
753    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
754        match self {
755            Self::Empty => serializer.serialize_none(),
756            Self::Bool(value) => serializer.serialize_bool(*value),
757            Self::Int(value) => serializer.serialize_i64(*value),
758            Self::Float(value) if value.is_finite() => serializer.serialize_f64(*value),
759            Self::Float(value) => Err(ser::Error::custom(format_args!(
760                "{value} is not a finite number"
761            ))),
762            #[cfg(feature = "decimal")]
763            Self::Decimal(value) => {
764                use ser::SerializeMap as _;
765                let mut map = serializer.serialize_map(Some(1))?;
766                map.serialize_entry("decimal", &decimal::DecimalText(*value))?;
767                map.end()
768            }
769            Self::Text(text) => serializer.serialize_str(text),
770            Self::Timestamp(value) => {
771                use ser::SerializeMap as _;
772                let mut map = serializer.serialize_map(Some(1))?;
773                map.serialize_entry("timestamp", &timestamp::TimestampText(*value))?;
774                map.end()
775            }
776            Self::Custom(value) => match value.export_value() {
777                Some(export) if !matches!(export, CellValue::Custom(_)) => {
778                    Self::from_cell(export).serialize(serializer)
779                }
780                _ => {
781                    let mut text = String::new();
782                    value.write_text(&mut text);
783                    serializer.serialize_str(&text)
784                }
785            },
786        }
787    }
788}
789
790/// The key of a value serialized as a one-entry map: `{"timestamp": …}`,
791/// `{"decimal": …}`.
792#[derive(Deserialize)]
793#[serde(field_identifier, rename_all = "lowercase")]
794enum TaggedValue {
795    Timestamp,
796    #[cfg(feature = "decimal")]
797    Decimal,
798}
799
800impl<'de> Deserialize<'de> for OwnedValue {
801    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
802        struct ValueVisitor;
803
804        impl<'v> de::Visitor<'v> for ValueVisitor {
805            type Value = OwnedValue;
806
807            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
808                formatter.write_str(if cfg!(feature = "decimal") {
809                    r#"null, a boolean, a number, a string, {"timestamp": text} or {"decimal": text}"#
810                } else {
811                    r#"null, a boolean, a number, a string or {"timestamp": text}"#
812                })
813            }
814
815            fn visit_unit<E: de::Error>(self) -> Result<OwnedValue, E> {
816                Ok(OwnedValue::Empty)
817            }
818
819            fn visit_none<E: de::Error>(self) -> Result<OwnedValue, E> {
820                Ok(OwnedValue::Empty)
821            }
822
823            fn visit_bool<E: de::Error>(self, value: bool) -> Result<OwnedValue, E> {
824                Ok(OwnedValue::Bool(value))
825            }
826
827            fn visit_i64<E: de::Error>(self, value: i64) -> Result<OwnedValue, E> {
828                Ok(OwnedValue::Int(value))
829            }
830
831            fn visit_u64<E: de::Error>(self, value: u64) -> Result<OwnedValue, E> {
832                i64::try_from(value)
833                    .map(OwnedValue::Int)
834                    .map_err(|_| E::invalid_value(de::Unexpected::Unsigned(value), &self))
835            }
836
837            fn visit_f64<E: de::Error>(self, value: f64) -> Result<OwnedValue, E> {
838                Ok(OwnedValue::Float(value))
839            }
840
841            fn visit_str<E: de::Error>(self, value: &str) -> Result<OwnedValue, E> {
842                Ok(OwnedValue::Text(value.into()))
843            }
844
845            fn visit_map<A: de::MapAccess<'v>>(self, mut map: A) -> Result<OwnedValue, A::Error> {
846                let Some(tag) = map.next_key::<TaggedValue>()? else {
847                    return Err(de::Error::invalid_length(0, &self));
848                };
849                let text: String = map.next_value()?;
850                let value = match tag {
851                    TaggedValue::Timestamp => {
852                        OwnedValue::Timestamp(timestamp::read_timestamp(&text)?)
853                    }
854                    #[cfg(feature = "decimal")]
855                    TaggedValue::Decimal => OwnedValue::Decimal(decimal::read_decimal(&text)?),
856                };
857                if map.next_key::<de::IgnoredAny>()?.is_some() {
858                    return Err(de::Error::invalid_length(2, &self));
859                }
860                Ok(value)
861            }
862        }
863
864        deserializer.deserialize_any(ValueVisitor)
865    }
866}
867
868/// The text of a cell as the body paints it: `Text` and the text of an
869/// `Icon` borrowed without a copy, `Int` as `{}`, `Float` as `{:.2}`,
870/// `Decimal` as `{}` with its scale (`12.30`), `Timestamp` in UTC as
871/// `2026-11-04 12:34:56`, `Custom` as its [`GridValue::write_text`],
872/// `Bool(true)` as "✓", `Bool(false)` and `Empty` as no text.
873///
874/// Numbers, timestamps and custom values are written into `buffer`, which
875/// the caller reuses across cells, so a frame does not allocate per cell.
876/// Inlined: the generic `plan_frame` is instantiated in the application
877/// crate and calls this for every cell.
878#[inline]
879pub(crate) fn display_text<'a>(value: CellValue<'a>, buffer: &'a mut String) -> &'a str {
880    display_text_in(value, NumberFormat::new(), buffer)
881}
882
883/// How a column shows its numbers (`GridColumn::with_number_format`): the
884/// decimals of a `Float`. The default shows two, as the grid always did.
885///
886/// A copy, a CSV file and an export's number hint follow the column's
887/// format too, so they give what the grid shows (`RawCells`). An exact
888/// `Decimal` keeps its own scale, an `Int` has no decimals, and a
889/// `Custom` value writes its own text.
890///
891/// ```
892/// use gpui_kit_grid::NumberFormat;
893///
894/// // Knots to a tenth.
895/// let knots = NumberFormat::new().with_decimals(1);
896/// assert_eq!(knots.decimals(), 1);
897/// assert_eq!(NumberFormat::default().decimals(), 2);
898/// ```
899#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
900pub struct NumberFormat {
901    decimals: u8,
902}
903
904impl NumberFormat {
905    /// The most decimals a format shows.
906    pub const MAX_DECIMALS: u8 = 15;
907
908    /// The default: two decimals.
909    pub const fn new() -> Self {
910        Self { decimals: 2 }
911    }
912
913    /// Shows `decimals` digits after the point, rounded half to even as
914    /// `format!("{:.n}")` does; at most [`Self::MAX_DECIMALS`].
915    pub const fn with_decimals(mut self, decimals: u8) -> Self {
916        self.decimals = if decimals > Self::MAX_DECIMALS {
917            Self::MAX_DECIMALS
918        } else {
919            decimals
920        };
921        self
922    }
923
924    /// The digits shown after the point of a `Float`.
925    pub const fn decimals(&self) -> u8 {
926        self.decimals
927    }
928}
929
930impl Default for NumberFormat {
931    fn default() -> Self {
932        Self::new()
933    }
934}
935
936/// [`display_text`] with a column's [`NumberFormat`]: the text the body
937/// paints in that column.
938#[inline]
939pub(crate) fn display_text_in<'a>(
940    value: CellValue<'a>,
941    format: NumberFormat,
942    buffer: &'a mut String,
943) -> &'a str {
944    match value {
945        CellValue::Text(text) => text,
946        CellValue::Empty | CellValue::Bool(false) => "",
947        CellValue::Bool(true) => "✓",
948        CellValue::Int(value) => {
949            buffer.clear();
950            let _ = write!(buffer, "{value}");
951            buffer
952        }
953        CellValue::Float(value) => {
954            buffer.clear();
955            float_text::push_fixed(value, format.decimals, buffer);
956            buffer
957        }
958        #[cfg(feature = "decimal")]
959        CellValue::Decimal(value) => {
960            buffer.clear();
961            decimal::push_decimal(value, buffer);
962            buffer
963        }
964        CellValue::Timestamp(value) => {
965            buffer.clear();
966            push_timestamp(value, buffer);
967            buffer
968        }
969        CellValue::Icon(icon) => icon.text(),
970        CellValue::Image(image) => image.text(),
971        CellValue::Link(link) => link.text(),
972        CellValue::Custom(value) => {
973            buffer.clear();
974            value.write_text(buffer);
975            buffer
976        }
977    }
978}
979
980/// The digits [`display_text_in`] writes after the decimal point of a number
981/// in a column of `format`: `Int` 0, `Float` the format's decimals, `Decimal`
982/// its scale. A `Custom` value counts by its
983/// [`GridValue::export_value`] when that is one of these. `None` for any
984/// other value.
985///
986/// One rule for the body and an export: an export format reads it as
987/// `ExportCell::number_hint`, so a spreadsheet shows a number as the grid
988/// does. No allocation: called per exported cell.
989#[inline]
990pub(crate) fn number_fraction_digits(value: CellValue<'_>, format: NumberFormat) -> Option<u32> {
991    let value = match value {
992        CellValue::Custom(value) => value.export_value()?,
993        value => value,
994    };
995    match value {
996        CellValue::Int(_) => Some(0),
997        CellValue::Float(_) => Some(u32::from(format.decimals)),
998        #[cfg(feature = "decimal")]
999        CellValue::Decimal(value) => Some(value.scale()),
1000        _ => None,
1001    }
1002}
1003
1004/// Returns true if `text` may occur, ignoring ASCII case, in the display
1005/// text of a number (`Int`, `Float`, `Decimal`). [`display_text`] writes
1006/// numbers with ASCII digits, `-`, `.`, `NaN` and `inf` alone, so a quick
1007/// filter term with any other character skips formatting numbers.
1008pub(crate) fn may_occur_in_number_text(text: &str) -> bool {
1009    text.bytes().all(|byte| {
1010        matches!(
1011            byte.to_ascii_lowercase(),
1012            b'0'..=b'9' | b'-' | b'.' | b'n' | b'a' | b'i' | b'f'
1013        )
1014    })
1015}
1016
1017/// Compares an integer with a float exactly, consistent with
1018/// `f64::total_cmp` between floats. Converting the integer to `f64` loses
1019/// precision above 2^53 and breaks transitivity, which sorting relies on.
1020fn cmp_int_float(int: i64, float: f64) -> Ordering {
1021    if float.is_nan() {
1022        // Positive NaN sorts after every number under `total_cmp`.
1023        return if float.is_sign_negative() {
1024            Ordering::Greater
1025        } else {
1026            Ordering::Less
1027        };
1028    }
1029    if float >= 9.223_372_036_854_775_807e18 {
1030        return Ordering::Less;
1031    }
1032    if float < -9.223_372_036_854_775_808e18 {
1033        return Ordering::Greater;
1034    }
1035    let whole = float.trunc();
1036    // The fraction decides between an integer and a float of the same whole
1037    // part. A zero keeps its sign: -0.0 - -0.0 is 0.0, but `total_cmp` puts
1038    // -0.0 below 0.0, so `Int(0)` is above -0.0 and equal to 0.0 only.
1039    let fraction = if float == 0.0 { float } else { float - whole };
1040    i128::from(int)
1041        .cmp(&(whole as i128))
1042        .then_with(|| 0.0f64.total_cmp(&fraction))
1043}
1044
1045/// A row of the grid. Rows are immutable snapshots shared through `Arc`.
1046pub trait GridRow: Send + Sync + 'static {
1047    /// The domain key of the row.
1048    type Key: Clone + Eq + Hash + Debug + Send + Sync + 'static;
1049    /// The columns of the row type.
1050    type Column: GridColumns;
1051
1052    /// The key of this row.
1053    fn key(&self) -> &Self::Key;
1054
1055    /// The value of a column. Called for visible cells on every frame and
1056    /// for every row when sorting: it must not allocate or lock.
1057    fn value(&self, column: Self::Column) -> CellValue<'_>;
1058
1059    /// Adds to `out` the columns whose values differ from `previous`.
1060    ///
1061    /// The default compares every column with [`GridRow::value`]; override
1062    /// it when the producer knows cheaper what changed. Called by ingest,
1063    /// and by a writer whose upsert takes the place of a pending upsert of
1064    /// the same key ([`WriteCoalescing::ByKey`](crate::WriteCoalescing::ByKey)),
1065    /// under the store's write lock: it must not lock.
1066    fn changed_columns(&self, previous: &Self, out: &mut ColumnSet) {
1067        for &column in Self::Column::all() {
1068            if self.value(column) != previous.value(column) {
1069                out.insert(column.ix());
1070            }
1071        }
1072    }
1073
1074    /// The parent row's key in a tree view; `None` for a root. Called in
1075    /// the background while a tree is built, and by ingest (or a writer
1076    /// whose upsert merges into a pending one) on every replacement of the
1077    /// row, to compare it with the previous version
1078    /// ([`ChangeSet::has_parent_change`](crate::ChangeSet::has_parent_change)):
1079    /// it must not allocate or lock.
1080    fn parent_key(&self) -> Option<&Self::Key> {
1081        None
1082    }
1083
1084    /// The direction of `column` after a change from `previous`.
1085    ///
1086    /// The default is `Up` or `Down` when both values are numbers, by
1087    /// [`CellValue::cmp_total`], and `None` otherwise or when they are
1088    /// equal. Override it for a column whose rise is bad news, such as a
1089    /// spread. Called for changed columns only, by ingest or by a writer
1090    /// whose upsert merges into a pending one: it must not allocate or lock.
1091    fn change_direction(&self, previous: &Self, column: Self::Column) -> Option<ChangeDirection> {
1092        let (value, before) = (self.value(column), previous.value(column));
1093        if !(value.is_number() && before.is_number()) {
1094            return None;
1095        }
1096        match value.cmp_total(&before) {
1097            Ordering::Greater => Some(ChangeDirection::Up),
1098            Ordering::Less => Some(ChangeDirection::Down),
1099            Ordering::Equal => None,
1100        }
1101    }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106    use super::*;
1107
1108    #[test]
1109    fn test_cell_value_order_is_exact_across_int_and_float() {
1110        let big = 1i64 << 53;
1111        let values = [
1112            CellValue::Int(big + 1),
1113            CellValue::Float(big as f64),
1114            CellValue::Int(big),
1115            CellValue::Float(f64::NAN),
1116            CellValue::Float(-0.5),
1117            CellValue::Int(-1),
1118            CellValue::Empty,
1119            CellValue::Text("a"),
1120        ];
1121        // Above 2^53 an i64 → f64 cast would call `big + 1` equal to the float.
1122        assert_eq!(values[0].cmp_total(&values[1]), Ordering::Greater);
1123        assert_eq!(values[2].cmp_total(&values[1]), Ordering::Equal);
1124        assert_eq!(values[1].cmp_total(&values[0]), Ordering::Less);
1125        assert_eq!(values[3].cmp_total(&values[0]), Ordering::Greater);
1126        assert_eq!(values[4].cmp_total(&values[5]), Ordering::Greater);
1127        // The order is a total order: sorting must not panic and must be stable
1128        // under every permutation of the inputs.
1129        let mut sorted = values;
1130        sorted.sort_by(|a, b| a.cmp_total(b));
1131        for window in sorted.windows(2) {
1132            assert_ne!(window[0].cmp_total(&window[1]), Ordering::Greater);
1133        }
1134    }
1135
1136    /// Every kind of value in ascending order, written out by hand: each rung
1137    /// holds values `cmp_total` calls equal. The numbers mix `Int` and
1138    /// `Float` at the edges where an exact comparison is easy to get wrong:
1139    /// zeros, 2^53 (above it `f64` skips integers), 2^63 (the end of `i64`),
1140    /// infinities and NaNs of both signs.
1141    fn ladder() -> Vec<Vec<CellValue<'static>>> {
1142        use CellValue::{Bool, Empty, Float, Int, Text};
1143        let p53 = 1i64 << 53;
1144        let p63 = 9_223_372_036_854_775_808.0f64;
1145        vec![
1146            vec![Empty],
1147            vec![Bool(false)],
1148            vec![Bool(true)],
1149            // `total_cmp` orders negative NaNs by payload, the largest first.
1150            vec![Float(f64::from_bits(0xFFF8_0000_0000_0000))],
1151            vec![Float(f64::from_bits(0xFFF0_0000_0000_0001))],
1152            vec![Float(f64::NEG_INFINITY)],
1153            vec![Float(f64::MIN)],
1154            vec![Float(-p63 - 2048.0)],
1155            vec![Int(i64::MIN), Float(-p63)],
1156            vec![Int(i64::MIN + 1)],
1157            vec![Int(-p53 - 1)],
1158            vec![Int(-p53), Float(-p53 as f64)],
1159            vec![Float(-1.5)],
1160            vec![Int(-1), Float(-1.0)],
1161            vec![Float(-0.5)],
1162            vec![Float(-f64::MIN_POSITIVE)],
1163            vec![Float(-f64::from_bits(1))],
1164            vec![Float(-0.0)],
1165            vec![Int(0), Float(0.0)],
1166            vec![Float(f64::from_bits(1))],
1167            vec![Float(0.5)],
1168            vec![Int(1), Float(1.0)],
1169            vec![Float(1.5)],
1170            vec![Int(p53 - 1), Float((p53 - 1) as f64)],
1171            vec![Int(p53), Float(p53 as f64)],
1172            vec![Int(p53 + 1)],
1173            vec![Int(p53 + 2), Float((p53 + 2) as f64)],
1174            vec![Int(i64::MAX - 1023), Float(p63 - 1024.0)],
1175            vec![Int(i64::MAX - 1)],
1176            vec![Int(i64::MAX)],
1177            vec![Float(p63)],
1178            vec![Float(f64::MAX)],
1179            vec![Float(f64::INFINITY)],
1180            vec![Float(f64::from_bits(0x7FF0_0000_0000_0001))],
1181            vec![Float(f64::from_bits(0x7FF8_0000_0000_0000))],
1182            vec![Text("")],
1183            vec![Text("a")],
1184            vec![Text("b")],
1185        ]
1186    }
1187
1188    /// Checks one triple against the laws of a total order: reflexive,
1189    /// antisymmetric and transitive, with equality transitive too.
1190    fn check_total_order(a: &CellValue, b: &CellValue, c: &CellValue) {
1191        let (ab, bc, ac) = (a.cmp_total(b), b.cmp_total(c), a.cmp_total(c));
1192        assert_eq!(
1193            a.cmp_total(a),
1194            Ordering::Equal,
1195            "{a:?} is not equal to itself"
1196        );
1197        assert_eq!(b.cmp_total(a), ab.reverse(), "{a:?} and {b:?}");
1198        let expected = match (ab, bc) {
1199            (Ordering::Equal, _) => bc,
1200            (_, Ordering::Equal) => ab,
1201            _ if ab == bc => ab,
1202            // a < b > c or a > b < c: any order of a and c is consistent.
1203            _ => return,
1204        };
1205        assert_eq!(ac, expected, "{a:?}, {b:?}, {c:?}: {ab:?} then {bc:?}");
1206    }
1207
1208    #[test]
1209    fn test_cell_value_order_matches_ladder() {
1210        let ladder = ladder();
1211        for (rung, values) in ladder.iter().enumerate() {
1212            for (other_rung, others) in ladder.iter().enumerate() {
1213                for a in values {
1214                    for b in others {
1215                        assert_eq!(a.cmp_total(b), rung.cmp(&other_rung), "{a:?} against {b:?}");
1216                    }
1217                }
1218            }
1219        }
1220
1221        // Signed zeros: -0.0 sorts just below 0, as under `total_cmp`, so
1222        // `Int(0)` cannot equal both zeros.
1223        let zero = CellValue::Int(0);
1224        assert_eq!(zero.cmp_total(&CellValue::Float(-0.0)), Ordering::Greater);
1225        assert_eq!(CellValue::Float(-0.0).cmp_total(&zero), Ordering::Less);
1226        assert_eq!(zero.cmp_total(&CellValue::Float(0.0)), Ordering::Equal);
1227        assert_eq!(
1228            CellValue::Int(-1).cmp_total(&CellValue::Float(-0.0)),
1229            Ordering::Less
1230        );
1231    }
1232
1233    #[test]
1234    fn test_cell_value_order_is_total() {
1235        let ladder: Vec<CellValue> = ladder().into_iter().flatten().collect();
1236        // Every triple of the edge values.
1237        for a in &ladder {
1238            for b in &ladder {
1239                for c in &ladder {
1240                    check_total_order(a, b, c);
1241                }
1242            }
1243        }
1244
1245        // Random triples: the edge values, their neighbours and random
1246        // integers, integral and fractional floats, and random bit patterns
1247        // (NaNs with any payload, subnormals). Small ranges make ties
1248        // between `Int` and `Float` common.
1249        let mut rng = fastrand::Rng::with_seed(20);
1250        let random = |rng: &mut fastrand::Rng| match rng.u8(0..9) {
1251            0 => ladder[rng.usize(..ladder.len())],
1252            1 => CellValue::Int(rng.i64(-3..=3)),
1253            2 => CellValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1254            3 => CellValue::Float([0.0, -0.0][rng.usize(..2)]),
1255            4 => {
1256                let edge = [0, 1 << 53, i64::MIN, i64::MAX][rng.usize(..4)];
1257                CellValue::Int(edge.saturating_add(rng.i64(-3..=3)))
1258            }
1259            5 => {
1260                let edge = [1u64 << 53, 1 << 63, 0][rng.usize(..3)] as f64;
1261                let bits = edge.to_bits().saturating_add_signed(rng.i64(-2..=2));
1262                let float = f64::from_bits(bits);
1263                CellValue::Float(if rng.bool() { float } else { -float })
1264            }
1265            6 => CellValue::Int(rng.i64(..)),
1266            7 => CellValue::Float(rng.i64(..) as f64),
1267            _ => CellValue::Float(f64::from_bits(rng.u64(..))),
1268        };
1269        for _ in 0..200_000 {
1270            let (a, b, c) = (random(&mut rng), random(&mut rng), random(&mut rng));
1271            check_total_order(&a, &b, &c);
1272        }
1273
1274        // An integer every `f64` holds compares as that float under
1275        // `total_cmp`, signed zeros and NaNs included.
1276        for _ in 0..100_000 {
1277            let int = match rng.u8(0..3) {
1278                0 => rng.i64(-4..=4),
1279                1 => rng.i64(-(1 << 53)..=1 << 53),
1280                _ => [0, 1 << 53, -(1 << 53)][rng.usize(..3)],
1281            };
1282            let float = match random(&mut rng) {
1283                CellValue::Float(float) => float,
1284                CellValue::Int(other) => other as f64,
1285                _ => -0.0,
1286            };
1287            assert_eq!(
1288                CellValue::Int(int).cmp_total(&CellValue::Float(float)),
1289                (int as f64).total_cmp(&float),
1290                "{int} against {float:?}"
1291            );
1292        }
1293    }
1294
1295    #[test]
1296    fn test_column_id() {
1297        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1298        enum Column {
1299            Price,
1300        }
1301
1302        impl GridColumns for Column {
1303            fn all() -> &'static [Self] {
1304                &[Column::Price]
1305            }
1306
1307            fn id(self) -> &'static str {
1308                "price"
1309            }
1310
1311            fn ix(self) -> ColumnIx {
1312                ColumnIx::new(0)
1313            }
1314        }
1315
1316        // An id from the schema borrows `GridColumns::id`; one from a file owns
1317        // its text. They are the same id.
1318        const PRICE: ColumnId = ColumnId::new_static("price");
1319        let from_schema = Column::Price.column_id();
1320        assert!(matches!(from_schema.0, Cow::Borrowed("price")));
1321        let from_file: ColumnId = serde_json::from_str("\"price\"").unwrap();
1322        assert!(matches!(from_file.0, Cow::Owned(_)));
1323        assert_eq!(from_schema, from_file);
1324        assert_eq!(from_schema, PRICE);
1325        assert_eq!(serde_json::to_string(&from_schema).unwrap(), "\"price\"");
1326
1327        // Map keys: borrowed and owned ids hash and order by text, and `&str`
1328        // looks them up.
1329        let ids = rustc_hash::FxHashSet::from_iter([from_file.clone()]);
1330        assert!(ids.contains(&from_schema) && ids.contains("price"));
1331        let ordered =
1332            std::collections::BTreeMap::from([(ColumnId::new("b"), 2), (ColumnId::from("a"), 1)]);
1333        assert_eq!(
1334            ordered.keys().map(ColumnId::as_str).collect::<Vec<_>>(),
1335            ["a", "b"]
1336        );
1337        assert_eq!(ordered.get("b"), Some(&2));
1338
1339        assert_eq!(ColumnId::from(String::from("fee")), ColumnId::from("fee"));
1340        assert_eq!(from_file.to_string(), "price");
1341        assert_eq!(from_file.as_ref(), "price");
1342    }
1343
1344    #[test]
1345    fn test_display_text_in_a_number_format() {
1346        let mut buffer = String::new();
1347        let value = CellValue::Float(-0.489_999_999_999_994_9);
1348        for (decimals, expected) in [(0, "-0"), (1, "-0.5"), (2, "-0.49"), (4, "-0.4900")] {
1349            let format = NumberFormat::new().with_decimals(decimals);
1350            assert_eq!(display_text_in(value, format, &mut buffer), expected);
1351            assert_eq!(
1352                number_fraction_digits(value, format),
1353                Some(u32::from(decimals))
1354            );
1355        }
1356        // Capped; integers and text keep their text.
1357        let many = NumberFormat::new().with_decimals(40);
1358        assert_eq!(many.decimals(), NumberFormat::MAX_DECIMALS);
1359        assert_eq!(display_text_in(CellValue::Int(7), many, &mut buffer), "7");
1360        assert_eq!(
1361            display_text_in(CellValue::Text("1.5"), many, &mut buffer),
1362            "1.5"
1363        );
1364        assert_eq!(number_fraction_digits(CellValue::Int(7), many), Some(0));
1365        assert_eq!(NumberFormat::default(), NumberFormat::new());
1366    }
1367
1368    #[test]
1369    fn test_display_text() {
1370        let mut buffer = String::with_capacity(32);
1371        let storage = buffer.as_ptr();
1372        let cases = [
1373            (CellValue::Empty, ""),
1374            (CellValue::Bool(true), "✓"),
1375            (CellValue::Bool(false), ""),
1376            (CellValue::Int(0), "0"),
1377            (CellValue::Int(-42), "-42"),
1378            (CellValue::Int(i64::MAX), "9223372036854775807"),
1379            (CellValue::Float(12.5), "12.50"),
1380            (CellValue::Float(2.0 / 3.0), "0.67"),
1381            (CellValue::Float(-1234.567), "-1234.57"),
1382            (CellValue::Float(f64::NAN), "NaN"),
1383            (CellValue::Text("AAPL"), "AAPL"),
1384            // A shorter number after a longer one leaves nothing behind.
1385            (CellValue::Int(7), "7"),
1386        ];
1387        for (value, expected) in cases {
1388            assert_eq!(display_text(value, &mut buffer), expected, "{value:?}");
1389        }
1390        // Numbers reuse the caller's buffer: no allocation per cell.
1391        assert_eq!(buffer.as_ptr(), storage);
1392
1393        // Text is borrowed from the row, not copied into the buffer.
1394        let row_text = String::from("Сбербанк");
1395        let text = display_text(CellValue::Text(&row_text), &mut buffer);
1396        assert!(std::ptr::eq(text, row_text.as_str()));
1397    }
1398
1399    #[test]
1400    fn test_number_text_alphabet() {
1401        // The quick filter skips numbers for terms outside this alphabet, so
1402        // every number must display inside it, the `core::fmt` fallbacks too.
1403        let mut buffer = String::new();
1404        let mut check = |value: CellValue| {
1405            let text = display_text(value, &mut buffer);
1406            assert!(may_occur_in_number_text(text), "{value:?} shows as {text}");
1407        };
1408        for value in [i64::MIN, -1, 0, 7, i64::MAX] {
1409            check(CellValue::Int(value));
1410        }
1411        for value in [
1412            f64::NAN,
1413            -f64::NAN,
1414            f64::INFINITY,
1415            f64::NEG_INFINITY,
1416            f64::MAX,
1417            f64::MIN,
1418            f64::MIN_POSITIVE,
1419            -0.0,
1420            1e19,
1421            -1e300,
1422        ] {
1423            check(CellValue::Float(value));
1424        }
1425        let mut rng = fastrand::Rng::with_seed(3);
1426        for _ in 0..10_000 {
1427            check(CellValue::Int(rng.i64(..)));
1428            check(CellValue::Float(f64::from_bits(rng.u64(..))));
1429        }
1430
1431        for term in ["12.50", "-3", "nan", "INF", "1e", "", "12,5", "✓", "sber"] {
1432            let expected = matches!(term, "12.50" | "-3" | "nan" | "INF" | "");
1433            assert_eq!(may_occur_in_number_text(term), expected, "{term}");
1434        }
1435    }
1436
1437    #[test]
1438    fn test_number_fraction_digits() {
1439        /// Exports as a float, or as another custom value.
1440        #[derive(Debug)]
1441        struct Exported(Option<CellValue<'static>>);
1442
1443        impl GridValue for Exported {
1444            fn cmp_value(&self, _: &dyn GridValue) -> Ordering {
1445                Ordering::Equal
1446            }
1447
1448            fn write_text(&self, out: &mut String) {
1449                out.push_str("3M");
1450            }
1451
1452            fn export_value(&self) -> Option<CellValue<'_>> {
1453                self.0
1454            }
1455        }
1456
1457        static TENOR: Exported = Exported(Some(CellValue::Float(0.25)));
1458        static NAMED: Exported = Exported(None);
1459        static ALIAS: Exported = Exported(Some(CellValue::Custom(&TENOR)));
1460
1461        // The digits after the point of a finite number's display text.
1462        let mut buffer = String::new();
1463        let numbers = [
1464            CellValue::Int(0),
1465            CellValue::Int(i64::MIN),
1466            CellValue::Float(12.5),
1467            CellValue::Float(-1234.567),
1468            CellValue::Float(2.0 / 3.0),
1469            CellValue::Float(1e300),
1470        ]
1471        .into_iter();
1472        #[cfg(feature = "decimal")]
1473        let numbers = numbers.chain(
1474            [(1230, 2), (5, 0), (-5, 2), (0, 3), (1, 28)]
1475                .map(|(mantissa, scale)| CellValue::Decimal(Decimal::new(mantissa, scale))),
1476        );
1477        for value in numbers {
1478            let text = display_text(value, &mut buffer);
1479            let shown = text
1480                .split_once('.')
1481                .map_or(0, |(_, fraction)| fraction.len());
1482            assert_eq!(
1483                number_fraction_digits(value, NumberFormat::new()),
1484                Some(shown as u32),
1485                "{value:?} shows as {text}"
1486            );
1487        }
1488        // A float that is not finite still asks for two decimals.
1489        assert_eq!(
1490            number_fraction_digits(CellValue::Float(f64::NAN), NumberFormat::new()),
1491            Some(2)
1492        );
1493
1494        // A custom value by its export value, when that is a number.
1495        assert_eq!(
1496            number_fraction_digits(CellValue::Custom(&TENOR), NumberFormat::new()),
1497            Some(2)
1498        );
1499        for value in [
1500            CellValue::Empty,
1501            CellValue::Bool(true),
1502            CellValue::Text("1.5"),
1503            CellValue::Timestamp(DateTime::<Utc>::UNIX_EPOCH),
1504            CellValue::Icon(IconValue::new("icons/check.svg", "1.5")),
1505            CellValue::Image(ImageValue::new("images/one.png", "1.5")),
1506            CellValue::Custom(&NAMED),
1507            CellValue::Custom(&ALIAS),
1508        ] {
1509            assert_eq!(
1510                number_fraction_digits(value, NumberFormat::new()),
1511                None,
1512                "{value:?}"
1513            );
1514        }
1515    }
1516
1517    fn hash_of(value: &OwnedValue) -> u64 {
1518        let mut hasher = rustc_hash::FxHasher::default();
1519        value.hash(&mut hasher);
1520        hasher.finish()
1521    }
1522
1523    #[test]
1524    fn test_owned_value() {
1525        // Every kind of value comes back as it was borrowed, bit for bit.
1526        let ladder: Vec<CellValue> = ladder().into_iter().flatten().collect();
1527        for value in &ladder {
1528            let owned = OwnedValue::from_cell(*value);
1529            let back = owned.as_cell();
1530            match (value, back) {
1531                (CellValue::Float(a), CellValue::Float(b)) => assert_eq!(a.to_bits(), b.to_bits()),
1532                _ => assert_eq!(*value, back),
1533            }
1534            assert_eq!(owned, owned.clone(), "{value:?} equals itself");
1535        }
1536
1537        // Equal exactly when `cmp_total` says so, and equal values hash
1538        // alike: on the edge values and on random pairs that tie often.
1539        let check = |a: &OwnedValue, b: &OwnedValue| {
1540            let equal = a.cmp_total(b).is_eq();
1541            assert_eq!(a == b, equal, "{a:?} and {b:?}");
1542            if equal {
1543                assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
1544            }
1545        };
1546        let owned: Vec<OwnedValue> = ladder.iter().map(|v| OwnedValue::from_cell(*v)).collect();
1547        for a in &owned {
1548            for b in &owned {
1549                check(a, b);
1550            }
1551        }
1552        let mut rng = fastrand::Rng::with_seed(26);
1553        let random = |rng: &mut fastrand::Rng| match rng.u8(0..6) {
1554            0 => owned[rng.usize(..owned.len())].clone(),
1555            1 => OwnedValue::Int(rng.i64(-3..=3)),
1556            2 => OwnedValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1557            3 => OwnedValue::Float([0.0, -0.0, f64::NAN, -f64::NAN][rng.usize(..4)]),
1558            4 => OwnedValue::Text(["", "a", "b"][rng.usize(..3)].into()),
1559            _ => [OwnedValue::Empty, OwnedValue::Bool(rng.bool())][rng.usize(..2)].clone(),
1560        };
1561        for _ in 0..10_000 {
1562            let (a, b) = (random(&mut rng), random(&mut rng));
1563            check(&a, &b);
1564        }
1565
1566        // One group key for 1 and 1.0; two for the zeros; a NaN is itself.
1567        use OwnedValue::{Float, Int};
1568        assert_eq!(Int(1), Float(1.0));
1569        assert_eq!(hash_of(&Int(1)), hash_of(&Float(1.0)));
1570        assert_eq!(Int(i64::MIN), Float(-9_223_372_036_854_775_808.0));
1571        assert_ne!(Float(-0.0), Float(0.0));
1572        assert_eq!(Int(0), Float(0.0));
1573        assert_ne!(Int(0), Float(-0.0));
1574        assert_eq!(Float(f64::NAN), Float(f64::NAN));
1575        assert_ne!(Float(f64::NAN), Float(-f64::NAN));
1576        assert_ne!(Int(1), Float(1.5));
1577        // Transitive on the zeros.
1578        let zeros = [Int(0), Float(-0.0), Float(0.0)];
1579        for a in &zeros {
1580            for b in &zeros {
1581                for c in &zeros {
1582                    if a == b && b == c {
1583                        assert_eq!(a, c, "{a:?}, {b:?}, {c:?}");
1584                    }
1585                }
1586            }
1587        }
1588        let set = rustc_hash::FxHashSet::from_iter(zeros);
1589        assert_eq!(set.len(), 2, "-0.0 and 0 are two keys");
1590    }
1591
1592    #[test]
1593    fn test_owned_value_serde() {
1594        use OwnedValue::{Bool, Empty, Float, Int, Text};
1595        for (value, json) in [
1596            (Empty, "null"),
1597            (Bool(true), "true"),
1598            (Int(-12), "-12"),
1599            (Int(i64::MAX), "9223372036854775807"),
1600            (Float(1.0), "1.0"),
1601            (Float(-0.0), "-0.0"),
1602            (Float(12.5), "12.5"),
1603            (Float(1e20), "1e+20"),
1604            (Text("AAPL".into()), r#""AAPL""#),
1605        ] {
1606            assert_eq!(serde_json::to_string(&value).unwrap(), json);
1607            let back: OwnedValue = serde_json::from_str(json).unwrap();
1608            assert_eq!(back, value, "{json}");
1609            // An integral float reads back as a float, an integer as one.
1610            assert_eq!(
1611                std::mem::discriminant(&back),
1612                std::mem::discriminant(&value),
1613                "{json}"
1614            );
1615        }
1616        assert!(matches!(serde_json::from_str("1"), Ok(Int(1))));
1617        assert!(matches!(serde_json::from_str("1.0"), Ok(Float(1.0))));
1618        // Neither a NaN nor an infinity is written; nor is an integer read
1619        // past `i64`, nor a list.
1620        for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1621            assert!(serde_json::to_string(&Float(value)).is_err(), "{value}");
1622        }
1623        assert!(serde_json::from_str::<OwnedValue>("9223372036854775808").is_err());
1624        assert!(serde_json::from_str::<OwnedValue>("[1]").is_err());
1625    }
1626
1627    #[test]
1628    fn test_change_direction_default() {
1629        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1630        struct Value;
1631
1632        impl GridColumns for Value {
1633            fn all() -> &'static [Self] {
1634                &[Self]
1635            }
1636
1637            fn id(self) -> &'static str {
1638                "value"
1639            }
1640
1641            fn ix(self) -> ColumnIx {
1642                ColumnIx::new(0)
1643            }
1644        }
1645
1646        struct Row(CellValue<'static>);
1647
1648        impl GridRow for Row {
1649            type Key = u8;
1650            type Column = Value;
1651
1652            fn key(&self) -> &u8 {
1653                &0
1654            }
1655
1656            fn value(&self, _: Value) -> CellValue<'_> {
1657                self.0
1658            }
1659        }
1660
1661        use CellValue::{Bool, Empty, Float, Int, Text};
1662        use ChangeDirection::{Down, Up};
1663        for (before, after, expected) in [
1664            (Int(1), Int(2), Some(Up)),
1665            (Int(2), Int(1), Some(Down)),
1666            (Float(1.5), Float(0.5), Some(Down)),
1667            (Float(0.5), Float(1.5), Some(Up)),
1668            (Int(1), Float(1.5), Some(Up)),
1669            (Float(1.5), Int(1), Some(Down)),
1670            (Int(1), Float(1.0), None),
1671            (Int(3), Int(3), None),
1672            (Text("a"), Text("b"), None),
1673            (Bool(false), Bool(true), None),
1674            (Empty, Int(1), None),
1675            (Int(1), Text("2"), None),
1676        ] {
1677            assert_eq!(
1678                Row(after).change_direction(&Row(before), Value),
1679                expected,
1680                "{before:?} → {after:?}"
1681            );
1682        }
1683        assert_eq!(Row(Int(1)).parent_key(), None);
1684    }
1685
1686    /// `Decimal` values, with the `decimal` feature.
1687    #[cfg(feature = "decimal")]
1688    mod decimal_values {
1689        use super::*;
1690
1691        /// A decimal from its mantissa and scale.
1692        fn dec(mantissa: i128, scale: u32) -> Decimal {
1693            Decimal::from_i128_with_scale(mantissa, scale)
1694        }
1695
1696        /// A zero with its sign bit set, as `-Decimal::ZERO` gives.
1697        fn negative_zero(scale: u32) -> Decimal {
1698            let mut zero = dec(0, scale);
1699            zero.set_sign_negative(true);
1700            zero
1701        }
1702
1703        /// Numbers of the three kinds in ascending order, written out by hand:
1704        /// each rung holds values `cmp_total` calls equal. Decimals of several
1705        /// scales sit with the integers and floats of their value, and between
1706        /// the floats where no float equals them: around zero, 10^-28 and 0.1
1707        /// (floats a little off their decimals), 1, 2^53, 2^63 and the ends of
1708        /// the decimal range, ±(2^96 - 1).
1709        fn decimal_ladder() -> Vec<Vec<CellValue<'static>>> {
1710            use CellValue::{Bool, Decimal, Empty, Float, Int, Text};
1711            let p53 = 1i64 << 53;
1712            let p63 = 9_223_372_036_854_775_808.0f64;
1713            let p96 = 79_228_162_514_264_337_593_543_950_336.0f64;
1714            let below_p96 = p96.next_down();
1715            let below_p96_int = (1i128 << 96) - (1 << 43);
1716            let max = rust_decimal::Decimal::MAX;
1717            vec![
1718                vec![Empty],
1719                vec![Bool(true)],
1720                vec![Float(f64::from_bits(0xFFF8_0000_0000_0000))],
1721                vec![Float(f64::NEG_INFINITY)],
1722                vec![Float(f64::MIN)],
1723                vec![Float(-p96)],
1724                vec![Decimal(rust_decimal::Decimal::MIN)],
1725                vec![Float(-below_p96), Decimal(dec(-below_p96_int, 0))],
1726                vec![Int(i64::MIN), Float(-p63), Decimal(dec(i64::MIN.into(), 0))],
1727                vec![Decimal(dec(i128::from(i64::MIN) * 10 + 5, 1))],
1728                vec![Int(i64::MIN + 1), Decimal(dec((i64::MIN + 1).into(), 0))],
1729                vec![Int(-p53 - 1), Decimal(dec(i128::from(-p53 - 1) * 100, 2))],
1730                vec![
1731                    Int(-p53),
1732                    Float(-p53 as f64),
1733                    Decimal(dec((-p53).into(), 0)),
1734                ],
1735                vec![Float(-1.5), Decimal(dec(-150, 2))],
1736                vec![
1737                    Int(-1),
1738                    Float(-1.0),
1739                    Decimal(dec(-1, 0)),
1740                    Decimal(dec(-1000, 3)),
1741                ],
1742                vec![Float(-0.5), Decimal(dec(-5, 1)), Decimal(dec(-50, 2))],
1743                vec![Float(-0.1)],
1744                vec![Decimal(dec(-1, 1))],
1745                vec![Decimal(dec(-1, 28))],
1746                // The float nearest 10^-28 is a little below it.
1747                vec![Float(-1e-28)],
1748                vec![Float(-f64::MIN_POSITIVE)],
1749                vec![Float(-f64::from_bits(1))],
1750                vec![Float(-0.0)],
1751                vec![
1752                    Int(0),
1753                    Float(0.0),
1754                    Decimal(dec(0, 0)),
1755                    Decimal(dec(0, 2)),
1756                    Decimal(-rust_decimal::Decimal::ZERO),
1757                    Decimal(negative_zero(28)),
1758                ],
1759                vec![Float(f64::from_bits(1))],
1760                vec![Float(f64::MIN_POSITIVE)],
1761                vec![Float(1e-28)],
1762                vec![Decimal(dec(1, 28))],
1763                vec![Decimal(dec(1, 1)), Decimal(dec(10, 2))],
1764                vec![Float(0.1)],
1765                vec![Float(0.5), Decimal(dec(5, 1)), Decimal(dec(5000, 4))],
1766                vec![Float(1f64.next_down())],
1767                vec![Decimal(dec(9_999_999_999_999_999_999_999_999_999, 28))],
1768                vec![Int(1), Float(1.0), Decimal(dec(1, 0)), Decimal(dec(100, 2))],
1769                vec![Decimal(dec(10_000_000_000_000_000_000_000_000_001, 28))],
1770                vec![Float(1f64.next_up())],
1771                vec![Float(1.5), Decimal(dec(15, 1))],
1772                vec![Decimal(dec(max.mantissa(), 28))],
1773                vec![Decimal(dec(123, 1)), Decimal(dec(1230, 2))],
1774                vec![
1775                    Int(p53 - 1),
1776                    Float((p53 - 1) as f64),
1777                    Decimal(dec((p53 - 1).into(), 0)),
1778                ],
1779                vec![
1780                    Int(p53),
1781                    Float(p53 as f64),
1782                    Decimal(dec(i128::from(p53) * 10, 1)),
1783                ],
1784                vec![Decimal(dec(i128::from(p53) * 10 + 5, 1))],
1785                vec![Int(p53 + 1), Decimal(dec((p53 + 1).into(), 0))],
1786                vec![
1787                    Int(p53 + 2),
1788                    Float((p53 + 2) as f64),
1789                    Decimal(dec((p53 + 2).into(), 0)),
1790                ],
1791                vec![Int(i64::MAX), Decimal(dec(i64::MAX.into(), 0))],
1792                vec![Decimal(dec(i128::from(i64::MAX) * 10 + 5, 1))],
1793                vec![
1794                    Float(p63),
1795                    Decimal(dec(1 << 63, 0)),
1796                    Decimal(dec(10 << 63, 1)),
1797                ],
1798                vec![Decimal(dec((1 << 63) + 1, 0))],
1799                vec![Float(below_p96), Decimal(dec(below_p96_int, 0))],
1800                vec![Decimal(max)],
1801                vec![Float(p96)],
1802                vec![Float(f64::MAX)],
1803                vec![Float(f64::INFINITY)],
1804                vec![Float(f64::from_bits(0x7FF8_0000_0000_0000))],
1805                vec![Text("0")],
1806            ]
1807        }
1808
1809        /// A random decimal: small values that tie with the integers and the
1810        /// halves `random_number` makes, zeros of any sign and scale, the
1811        /// decimal nearest a random float (often equal to it), and any mantissa
1812        /// below 2^96 with any scale.
1813        fn random_decimal(rng: &mut fastrand::Rng) -> Decimal {
1814            match rng.u8(0..4) {
1815                0 => dec(rng.i64(-30..=30).into(), rng.u32(0..=2)),
1816                1 => negative_zero(rng.u32(0..=28)),
1817                2 => Decimal::from_f64_retain(f64::from(rng.i32(..)) / 1024.0).unwrap_or_default(),
1818                _ => {
1819                    let bits = rng.u32(1..=96);
1820                    let magnitude = (rng.u128(..) >> (128 - bits)) as i128;
1821                    dec(
1822                        if rng.bool() { -magnitude } else { magnitude },
1823                        rng.u32(0..=28),
1824                    )
1825                }
1826            }
1827        }
1828
1829        #[test]
1830        fn test_decimal_display_and_order() {
1831            // The scale shows; numbers go into the caller's buffer.
1832            let mut buffer = String::with_capacity(64);
1833            let storage = buffer.as_ptr();
1834            for (value, expected) in [
1835                (dec(1230, 2), "12.30"),
1836                (dec(-5, 2), "-0.05"),
1837                (dec(0, 2), "0.00"),
1838                (dec(7, 0), "7"),
1839                (Decimal::MAX, "79228162514264337593543950335"),
1840                (dec(1, 28), "0.0000000000000000000000000001"),
1841                // A shorter number after a longer one leaves nothing behind.
1842                (dec(-1, 0), "-1"),
1843            ] {
1844                let text = display_text(CellValue::Decimal(value), &mut buffer);
1845                assert_eq!(text, expected, "{value:?}");
1846                assert!(may_occur_in_number_text(text), "{text}");
1847            }
1848            assert_eq!(buffer.as_ptr(), storage);
1849            assert!(CellValue::Decimal(dec(1, 0)).is_number());
1850            // `==` compares decimals by value: a change of scale alone is no
1851            // change for `GridRow::changed_columns`.
1852            assert_eq!(
1853                CellValue::Decimal(dec(10, 1)),
1854                CellValue::Decimal(dec(100, 2))
1855            );
1856            assert_ne!(CellValue::Decimal(dec(1, 0)), CellValue::Int(1));
1857
1858            // The hand-written order, and every triple of it.
1859            let ladder = decimal_ladder();
1860            for (rung, values) in ladder.iter().enumerate() {
1861                for (other_rung, others) in ladder.iter().enumerate() {
1862                    for a in values {
1863                        for b in others {
1864                            assert_eq!(
1865                                a.cmp_total(b),
1866                                rung.cmp(&other_rung),
1867                                "{a:?} against {b:?}"
1868                            );
1869                        }
1870                    }
1871                }
1872            }
1873            let values: Vec<CellValue> = ladder.into_iter().flatten().collect();
1874            for a in &values {
1875                for b in &values {
1876                    for c in &values {
1877                        check_total_order(a, b, c);
1878                    }
1879                }
1880            }
1881
1882            // Random triples that tie often across the three kinds, zeros of
1883            // every kind included.
1884            let mut rng = fastrand::Rng::with_seed(0xdec1);
1885            let random = |rng: &mut fastrand::Rng| match rng.u8(0..8) {
1886                0 => values[rng.usize(..values.len())],
1887                1 => CellValue::Int(rng.i64(-3..=3)),
1888                2 => CellValue::Float(f64::from(rng.i8(-6..=6)) / 2.0),
1889                3 => CellValue::Float([0.0, -0.0][rng.usize(..2)]),
1890                4 => CellValue::Float(f64::from(rng.i32(..)) / 1024.0),
1891                5 => CellValue::Float(f64::from_bits(rng.u64(..))),
1892                _ => CellValue::Decimal(random_decimal(rng)),
1893            };
1894            for _ in 0..200_000 {
1895                let (a, b, c) = (random(&mut rng), random(&mut rng), random(&mut rng));
1896                check_total_order(&a, &b, &c);
1897            }
1898
1899            // A float with more decimals than a `Decimal` holds, its neighbours
1900            // and the decimal nearest it: rounding the float to compare would
1901            // call the decimal equal to two different floats.
1902            for _ in 0..20_000 {
1903                let float = f64::from(rng.u32(..)) * 10f64.powi(-rng.i32(10..=40));
1904                let float = if rng.bool() { float } else { -float };
1905                let Some(near) = Decimal::from_f64_retain(float) else {
1906                    continue;
1907                };
1908                let near = CellValue::Decimal(near);
1909                for other in [float.next_down(), float.next_up()] {
1910                    let (float, other) = (CellValue::Float(float), CellValue::Float(other));
1911                    for (a, b, c) in [
1912                        (&near, &float, &other),
1913                        (&float, &near, &other),
1914                        (&float, &other, &near),
1915                    ] {
1916                        check_total_order(a, b, c);
1917                    }
1918                }
1919            }
1920
1921            // Sorting a random mix does not panic, and neighbours are in order.
1922            let mut mix: Vec<CellValue> = (0..20_000).map(|_| random(&mut rng)).collect();
1923            mix.sort_by(|a, b| a.cmp_total(b));
1924            for pair in mix.windows(2) {
1925                assert_ne!(pair[0].cmp_total(&pair[1]), Ordering::Greater, "{pair:?}");
1926            }
1927        }
1928
1929        #[test]
1930        fn test_decimal_owned_value() {
1931            use OwnedValue::{Decimal as Dec, Float, Int};
1932            let check = |a: &OwnedValue, b: &OwnedValue| {
1933                let equal = a.cmp_total(b).is_eq();
1934                assert_eq!(a == b, equal, "{a:?} and {b:?}");
1935                if equal {
1936                    assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
1937                }
1938            };
1939            let owned: Vec<OwnedValue> = decimal_ladder()
1940                .into_iter()
1941                .flatten()
1942                .map(OwnedValue::from_cell)
1943                .collect();
1944            for a in &owned {
1945                assert!(
1946                    matches!((a, a.as_cell()), (Dec(a), CellValue::Decimal(b)) if a.scale() == b.scale())
1947                        || !matches!(a, Dec(_)),
1948                    "{a:?} keeps its scale"
1949                );
1950                for b in &owned {
1951                    check(a, b);
1952                }
1953            }
1954            let mut rng = fastrand::Rng::with_seed(0xdec2);
1955            let random = |rng: &mut fastrand::Rng| match rng.u8(0..5) {
1956                0 => owned[rng.usize(..owned.len())].clone(),
1957                1 => Int(rng.i64(-30..=30)),
1958                2 => Float(f64::from(rng.i32(..)) / 1024.0),
1959                3 => Float(f64::from(rng.i8(-6..=6)) / 2.0),
1960                _ => Dec(random_decimal(rng)),
1961            };
1962            for _ in 0..50_000 {
1963                let (a, b) = (random(&mut rng), random(&mut rng));
1964                check(&a, &b);
1965            }
1966
1967            // One group key for 1, 1.0 and 1.00; zeros of every sign and scale
1968            // are the key of `Int(0)`, not of -0.0.
1969            assert_eq!(Dec(dec(100, 2)), Int(1));
1970            assert_eq!(Dec(dec(100, 2)), Float(1.0));
1971            assert_eq!(Dec(dec(5, 1)), Float(0.5));
1972            assert_ne!(Dec(dec(1, 1)), Float(0.1));
1973            assert_eq!(hash_of(&Dec(dec(150, 2))), hash_of(&Dec(dec(15, 1))));
1974            let zeros = [
1975                Int(0),
1976                Float(0.0),
1977                Float(-0.0),
1978                Dec(dec(0, 0)),
1979                Dec(dec(0, 3)),
1980                Dec(negative_zero(2)),
1981            ];
1982            for a in &zeros {
1983                for b in &zeros {
1984                    check(a, b);
1985                }
1986            }
1987            let set = rustc_hash::FxHashSet::from_iter(zeros);
1988            assert_eq!(set.len(), 2, "-0.0 and 0 are two keys");
1989        }
1990
1991        #[test]
1992        fn test_decimal_owned_value_serde() {
1993            for (value, json) in [
1994                (dec(1230, 2), r#"{"decimal":"12.30"}"#),
1995                (dec(-5, 2), r#"{"decimal":"-0.05"}"#),
1996                (dec(0, 3), r#"{"decimal":"0.000"}"#),
1997                (
1998                    Decimal::MAX,
1999                    r#"{"decimal":"79228162514264337593543950335"}"#,
2000                ),
2001                (
2002                    dec(1, 28),
2003                    r#"{"decimal":"0.0000000000000000000000000001"}"#,
2004                ),
2005            ] {
2006                let owned = OwnedValue::Decimal(value);
2007                assert_eq!(serde_json::to_string(&owned).unwrap(), json);
2008                let back: OwnedValue = serde_json::from_str(json).unwrap();
2009                // The scale survives the round trip: `12.30`, not `12.3`.
2010                assert!(
2011                    matches!(back, OwnedValue::Decimal(back) if back.to_string() == value.to_string()),
2012                    "{json}: {back:?}"
2013                );
2014            }
2015            let pretty: OwnedValue = serde_json::from_str(r#" { "decimal" : "1.50" } "#).unwrap();
2016            assert!(matches!(pretty, OwnedValue::Decimal(value) if value.to_string() == "1.50"));
2017            // A number, not a decimal text; text that is not a number; more
2018            // digits than a `Decimal` holds (it would round); another key, none
2019            // or one too many.
2020            for json in [
2021                r#"{"decimal":12.3}"#,
2022                r#"{"decimal":"12,30"}"#,
2023                r#"{"decimal":"1e3"}"#,
2024                r#"{"decimal":"0.00000000000000000000000000001"}"#,
2025                r#"{"decimal":"79228162514264337593543950336"}"#,
2026                r#"{"money":"12.30"}"#,
2027                r#"{}"#,
2028                r#"{"decimal":"1","decimal":"2"}"#,
2029            ] {
2030                assert!(serde_json::from_str::<OwnedValue>(json).is_err(), "{json}");
2031            }
2032            // Other values read as before.
2033            assert!(matches!(serde_json::from_str("1"), Ok(OwnedValue::Int(1))));
2034            assert!(
2035                matches!(serde_json::from_str(r#""12.30""#), Ok(OwnedValue::Text(text)) if &*text == "12.30")
2036            );
2037        }
2038
2039        #[test]
2040        fn test_decimal_change_direction() {
2041            #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2042            struct Amount;
2043
2044            impl GridColumns for Amount {
2045                fn all() -> &'static [Self] {
2046                    &[Self]
2047                }
2048
2049                fn id(self) -> &'static str {
2050                    "amount"
2051                }
2052
2053                fn ix(self) -> ColumnIx {
2054                    ColumnIx::new(0)
2055                }
2056            }
2057
2058            struct Row(CellValue<'static>);
2059
2060            impl GridRow for Row {
2061                type Key = u8;
2062                type Column = Amount;
2063
2064                fn key(&self) -> &u8 {
2065                    &0
2066                }
2067
2068                fn value(&self, _: Amount) -> CellValue<'_> {
2069                    self.0
2070                }
2071            }
2072
2073            use CellValue::{Decimal as Dec, Float, Int, Text};
2074            use ChangeDirection::{Down, Up};
2075            for (before, after, expected) in [
2076                (Dec(dec(1230, 2)), Dec(dec(1231, 2)), Some(Up)),
2077                (Dec(dec(1230, 2)), Dec(dec(-1, 0)), Some(Down)),
2078                (Dec(dec(10, 1)), Dec(dec(100, 2)), None),
2079                (Int(1), Dec(dec(100, 2)), None),
2080                (Float(0.1), Dec(dec(1, 1)), Some(Down)),
2081                (Dec(dec(5, 1)), Float(0.75), Some(Up)),
2082                (Text("1"), Dec(dec(1, 0)), None),
2083            ] {
2084                assert_eq!(
2085                    Row(after).change_direction(&Row(before), Amount),
2086                    expected,
2087                    "{before:?} → {after:?}"
2088                );
2089            }
2090            // A change of scale alone is not a change.
2091            let mut changed = ColumnSet::EMPTY;
2092            Row(Dec(dec(100, 2))).changed_columns(&Row(Dec(dec(1, 0))), &mut changed);
2093            assert!(changed.is_empty());
2094        }
2095    }
2096
2097    /// `Timestamp`, `Icon` and `Custom` values.
2098    mod new_values {
2099        use chrono::TimeZone as _;
2100
2101        use super::*;
2102
2103        /// A custom value that sorts by rank: worst first.
2104        #[derive(Debug)]
2105        struct Severity(u8);
2106
2107        impl GridValue for Severity {
2108            fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
2109                match other.downcast_ref::<Severity>() {
2110                    Some(other) => self.0.cmp(&other.0),
2111                    None => type_order::<Self>(other),
2112                }
2113            }
2114
2115            fn write_text(&self, out: &mut String) {
2116                out.push_str(["Critical", "Warning", "Info"][usize::from(self.0)]);
2117            }
2118        }
2119
2120        /// A custom value with its own export and owned form: a tenor in
2121        /// days, shown as `3M`.
2122        #[derive(Debug)]
2123        struct Tenor(u32);
2124
2125        impl GridValue for Tenor {
2126            fn cmp_value(&self, other: &dyn GridValue) -> Ordering {
2127                match other.downcast_ref::<Tenor>() {
2128                    Some(other) => self.0.cmp(&other.0),
2129                    None => type_order::<Self>(other),
2130                }
2131            }
2132
2133            fn write_text(&self, out: &mut String) {
2134                let _ = write!(out, "{}M", self.0 / 30);
2135            }
2136
2137            fn export_value(&self) -> Option<CellValue<'_>> {
2138                Some(CellValue::Int(self.0.into()))
2139            }
2140
2141            fn to_owned_value(&self) -> OwnedValue {
2142                OwnedValue::Int(self.0.into())
2143            }
2144        }
2145
2146        /// Values of two types order by type, as `GridValue::cmp_value` asks.
2147        fn type_order<T: GridValue>(other: &dyn GridValue) -> Ordering {
2148            std::any::TypeId::of::<T>().cmp(&other.as_any().type_id())
2149        }
2150
2151        static SEVERITY: [Severity; 4] = [Severity(0), Severity(1), Severity(1), Severity(2)];
2152        static TENOR: [Tenor; 2] = [Tenor(30), Tenor(90)];
2153
2154        fn at(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime<Utc> {
2155            Utc.with_ymd_and_hms(year, month, day, hour, min, sec)
2156                .unwrap()
2157        }
2158
2159        fn icon(path: &'static str, text: &'static str) -> CellValue<'static> {
2160            CellValue::Icon(IconValue::new(path, text))
2161        }
2162
2163        fn image(id: &'static str, text: &'static str) -> CellValue<'static> {
2164            CellValue::Image(ImageValue::new(id, text))
2165        }
2166
2167        /// The `ladder()` with the new values in their places:
2168        /// timestamps between numbers and text, icons beside the text of
2169        /// their own text, custom values last, the two types apart.
2170        fn new_values_ladder() -> Vec<Vec<CellValue<'static>>> {
2171            use CellValue::{Custom, Text, Timestamp};
2172            let mut ladder = ladder();
2173            let text_at = ladder
2174                .iter()
2175                .position(|rung| matches!(rung[0], Text(_)))
2176                .unwrap();
2177            for (offset, time) in [
2178                DateTime::<Utc>::MIN_UTC,
2179                at(1969, 12, 31, 23, 59, 59),
2180                DateTime::UNIX_EPOCH,
2181                at(2026, 11, 4, 12, 34, 56),
2182                at(2026, 11, 4, 12, 34, 56) + chrono::Duration::nanoseconds(1),
2183                DateTime::<Utc>::MAX_UTC,
2184            ]
2185            .into_iter()
2186            .enumerate()
2187            {
2188                ladder.insert(text_at + offset, vec![Timestamp(time)]);
2189            }
2190            for rung in &mut ladder {
2191                match rung[0] {
2192                    Text("") => rung.push(icon("icons/blank.svg", "")),
2193                    Text("a") => rung.extend([
2194                        icon("icons/a.svg", "a"),
2195                        icon("icons/b.svg", "a"),
2196                        image("images/a.png", "a"),
2197                    ]),
2198                    _ => {}
2199                }
2200            }
2201            ladder.push(vec![icon("icons/z.svg", "z"), image("images/z.svg", "z")]);
2202            let severities = [
2203                vec![Custom(&SEVERITY[0])],
2204                vec![Custom(&SEVERITY[1]), Custom(&SEVERITY[2])],
2205                vec![Custom(&SEVERITY[3])],
2206            ];
2207            let tenors = [vec![Custom(&TENOR[0])], vec![Custom(&TENOR[1])]];
2208            // The two types in the order of their `TypeId`s.
2209            if type_order::<Severity>(&TENOR[0]).is_lt() {
2210                ladder.extend(severities.into_iter().chain(tenors));
2211            } else {
2212                ladder.extend(tenors.into_iter().chain(severities));
2213            }
2214            ladder
2215        }
2216
2217        #[test]
2218        fn test_new_values_order() {
2219            let ladder = new_values_ladder();
2220            for (rung, values) in ladder.iter().enumerate() {
2221                for (other_rung, others) in ladder.iter().enumerate() {
2222                    for a in values {
2223                        for b in others {
2224                            assert_eq!(
2225                                a.cmp_total(b),
2226                                rung.cmp(&other_rung),
2227                                "{a:?} against {b:?}"
2228                            );
2229                        }
2230                    }
2231                }
2232            }
2233            let values: Vec<CellValue> = ladder.into_iter().flatten().collect();
2234            for a in &values {
2235                for b in &values {
2236                    for c in &values {
2237                        check_total_order(a, b, c);
2238                    }
2239                }
2240            }
2241            // An icon sorts by its text, not its path: equal to the text.
2242            assert_eq!(
2243                icon("icons/z.svg", "a").cmp_total(&CellValue::Text("a")),
2244                Ordering::Equal
2245            );
2246            assert_eq!(
2247                icon("icons/a.svg", "b").cmp_total(&icon("icons/z.svg", "a")),
2248                Ordering::Greater
2249            );
2250            // An image too, by its text, not its id.
2251            assert_eq!(
2252                image("images/z.png", "a").cmp_total(&CellValue::Text("a")),
2253                Ordering::Equal
2254            );
2255            assert_eq!(
2256                image("images/a.png", "b").cmp_total(&icon("icons/z.svg", "a")),
2257                Ordering::Greater
2258            );
2259            // Sorting a shuffled mix does not panic, and neighbours are in order.
2260            let mut rng = fastrand::Rng::with_seed(0x5e7);
2261            let mut mix: Vec<CellValue> = (0..5_000)
2262                .map(|_| values[rng.usize(..values.len())])
2263                .collect();
2264            mix.sort_by(|a, b| a.cmp_total(b));
2265            for pair in mix.windows(2) {
2266                assert_ne!(pair[0].cmp_total(&pair[1]), Ordering::Greater, "{pair:?}");
2267            }
2268            // None of them is a number: left-aligned, no direction, no sum.
2269            for value in [
2270                CellValue::Timestamp(DateTime::UNIX_EPOCH),
2271                icon("icons/a.svg", "1"),
2272                image("images/a.png", "1"),
2273                CellValue::Custom(&TENOR[0]),
2274            ] {
2275                assert!(!value.is_number(), "{value:?}");
2276            }
2277        }
2278
2279        #[test]
2280        fn test_new_values_text() {
2281            let mut buffer = String::with_capacity(32);
2282            let (storage, capacity) = (buffer.as_ptr(), buffer.capacity());
2283            for _ in 0..2 {
2284                for (value, expected) in [
2285                    (
2286                        CellValue::Timestamp(at(2026, 11, 4, 12, 34, 56)),
2287                        "2026-11-04 12:34:56",
2288                    ),
2289                    (
2290                        CellValue::Timestamp(at(2026, 11, 4, 0, 0, 0)),
2291                        "2026-11-04 00:00:00",
2292                    ),
2293                    (
2294                        CellValue::Timestamp(at(2024, 2, 29, 23, 59, 59)),
2295                        "2024-02-29 23:59:59",
2296                    ),
2297                    // A fraction does not show.
2298                    (
2299                        CellValue::Timestamp(
2300                            at(1999, 12, 31, 23, 59, 59) + chrono::Duration::milliseconds(999),
2301                        ),
2302                        "1999-12-31 23:59:59",
2303                    ),
2304                    (icon("icons/check.svg", "Filled"), "Filled"),
2305                    (image("images/avatar.png", "Ada"), "Ada"),
2306                    (CellValue::Custom(&SEVERITY[0]), "Critical"),
2307                    (CellValue::Custom(&TENOR[1]), "3M"),
2308                    // A shorter text after a longer one leaves nothing behind.
2309                    (CellValue::Custom(&SEVERITY[3]), "Info"),
2310                ] {
2311                    assert_eq!(display_text(value, &mut buffer), expected, "{value:?}");
2312                }
2313            }
2314            // Timestamps and custom values reuse the caller's buffer: no
2315            // allocation per cell, even the second time round.
2316            assert_eq!((buffer.as_ptr(), buffer.capacity()), (storage, capacity));
2317
2318            // The text of an icon is borrowed from the row, not its path.
2319            let row_text = String::from("Отменена");
2320            let borrowed = CellValue::Icon(IconValue::new("icons/x.svg", &row_text));
2321            assert!(std::ptr::eq(
2322                display_text(borrowed, &mut buffer),
2323                row_text.as_str()
2324            ));
2325            let value = IconValue::new("icons/x.svg", "x");
2326            assert_eq!((value.path(), value.text()), ("icons/x.svg", "x"));
2327            let value = ImageValue::new("images/x.png", "x");
2328            assert_eq!((value.id(), value.text()), ("images/x.png", "x"));
2329            assert_eq!(
2330                OwnedValue::from_cell(CellValue::Image(value)),
2331                OwnedValue::Text("x".into())
2332            );
2333        }
2334
2335        #[test]
2336        fn test_new_values_eq_and_diff() {
2337            use CellValue::{Custom, Float, Int, Text, Timestamp};
2338            // The old variants compare as the derived `PartialEq` did.
2339            assert_ne!(Float(f64::NAN), Float(f64::NAN));
2340            assert_eq!(Float(-0.0), Float(0.0));
2341            assert_ne!(Int(1), Float(1.0));
2342            assert_eq!(Text("a"), Text("a"));
2343            assert_ne!(CellValue::Empty, CellValue::Bool(false));
2344            // A timestamp by its time; an icon by path and text, never equal
2345            // to a text; a custom value by `cmp_value`.
2346            let noon = at(2026, 11, 4, 12, 0, 0);
2347            assert_eq!(Timestamp(noon), Timestamp(noon));
2348            assert_ne!(
2349                Timestamp(noon),
2350                Timestamp(noon + chrono::Duration::seconds(1))
2351            );
2352            assert_eq!(icon("icons/a.svg", "a"), icon("icons/a.svg", "a"));
2353            assert_ne!(icon("icons/a.svg", "a"), icon("icons/b.svg", "a"));
2354            assert_ne!(icon("icons/a.svg", "a"), icon("icons/a.svg", "b"));
2355            assert_ne!(icon("icons/a.svg", "a"), Text("a"));
2356            assert_eq!(Custom(&SEVERITY[1]), Custom(&SEVERITY[2]));
2357            assert_ne!(Custom(&SEVERITY[0]), Custom(&SEVERITY[1]));
2358            assert_ne!(Custom(&SEVERITY[0]), Custom(&TENOR[0]));
2359            assert_ne!(Custom(&SEVERITY[0]), Text("Critical"));
2360
2361            // The default `changed_columns` sees each kind of change.
2362            #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2363            enum Column {
2364                Placed,
2365                Status,
2366                Severity,
2367            }
2368
2369            impl GridColumns for Column {
2370                fn all() -> &'static [Self] {
2371                    &[Self::Placed, Self::Status, Self::Severity]
2372                }
2373
2374                fn id(self) -> &'static str {
2375                    match self {
2376                        Self::Placed => "placed",
2377                        Self::Status => "status",
2378                        Self::Severity => "severity",
2379                    }
2380                }
2381
2382                fn ix(self) -> ColumnIx {
2383                    ColumnIx::new(self as u16)
2384                }
2385            }
2386
2387            struct Row {
2388                placed: DateTime<Utc>,
2389                status: (&'static str, &'static str),
2390                severity: &'static Severity,
2391            }
2392
2393            impl GridRow for Row {
2394                type Key = u8;
2395                type Column = Column;
2396
2397                fn key(&self) -> &u8 {
2398                    &0
2399                }
2400
2401                fn value(&self, column: Column) -> CellValue<'_> {
2402                    match column {
2403                        Column::Placed => Timestamp(self.placed),
2404                        Column::Status => {
2405                            CellValue::Icon(IconValue::new(self.status.0, self.status.1))
2406                        }
2407                        Column::Severity => Custom(self.severity),
2408                    }
2409                }
2410            }
2411
2412            let before = Row {
2413                placed: noon,
2414                status: ("icons/clock.svg", "Working"),
2415                severity: &SEVERITY[1],
2416            };
2417            let changes = |after: Row| {
2418                let mut changed = ColumnSet::EMPTY;
2419                after.changed_columns(&before, &mut changed);
2420                Column::all()
2421                    .iter()
2422                    .copied()
2423                    .filter(|column| changed.contains(column.ix()))
2424                    .collect::<Vec<_>>()
2425            };
2426            assert_eq!(
2427                changes(Row {
2428                    severity: &SEVERITY[2],
2429                    ..before
2430                }),
2431                []
2432            );
2433            assert_eq!(
2434                changes(Row {
2435                    placed: noon + chrono::Duration::milliseconds(1),
2436                    ..before
2437                }),
2438                [Column::Placed]
2439            );
2440            assert_eq!(
2441                changes(Row {
2442                    status: ("icons/check.svg", "Working"),
2443                    ..before
2444                }),
2445                [Column::Status]
2446            );
2447            assert_eq!(
2448                changes(Row {
2449                    status: ("icons/clock.svg", "Filled"),
2450                    severity: &SEVERITY[0],
2451                    ..before
2452                }),
2453                [Column::Status, Column::Severity]
2454            );
2455            // No direction for new values: they are not numbers.
2456            let later = Row {
2457                placed: noon + chrono::Duration::hours(1),
2458                ..before
2459            };
2460            assert_eq!(later.change_direction(&before, Column::Placed), None);
2461        }
2462
2463        #[test]
2464        fn test_owned_timestamp_serde() {
2465            let time = at(2026, 11, 4, 12, 34, 56);
2466            for (value, json) in [
2467                (time, r#"{"timestamp":"2026-11-04T12:34:56Z"}"#),
2468                (
2469                    time + chrono::Duration::milliseconds(250),
2470                    r#"{"timestamp":"2026-11-04T12:34:56.250Z"}"#,
2471                ),
2472                (
2473                    time + chrono::Duration::nanoseconds(1),
2474                    r#"{"timestamp":"2026-11-04T12:34:56.000000001Z"}"#,
2475                ),
2476                (
2477                    DateTime::UNIX_EPOCH,
2478                    r#"{"timestamp":"1970-01-01T00:00:00Z"}"#,
2479                ),
2480                (
2481                    at(0, 1, 1, 0, 0, 0),
2482                    r#"{"timestamp":"0000-01-01T00:00:00Z"}"#,
2483                ),
2484            ] {
2485                let owned = OwnedValue::Timestamp(value);
2486                assert_eq!(serde_json::to_string(&owned).unwrap(), json);
2487                let back: OwnedValue = serde_json::from_str(json).unwrap();
2488                assert!(
2489                    matches!(back, OwnedValue::Timestamp(back) if back == value),
2490                    "{json}"
2491                );
2492            }
2493            // Any offset reads as the point in time it names.
2494            let moscow: OwnedValue =
2495                serde_json::from_str(r#" { "timestamp" : "2026-11-04T15:34:56+03:00" } "#).unwrap();
2496            assert_eq!(moscow, OwnedValue::Timestamp(time));
2497            assert_eq!(hash_of(&moscow), hash_of(&OwnedValue::Timestamp(time)));
2498            // A string stays text, even one that reads as a time.
2499            assert!(matches!(
2500                serde_json::from_str(r#""2026-11-04T12:34:56Z""#),
2501                Ok(OwnedValue::Text(text)) if &*text == "2026-11-04T12:34:56Z"
2502            ));
2503            // Not a time, not RFC 3339, a number, another key, none, or one
2504            // too many.
2505            for json in [
2506                r#"{"timestamp":"x"}"#,
2507                r#"{"timestamp":"2026-11-04 12:34:56"}"#,
2508                r#"{"timestamp":"2026-11-04"}"#,
2509                r#"{"timestamp":1762259696}"#,
2510                r#"{"time":"2026-11-04T12:34:56Z"}"#,
2511                r#"{}"#,
2512                r#"{"timestamp":"2026-11-04T12:34:56Z","timestamp":"2026-11-04T12:34:56Z"}"#,
2513            ] {
2514                assert!(serde_json::from_str::<OwnedValue>(json).is_err(), "{json}");
2515            }
2516            // A year RFC 3339 cannot write is not serialized.
2517            for value in [DateTime::<Utc>::MAX_UTC, DateTime::<Utc>::MIN_UTC] {
2518                assert!(serde_json::to_string(&OwnedValue::Timestamp(value)).is_err());
2519            }
2520
2521            // Owned values: a timestamp is itself, an icon its text, a custom
2522            // value what `to_owned_value` gives (by default its text).
2523            assert_eq!(
2524                OwnedValue::from_cell(CellValue::Timestamp(time)).as_cell(),
2525                CellValue::Timestamp(time)
2526            );
2527            assert!(matches!(
2528                OwnedValue::from_cell(icon("icons/check.svg", "Filled")),
2529                OwnedValue::Text(text) if &*text == "Filled"
2530            ));
2531            assert!(matches!(
2532                OwnedValue::from_cell(CellValue::Custom(&SEVERITY[0])),
2533                OwnedValue::Text(text) if &*text == "Critical"
2534            ));
2535            assert!(matches!(
2536                OwnedValue::from_cell(CellValue::Custom(&TENOR[1])),
2537                OwnedValue::Int(90)
2538            ));
2539            // Equal exactly when `cmp_total` says so, and equal values hash
2540            // alike, timestamps beside every other kind.
2541            let owned: Vec<OwnedValue> = new_values_ladder()
2542                .into_iter()
2543                .flatten()
2544                .map(OwnedValue::from_cell)
2545                .collect();
2546            for a in &owned {
2547                for b in &owned {
2548                    assert_eq!(a == b, a.cmp_total(b).is_eq(), "{a:?} and {b:?}");
2549                    if a == b {
2550                        assert_eq!(hash_of(a), hash_of(b), "{a:?} and {b:?} hash apart");
2551                    }
2552                }
2553            }
2554        }
2555
2556        #[test]
2557        fn test_grid_value_downcast() {
2558            let value: &dyn GridValue = &TENOR[0];
2559            assert!(
2560                value
2561                    .downcast_ref::<Tenor>()
2562                    .is_some_and(|tenor| tenor.0 == 30)
2563            );
2564            assert!(value.downcast_ref::<Severity>().is_none());
2565            assert_eq!(value.as_any().type_id(), std::any::TypeId::of::<Tenor>());
2566            // The default export is none: a raw copy writes the text.
2567            assert!(SEVERITY[0].export_value().is_none());
2568        }
2569    }
2570
2571    #[test]
2572    fn test_column_set_rank() {
2573        let mut set = ColumnSet::EMPTY;
2574        for ix in [1, 4, 127] {
2575            set.insert(ColumnIx::new(ix));
2576        }
2577        let ranks: Vec<usize> = [0, 1, 2, 4, 5, 127]
2578            .map(|ix| set.rank(ColumnIx::new(ix)))
2579            .to_vec();
2580        assert_eq!(ranks, [0, 0, 1, 1, 2, 2]);
2581    }
2582}