1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
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 "<nil>"
}
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 getMaxValue(thread *Thread, uidMap map[models.UID]int) int {
var max int
_ = thread.Walk(func(t *Thread, _ int, currentErr error) error {
if t.Deleted || t.Hidden > 0 {
return nil
}
if uidMap[t.Uid] > max {
max = uidMap[t.Uid]
}
return nil
})
return max
}
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 getMaxValue(toSort[i], uidMap) < getMaxValue(toSort[j], uidMap)
})
}
|