This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
7
4
[PATCH aerc 1/2] ui: enable showing of entire-thread
Add a UI config value to enable showing of an "entire thread", similar
to `notmuch show --entire-thread=true`. Add an associated style called
"msglist_borrowed" which can be used to style such messages.
Currently this feature is only supported by notmuch. It would be
possible for maildir to implement as well, IMAP with gmail custom
extensions, and JMAP. This patch merely implements the notmuch version
and puts the groundwork in for handling these sorts of displays.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
commands/commands_test.go | 1 +
config/aerc.conf | 8 ++++++++
config/style.go | 2 ++
config/templates.go | 1 +
config/ui.go | 1 +
lib/msgstore.go | 4 ++++
lib/state/templates.go | 10 ++++++++ --
models/templates.go | 1 +
widgets/account.go | 1 +
widgets/msglist.go | 14 ++++++++++ ----
worker/notmuch/lib/database.go | 19 ++++++++++++ -------
worker/notmuch/worker.go | 4 +++ -
worker/types/messages.go | 1 +
worker/types/thread.go | 4 ++++
14 files changed, 57 insertions(+), 14 deletions(-)
diff --git a/commands/commands_test.go b/commands/commands_test.go
index a1518aaa6690..418e34374799 100644
--- a/commands/commands_test.go
+++ b/commands/commands_test.go
@@ -77,6 +77,7 @@ func (d *dummyData) Header(string) string { return "" }
func (d *dummyData) ThreadPrefix() string { return "└─>" }
func (d *dummyData) ThreadCount() int { return 0 }
func (d *dummyData) ThreadFolded() bool { return false }
+ func (d *dummyData) ThreadBorrowed() bool { return false }
func (d *dummyData) Subject() string { return "Re: [PATCH] hey" }
func (d *dummyData) SubjectBase() string { return "[PATCH] hey" }
func (d *dummyData) Number() int { return 0 }
diff --git a/config/aerc.conf b/config/aerc.conf
index 46b24f1c9d93..eb6925633e03 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -290,6 +290,14 @@
# Default: false
#force-client-threads=false
+ # Show entire thread enables messages which do not match the current query (or
+ # belong to the current mailbox) to be shown for context. These messages can be
+ # styled separately using "msglist_borrowed" in a styleset. This feature is not
+ # supported by all backends
+ #
+ # Default: false
+ #entire-thread=false
+
# Debounce client-side thread building
#
# Default: 50ms
diff --git a/config/style.go b/config/style.go
index 50c53de328b5..67f608bdc6f4 100644
--- a/config/style.go
+++ b/config/style.go
@@ -41,6 +41,7 @@ const (
STYLE_MSGLIST_THREAD_FOLDED
STYLE_MSGLIST_GUTTER
STYLE_MSGLIST_PILL
+ STYLE_MSGLIST_BORROWED
STYLE_DIRLIST_DEFAULT
STYLE_DIRLIST_UNREAD
@@ -86,6 +87,7 @@ var StyleNames = map[string]StyleObject{
"msglist_pill": STYLE_MSGLIST_PILL,
"msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
+ "msglist_borrowed": STYLE_MSGLIST_BORROWED,
"dirlist_default": STYLE_DIRLIST_DEFAULT,
"dirlist_unread": STYLE_DIRLIST_UNREAD,
diff --git a/config/templates.go b/config/templates.go
index 3f0249dfccef..e81e42ac6c6c 100644
--- a/config/templates.go
+++ b/config/templates.go
@@ -78,6 +78,7 @@ func (d *dummyData) Header(string) string { return "" }
func (d *dummyData) ThreadPrefix() string { return "└─>" }
func (d *dummyData) ThreadCount() int { return 0 }
func (d *dummyData) ThreadFolded() bool { return false }
+ func (d *dummyData) ThreadBorrowed() bool { return true }
func (d *dummyData) Subject() string { return "Re: [PATCH] hey" }
func (d *dummyData) SubjectBase() string { return "[PATCH] hey" }
func (d *dummyData) Attach(string) string { return "" }
diff --git a/config/ui.go b/config/ui.go
index 699e8a0f66c4..7c0d52494cfd 100644
--- a/config/ui.go
+++ b/config/ui.go
@@ -41,6 +41,7 @@ type UIConfig struct {
ThreadingEnabled bool `ini:"threading-enabled"`
ForceClientThreads bool `ini:"force-client-threads"`
ClientThreadsDelay time.Duration `ini:"client-threads-delay" default:"50ms"`
+ EntireThread bool `ini:"entire-thread"`
FuzzyComplete bool `ini:"fuzzy-complete"`
NewMessageBell bool `ini:"new-message-bell" default:"true"`
Spinner string `ini:"spinner" default:"[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] "`
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 01b782898318..9cf99371001e 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -49,6 +49,7 @@ type MessageStore struct {
threadedView bool
reverseThreadOrder bool
+ entireThread bool
sortThreadSiblings bool
buildThreads bool
builder *ThreadBuilder
@@ -87,6 +88,7 @@ func NewMessageStore(worker *types.Worker,
reverseOrder bool, reverseThreadOrder bool, sortThreadSiblings bool,
triggerNewEmail func(*models.MessageInfo),
triggerDirectoryChange func(), onSelect func(*models.MessageInfo),
+ entireThread bool,
) *MessageStore {
if !worker.Backend.Capabilities().Thread {
clientThreads = true
@@ -104,6 +106,7 @@ func NewMessageStore(worker *types.Worker,
threadedView: thread,
buildThreads: clientThreads,
+ entireThread: entireThread,
reverseThreadOrder: reverseThreadOrder,
sortThreadSiblings: sortThreadSiblings,
@@ -831,6 +834,7 @@ func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func(types.W
Context: store.ctx,
SortCriteria: criteria,
FilterCriteria: store.filter,
+ EntireThread: store.entireThread,
}, handle_return)
} else {
store.worker.PostAction(&types.FetchDirectoryContents{
diff --git a/lib/state/templates.go b/lib/state/templates.go
index 22d065894d50..b10c1133c622 100644
--- a/lib/state/templates.go
+++ b/lib/state/templates.go
@@ -20,7 +20,7 @@ type DataSetter interface {
Data() models.TemplateData
SetHeaders(*mail.Header, *models.OriginalMail)
SetInfo(*models.MessageInfo, int, bool)
- SetThreading(string, bool, int, bool)
+ SetThreading(string, bool, int, bool, bool)
SetComposer(Composer)
SetAccount(*config.AccountConfig)
SetFolder(*models.Directory)
@@ -34,6 +34,7 @@ type ThreadInfo struct {
Prefix string
Count int
Folded bool
+ Borrowed bool
}
type templateData struct {
@@ -86,12 +87,13 @@ func (d *templateData) SetInfo(info *models.MessageInfo, num int, marked bool,
}
func (d *templateData) SetThreading(prefix string, same bool, count int,
- folded bool,
+ folded bool, borrowed bool,
) {
d.threadInfo.Prefix = prefix
d.threadInfo.SameSubject = same
d.threadInfo.Count = count
d.threadInfo.Folded = folded
+ d.threadInfo.Borrowed = borrowed
}
func (d *templateData) SetAccount(acct *config.AccountConfig) {
@@ -300,6 +302,10 @@ func (d *templateData) ThreadFolded() bool {
return d.threadInfo.Folded
}
+ func (d *templateData) ThreadBorrowed() bool {
+ return d.threadInfo.Borrowed
+ }
+
func (d *templateData) Subject() string {
var subject string
switch {
diff --git a/models/templates.go b/models/templates.go
index 0c684e863905..92cef2acb458 100644
--- a/models/templates.go
+++ b/models/templates.go
@@ -22,6 +22,7 @@ type TemplateData interface {
ThreadPrefix() string
ThreadCount() int
ThreadFolded() bool
+ ThreadBorrowed() bool
Subject() string
SubjectBase() string
Number() int
diff --git a/widgets/account.go b/widgets/account.go
index 47bc992427bc..b5c71c297e73 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -255,6 +255,7 @@ func (acct *AccountView) newStore(name string) *lib.MessageStore {
}
},
acct.updateSplitView,
+ acct.dirlist.UiConfig(name).EntireThread,
)
store.SetMarker(marker.New(store))
return store
diff --git a/widgets/msglist.go b/widgets/msglist.go
index dcb4cd31fb56..9371e4953d8e 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -209,8 +209,13 @@ func addMessage(
}
// folded thread
templateData, ok := data.(models.TemplateData)
- if ok && templateData.ThreadFolded() {
- params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_FOLDED)
+ if ok {
+ if templateData.ThreadFolded() {
+ params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_FOLDED)
+ }
+ if templateData.ThreadBorrowed() {
+ params.styles = append(params.styles, config.STYLE_MSGLIST_BORROWED)
+ }
}
// marked message
marked := store.Marker().IsMarked(msg.Uid)
@@ -476,7 +481,7 @@ func newThreadView(store *lib.MessageStore) *threadView {
}
func (t *threadView) Update(data state.DataSetter, uid uint32) {
- prefix, same, count, folded := "", false, 0, false
+ prefix, same, count, folded, brwd := "", false, 0, false, false
thread, err := t.store.Thread(uid)
if thread != nil && err == nil {
prefix = threadPrefix(thread, t.reverse, true)
@@ -486,6 +491,7 @@ func (t *threadView) Update(data state.DataSetter, uid uint32) {
t.prevSubj = subject
count = countThreads(thread)
folded = thread.FirstChild != nil && thread.FirstChild.Hidden
+ brwd = thread.Borrowed
}
- data.SetThreading(prefix, same, count, folded)
+ data.SetThreading(prefix, same, count, folded, brwd)
}
diff --git a/worker/notmuch/lib/database.go b/worker/notmuch/lib/database.go
index 9a6689c4b57b..238de2147808 100644
--- a/worker/notmuch/lib/database.go
+++ b/worker/notmuch/lib/database.go
@@ -111,7 +111,7 @@ func (db *DB) MsgIDsFromQuery(ctx context.Context, q string) ([]string, error) {
return msgIDs, err
}
- func (db *DB) ThreadsFromQuery(ctx context.Context, q string) ([]*types.Thread, error) {
+ func (db *DB) ThreadsFromQuery(ctx context.Context, q string, entireThread bool) ([]*types.Thread, error) {
query, err := db.newQuery(q)
if err != nil {
return nil, err
@@ -136,7 +136,7 @@ func (db *DB) ThreadsFromQuery(ctx context.Context, q string) ([]*types.Thread,
default:
thread := threads.Thread()
tlm := thread.TopLevelMessages()
- root := db.makeThread(nil, &tlm)
+ root := db.makeThread(nil, &tlm, entireThread)
res = append(res, root)
tlm.Close()
thread.Close()
@@ -306,7 +306,7 @@ func (db *DB) KeyFromUid(uid uint32) (string, bool) {
return db.uidStore.GetKey(uid)
}
- func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Thread {
+ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages, entireThread bool) *types.Thread {
var lastSibling *types.Thread
for msgs.Next() {
msg := msgs.Message()
@@ -319,14 +319,19 @@ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Th
}
replies := msg.Replies()
defer replies.Close()
- if !match {
- parent = db.makeThread(parent, &replies)
+ if !match && !entireThread {
+ parent = db.makeThread(parent, &replies, entireThread)
continue
}
node := &types.Thread{
Uid: db.uidStore.GetOrInsert(msgID),
Parent: parent,
- Hidden: !match,
+ }
+ switch entireThread {
+ case true:
+ node.Borrowed = !match
+ default:
+ node.Hidden = !match
}
if parent != nil && parent.FirstChild == nil {
parent.FirstChild = node
@@ -340,7 +345,7 @@ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Th
lastSibling.NextSibling = node
}
lastSibling = node
- db.makeThread(node, &replies)
+ db.makeThread(node, &replies, entireThread)
}
// We want to return the root node
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index d1eb69d011aa..809085c8ac74 100644
--- a/worker/notmuch/worker.go
+++ b/worker/notmuch/worker.go
@@ -635,6 +635,7 @@ func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
query := w.query
ctx := context.Background()
+ entireThread := false
if msg, ok := parent.(*types.FetchDirectoryThreaded); ok {
log.Debugf("filter input: '%v'", msg.FilterCriteria)
s, err := translate(msg.FilterCriteria)
@@ -646,8 +647,9 @@ func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
log.Debugf("filter query: '%s'", query)
}
ctx = msg.Context
+ entireThread = msg.EntireThread
}
- threads, err := w.db.ThreadsFromQuery(ctx, query)
+ threads, err := w.db.ThreadsFromQuery(ctx, query, entireThread)
if err != nil {
return err
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 26408684a4ba..ec3b1ed87335 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -115,6 +115,7 @@ type FetchDirectoryThreaded struct {
Context context.Context
SortCriteria []*SortCriterion
FilterCriteria []string
+ EntireThread bool
}
type SearchDirectory struct {
diff --git a/worker/types/thread.go b/worker/types/thread.go
index 2f739bc28ed3..f67c7f36c2eb 100644
--- a/worker/types/thread.go
+++ b/worker/types/thread.go
@@ -17,6 +17,10 @@ type Thread struct {
Hidden bool // if this flag is set the message isn't rendered in the UI
Deleted bool // if this flag is set the message was deleted
+
+ // Borrowed indicates the message doesn't match the mailbox / query but
+ // is displayed for context
+ Borrowed bool
}
// AddChild appends the child node at the end of the existing children of t.
--
2.42.0
[PATCH aerc 2/2] commands: add :toggle-entire-thread command
Add a command to toggle the display of an entire-thread. Update
CHANGELOG
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
CHANGELOG.md | 1 +
commands/msg/toggle-entire-thread.go | 36 ++++++++++++++++++++++++++++
doc/aerc.1.scd | 4 ++++
lib/msgstore.go | 8 +++++++
4 files changed, 49 insertions(+)
create mode 100644 commands/msg/toggle-entire-thread.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20a1697fbe66..5d2cbba7eeef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
temporary file from which selected files will be read instead of the standard
output.
- Save drafts in custom folders with `:postpone -t <folder>`.
+ - View "entire-thread" in notmuch backends with `:toggle-entire-thread`
### Fixed
diff --git a/commands/msg/toggle-entire-thread.go b/commands/msg/toggle-entire-thread.go
new file mode 100644
index 000000000000..8b23d6d541aa
--- /dev/null
+++ b/commands/msg/toggle-entire-thread.go
@@ -0,0 +1,36 @@
+ package msg
+
+ import (
+ "errors"
+
+ "git.sr.ht/~rjarry/aerc/lib/ui"
+ "git.sr.ht/~rjarry/aerc/widgets"
+ )
+
+ type ToggleEntireThread struct{}
+
+ func init() {
+ register(ToggleEntireThread{})
+ }
+
+ func (ToggleEntireThread) Aliases() []string {
+ return []string{"toggle-entire-thread"}
+ }
+
+ func (ToggleEntireThread) Complete(aerc *widgets.Aerc, args []string) []string {
+ return nil
+ }
+
+ func (ToggleEntireThread) Execute(aerc *widgets.Aerc, args []string) error {
+ if len(args) != 1 {
+ return errors.New("Usage: toggle-entire-thread")
+ }
+ h := newHelper(aerc)
+ store, err := h.store()
+ if err != nil {
+ return err
+ }
+ store.ToggleEntireThread()
+ ui.Invalidate()
+ return nil
+ }
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 440cfc291679..e56443ea3c0b 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -469,6 +469,10 @@ message list, the message in the message viewer, etc).
*:toggle-threads*
Toggles between message threading and the normal message list.
+ *:toggle-entire-thread*
+ Toggles between showing entire thread (when supported) and only showing
+ messages which match the current query / mailbox.
+
*:view* [*-p*]++
*:view-message* [*-p*]
Opens the message viewer to display the selected message. If the peek
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 9cf99371001e..79148868a80b 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -442,6 +442,14 @@ func (store *MessageStore) ThreadedView() bool {
return store.threadedView
}
+ func (store *MessageStore) ToggleEntireThread() {
+ if !store.threadedView {
+ return
+ }
+ store.entireThread = !store.entireThread
+ store.Sort(store.sortCriteria, nil)
+ }
+
func (store *MessageStore) BuildThreads() bool {
return store.buildThreads
}
--
2.42.0
[aerc/patches] build success
This is great. Setting mgslist_borrowed.dim=true works nicely. Thanks!
Tested-By: inwit <inwit@sindominio.net >
On 07/09/2023, 18:45, Tim Culverhouse wrote:
> Add a UI config value to enable showing of an "entire thread", similar
> to `notmuch show --entire-thread=true`. Add an associated style called
> "msglist_borrowed" which can be used to style such messages.
>
> Currently this feature is only supported by notmuch. It would be
> possible for maildir to implement as well, IMAP with gmail custom
> extensions, and JMAP. This patch merely implements the notmuch version
> and puts the groundwork in for handling these sorts of displays.
>
> Signed-off-by: Tim Culverhouse <tim@timculverhouse.com >
> ---
> commands/commands_test.go | 1 +
> config/aerc.conf | 8 ++++++++
> config/style.go | 2 ++
> config/templates.go | 1 +
> config/ui.go | 1 +
> lib/msgstore.go | 4 ++++
> lib/state/templates.go | 10 ++++++++--
> models/templates.go | 1 +
> widgets/account.go | 1 +
> widgets/msglist.go | 14 ++++++++++----
> worker/notmuch/lib/database.go | 19 ++++++++++++-------
> worker/notmuch/worker.go | 4 +++-
> worker/types/messages.go | 1 +
> worker/types/thread.go | 4 ++++
> 14 files changed, 57 insertions(+), 14 deletions(-)
>
> diff --git a/commands/commands_test.go b/commands/commands_test.go
> index a1518aaa6690..418e34374799 100644
> --- a/commands/commands_test.go
> +++ b/commands/commands_test.go
> @@ -77,6 +77,7 @@ func (d *dummyData) Header(string) string { return "" }
> func (d *dummyData) ThreadPrefix() string { return "└─>" }
> func (d *dummyData) ThreadCount() int { return 0 }
> func (d *dummyData) ThreadFolded() bool { return false }
> +func (d *dummyData) ThreadBorrowed() bool { return false }
> func (d *dummyData) Subject() string { return "Re: [PATCH] hey" }
> func (d *dummyData) SubjectBase() string { return "[PATCH] hey" }
> func (d *dummyData) Number() int { return 0 }
> diff --git a/config/aerc.conf b/config/aerc.conf
> index 46b24f1c9d93..eb6925633e03 100644
> --- a/config/aerc.conf
> +++ b/config/aerc.conf
> @@ -290,6 +290,14 @@
> # Default: false
> #force-client-threads=false
>
> +# Show entire thread enables messages which do not match the current query (or
> +# belong to the current mailbox) to be shown for context. These messages can be
> +# styled separately using "msglist_borrowed" in a styleset. This feature is not
> +# supported by all backends
> +#
> +# Default: false
> +#entire-thread=false
> +
> # Debounce client-side thread building
> #
> # Default: 50ms
> diff --git a/config/style.go b/config/style.go
> index 50c53de328b5..67f608bdc6f4 100644
> --- a/config/style.go
> +++ b/config/style.go
> @@ -41,6 +41,7 @@ const (
> STYLE_MSGLIST_THREAD_FOLDED
> STYLE_MSGLIST_GUTTER
> STYLE_MSGLIST_PILL
> + STYLE_MSGLIST_BORROWED
>
> STYLE_DIRLIST_DEFAULT
> STYLE_DIRLIST_UNREAD
> @@ -86,6 +87,7 @@ var StyleNames = map[string]StyleObject{
> "msglist_pill": STYLE_MSGLIST_PILL,
>
> "msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
> + "msglist_borrowed": STYLE_MSGLIST_BORROWED,
>
> "dirlist_default": STYLE_DIRLIST_DEFAULT,
> "dirlist_unread": STYLE_DIRLIST_UNREAD,
> diff --git a/config/templates.go b/config/templates.go
> index 3f0249dfccef..e81e42ac6c6c 100644
> --- a/config/templates.go
> +++ b/config/templates.go
> @@ -78,6 +78,7 @@ func (d *dummyData) Header(string) string { return "" }
> func (d *dummyData) ThreadPrefix() string { return "└─>" }
> func (d *dummyData) ThreadCount() int { return 0 }
> func (d *dummyData) ThreadFolded() bool { return false }
> +func (d *dummyData) ThreadBorrowed() bool { return true }
> func (d *dummyData) Subject() string { return "Re: [PATCH] hey" }
> func (d *dummyData) SubjectBase() string { return "[PATCH] hey" }
> func (d *dummyData) Attach(string) string { return "" }
> diff --git a/config/ui.go b/config/ui.go
> index 699e8a0f66c4..7c0d52494cfd 100644
> --- a/config/ui.go
> +++ b/config/ui.go
> @@ -41,6 +41,7 @@ type UIConfig struct {
> ThreadingEnabled bool `ini:"threading-enabled"`
> ForceClientThreads bool `ini:"force-client-threads"`
> ClientThreadsDelay time.Duration `ini:"client-threads-delay" default:"50ms"`
> + EntireThread bool `ini:"entire-thread"`
> FuzzyComplete bool `ini:"fuzzy-complete"`
> NewMessageBell bool `ini:"new-message-bell" default:"true"`
> Spinner string `ini:"spinner" default:"[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] "`
> diff --git a/lib/msgstore.go b/lib/msgstore.go
> index 01b782898318..9cf99371001e 100644
> --- a/lib/msgstore.go
> +++ b/lib/msgstore.go
> @@ -49,6 +49,7 @@ type MessageStore struct {
>
> threadedView bool
> reverseThreadOrder bool
> + entireThread bool
> sortThreadSiblings bool
> buildThreads bool
> builder *ThreadBuilder
> @@ -87,6 +88,7 @@ func NewMessageStore(worker *types.Worker,
> reverseOrder bool, reverseThreadOrder bool, sortThreadSiblings bool,
> triggerNewEmail func(*models.MessageInfo),
> triggerDirectoryChange func(), onSelect func(*models.MessageInfo),
> + entireThread bool,
> ) *MessageStore {
> if !worker.Backend.Capabilities().Thread {
> clientThreads = true
> @@ -104,6 +106,7 @@ func NewMessageStore(worker *types.Worker,
>
> threadedView: thread,
> buildThreads: clientThreads,
> + entireThread: entireThread,
> reverseThreadOrder: reverseThreadOrder,
> sortThreadSiblings: sortThreadSiblings,
>
> @@ -831,6 +834,7 @@ func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func(types.W
> Context: store.ctx,
> SortCriteria: criteria,
> FilterCriteria: store.filter,
> + EntireThread: store.entireThread,
> }, handle_return)
> } else {
> store.worker.PostAction(&types.FetchDirectoryContents{
> diff --git a/lib/state/templates.go b/lib/state/templates.go
> index 22d065894d50..b10c1133c622 100644
> --- a/lib/state/templates.go
> +++ b/lib/state/templates.go
> @@ -20,7 +20,7 @@ type DataSetter interface {
> Data() models.TemplateData
> SetHeaders(*mail.Header, *models.OriginalMail)
> SetInfo(*models.MessageInfo, int, bool)
> - SetThreading(string, bool, int, bool)
> + SetThreading(string, bool, int, bool, bool)
> SetComposer(Composer)
> SetAccount(*config.AccountConfig)
> SetFolder(*models.Directory)
> @@ -34,6 +34,7 @@ type ThreadInfo struct {
> Prefix string
> Count int
> Folded bool
> + Borrowed bool
> }
>
> type templateData struct {
> @@ -86,12 +87,13 @@ func (d *templateData) SetInfo(info *models.MessageInfo, num int, marked bool,
> }
>
> func (d *templateData) SetThreading(prefix string, same bool, count int,
> - folded bool,
> + folded bool, borrowed bool,
> ) {
> d.threadInfo.Prefix = prefix
> d.threadInfo.SameSubject = same
> d.threadInfo.Count = count
> d.threadInfo.Folded = folded
> + d.threadInfo.Borrowed = borrowed
> }
>
> func (d *templateData) SetAccount(acct *config.AccountConfig) {
> @@ -300,6 +302,10 @@ func (d *templateData) ThreadFolded() bool {
> return d.threadInfo.Folded
> }
>
> +func (d *templateData) ThreadBorrowed() bool {
> + return d.threadInfo.Borrowed
> +}
> +
> func (d *templateData) Subject() string {
> var subject string
> switch {
> diff --git a/models/templates.go b/models/templates.go
> index 0c684e863905..92cef2acb458 100644
> --- a/models/templates.go
> +++ b/models/templates.go
> @@ -22,6 +22,7 @@ type TemplateData interface {
> ThreadPrefix() string
> ThreadCount() int
> ThreadFolded() bool
> + ThreadBorrowed() bool
> Subject() string
> SubjectBase() string
> Number() int
> diff --git a/widgets/account.go b/widgets/account.go
> index 47bc992427bc..b5c71c297e73 100644
> --- a/widgets/account.go
> +++ b/widgets/account.go
> @@ -255,6 +255,7 @@ func (acct *AccountView) newStore(name string) *lib.MessageStore {
> }
> },
> acct.updateSplitView,
> + acct.dirlist.UiConfig(name).EntireThread,
> )
> store.SetMarker(marker.New(store))
> return store
> diff --git a/widgets/msglist.go b/widgets/msglist.go
> index dcb4cd31fb56..9371e4953d8e 100644
> --- a/widgets/msglist.go
> +++ b/widgets/msglist.go
> @@ -209,8 +209,13 @@ func addMessage(
> }
> // folded thread
> templateData, ok := data.(models.TemplateData)
> - if ok && templateData.ThreadFolded() {
> - params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_FOLDED)
> + if ok {
> + if templateData.ThreadFolded() {
> + params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_FOLDED)
> + }
> + if templateData.ThreadBorrowed() {
> + params.styles = append(params.styles, config.STYLE_MSGLIST_BORROWED)
> + }
> }
> // marked message
> marked := store.Marker().IsMarked(msg.Uid)
> @@ -476,7 +481,7 @@ func newThreadView(store *lib.MessageStore) *threadView {
> }
>
> func (t *threadView) Update(data state.DataSetter, uid uint32) {
> - prefix, same, count, folded := "", false, 0, false
> + prefix, same, count, folded, brwd := "", false, 0, false, false
> thread, err := t.store.Thread(uid)
> if thread != nil && err == nil {
> prefix = threadPrefix(thread, t.reverse, true)
> @@ -486,6 +491,7 @@ func (t *threadView) Update(data state.DataSetter, uid uint32) {
> t.prevSubj = subject
> count = countThreads(thread)
> folded = thread.FirstChild != nil && thread.FirstChild.Hidden
> + brwd = thread.Borrowed
> }
> - data.SetThreading(prefix, same, count, folded)
> + data.SetThreading(prefix, same, count, folded, brwd)
> }
> diff --git a/worker/notmuch/lib/database.go b/worker/notmuch/lib/database.go
> index 9a6689c4b57b..238de2147808 100644
> --- a/worker/notmuch/lib/database.go
> +++ b/worker/notmuch/lib/database.go
> @@ -111,7 +111,7 @@ func (db *DB) MsgIDsFromQuery(ctx context.Context, q string) ([]string, error) {
> return msgIDs, err
> }
>
> -func (db *DB) ThreadsFromQuery(ctx context.Context, q string) ([]*types.Thread, error) {
> +func (db *DB) ThreadsFromQuery(ctx context.Context, q string, entireThread bool) ([]*types.Thread, error) {
> query, err := db.newQuery(q)
> if err != nil {
> return nil, err
> @@ -136,7 +136,7 @@ func (db *DB) ThreadsFromQuery(ctx context.Context, q string) ([]*types.Thread,
> default:
> thread := threads.Thread()
> tlm := thread.TopLevelMessages()
> - root := db.makeThread(nil, &tlm)
> + root := db.makeThread(nil, &tlm, entireThread)
> res = append(res, root)
> tlm.Close()
> thread.Close()
> @@ -306,7 +306,7 @@ func (db *DB) KeyFromUid(uid uint32) (string, bool) {
> return db.uidStore.GetKey(uid)
> }
>
> -func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Thread {
> +func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages, entireThread bool) *types.Thread {
> var lastSibling *types.Thread
> for msgs.Next() {
> msg := msgs.Message()
> @@ -319,14 +319,19 @@ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Th
> }
> replies := msg.Replies()
> defer replies.Close()
> - if !match {
> - parent = db.makeThread(parent, &replies)
> + if !match && !entireThread {
> + parent = db.makeThread(parent, &replies, entireThread)
> continue
> }
> node := &types.Thread{
> Uid: db.uidStore.GetOrInsert(msgID),
> Parent: parent,
> - Hidden: !match,
> + }
> + switch entireThread {
> + case true:
> + node.Borrowed = !match
> + default:
> + node.Hidden = !match
> }
> if parent != nil && parent.FirstChild == nil {
> parent.FirstChild = node
> @@ -340,7 +345,7 @@ func (db *DB) makeThread(parent *types.Thread, msgs *notmuch.Messages) *types.Th
> lastSibling.NextSibling = node
> }
> lastSibling = node
> - db.makeThread(node, &replies)
> + db.makeThread(node, &replies, entireThread)
> }
>
> // We want to return the root node
> diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
> index d1eb69d011aa..809085c8ac74 100644
> --- a/worker/notmuch/worker.go
> +++ b/worker/notmuch/worker.go
> @@ -635,6 +635,7 @@ func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
> func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
> query := w.query
> ctx := context.Background()
> + entireThread := false
> if msg, ok := parent.(*types.FetchDirectoryThreaded); ok {
> log.Debugf("filter input: '%v'", msg.FilterCriteria)
> s, err := translate(msg.FilterCriteria)
> @@ -646,8 +647,9 @@ func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
> log.Debugf("filter query: '%s'", query)
> }
> ctx = msg.Context
> + entireThread = msg.EntireThread
> }
> - threads, err := w.db.ThreadsFromQuery(ctx, query)
> + threads, err := w.db.ThreadsFromQuery(ctx, query, entireThread)
> if err != nil {
> return err
> }
> diff --git a/worker/types/messages.go b/worker/types/messages.go
> index 26408684a4ba..ec3b1ed87335 100644
> --- a/worker/types/messages.go
> +++ b/worker/types/messages.go
> @@ -115,6 +115,7 @@ type FetchDirectoryThreaded struct {
> Context context.Context
> SortCriteria []*SortCriterion
> FilterCriteria []string
> + EntireThread bool
> }
>
> type SearchDirectory struct {
> diff --git a/worker/types/thread.go b/worker/types/thread.go
> index 2f739bc28ed3..f67c7f36c2eb 100644
> --- a/worker/types/thread.go
> +++ b/worker/types/thread.go
> @@ -17,6 +17,10 @@ type Thread struct {
>
> Hidden bool // if this flag is set the message isn't rendered in the UI
> Deleted bool // if this flag is set the message was deleted
> +
> + // Borrowed indicates the message doesn't match the mailbox / query but
> + // is displayed for context
> + Borrowed bool
> }
>
> // AddChild appends the child node at the end of the existing children of t.
> --
> 2.42.0
Tim Culverhouse, Sep 07, 2023 at 18:50:
> Add a UI config value to enable showing of an "entire thread", similar
> to `notmuch show --entire-thread=true`. Add an associated style called
> "msglist_borrowed" which can be used to style such messages.
>
> Currently this feature is only supported by notmuch. It would be
> possible for maildir to implement as well, IMAP with gmail custom
> extensions, and JMAP. This patch merely implements the notmuch version
> and puts the groundwork in for handling these sorts of displays.
>
> Signed-off-by: Tim Culverhouse <tim@timculverhouse.com >
Hey Tim,
This is nice, I hope we'll get this for other backends soon(tm) :)
> diff --git a/config/aerc.conf b/config/aerc.conf
> index 46b24f1c9d93..eb6925633e03 100644
> --- a/config/aerc.conf
> +++ b/config/aerc.conf
> @@ -290,6 +290,14 @@
> # Default: false
> #force-client-threads=false
>
> +# Show entire thread enables messages which do not match the current query (or
> +# belong to the current mailbox) to be shown for context. These messages can be
> +# styled separately using "msglist_borrowed" in a styleset. This feature is not
> +# supported by all backends
> +#
> +# Default: false
> +#entire-thread=false
> +
How about "show-thread-context" instead to match the style object name
(see below)?
Also, you forgot to add this in aerc-config(5).
> # Debounce client-side thread building
> #
> # Default: 50ms
> diff --git a/config/style.go b/config/style.go
> index 50c53de328b5..67f608bdc6f4 100644
> --- a/config/style.go
> +++ b/config/style.go
> @@ -41,6 +41,7 @@ const (
> STYLE_MSGLIST_THREAD_FOLDED
> STYLE_MSGLIST_GUTTER
> STYLE_MSGLIST_PILL
> + STYLE_MSGLIST_BORROWED
>
> STYLE_DIRLIST_DEFAULT
> STYLE_DIRLIST_UNREAD
> @@ -86,6 +87,7 @@ var StyleNames = map[string]StyleObject{
> "msglist_pill": STYLE_MSGLIST_PILL,
>
> "msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
> + "msglist_borrowed": STYLE_MSGLIST_BORROWED,
The "borrowed" term feels weird. How about msglist_thread_context
instead?
On 19/09/2023, 23:19, Robin Jarry wrote:
> How about "show-thread-context" instead to match the style object name
> (see below)?
Wouldn't it be better "show-message-context"? Since the command really applies to messages and not threads and given that, in notmuch, a thread is an auto-contained object, "thread-context" sounds a bit weird to me.
Also, currently the command applies to all the listed messages in current folder/query: either we use "show-messages-context", in plural, or we change the behaviour of the command to apply to just the selected message. The later feels more natural to me, since one is generally focusing on a single thread when in need for drilling (maybe "drill" could be a short synonym?).
> The "borrowed" term feels weird. How about msglist_thread_context
> instead?
I'd say the same: msglist_message_context.
m2¢
On Wed Sep 20, 2023 at 2:02 AM CDT, inwit wrote:
> On 19/09/2023, 23:19, Robin Jarry wrote:
> > How about "show-thread-context" instead to match the style object name (see
> > below)?
> Wouldn't it be better "show-message-context"? Since the command really applies
> to messages and not threads and given that, in notmuch, a thread is an
> auto-contained object, "thread-context" sounds a bit weird to me.
I prefer show-thread-context, this view only exists in a threaded view...this
is also more closely aligned to notmuch terminology ("show-entire-thread").
> Also, currently the command applies to all the listed messages in current
> folder/query: either we use "show-messages-context", in plural, or we change
> the behaviour of the command to apply to just the selected message. The later
> feels more natural to me, since one is generally focusing on a single thread
> when in need for drilling (maybe "drill" could be a short synonym?).
I still think "show-thread-context" is a better key. "show-thread-contexts"
if we wanted to pluralize. I think the command we should use if we implemented
showing a _single_ thread context is the same as notmuch: "show-entire-thread"
What do you think?
--
Tim
On 20/09/2023, 16:01, Tim Culverhouse wrote:
> What do you think?
Not entirely convinced, but I'm fine with your proposal. Let's go ahead with this! :)