From 8f7695fde5cd84b7f6b8f3193270eda2fd62448c Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Tue, 26 Jul 2022 11:30:26 +0200 Subject: [PATCH] msgstore: implement a uid-based architecture Change the message store architecture from an index-based to a uid-based one. Key advantage of this design approach is that no reselect mechanism is required anymore since it comes with the design for free. Fixes: https://todo.sr.ht/~rjarry/aerc/43 Signed-off-by: Koni Marti Tested-by: Tim Culverhouse Acked-by: Robin Jarry --- commands/account/clear.go | 2 - commands/msg/delete.go | 4 +- commands/msg/toggle-threads.go | 1 - lib/msgstore.go | 127 +++++++++++++-------------------- widgets/msglist.go | 25 +++++-- 5 files changed, 73 insertions(+), 86 deletions(-) diff --git a/commands/account/clear.go b/commands/account/clear.go index 5bab710..af7da32 100644 --- a/commands/account/clear.go +++ b/commands/account/clear.go @@ -51,8 +51,6 @@ func (Clear) Execute(aerc *widgets.Aerc, args []string) error { if clearSelected { defer store.Select(0) - } else { - store.SetReselect(store.Selected()) } store.ApplyClear() acct.SetStatus(statusline.SearchFilterClear()) diff --git a/commands/msg/delete.go b/commands/msg/delete.go index 2d7ad0f..d144388 100644 --- a/commands/msg/delete.go +++ b/commands/msg/delete.go @@ -56,7 +56,7 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error { // no more messages in the list if next == nil { aerc.RemoveTab(h.msgProvider) - store.Select(len(store.Uids())) + acct.Messages().Select(0) acct.Messages().Invalidate() return } @@ -74,7 +74,7 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error { if next == nil { // We deleted the last message, select the new last message // instead of the first message - store.Select(len(store.Uids())) + acct.Messages().Select(0) } } acct.Messages().Invalidate() diff --git a/commands/msg/toggle-threads.go b/commands/msg/toggle-threads.go index 05c2c5e..af694bc 100644 --- a/commands/msg/toggle-threads.go +++ b/commands/msg/toggle-threads.go @@ -34,7 +34,6 @@ func (ToggleThreads) Execute(aerc *widgets.Aerc, args []string) error { if err != nil { return err } - store.SetReselect(store.Selected()) store.SetThreadedView(!store.ThreadedView()) acct.SetStatus(statusline.Threading(store.ThreadedView())) acct.Messages().Invalidate() diff --git a/lib/msgstore.go b/lib/msgstore.go index 34c2676..61a6dd4 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -20,7 +20,7 @@ type MessageStore struct { uids []uint32 Threads []*types.Thread - selected int + selectedUid uint32 reselect *models.MessageInfo bodyCallbacks map[uint32][]func(*types.FullMessage) headerCallbacks map[uint32][]func(*types.MessageInfo) @@ -57,6 +57,8 @@ type MessageStore struct { dirInfoUpdateDelay time.Duration } +const MagicUid = 0xFFFFFFFF + func NewMessageStore(worker *types.Worker, dirInfo *models.DirectoryInfo, defaultSortCriteria []*types.SortCriterion, @@ -75,7 +77,7 @@ func NewMessageStore(worker *types.Worker, DirInfo: *dirInfo, Messages: make(map[uint32]*models.MessageInfo), - selected: 0, + selectedUid: MagicUid, marked: make(map[uint32]struct{}), bodyCallbacks: make(map[uint32][]func(*types.FullMessage)), headerCallbacks: make(map[uint32][]func(*types.MessageInfo)), @@ -197,9 +199,6 @@ func merge(to *models.MessageInfo, from *models.MessageInfo) { func (store *MessageStore) Update(msg types.WorkerMessage) { update := false directoryChange := false - if store.reselect == nil { - store.SetReselect(store.Selected()) - } switch msg := msg.(type) { case *types.DirectoryInfo: store.DirInfo = *msg.Info @@ -367,7 +366,6 @@ func (store *MessageStore) SetThreadedView(thread bool) { if store.threadedView { store.runThreadBuilder() } - store.Reselect() return } store.Sort(store.sortCriteria, nil) @@ -490,56 +488,22 @@ func (store *MessageStore) Uids() []uint32 { } func (store *MessageStore) Selected() *models.MessageInfo { - uids := store.Uids() - idx := len(uids) - store.selected - 1 - if len(uids) == 0 || idx < 0 || idx >= len(uids) { - return nil - } - return store.Messages[uids[idx]] + return store.Messages[store.selectedUid] } -func (store *MessageStore) SelectedIndex() int { - return store.selected +func (store *MessageStore) SelectedUid() uint32 { + if store.selectedUid == MagicUid && len(store.Uids()) > 0 { + uids := store.Uids() + store.selectedUid = uids[len(uids)-1] + } + return store.selectedUid } -func (store *MessageStore) Select(index int) { - l := len(store.Uids()) - switch { - case l+index < 0: - // negative index overruns length of list - store.selected = 0 - case index < 0: - // negative index, select from bottom - store.selected = l + index - case index >= l: - // index greater than length, select last - store.selected = l - 1 - default: - store.selected = index - } +func (store *MessageStore) Select(uid uint32) { + store.selectedUid = uid store.updateVisual() } -func (store *MessageStore) Reselect() { - if store.reselect == nil { - return - } - uid := store.reselect.Uid - newIdx := 0 - for idx, uidStore := range store.Uids() { - if uidStore == uid { - newIdx = len(store.Uids()) - idx - 1 - break - } - } - store.reselect = nil - store.Select(newIdx) -} - -func (store *MessageStore) SetReselect(info *models.MessageInfo) { - store.reselect = info -} - // Mark sets the marked state on a MessageInfo func (store *MessageStore) Mark(uid uint32) { if store.visualMarkMode { @@ -648,15 +612,20 @@ func (store *MessageStore) updateVisual() { store.ClearVisualMark() return } - uidLen := len(store.Uids()) - // store.selected is the inverted form of the actual array - selectedIdx := uidLen - store.selected - 1 + + selectedIdx := store.FindIndexByUid(store.SelectedUid()) + if selectedIdx < 0 { + store.ClearVisualMark() + return + } + var visUids []uint32 if selectedIdx > startIdx { visUids = store.Uids()[startIdx : selectedIdx+1] } else { visUids = store.Uids()[selectedIdx : startIdx+1] } + store.resetMark() for _, uid := range visUids { store.marked[uid] = struct{}{} @@ -675,20 +644,31 @@ func (store *MessageStore) NextPrev(delta int) { if len(uids) == 0 { return } - idx := store.SelectedIndex() + delta - if idx < 0 { - store.Select(0) - } else { - store.Select(idx) + + uid := store.SelectedUid() + + newIdx := store.FindIndexByUid(uid) + if newIdx < 0 { + store.Select(uids[len(uids)-1]) } + + newIdx -= delta + if newIdx >= len(uids) { + newIdx = len(uids) - 1 + } else if newIdx < 0 { + newIdx = 0 + } + + store.Select(uids[newIdx]) + store.updateVisual() + nextResultIndex := len(store.results) - store.resultIndex - 2*delta if nextResultIndex < 0 || nextResultIndex >= len(store.results) { return } nextResultUid := store.results[nextResultIndex] - selectedUid := uids[len(uids)-store.selected-1] - if nextResultUid == selectedUid { + if nextResultUid == store.SelectedUid() { store.resultIndex += delta } } @@ -734,21 +714,12 @@ func (store *MessageStore) SetFilter(args []string) { } func (store *MessageStore) ApplyClear() { - if store.reselect == nil { - store.SetReselect(store.Selected()) - } store.filter = []string{"filter"} store.results = nil if store.onFilterChange != nil { store.onFilterChange(store) } - cb := func(msg types.WorkerMessage) { - switch msg.(type) { - case *types.Done: - store.Reselect() - } - } - store.Sort(nil, cb) + store.Sort(nil, nil) } func (store *MessageStore) nextPrevResult(delta int) { @@ -762,13 +733,7 @@ func (store *MessageStore) nextPrevResult(delta int) { if store.resultIndex < 0 { store.resultIndex = len(store.results) - 1 } - uids := store.Uids() - for i, uid := range uids { - if store.results[len(store.results)-store.resultIndex-1] == uid { - store.Select(len(uids) - i - 1) - break - } - } + store.Select(store.results[len(store.results)-store.resultIndex-1]) store.update() } @@ -826,3 +791,13 @@ func (store *MessageStore) visualStartIdx() int { } return -1 } + +// FindIndexByUid returns the index in store.Uids() or -1 if not found +func (store *MessageStore) FindIndexByUid(uid uint32) int { + for idx, u := range store.Uids() { + if u == uid { + return idx + } + } + return -1 +} diff --git a/widgets/msglist.go b/widgets/msglist.go index a190c18..4c7c1c2 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -69,7 +69,9 @@ func (ml *MessageList) Draw(ctx *ui.Context) { ml.UpdateScroller(ml.height, len(store.Uids())) if store := ml.Store(); store != nil && len(store.Uids()) > 0 { - ml.EnsureScroll(store.SelectedIndex()) + if idx := store.FindIndexByUid(store.SelectedUid()); idx >= 0 { + ml.EnsureScroll(len(store.Uids()) - idx - 1) + } } textWidth := ctx.Width() @@ -244,7 +246,7 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i var style tcell.Style // current row - if row == ml.store.SelectedIndex()-ml.Scroll() { + if msg.Uid == ml.store.SelectedUid() { style = uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, msg_styles) } else { style = uiConfig.GetComposedStyle(config.STYLE_MSGLIST_DEFAULT, msg_styles) @@ -342,7 +344,6 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) { if ml.Store() != store { return } - store.Reselect() } func (ml *MessageList) SetStore(store *lib.MessageStore) { @@ -352,7 +353,8 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) { ml.store = store if store != nil { ml.spinner.Stop() - ml.nmsgs = len(store.Uids()) + uids := store.Uids() + ml.nmsgs = len(uids) store.OnUpdate(ml.storeUpdate) store.OnFilterChange(func(store *lib.MessageStore) { if ml.Store() != store { @@ -384,8 +386,21 @@ func (ml *MessageList) Selected() *models.MessageInfo { } func (ml *MessageList) Select(index int) { + // Note that the msgstore.Select function expects a uid as argument + // whereas the msglist.Select expects the message number store := ml.Store() - store.Select(index) + uids := store.Uids() + if len(uids) == 0 { + return + } + uidIdx := len(uids) - index - 1 + if uidIdx >= len(store.Uids()) { + uidIdx = 0 + } else if uidIdx < 0 { + uidIdx = len(store.Uids()) - 1 + } + store.Select(store.Uids()[uidIdx]) + ml.Invalidate() }