From edf48f6f11b1a8ea773cb91ebda92fd006a4b1e7 Mon Sep 17 00:00:00 2001 From: Yorhel <git@yorhel.nl> Date: Thu, 3 Feb 2022 10:59:42 +0100 Subject: [PATCH] Use natsort when sorting by name Fixes #181, now also for Zig. --- src/browser.zig | 5 +-- src/util.zig | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/browser.zig b/src/browser.zig index 631f908..44fda3f 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -103,11 +103,10 @@ fn sortLt(_: void, ap: ?*model.Entry, bp: ?*model.Entry) bool { }, } - // TODO: Unicode-aware sorting might be nice (and slow) const an = a.name(); const bn = b.name(); - return if (main.config.sort_order == .asc) std.mem.lessThan(u8, an, bn) - else std.mem.lessThan(u8, bn, an); + return if (main.config.sort_order == .asc) util.strnatcmp(an, bn) == .lt + else util.strnatcmp(bn, an) == .lt; } // Should be called when: diff --git a/src/util.zig b/src/util.zig index 7655cae..3d7ba3c 100644 --- a/src/util.zig +++ b/src/util.zig @@ -36,3 +36,109 @@ pub fn arrayListBufZ(buf: *std.ArrayList(u8)) [:0]const u8 { defer buf.items.len -= 1; return buf.items[0..buf.items.len-1:0]; } + +// Straightforward Zig port of strnatcmp() from https://github.com/sourcefrog/natsort/ +// (Requiring nul-terminated strings is ugly, but we've got them anyway and it does simplify the code) +pub fn strnatcmp(a: [:0]const u8, b: [:0]const u8) std.math.Order { + var ai: usize = 0; + var bi: usize = 0; + const isDigit = std.ascii.isDigit; + while (true) { + while (std.ascii.isSpace(a[ai])) ai += 1; + while (std.ascii.isSpace(b[bi])) bi += 1; + + if (isDigit(a[ai]) and isDigit(b[bi])) { + if (a[ai] == '0' or b[bi] == '0') { // compare_left + while (true) { + if (!isDigit(a[ai]) and !isDigit(b[bi])) break; + if (!isDigit(a[ai])) return .lt; + if (!isDigit(b[bi])) return .gt; + if (a[ai] < b[bi]) return .lt; + if (a[ai] > b[bi]) return .gt; + ai += 1; + bi += 1; + } + } else { // compare_right - for right-aligned numbers + var bias = std.math.Order.eq; + while (true) { + if (!isDigit(a[ai]) and !isDigit(b[bi])) { + if (bias != .eq or (a[ai] == 0 and b[bi] == 0)) return bias + else break; + } + if (!isDigit(a[ai])) return .lt; + if (!isDigit(b[bi])) return .gt; + if (bias == .eq) { + if (a[ai] < b[bi]) bias = .lt; + if (a[ai] > b[bi]) bias = .gt; + } + ai += 1; + bi += 1; + } + } + } + if (a[ai] == 0 and b[bi] == 0) return .eq; + if (a[ai] < b[bi]) return .lt; + if (a[ai] > b[bi]) return .gt; + ai += 1; + bi += 1; + } +} + +test "strnatcmp" { + // Test strings from https://github.com/sourcefrog/natsort/ + // Includes sorted-words, sorted-dates and sorted-fractions. + const w = [_][:0]const u8{ + "1-02", + "1-2", + "1-20", + "1.002.01", + "1.002.03", + "1.002.08", + "1.009.02", + "1.009.10", + "1.009.20", + "1.010.12", + "1.011.02", + "10-20", + "1999-3-3", + "1999-12-25", + "2000-1-2", + "2000-1-10", + "2000-3-23", + "fred", + "jane", + "pic01", + "pic02", + "pic02a", + "pic02000", + "pic05", + "pic2", + "pic3", + "pic4", + "pic 4 else", + "pic 5", + "pic 5 ", + "pic 5 something", + "pic 6", + "pic 7", + "pic100", + "pic100a", + "pic120", + "pic121", + "tom", + "x2-g8", + "x2-y08", + "x2-y7", + "x8-y8", + }; + // Test each string against each other string, simple and thorough. + const eq = std.testing.expectEqual; + var i: usize = 0; + while (i < w.len) : (i += 1) { + var j: usize = 0; + try eq(strnatcmp(w[i], w[i]), .eq); + while (j < i) : (j += 1) try eq(strnatcmp(w[i], w[j]), .gt); + j += 1; + while (j < w.len) : (j += 1) try eq(strnatcmp(w[i], w[j]), .lt); + } +} -- GitLab