acme/Mail: fix redrawn line offsets, add support for flag filters

maintaining ->nsub was fragile, and didn't save very many cycles;
instead just compute it every time; it's only going to hurt with
a ton of giant threads.
This commit is contained in:
Ori Bernstein 2024-12-09 05:27:05 +00:00
parent af83b606f9
commit 8e2a071b8b
3 changed files with 93 additions and 48 deletions

View file

@ -101,11 +101,17 @@ are listed in
.IR upasfs (4) .IR upasfs (4)
.PD 0 .PD 0
.TP .TP
.B Filter [pattern] .B Filter [filter] [*flags]
Shows only messages where the sender or subject match Shows only messages where the sender or subject match
the the
.I pattern .I filter
regexp. regexp,
or where the
.I flags
string matches the state of the message. The flags
used are the same as for Mark, with the addition of
.I u
to represent unread messages.
Filter without an argument resets the filtering, Filter without an argument resets the filtering,
showing all messages again. showing all messages again.
.PD 0 .PD 0
@ -127,15 +133,12 @@ As with the message view, but applied to the open message.
.TP .TP
.B Mark .B Mark
As with the message view, but applied to the open message. As with the message view, but applied to the open message.
.PP .PP
The following text commands are recognized by the composition The following text commands are recognized by the composition
window: window:
.TP .TP
.B Post .B Post
Sends the message currently being composed. Sends the message currently being composed.
.SS Format strings .SS Format strings
The formatting of messages in the list view is controlled by the The formatting of messages in the list view is controlled by the
format string defined through the format string defined through the

View file

