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 <koni.marti@gmail.com> Tested-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
3b90b3b0dd
commit
8f7695fde5
5 changed files with 73 additions and 86 deletions
|
@ -51,8 +51,6 @@ func (Clear) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
||||||
if clearSelected {
|
if clearSelected {
|
||||||
defer store.Select(0)
|
defer store.Select(0)
|
||||||
} else {
|
|
||||||
store.SetReselect(store.Selected())
|
|
||||||
}
|
}
|
||||||
store.ApplyClear()
|
store.ApplyClear()
|
||||||
acct.SetStatus(statusline.SearchFilterClear())
|
acct.SetStatus(statusline.SearchFilterClear())
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
// no more messages in the list
|
// no more messages in the list
|
||||||
if next == nil {
|
if next == nil {
|
||||||
aerc.RemoveTab(h.msgProvider)
|
aerc.RemoveTab(h.msgProvider)
|
||||||
store.Select(len(store.Uids()))
|
acct.Messages().Select(0)
|
||||||
acct.Messages().Invalidate()
|
acct.Messages().Invalidate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
if next == nil {
|
if next == nil {
|
||||||
// We deleted the last message, select the new last message
|
// We deleted the last message, select the new last message
|
||||||
// instead of the first message
|
// instead of the first message
|
||||||
store.Select(len(store.Uids()))
|
acct.Messages().Select(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
acct.Messages().Invalidate()
|
acct.Messages().Invalidate()
|
||||||
|
|
|
@ -34,7 +34,6 @@ func (ToggleThreads) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
store.SetReselect(store.Selected())
|
|
||||||
store.SetThreadedView(!store.ThreadedView())
|
store.SetThreadedView(!store.ThreadedView())
|
||||||
acct.SetStatus(statusline.Threading(store.ThreadedView()))
|
acct.SetStatus(statusline.Threading(store.ThreadedView()))
|
||||||
acct.Messages().Invalidate()
|
acct.Messages().Invalidate()
|
||||||
|
|
127
lib/msgstore.go
127
lib/msgstore.go
|
@ -20,7 +20,7 @@ type MessageStore struct {
|
||||||
uids []uint32
|
uids []uint32
|
||||||
Threads []*types.Thread
|
Threads []*types.Thread
|
||||||
|
|
||||||
selected int
|
selectedUid uint32
|
||||||
reselect *models.MessageInfo
|
reselect *models.MessageInfo
|
||||||
bodyCallbacks map[uint32][]func(*types.FullMessage)
|
bodyCallbacks map[uint32][]func(*types.FullMessage)
|
||||||
headerCallbacks map[uint32][]func(*types.MessageInfo)
|
headerCallbacks map[uint32][]func(*types.MessageInfo)
|
||||||
|
@ -57,6 +57,8 @@ type MessageStore struct {
|
||||||
dirInfoUpdateDelay time.Duration
|
dirInfoUpdateDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MagicUid = 0xFFFFFFFF
|
||||||
|
|
||||||
func NewMessageStore(worker *types.Worker,
|
func NewMessageStore(worker *types.Worker,
|
||||||
dirInfo *models.DirectoryInfo,
|
dirInfo *models.DirectoryInfo,
|
||||||
defaultSortCriteria []*types.SortCriterion,
|
defaultSortCriteria []*types.SortCriterion,
|
||||||
|
@ -75,7 +77,7 @@ func NewMessageStore(worker *types.Worker,
|
||||||
DirInfo: *dirInfo,
|
DirInfo: *dirInfo,
|
||||||
Messages: make(map[uint32]*models.MessageInfo),
|
Messages: make(map[uint32]*models.MessageInfo),
|
||||||
|
|
||||||
selected: 0,
|
selectedUid: MagicUid,
|
||||||
marked: make(map[uint32]struct{}),
|
marked: make(map[uint32]struct{}),
|
||||||
bodyCallbacks: make(map[uint32][]func(*types.FullMessage)),
|
bodyCallbacks: make(map[uint32][]func(*types.FullMessage)),
|
||||||
headerCallbacks: make(map[uint32][]func(*types.MessageInfo)),
|
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) {
|
func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
update := false
|
update := false
|
||||||
directoryChange := false
|
directoryChange := false
|
||||||
if store.reselect == nil {
|
|
||||||
store.SetReselect(store.Selected())
|
|
||||||
}
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *types.DirectoryInfo:
|
case *types.DirectoryInfo:
|
||||||
store.DirInfo = *msg.Info
|
store.DirInfo = *msg.Info
|
||||||
|
@ -367,7 +366,6 @@ func (store *MessageStore) SetThreadedView(thread bool) {
|
||||||
if store.threadedView {
|
if store.threadedView {
|
||||||
store.runThreadBuilder()
|
store.runThreadBuilder()
|
||||||
}
|
}
|
||||||
store.Reselect()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.Sort(store.sortCriteria, nil)
|
store.Sort(store.sortCriteria, nil)
|
||||||
|
@ -490,56 +488,22 @@ func (store *MessageStore) Uids() []uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) Selected() *models.MessageInfo {
|
func (store *MessageStore) Selected() *models.MessageInfo {
|
||||||
uids := store.Uids()
|
return store.Messages[store.selectedUid]
|
||||||
idx := len(uids) - store.selected - 1
|
|
||||||
if len(uids) == 0 || idx < 0 || idx >= len(uids) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return store.Messages[uids[idx]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) SelectedIndex() int {
|
func (store *MessageStore) SelectedUid() uint32 {
|
||||||
return store.selected
|
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) {
|
func (store *MessageStore) Select(uid uint32) {
|
||||||
l := len(store.Uids())
|
store.selectedUid = uid
|
||||||
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
|
|
||||||
}
|
|
||||||
store.updateVisual()
|
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
|
// Mark sets the marked state on a MessageInfo
|
||||||
func (store *MessageStore) Mark(uid uint32) {
|
func (store *MessageStore) Mark(uid uint32) {
|
||||||
if store.visualMarkMode {
|
if store.visualMarkMode {
|
||||||
|
@ -648,15 +612,20 @@ func (store *MessageStore) updateVisual() {
|
||||||
store.ClearVisualMark()
|
store.ClearVisualMark()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uidLen := len(store.Uids())
|
|
||||||
// store.selected is the inverted form of the actual array
|
selectedIdx := store.FindIndexByUid(store.SelectedUid())
|
||||||
selectedIdx := uidLen - store.selected - 1
|
if selectedIdx < 0 {
|
||||||
|
store.ClearVisualMark()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var visUids []uint32
|
var visUids []uint32
|
||||||
if selectedIdx > startIdx {
|
if selectedIdx > startIdx {
|
||||||
visUids = store.Uids()[startIdx : selectedIdx+1]
|
visUids = store.Uids()[startIdx : selectedIdx+1]
|
||||||
} else {
|
} else {
|
||||||
visUids = store.Uids()[selectedIdx : startIdx+1]
|
visUids = store.Uids()[selectedIdx : startIdx+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
store.resetMark()
|
store.resetMark()
|
||||||
for _, uid := range visUids {
|
for _, uid := range visUids {
|
||||||
store.marked[uid] = struct{}{}
|
store.marked[uid] = struct{}{}
|
||||||
|
@ -675,20 +644,31 @@ func (store *MessageStore) NextPrev(delta int) {
|
||||||
if len(uids) == 0 {
|
if len(uids) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
idx := store.SelectedIndex() + delta
|
|
||||||
if idx < 0 {
|
uid := store.SelectedUid()
|
||||||
store.Select(0)
|
|
||||||
} else {
|
newIdx := store.FindIndexByUid(uid)
|
||||||
store.Select(idx)
|
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()
|
store.updateVisual()
|
||||||
|
|
||||||
nextResultIndex := len(store.results) - store.resultIndex - 2*delta
|
nextResultIndex := len(store.results) - store.resultIndex - 2*delta
|
||||||
if nextResultIndex < 0 || nextResultIndex >= len(store.results) {
|
if nextResultIndex < 0 || nextResultIndex >= len(store.results) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nextResultUid := store.results[nextResultIndex]
|
nextResultUid := store.results[nextResultIndex]
|
||||||
selectedUid := uids[len(uids)-store.selected-1]
|
if nextResultUid == store.SelectedUid() {
|
||||||
if nextResultUid == selectedUid {
|
|
||||||
store.resultIndex += delta
|
store.resultIndex += delta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -734,21 +714,12 @@ func (store *MessageStore) SetFilter(args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) ApplyClear() {
|
func (store *MessageStore) ApplyClear() {
|
||||||
if store.reselect == nil {
|
|
||||||
store.SetReselect(store.Selected())
|
|
||||||
}
|
|
||||||
store.filter = []string{"filter"}
|
store.filter = []string{"filter"}
|
||||||
store.results = nil
|
store.results = nil
|
||||||
if store.onFilterChange != nil {
|
if store.onFilterChange != nil {
|
||||||
store.onFilterChange(store)
|
store.onFilterChange(store)
|
||||||
}
|
}
|
||||||
cb := func(msg types.WorkerMessage) {
|
store.Sort(nil, nil)
|
||||||
switch msg.(type) {
|
|
||||||
case *types.Done:
|
|
||||||
store.Reselect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.Sort(nil, cb)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) nextPrevResult(delta int) {
|
func (store *MessageStore) nextPrevResult(delta int) {
|
||||||
|
@ -762,13 +733,7 @@ func (store *MessageStore) nextPrevResult(delta int) {
|
||||||
if store.resultIndex < 0 {
|
if store.resultIndex < 0 {
|
||||||
store.resultIndex = len(store.results) - 1
|
store.resultIndex = len(store.results) - 1
|
||||||
}
|
}
|
||||||
uids := store.Uids()
|
store.Select(store.results[len(store.results)-store.resultIndex-1])
|
||||||
for i, uid := range uids {
|
|
||||||
if store.results[len(store.results)-store.resultIndex-1] == uid {
|
|
||||||
store.Select(len(uids) - i - 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.update()
|
store.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,3 +791,13 @@ func (store *MessageStore) visualStartIdx() int {
|
||||||
}
|
}
|
||||||
return -1
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -69,7 +69,9 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
|
|
||||||
ml.UpdateScroller(ml.height, len(store.Uids()))
|
ml.UpdateScroller(ml.height, len(store.Uids()))
|
||||||
if store := ml.Store(); store != nil && len(store.Uids()) > 0 {
|
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()
|
textWidth := ctx.Width()
|
||||||
|
@ -244,7 +246,7 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
|
||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
// current row
|
// current row
|
||||||
if row == ml.store.SelectedIndex()-ml.Scroll() {
|
if msg.Uid == ml.store.SelectedUid() {
|
||||||
style = uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, msg_styles)
|
style = uiConfig.GetComposedStyleSelected(config.STYLE_MSGLIST_DEFAULT, msg_styles)
|
||||||
} else {
|
} else {
|
||||||
style = uiConfig.GetComposedStyle(config.STYLE_MSGLIST_DEFAULT, msg_styles)
|
style = uiConfig.GetComposedStyle(config.STYLE_MSGLIST_DEFAULT, msg_styles)
|
||||||
|
@ -342,7 +344,6 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
|
||||||
if ml.Store() != store {
|
if ml.Store() != store {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.Reselect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MessageList) SetStore(store *lib.MessageStore) {
|
func (ml *MessageList) SetStore(store *lib.MessageStore) {
|
||||||
|
@ -352,7 +353,8 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) {
|
||||||
ml.store = store
|
ml.store = store
|
||||||
if store != nil {
|
if store != nil {
|
||||||
ml.spinner.Stop()
|
ml.spinner.Stop()
|
||||||
ml.nmsgs = len(store.Uids())
|
uids := store.Uids()
|
||||||
|
ml.nmsgs = len(uids)
|
||||||
store.OnUpdate(ml.storeUpdate)
|
store.OnUpdate(ml.storeUpdate)
|
||||||
store.OnFilterChange(func(store *lib.MessageStore) {
|
store.OnFilterChange(func(store *lib.MessageStore) {
|
||||||
if ml.Store() != store {
|
if ml.Store() != store {
|
||||||
|
@ -384,8 +386,21 @@ func (ml *MessageList) Selected() *models.MessageInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MessageList) Select(index int) {
|
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 := 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()
|
ml.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue