Merge branch 'master' of github.com:djcb/mu

This commit is contained in:
djcb
2014-09-01 20:13:57 -07:00
46 changed files with 617 additions and 141 deletions

View File

@ -52,6 +52,7 @@ mu_container_new (MuMsg *msg, guint docid, const char *msgid)
if (msg) if (msg)
c->msg = mu_msg_ref (msg); c->msg = mu_msg_ref (msg);
c->leader = c;
c->docid = docid; c->docid = docid;
c->msgid = msgid; c->msgid = msgid;
@ -263,9 +264,24 @@ mu_container_foreach (MuContainer *c, MuContainerForeachFunc func,
return func (c, user_data); return func (c, user_data);
} }
MuContainer*
mu_container_splice_children (MuContainer *c, MuContainer *sibling)
{
MuContainer *children;
g_return_val_if_fail (c, NULL);
g_return_val_if_fail (sibling, NULL);
children = sibling->child;
sibling->child = NULL;
c = mu_container_remove_sibling (c, sibling);
return mu_container_append_siblings (c, children);
}
MuContainer* MuContainer*
mu_container_splice_children (MuContainer *parent, MuContainer *child) mu_container_splice_grandchildren (MuContainer *parent, MuContainer *child)
{ {
MuContainer *newchild; MuContainer *newchild;
@ -293,17 +309,28 @@ mu_container_to_list (MuContainer *c)
return lst; return lst;
} }
static gpointer
list_last_data (GSList *lst)
{
GSList *tail;
tail = g_slist_last (lst);
return tail->data;
}
static MuContainer* static MuContainer*
mu_container_from_list (GSList *lst) mu_container_from_list (GSList *lst)
{ {
MuContainer *c, *cur; MuContainer *c, *cur, *tail;
if (!lst) if (!lst)
return NULL; return NULL;
tail = list_last_data (lst);
for (c = cur = (MuContainer*)lst->data; cur; lst = g_slist_next(lst)) { for (c = cur = (MuContainer*)lst->data; cur; lst = g_slist_next(lst)) {
cur->next = lst ? (MuContainer*)lst->data : NULL; cur->next = lst ? (MuContainer*)lst->data : NULL;
cur->last = tail;
cur=cur->next; cur=cur->next;
} }
@ -317,47 +344,54 @@ struct _SortFuncData {
}; };
typedef struct _SortFuncData SortFuncData; typedef struct _SortFuncData SortFuncData;
static MuContainer* static int
get_top_msg (MuContainer *c, MuMsgFieldId mfid) container_cmp (MuContainer *a, MuContainer *b, MuMsgFieldId mfid)
{ {
MuContainer *piv, *extreme = c; if (a == b)
for (piv = c; piv != NULL && piv->msg != NULL; piv = piv->child) { return 0;
if (mu_msg_cmp (piv->msg, extreme->msg, mfid) > 0) else if (!a->msg)
extreme = piv; return -1;
if (piv != c && piv->next) { else if (!b->msg)
MuContainer *sub = get_top_msg (piv->next, mfid); return 1;
if (sub->msg != NULL && mu_msg_cmp (sub->msg, extreme->msg, mfid) > 0) return mu_msg_cmp (a->msg, b->msg, mfid);
extreme = sub; }
}
} static gboolean
return extreme; container_is_leaf (const MuContainer *c)
{
return c->child == NULL;
}
static MuContainer*
container_max (MuContainer *a, MuContainer *b, MuMsgFieldId mfid)
{
return container_cmp (a, b, mfid) > 0 ? a : b;
}
static MuContainer*
find_sorted_tree_leader (MuContainer *root, SortFuncData *order)
{
MuContainer *last_child;
if (container_is_leaf (root))
return root;
if (!order->descending)
last_child = root->child->last;
else /* reversed order, first is last */
last_child = root->child;
return container_max (root, last_child->leader, order->mfid);
} }
static int static int
sort_func_wrapper (MuContainer *a, MuContainer *b, SortFuncData *data) sort_func_wrapper (MuContainer *a, MuContainer *b, SortFuncData *data)
{ {
MuContainer *a1, *b1;
/* use the first non-empty 'left child' message if this one
* is */
for (a1 = a; a1->msg == NULL && a1->child != NULL; a1 = a1->child);
for (b1 = b; b1->msg == NULL && b1->child != NULL; b1 = b1->child);
a1 = get_top_msg (a1, data->mfid);
b1 = get_top_msg (b1, data->mfid);
if (a1 == b1)
return 0;
else if (!a1->msg)
return 1;
else if (!b1->msg)
return -1;
if (data->descending) if (data->descending)
return mu_msg_cmp (b1->msg, a1->msg, data->mfid); return container_cmp (b->leader, a->leader, data->mfid);
else else
return mu_msg_cmp (a1->msg, b1->msg, data->mfid); return container_cmp (a->leader, b->leader, data->mfid);
} }
static MuContainer* static MuContainer*
@ -369,9 +403,11 @@ container_sort_real (MuContainer *c, SortFuncData *sfdata)
if (!c) if (!c)
return NULL; return NULL;
for (cur = c; cur; cur = cur->next) for (cur = c; cur; cur = cur->next) {
if (cur->child) if (cur->child)
cur->child = container_sort_real (cur->child, sfdata); cur->child = container_sort_real (cur->child, sfdata);
cur->leader = find_sorted_tree_leader (cur, sfdata);
}
/* sort siblings */ /* sort siblings */
lst = mu_container_to_list (c); lst = mu_container_to_list (c);
@ -384,7 +420,6 @@ container_sort_real (MuContainer *c, SortFuncData *sfdata)
return c; return c;
} }
MuContainer* MuContainer*
mu_container_sort (MuContainer *c, MuMsgFieldId mfid, gboolean descending, mu_container_sort (MuContainer *c, MuMsgFieldId mfid, gboolean descending,
gpointer user_data) gpointer user_data)

