store: extract marking behavior and add tests
Separate the marking functions from the message store and extract the marking behavior into its own class with tests. Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
ee961d3b1d
commit
cfc19a7ec2
14 changed files with 373 additions and 177 deletions
|
@ -57,7 +57,8 @@ func (Archive) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
uids = append(uids, msg.Uid)
|
uids = append(uids, msg.Uid)
|
||||||
}
|
}
|
||||||
store.ClearVisualMark()
|
marker := store.Marker()
|
||||||
|
marker.ClearVisualMark()
|
||||||
findNextNonDeleted(uids, store)
|
findNextNonDeleted(uids, store)
|
||||||
|
|
||||||
var uidMap map[string][]uint32
|
var uidMap map[string][]uint32
|
||||||
|
@ -95,7 +96,7 @@ func (Archive) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
aerc.PushError(msg.Error.Error())
|
aerc.PushError(msg.Error.Error())
|
||||||
success = false
|
success = false
|
||||||
wg.Done()
|
wg.Done()
|
||||||
store.Remark()
|
marker.Remark()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (Copy) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *types.Done:
|
case *types.Done:
|
||||||
aerc.PushStatus("Messages copied.", 10*time.Second)
|
aerc.PushStatus("Messages copied.", 10*time.Second)
|
||||||
store.ClearVisualMark()
|
store.Marker().ClearVisualMark()
|
||||||
case *types.Error:
|
case *types.Error:
|
||||||
aerc.PushError(msg.Error.Error())
|
aerc.PushError(msg.Error.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,10 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sel := store.Selected()
|
sel := store.Selected()
|
||||||
|
marker := store.Marker()
|
||||||
|
marker.ClearVisualMark()
|
||||||
// caution, can be nil
|
// caution, can be nil
|
||||||
next := findNextNonDeleted(uids, store)
|
next := findNextNonDeleted(uids, store)
|
||||||
store.ClearVisualMark()
|
|
||||||
store.Delete(uids, func(msg types.WorkerMessage) {
|
store.Delete(uids, func(msg types.WorkerMessage) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *types.Done:
|
case *types.Done:
|
||||||
|
@ -80,11 +81,11 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *types.Error:
|
case *types.Error:
|
||||||
store.Remark()
|
marker.Remark()
|
||||||
store.Select(sel.Uid)
|
store.Select(sel.Uid)
|
||||||
aerc.PushError(msg.Error.Error())
|
aerc.PushError(msg.Error.Error())
|
||||||
case *types.Unsupported:
|
case *types.Unsupported:
|
||||||
store.Remark()
|
marker.Remark()
|
||||||
store.Select(sel.Uid)
|
store.Select(sel.Uid)
|
||||||
// notmuch doesn't support it, we want the user to know
|
// notmuch doesn't support it, we want the user to know
|
||||||
aerc.PushError(" error, unsupported for this worker")
|
aerc.PushError(" error, unsupported for this worker")
|
||||||
|
|
|
@ -31,6 +31,7 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
marker := store.Marker()
|
||||||
opts, _, err := getopt.Getopts(args, "atv")
|
opts, _, err := getopt.Getopts(args, "atv")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -57,9 +58,9 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
||||||
var modFunc func(uint32)
|
var modFunc func(uint32)
|
||||||
if toggle {
|
if toggle {
|
||||||
modFunc = store.ToggleMark
|
modFunc = marker.ToggleMark
|
||||||
} else {
|
} else {
|
||||||
modFunc = store.Mark
|
modFunc = marker.Mark
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case all:
|
case all:
|
||||||
|
@ -69,7 +70,7 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case visual:
|
case visual:
|
||||||
store.ToggleVisualMark()
|
marker.ToggleVisualMark()
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
modFunc(selected.Uid)
|
modFunc(selected.Uid)
|
||||||
|
@ -85,21 +86,21 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
case all && toggle:
|
case all && toggle:
|
||||||
uids := store.Uids()
|
uids := store.Uids()
|
||||||
for _, uid := range uids {
|
for _, uid := range uids {
|
||||||
store.ToggleMark(uid)
|
marker.ToggleMark(uid)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case all && !toggle:
|
case all && !toggle:
|
||||||
store.ClearVisualMark()
|
marker.ClearVisualMark()
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
store.Unmark(selected.Uid)
|
marker.Unmark(selected.Uid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case "remark":
|
case "remark":
|
||||||
if all || visual || toggle {
|
if all || visual || toggle {
|
||||||
return fmt.Errorf("Usage: :remark")
|
return fmt.Errorf("Usage: :remark")
|
||||||
}
|
}
|
||||||
store.Remark()
|
marker.Remark()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil // never reached
|
return nil // never reached
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (ModifyLabels) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *types.Done:
|
case *types.Done:
|
||||||
aerc.PushStatus("labels updated", 10*time.Second)
|
aerc.PushStatus("labels updated", 10*time.Second)
|
||||||
store.ClearVisualMark()
|
store.Marker().ClearVisualMark()
|
||||||
case *types.Error:
|
case *types.Error:
|
||||||
aerc.PushError(msg.Error.Error())
|
aerc.PushError(msg.Error.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,8 @@ func (Move) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
if isMsgView {
|
if isMsgView {
|
||||||
aerc.RemoveTab(h.msgProvider)
|
aerc.RemoveTab(h.msgProvider)
|
||||||
}
|
}
|
||||||
store.ClearVisualMark()
|
marker := store.Marker()
|
||||||
|
marker.ClearVisualMark()
|
||||||
findNextNonDeleted(uids, store)
|
findNextNonDeleted(uids, store)
|
||||||
joinedArgs := strings.Join(args[optind:], " ")
|
joinedArgs := strings.Join(args[optind:], " ")
|
||||||
store.Move(uids, joinedArgs, createParents, func(
|
store.Move(uids, joinedArgs, createParents, func(
|
||||||
|
@ -64,7 +65,7 @@ func (Move) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
case *types.Done:
|
case *types.Done:
|
||||||
aerc.PushStatus("Message moved to "+joinedArgs, 10*time.Second)
|
aerc.PushStatus("Message moved to "+joinedArgs, 10*time.Second)
|
||||||
case *types.Error:
|
case *types.Error:
|
||||||
store.Remark()
|
marker.Remark()
|
||||||
aerc.PushError(msg.Error.Error())
|
aerc.PushError(msg.Error.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -218,7 +218,7 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
provider.Store().ClearVisualMark()
|
provider.Store().Marker().ClearVisualMark()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ func (FlagMsg) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if success {
|
if success {
|
||||||
aerc.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second)
|
aerc.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second)
|
||||||
store.ClearVisualMark()
|
store.Marker().ClearVisualMark()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
179
lib/marker/marker.go
Normal file
179
lib/marker/marker.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package marker
|
||||||
|
|
||||||
|
// TODO: fix headers for message that are nil
|
||||||
|
|
||||||
|
// Marker provides the interface for the marking behavior of messages
|
||||||
|
type Marker interface {
|
||||||
|
Mark(uint32)
|
||||||
|
Unmark(uint32)
|
||||||
|
ToggleMark(uint32)
|
||||||
|
Remark()
|
||||||
|
Marked() []uint32
|
||||||
|
IsMarked(uint32) bool
|
||||||
|
ToggleVisualMark()
|
||||||
|
UpdateVisualMark()
|
||||||
|
ClearVisualMark()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDProvider provides the underlying uids and the selected message index
|
||||||
|
type UIDProvider interface {
|
||||||
|
Uids() []uint32
|
||||||
|
SelectedIndex() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
uidProvider UIDProvider
|
||||||
|
marked map[uint32]struct{}
|
||||||
|
lastMarked map[uint32]struct{}
|
||||||
|
visualStartUID uint32
|
||||||
|
visualMarkMode bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Marker
|
||||||
|
func New(up UIDProvider) Marker {
|
||||||
|
return &controller{
|
||||||
|
uidProvider: up,
|
||||||
|
marked: make(map[uint32]struct{}),
|
||||||
|
lastMarked: make(map[uint32]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark markes the uid as marked
|
||||||
|
func (mc *controller) Mark(uid uint32) {
|
||||||
|
if mc.visualMarkMode {
|
||||||
|
// visual mode has override, bogus input from user
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mc.marked[uid] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmark unmarks the uid
|
||||||
|
func (mc *controller) Unmark(uid uint32) {
|
||||||
|
if mc.visualMarkMode {
|
||||||
|
// user probably wanted to clear the visual marking
|
||||||
|
mc.ClearVisualMark()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(mc.marked, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remark restores the previous marks
|
||||||
|
func (mc *controller) Remark() {
|
||||||
|
mc.marked = mc.lastMarked
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleMark toggles the marked state for the given uid
|
||||||
|
func (mc *controller) ToggleMark(uid uint32) {
|
||||||
|
if mc.visualMarkMode {
|
||||||
|
// visual mode has override, bogus input from user
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mc.IsMarked(uid) {
|
||||||
|
mc.Unmark(uid)
|
||||||
|
} else {
|
||||||
|
mc.Mark(uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetMark removes the marking from all messages
|
||||||
|
func (mc *controller) resetMark() {
|
||||||
|
mc.lastMarked = mc.marked
|
||||||
|
mc.marked = make(map[uint32]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeStaleUID removes uids that are no longer presents in the UIDProvider
|
||||||
|
func (mc *controller) removeStaleUID() {
|
||||||
|
for mark := range mc.marked {
|
||||||
|
present := false
|
||||||
|
for _, uid := range mc.uidProvider.Uids() {
|
||||||
|
if mark == uid {
|
||||||
|
present = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !present {
|
||||||
|
delete(mc.marked, mark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMarked checks whether the given uid has been marked
|
||||||
|
func (mc *controller) IsMarked(uid uint32) bool {
|
||||||
|
_, marked := mc.marked[uid]
|
||||||
|
return marked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marked returns the uids of all marked messages
|
||||||
|
func (mc *controller) Marked() []uint32 {
|
||||||
|
mc.removeStaleUID()
|
||||||
|
marked := make([]uint32, len(mc.marked))
|
||||||
|
i := 0
|
||||||
|
for uid := range mc.marked {
|
||||||
|
marked[i] = uid
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return marked
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleVisualMark enters or leaves the visual marking mode
|
||||||
|
func (mc *controller) ToggleVisualMark() {
|
||||||
|
mc.visualMarkMode = !mc.visualMarkMode
|
||||||
|
if mc.visualMarkMode {
|
||||||
|
// just entered visual mode, reset whatever marking was already done
|
||||||
|
mc.resetMark()
|
||||||
|
uids := mc.uidProvider.Uids()
|
||||||
|
if idx := mc.uidProvider.SelectedIndex(); idx >= 0 && idx < len(uids) {
|
||||||
|
mc.visualStartUID = uids[idx]
|
||||||
|
mc.marked[mc.visualStartUID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearVisualMark leaves the visual marking mode and resets any marking
|
||||||
|
func (mc *controller) ClearVisualMark() {
|
||||||
|
mc.resetMark()
|
||||||
|
mc.visualMarkMode = false
|
||||||
|
mc.visualStartUID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateVisualMark updates the index with the currently selected message
|
||||||
|
func (mc *controller) UpdateVisualMark() {
|
||||||
|
if !mc.visualMarkMode {
|
||||||
|
// nothing to do
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startIdx := mc.visualStartIdx()
|
||||||
|
if startIdx < 0 {
|
||||||
|
// something deleted the startuid, abort the marking process
|
||||||
|
mc.ClearVisualMark()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIdx := mc.uidProvider.SelectedIndex()
|
||||||
|
if selectedIdx < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uids := mc.uidProvider.Uids()
|
||||||
|
|
||||||
|
var visUids []uint32
|
||||||
|
if selectedIdx > startIdx {
|
||||||
|
visUids = uids[startIdx : selectedIdx+1]
|
||||||
|
} else {
|
||||||
|
visUids = uids[selectedIdx : startIdx+1]
|
||||||
|
}
|
||||||
|
mc.resetMark()
|
||||||
|
for _, uid := range visUids {
|
||||||
|
mc.marked[uid] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the index of needle in haystack or -1 if not found
|
||||||
|
func (mc *controller) visualStartIdx() int {
|
||||||
|
for idx, u := range mc.uidProvider.Uids() {
|
||||||
|
if u == mc.visualStartUID {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
139
lib/marker/marker_test.go
Normal file
139
lib/marker/marker_test.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package marker_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/lib/marker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockUidProvider implements the UidProvider interface and mocks the message
|
||||||
|
// store for testing
|
||||||
|
type mockUidProvider struct {
|
||||||
|
uids []uint32
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockUidProvider) Uids() []uint32 {
|
||||||
|
return mock.uids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockUidProvider) SelectedIndex() int {
|
||||||
|
return mock.idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMarker() (marker.Marker, *mockUidProvider) {
|
||||||
|
uidProvider := &mockUidProvider{
|
||||||
|
uids: []uint32{1, 2, 3, 4},
|
||||||
|
idx: 1,
|
||||||
|
}
|
||||||
|
m := marker.New(uidProvider)
|
||||||
|
return m, uidProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarker_MarkUnmark(t *testing.T) {
|
||||||
|
m, _ := createMarker()
|
||||||
|
uid := uint32(4)
|
||||||
|
|
||||||
|
m.Mark(uid)
|
||||||
|
if !m.IsMarked(uid) {
|
||||||
|
t.Errorf("Marking failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Unmark(uid)
|
||||||
|
if m.IsMarked(uid) {
|
||||||
|
t.Errorf("Unmarking failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarker_ToggleMark(t *testing.T) {
|
||||||
|
m, _ := createMarker()
|
||||||
|
uid := uint32(4)
|
||||||
|
|
||||||
|
if m.IsMarked(uid) {
|
||||||
|
t.Errorf("ToggleMark: uid should not be marked")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ToggleMark(uid)
|
||||||
|
if !m.IsMarked(uid) {
|
||||||
|
t.Errorf("ToggleMark: uid should be marked")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ToggleMark(uid)
|
||||||
|
if m.IsMarked(uid) {
|
||||||
|
t.Errorf("ToggleMark: uid should not be marked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarker_Marked(t *testing.T) {
|
||||||
|
m, _ := createMarker()
|
||||||
|
expected := map[uint32]struct{}{
|
||||||
|
uint32(1): {},
|
||||||
|
uint32(4): {},
|
||||||
|
}
|
||||||
|
for uid := range expected {
|
||||||
|
m.Mark(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := m.Marked()
|
||||||
|
if len(expected) != len(got) {
|
||||||
|
t.Errorf("Marked: expected len of %d but got %d", len(expected), len(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, uid := range got {
|
||||||
|
if _, ok := expected[uid]; !ok {
|
||||||
|
t.Errorf("Marked: received uid %d as marked but it should not be", uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarker_VisualMode(t *testing.T) {
|
||||||
|
m, up := createMarker()
|
||||||
|
|
||||||
|
// activate visual mode
|
||||||
|
m.ToggleVisualMark()
|
||||||
|
|
||||||
|
// marking should now fail silently because we're in visual mode
|
||||||
|
m.Mark(1)
|
||||||
|
if m.IsMarked(1) {
|
||||||
|
t.Errorf("marking in visual mode should not work")
|
||||||
|
}
|
||||||
|
|
||||||
|
// move selection index to last item
|
||||||
|
up.idx = len(up.uids) - 1
|
||||||
|
m.UpdateVisualMark()
|
||||||
|
expectedMarked := []uint32{2, 3, 4}
|
||||||
|
|
||||||
|
for _, uidMarked := range expectedMarked {
|
||||||
|
if !m.IsMarked(uidMarked) {
|
||||||
|
t.Logf("expected: %#v, got: %#v", expectedMarked, m.Marked())
|
||||||
|
t.Errorf("updatevisual: uid %v should be marked in visual mode", uidMarked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear all
|
||||||
|
m.ClearVisualMark()
|
||||||
|
if len(m.Marked()) > 0 {
|
||||||
|
t.Errorf("no uids should be marked after clearing visual mark")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test remark
|
||||||
|
m.Remark()
|
||||||
|
for _, uidMarked := range expectedMarked {
|
||||||
|
if !m.IsMarked(uidMarked) {
|
||||||
|
t.Errorf("remark: uid %v should be marked in visual mode", uidMarked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarker_MarkOutOfBound(t *testing.T) {
|
||||||
|
m, _ := createMarker()
|
||||||
|
|
||||||
|
outOfBoundUid := uint32(100)
|
||||||
|
|
||||||
|
m.Mark(outOfBoundUid)
|
||||||
|
for _, markedUid := range m.Marked() {
|
||||||
|
if markedUid == outOfBoundUid {
|
||||||
|
t.Errorf("out-of-bound uid should not be marked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
lib/msgstore.go
175
lib/msgstore.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/lib/marker"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/sort"
|
"git.sr.ht/~rjarry/aerc/lib/sort"
|
||||||
"git.sr.ht/~rjarry/aerc/logging"
|
"git.sr.ht/~rjarry/aerc/logging"
|
||||||
"git.sr.ht/~rjarry/aerc/models"
|
"git.sr.ht/~rjarry/aerc/models"
|
||||||
|
@ -27,10 +28,7 @@ type MessageStore struct {
|
||||||
headerCallbacks map[uint32][]func(*types.MessageInfo)
|
headerCallbacks map[uint32][]func(*types.MessageInfo)
|
||||||
|
|
||||||
// marking
|
// marking
|
||||||
marked map[uint32]struct{}
|
marker marker.Marker
|
||||||
lastMarked map[uint32]struct{}
|
|
||||||
visualStartUid uint32
|
|
||||||
visualMarkMode bool
|
|
||||||
|
|
||||||
// Search/filter results
|
// Search/filter results
|
||||||
results []uint32
|
results []uint32
|
||||||
|
@ -78,8 +76,8 @@ func NewMessageStore(worker *types.Worker,
|
||||||
DirInfo: *dirInfo,
|
DirInfo: *dirInfo,
|
||||||
Messages: make(map[uint32]*models.MessageInfo),
|
Messages: make(map[uint32]*models.MessageInfo),
|
||||||
|
|
||||||
selectedUid: MagicUid,
|
selectedUid: MagicUid,
|
||||||
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)),
|
||||||
|
|
||||||
|
@ -215,7 +213,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
}
|
}
|
||||||
store.Messages = newMap
|
store.Messages = newMap
|
||||||
store.uids = msg.Uids
|
store.uids = msg.Uids
|
||||||
store.checkMark()
|
|
||||||
update = true
|
update = true
|
||||||
case *types.DirectoryThreaded:
|
case *types.DirectoryThreaded:
|
||||||
var uids []uint32
|
var uids []uint32
|
||||||
|
@ -236,7 +233,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
}
|
}
|
||||||
store.Messages = newMap
|
store.Messages = newMap
|
||||||
store.uids = uids
|
store.uids = uids
|
||||||
store.checkMark()
|
|
||||||
store.threads = msg.Threads
|
store.threads = msg.Threads
|
||||||
update = true
|
update = true
|
||||||
case *types.MessageInfo:
|
case *types.MessageInfo:
|
||||||
|
@ -290,7 +286,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
toDelete[uid] = nil
|
toDelete[uid] = nil
|
||||||
delete(store.Messages, uid)
|
delete(store.Messages, uid)
|
||||||
delete(store.Deleted, uid)
|
delete(store.Deleted, uid)
|
||||||
delete(store.marked, uid)
|
|
||||||
}
|
}
|
||||||
uids := make([]uint32, len(store.uids)-len(msg.Uids))
|
uids := make([]uint32, len(store.uids)-len(msg.Uids))
|
||||||
j := 0
|
j := 0
|
||||||
|
@ -539,142 +534,9 @@ func (store *MessageStore) SelectedUid() uint32 {
|
||||||
|
|
||||||
func (store *MessageStore) Select(uid uint32) {
|
func (store *MessageStore) Select(uid uint32) {
|
||||||
store.selectedUid = uid
|
store.selectedUid = uid
|
||||||
store.updateVisual()
|
if store.marker != nil {
|
||||||
}
|
store.marker.UpdateVisualMark()
|
||||||
|
|
||||||
// Mark sets the marked state on a MessageInfo
|
|
||||||
func (store *MessageStore) Mark(uid uint32) {
|
|
||||||
if store.visualMarkMode {
|
|
||||||
// visual mode has override, bogus input from user
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
store.marked[uid] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmark removes the marked state on a MessageInfo
|
|
||||||
func (store *MessageStore) Unmark(uid uint32) {
|
|
||||||
if store.visualMarkMode {
|
|
||||||
// user probably wanted to clear the visual marking
|
|
||||||
store.ClearVisualMark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(store.marked, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *MessageStore) Remark() {
|
|
||||||
store.marked = store.lastMarked
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleMark toggles the marked state on a MessageInfo
|
|
||||||
func (store *MessageStore) ToggleMark(uid uint32) {
|
|
||||||
if store.visualMarkMode {
|
|
||||||
// visual mode has override, bogus input from user
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if store.IsMarked(uid) {
|
|
||||||
store.Unmark(uid)
|
|
||||||
} else {
|
|
||||||
store.Mark(uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetMark removes the marking from all messages
|
|
||||||
func (store *MessageStore) resetMark() {
|
|
||||||
store.lastMarked = store.marked
|
|
||||||
store.marked = make(map[uint32]struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMark checks that no stale uids remain marked
|
|
||||||
func (store *MessageStore) checkMark() {
|
|
||||||
for mark := range store.marked {
|
|
||||||
present := false
|
|
||||||
for _, uid := range store.uids {
|
|
||||||
if mark == uid {
|
|
||||||
present = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !present {
|
|
||||||
delete(store.marked, mark)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMarked checks whether a MessageInfo has been marked
|
|
||||||
func (store *MessageStore) IsMarked(uid uint32) bool {
|
|
||||||
_, marked := store.marked[uid]
|
|
||||||
return marked
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleVisualMark enters or leaves the visual marking mode
|
|
||||||
func (store *MessageStore) ToggleVisualMark() {
|
|
||||||
store.visualMarkMode = !store.visualMarkMode
|
|
||||||
switch store.visualMarkMode {
|
|
||||||
case true:
|
|
||||||
// just entered visual mode, reset whatever marking was already done
|
|
||||||
store.resetMark()
|
|
||||||
store.visualStartUid = store.Selected().Uid
|
|
||||||
store.marked[store.visualStartUid] = struct{}{}
|
|
||||||
case false:
|
|
||||||
// visual mode ended, nothing to do
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearVisualMark leaves the visual marking mode and resets any marking
|
|
||||||
func (store *MessageStore) ClearVisualMark() {
|
|
||||||
store.resetMark()
|
|
||||||
store.visualMarkMode = false
|
|
||||||
store.visualStartUid = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marked returns the uids of all marked messages
|
|
||||||
func (store *MessageStore) Marked() []uint32 {
|
|
||||||
marked := make([]uint32, len(store.marked))
|
|
||||||
i := 0
|
|
||||||
for uid := range store.marked {
|
|
||||||
marked[i] = uid
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return marked
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *MessageStore) updateVisual() {
|
|
||||||
if !store.visualMarkMode {
|
|
||||||
// nothing to do
|
|
||||||
return
|
|
||||||
}
|
|
||||||
startIdx := store.visualStartIdx()
|
|
||||||
if startIdx < 0 {
|
|
||||||
// something deleted the startuid, abort the marking process
|
|
||||||
store.ClearVisualMark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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{}{}
|
|
||||||
}
|
|
||||||
missing := make([]uint32, 0)
|
|
||||||
for _, uid := range visUids {
|
|
||||||
if msg := store.Messages[uid]; msg == nil {
|
|
||||||
missing = append(missing, uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.FetchHeaders(missing, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) NextPrev(delta int) {
|
func (store *MessageStore) NextPrev(delta int) {
|
||||||
|
@ -700,7 +562,9 @@ func (store *MessageStore) NextPrev(delta int) {
|
||||||
|
|
||||||
store.Select(uids[newIdx])
|
store.Select(uids[newIdx])
|
||||||
|
|
||||||
store.updateVisual()
|
if store.marker != nil {
|
||||||
|
store.marker.UpdateVisualMark()
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -821,14 +685,15 @@ func (store *MessageStore) GetCurrentSortCriteria() []*types.SortCriterion {
|
||||||
return store.sortCriteria
|
return store.sortCriteria
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the index of needle in haystack or -1 if not found
|
func (store *MessageStore) SetMarker(m marker.Marker) {
|
||||||
func (store *MessageStore) visualStartIdx() int {
|
store.marker = m
|
||||||
for idx, u := range store.Uids() {
|
}
|
||||||
if u == store.visualStartUid {
|
|
||||||
return idx
|
func (store *MessageStore) Marker() marker.Marker {
|
||||||
}
|
if store.marker == nil {
|
||||||
|
store.marker = marker.New(store)
|
||||||
}
|
}
|
||||||
return -1
|
return store.marker
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindIndexByUid returns the index in store.Uids() or -1 if not found
|
// FindIndexByUid returns the index in store.Uids() or -1 if not found
|
||||||
|
@ -845,3 +710,9 @@ func (store *MessageStore) FindIndexByUid(uid uint32) int {
|
||||||
func (store *MessageStore) Capabilities() *models.Capabilities {
|
func (store *MessageStore) Capabilities() *models.Capabilities {
|
||||||
return store.DirInfo.Caps
|
return store.DirInfo.Caps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectedIndex returns the index of the selected message in the uid list or
|
||||||
|
// -1 if not found
|
||||||
|
func (store *MessageStore) SelectedIndex() int {
|
||||||
|
return store.FindIndexByUid(store.selectedUid)
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"git.sr.ht/~rjarry/aerc/config"
|
"git.sr.ht/~rjarry/aerc/config"
|
||||||
"git.sr.ht/~rjarry/aerc/lib"
|
"git.sr.ht/~rjarry/aerc/lib"
|
||||||
|
"git.sr.ht/~rjarry/aerc/lib/marker"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/sort"
|
"git.sr.ht/~rjarry/aerc/lib/sort"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
"git.sr.ht/~rjarry/aerc/lib/statusline"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/ui"
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
||||||
|
@ -222,8 +223,10 @@ func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acct *AccountView) MarkedMessages() ([]uint32, error) {
|
func (acct *AccountView) MarkedMessages() ([]uint32, error) {
|
||||||
store := acct.Store()
|
if store := acct.Store(); store != nil {
|
||||||
return store.Marked(), nil
|
return store.Marker().Marked(), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("no store available")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (acct *AccountView) SelectedMessagePart() *PartInfo {
|
func (acct *AccountView) SelectedMessagePart() *PartInfo {
|
||||||
|
@ -301,6 +304,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
||||||
acct.host.Beep()
|
acct.host.Beep()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
store.SetMarker(marker.New(store))
|
||||||
acct.dirlist.SetMsgStore(msg.Info.Name, store)
|
acct.dirlist.SetMsgStore(msg.Info.Name, store)
|
||||||
}
|
}
|
||||||
case *types.DirectoryContents:
|
case *types.DirectoryContents:
|
||||||
|
|
|
@ -122,7 +122,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
AccountName: acct.Name(),
|
AccountName: acct.Name(),
|
||||||
MsgInfo: msg,
|
MsgInfo: msg,
|
||||||
MsgNum: row,
|
MsgNum: row,
|
||||||
MsgIsMarked: store.IsMarked(t.Uid),
|
MsgIsMarked: store.Marker().IsMarked(t.Uid),
|
||||||
ThreadPrefix: prefix,
|
ThreadPrefix: prefix,
|
||||||
ThreadSameSubject: normalizedSubject == lastSubject,
|
ThreadSameSubject: normalizedSubject == lastSubject,
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
AccountName: acct.Name(),
|
AccountName: acct.Name(),
|
||||||
MsgInfo: msg,
|
MsgInfo: msg,
|
||||||
MsgNum: row,
|
MsgNum: row,
|
||||||
MsgIsMarked: store.IsMarked(uid),
|
MsgIsMarked: store.Marker().IsMarked(uid),
|
||||||
}
|
}
|
||||||
if ml.drawRow(textWidth, ctx, uid, row, &needsHeaders, fmtCtx) {
|
if ml.drawRow(textWidth, ctx, uid, row, &needsHeaders, fmtCtx) {
|
||||||
break
|
break
|
||||||
|
@ -244,7 +244,7 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
|
||||||
}
|
}
|
||||||
|
|
||||||
// marked message
|
// marked message
|
||||||
if store.IsMarked(msg.Uid) {
|
if store.Marker().IsMarked(msg.Uid) {
|
||||||
msg_styles = append(msg_styles, config.STYLE_MSGLIST_MARKED)
|
msg_styles = append(msg_styles, config.STYLE_MSGLIST_MARKED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -302,8 +302,7 @@ func (mv *MessageViewer) SelectedMessage() (*models.MessageInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mv *MessageViewer) MarkedMessages() ([]uint32, error) {
|
func (mv *MessageViewer) MarkedMessages() ([]uint32, error) {
|
||||||
store := mv.Store()
|
return mv.acct.MarkedMessages()
|
||||||
return store.Marked(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mv *MessageViewer) ToggleHeaders() {
|
func (mv *MessageViewer) ToggleHeaders() {
|
||||||
|
|
Loading…
Reference in a new issue