Compare commits
No commits in common. "14ceca320065656ea31994191f9e74d254a72e04" and "206665a2d97106722a6b32e24a504863040ca515" have entirely different histories.
14ceca3200
...
206665a2d9
11 changed files with 105 additions and 180 deletions
|
@ -12,8 +12,6 @@ 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,9 +10,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.sr.ht/~rjarry/aerc/logging"
|
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,10 +71,6 @@ 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.
|
||||||
|
@ -94,8 +88,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -107,12 +99,6 @@ 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 != "" {
|
||||||
|
@ -162,38 +148,27 @@ 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 i := 0; i < maxCompletionLines; i++ {
|
for {
|
||||||
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 {
|
||||||
logging.Warnf(
|
return nil, err
|
||||||
"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 {
|
||||||
logging.Warnf(
|
return nil, fmt.Errorf("could not decode MIME string: %w", err)
|
||||||
"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,14 +194,6 @@ 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,8 +79,7 @@ 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,14 +335,6 @@ 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,9 +21,6 @@ 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,10 +39,9 @@ type MessageStore struct {
|
||||||
|
|
||||||
sortCriteria []*types.SortCriterion
|
sortCriteria []*types.SortCriterion
|
||||||
|
|
||||||
threadedView bool
|
threadedView bool
|
||||||
reverseThreadOrder bool
|
buildThreads bool
|
||||||
buildThreads bool
|
builder *ThreadBuilder
|
||||||
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
|
||||||
|
@ -75,7 +74,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, reverseThreadOrder bool,
|
reverseOrder bool,
|
||||||
triggerNewEmail func(*models.MessageInfo),
|
triggerNewEmail func(*models.MessageInfo),
|
||||||
triggerDirectoryChange func(),
|
triggerDirectoryChange func(),
|
||||||
) *MessageStore {
|
) *MessageStore {
|
||||||
|
@ -92,9 +91,8 @@ 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,
|
||||||
|
@ -233,7 +231,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, store.reverseThreadOrder)
|
builder.RebuildUids(msg.Threads)
|
||||||
store.uids = builder.Uids()
|
store.uids = builder.Uids()
|
||||||
store.threads = msg.Threads
|
store.threads = msg.Threads
|
||||||
|
|
||||||
|
@ -366,14 +364,6 @@ 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 {
|
||||||
|
@ -434,7 +424,7 @@ func (store *MessageStore) runThreadBuilderNow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// build new threads
|
// build new threads
|
||||||
th := store.builder.Threads(store.uids, store.reverseThreadOrder)
|
th := store.builder.Threads(store.uids)
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -603,20 +593,29 @@ 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)
|
||||||
|
|
||||||
newIdx := store.FindIndexByUid(store.SelectedUid())
|
uid := 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(
|
|
||||||
newIdx,
|
low, high := iter.EndIndex(), iter.StartIndex()
|
||||||
delta,
|
sign := -1
|
||||||
iter,
|
if high < low {
|
||||||
iterator.FixBounds,
|
low, high = high, low
|
||||||
)
|
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() {
|
||||||
|
@ -632,7 +631,15 @@ 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() {
|
||||||
|
@ -683,35 +690,18 @@ 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
|
||||||
}
|
}
|
||||||
iter := store.iterFactory.NewIterator(store.results)
|
store.resultIndex += delta
|
||||||
if store.resultIndex < 0 {
|
if store.resultIndex >= len(store.results) {
|
||||||
store.resultIndex = iter.StartIndex()
|
store.resultIndex = 0
|
||||||
} else {
|
|
||||||
store.resultIndex = iterator.MoveIndex(
|
|
||||||
store.resultIndex,
|
|
||||||
delta,
|
|
||||||
iter,
|
|
||||||
iterator.WrapBounds,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
store.Select(store.results[store.resultIndex])
|
if store.resultIndex < 0 {
|
||||||
|
store.resultIndex = len(store.results) - 1
|
||||||
|
}
|
||||||
|
store.Select(store.results[len(store.results)-store.resultIndex-1])
|
||||||
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, inverse bool) []*types.Thread {
|
func (builder *ThreadBuilder) Threads(uids []uint32) []*types.Thread {
|
||||||
builder.Lock()
|
builder.Lock()
|
||||||
defer builder.Unlock()
|
defer builder.Unlock()
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (builder *ThreadBuilder) Threads(uids []uint32, inverse bool) []*types.Thre
|
||||||
builder.sortThreads(threads, uids)
|
builder.sortThreads(threads, uids)
|
||||||
|
|
||||||
// rebuild uids from threads
|
// rebuild uids from threads
|
||||||
builder.RebuildUids(threads, inverse)
|
builder.RebuildUids(threads)
|
||||||
|
|
||||||
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,23 +155,15 @@ 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, inverse bool) {
|
func (builder *ThreadBuilder) RebuildUids(threads []*types.Thread) {
|
||||||
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 {
|
||||||
threaduids = append(threaduids, t.Uid)
|
uids = append(uids, 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,7 +286,6 @@ 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, false), getFlag(node), elems[countLevels(node)])
|
return fmt.Sprintf("%s%s%s", threadPrefix(node), getFlag(node), elems[countLevels(node)])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dt *DirectoryTree) getDirectory(node *types.Thread) string {
|
func (dt *DirectoryTree) getDirectory(node *types.Thread) string {
|
||||||
|
|
|
@ -12,7 +12,6 @@ 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"
|
||||||
|
@ -89,61 +88,57 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
|
||||||
row int = 0
|
row int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
createBaseCtx := func(uid uint32, row int) format.Ctx {
|
|
||||||
return format.Ctx{
|
|
||||||
FromAddress: acct.acct.From,
|
|
||||||
AccountName: acct.Name(),
|
|
||||||
MsgInfo: store.Messages[uid],
|
|
||||||
MsgNum: row,
|
|
||||||
MsgIsMarked: store.Marker().IsMarked(uid),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if store.ThreadedView() {
|
if store.ThreadedView() {
|
||||||
var (
|
iter := store.ThreadsIterator()
|
||||||
lastSubject string
|
var i int = 0
|
||||||
prevThread *types.Thread
|
|
||||||
i int = 0
|
for iter.Next() {
|
||||||
)
|
thread := iter.Value().(*types.Thread)
|
||||||
factory := iterator.NewFactory(!store.ReverseThreadOrder())
|
var lastSubject string
|
||||||
threadLoop:
|
err := thread.Walk(func(t *types.Thread, _ int, currentErr error) error {
|
||||||
for iter := store.ThreadsIterator(); iter.Next(); {
|
if currentErr != nil {
|
||||||
var cur []*types.Thread
|
return currentErr
|
||||||
err := iter.Value().(*types.Thread).Walk(
|
}
|
||||||
func(t *types.Thread, _ int, _ error,
|
if t.Hidden || t.Deleted {
|
||||||
) 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++
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
if thread := curIter.Value().(*types.Thread); thread != nil {
|
i++
|
||||||
fmtCtx := createBaseCtx(thread.Uid, row)
|
msg := store.Messages[t.Uid]
|
||||||
fmtCtx.ThreadPrefix = threadPrefix(thread,
|
var prefix string
|
||||||
store.ReverseThreadOrder())
|
var subject string
|
||||||
if fmtCtx.MsgInfo != nil && fmtCtx.MsgInfo.Envelope != nil {
|
var normalizedSubject string
|
||||||
baseSubject, _ := sortthread.GetBaseSubject(
|
if msg != nil {
|
||||||
fmtCtx.MsgInfo.Envelope.Subject)
|
prefix = threadPrefix(t)
|
||||||
fmtCtx.ThreadSameSubject = baseSubject == lastSubject &&
|
if msg.Envelope != nil {
|
||||||
sameParent(thread, prevThread) &&
|
subject = msg.Envelope.Subject
|
||||||
!isParent(thread)
|
normalizedSubject, _ = sortthread.GetBaseSubject(subject)
|
||||||
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 {
|
||||||
|
@ -153,7 +148,15 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -426,17 +429,13 @@ 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, reverse bool) string {
|
func threadPrefix(t *types.Thread) 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 {
|
||||||
if reverse {
|
arrow = "└─>"
|
||||||
arrow = "┌─>"
|
|
||||||
} else {
|
|
||||||
arrow = "└─>"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var prefix []string
|
var prefix []string
|
||||||
|
@ -459,11 +458,3 @@ func threadPrefix(t *types.Thread, reverse bool) 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