View File

@ -45,6 +45,11 @@ struct _MuContainer {
* */ * */
struct _MuContainer *last; struct _MuContainer *last;
/* Node in the subtree rooted at this node which comes first
* in the descending sort order, e.g. the latest message if
* sorting by date. We compare the leaders when ordering
* subtrees. */
struct _MuContainer *leader;
MuMsg *msg; MuMsg *msg;
const char *msgid; const char *msgid;
@ -122,6 +127,19 @@ MuContainer* mu_container_remove_child (MuContainer *c, MuContainer *child);
*/ */
MuContainer* mu_container_remove_sibling (MuContainer *c, MuContainer *sibling); MuContainer* mu_container_remove_sibling (MuContainer *c, MuContainer *sibling);
/**
* promote sibling's children to be this container's siblings and
* remove the sibling
*
* @param c a container instance
* @param sibling a sibling of this container
*
* @return the container with the sibling's children promoted and the
* sibling itself removed
*/
MuContainer* mu_container_splice_children (MuContainer *c,
MuContainer *sibling);
/** /**
* promote child's children to be parent's children and remove child * promote child's children to be parent's children and remove child
@ -131,8 +149,8 @@ MuContainer* mu_container_remove_sibling (MuContainer *c, MuContainer *sibling);
* *
* @return the new container with it's children's children promoted * @return the new container with it's children's children promoted
*/ */
MuContainer* mu_container_splice_children (MuContainer *parent, MuContainer* mu_container_splice_grandchildren (MuContainer *parent,
MuContainer *child); MuContainer *child);
typedef gboolean (*MuContainerForeachFunc) (MuContainer*, gpointer); typedef gboolean (*MuContainerForeachFunc) (MuContainer*, gpointer);

View File

@ -388,7 +388,7 @@ prune_maybe (MuContainer *c)
if (cur->flags & MU_CONTAINER_FLAG_DELETE) if (cur->flags & MU_CONTAINER_FLAG_DELETE)
c = mu_container_remove_child (c, cur); c = mu_container_remove_child (c, cur);
else if (cur->flags & MU_CONTAINER_FLAG_SPLICE) else if (cur->flags & MU_CONTAINER_FLAG_SPLICE)
c = mu_container_splice_children (c, cur); c = mu_container_splice_grandchildren (c, cur);
} }
g_return_val_if_fail (c, FALSE); g_return_val_if_fail (c, FALSE);
@ -433,17 +433,10 @@ prune_empty_containers (MuContainer *root_set)
/* and prune the root_set itself... */ /* and prune the root_set itself... */
for (cur = root_set; cur; cur = cur->next) { for (cur = root_set; cur; cur = cur->next) {
if (cur->flags & MU_CONTAINER_FLAG_DELETE) if (cur->flags & MU_CONTAINER_FLAG_DELETE)
root_set = mu_container_remove_sibling (root_set, cur); root_set = mu_container_remove_sibling (root_set, cur);
else if (cur->flags & MU_CONTAINER_FLAG_SPLICE)
else if (cur->flags & MU_CONTAINER_FLAG_SPLICE) { root_set = mu_container_splice_children (root_set, cur);
MuContainer *newchild;
newchild = cur->child;
cur->child = NULL;
root_set = mu_container_append_siblings (root_set,
newchild);
}
} }
return root_set; return root_set;

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: A
Message-Id: <A@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
A

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: B
Message-Id: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
B

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: C
Message-Id: <C@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
C

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: D
Message-Id: <D@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
D

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: A
Message-Id: <A@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
A

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: B
Message-Id: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
B

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: C
Message-Id: <C@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
C

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: D
Message-Id: <D@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
D

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: E
Message-Id: <E@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
E

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: A
Message-Id: <A@msg.id>
References: <Y@msg.id>
In-reply-to: <Y@msg.id>
Aate: Sat, 17 May 2014 10:00:00 +0000
A

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: X
Message-Id: <X@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
X

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: Y
Message-Id: <Y@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
Y

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: Z
Message-Id: <Z@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
Z

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: A
Message-Id: <A@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
A

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: B
Message-Id: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
B

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: C
Message-Id: <C@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
C

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: D
Message-Id: <D@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
D

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: E
Message-Id: <E@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
E

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: F
Message-Id: <F@msg.id>
References: <B@msg.id> <D@msg.id>
In-reply-to: <D@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
F

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: G
Message-Id: <G@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
G

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: A
Message-Id: <A@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
A

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: B
Message-Id: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
B

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: C
Message-Id: <C@msg.id>
References: <B@msg.id>
In-reply-to: <B@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
C