@ -16,6 +16,7 @@ enum {
Stoplev = 1<<1, /* not a response to anything */ Stoplev = 1<<1, /* not a response to anything */
Sopen = 1<<2, /* opened for viewing */ Sopen = 1<<2, /* opened for viewing */
Szap = 1<<3, /* flushed, to be removed from list */ Szap = 1<<3, /* flushed, to be removed from list */
Shide = 1<<4, /* hidden from view */
}; };
enum { enum {
@ -95,7 +96,6 @@ struct Mesg {
Mesg *parent; Mesg *parent;
Mesg **child; Mesg **child;
int nchild; int nchild;
int nsub; /* transitive children */
Mesg *body; /* best attachment to use, or nil */ Mesg *body; /* best attachment to use, or nil */
Mesg **parts; Mesg **parts;

View file

@ -32,6 +32,7 @@ Mesg dead = {.messageid="", .hash=42};
Reprog *mesgpat; Reprog *mesgpat;
Reprog *filterpat; Reprog *filterpat;
char *filterflags;
int threadsort = 1; int threadsort = 1;
int sender; int sender;
@ -125,6 +126,45 @@ rcmpmesg(void *pa, void *pb)
return a->time - b->time; return a->time - b->time;
} }
static int
matchfilter(Mesg *m)
{
char *p;
int ok;
ok = 1;
if(filterpat != nil
&& m->subject != nil
&& m->from != nil){
if(!regexec(filterpat, m->subject, nil, 0)
&& !regexec(filterpat, m->from, nil, 0))
ok = 0;
}
for(p = filterflags; p && *p; p++){
switch(*p){
case 's': ok = ok && (m->flags & Fseen); break;
case 'u': ok = ok && !(m->flags & Fseen); break;
case 'a': ok = ok && (m->flags & Fresp); break;
}
}
return ok;
}
static int
nsub(Mesg *m)
{
Mesg *c;
int n, i;
n = 0;
for(i = 0; i < m->nchild; i++){
c = m->child[i];
if(!(c->state & (Sdummy|Shide)))
n += nsub(c)+1;
}
return n;
}
static int static int
mesglineno(Mesg *msg, int *depth) mesglineno(Mesg *msg, int *depth)
{ {
@ -143,9 +183,9 @@ mesglineno(Mesg *msg, int *depth)
for(i = 0; i < p->nchild; i++){ for(i = 0; i < p->nchild; i++){
if(p->child[i] == m) if(p->child[i] == m)
break; break;
o += p->child[i]->nsub + 1; o += nsub(p->child[i]) + 1;
} }
if(!(p->state & (Sdummy|Szap))){ if(!(p->state & (Sdummy|Shide))){
o++; o++;
d++; d++;
} }
@ -157,8 +197,8 @@ mesglineno(Mesg *msg, int *depth)
if(m == p) if(m == p)
break; break;
if(m->state & Stoplev){ if(m->state & Stoplev){
n += mbox.mesg[i]->nsub; n += nsub(mbox.mesg[i]);
if(!(m->state & (Sdummy|Szap))) if(!(m->state & (Sdummy|Szap|Shide)))
n++; n++;
} }
@ -170,7 +210,7 @@ mesglineno(Mesg *msg, int *depth)
} }
static int static int
addchild(Mesg *p, Mesg *m, int d) addchild(Mesg *p, Mesg *m)
{ {
Mesg *q; Mesg *q;
@ -182,8 +222,6 @@ addchild(Mesg *p, Mesg *m, int d)
if(m->time > q->time) if(m->time > q->time)
q->time = m->time; q->time = m->time;
} }
for(q = p; q != nil; q = q->parent)
q->nsub += d;
p->child = erealloc(p->child, ++p->nchild*sizeof(Mesg*)); p->child = erealloc(p->child, ++p->nchild*sizeof(Mesg*));
p->child[p->nchild - 1] = m; p->child[p->nchild - 1] = m;
qsort(p->child, p->nchild, sizeof(Mesg*), rcmpmesg); qsort(p->child, p->nchild, sizeof(Mesg*), rcmpmesg);
@ -347,7 +385,6 @@ static Mesg*
load(char *name, char *digest, int ins) load(char *name, char *digest, int ins)
{ {
Mesg *m, *p; Mesg *m, *p;
int d;
if(strncmp(name, mbox.path, strlen(mbox.path)) == 0) if(strncmp(name, mbox.path, strlen(mbox.path)) == 0)
name += strlen(mbox.path); name += strlen(mbox.path);
@ -357,13 +394,10 @@ load(char *name, char *digest, int ins)
if(digest != nil && strcmp(digest, m->digest) != 0) if(digest != nil && strcmp(digest, m->digest) != 0)
goto error; goto error;
/* if we already have a dummy, populate it */ /* if we already have a dummy, populate it */
d = 1;
p = lookupid(m->messageid); p = lookupid(m->messageid);
if(p != nil && (p->state & Sdummy)){ if(p != nil && (p->state & Sdummy)){
d = p->nsub + 1;
m->child = p->child; m->child = p->child;
m->nchild = p->nchild; m->nchild = p->nchild;
m->nsub = p->nsub;
mesgclear(p); mesgclear(p);
memcpy(p, m, sizeof(*p)); memcpy(p, m, sizeof(*p));
free(m); free(m);
@ -387,8 +421,10 @@ load(char *name, char *digest, int ins)
p = lookupid(m->inreplyto); p = lookupid(m->inreplyto);
if(p == nil) if(p == nil)
p = placeholder(m->inreplyto, m->time, ins); p = placeholder(m->inreplyto, m->time, ins);
if(!addchild(p, m, d)) if(!addchild(p, m))
m->state |= Stoplev; m->state |= Stoplev;
if(!matchfilter(m))
m->state |= Shide;
return m; return m;
error: error:
mesgfree(m); mesgfree(m);
@ -576,22 +612,12 @@ fmtmesg(Biobuf *bp, char *fmt, Mesg *m, int depth)
Bputc(bp, '\n'); Bputc(bp, '\n');
} }
static int
matchfilter(Mesg *m)
{
if(filterpat == nil
|| regexec(filterpat, m->subject, nil, 0)
|| regexec(filterpat, m->from, nil, 0))
return 1;
return 0;
}
static void static void
showmesg(Biobuf *bfd, Mesg *m, int depth, int recurse) showmesg(Biobuf *bfd, Mesg *m, int depth, int recurse)
{ {
int i; int i;
if(!(m->state & Sdummy) && matchfilter(m)){ if(!(m->state & (Sdummy|Shide))){
fmtmesg(bfd, listfmt, m, depth); fmtmesg(bfd, listfmt, m, depth);
depth++; depth++;
} }
@ -664,7 +690,7 @@ mbmark(char **f, int nf)
static void static void
relinkmsg(Mesg *p, Mesg *m) relinkmsg(Mesg *p, Mesg *m)
{ {
Mesg *c, *pp; Mesg *c;
int i, j; int i, j;
/* remove child, preserving order */ /* remove child, preserving order */
@ -674,14 +700,12 @@ relinkmsg(Mesg *p, Mesg *m)
p->child[j++] = p->child[i]; p->child[j++] = p->child[i];
} }
p->nchild = j; p->nchild = j;
for(pp = p; pp != nil; pp = pp->parent)
pp->nsub -= m->nsub + 1;
/* reparent children */ /* reparent children */
for(i = 0; i < m->nchild; i++){ for(i = 0; i < m->nchild; i++){
c = m->child[i]; c = m->child[i];
c->parent = nil; c->parent = nil;
addchild(p, c, c->nsub + 1); addchild(p, c);
} }
} }
@ -704,17 +728,15 @@ mbflush(char **, int)
continue; continue;
ln = mesglineno(m, nil); ln = mesglineno(m, nil);
fprint(mbox.addr, "%d,%d", ln, ln+m->nsub); fprint(mbox.addr, "%d,%d", ln, ln+nsub(m));
write(mbox.data, "", 0); write(mbox.data, "", 0);
if(m->flags & Ftodel) if(m->flags & Ftodel)
fprint(fd, "delete %s %d", mailbox, atoi(m->name)); fprint(fd, "delete %s %d", mailbox, atoi(m->name));
removeid(m); removeid(m);
m->state |= Szap; m->state |= Szap;
if(p == nil && m->nsub != 0){ if(p == nil && nsub(m) != 0)
p = placeholder(m->messageid, m->time, 1); p = placeholder(m->messageid, m->time, 1);
p->nsub = m->nsub + 1;
}
if(p != nil) if(p != nil)
relinkmsg(p, m); relinkmsg(p, m);
for(j = 0; j < m->nchild; j++) for(j = 0; j < m->nchild; j++)
@ -824,10 +846,10 @@ changemesg(Plumbmsg *pm)
for(r = m; r->parent != nil; r = r->parent) for(r = m; r->parent != nil; r = r->parent)
/* nothing */; /* nothing */;
/* Bump whole thread up in list */ /* Bump whole thread up in list */
if(r->nsub > 0){ if(nsub(r) > 0){
ln = mesglineno(r, nil); ln = mesglineno(r, nil);
nr = r->nsub-1; nr = nsub(r)-1;
if(!(r->state & Sdummy)) if(!(r->state & (Sdummy|Shide)))
nr++; nr++;
/* /*
* We can end up with an empty container * We can end up with an empty container
@ -881,19 +903,39 @@ redraw(char **, int)
static void static void
filter(char **filt, int nfilt) filter(char **filt, int nfilt)
{ {
if(nfilt > 1){ Mesg *m;
fprint(2, "filter: only one argument supported"); int i;
if(nfilt > 2){
Usage:
fprint(2, "usage: Filter [regexp] [*flags]");
return; return;
} }
free(filterpat); free(filterpat);
free(filterflags);
filterpat = nil; filterpat = nil;
if(nfilt == 1){ filterflags = nil;
filterpat = regcomp(filt[0]); for(i = 0; i < nfilt; i++){
if(filterpat == nil){ if(*filt[i] == '*'){
fprint(2, "Filter: %r"); if(filterflags != nil)
return; goto Usage;
filterflags = strdup(filt[i]+1);
}else{
if(filterpat != nil)
goto Usage;
filterpat = regcomp(filt[i]);
if(filterpat == nil){
fprint(2, "recomp: %r");
return;
}
} }
} }
for(i = 0; i < mbox.nmesg; i++){
m = mbox.mesg[i];
m->state &= ~Shide;
if(!matchfilter(m))
m->state |= Shide;
}
fprint(mbox.addr, ","); fprint(mbox.addr, ",");
showlist(); showlist();
} }