~rjarry/aerc-devel

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] threadbuilder: show siblings even when no parent found

Details
Message ID
<20240610191202.304723-3-robin@jarry.cc>
DKIM signature
pass
Download raw message
Patch: +55 -29
From: Koni Marti <koni.marti@gmail.com>

Show all threading associations even when not all nodes are present.
Indicate if a thread is incomplete, i.e. misses a direct parent node.

Use the `msglist_thread_orphan.fg=red` styleobject in your stylesheet to
indicate whether a messsage has a missing parent.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
 app/msglist.go           | 20 ++++++++++++--------
 config/style.go          |  2 ++
 config/templates.go      |  1 +
 doc/aerc-stylesets.7.scd |  3 +++
 lib/state/templates.go   | 18 ++++++++----------
 lib/threadbuilder.go     | 36 +++++++++++++++++++++++++-----------
 models/templates.go      |  1 +
 worker/types/thread.go   |  3 +++
 8 files changed, 55 insertions(+), 29 deletions(-)

diff --git a/app/msglist.go b/app/msglist.go
index 3a6d4c06ecbb..5ccdf0ed10ad 100644
--- a/app/msglist.go
+++ b/app/msglist.go
@@ -245,6 +245,9 @@ func addMessage(
		if templateData.ThreadContext() {
			params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_CONTEXT)
		}
		if templateData.ThreadOrphan() {
			params.styles = append(params.styles, config.STYLE_MSGLIST_THREAD_ORPHAN)
		}
	}
	// marked message
	marked := store.Marker().IsMarked(msg.Uid)
@@ -571,18 +574,19 @@ func newThreadView(store *lib.MessageStore) *threadView {
}

