package types import ( "errors" "fmt" "sort" "git.sr.ht/~rjarry/aerc/lib/log" "git.sr.ht/~rjarry/aerc/models" ) type Thread struct { Uid models.UID Parent *Thread PrevSibling *Thread NextSibling *Thread FirstChild *Thread Hidden int // if this flag is not zero the message isn't rendered in the UI Deleted bool // if this flag is set the message was deleted // if this flag is set the message is the root of an incomplete thread Dummy bool // Context indicates the message doesn't match the mailbox / query but // is displayed for context Context bool } // AddChild appends the child node at the end of the existing children of t. func (t *Thread) AddChild(child *Thread) { t.InsertCmp(child, func(_, _ *Thread) bool { return true }) } // OrderedInsert inserts the child node in ascending order among the existing // children based on their respective UIDs. func (t *Thread) OrderedInsert(child *Thread) { t.InsertCmp(child, func(child, iter *Thread) bool { return child.Uid > iter.Uid }) } // InsertCmp inserts child as a child node into t in ascending order. The // ascending order is determined by the bigger function that compares the child // with the existing children. It should return true when the child is bigger // than the other, and false otherwise. func (t *Thread) InsertCmp(child *Thread, bigger func(*Thread, *Thread) bool) { if t.FirstChild == nil { t.FirstChild = child } else { start := &Thread{NextSibling: t.FirstChild} var iter *Thread for iter = start; iter.NextSibling != nil && bigger(child, iter.NextSibling); iter = iter.NextSibling { } child.NextSibling = iter.NextSibling iter.NextSibling = child t.FirstChild = start.NextSibling } child.Parent = t } func (t *Thread) Walk(walkFn NewThreadWalkFn) error { err := newWalk(t, walkFn, 0, nil) if errors.Is(err, ErrSkipThread) { return nil } return err } // Root returns the root thread of the thread tree func (t *Thread) Root() *Thread { if t == nil { return nil } var iter *Thread for iter = t; iter.Parent != nil; iter = iter.Parent { } return iter } // Uids returns all associated uids for the given thread and its children func (t *Thread) Uids() []models.UID { if t == nil { return nil } uids := make([]models.UID, 0) err := t.Walk(func(node *Thread, _ int, _ error) error { uids = append(uids, node.Uid) return nil }) if err != nil { log.Errorf("walk to collect uids failed: %v", err) } return uids } func (t *Thread) String() string { if t == nil { return "" } var parent models.UID if t.Parent != nil { parent = t.Parent.Uid } var next models.UID if t.NextSibling != nil { next = t.NextSibling.Uid } var child models.UID if t.FirstChild != nil { child = t.FirstChild.Uid } return fmt.Sprintf( "[%s] (parent:%s, next:%s, child:%s)", t.Uid, parent, next, child, ) } func newWalk(node *Thread, walkFn NewThreadWalkFn, lvl int, ce error) error { if node == nil { return nil } err := walkFn(node, lvl, ce) if err != nil { return err } for child := node.FirstChild; child != nil; child = child.NextSibling { err = newWalk(child, walkFn, lvl+1, err) if errors.Is(err, ErrSkipThread) { err = nil continue } else if err != nil { return err } } return nil } var ErrSkipThread = errors.New("skip this Thread") type NewThreadWalkFn func(t *Thread, level int, currentErr error) error // Implement interface to be able to sort threads by newest (max UID) type ByUID []*Thread func getMaxUID(thread *Thread) models.UID { // TODO: should we make this part of the Thread type to avoid recomputation? var Uid models.UID _ = thread.Walk(func(t *Thread, _ int, currentErr error) error { if t.Deleted || t.Hidden > 0 { return nil } if t.Uid > Uid { Uid = t.Uid } return nil }) return Uid } func (s ByUID) Len() int { return len(s) } func (s ByUID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByUID) Less(i, j int) bool { maxUID_i := getMaxUID(s[i]) maxUID_j := getMaxUID(s[j]) return maxUID_i < maxUID_j } func SortThreadsBy(toSort []*Thread, sortBy []models.UID) { // build a map from sortBy uidMap := make(map[models.UID]int) for i, uid := range sortBy { uidMap[uid] = i } // sortslice of toSort with less function of indexing the map sortBy sort.Slice(toSort, func(i, j int) bool { return uidMap[getMaxUID(toSort[i])] < uidMap[getMaxUID(toSort[j])] }) }