Skip to content

Commit 74ee204

Browse files
committed
fix sort
1 parent 4289f66 commit 74ee204

2 files changed

Lines changed: 68 additions & 28 deletions

File tree

internal/ui/model.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,22 +2361,12 @@ func (m Model) updateInbox(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
23612361
func (m *Model) sortEmails() tea.Cmd {
23622362
field, rev := m.sortField, m.sortReverse
23632363
sort.SliceStable(m.emails, func(i, j int) bool {
2364-
a, b := m.emails[i], m.emails[j]
2365-
var less bool
2366-
switch field {
2367-
case "from":
2368-
less = strings.ToLower(a.From) < strings.ToLower(b.From)
2369-
case "subject":
2370-
less = strings.ToLower(a.Subject) < strings.ToLower(b.Subject)
2371-
case "size":
2372-
less = a.Size < b.Size
2373-
default: // "date"
2374-
less = a.Date.Before(b.Date)
2375-
}
2364+
cmp := compareEmails(m.emails[i], m.emails[j], field)
2365+
// Apply sort direction
23762366
if rev {
2377-
return !less
2367+
return cmp > 0 // descending: a > b means a comes first
23782368
}
2379-
return less
2369+
return cmp < 0 // ascending: a < b means a comes first
23802370
})
23812371
return m.applyFilter()
23822372
}

internal/ui/thread.go

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,64 @@ import (
1111
// replyPrefixRe matches common reply/forward prefixes.
1212
var replyPrefixRe = regexp.MustCompile(`(?i)^(re|fwd?|fw|aw|sv|vs|ref|rif)\s*(\[\d+\])?\s*:\s*`)
1313

14+
// compareEmails returns -1 if a < b, 0 if a == b, 1 if a > b.
15+
// Comparison uses the specified sortField with deterministic tie-breakers:
16+
// 1. Primary sort field (from/subject/size/date)
17+
// 2. Date (newest first) if primary keys match and sortField != "date"
18+
// 3. UID for fully deterministic ordering
19+
func compareEmails(a, b imap.Email, sortField string) int {
20+
// Primary sort comparison
21+
var cmp int // -1 = a < b, 0 = equal, 1 = a > b
22+
switch sortField {
23+
case "from":
24+
aFrom, bFrom := strings.ToLower(a.From), strings.ToLower(b.From)
25+
if aFrom < bFrom {
26+
cmp = -1
27+
} else if aFrom > bFrom {
28+
cmp = 1
29+
}
30+
case "subject":
31+
aSubj, bSubj := strings.ToLower(a.Subject), strings.ToLower(b.Subject)
32+
if aSubj < bSubj {
33+
cmp = -1
34+
} else if aSubj > bSubj {
35+
cmp = 1
36+
}
37+
case "size":
38+
if a.Size < b.Size {
39+
cmp = -1
40+
} else if a.Size > b.Size {
41+
cmp = 1
42+
}
43+
default: // "date"
44+
if a.Date.Before(b.Date) {
45+
cmp = -1
46+
} else if a.Date.After(b.Date) {
47+
cmp = 1
48+
}
49+
}
50+
51+
// Tie-breaker 1: date (newest first) if primary keys are equal
52+
if cmp == 0 && sortField != "date" {
53+
if a.Date.After(b.Date) {
54+
cmp = -1
55+
} else if a.Date.Before(b.Date) {
56+
cmp = 1
57+
}
58+
}
59+
60+
// Tie-breaker 2: UID for deterministic ordering
61+
if cmp == 0 {
62+
if a.UID < b.UID {
63+
cmp = -1
64+
} else if a.UID > b.UID {
65+
cmp = 1
66+
}
67+
}
68+
69+
return cmp
70+
}
71+
1472
// hasReplyPrefix returns true if the subject starts with a reply/forward prefix.
1573
func hasReplyPrefix(subject string) bool {
1674
return replyPrefixRe.MatchString(strings.TrimSpace(subject))
@@ -144,24 +202,16 @@ func threadEmails(emails []imap.Email, sortField string, sortReverse bool) []thr
144202

145203
// Sort threads by user's chosen sort field and order.
146204
// We use the newest email in each thread as the representative for sorting.
147-
sort.Slice(threads, func(i, j int) bool {
205+
sort.SliceStable(threads, func(i, j int) bool {
148206
a := emails[threads[i].newestIdx]
149207
b := emails[threads[j].newestIdx]
150-
var less bool
151-
switch sortField {
152-
case "from":
153-
less = strings.ToLower(a.From) < strings.ToLower(b.From)
154-
case "subject":
155-
less = strings.ToLower(a.Subject) < strings.ToLower(b.Subject)
156-
case "size":
157-
less = a.Size < b.Size
158-
default: // "date"
159-
less = a.Date.Before(b.Date)
160-
}
208+
cmp := compareEmails(a, b, sortField)
209+
210+
// Apply sort direction
161211
if sortReverse {
162-
return !less
212+
return cmp > 0 // descending: a > b means a comes first
163213
}
164-
return less
214+
return cmp < 0 // ascending: a < b means a comes first
165215
})
166216

167217
// Build output with thread connector lines.

0 commit comments

Comments
 (0)