aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Culverhouse <tim@timculverhouse.com>2023-09-25 09:07:43 -0500
committerRobin Jarry <robin@jarry.cc>2023-09-27 23:12:41 +0200
commit4ec1e1a5e4c74236a6d1992c8778c39fd25d847b (patch)
treeb6ade84273558cf6c03ae8e7b606c3d2edd998ab
parent439204d994e8ba9ab3045139bc716bee812f4029 (diff)
downloadaerc-4ec1e1a5e4c74236a6d1992c8778c39fd25d847b.tar.gz
aerc-4ec1e1a5e4c74236a6d1992c8778c39fd25d847b.zip
ui: enable showing of thread-context
Add a UI config value to enable showing of "thread-context", similar to `notmuch show --entire-thread=true`. Add an associated style called "msglist_thread_context" 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> Tested-by: Inwit <inwit@sindominio.net> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--commands/commands_test.go1
-rw-r--r--config/aerc.conf8
-rw-r--r--config/style.go4
-rw-r--r--config/templates.go1
-rw-r--r--config/ui.go1
-rw-r--r--doc/aerc-config.5.scd6
-rw-r--r--lib/msgstore.go4
-rw-r--r--lib/state/templates.go10
-rw-r--r--models/templates.go1
-rw-r--r--widgets/account.go1
-rw-r--r--widgets/msglist.go14
-rw-r--r--worker/notmuch/lib/database.go19
-rw-r--r--worker/notmuch/worker.go4
-rw-r--r--worker/types/messages.go1
-rw-r--r--worker/types/thread.go4
15 files changed, 64 insertions, 15 deletions
diff --git a/commands/commands_test.go b/commands/commands_test.go
index a1518aaa..7edd0228 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) ThreadContext() 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 00c8501f..4b4e7cb6 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -290,6 +290,14 @@
# Default: false
#force-client-threads=false
+# Show thread context 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_thread_context" in a styleset. This feature
+# is not supported by all backends
+#
+# Default: false
+#show-thread-context=false
+
# Debounce client-side thread building
#
# Default: 50ms
diff --git a/config/style.go b/config/style.go
index 3f117fc0..8a88dcfc 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_THREAD_CONTEXT
STYLE_DIRLIST_DEFAULT
STYLE_DIRLIST_UNREAD
@@ -89,7 +90,8 @@ var StyleNames = map[string]StyleObject{
"msglist_gutter": STYLE_MSGLIST_GUTTER,
"msglist_pill": STYLE_MSGLIST_PILL,
- "msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
+ "msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
+ "msglist_thread_context": STYLE_MSGLIST_THREAD_CONTEXT,
"dirlist_default": STYLE_DIRLIST_DEFAULT,
"dirlist_unread": STYLE_DIRLIST_UNREAD,
diff --git a/config/templates.go b/config/templates.go
index 3f0249df..e2adeac2 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) ThreadContext() 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 0479c13b..3b637278 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"`
+ ThreadContext bool `ini:"show-thread-context"`
FuzzyComplete bool `ini:"fuzzy-complete"`
NewMessageBell bool `ini:"new-message-bell" default:"true"`
Spinner string `ini:"spinner" default:"[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] "`
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index cace2beb..b6855c7a 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -431,6 +431,12 @@ These options are configured in the *[ui]* section of _aerc.conf_.
Default: _50ms_
+*show-thread-context* = _true_|_false_
+ Enable showing of thread context. Note: this is not supported by all
+ backends.
+
+ Default: _false_
+
## CONTEXTUAL UI CONFIGURATION
The UI configuration can be specialized for accounts, specific mail
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 01b78289..6186b195 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -49,6 +49,7 @@ type MessageStore struct {
threadedView bool
reverseThreadOrder bool
+ threadContext 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),
+ threadContext bool,
) *MessageStore {
if !worker.Backend.Capabilities().Thread {
clientThreads = true
@@ -104,6 +106,7 @@ func NewMessageStore(worker *types.Worker,
threadedView: thread,
buildThreads: clientThreads,
+ threadContext: threadContext,
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,
+ ThreadContext: store.threadContext,
}, handle_return)
} else {
store.worker.PostAction(&types.FetchDirectoryContents{
diff --git a/lib/state/templates.go b/lib/state/templates.go
index fbe5aa4a..88c5eeae 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
+ Context 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, context bool,
) {
d.threadInfo.Prefix = prefix
d.threadInfo.SameSubject = same
d.threadInfo.Count = count
d.threadInfo.Folded = folded
+ d.threadInfo.Context = context
}
func (d *templateData) SetAccount(acct *config.AccountConfig) {
@@ -300,6 +302,10 @@ func (d *templateData) ThreadFolded() bool {
return d.threadInfo.Folded
}
+func (d *templateData) ThreadContext() bool {
+ return d.threadInfo.Context
+}
+
func (d *templateData) Subject() string {
var subject string
switch {
diff --git a/models/templates.go b/models/templates.go
index 0c684e86..ac6d410a 100644
--- a/models/templates.go
+++ b/models/templates.go
@@ -22,6 +22,7 @@ type TemplateData interface {
ThreadPrefix() string
ThreadCount() int
ThreadFolded() bool
+ ThreadContext() bool
Subject() string
SubjectBase() string
Number() int
diff --git a/widgets/account.go b/widgets/account.go
index 982d75f8..7e380996 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).ThreadContext,
)
store.SetMarker(marker.New(store))
return store
diff --git a/widgets/msglist.go b/widgets/msglist.go
index dcb4cd31..3187b5d5 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.ThreadContext() {
+ params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_CONTEXT)
+ }
}
// 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, context := "", 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
+ context = thread.Context
}
- data.SetThreading(prefix, same, count, folded)
+ data.SetThreading(prefix, same, count, folded, context)
}
diff --git a/worker/notmuch/lib/database.go b/worker/notmuch/lib/database.go
index 9a6689c4..e7914156 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, threadContext 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 && !threadContext {
+ parent = db.makeThread(parent, &replies, threadContext)
continue
}
node := &types.Thread{
Uid: db.uidStore.GetOrInsert(msgID),
Parent: parent,
- Hidden: !match,
+ }
+ switch threadContext {
+ case true:
+ node.Context = !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, threadContext)
}
// We want to return the root node
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index d1eb69d0..2f601528 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()
+ threadContext := 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
+ threadContext = msg.ThreadContext
}
- threads, err := w.db.ThreadsFromQuery(ctx, query)
+ threads, err := w.db.ThreadsFromQuery(ctx, query, threadContext)
if err != nil {
return err
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 26408684..7cab9a7a 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
+ ThreadContext bool
}
type SearchDirectory struct {
diff --git a/worker/types/thread.go b/worker/types/thread.go
index 2f739bc2..75651280 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
+
+ // Context indicates the message doesn't match the mailbox / query but
+ // is displayed for context
+ Context bool
}
// AddChild appends the child node at the end of the existing children of t.