From 588be1a28422a6c431f11f820af73e99f11342c6 Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Fri, 12 Aug 2022 23:15:42 +0200 Subject: [PATCH] store: improve cursor position Improve cursor re-positioning while filtering with and without threads. Reposition cursor in client-side threading mode with a callback that is set during store.NextPrev(). Run callback when the threads are constructed in order to reposition the cursor correctly. The callback is deactivated when store.Select() is called. Steps to reproduce two issues: * Reproduce issue 1: 1. Activate client-side threading 2. Apply a filter, e.g. :filter -f Koni 3. Move cursor around so that a message is highlighted 4. clear filter with :clear 5. The cursor is expected to remain on the selected message but is actually not * Reproduce issue 2: 1. Activate client-side threading 2. Go the end of the message list 2. Apply a filter, e.g. :filter -f Koni 5. The cursor is now at the end of the filtered results instead of at the beginning This patch fixes both of those issues. Tested in regular and threaded view according to the following check list (expected behavior in parenthesis): 1. Apply filter from a message that remains in the filter (cursor on message, message selected) 2. Apply filter from a message that will not remain (cursor at the top, no message selected) 3. Clear filter (cursor remains on message, message selected) 4. Scroll line-by-line (threads: cursor remains on line, does not "jump" with message) 5. Search (cursor on first result) Signed-off-by: Koni Marti Tested-by: Tim Culverhouse --- lib/msgstore.go | 48 +++++++++++++++++++++++++--------------------- widgets/msglist.go | 6 ++++-- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lib/msgstore.go b/lib/msgstore.go index d47c14f..2832346 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -53,7 +53,9 @@ type MessageStore struct { threadBuilderDebounce *time.Timer threadBuilderDelay time.Duration + threadCallback func() + // threads mutex protects the store.threads and store.threadCallback threadsMutex sync.Mutex } @@ -343,6 +345,8 @@ func (store *MessageStore) SetThreadedView(thread bool) { if store.buildThreads { if store.threadedView { store.runThreadBuilder() + } else if store.threadBuilderDebounce != nil { + store.threadBuilderDebounce.Stop() } return } @@ -376,35 +380,19 @@ func (store *MessageStore) runThreadBuilder() { } } store.threadBuilderDebounce = time.AfterFunc(store.threadBuilderDelay, func() { - // temporarily deactiviate the selector in the message list by - // setting SelectedUid to the MagicUid - oldUid := store.SelectedUid() - store.Select(MagicUid) - - // Get the current index (we want to stay at that position in - // the updated uid list to provide a similar scrolling - // experience to the user as in the regular view - idx := store.FindIndexByUid(oldUid) - // build new threads th := store.builder.Threads(store.uids) - // try to select the same index in the updated uid list; if - // index is out of bound, stay at the selected message - rebuildUids := store.builder.Uids() - if idx >= 0 && idx < len(rebuildUids) { - store.Select(rebuildUids[idx]) - } else { - store.Select(oldUid) - } - - // save local threads to the message store variable + // save local threads to the message store variable and + // run callback if defined (callback should reposition cursor) store.threadsMutex.Lock() store.threads = th + if store.threadCallback != nil { + store.threadCallback() + } store.threadsMutex.Unlock() - // invalidate message list so that it is redrawn with the new - // threads and selected message + // invalidate message list if store.onUpdate != nil { store.onUpdate(store) } @@ -543,6 +531,11 @@ func (store *MessageStore) SelectedUid() uint32 { } func (store *MessageStore) Select(uid uint32) { + store.threadsMutex.Lock() + if store.threadCallback != nil { + store.threadCallback = nil + } + store.threadsMutex.Unlock() store.selectedUid = uid if store.marker != nil { store.marker.UpdateVisualMark() @@ -572,6 +565,16 @@ func (store *MessageStore) NextPrev(delta int) { store.Select(uids[newIdx]) + if store.BuildThreads() && store.ThreadedView() { + store.threadsMutex.Lock() + store.threadCallback = func() { + if uids := store.Uids(); len(uids) > newIdx { + store.selectedUid = uids[newIdx] + } + } + store.threadsMutex.Unlock() + } + if store.marker != nil { store.marker.UpdateVisualMark() } @@ -672,6 +675,7 @@ func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func(types.W store.Sorting = true handle_return := func(msg types.WorkerMessage) { + store.Select(store.SelectedUid()) store.Sorting = false if cb != nil { cb(msg) diff --git a/widgets/msglist.go b/widgets/msglist.go index 935566f..ba09a8e 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -70,9 +70,11 @@ func (ml *MessageList) Draw(ctx *ui.Context) { ml.UpdateScroller(ml.height, len(store.Uids())) if store := ml.Store(); store != nil && len(store.Uids()) > 0 { - if idx := store.FindIndexByUid(store.SelectedUid()); idx >= 0 { - ml.EnsureScroll(len(store.Uids()) - idx - 1) + idx := store.FindIndexByUid(store.SelectedUid()) + if idx < 0 { + idx = len(store.Uids()) - 1 } + ml.EnsureScroll(len(store.Uids()) - idx - 1) } textWidth := ctx.Width()