package lib import ( "sort" "strings" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" "github.com/emersion/go-message/mail" ) func Sort(messageInfos []*models.MessageInfo, criteria []*types.SortCriterion, ) ([]uint32, error) { // loop through in reverse to ensure we sort by non-primary fields first for i := len(criteria) - 1; i >= 0; i-- { criterion := criteria[i] switch criterion.Field { case types.SortArrival: sortSlice(criterion, messageInfos, func(i, j int) bool { return messageInfos[i].InternalDate.Before(messageInfos[j].InternalDate) }) case types.SortCc: sortAddresses(messageInfos, criterion, func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.Cc }) case types.SortDate: sortSlice(criterion, messageInfos, func(i, j int) bool { return messageInfos[i].Envelope.Date.Before(messageInfos[j].Envelope.Date) }) case types.SortFrom: sortAddresses(messageInfos, criterion, func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.From }) case types.SortRead: sortFlags(messageInfos, criterion, models.SeenFlag) case types.SortSize: sortSlice(criterion, messageInfos, func(i, j int) bool { return messageInfos[i].Size < messageInfos[j].Size }) case types.SortSubject: sortStrings(messageInfos, criterion, func(msgInfo *models.MessageInfo) string { subject := strings.ToLower(msgInfo.Envelope.Subject) subject = strings.TrimPrefix(subject, "re: ") return strings.TrimPrefix(subject, "fwd: ") }) case types.SortTo: sortAddresses(messageInfos, criterion, func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.To }) } } var uids []uint32 // copy in reverse as msgList displays backwards for i := len(messageInfos) - 1; i >= 0; i-- { uids = append(uids, messageInfos[i].Uid) } return uids, nil } func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCriterion, getValue func(*models.MessageInfo) []*mail.Address, ) { sortSlice(criterion, messageInfos, func(i, j int) bool { addressI, addressJ := getValue(messageInfos[i]), getValue(messageInfos[j]) var firstI, firstJ *mail.Address if len(addressI) > 0 { firstI = addressI[0] } if len(addressJ) > 0 { firstJ = addressJ[0] } if firstI == nil && firstJ == nil { return false } else if firstI == nil && firstJ != nil { return false } else if firstI != nil && firstJ == nil { return true } else /* firstI != nil && firstJ != nil */ { getName := func(addr *mail.Address) string { if addr.Name != "" { return addr.Name } else { return addr.Address } } return getName(firstI) < getName(firstJ) } }) } func sortFlags(messageInfos []*models.MessageInfo, criterion *types.SortCriterion, testFlag models.Flag, ) { var slice []*boolStore for _, msgInfo := range messageInfos { flagPresent := false for _, flag := range msgInfo.Flags { if flag == testFlag { flagPresent = true } } slice = append(slice, &boolStore{ Value: flagPresent, MsgInfo: msgInfo, }) } sortSlice(criterion, slice, func(i, j int) bool { valI, valJ := slice[i].Value, slice[j].Value return valI && !valJ }) for i := 0; i < len(messageInfos); i++ { messageInfos[i] = slice[i].MsgInfo } } func sortStrings(messageInfos []*models.MessageInfo, criterion *types.SortCriterion, getValue func(*models.MessageInfo) string, ) { var slice []*lexiStore for _, msgInfo := range messageInfos { slice = append(slice, &lexiStore{ Value: getValue(msgInfo), MsgInfo: msgInfo, }) } sortSlice(criterion, slice, func(i, j int) bool { return slice[i].Value < slice[j].Value }) for i := 0; i < len(messageInfos); i++ { messageInfos[i] = slice[i].MsgInfo } } type lexiStore struct { Value string MsgInfo *models.MessageInfo } type boolStore struct { Value bool MsgInfo *models.MessageInfo } func sortSlice(criterion *types.SortCriterion, slice interface{}, less func(i, j int) bool) { if criterion.Reverse { sort.SliceStable(slice, func(i, j int) bool { return less(j, i) }) } else { sort.SliceStable(slice, less) } }