From 5e22f9b373f32c3af7cee5f0f339482c207ed6a6 Mon Sep 17 00:00:00 2001 From: nihilist-narwhal <240798405+nihilist-narwhal@users.noreply.github.com> Date: Sat, 10 Oct 2026 03:40:56 -0500 Subject: [PATCH] Sort: case-sensitive order puts lowercase before uppercase With Case sensitive checked, Sort compared text by code point, so every uppercase word came before every lowercase one (A, B, a, b). Excel compares ignoring case first and only then puts lowercase before uppercase: a, A, b, B. --- crates/engine/src/cmd/data.rs | 8 +++++++- crates/engine/src/tests.rs | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs index fd50afbf2..94733071c 100644 --- a/crates/engine/src/cmd/data.rs +++ b/crates/engine/src/cmd/data.rs @@ -248,7 +248,7 @@ fn do_sort(s: &mut Session, r: RangeRef, header: bool, keys: Vec, by_co let pos = |v: &Value| v.as_text().and_then(|t| list.iter().position(|x| x.eq_ignore_ascii_case(t))).unwrap_or(usize::MAX); pos(va).cmp(&pos(vb)) } else if case && va.is_text() && vb.is_text() { - va.as_text().unwrap_or("").cmp(vb.as_text().unwrap_or("")) + sort_compare(va, vb).then_with(|| case_order(va.as_text().unwrap_or(""), vb.as_text().unwrap_or(""))) } else { sort_compare(va, vb) }; @@ -653,6 +653,12 @@ pub fn filter_values(wb: &Workbook, sheet: usize, col: u32) -> Vec<(String, bool seen.into_iter().map(|((_, t), on)| (t, on)).collect() } +/// Case-sensitive tie-break for text that is equal ignoring case: as in Excel, lowercase sorts +/// before uppercase at the first letter that differs in case. +fn case_order(a: &str, b: &str) -> Ordering { + a.chars().zip(b.chars()).find(|(x, y)| x != y).map_or(Ordering::Equal, |(x, y)| y.is_lowercase().cmp(&x.is_lowercase())) +} + fn sort_key(v: &Value) -> (u8, i64, String) { match v { Value::Number(n) => (0, (n * 1e6) as i64, String::new()), diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 6bd03d55e..92ca38665 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -606,6 +606,15 @@ fn hidden_formulas_stay_hidden_on_protected_sheets() { assert_eq!(r["results"][0]["value"], "7"); } +#[test] +fn case_sensitive_sort_puts_lowercase_first() { + let mut s = s(); + s.execute("range.setValues", json!({"range": "A1", "values": [["b"], ["B"], ["Ab"], ["a"], ["aB"], ["A"]]})).unwrap(); + s.execute("data.sort", json!({"range": "A1:A6", "header": false, "matchCase": true, "keys": [{"column": "A"}]})).unwrap(); + let col: Vec = (1..=6).map(|r| v(&s, &format!("A{r}"))).collect(); + assert_eq!(col, ["a", "A", "aB", "Ab", "b", "B"].map(Value::from)); +} + #[test] fn formatting_and_styles() { let mut s = s();