func (t *threadView) Update(data state.DataSetter, uid uint32) {
	prefix, same, count, unread, folded, context := "", false, 0, 0, false, false
	thread, err := t.store.Thread(uid)
	info := state.ThreadInfo{}
	if thread != nil && err == nil {
		prefix = threadPrefix(thread, t.reverse, true)
		info.Prefix = threadPrefix(thread, t.reverse, true)
		subject := threadSubject(t.store, thread)
		same = subject == t.prevSubj && sameParent(thread, t.prev) && !isParent(thread)
		info.SameSubject = subject == t.prevSubj && sameParent(thread, t.prev) && !isParent(thread)
		t.prev = thread
		t.prevSubj = subject
		count = countThreads(thread)
		unread = unreadInThread(thread, t.store)
		folded = thread.FirstChild != nil && thread.FirstChild.Hidden != 0
		context = thread.Context
		info.Count = countThreads(thread)
		info.Unread = unreadInThread(thread, t.store)
		info.Folded = thread.FirstChild != nil && thread.FirstChild.Hidden != 0
		info.Context = thread.Context
		info.Orphan = thread.Parent != nil && thread.Parent.Hidden > 0 && thread.Hidden == 0
	}
	data.SetThreading(prefix, same, count, unread, folded, context)
	data.SetThreading(info)
}
diff --git a/config/style.go b/config/style.go
index 95e60616ce57..46c46378c408 100644
--- a/config/style.go
+++ b/config/style.go
@@ -42,6 +42,7 @@ const (
	STYLE_MSGLIST_GUTTER
	STYLE_MSGLIST_PILL
	STYLE_MSGLIST_THREAD_CONTEXT
	STYLE_MSGLIST_THREAD_ORPHAN

	STYLE_DIRLIST_DEFAULT
	STYLE_DIRLIST_UNREAD
@@ -92,6 +93,7 @@ var StyleNames = map[string]StyleObject{

	"msglist_thread_folded":  STYLE_MSGLIST_THREAD_FOLDED,
	"msglist_thread_context": STYLE_MSGLIST_THREAD_CONTEXT,
	"msglist_thread_orphan":  STYLE_MSGLIST_THREAD_ORPHAN,

	"dirlist_default": STYLE_DIRLIST_DEFAULT,
	"dirlist_unread":  STYLE_DIRLIST_UNREAD,
diff --git a/config/templates.go b/config/templates.go
index 5a38cd615452..4000b1fcf3cd 100644
--- a/config/templates.go
+++ b/config/templates.go
@@ -82,6 +82,7 @@ func (d *dummyData) ThreadCount() int                { return 0 }
func (d *dummyData) ThreadUnread() int               { return 0 }
func (d *dummyData) ThreadFolded() bool              { return false }
func (d *dummyData) ThreadContext() bool             { return true }
func (d *dummyData) ThreadOrphan() 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/doc/aerc-stylesets.7.scd b/doc/aerc-stylesets.7.scd
index bfe9dc052db3..d86d9f98a795 100644
--- a/doc/aerc-stylesets.7.scd
+++ b/doc/aerc-stylesets.7.scd
@@ -117,6 +117,8 @@ styling.
:  Visible messages that have folded thread children.
|  *msglist_thread_context*
:  The messages not matching the mailbox / query, displayed for context.
|  *msglist_thread_orphan*
:  Threaded messages that have a missing parent message.
|  *dirlist_default*
:  The default style for directories in the directory list.
|  *dirlist_unread*
@@ -287,6 +289,7 @@ The order that *msglist_\** styles are applied in is, from first to last:
. *msglist_result*
. *msglist_thread_folded*
. *msglist_thread_context*
. *msglist_thread_orphan*
. *msglist_marked*

So, the marked style will override all other msglist styles.
diff --git a/lib/state/templates.go b/lib/state/templates.go
index 67e76a9f54b8..e01462e2c149 100644
--- a/lib/state/templates.go
+++ b/lib/state/templates.go
@@ -28,7 +28,7 @@ type DataSetter interface {
	SetHeaders(*mail.Header, *models.OriginalMail)
	SetInfo(*models.MessageInfo, int, bool)
	SetVisual(bool)
	SetThreading(string, bool, int, int, bool, bool)
	SetThreading(ThreadInfo)
	SetComposer(Composer)
	SetAccount(*config.AccountConfig)
	SetFolder(*models.Directory)
@@ -44,6 +44,7 @@ type ThreadInfo struct {
	Unread      int
	Folded      bool
	Context     bool
	Orphan      bool
}

type templateData struct {
@@ -100,15 +101,8 @@ func (d *templateData) SetVisual(visual bool) {
	d.visual = visual
}

func (d *templateData) SetThreading(prefix string, same bool, count int,
	unread int, folded bool, context bool,
) {
	d.threadInfo.Prefix = prefix
	d.threadInfo.SameSubject = same
	d.threadInfo.Count = count
	d.threadInfo.Unread = unread
	d.threadInfo.Folded = folded
	d.threadInfo.Context = context
func (d *templateData) SetThreading(info ThreadInfo) {
	d.threadInfo = info
}

func (d *templateData) SetAccount(acct *config.AccountConfig) {
@@ -334,6 +328,10 @@ func (d *templateData) ThreadContext() bool {
	return d.threadInfo.Context
}

func (d *templateData) ThreadOrphan() bool {
	return d.threadInfo.Orphan
}

func (d *templateData) Subject() string {
	var subject string
	switch {
diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go
index c18ab3d03cbb..b7dae4310b5b 100644
--- a/lib/threadbuilder.go
+++ b/lib/threadbuilder.go
@@ -165,23 +165,30 @@ func (builder *ThreadBuilder) buildTree(c jwz.Threadable, parent *types.Thread,
		return
	}
	for node := c; node != nil; node = node.GetNext() {
		thread := parent
		if !node.IsDummy() {
			thread = builder.newThread(node, parent)
			if rootLevel {
				thread.NextSibling = parent.FirstChild
				parent.FirstChild = thread
			} else {
				parent.InsertCmp(thread, bigger)
			}
		thread := builder.newThread(node, parent, node.IsDummy())
		if rootLevel {
			thread.NextSibling = parent.FirstChild
			parent.FirstChild = thread
		} else {
			parent.InsertCmp(thread, bigger)
		}
		builder.buildTree(node.GetChild(), thread, bigger, node.IsDummy())
	}
}

func (builder *ThreadBuilder) newThread(c jwz.Threadable, parent *types.Thread) *types.Thread {
func (builder *ThreadBuilder) newThread(c jwz.Threadable, parent *types.Thread,
	hidden bool,
) *types.Thread {
	hide := 0
	if hidden {
		hide += 1
	}
	if threadable, ok := c.(*threadable); ok {
		return &types.Thread{Uid: threadable.MsgInfo.Uid, Parent: parent}
		return &types.Thread{
			Uid:    threadable.UID(),
			Parent: parent,
			Hidden: hide,
		}
	}
	return nil
}
@@ -297,6 +304,13 @@ func cleanRefs(m, irp string, refs []string) []string {
	return cleanRefs
}

func (t *threadable) UID() uint32 {
	if t.MsgInfo == nil {
		return 0
	}
	return t.MsgInfo.Uid
}

func (t *threadable) Subject() string {
	// deactivate threading by subject for now
	return ""
diff --git a/models/templates.go b/models/templates.go
index c9b5d5b3d4c9..757e934a2794 100644
--- a/models/templates.go
+++ b/models/templates.go
@@ -25,6 +25,7 @@ type TemplateData interface {
	ThreadUnread() int
	ThreadFolded() bool
	ThreadContext() bool
	ThreadOrphan() bool
	Subject() string
	SubjectBase() string
	Number() int
diff --git a/worker/types/thread.go b/worker/types/thread.go
index a79a0b2d7487..42565964d561 100644
--- a/worker/types/thread.go
+++ b/worker/types/thread.go
@@ -146,6 +146,9 @@ func getMaxUID(thread *Thread) uint32 {
	var Uid uint32

	_ = thread.Walk(func(t *Thread, _ int, currentErr error) error {
		if t.Deleted || t.Hidden > 0 {
			return nil
		}
		if t.Uid > Uid {
			Uid = t.Uid
		}
-- 
2.45.2

[PATCH aerc 2/2] threadbuilder: fallback to threading by subject

Details
Message ID
<20240610191202.304723-4-robin@jarry.cc>
In-Reply-To
<20240610191202.304723-3-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Patch: +9 -4
If no match were found in the References and In-Reply-To headers,
attempt threading by looking at subjects.

Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 lib/threadbuilder.go | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/lib/threadbuilder.go b/lib/threadbuilder.go
index b7dae4310b5b..32a608078b19 100644
--- a/lib/threadbuilder.go
+++ b/lib/threadbuilder.go
@@ -9,6 +9,7 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	sortthread "github.com/emersion/go-imap-sortthread"
	"github.com/gatherstars-com/jwz"
)

@@ -312,16 +313,20 @@ func (t *threadable) UID() uint32 {
}

func (t *threadable) Subject() string {
	// deactivate threading by subject for now
	return ""
	if t.MsgInfo == nil || t.MsgInfo.Envelope == nil {
		return ""
	}
	return t.MsgInfo.Envelope.Subject
}

func (t *threadable) SimplifiedSubject() string {
	return ""
	subject, _ := sortthread.GetBaseSubject(t.Subject())
	return subject
}

func (t *threadable) SubjectIsReply() bool {
	return false
	_, replyOrForward := sortthread.GetBaseSubject(t.Subject())
	return replyOrForward
}

func (t *threadable) SetNext(next jwz.Threadable) {
-- 
2.45.2

[aerc/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D1WKV9URZLBZ.1M2YY7UC6Q0XZ@fra02>
In-Reply-To
<20240610191202.304723-4-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
aerc/patches: SUCCESS in 2m1s

[threadbuilder: show siblings even when no parent found][0] from [Robin Jarry][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/53214
[1]: robin@jarry.cc

✓ #1247507 SUCCESS aerc/patches/alpine-edge.yml https://builds.sr.ht/~rjarry/job/1247507
✓ #1247508 SUCCESS aerc/patches/openbsd.yml     https://builds.sr.ht/~rjarry/job/1247508
Details
Message ID
<D1WLQP315ZKF.373OCGKDGLMQ4@cepl.eu>
In-Reply-To
<20240610191202.304723-3-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
On Mon Jun 10, 2024 at 9:12 PM CEST, Robin Jarry wrote:
> Show all threading associations even when not all nodes are present.
> Indicate if a thread is incomplete, i.e. misses a direct parent node.
>
> Use the `msglist_thread_orphan.fg=red` styleobject in your stylesheet to
> indicate whether a messsage has a missing parent.

Hmm, not yet: this is what I get [1] with this folder [2].

Thank you very much for the effort,

Matěj

[1] https://mcepl.fedorapeople.org/tmp/screenshot-2024-06-10_21-06-1718049297.png
[2] https://mcepl.fedorapeople.org/tmp/cpython.mbx
-- 
http://matej.ceplovi.cz/blog/, @mcepl@floss.social
GPG Finger: 3C76 A027 CA45 AD70 98B5  BC1D 7920 5802 880B C9D8
 
… one of the main causes of the fall of the Roman Empire was
that, lacking zero, they had no way to indicate successful
termination of their C programs.
  -- Robert Firth
Details
Message ID
<D1WLZOMXN6V3.1VYFG4L5Y9HNL@ringo>
In-Reply-To
<D1WLQP315ZKF.373OCGKDGLMQ4@cepl.eu> (view parent)
DKIM signature
pass
Download raw message
Matěj Cepl, Jun 10, 2024 at 21:59:
> On Mon Jun 10, 2024 at 9:12 PM CEST, Robin Jarry wrote:
> > Show all threading associations even when not all nodes are present.
> > Indicate if a thread is incomplete, i.e. misses a direct parent node.
> >
> > Use the `msglist_thread_orphan.fg=red` styleobject in your stylesheet to
> > indicate whether a messsage has a missing parent.
>
> Hmm, not yet: this is what I get [1] with this folder [2].

Weird, did you apply both patches of the series?

This is what I get: https://f.jarry.cc/p/cpython-threading.png
Details
Message ID
<D1WM7CW194HF.PO25V5Q0HNZ5@cepl.eu>
In-Reply-To
<D1WLZOMXN6V3.1VYFG4L5Y9HNL@ringo> (view parent)
DKIM signature
pass
Download raw message
On Mon Jun 10, 2024 at 10:11 PM CEST, Robin Jarry wrote:
> Weird, did you apply both patches of the series?
>
> This is what I get: https://f.jarry.cc/p/cpython-threading.png

This is the branch I build from https://git.cepl.eu/cgit/packaging/aerc/log/?h=devel

Matěj

-- 
http://matej.ceplovi.cz/blog/, @mcepl@floss.social
GPG Finger: 3C76 A027 CA45 AD70 98B5  BC1D 7920 5802 880B C9D8
 
Data matures like wine, applications like fish.
  -- James Governor
  http://redmonk.com/jgovernor/2007/04/05\
  /why-applications-are-like-fish-and-data-is-like-wine/
Details
Message ID
<D20V9N5GDV8F.291NER8VLHTE@sindominio.net>
In-Reply-To
<20240610191202.304723-3-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
Tested-By: inwit <inwit@sindominio.net>

Re: [PATCH aerc 2/2] threadbuilder: fallback to threading by subject

Details
Message ID
<D20VAGN45P0J.3I57KDRZWS1Z7@sindominio.net>
In-Reply-To
<20240610191202.304723-4-robin@jarry.cc> (view parent)
DKIM signature
pass
Download raw message
On 10/06/2024, 21:12, Robin Jarry wrote:
> If no match were found in the References and In-Reply-To headers,
> attempt threading by looking at subjects.

This might be tricky for repetitive messages (periodic automatic reports, for
instance). Maybe it should be optional?

Tested-By: inwit <inwit@sindominio.net>
Reply to thread Export thread (mbox)