View File

@ -0,0 +1,7 @@
From: testfrom@example.com
To: testto@example.com
Subject: D
Message-Id: <D@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
D

View File

@ -0,0 +1,9 @@
From: testfrom@example.com
To: testto@example.com
Subject: E
Message-Id: <E@msg.id>
References: <B@msg.id> <C@msg.id>
In-reply-to: <C@msg.id>
Date: Sat, 17 May 2014 10:00:00 +0000
E

View File

@ -133,7 +133,7 @@ of the message, as listed in the following table:
p,passed Passed ('Handled') p,passed Passed ('Handled')
r,replied Replied r,replied Replied
s,seen Seen s,seen Seen
t,thrashed Marked for deletion t,trashed Marked for deletion
a,attach Has attachment a,attach Has attachment
z,signed Signed message z,signed Signed message
x,encrypted Encrypted message x,encrypted Encrypted message

View File

@ -34,6 +34,65 @@
#include "mu-query.h" #include "mu-query.h"
#include "mu-str.h" #include "mu-str.h"
struct _tinfo {
const char *threadpath;
const char *msgid;
const char *subject;
};
typedef struct _tinfo tinfo;
static void
assert_tinfo_equal (const tinfo *expected, const tinfo *actual)
{
g_assert_cmpstr (expected->threadpath,==,actual->threadpath);
g_assert_cmpstr (expected->subject,==,actual->subject);
g_assert_cmpstr (expected->msgid,==,actual->msgid);
}
static void
tinfo_init_from_iter (tinfo *item, MuMsgIter *iter)
{
MuMsg *msg;
const MuMsgIterThreadInfo *ti;
msg = mu_msg_iter_get_msg_floating (iter);
g_assert (msg);
ti = mu_msg_iter_get_thread_info (iter);
if (!ti)
g_print ("%s: thread info not found\n", mu_msg_get_msgid (msg));
g_assert (ti);
item->threadpath = ti->threadpath;
item->subject = mu_msg_get_subject (msg);
item->msgid = mu_msg_get_msgid (msg);
if (g_test_verbose())
g_print ("%s %s %s\n",
item->threadpath, item->subject, item->msgid);
}
static void
foreach_assert_tinfo_equal (MuMsgIter *iter, const tinfo items[], guint n_items)
{
guint u;
u = 0;
while (!mu_msg_iter_is_done (iter) && u < n_items) {
tinfo ti;
tinfo_init_from_iter (&ti, iter);
g_assert (u < n_items);
assert_tinfo_equal (&items[u], &ti);
++u;
mu_msg_iter_next (iter);
}
g_assert (u == n_items);
}
static gchar* static gchar*
fill_database (const char *testdir) fill_database (const char *testdir)
{ {
@ -58,7 +117,8 @@ fill_database (const char *testdir)
/* note: this also *moves the iter* */ /* note: this also *moves the iter* */
static MuMsgIter* static MuMsgIter*
run_and_get_iter (const char *xpath, const char *query) run_and_get_iter_full (const char *xpath, const char *query,
MuMsgFieldId sort_field, MuQueryFlags flags)
{ {
MuQuery *mquery; MuQuery *mquery;
MuStore *store; MuStore *store;
@ -71,27 +131,28 @@ run_and_get_iter (const char *xpath, const char *query)
mu_store_unref (store); mu_store_unref (store);
g_assert (query); g_assert (query);
iter = mu_query_run (mquery, query, MU_MSG_FIELD_ID_DATE, flags |= MU_QUERY_FLAG_THREADS;
-1, MU_QUERY_FLAG_THREADS, NULL); iter = mu_query_run (mquery, query, sort_field, -1, flags, NULL);
mu_query_destroy (mquery); mu_query_destroy (mquery);
g_assert (iter); g_assert (iter);
return iter; return iter;
} }
static MuMsgIter*
run_and_get_iter (const char *xpath, const char *query)
{
return run_and_get_iter_full (xpath, query, MU_MSG_FIELD_ID_DATE,
MU_QUERY_FLAG_NONE);
}
static void static void
test_mu_threads_01 (void) test_mu_threads_01 (void)
{ {
gchar *xpath; gchar *xpath;
MuMsgIter *iter; MuMsgIter *iter;
unsigned u;
struct { const tinfo items [] = {
const char* threadpath;
const char *msgid;
const char* subject;
} items [] = {
{"0", "root0@msg.id", "root0"}, {"0", "root0@msg.id", "root0"},
{"0:0", "child0.0@msg.id", "Re: child 0.0"}, {"0:0", "child0.0@msg.id", "Re: child 0.0"},
{"0:1", "child0.1@msg.id", "Re: child 0.1"}, {"0:1", "child0.1@msg.id", "Re: child 0.1"},
@ -115,54 +176,17 @@ test_mu_threads_01 (void)
g_assert (iter); g_assert (iter);
g_assert (!mu_msg_iter_is_done(iter)); g_assert (!mu_msg_iter_is_done(iter));
u = 0; foreach_assert_tinfo_equal (iter, items, G_N_ELEMENTS (items));
while (!mu_msg_iter_is_done (iter) && u < G_N_ELEMENTS(items)) {
MuMsg *msg;
const MuMsgIterThreadInfo *ti;
ti = mu_msg_iter_get_thread_info (iter);
if (!ti)
g_print ("%s: thread info not found for %u\n",
__FUNCTION__, (unsigned)mu_msg_iter_get_docid(iter));
g_assert(ti);
msg = mu_msg_iter_get_msg_floating (iter);
g_assert (msg);
if (g_test_verbose())
g_print ("%s %s %s\n", ti->threadpath,
mu_msg_get_msgid(msg),
mu_msg_get_path (msg));
g_assert (u < G_N_ELEMENTS(items));
g_assert_cmpstr (ti->threadpath,==,items[u].threadpath);
g_assert_cmpstr (mu_msg_get_subject(msg),==,items[u].subject);
g_assert_cmpstr (mu_msg_get_msgid(msg),==,items[u].msgid);
++u;
mu_msg_iter_next (iter);
}
g_assert (u == G_N_ELEMENTS(items));
g_free (xpath); g_free (xpath);
mu_msg_iter_destroy (iter); mu_msg_iter_destroy (iter);
} }
struct _tinfo {
const char* threadpath;
const char *msgid;
const char* subject;
};
typedef struct _tinfo tinfo;
static void static void
test_mu_threads_rogue (void) test_mu_threads_rogue (void)
{ {
gchar *xpath; gchar *xpath;
MuMsgIter *iter; MuMsgIter *iter;
unsigned u;
tinfo *items; tinfo *items;
tinfo items1 [] = { tinfo items1 [] = {
@ -194,40 +218,214 @@ test_mu_threads_rogue (void)
else else
items = items2; items = items2;
u = 0; foreach_assert_tinfo_equal (iter, items, G_N_ELEMENTS (items1));
while (!mu_msg_iter_is_done (iter) && u < G_N_ELEMENTS(items1)) {
MuMsg *msg;
const MuMsgIterThreadInfo *ti;
ti = mu_msg_iter_get_thread_info (iter);
if (!ti)
g_print ("%s: thread info not found\n",
mu_msg_get_msgid(mu_msg_iter_get_msg_floating (iter)));
g_assert(ti);
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
/* g_print ("%s %s %s\n", ti->threadpath, */
/* mu_msg_get_msgid(msg), */
/* mu_msg_get_path (msg) */
/* ); */
g_assert (u < G_N_ELEMENTS(items1));
g_assert_cmpstr (ti->threadpath,==,(items)[u].threadpath);
g_assert_cmpstr (mu_msg_get_subject(msg),==,(items)[u].subject);
g_assert_cmpstr (mu_msg_get_msgid(msg),==,(items)[u].msgid);
++u;
mu_msg_iter_next (iter);
}
g_assert (u == G_N_ELEMENTS(items1));
g_free (xpath); g_free (xpath);
mu_msg_iter_destroy (iter); mu_msg_iter_destroy (iter);
} }
static MuMsgIter*
query_testdir (const char *query, MuMsgFieldId sort_field, gboolean descending)
{
MuMsgIter *iter;
gchar *xpath;
MuQueryFlags flags;
flags = MU_QUERY_FLAG_NONE;
if (descending)
flags |= MU_QUERY_FLAG_DESCENDING;
xpath = fill_database (MU_TESTMAILDIR3);
g_assert (xpath != NULL);
iter = run_and_get_iter_full (xpath, query, sort_field, flags);
g_assert (iter != NULL);
g_assert (!mu_msg_iter_is_done (iter));
g_free (xpath);
return iter;
}
static void
check_sort_by_subject (const char *query, const tinfo expected[],
guint n_expected, gboolean descending)
{
MuMsgIter *iter;
iter = query_testdir (query, MU_MSG_FIELD_ID_SUBJECT, descending);
foreach_assert_tinfo_equal (iter, expected, n_expected);
mu_msg_iter_destroy (iter);
}
static void
check_sort_by_subject_asc (const char *query, const tinfo expected[],
guint n_expected)
{
check_sort_by_subject (query, expected, n_expected, FALSE);
}
static void
check_sort_by_subject_desc (const char *query, const tinfo expected[],
guint n_expected)
{
check_sort_by_subject (query, expected, n_expected, TRUE);
}
static void
test_mu_threads_sort_1st_child_promotes_thread (void)
{
const char *query = "maildir:/sort/1st-child-promotes-thread";
const tinfo expected_asc [] = {
{ "0", "A@msg.id", "A"},
{ "1", "C@msg.id", "C"},
{ "2", "B@msg.id", "B"},
{ "2:0", "D@msg.id", "D"},
};
const tinfo expected_desc [] = {
{ "0", "B@msg.id", "B"},
{ "0:0", "D@msg.id", "D"},
{ "1", "C@msg.id", "C"},
{ "2", "A@msg.id", "A"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
static void
test_mu_threads_sort_2nd_child_promotes_thread (void)
{
const char *query = "maildir:/sort/2nd-child-promotes-thread";
const tinfo expected_asc [] = {
{ "0", "A@msg.id", "A"},
{ "1", "D@msg.id", "D"},
{ "2", "B@msg.id", "B"},
{ "2:0", "C@msg.id", "C"},
{ "2:1", "E@msg.id", "E"},
};
const tinfo expected_desc [] = {
{ "0", "B@msg.id", "B"},
{ "0:0", "E@msg.id", "E"},
{ "0:1", "C@msg.id", "C"},
{ "1", "D@msg.id", "D"},
{ "2", "A@msg.id", "A"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
static void
test_mu_threads_sort_orphan_promotes_thread (void)
{
const char *query = "maildir:/sort/2nd-child-promotes-thread NOT B";
/* B lost, C & E orphaned but not promoted */
const tinfo expected_asc [] = {
{ "0", "A@msg.id", "A"},
{ "1", "D@msg.id", "D"},
{ "2:0", "C@msg.id", "C"},
{ "2:1", "E@msg.id", "E"},
};
const tinfo expected_desc [] = {
{ "0:0", "E@msg.id", "E"},
{ "0:1", "C@msg.id", "C"},
{ "1", "D@msg.id", "D"},
{ "2", "A@msg.id", "A"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
/* Won't normally happen when sorting by date. */
static void
test_mu_threads_sort_child_does_not_promote_thread (void)
{
const char *query = "maildir:/sort/child-does-not-promote-thread";
const tinfo expected_asc [] = {
{ "0", "X@msg.id", "X"},
{ "1", "Y@msg.id", "Y"},
{ "1:0", "A@msg.id", "A"},
{ "2", "Z@msg.id", "Z"},
};
const tinfo expected_desc [] = {
{ "0", "Z@msg.id", "Z"},
{ "1", "Y@msg.id", "Y"},
{ "1:0", "A@msg.id", "A"},
{ "2", "X@msg.id", "X"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
static void
test_mu_threads_sort_grandchild_promotes_thread (void)
{
const char *query = "maildir:/sort/grandchild-promotes-thread";
const tinfo expected_asc [] = {
{ "0", "A@msg.id", "A"},
{ "1", "D@msg.id", "D"},
{ "2", "B@msg.id", "B"},
{ "2:0", "C@msg.id", "C"},
{ "2:0:0", "E@msg.id", "E"},
};
const tinfo expected_desc [] = {
{ "0", "B@msg.id", "B"},
{ "0:0", "C@msg.id", "C"},
{ "0:0:0", "E@msg.id", "E"},
{ "1", "D@msg.id", "D"},
{ "2", "A@msg.id", "A"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
static void
test_mu_threads_sort_granchild_promotes_only_subthread (void)
{
const char *query = "maildir:/sort/grandchild-promotes-only-subthread";
const tinfo expected_asc [] = {
{ "0", "A@msg.id", "A"},
{ "1", "B@msg.id", "B"},
{ "1:0", "C@msg.id", "C"},
{ "1:1", "E@msg.id", "E"},
{ "1:2", "D@msg.id", "D"},
{ "1:2:0", "F@msg.id", "F"},
{ "2", "G@msg.id", "G"},
};
const tinfo expected_desc [] = {
{ "0", "G@msg.id", "G"},
{ "1", "B@msg.id", "B"},
{ "1:0", "D@msg.id", "D"},
{ "1:0:0", "F@msg.id", "F"},
{ "1:1", "E@msg.id", "E"},
{ "1:2", "C@msg.id", "C"},
{ "2", "A@msg.id", "A"},
};
check_sort_by_subject_asc (query, expected_asc,
G_N_ELEMENTS (expected_asc));
check_sort_by_subject_desc (query, expected_desc,
G_N_ELEMENTS (expected_desc));
}
int int
main (int argc, char *argv[]) main (int argc, char *argv[])
{ {
@ -237,6 +435,18 @@ main (int argc, char *argv[])
g_test_add_func ("/mu-query/test-mu-threads-01", test_mu_threads_01); g_test_add_func ("/mu-query/test-mu-threads-01", test_mu_threads_01);
g_test_add_func ("/mu-query/test-mu-threads-rogue", test_mu_threads_rogue); g_test_add_func ("/mu-query/test-mu-threads-rogue", test_mu_threads_rogue);
g_test_add_func ("/mu-query/test-mu-threads-sort-1st-child-promotes-thread",
test_mu_threads_sort_1st_child_promotes_thread);
g_test_add_func ("/mu-query/test-mu-threads-sort-2nd-child-promotes-thread",
test_mu_threads_sort_2nd_child_promotes_thread);
g_test_add_func ("/mu-query/test-mu-threads-orphan-promotes-thread",
test_mu_threads_sort_orphan_promotes_thread);
g_test_add_func ("/mu-query/test-mu-threads-sort-child-does-not-promote-thread",
test_mu_threads_sort_child_does_not_promote_thread);
g_test_add_func ("/mu-query/test-mu-threads-sort-grandchild-promotes-thread",
test_mu_threads_sort_grandchild_promotes_thread);
g_test_add_func ("/mu-query/test-mu-threads-sort-grandchild-promotes-only-subthread",
test_mu_threads_sort_granchild_promotes_only_subthread);
g_log_set_handler (NULL, g_log_set_handler (NULL,
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,

View File

@ -326,7 +326,7 @@ appear on disk."
mu4e~compose-buffer-max-name-length mu4e~compose-buffer-max-name-length
nil nil t))))) nil nil t)))))
(defun mu4e~compose-handler (compose-type &optional original-msg includes) (defun* mu4e~compose-handler (compose-type &optional original-msg includes)
"Create a new draft message, or open an existing one. "Create a new draft message, or open an existing one.
COMPOSE-TYPE determines the kind of message to compose and is a COMPOSE-TYPE determines the kind of message to compose and is a
@ -352,7 +352,10 @@ tempfile)."
(run-hooks 'mu4e-compose-pre-hook) (run-hooks 'mu4e-compose-pre-hook)
;; this opens (or re-opens) a messages with all the basic headers set. ;; this opens (or re-opens) a messages with all the basic headers set.
(mu4e-draft-open compose-type original-msg) (condition-case nil
(mu4e-draft-open compose-type original-msg)
(quit (kill-buffer) (message "[mu4e] Operation aborted")
(return-from mu4e~compose-handler)))
;; insert mail-header-separator, which is needed by message mode to separate ;; insert mail-header-separator, which is needed by message mode to separate
;; headers and body. will be removed before saving to disk ;; headers and body. will be removed before saving to disk
(mu4e~draft-insert-mail-header-separator) (mu4e~draft-insert-mail-header-separator)

View File

@ -31,6 +31,7 @@
("boost-announce.lists.boost.org" . "BoostA") ("boost-announce.lists.boost.org" . "BoostA")
("boost-interest.lists.boost.org" . "BoostI") ("boost-interest.lists.boost.org" . "BoostI")
("conkeror.mozdev.org" . "Conkeror") ("conkeror.mozdev.org" . "Conkeror")
("curl-library.cool.haxx.se" . "LibCurl")
("crypto-gram-list.schneier.com " . "CryptoGr") ("crypto-gram-list.schneier.com " . "CryptoGr")
("dbus.lists.freedesktop.org" . "DBus") ("dbus.lists.freedesktop.org" . "DBus")
("desktop-devel-list.gnome.org" . "GnomeDT") ("desktop-devel-list.gnome.org" . "GnomeDT")

View File

@ -64,11 +64,9 @@
"Major mode for the mu4e main screen. "Major mode for the mu4e main screen.
\\{mu4e-main-mode-map}." \\{mu4e-main-mode-map}."
(use-local-map mu4e-main-mode-map) (use-local-map mu4e-main-mode-map)
(setq (setq truncate-lines t
truncate-lines t overwrite-mode 'overwrite-mode-binary)
overwrite-mode 'overwrite-mode-binary (set (make-local-variable 'revert-buffer-function) #'mu4e:main-revert-buffer))
revert-buffer-function 'mu4e:main-revert-buffer
))
(defun mu4e~main-action-str (str &optional func-or-shortcut) (defun mu4e~main-action-str (str &optional func-or-shortcut)

View File

@ -138,7 +138,7 @@ The following marks are available, and the corresponding props:
`flag' n mark this message for flagging `flag' n mark this message for flagging
`move' y move the message to some folder `move' y move the message to some folder
`read' n mark the message as read `read' n mark the message as read
`trash' y thrash the message to some folder `trash' y trash the message to some folder
`unflag' n mark this message for unflagging `unflag' n mark this message for unflagging
`untrash' n remove the 'trashed' flag from a message `untrash' n remove the 'trashed' flag from a message
`unmark' n unmark this message `unmark' n unmark this message
@ -286,7 +286,7 @@ user which one)."
mu4e~mark-map)))) mu4e~mark-map))))
(defun mu4e~mark-check-target (target) (defun mu4e~mark-check-target (target)
"Check if the target exists if not, offer to create it." "Check if the target exists; if not, offer to create it."
(let ((fulltarget (concat mu4e-maildir target))) (let ((fulltarget (concat mu4e-maildir target)))
(if (not (mu4e-create-maildir-maybe fulltarget)) (if (not (mu4e-create-maildir-maybe fulltarget))
(mu4e-error "Target dir %s does not exist " fulltarget) (mu4e-error "Target dir %s does not exist " fulltarget)
@ -298,9 +298,9 @@ user which one)."
After the actions have been executed succesfully, the affected After the actions have been executed succesfully, the affected
messages are *hidden* from the current header list. Since the messages are *hidden* from the current header list. Since the
headers are the result of a search, we cannot be certain that the headers are the result of a search, we cannot be certain that the
messages no longer matches the current one - to get that messages no longer match the current one - to get that
certainty, we need to rerun the search, but we don't want to do certainty, we need to rerun the search, but we don't want to do
that automatically, as it may be too slow and/or break the users that automatically, as it may be too slow and/or break the user's
flow. Therefore, we hide the message, which in practice seems to flow. Therefore, we hide the message, which in practice seems to
work well. work well.

View File

@ -597,6 +597,7 @@ FUNC should be a function taking two arguments:
;; misc ;; misc
(define-key map "w" 'visual-line-mode) (define-key map "w" 'visual-line-mode)
(define-key map "h" 'mu4e-view-toggle-hide-cited) (define-key map "h" 'mu4e-view-toggle-hide-cited)
(define-key map (kbd "M-q") 'mu4e-view-fill-long-lines)
;; next 3 only warn user when attempt in the message view ;; next 3 only warn user when attempt in the message view
(define-key map "u" 'mu4e-view-unmark) (define-key map "u" 'mu4e-view-unmark)
@ -909,6 +910,28 @@ Add this function to `mu4e-view-mode-hook' to enable this feature."
(overlay-put ov 'face 'mu4e-region-code)) (overlay-put ov 'face 'mu4e-region-code))
(setq beg nil end nil)))))) (setq beg nil end nil))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Wash functions
(defun mu4e-view-fill-long-lines ()
"Fill lines that are wider than the window width or `fill-column'."
(interactive)
(with-current-buffer mu4e~view-buffer
(save-excursion
(let ((inhibit-read-only t)
(width (window-width (get-buffer-window (current-buffer)))))
(save-restriction
(message-goto-body)
(while (not (eobp))
(end-of-line)
(when (>= (current-column) (min fill-column width))
(narrow-to-region (min (1+ (point)) (point-max))
(point-at-bol))
(let ((goback (point-marker)))
(fill-paragraph nil)
(goto-char (marker-position goback)))
(widen))
(forward-line 1)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; attachment handling ;; attachment handling
(defun mu4e~view-get-attach-num (prompt msg &optional multi) (defun mu4e~view-get-attach-num (prompt msg &optional multi)

View File

@ -810,7 +810,7 @@ W toggle include-related
marking marking
------- -------
d mark for moving to the trash folder d mark for moving to the trash folder
= mark for remove thrash flags ('untrash') = mark for removing trash flag ('untrash')
DEL,D mark for complete deletion DEL,D mark for complete deletion
m mark for moving to another maildir folder m mark for moving to another maildir folder
r mark for refiling r mark for refiling
@ -1076,7 +1076,7 @@ M-right next query
marking marking
------- -------
d mark for moving to the trash folder d mark for moving to the trash folder
= mark for remove thrash flags ('untrash') = mark for removing trash flag ('untrash')
DEL,D mark for complete deletion DEL,D mark for complete deletion
m mark for moving to another maildir folder m mark for moving to another maildir folder
r mark for refiling r mark for refiling
@ -1839,12 +1839,12 @@ previous/next queries, you can use @kbd{M-x mu4e-headers-forget-queries}.
It can be useful to narrow existing search results, that is, to add some It can be useful to narrow existing search results, that is, to add some
clauses to the current query to match fewer messages. clauses to the current query to match fewer messages.
For example, suppose you're looking at the some mailing list, perhaps by For example, suppose you're looking at some mailing list, perhaps by
jumping to a maildir (@kbd{M-x mu4e-headers-jump-to-maildir}, @key{j}) or jumping to a maildir (@kbd{M-x mu4e-headers-jump-to-maildir}, @key{j}) or
because you followed some bookmark (@kbd{M-x mu4e-headers-search-bookmark}, because you followed some bookmark (@kbd{M-x mu4e-headers-search-bookmark},
@key{b}). Now, you want to narrow things down to only those messages that have @key{b}). Now, you want to narrow things down to only those messages that have
attachments. attachments.
1
This is when @kbd{M-x mu4e-headers-search-narrow} (@key{/}) comes in handy. It This is when @kbd{M-x mu4e-headers-search-narrow} (@key{/}) comes in handy. It
asks for an additional search pattern, which is appended to the current search asks for an additional search pattern, which is appended to the current search
query, in effect getting you the subset of the currently shown headers that query, in effect getting you the subset of the currently shown headers that
@ -3234,10 +3234,10 @@ value)} pairs:
("Account2" ("Account2"
(mu4e-sent-folder "/Account2/Saved Items") (mu4e-sent-folder "/Account2/Saved Items")
(mu4e-drafts-folder "/Account2/Drafts") (mu4e-drafts-folder "/Account2/Drafts")
(user-mail-address "my.address@@account2.tld) (user-mail-address "my.address@@account2.tld")
(smtpmail-default-smtp-server "smtp.account2.tld) (smtpmail-default-smtp-server "smtp.account2.tld")
(smtpmail-local-domain "account2.tld") (smtpmail-local-domain "account2.tld")
(smtpmail-smtp-server "smtp.account2.tld) (smtpmail-smtp-server "smtp.account2.tld")
(smtpmail-stream-type starttls) (smtpmail-stream-type starttls)
(smtpmail-smtp-service 587)))) (smtpmail-smtp-service 587))))
@end lisp @end lisp