Compare commits
5 commits
206665a2d9
...
14ceca3200
Author | SHA1 | Date | |
---|---|---|---|
|
14ceca3200 | ||
|
7565a96525 | ||
|
ae99f4c5bb | ||
|
006e10357b | ||
|
88afe7bb4a |
11 changed files with 179 additions and 104 deletions
|
@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
`:rmdir`, `:archive` and the `copy-to` option.
|
`:rmdir`, `:archive` and the `copy-to` option.
|
||||||
- Display messages from bottom to top with `reverse-msglist-order=true` in
|
- Display messages from bottom to top with `reverse-msglist-order=true` in
|
||||||
`aerc.conf`.
|
`aerc.conf`.
|
||||||
|
- Display threads from bottom to top with `reverse-thread-order=true` in
|
||||||
|
`aerc.conf`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/logging"
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,6 +73,10 @@ func isAddressHeader(h string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxCompletionLines = 100
|
||||||
|
|
||||||
|
var tooManyLines = fmt.Errorf("returned more than %d lines", maxCompletionLines)
|
||||||
|
|
||||||
// completeAddress uses the configured address book completion command to fetch
|
// completeAddress uses the configured address book completion command to fetch
|
||||||
// completions for the specified string, returning a slice of completions and
|
// completions for the specified string, returning a slice of completions and
|
||||||
// a prefix to be prepended to the selected completion, or an error.
|
// a prefix to be prepended to the selected completion, or an error.
|
||||||
|
@ -88,6 +94,8 @@ func (c *Completer) completeAddress(s string) ([]string, string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("stderr: %w", err)
|
return nil, "", fmt.Errorf("stderr: %w", err)
|
||||||
}
|
}
|
||||||
|
// reset the process group id to allow killing all its children
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return nil, "", fmt.Errorf("cmd start: %w", err)
|
return nil, "", fmt.Errorf("cmd start: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -99,6 +107,12 @@ func (c *Completer) completeAddress(s string) ([]string, string, error) {
|
||||||
|
|
||||||
completions, err := readCompletions(stdout)
|
completions, err := readCompletions(stdout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// make sure to kill the process *and* all its children
|
||||||
|
//nolint:errcheck // who cares?
|
||||||
|
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
logging.Warnf("command %s killed: %s", cmd, err)
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, tooManyLines) {
|
||||||
buf, _ := io.ReadAll(stderr)
|
buf, _ := io.ReadAll(stderr)
|
||||||
msg := strings.TrimSpace(string(buf))
|
msg := strings.TrimSpace(string(buf))
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
|
@ -148,27 +162,38 @@ func (c *Completer) getAddressCmd(s string) (*exec.Cmd, error) {
|
||||||
func readCompletions(r io.Reader) ([]string, error) {
|
func readCompletions(r io.Reader) ([]string, error) {
|
||||||
buf := bufio.NewReader(r)
|
buf := bufio.NewReader(r)
|
||||||
completions := []string{}
|
completions := []string{}
|
||||||
for {
|
for i := 0; i < maxCompletionLines; i++ {
|
||||||
line, err := buf.ReadString('\n')
|
line, err := buf.ReadString('\n')
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
return completions, nil
|
return completions, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
// skip empty lines
|
||||||
|
continue
|
||||||
|
}
|
||||||
parts := strings.SplitN(line, "\t", 3)
|
parts := strings.SplitN(line, "\t", 3)
|
||||||
addr, err := mail.ParseAddress(strings.TrimSpace(parts[0]))
|
addr, err := mail.ParseAddress(strings.TrimSpace(parts[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
logging.Warnf(
|
||||||
|
"line %d: %#v: could not parse address: %v",
|
||||||
|
line, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
addr.Name = strings.TrimSpace(parts[1])
|
addr.Name = strings.TrimSpace(parts[1])
|
||||||
}
|
}
|
||||||
decoded, err := decodeMIME(addr.String())
|
decoded, err := decodeMIME(addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not decode MIME string: %w", err)
|
logging.Warnf(
|
||||||
|
"line %d: %#v: could not decode MIME string: %v",
|
||||||
|
i+1, line, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
completions = append(completions, decoded)
|
completions = append(completions, decoded)
|
||||||
}
|
}
|
||||||
|
return completions, tooManyLines
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMIME(s string) (string, error) {
|
func decodeMIME(s string) (string, error) {
|
||||||
|
|
|
@ -194,6 +194,14 @@ completion-popovers=true
|
||||||
# Default: false
|
# Default: false
|
||||||
#reverse-msglist-order = false
|
#reverse-msglist-order = false
|
||||||
|
|
||||||
|
# Reverse display of the mesage threads. Default order is the the intial
|
||||||
|
# message is on the top with all the replies being displayed below. The
|
||||||
|
# reverse option will put the initial message at the bottom with the
|
||||||
|
# replies on top.
|
||||||
|
#
|
||||||
|
# Default: false
|
||||||
|
#reverse-thread-order=false
|
||||||
|
|
||||||
#[ui:account=foo]
|
#[ui:account=foo]
|
||||||
#
|
#
|
||||||
# Enable a threaded view of messages. If this is not supported by the backend
|
# Enable a threaded view of messages. If this is not supported by the backend
|
||||||
|
|
|
@ -79,7 +79,8 @@ type UIConfig struct {
|
||||||
BorderCharVertical rune `ini:"-"`
|
BorderCharVertical rune `ini:"-"`
|
||||||
BorderCharHorizontal rune `ini:"-"`
|
BorderCharHorizontal rune `ini:"-"`
|
||||||
|
|
||||||
ReverseOrder bool `ini:"reverse-msglist-order"`
|
ReverseOrder bool `ini:"reverse-msglist-order"`
|
||||||
|
ReverseThreadOrder bool `ini:"reverse-thread-order"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextType int
|
type ContextType int
|
||||||
|
|
|
@ -335,6 +335,14 @@ These options are configured in the *[ui]* section of aerc.conf.
|
||||||
|
|
||||||
Default: false
|
Default: false
|
||||||
|
|
||||||
|
*reverse-thread-order*
|
||||||
|
Reverse display of the mesage threads. Default order is the the intial
|
||||||
|
message is on the top with all the replies being displayed below. The
|
||||||
|
reverse option will put the initial message at the bottom with the
|
||||||
|
replies on top.
|
||||||
|
|
||||||
|
Default: false
|
||||||
|
|
||||||
*threading-enabled*
|
*threading-enabled*
|
||||||
Enable a threaded view of messages. If this is not supported by the
|
Enable a threaded view of messages. If this is not supported by the
|
||||||
backend (IMAP server or notmuch), threads will be built by the client.
|
backend (IMAP server or notmuch), threads will be built by the client.
|
||||||
|
|
|
@ -21,6 +21,9 @@ func FixBounds(i, lower, upper int) int {
|
||||||
// WrapBounds will wrap the index i around its upper- or lower-bound if
|
// WrapBounds will wrap the index i around its upper- or lower-bound if
|
||||||
// out-of-bound
|
// out-of-bound
|
||||||
func WrapBounds(i, lower, upper int) int {
|
func WrapBounds(i, lower, upper int) int {
|
||||||
|
if upper <= 0 {
|
||||||
|
return lower
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case i > upper:
|
case i > upper:
|
||||||
i = lower + (i-upper-1)%upper
|
i = lower + (i-upper-1)%upper
|
||||||
|
|
|
@ -39,9 +39,10 @@ type MessageStore struct {
|
||||||
|
|
||||||
sortCriteria []*types.SortCriterion
|
sortCriteria []*types.SortCriterion
|
||||||
|
|
||||||
threadedView bool
|
threadedView bool
|
||||||
buildThreads bool
|
reverseThreadOrder bool
|
||||||
builder *ThreadBuilder
|
buildThreads bool
|
||||||
|
builder *ThreadBuilder
|
||||||
|
|
||||||
// Map of uids we've asked the worker to fetch
|
// Map of uids we've asked the worker to fetch
|
||||||
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
|
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
|
||||||
|
@ -74,7 +75,7 @@ func NewMessageStore(worker *types.Worker,
|
||||||
dirInfo *models.DirectoryInfo,
|
dirInfo *models.DirectoryInfo,
|
||||||
defaultSortCriteria []*types.SortCriterion,
|
defaultSortCriteria []*types.SortCriterion,
|
||||||
thread bool, clientThreads bool, clientThreadsDelay time.Duration,
|
thread bool, clientThreads bool, clientThreadsDelay time.Duration,
|
||||||
reverseOrder bool,
|
reverseOrder bool, reverseThreadOrder bool,
|
||||||
triggerNewEmail func(*models.MessageInfo),
|
triggerNewEmail func(*models.MessageInfo),
|
||||||
triggerDirectoryChange func(),
|
triggerDirectoryChange func(),
|
||||||
) *MessageStore {
|
) *MessageStore {
|
||||||
|
@ -91,8 +92,9 @@ func NewMessageStore(worker *types.Worker,
|
||||||
|
|
||||||
bodyCallbacks: make(map[uint32][]func(*types.FullMessage)),
|
bodyCallbacks: make(map[uint32][]func(*types.FullMessage)),
|
||||||
|
|
||||||
threadedView: thread,
|
threadedView: thread,
|
||||||
buildThreads: clientThreads,
|
buildThreads: clientThreads,
|
||||||
|
reverseThreadOrder: reverseThreadOrder,
|
||||||
|
|
||||||
filter: []string{"filter"},
|
filter: []string{"filter"},
|
||||||
sortCriteria: defaultSortCriteria,
|
sortCriteria: defaultSortCriteria,
|
||||||
|
@ -231,7 +233,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
|
||||||
newMap := make(map[uint32]*models.MessageInfo)
|
newMap := make(map[uint32]*models.MessageInfo)
|
||||||
|
|
||||||
builder := NewThreadBuilder(store.iterFactory)
|
builder := NewThreadBuilder(store.iterFactory)
|
||||||
builder.RebuildUids(msg.Threads)
|
builder.RebuildUids(msg.Threads, store.reverseThreadOrder)
|
||||||
store.uids = builder.Uids()
|
store.uids = builder.Uids()
|
||||||
store.threads = msg.Threads
|
store.threads = msg.Threads
|
||||||
|
|
||||||
|
@ -364,6 +366,14 @@ func (store *MessageStore) update(threads bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *MessageStore) SetReverseThreadOrder(reverse bool) {
|
||||||
|
store.reverseThreadOrder = reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *MessageStore) ReverseThreadOrder() bool {
|
||||||
|
return store.reverseThreadOrder
|
||||||
|
}
|
||||||
|
|
||||||
func (store *MessageStore) SetThreadedView(thread bool) {
|
func (store *MessageStore) SetThreadedView(thread bool) {
|
||||||
store.threadedView = thread
|
store.threadedView = thread
|
||||||
if store.buildThreads {
|
if store.buildThreads {
|
||||||
|
@ -424,7 +434,7 @@ func (store *MessageStore) runThreadBuilderNow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// build new threads
|
// build new threads
|
||||||
th := store.builder.Threads(store.uids)
|
th := store.builder.Threads(store.uids, store.reverseThreadOrder)
|
||||||
|
|
||||||
// save local threads to the message store variable and
|
// save local threads to the message store variable and
|
||||||
// run callback if defined (callback should reposition cursor)
|
// run callback if defined (callback should reposition cursor)
|
||||||
|
@ -593,29 +603,20 @@ func (store *MessageStore) NextPrev(delta int) {
|
||||||
if len(uids) == 0 {
|
if len(uids) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iter := store.iterFactory.NewIterator(uids)
|
iter := store.iterFactory.NewIterator(uids)
|
||||||
|
|
||||||
uid := store.SelectedUid()
|
newIdx := store.FindIndexByUid(store.SelectedUid())
|
||||||
|
|
||||||
newIdx := store.FindIndexByUid(uid)
|
|
||||||
if newIdx < 0 {
|
if newIdx < 0 {
|
||||||
store.Select(uids[iter.StartIndex()])
|
store.Select(uids[iter.StartIndex()])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
newIdx = iterator.MoveIndex(
|
||||||
low, high := iter.EndIndex(), iter.StartIndex()
|
newIdx,
|
||||||
sign := -1
|
delta,
|
||||||
if high < low {
|
iter,
|
||||||
low, high = high, low
|
iterator.FixBounds,
|
||||||
sign = 1
|
)
|
||||||
}
|
|
||||||
newIdx += sign * delta
|
|
||||||
if newIdx >= len(uids) {
|
|
||||||
newIdx = high
|
|
||||||
} else if newIdx < 0 {
|
|
||||||
newIdx = low
|
|
||||||
}
|
|
||||||
|
|
||||||
store.Select(uids[newIdx])
|
store.Select(uids[newIdx])
|
||||||
|
|
||||||
if store.BuildThreads() && store.ThreadedView() {
|
if store.BuildThreads() && store.ThreadedView() {
|
||||||
|
@ -631,15 +632,7 @@ func (store *MessageStore) NextPrev(delta int) {
|
||||||
if store.marker != nil {
|
if store.marker != nil {
|
||||||
store.marker.UpdateVisualMark()
|
store.marker.UpdateVisualMark()
|
||||||
}
|
}
|
||||||
|
store.updateResults()
|
||||||
nextResultIndex := len(store.results) - store.resultIndex - 2*delta
|
|
||||||
if nextResultIndex < 0 || nextResultIndex >= len(store.results) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nextResultUid := store.results[nextResultIndex]
|
|
||||||
if nextResultUid == store.SelectedUid() {
|
|
||||||
store.resultIndex += delta
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MessageStore) Next() {
|
func (store *MessageStore) Next() {
|
||||||
|
@ -690,18 +683,35 @@ func (store *MessageStore) ApplyClear() {
|
||||||
store.Sort(nil, nil)
|
store.Sort(nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *MessageStore) updateResults() {
|
||||||
|
if len(store.results) == 0 || store.resultIndex < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := store.SelectedUid()
|
||||||
|
for i, u := range store.results {
|
||||||
|
if uid == u {
|
||||||
|
store.resultIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (store *MessageStore) nextPrevResult(delta int) {
|
func (store *MessageStore) nextPrevResult(delta int) {
|
||||||
if len(store.results) == 0 {
|
if len(store.results) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.resultIndex += delta
|
iter := store.iterFactory.NewIterator(store.results)
|
||||||
if store.resultIndex >= len(store.results) {
|
|
||||||
store.resultIndex = 0
|
|
||||||
}
|
|
||||||
if store.resultIndex < 0 {
|
if store.resultIndex < 0 {
|
||||||
store.resultIndex = len(store.results) - 1
|
store.resultIndex = iter.StartIndex()
|
||||||
|
} else {
|
||||||
|
store.resultIndex = iterator.MoveIndex(
|
||||||
|
store.resultIndex,
|
||||||
|
delta,
|
||||||
|
iter,
|
||||||
|
iterator.WrapBounds,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
store.Select(store.results[len(store.results)-store.resultIndex-1])
|
store.Select(store.results[store.resultIndex])
|
||||||
store.update(false)
|
store.update(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (builder *ThreadBuilder) Update(msg *models.MessageInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Threads returns a slice of threads for the given list of uids
|
// Threads returns a slice of threads for the given list of uids
|
||||||
func (builder *ThreadBuilder) Threads(uids []uint32) []*types.Thread {
|
func (builder *ThreadBuilder) Threads(uids []uint32, inverse bool) []*types.Thread {
|
||||||
builder.Lock()
|
builder.Lock()
|
||||||
defer builder.Unlock()
|
defer builder.Unlock()
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (builder *ThreadBuilder) Threads(uids []uint32) []*types.Thread {
|
||||||
builder.sortThreads(threads, uids)
|
builder.sortThreads(threads, uids)
|
||||||
|
|
||||||
// rebuild uids from threads
|
// rebuild uids from threads
|
||||||
builder.RebuildUids(threads)
|
builder.RebuildUids(threads, inverse)
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
elapsed := time.Since(start)
|
||||||
logging.Infof("%d threads created in %s", len(threads), elapsed)
|
logging.Infof("%d threads created in %s", len(threads), elapsed)
|
||||||
|
@ -155,15 +155,23 @@ func (builder *ThreadBuilder) sortThreads(threads []*types.Thread, orderedUids [
|
||||||
}
|
}
|
||||||
|
|
||||||
// RebuildUids rebuilds the uids from the given slice of threads
|
// RebuildUids rebuilds the uids from the given slice of threads
|
||||||
func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread) {
|
func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread, inverse bool) {
|
||||||
uids := make([]uint32, 0, len(threads))
|
uids := make([]uint32, 0, len(threads))
|
||||||
iterT := builder.iterFactory.NewIterator(threads)
|
iterT := builder.iterFactory.NewIterator(threads)
|
||||||
for iterT.Next() {
|
for iterT.Next() {
|
||||||
|
var threaduids []uint32
|
||||||
_ = iterT.Value().(*types.Thread).Walk(
|
_ = iterT.Value().(*types.Thread).Walk(
|
||||||
func(t *types.Thread, level int, currentErr error) error {
|
func(t *types.Thread, level int, currentErr error) error {
|
||||||
uids = append(uids, t.Uid)
|
threaduids = append(threaduids, t.Uid)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if inverse {
|
||||||
|
for j := len(threaduids) - 1; j >= 0; j-- {
|
||||||
|
uids = append(uids, threaduids[j])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uids = append(uids, threaduids...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result := make([]uint32, 0, len(uids))
|
result := make([]uint32, 0, len(uids))
|
||||||
iterU := builder.iterFactory.NewIterator(uids)
|
iterU := builder.iterFactory.NewIterator(uids)
|
||||||
|
|
|
@ -286,6 +286,7 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
||||||
acct.dirlist.UiConfig(name).ForceClientThreads,
|
acct.dirlist.UiConfig(name).ForceClientThreads,
|
||||||
acct.dirlist.UiConfig(name).ClientThreadsDelay,
|
acct.dirlist.UiConfig(name).ClientThreadsDelay,
|
||||||
acct.dirlist.UiConfig(name).ReverseOrder,
|
acct.dirlist.UiConfig(name).ReverseOrder,
|
||||||
|
acct.dirlist.UiConfig(name).ReverseThreadOrder,
|
||||||
func(msg *models.MessageInfo) {
|
func(msg *models.MessageInfo) {
|
||||||
acct.conf.Triggers.ExecNewEmail(acct.acct,
|
acct.conf.Triggers.ExecNewEmail(acct.acct,
|
||||||
acct.conf, msg)
|
acct.conf, msg)
|
||||||
|
|
|
@ -249,7 +249,7 @@ func (dt *DirectoryTree) countVisible(list []*types.Thread) (n int) {
|
||||||
|
|
||||||
func (dt *DirectoryTree) displayText(node *types.Thread) string {
|
func (dt *DirectoryTree) displayText(node *types.Thread) string {
|
||||||
elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.pathSeparator)
|
elems := strings.Split(dt.treeDirs[getAnyUid(node)], dt.pathSeparator)
|
||||||
return fmt.Sprintf("%s%s%s", threadPrefix(node), getFlag(node), elems[countLevels(node)])
|
return fmt.Sprintf("%s%s%s", threadPrefix(node, false), getFlag(node), elems[countLevels(node)])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *DirectoryTree) getDirectory(node *types.Thread) string {
|
func (dt *DirectoryTree) getDirectory(node *types.Thread) string {
|
||||||
|
|
|
@ -12,6 +12,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/format"
|
"git.sr.ht/~rjarry/aerc/lib/format"
|
||||||
|
"git.sr.ht/~rjarry/aerc/lib/iterator"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/ui"
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
||||||
"git.sr.ht/~rjarry/aerc/logging"
|
"git.sr.ht/~rjarry/aerc/logging"
|
||||||
"git.sr.ht/~rjarry/aerc/models"
|
"git.sr.ht/~rjarry/aerc/models"
|
||||||
|
@ -88,57 +89,61 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
row int = 0
|
row int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
if store.ThreadedView() {
|
createBaseCtx := func(uid uint32, row int) format.Ctx {
|
||||||
iter := store.ThreadsIterator()
|
return format.Ctx{
|
||||||
var i int = 0
|
FromAddress: acct.acct.From,
|
||||||
|
AccountName: acct.Name(),
|
||||||
|
MsgInfo: store.Messages[uid],
|
||||||
|
MsgNum: row,
|
||||||
|
MsgIsMarked: store.Marker().IsMarked(uid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for iter.Next() {
|
if store.ThreadedView() {
|
||||||
thread := iter.Value().(*types.Thread)
|
var (
|
||||||
var lastSubject string
|
lastSubject string
|
||||||
err := thread.Walk(func(t *types.Thread, _ int, currentErr error) error {
|
prevThread *types.Thread
|
||||||
if currentErr != nil {
|
i int = 0
|
||||||
return currentErr
|
)
|
||||||
}
|
factory := iterator.NewFactory(!store.ReverseThreadOrder())
|
||||||
if t.Hidden || t.Deleted {
|
threadLoop:
|
||||||
|
for iter := store.ThreadsIterator(); iter.Next(); {
|
||||||
|
var cur []*types.Thread
|
||||||
|
err := iter.Value().(*types.Thread).Walk(
|
||||||
|
func(t *types.Thread, _ int, _ error,
|
||||||
|
) error {
|
||||||
|
if t.Hidden || t.Deleted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cur = append(cur, t)
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
|
if err != nil {
|
||||||
|
logging.Errorf("thread walk: %v", err)
|
||||||
|
}
|
||||||
|
for curIter := factory.NewIterator(cur); curIter.Next(); {
|
||||||
if i < ml.Scroll() {
|
if i < ml.Scroll() {
|
||||||
i++
|
i++
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
i++
|
if thread := curIter.Value().(*types.Thread); thread != nil {
|
||||||
msg := store.Messages[t.Uid]
|
fmtCtx := createBaseCtx(thread.Uid, row)
|
||||||
var prefix string
|
fmtCtx.ThreadPrefix = threadPrefix(thread,
|
||||||
var subject string
|
store.ReverseThreadOrder())
|
||||||
var normalizedSubject string
|
if fmtCtx.MsgInfo != nil && fmtCtx.MsgInfo.Envelope != nil {
|
||||||
if msg != nil {
|
baseSubject, _ := sortthread.GetBaseSubject(
|
||||||
prefix = threadPrefix(t)
|
fmtCtx.MsgInfo.Envelope.Subject)
|
||||||
if msg.Envelope != nil {
|
fmtCtx.ThreadSameSubject = baseSubject == lastSubject &&
|
||||||
subject = msg.Envelope.Subject
|
sameParent(thread, prevThread) &&
|
||||||
normalizedSubject, _ = sortthread.GetBaseSubject(subject)
|
!isParent(thread)
|
||||||
|
lastSubject = baseSubject
|
||||||
|
prevThread = thread
|
||||||
}
|
}
|
||||||
|
if ml.drawRow(textWidth, ctx, thread.Uid, row, &needsHeaders, fmtCtx) {
|
||||||
|
break threadLoop
|
||||||
|
}
|
||||||
|
row += 1
|
||||||
}
|
}
|
||||||
fmtCtx := format.Ctx{
|
|
||||||
FromAddress: acct.acct.From,
|
|
||||||
AccountName: acct.Name(),
|
|
||||||
MsgInfo: msg,
|
|
||||||
MsgNum: row,
|
|
||||||
MsgIsMarked: store.Marker().IsMarked(t.Uid),
|
|
||||||
ThreadPrefix: prefix,
|
|
||||||
ThreadSameSubject: normalizedSubject == lastSubject,
|
|
||||||
}
|
|
||||||
if ml.drawRow(textWidth, ctx, t.Uid, row, &needsHeaders, fmtCtx) {
|
|
||||||
return types.ErrSkipThread
|
|
||||||
}
|
|
||||||
lastSubject = normalizedSubject
|
|
||||||
row++
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logging.Warnf("failed to walk threads: %v", err)
|
|
||||||
}
|
|
||||||
if row >= ctx.Height() {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,15 +153,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
uid := iter.Value().(uint32)
|
uid := iter.Value().(uint32)
|
||||||
|
fmtCtx := createBaseCtx(uid, row)
|
||||||
msg := store.Messages[uid]
|
|
||||||
fmtCtx := format.Ctx{
|
|
||||||
FromAddress: acct.acct.From,
|
|
||||||
AccountName: acct.Name(),
|
|
||||||
MsgInfo: msg,
|
|
||||||
MsgNum: row,
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -429,13 +426,17 @@ func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
|
||||||
uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg)
|
uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func threadPrefix(t *types.Thread) string {
|
func threadPrefix(t *types.Thread, reverse bool) string {
|
||||||
var arrow string
|
var arrow string
|
||||||
if t.Parent != nil {
|
if t.Parent != nil {
|
||||||
if t.NextSibling != nil {
|
if t.NextSibling != nil {
|
||||||
arrow = "├─>"
|
arrow = "├─>"
|
||||||
} else {
|
} else {
|
||||||
arrow = "└─>"
|
if reverse {
|
||||||
|
arrow = "┌─>"
|
||||||
|
} else {
|
||||||
|
arrow = "└─>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var prefix []string
|
var prefix []string
|
||||||
|
@ -458,3 +459,11 @@ func threadPrefix(t *types.Thread) string {
|
||||||
ps := strings.Join(prefix, "")
|
ps := strings.Join(prefix, "")
|
||||||
return fmt.Sprintf("%v%v", ps, arrow)
|
return fmt.Sprintf("%v%v", ps, arrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameParent(left, right *types.Thread) bool {
|
||||||
|
return left.Root() == right.Root()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isParent(t *types.Thread) bool {
|
||||||
|
return t == t.Root()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue