first commit

This commit is contained in:
Iris Lightshard 2019-11-14 18:15:48 -05:00
commit 690197f35d
44 changed files with 19099 additions and 0 deletions

1172
acme.c Normal file

File diff suppressed because it is too large Load diff

295
addr.c Normal file
View file

@ -0,0 +1,295 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
enum
{
None = 0,
Fore = '+',
Back = '-'
};
enum
{
Char,
Line
};
int
isaddrc(int r)
{
if(r && utfrune("0123456789+-/$.#,;?", r)!=nil)
return TRUE;
return FALSE;
}
/*
* quite hard: could be almost anything but white space, but we are a little conservative,
* aiming for regular expressions of alphanumerics and no white space
*/
int
isregexc(int r)
{
if(r == 0)
return FALSE;
if(isalnum(r))
return TRUE;
if(utfrune("^+-.*?#,;[]()$", r)!=nil)
return TRUE;
return FALSE;
}
// nlcounttopos starts at q0 and advances nl lines,
// being careful not to walk past the end of the text,
// and then nr chars, being careful not to walk past
// the end of the current line.
// It returns the final position.
long
nlcounttopos(Text *t, long q0, long nl, long nr)
{
while(nl > 0 && q0 < t->file->b.nc) {
if(textreadc(t, q0++) == '\n')
nl--;
}
if(nl > 0)
return q0;
while(nr > 0 && q0 < t->file->b.nc && textreadc(t, q0) != '\n') {
q0++;
nr--;
}
return q0;
}
Range
number(uint showerr, Text *t, Range r, int line, int dir, int size, int *evalp)
{
uint q0, q1;
if(size == Char){
if(dir == Fore)
line = r.q1+line;
else if(dir == Back){
if(r.q0==0 && line>0)
r.q0 = t->file->b.nc;
line = r.q0 - line;
}
if(line<0 || line>t->file->b.nc)
goto Rescue;
*evalp = TRUE;
return range(line, line);
}
q0 = r.q0;
q1 = r.q1;
switch(dir){
case None:
q0 = 0;
q1 = 0;
Forward:
while(line>0 && q1<t->file->b.nc)
if(textreadc(t, q1++) == '\n' || q1==t->file->b.nc)
if(--line > 0)
q0 = q1;
if(line==1 && q1==t->file->b.nc) // 6 goes to end of 5-line file
break;
if(line > 0)
goto Rescue;
break;
case Fore:
if(q1 > 0)
while(q1<t->file->b.nc && textreadc(t, q1-1) != '\n')
q1++;
q0 = q1;
goto Forward;
case Back:
if(q0 < t->file->b.nc)
while(q0>0 && textreadc(t, q0-1)!='\n')
q0--;
q1 = q0;
while(line>0 && q0>0){
if(textreadc(t, q0-1) == '\n'){
if(--line >= 0)
q1 = q0;
}
--q0;
}
/* :1-1 is :0 = #0, but :1-2 is an error */
if(line > 1)
goto Rescue;
while(q0>0 && textreadc(t, q0-1)!='\n')
--q0;
}
*evalp = TRUE;
return range(q0, q1);
Rescue:
if(showerr)
warning(nil, "address out of range\n");
*evalp = FALSE;
return r;
}
Range
regexp(uint showerr, Text *t, Range lim, Range r, Rune *pat, int dir, int *foundp)
{
int found;
Rangeset sel;
int q;
if(pat[0] == '\0' && rxnull()){
if(showerr)
warning(nil, "no previous regular expression\n");
*foundp = FALSE;
return r;
}
if(pat[0] && rxcompile(pat) == FALSE){
*foundp = FALSE;
return r;
}
if(dir == Back)
found = rxbexecute(t, r.q0, &sel);
else{
if(lim.q0 < 0)
q = Infinity;
else
q = lim.q1;
found = rxexecute(t, nil, r.q1, q, &sel);
}
if(!found && showerr)
warning(nil, "no match for regexp\n");
*foundp = found;
return sel.r[0];
}
Range
address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp)
{
int dir, size, npat;
int prevc, c, nc, n;
uint q;
Rune *pat;
Range r, nr;
r = ar;
q = q0;
dir = None;
size = Line;
c = 0;
while(q < q1){
prevc = c;
c = (*getc)(a, q++);
switch(c){
default:
*qp = q-1;
return r;
case ';':
ar = r;
/* fall through */
case ',':
if(prevc == 0) /* lhs defaults to 0 */
r.q0 = 0;
if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */
r.q1 = t->file->b.nc;
else{
nr = address(showerr, t, lim, ar, a, q, q1, getc, evalp, &q);
r.q1 = nr.q1;
}
*qp = q;
return r;
case '+':
case '-':
if(*evalp && (prevc=='+' || prevc=='-'))
if((nc=(*getc)(a, q))!='#' && nc!='/' && nc!='?')
r = number(showerr, t, r, 1, prevc, Line, evalp); /* do previous one */
dir = c;
break;
case '.':
case '$':
if(q != q0+1){
*qp = q-1;
return r;
}
if(*evalp)
if(c == '.')
r = ar;
else
r = range(t->file->b.nc, t->file->b.nc);
if(q < q1)
dir = Fore;
else
dir = None;
break;
case '#':
if(q==q1 || (c=(*getc)(a, q++))<'0' || '9'<c){
*qp = q-1;
return r;
}
size = Char;
/* fall through */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
n = c -'0';
while(q<q1){
nc = (*getc)(a, q++);
if(nc<'0' || '9'<nc){
q--;
break;
}
n = n*10+(nc-'0');
}
if(*evalp)
r = number(showerr, t, r, n, dir, size, evalp);
dir = None;
size = Line;
break;
case '?':
dir = Back;
/* fall through */
case '/':
npat = 0;
pat = nil;
while(q<q1){
c = (*getc)(a, q++);
switch(c){
case '\n':
--q;
goto out;
case '\\':
pat = runerealloc(pat, npat+1);
pat[npat++] = c;
if(q == q1)
goto out;
c = (*getc)(a, q++);
break;
case '/':
goto out;
}
pat = runerealloc(pat, npat+1);
pat[npat++] = c;
}
out:
pat = runerealloc(pat, npat+1);
pat[npat] = 0;
if(*evalp)
r = regexp(showerr, t, lim, r, pat, dir, evalp);
free(pat);
dir = None;
size = Line;
break;
}
}
if(*evalp && dir != None)
r = number(showerr, t, r, 1, dir, Line, evalp); /* do previous one */
*qp = q;
return r;
}

325
buff.c Normal file
View file

@ -0,0 +1,325 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
enum
{
Slop = 100 /* room to grow with reallocation */
};
static
void
sizecache(Buffer *b, uint n)
{
if(n <= b->cmax)
return;
b->cmax = n+Slop;
b->c = runerealloc(b->c, b->cmax);
}
static
void
addblock(Buffer *b, uint i, uint n)
{
if(i > b->nbl)
error("internal error: addblock");
b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
if(i < b->nbl)
memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
b->bl[i] = disknewblock(disk, n);
b->nbl++;
}
static
void
delblock(Buffer *b, uint i)
{
if(i >= b->nbl)
error("internal error: delblock");
diskrelease(disk, b->bl[i]);
b->nbl--;
if(i < b->nbl)
memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
}
/*
* Move cache so b->cq <= q0 < b->cq+b->cnc.
* If at very end, q0 will fall on end of cache block.
*/
static
void
flush(Buffer *b)
{
if(b->cdirty || b->cnc==0){
if(b->cnc == 0)
delblock(b, b->cbi);
else
diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
b->cdirty = FALSE;
}
}
static
void
setcache(Buffer *b, uint q0)
{
Block **blp, *bl;
uint i, q;
if(q0 > b->nc)
error("internal error: setcache");
/*
* flush and reload if q0 is not in cache.
*/
if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
return;
/*
* if q0 is at end of file and end of cache, continue to grow this block
*/
if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<Maxblock)
return;
flush(b);
/* find block */
if(q0 < b->cq){
q = 0;
i = 0;
}else{
q = b->cq;
i = b->cbi;
}
blp = &b->bl[i];
while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){
q += (*blp)->u.n;
i++;
blp++;
if(i >= b->nbl)
error("block not found");
}
bl = *blp;
/* remember position */
b->cbi = i;
b->cq = q;
sizecache(b, bl->u.n);
b->cnc = bl->u.n;
/*read block*/
diskread(disk, bl, b->c, b->cnc);
}
void
bufinsert(Buffer *b, uint q0, Rune *s, uint n)
{
uint i, m, t, off;
if(q0 > b->nc)
error("internal error: bufinsert");
while(n > 0){
setcache(b, q0);
off = q0-b->cq;
if(b->cnc+n <= Maxblock){
/* Everything fits in one block. */
t = b->cnc+n;
m = n;
if(b->bl == nil){ /* allocate */
if(b->cnc != 0)
error("internal error: bufinsert1 cnc!=0");
addblock(b, 0, t);
b->cbi = 0;
}
sizecache(b, t);
runemove(b->c+off+m, b->c+off, b->cnc-off);
runemove(b->c+off, s, m);
b->cnc = t;
goto Tail;
}
/*
* We must make a new block. If q0 is at
* the very beginning or end of this block,
* just make a new block and fill it.
*/
if(q0==b->cq || q0==b->cq+b->cnc){
if(b->cdirty)
flush(b);
m = min(n, Maxblock);
if(b->bl == nil){ /* allocate */
if(b->cnc != 0)
error("internal error: bufinsert2 cnc!=0");
i = 0;
}else{
i = b->cbi;
if(q0 > b->cq)
i++;
}
addblock(b, i, m);
sizecache(b, m);
runemove(b->c, s, m);
b->cq = q0;
b->cbi = i;
b->cnc = m;
goto Tail;
}
/*
* Split the block; cut off the right side and
* let go of it.
*/
m = b->cnc-off;
if(m > 0){
i = b->cbi+1;
addblock(b, i, m);
diskwrite(disk, &b->bl[i], b->c+off, m);
b->cnc -= m;
}
/*
* Now at end of block. Take as much input
* as possible and tack it on end of block.
*/
m = min(n, Maxblock-b->cnc);
sizecache(b, b->cnc+m);
runemove(b->c+b->cnc, s, m);
b->cnc += m;
Tail:
b->nc += m;
q0 += m;
s += m;
n -= m;
b->cdirty = TRUE;
}
}
void
bufdelete(Buffer *b, uint q0, uint q1)
{
uint m, n, off;
if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
error("internal error: bufdelete");
while(q1 > q0){
setcache(b, q0);
off = q0-b->cq;
if(q1 > b->cq+b->cnc)
n = b->cnc - off;
else
n = q1-q0;
m = b->cnc - (off+n);
if(m > 0)
runemove(b->c+off, b->c+off+n, m);
b->cnc -= n;
b->cdirty = TRUE;
q1 -= n;
b->nc -= n;
}
}
static int
bufloader(void *v, uint q0, Rune *r, int nr)
{
bufinsert(v, q0, r, nr);
return nr;
}
uint
loadfile(int fd, uint q0, int *nulls, int(*f)(void*, uint, Rune*, int), void *arg, DigestState *h)
{
char *p;
Rune *r;
int l, m, n, nb, nr;
uint q1;
p = emalloc((Maxblock+UTFmax+1)*sizeof p[0]);
r = runemalloc(Maxblock);
m = 0;
n = 1;
q1 = q0;
/*
* At top of loop, may have m bytes left over from
* last pass, possibly representing a partial rune.
*/
while(n > 0){
n = read(fd, p+m, Maxblock);
if(n < 0){
warning(nil, "read error in Buffer.load");
break;
}
if(h != nil)
sha1((uchar*)p+m, n, nil, h);
m += n;
p[m] = 0;
l = m;
if(n > 0)
l -= UTFmax;
cvttorunes(p, l, r, &nb, &nr, nulls);
memmove(p, p+nb, m-nb);
m -= nb;
q1 += (*f)(arg, q1, r, nr);
}
free(p);
free(r);
return q1-q0;
}
uint
bufload(Buffer *b, uint q0, int fd, int *nulls, DigestState *h)
{
if(q0 > b->nc)
error("internal error: bufload");
return loadfile(fd, q0, nulls, bufloader, b, h);
}
void
bufread(Buffer *b, uint q0, Rune *s, uint n)
{
uint m;
if(!(q0<=b->nc && q0+n<=b->nc))
error("bufread: internal error");
while(n > 0){
setcache(b, q0);
m = min(n, b->cnc-(q0-b->cq));
runemove(s, b->c+(q0-b->cq), m);
q0 += m;
s += m;
n -= m;
}
}
void
bufreset(Buffer *b)
{
int i;
b->nc = 0;
b->cnc = 0;
b->cq = 0;
b->cdirty = 0;
b->cbi = 0;
/* delete backwards to avoid n² behavior */
for(i=b->nbl-1; --i>=0; )
delblock(b, i);
}
void
bufclose(Buffer *b)
{
bufreset(b);
free(b->c);
b->c = nil;
b->cnc = 0;
free(b->bl);
b->bl = nil;
b->nbl = 0;
}

586
cols.c Normal file
View file

@ -0,0 +1,586 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Rune Lheader[] = {
'N', 'e', 'w', ' ',
'C', 'u', 't', ' ',
'P', 'a', 's', 't', 'e', ' ',
'S', 'n', 'a', 'r', 'f', ' ',
'S', 'o', 'r', 't', ' ',
'Z', 'e', 'r', 'o', 'x', ' ',
'D', 'e', 'l', 'c', 'o', 'l', ' ',
0
};
void
colinit(Column *c, Rectangle r)
{
Rectangle r1;
Text *t;
draw(screen, r, allocimage(display,Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
c->r = r;
c->w = nil;
c->nw = 0;
t = &c->tag;
t->w = nil;
t->col = c;
r1 = r;
r1.max.y = r1.min.y + font->height;
textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
t->what = Columntag;
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
textinsert(t, 0, Lheader, 38, TRUE);
textsetselect(t, t->file->b.nc, t->file->b.nc);
draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
c->safe = TRUE;
}
Window*
coladd(Column *c, Window *w, Window *clone, int y)
{
Rectangle r, r1;
Window *v;
int i, j, minht, ymax, buggered;
v = nil;
r = c->r;
r.min.y = c->tag.fr.r.max.y+Border;
if(y<r.min.y && c->nw>0){ /* steal half of last window by default */
v = c->w[c->nw-1];
y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
}
/* look for window we'll land on */
for(i=0; i<c->nw; i++){
v = c->w[i];
if(y < v->r.max.y)
break;
}
buggered = 0;
if(c->nw > 0){
if(i < c->nw)
i++; /* new window will go after v */
/*
* if landing window (v) is too small, grow it first.
*/
minht = v->tag.fr.font->height+Border+1;
j = 0;
while(!c->safe || v->body.fr.maxlines<=3 || Dy(v->body.all) <= minht){
if(++j > 10){
buggered = 1; /* too many windows in column */
break;
}
colgrow(c, v, 1);
}
/*
* figure out where to split v to make room for w
*/
/* new window stops where next window begins */
if(i < c->nw)
ymax = c->w[i]->r.min.y-Border;
else
ymax = c->r.max.y;
/* new window must start after v's tag ends */
y = max(y, v->tagtop.max.y+Border);
/* new window must start early enough to end before ymax */
y = min(y, ymax - minht);
/* if y is too small, too many windows in column */
if(y < v->tagtop.max.y+Border)
buggered = 1;
/*
* resize & redraw v
*/
r = v->r;
r.max.y = ymax;
draw(screen, r, textcols[BACK], nil, ZP);
r1 = r;
y = min(y, ymax-(v->tag.fr.font->height*v->taglines+v->body.fr.font->height+Border+1));
r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height);
r1.min.y = winresize(v, r1, FALSE, FALSE);
r1.max.y = r1.min.y+Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
/*
* leave r with w's coordinates
*/
r.min.y = r1.max.y;
}
if(w == nil){
w = emalloc(sizeof(Window));
w->col = c;
draw(screen, r, textcols[BACK], nil, ZP);
wininit(w, clone, r);
}else{
w->col = c;
winresize(w, r, FALSE, TRUE);
}
w->tag.col = c;
w->tag.row = c->row;
w->body.col = c;
w->body.row = c->row;
c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
c->nw++;
c->w[i] = w;
c->safe = TRUE;
/* if there were too many windows, redraw the whole column */
if(buggered)
colresize(c, c->r);
savemouse(w);
/* near the button, but in the body */
moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
barttext = &w->body;
return w;
}
void
colclose(Column *c, Window *w, int dofree)
{
Rectangle r;
int i, didmouse, up;
/* w is locked */
if(!c->safe)
colgrow(c, w, 1);
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
r = w->r;
w->tag.col = nil;
w->body.col = nil;
w->col = nil;
didmouse = restoremouse(w);
if(dofree){
windelete(w);
winclose(w);
}
c->nw--;
memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
c->w = realloc(c->w, c->nw*sizeof(Window*));
if(c->nw == 0){
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
return;
}
up = 0;
if(i == c->nw){ /* extend last window down */
w = c->w[i-1];
r.min.y = w->r.min.y;
r.max.y = c->r.max.y;
}else{ /* extend next window up */
up = 1;
w = c->w[i];
r.max.y = w->r.max.y;
}
draw(screen, r, textcols[BACK], nil, ZP);
if(c->safe) {
if(!didmouse && up)
w->showdel = TRUE;
winresize(w, r, FALSE, TRUE);
if(!didmouse && up)
movetodel(w);
}
}
void
colcloseall(Column *c)
{
int i;
Window *w;
if(c == activecol)
activecol = nil;
textclose(&c->tag);
for(i=0; i<c->nw; i++){
w = c->w[i];
winclose(w);
}
c->nw = 0;
free(c->w);
free(c);
clearmouse();
}
void
colmousebut(Column *c)
{
moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
}
void
colresize(Column *c, Rectangle r)
{
int i;
Rectangle r1, r2;
Window *w;
clearmouse();
r1 = r;
r1.max.y = r1.min.y + c->tag.fr.font->height;
textresize(&c->tag, r1, TRUE);
draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r1.max.y = r.max.y;
for(i=0; i<c->nw; i++){
w = c->w[i];
w->maxlines = 0;
if(i == c->nw-1)
r1.max.y = r.max.y;
else{
r1.max.y = r1.min.y;
if(Dy(c->r) != 0){
r1.max.y += (Dy(w->r)+Border)*Dy(r)/Dy(c->r);
}
}
r1.max.y = max(r1.max.y, r1.min.y + Border+font->height);
r2 = r1;
r2.max.y = r2.min.y+Border;
draw(screen, r2, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r1.min.y = r2.max.y;
r1.min.y = winresize(w, r1, FALSE, i==c->nw-1);
}
c->r = r;
}
static
int
colcmp(const void *a, const void *b)
{
Rune *r1, *r2;
int i, nr1, nr2;
r1 = (*(Window**)a)->body.file->name;
nr1 = (*(Window**)a)->body.file->nname;
r2 = (*(Window**)b)->body.file->name;
nr2 = (*(Window**)b)->body.file->nname;
for(i=0; i<nr1 && i<nr2; i++){
if(*r1 != *r2)
return *r1-*r2;
r1++;
r2++;
}
return nr1-nr2;
}
void
colsort(Column *c)
{
int i, y;
Rectangle r, r1, *rp;
Window **wp, *w;
if(c->nw == 0)
return;
clearmouse();
rp = emalloc(c->nw*sizeof(Rectangle));
wp = emalloc(c->nw*sizeof(Window*));
memmove(wp, c->w, c->nw*sizeof(Window*));
qsort(wp, c->nw, sizeof(Window*), colcmp);
for(i=0; i<c->nw; i++)
rp[i] = wp[i]->r;
r = c->r;
r.min.y = c->tag.fr.r.max.y;
draw(screen, r, textcols[BACK], nil, ZP);
y = r.min.y;
for(i=0; i<c->nw; i++){
w = wp[i];
r.min.y = y;
if(i == c->nw-1)
r.max.y = c->r.max.y;
else
r.max.y = r.min.y+Dy(w->r)+Border;
r1 = r;
r1.max.y = r1.min.y+Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.min.y = r1.max.y;
y = winresize(w, r, FALSE, i==c->nw-1);
}
free(rp);
free(c->w);
c->w = wp;
}
void
colgrow(Column *c, Window *w, int but)
{
Rectangle r, cr;
int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
Window *v;
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
cr = c->r;
if(but < 0){ /* make sure window fills its own space properly */
r = w->r;
if(i==c->nw-1 || c->safe==FALSE)
r.max.y = cr.max.y;
else
r.max.y = c->w[i+1]->r.min.y - Border;
winresize(w, r, FALSE, TRUE);
return;
}
cr.min.y = c->w[0]->r.min.y;
if(but == 3){ /* full size */
if(i != 0){
v = c->w[0];
c->w[0] = w;
c->w[i] = v;
}
draw(screen, cr, textcols[BACK], nil, ZP);
winresize(w, cr, FALSE, TRUE);
for(i=1; i<c->nw; i++)
c->w[i]->body.fr.maxlines = 0;
c->safe = FALSE;
return;
}
/* store old #lines for each window */
onl = w->body.fr.maxlines;
nl = emalloc(c->nw * sizeof(int));
ny = emalloc(c->nw * sizeof(int));
tot = 0;
for(j=0; j<c->nw; j++){
l = c->w[j]->taglines-1 + c->w[j]->body.fr.maxlines;
nl[j] = l;
tot += l;
}
/* approximate new #lines for this window */
if(but == 2){ /* as big as can be */
memset(nl, 0, c->nw * sizeof(int));
goto Pack;
}
nnl = min(onl + max(min(5, w->taglines-1+w->maxlines), onl/2), tot);
if(nnl < w->taglines-1+w->maxlines)
nnl = (w->taglines-1+w->maxlines + nnl)/2;
if(nnl == 0)
nnl = 2;
dnl = nnl - onl;
/* compute new #lines for each window */
for(k=1; k<c->nw; k++){
/* prune from later window */
j = i+k;
if(j<c->nw && nl[j]){
l = min(dnl, max(1, nl[j]/2));
nl[j] -= l;
nl[i] += l;
dnl -= l;
}
/* prune from earlier window */
j = i-k;
if(j>=0 && nl[j]){
l = min(dnl, max(1, nl[j]/2));
nl[j] -= l;
nl[i] += l;
dnl -= l;
}
}
Pack:
/* pack everyone above */
y1 = cr.min.y;
for(j=0; j<i; j++){
v = c->w[j];
r = v->r;
r.min.y = y1;
r.max.y = y1+Dy(v->tagtop);
if(nl[j])
r.max.y += 1 + nl[j]*v->body.fr.font->height;
r.min.y = winresize(v, r, c->safe, FALSE);
r.max.y += Border;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
y1 = r.max.y;
}
/* scan to see new size of everyone below */
y2 = c->r.max.y;
for(j=c->nw-1; j>i; j--){
v = c->w[j];
r = v->r;
r.min.y = y2-Dy(v->tagtop);
if(nl[j])
r.min.y -= 1 + nl[j]*v->body.fr.font->height;
r.min.y -= Border;
ny[j] = r.min.y;
y2 = r.min.y;
}
/* compute new size of window */
r = w->r;
r.min.y = y1;
r.max.y = y2;
h = w->body.fr.font->height;
if(Dy(r) < Dy(w->tagtop)+1+h+Border)
r.max.y = r.min.y + Dy(w->tagtop)+1+h+Border;
/* draw window */
r.max.y = winresize(w, r, c->safe, TRUE);
if(i < c->nw-1){
r.min.y = r.max.y;
r.max.y += Border;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
for(j=i+1; j<c->nw; j++)
ny[j] -= (y2-r.max.y);
}
/* pack everyone below */
y1 = r.max.y;
for(j=i+1; j<c->nw; j++){
v = c->w[j];
r = v->r;
r.min.y = y1;
r.max.y = y1+Dy(v->tagtop);
if(nl[j])
r.max.y += 1 + nl[j]*v->body.fr.font->height;
y1 = winresize(v, r, c->safe, j==c->nw-1);
if(j < c->nw-1){ /* no border on last window */
r.min.y = y1;
r.max.y += Border;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
y1 = r.max.y;
}
}
free(nl);
free(ny);
c->safe = TRUE;
winmousebut(w);
}
void
coldragwin(Column *c, Window *w, int but)
{
Rectangle r;
int i, b;
Point p, op;
Window *v;
Column *nc;
clearmouse();
setcursor2(mousectl, &boxcursor, &boxcursor2);
b = mouse->buttons;
op = mouse->xy;
while(mouse->buttons == b)
readmouse(mousectl);
setcursor(mousectl, nil);
if(mouse->buttons){
while(mouse->buttons)
readmouse(mousectl);
return;
}
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
if(w->tagexpand) /* force recomputation of window tag size */
w->taglines = 1;
p = mouse->xy;
if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
colgrow(c, w, but);
winmousebut(w);
return;
}
/* is it a flick to the right? */
if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
p.x = op.x+Dx(w->r); /* yes: toss to next column */
nc = rowwhichcol(c->row, p);
if(nc!=nil && nc!=c){
colclose(c, w, FALSE);
coladd(nc, w, nil, p.y);
winmousebut(w);
return;
}
if(i==0 && c->nw==1)
return; /* can't do it */
if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
|| (i==0 && p.y>w->r.max.y)){
/* shuffle */
colclose(c, w, FALSE);
coladd(c, w, nil, p.y);
winmousebut(w);
return;
}
if(i == 0)
return;
v = c->w[i-1];
if(p.y < v->tagtop.max.y)
p.y = v->tagtop.max.y;
if(p.y > w->r.max.y-Dy(w->tagtop)-Border)
p.y = w->r.max.y-Dy(w->tagtop)-Border;
r = v->r;
r.max.y = p.y;
if(r.max.y > v->body.fr.r.min.y){
r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height;
if(v->body.fr.r.min.y == v->body.fr.r.max.y)
r.max.y++;
}
r.min.y = winresize(v, r, c->safe, FALSE);
r.max.y = r.min.y+Border;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.min.y = r.max.y;
if(i == c->nw-1)
r.max.y = c->r.max.y;
else
r.max.y = c->w[i+1]->r.min.y-Border;
winresize(w, r, c->safe, TRUE);
c->safe = TRUE;
winmousebut(w);
}
Text*
colwhich(Column *c, Point p)
{
int i;
Window *w;
if(!ptinrect(p, c->r))
return nil;
if(ptinrect(p, c->tag.all))
return &c->tag;
for(i=0; i<c->nw; i++){
w = c->w[i];
if(ptinrect(p, w->r)){
if(ptinrect(p, w->tagtop) || ptinrect(p, w->tag.all))
return &w->tag;
/* exclude partial line at bottom */
if(p.x >= w->body.scrollr.max.x && p.y >= w->body.fr.r.max.y)
return nil;
return &w->body;
}
}
return nil;
}
int
colclean(Column *c)
{
int i, clean;
clean = TRUE;
for(i=0; i<c->nw; i++)
clean &= winclean(c->w[i], TRUE);
return clean;
}

581
dat.h Normal file
View file

@ -0,0 +1,581 @@
enum
{
Qdir,
Qacme,
Qcons,
Qconsctl,
Qdraw,
Qeditout,
Qindex,
Qlabel,
Qlog,
Qnew,
QWaddr,
QWbody,
QWctl,
QWdata,
QWeditout,
QWerrors,
QWevent,
QWrdsel,
QWwrsel,
QWtag,
QWxdata,
QMAX
};
enum
{
Blockincr = 256,
Maxblock = 8*1024,
NRange = 10,
Infinity = 0x7FFFFFFF /* huge value for regexp address */
};
#define Buffer AcmeBuffer
typedef struct Block Block;
typedef struct Buffer Buffer;
typedef struct Command Command;
typedef struct Column Column;
typedef struct Dirlist Dirlist;
typedef struct Dirtab Dirtab;
typedef struct Disk Disk;
typedef struct Expand Expand;
typedef struct Fid Fid;
typedef struct File File;
typedef struct Elog Elog;
typedef struct Mntdir Mntdir;
typedef struct Range Range;
typedef struct Rangeset Rangeset;
typedef struct Reffont Reffont;
typedef struct Row Row;
typedef struct Runestr Runestr;
typedef struct Text Text;
typedef struct Timer Timer;
typedef struct Window Window;
typedef struct Xfid Xfid;
struct Runestr
{
Rune *r;
int nr;
};
struct Range
{
int q0;
int q1;
};
struct Block
{
vlong addr; /* disk address in bytes */
union
{
uint n; /* number of used runes in block */
Block *next; /* pointer to next in free list */
} u;
};
struct Disk
{
int fd;
vlong addr; /* length of temp file */
Block *free[Maxblock/Blockincr+1];
};
Disk* diskinit(void);
Block* disknewblock(Disk*, uint);
void diskrelease(Disk*, Block*);
void diskread(Disk*, Block*, Rune*, uint);
void diskwrite(Disk*, Block**, Rune*, uint);
struct Buffer
{
uint nc;
Rune *c; /* cache */
uint cnc; /* bytes in cache */
uint cmax; /* size of allocated cache */
uint cq; /* position of cache */
int cdirty; /* cache needs to be written */
uint cbi; /* index of cache Block */
Block **bl; /* array of blocks */
uint nbl; /* number of blocks */
};
void bufinsert(Buffer*, uint, Rune*, uint);
void bufdelete(Buffer*, uint, uint);
uint bufload(Buffer*, uint, int, int*, DigestState*);
void bufread(Buffer*, uint, Rune*, uint);
void bufclose(Buffer*);
void bufreset(Buffer*);
struct Elog
{
short type; /* Delete, Insert, Filename */
uint q0; /* location of change (unused in f) */
uint nd; /* number of deleted characters */
uint nr; /* # runes in string or file name */
Rune *r;
};
void elogterm(File*);
void elogclose(File*);
void eloginsert(File*, int, Rune*, int);
void elogdelete(File*, int, int);
void elogreplace(File*, int, int, Rune*, int);
void elogapply(File*);
struct File
{
Buffer b; /* the data */
Buffer delta; /* transcript of changes */
Buffer epsilon; /* inversion of delta for redo */
Buffer *elogbuf; /* log of pending editor changes */
Elog elog; /* current pending change */
Rune *name; /* name of associated file */
int nname; /* size of name */
uvlong qidpath; /* of file when read */
ulong mtime; /* of file when read */
int dev; /* of file when read */
uchar sha1[20]; /* of file when read */
int unread; /* file has not been read from disk */
int editclean; /* mark clean after edit command */
int seq; /* if seq==0, File acts like Buffer */
int mod;
Text *curtext; /* most recently used associated text */
Text **text; /* list of associated texts */
int ntext;
int dumpid; /* used in dumping zeroxed windows */
};
File* fileaddtext(File*, Text*);
void fileclose(File*);
void filedelete(File*, uint, uint);
void filedeltext(File*, Text*);
void fileinsert(File*, uint, Rune*, uint);
uint fileload(File*, uint, int, int*, DigestState*);
void filemark(File*);
void filereset(File*);
void filesetname(File*, Rune*, int);
void fileundelete(File*, Buffer*, uint, uint);
void fileuninsert(File*, Buffer*, uint, uint);
void fileunsetname(File*, Buffer*);
void fileundo(File*, int, uint*, uint*);
uint fileredoseq(File*);
enum /* Text.what */
{
Columntag,
Rowtag,
Tag,
Body
};
struct Text
{
File *file;
Frame fr;
Reffont *reffont;
uint org;
uint q0;
uint q1;
int what;
int tabstop;
Window *w;
Rectangle scrollr;
Rectangle lastsr;
Rectangle all;
Row *row;
Column *col;
uint iq1; /* last input position */
uint eq0; /* start of typing for ESC */
uint cq0; /* cache position */
int ncache; /* storage for insert */
int ncachealloc;
Rune *cache;
int nofill;
int needundo;
};
uint textbacknl(Text*, uint, uint);
uint textbsinsert(Text*, uint, Rune*, uint, int, int*);
int textbswidth(Text*, Rune);
int textclickhtmlmatch(Text*, uint*, uint*);
int textclickmatch(Text*, int, int, int, uint*);
void textclose(Text*);
void textcolumnate(Text*, Dirlist**, int);
void textcommit(Text*, int);
void textconstrain(Text*, uint, uint, uint*, uint*);
void textdelete(Text*, uint, uint, int);
void textdoubleclick(Text*, uint*, uint*);
void textfill(Text*);
void textframescroll(Text*, int);
void textinit(Text*, File*, Rectangle, Reffont*, Image**);
void textinsert(Text*, uint, Rune*, uint, int);
int textload(Text*, uint, char*, int);
Rune textreadc(Text*, uint);
void textredraw(Text*, Rectangle, Font*, Image*, int);
void textreset(Text*);
int textresize(Text*, Rectangle, int);
void textscrdraw(Text*);
void textscroll(Text*, int);
void textselect(Text*);
int textselect2(Text*, uint*, uint*, Text**);
int textselect23(Text*, uint*, uint*, Image*, int);
int textselect3(Text*, uint*, uint*);
void textsetorigin(Text*, uint, int);
void textsetselect(Text*, uint, uint);
void textshow(Text*, uint, uint, int);
void texttype(Text*, Rune);
struct Window
{
QLock lk;
Ref ref;
Text tag;
Text body;
Rectangle r;
uchar isdir;
uchar isscratch;
uchar filemenu;
uchar dirty;
uchar autoindent;
uchar showdel;
int id;
Range addr;
Range limit;
uchar nopen[QMAX];
uchar nomark;
Range wrselrange;
int rdselfd;
Column *col;
Xfid *eventx;
char *events;
int nevents;
int owner;
int maxlines;
Dirlist **dlp;
int ndl;
int putseq;
int nincl;
Rune **incl;
Reffont *reffont;
QLock ctllock;
uint ctlfid;
char *dumpstr;
char *dumpdir;
int dumpid;
int utflastqid;
int utflastboff;
int utflastq;
int tagsafe; /* taglines is correct */
int tagexpand;
int taglines;
Rectangle tagtop;
QLock editoutlk;
};
void wininit(Window*, Window*, Rectangle);
void winlock(Window*, int);
void winlock1(Window*, int);
void winunlock(Window*);
void wintype(Window*, Text*, Rune);
void winundo(Window*, int);
void winsetname(Window*, Rune*, int);
void winsettag(Window*);
void winsettag1(Window*);
void wincommit(Window*, Text*);
int winresize(Window*, Rectangle, int, int);
void winclose(Window*);
void windelete(Window*);
int winclean(Window*, int);
void windirfree(Window*);
void winevent(Window*, char*, ...);
void winmousebut(Window*);
void winaddincl(Window*, Rune*, int);
void wincleartag(Window*);
char *winctlprint(Window*, char*, int);
struct Column
{
Rectangle r;
Text tag;
Row *row;
Window **w;
int nw;
int safe;
};
void colinit(Column*, Rectangle);
Window* coladd(Column*, Window*, Window*, int);
void colclose(Column*, Window*, int);
void colcloseall(Column*);
void colresize(Column*, Rectangle);
Text* colwhich(Column*, Point);
void coldragwin(Column*, Window*, int);
void colgrow(Column*, Window*, int);
int colclean(Column*);
void colsort(Column*);
void colmousebut(Column*);
struct Row
{
QLock lk;
Rectangle r;
Text tag;
Column **col;
int ncol;
};
void rowinit(Row*, Rectangle);
Column* rowadd(Row*, Column *c, int);
void rowclose(Row*, Column*, int);
Text* rowwhich(Row*, Point);
Column* rowwhichcol(Row*, Point);
void rowresize(Row*, Rectangle);
Text* rowtype(Row*, Rune, Point);
void rowdragcol(Row*, Column*, int but);
int rowclean(Row*);
void rowdump(Row*, char*);
int rowload(Row*, char*, int);
void rowloadfonts(char*);
struct Timer
{
int dt;
int cancel;
Channel *c; /* chan(int) */
Timer *next;
};
struct Command
{
int pid;
Rune *name;
int nname;
char *text;
char **av;
int iseditcmd;
Mntdir *md;
Command *next;
};
struct Dirtab
{
char *name;
uchar type;
uint qid;
uint perm;
};
struct Mntdir
{
int id;
int ref;
Rune *dir;
int ndir;
Mntdir *next;
int nincl;
Rune **incl;
};
struct Fid
{
int fid;
int busy;
int open;
Qid qid;
Window *w;
Dirtab *dir;
Fid *next;
Mntdir *mntdir;
int nrpart;
uchar rpart[UTFmax];
vlong logoff; // for putlog
};
struct Xfid
{
void *arg; /* args to xfidinit */
Fcall fcall;
Xfid *next;
Channel *c; /* chan(void(*)(Xfid*)) */
Fid *f;
uchar *buf;
int flushed;
};
void xfidctl(void *);
void xfidflush(Xfid*);
void xfidopen(Xfid*);
void xfidclose(Xfid*);
void xfidread(Xfid*);
void xfidwrite(Xfid*);
void xfidctlwrite(Xfid*, Window*);
void xfideventread(Xfid*, Window*);
void xfideventwrite(Xfid*, Window*);
void xfidindexread(Xfid*);
void xfidutfread(Xfid*, Text*, uint, int);
int xfidruneread(Xfid*, Text*, uint, uint);
void xfidlogopen(Xfid*);
void xfidlogread(Xfid*);
void xfidlogflush(Xfid*);
void xfidlog(Window*, char*);
struct Reffont
{
Ref ref;
Font *f;
};
Reffont *rfget(int, int, int, char*);
void rfclose(Reffont*);
struct Rangeset
{
Range r[NRange];
};
struct Dirlist
{
Rune *r;
int nr;
int wid;
};
struct Expand
{
uint q0;
uint q1;
Rune *name;
int nname;
char *bname;
int jump;
union{
Text *at;
Rune *ar;
} u;
int (*agetc)(void*, uint);
int a0;
int a1;
};
enum
{
/* fbufalloc() guarantees room off end of BUFSIZE */
BUFSIZE = Maxblock+IOHDRSZ, /* size from fbufalloc() */
RBUFSIZE = BUFSIZE/sizeof(Rune),
EVENTSIZE = 256,
};
#define Scrollwid scalesize(display, 12)
#define Scrollgap scalesize(display, 4)
#define Margin scalesize(display, 4)
#define Border scalesize(display, 2)
#define ButtonBorder scalesize(display, 2)
#define QID(w,q) ((w<<8)|(q))
#define WIN(q) ((((ulong)(q).path)>>8) & 0xFFFFFF)
#define FILE(q) ((q).path & 0xFF)
#undef FALSE
#undef TRUE
enum
{
FALSE,
TRUE,
XXX
};
enum
{
Empty = 0,
Null = '-',
Delete = 'd',
Insert = 'i',
Replace = 'r',
Filename = 'f'
};
enum /* editing */
{
Inactive = 0,
Inserting,
Collecting
};
uint globalincref;
uint seq;
uint maxtab; /* size of a tab, in units of the '0' character */
Display *display;
Image *screen;
Font *font;
Mouse *mouse;
Mousectl *mousectl;
Keyboardctl *keyboardctl;
Reffont reffont;
Image *modbutton;
Image *colbutton;
Image *button;
Image *but2col;
Image *but3col;
Cursor boxcursor;
Cursor2 boxcursor2;
Row row;
int timerpid;
Disk *disk;
Text *seltext;
Text *argtext;
Text *mousetext; /* global because Text.close needs to clear it */
Text *typetext; /* global because Text.close needs to clear it */
Text *barttext; /* shared between mousetask and keyboardthread */
int bartflag;
int swapscrollbuttons;
Window *activewin;
Column *activecol;
Buffer snarfbuf;
Rectangle nullrect;
int fsyspid;
char *cputype;
char *objtype;
char *home;
char *acmeshell;
char *fontnames[2];
Image *tagcols[NCOL];
Image *textcols[NCOL];
extern char wdir[]; /* must use extern because no dimension given */
int editing;
int erroutfd;
int messagesize; /* negotiated in 9P version setup */
int globalautoindent;
int dodollarsigns;
char* mtpt;
enum
{
Kscrolloneup = KF|0x20,
Kscrollonedown = KF|0x21
};
Channel *cplumb; /* chan(Plumbmsg*) */
Channel *cwait; /* chan(Waitmsg) */
Channel *ccommand; /* chan(Command*) */
Channel *ckill; /* chan(Rune*) */
Channel *cxfidalloc; /* chan(Xfid*) */
Channel *cxfidfree; /* chan(Xfid*) */
Channel *cnewwindow; /* chan(Channel*) */
Channel *mouseexit0; /* chan(int) */
Channel *mouseexit1; /* chan(int) */
Channel *cexit; /* chan(int) */
Channel *cerr; /* chan(char*) */
Channel *cedit; /* chan(int) */
Channel *cwarn; /* chan(void*)[1] (really chan(unit)[1]) */
QLock editoutlk;
#define STACK 65536

133
disk.c Normal file
View file

@ -0,0 +1,133 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Block *blist;
int
tempfile(void)
{
char buf[128];
int i, fd;
snprint(buf, sizeof buf, "/tmp/X%d.%.4sacme", getpid(), getuser());
for(i='A'; i<='Z'; i++){
buf[5] = i;
if(access(buf, AEXIST) == 0)
continue;
fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
if(fd >= 0)
return fd;
}
return -1;
}
Disk*
diskinit()
{
Disk *d;
d = emalloc(sizeof(Disk));
d->fd = tempfile();
if(d->fd < 0){
fprint(2, "acme: can't create temp file: %r\n");
threadexitsall("diskinit");
}
return d;
}
static
uint
ntosize(uint n, uint *ip)
{
uint size;
if(n > Maxblock)
error("internal error: ntosize");
size = n;
if(size & (Blockincr-1))
size += Blockincr - (size & (Blockincr-1));
/* last bucket holds blocks of exactly Maxblock */
if(ip)
*ip = size/Blockincr;
return size * sizeof(Rune);
}
Block*
disknewblock(Disk *d, uint n)
{
uint i, j, size;
Block *b;
size = ntosize(n, &i);
b = d->free[i];
if(b)
d->free[i] = b->u.next;
else{
/* allocate in chunks to reduce malloc overhead */
if(blist == nil){
blist = emalloc(100*sizeof(Block));
for(j=0; j<100-1; j++)
blist[j].u.next = &blist[j+1];
}
b = blist;
blist = b->u.next;
b->addr = d->addr;
if(d->addr+size < d->addr){
error("temp file overflow");
}
d->addr += size;
}
b->u.n = n;
return b;
}
void
diskrelease(Disk *d, Block *b)
{
uint i;
ntosize(b->u.n, &i);
b->u.next = d->free[i];
d->free[i] = b;
}
void
diskwrite(Disk *d, Block **bp, Rune *r, uint n)
{
int size, nsize;
Block *b;
b = *bp;
size = ntosize(b->u.n, nil);
nsize = ntosize(n, nil);
if(size != nsize){
diskrelease(d, b);
b = disknewblock(d, n);
*bp = b;
}
if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
error("write error to temp file");
b->u.n = n;
}
void
diskread(Disk *d, Block *b, Rune *r, uint n)
{
if(n > b->u.n)
error("internal error: diskread");
ntosize(b->u.n, nil);
if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
error("read error from temp file");
}

1377
ecmd.c Normal file

File diff suppressed because it is too large Load diff

684
edit.c Normal file
View file

@ -0,0 +1,684 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "edit.h"
#include "fns.h"
static char linex[]="\n";
static char wordx[]=" \t\n";
struct cmdtab cmdtab[]={
/* cmdc text regexp addr defcmd defaddr count token fn */
'\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd,
'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd,
'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd,
'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd,
'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd,
'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd,
'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd,
'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd,
'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd,
's', 0, 1, 0, 0, aDot, 1, 0, s_cmd,
't', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd,
'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd,
'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
'=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd,
'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd,
'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd,
'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
'<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
'|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
'>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd,
/* deliberately unimplemented:
'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd,
'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd,
'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd,
'!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd,
*/
0, 0, 0, 0, 0, 0, 0, 0
};
Cmd *parsecmd(int);
Addr *compoundaddr(void);
Addr *simpleaddr(void);
void freecmd(void);
void okdelim(int);
Rune *cmdstartp;
Rune *cmdendp;
Rune *cmdp;
Channel *editerrc;
String *lastpat;
int patset;
List cmdlist;
List addrlist;
List stringlist;
Text *curtext;
int editing = Inactive;
String* newstring(int);
void
editthread(void *v)
{
Cmd *cmdp;
USED(v);
threadsetname("editthread");
while((cmdp=parsecmd(0)) != 0){
if(cmdexec(curtext, cmdp) == 0)
break;
freecmd();
}
sendp(editerrc, nil);
}
void
allelogterm(Window *w, void *x)
{
USED(x);
elogterm(w->body.file);
}
void
alleditinit(Window *w, void *x)
{
USED(x);
textcommit(&w->tag, TRUE);
textcommit(&w->body, TRUE);
w->body.file->editclean = FALSE;
}
void
allupdate(Window *w, void *x)
{
Text *t;
int i;
File *f;
USED(x);
t = &w->body;
f = t->file;
if(f->curtext != t) /* do curtext only */
return;
if(f->elog.type == Null)
elogterm(f);
else if(f->elog.type != Empty){
elogapply(f);
if(f->editclean){
f->mod = FALSE;
for(i=0; i<f->ntext; i++)
f->text[i]->w->dirty = FALSE;
}
}
textsetselect(t, t->q0, t->q1);
textscrdraw(t);
winsettag(w);
}
void
editerror(char *fmt, ...)
{
va_list arg;
char *s;
va_start(arg, fmt);
s = vsmprint(fmt, arg);
va_end(arg);
freecmd();
allwindows(allelogterm, nil); /* truncate the edit logs */
sendp(editerrc, s);
threadexits(nil);
}
void
editcmd(Text *ct, Rune *r, uint n)
{
char *err;
if(n == 0)
return;
if(2*n > RBUFSIZE){
warning(nil, "string too long\n");
return;
}
allwindows(alleditinit, nil);
if(cmdstartp)
free(cmdstartp);
cmdstartp = runemalloc(n+2);
runemove(cmdstartp, r, n);
if(r[n-1] != '\n')
cmdstartp[n++] = '\n';
cmdstartp[n] = '\0';
cmdendp = cmdstartp+n;
cmdp = cmdstartp;
if(ct->w == nil)
curtext = nil;
else
curtext = &ct->w->body;
resetxec();
if(editerrc == nil){
editerrc = chancreate(sizeof(char*), 0);
chansetname(editerrc, "editerrc");
lastpat = allocstring(0);
}
threadcreate(editthread, nil, STACK);
err = recvp(editerrc);
editing = Inactive;
if(err != nil){
if(err[0] != '\0')
warning(nil, "Edit: %s\n", err);
free(err);
}
/* update everyone whose edit log has data */
allwindows(allupdate, nil);
}
int
getch(void)
{
if(cmdp == cmdendp)
return -1;
return *cmdp++;
}
int
nextc(void)
{
if(cmdp == cmdendp)
return -1;
return *cmdp;
}
void
ungetch(void)
{
if(--cmdp < cmdstartp)
error("ungetch");
}
long
getnum(int signok)
{
long n;
int c, sign;
n = 0;
sign = 1;
if(signok>1 && nextc()=='-'){
sign = -1;
getch();
}
if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
return sign;
while('0'<=(c=getch()) && c<='9')
n = n*10 + (c-'0');
ungetch();
return sign*n;
}
int
cmdskipbl(void)
{
int c;
do
c = getch();
while(c==' ' || c=='\t');
if(c >= 0)
ungetch();
return c;
}
/*
* Check that list has room for one more element.
*/
void
growlist(List *l)
{
if(l->u.listptr==0 || l->nalloc==0){
l->nalloc = INCR;
l->u.listptr = emalloc(INCR*sizeof(void*));
l->nused = 0;
}else if(l->nused == l->nalloc){
l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*));
memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*));
l->nalloc += INCR;
}
}
/*
* Remove the ith element from the list
*/
void
dellist(List *l, int i)
{
memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*));
l->nused--;
}
/*
* Add a new element, whose position is i, to the list
*/
void
inslist(List *l, int i, void *v)
{
growlist(l);
memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*));
l->u.ptr[i] = v;
l->nused++;
}
void
listfree(List *l)
{
free(l->u.listptr);
free(l);
}
String*
allocstring(int n)
{
String *s;
s = emalloc(sizeof(String));
s->n = n;
s->nalloc = n+10;
s->r = emalloc(s->nalloc*sizeof(Rune));
s->r[n] = '\0';
return s;
}
void
freestring(String *s)
{
free(s->r);
free(s);
}
Cmd*
newcmd(void){
Cmd *p;
p = emalloc(sizeof(Cmd));
inslist(&cmdlist, cmdlist.nused, p);
return p;
}
String*
newstring(int n)
{
String *p;
p = allocstring(n);
inslist(&stringlist, stringlist.nused, p);
return p;
}
Addr*
newaddr(void)
{
Addr *p;
p = emalloc(sizeof(Addr));
inslist(&addrlist, addrlist.nused, p);
return p;
}
void
freecmd(void)
{
int i;
while(cmdlist.nused > 0)
free(cmdlist.u.ucharptr[--cmdlist.nused]);
while(addrlist.nused > 0)
free(addrlist.u.ucharptr[--addrlist.nused]);
while(stringlist.nused>0){
i = --stringlist.nused;
freestring(stringlist.u.stringptr[i]);
}
}
void
okdelim(int c)
{
if(c=='\\' || ('a'<=c && c<='z')
|| ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
editerror("bad delimiter %c\n", c);
}
void
atnl(void)
{
int c;
cmdskipbl();
c = getch();
if(c != '\n')
editerror("newline expected (saw %C)", c);
}
void
Straddc(String *s, int c)
{
if(s->n+1 >= s->nalloc){
s->nalloc += 10;
s->r = erealloc(s->r, s->nalloc*sizeof(Rune));
}
s->r[s->n++] = c;
s->r[s->n] = '\0';
}
void
getrhs(String *s, int delim, int cmd)
{
int c;
while((c = getch())>0 && c!=delim && c!='\n'){
if(c == '\\'){
if((c=getch()) <= 0)
error("bad right hand side");
if(c == '\n'){
ungetch();
c='\\';
}else if(c == 'n')
c='\n';
else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */
Straddc(s, '\\');
}
Straddc(s, c);
}
ungetch(); /* let client read whether delimiter, '\n' or whatever */
}
String *
collecttoken(char *end)
{
String *s = newstring(0);
int c;
while((c=nextc())==' ' || c=='\t')
Straddc(s, getch()); /* blanks significant for getname() */
while((c=getch())>0 && utfrune(end, c)==0)
Straddc(s, c);
if(c != '\n')
atnl();
return s;
}
String *
collecttext(void)
{
String *s;
int begline, i, c, delim;
s = newstring(0);
if(cmdskipbl()=='\n'){
getch();
i = 0;
do{
begline = i;
while((c = getch())>0 && c!='\n')
i++, Straddc(s, c);
i++, Straddc(s, '\n');
if(c < 0)
goto Return;
}while(s->r[begline]!='.' || s->r[begline+1]!='\n');
s->r[s->n-2] = '\0';
s->n -= 2;
}else{
okdelim(delim = getch());
getrhs(s, delim, 'a');
if(nextc()==delim)
getch();
atnl();
}
Return:
return s;
}
int
cmdlookup(int c)
{
int i;
for(i=0; cmdtab[i].cmdc; i++)
if(cmdtab[i].cmdc == c)
return i;
return -1;
}
Cmd*
parsecmd(int nest)
{
int i, c;
struct cmdtab *ct;
Cmd *cp, *ncp;
Cmd cmd;
cmd.next = cmd.u.cmd = 0;
cmd.re = 0;
cmd.flag = cmd.num = 0;
cmd.addr = compoundaddr();
if(cmdskipbl() == -1)
return 0;
if((c=getch())==-1)
return 0;
cmd.cmdc = c;
if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */
getch(); /* the 'd' */
cmd.cmdc='c'|0x100;
}
i = cmdlookup(cmd.cmdc);
if(i >= 0){
if(cmd.cmdc == '\n')
goto Return; /* let nl_cmd work it all out */
ct = &cmdtab[i];
if(ct->defaddr==aNo && cmd.addr)
editerror("command takes no address");
if(ct->count)
cmd.num = getnum(ct->count);
if(ct->regexp){
/* x without pattern -> .*\n, indicated by cmd.re==0 */
/* X without pattern is all files */
if((ct->cmdc!='x' && ct->cmdc!='X') ||
((c = nextc())!=' ' && c!='\t' && c!='\n')){
cmdskipbl();
if((c = getch())=='\n' || c<0)
editerror("no address");
okdelim(c);
cmd.re = getregexp(c);
if(ct->cmdc == 's'){
cmd.u.text = newstring(0);
getrhs(cmd.u.text, c, 's');
if(nextc() == c){
getch();
if(nextc() == 'g')
cmd.flag = getch();
}
}
}
}
if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0)
editerror("bad address");
if(ct->defcmd){
if(cmdskipbl() == '\n'){
getch();
cmd.u.cmd = newcmd();
cmd.u.cmd->cmdc = ct->defcmd;
}else if((cmd.u.cmd = parsecmd(nest))==0)
error("defcmd");
}else if(ct->text)
cmd.u.text = collecttext();
else if(ct->token)
cmd.u.text = collecttoken(ct->token);
else
atnl();
}else
switch(cmd.cmdc){
case '{':
cp = 0;
do{
if(cmdskipbl()=='\n')
getch();
ncp = parsecmd(nest+1);
if(cp)
cp->next = ncp;
else
cmd.u.cmd = ncp;
}while(cp = ncp);
break;
case '}':
atnl();
if(nest==0)
editerror("right brace with no left brace");
return 0;
default:
editerror("unknown command %c", cmd.cmdc);
}
Return:
cp = newcmd();
*cp = cmd;
return cp;
}
String*
getregexp(int delim)
{
String *buf, *r;
int i, c;
buf = allocstring(0);
for(i=0; ; i++){
if((c = getch())=='\\'){
if(nextc()==delim)
c = getch();
else if(nextc()=='\\'){
Straddc(buf, c);
c = getch();
}
}else if(c==delim || c=='\n')
break;
if(i >= RBUFSIZE)
editerror("regular expression too long");
Straddc(buf, c);
}
if(c!=delim && c)
ungetch();
if(buf->n > 0){
patset = TRUE;
freestring(lastpat);
lastpat = buf;
}else
freestring(buf);
if(lastpat->n == 0)
editerror("no regular expression defined");
r = newstring(lastpat->n);
runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */
return r;
}
Addr *
simpleaddr(void)
{
Addr addr;
Addr *ap, *nap;
addr.num = 0;
addr.next = 0;
addr.u.left = 0;
switch(cmdskipbl()){
case '#':
addr.type = getch();
addr.num = getnum(1);
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
addr.num = getnum(1);
addr.type='l';
break;
case '/': case '?': case '"':
addr.u.re = getregexp(addr.type = getch());
break;
case '.':
case '$':
case '+':
case '-':
case '\'':
addr.type = getch();
break;
default:
return 0;
}
if(addr.next = simpleaddr())
switch(addr.next->type){
case '.':
case '$':
case '\'':
if(addr.type!='"')
case '"':
editerror("bad address syntax");
break;
case 'l':
case '#':
if(addr.type=='"')
break;
/* fall through */
case '/':
case '?':
if(addr.type!='+' && addr.type!='-'){
/* insert the missing '+' */
nap = newaddr();
nap->type='+';
nap->next = addr.next;
addr.next = nap;
}
break;
case '+':
case '-':
break;
default:
error("simpleaddr");
}
ap = newaddr();
*ap = addr;
return ap;
}
Addr *
compoundaddr(void)
{
Addr addr;
Addr *ap, *next;
addr.u.left = simpleaddr();
if((addr.type = cmdskipbl())!=',' && addr.type!=';')
return addr.u.left;
getch();
next = addr.next = compoundaddr();
if(next && (next->type==',' || next->type==';') && next->u.left==0)
editerror("bad address syntax");
ap = newaddr();
*ap = addr;
return ap;
}

99
edit.h Normal file
View file

@ -0,0 +1,99 @@
/*#pragma varargck argpos editerror 1*/
typedef struct Addr Addr;
typedef struct Address Address;
typedef struct Cmd Cmd;
typedef struct List List;
typedef struct String String;
struct String
{
int n; /* excludes NUL */
Rune *r; /* includes NUL */
int nalloc;
};
struct Addr
{
char type; /* # (char addr), l (line addr), / ? . $ + - , ; */
union{
String *re;
Addr *left; /* left side of , and ; */
} u;
ulong num;
Addr *next; /* or right side of , and ; */
};
struct Address
{
Range r;
File *f;
};
struct Cmd
{
Addr *addr; /* address (range of text) */
String *re; /* regular expression for e.g. 'x' */
union{
Cmd *cmd; /* target of x, g, {, etc. */
String *text; /* text of a, c, i; rhs of s */
Addr *mtaddr; /* address for m, t */
} u;
Cmd *next; /* pointer to next element in {} */
short num;
ushort flag; /* whatever */
ushort cmdc; /* command character; 'x' etc. */
};
extern struct cmdtab{
ushort cmdc; /* command character */
uchar text; /* takes a textual argument? */
uchar regexp; /* takes a regular expression? */
uchar addr; /* takes an address (m or t)? */
uchar defcmd; /* default command; 0==>none */
uchar defaddr; /* default address */
uchar count; /* takes a count e.g. s2/// */
char *token; /* takes text terminated by one of these */
int (*fn)(Text*, Cmd*); /* function to call with parse tree */
}cmdtab[];
#define INCR 25 /* delta when growing list */
struct List /* code depends on a long being able to hold a pointer */
{
int nalloc;
int nused;
union{
void *listptr;
void* *ptr;
uchar* *ucharptr;
String* *stringptr;
} u;
};
enum Defaddr{ /* default addresses */
aNo,
aDot,
aAll
};
int nl_cmd(Text*, Cmd*), a_cmd(Text*, Cmd*), b_cmd(Text*, Cmd*);
int c_cmd(Text*, Cmd*), d_cmd(Text*, Cmd*);
int B_cmd(Text*, Cmd*), D_cmd(Text*, Cmd*), e_cmd(Text*, Cmd*);
int f_cmd(Text*, Cmd*), g_cmd(Text*, Cmd*), i_cmd(Text*, Cmd*);
int k_cmd(Text*, Cmd*), m_cmd(Text*, Cmd*), n_cmd(Text*, Cmd*);
int p_cmd(Text*, Cmd*);
int s_cmd(Text*, Cmd*), u_cmd(Text*, Cmd*), w_cmd(Text*, Cmd*);
int x_cmd(Text*, Cmd*), X_cmd(Text*, Cmd*), pipe_cmd(Text*, Cmd*);
int eq_cmd(Text*, Cmd*);
String *allocstring(int);
void freestring(String*);
String *getregexp(int);
Addr *newaddr(void);
Address cmdaddress(Addr*, Address, int);
int cmdexec(Text*, Cmd*);
void editerror(char*, ...);
int cmdlookup(int);
void resetxec(void);
void Straddc(String*, int);

354
elog.c Normal file
View file

@ -0,0 +1,354 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
#include "edit.h"
static char Wsequence[] = "warning: changes out of sequence\n";
static int warned = FALSE;
/*
* Log of changes made by editing commands. Three reasons for this:
* 1) We want addresses in commands to apply to old file, not file-in-change.
* 2) It's difficult to track changes correctly as things move, e.g. ,x m$
* 3) This gives an opportunity to optimize by merging adjacent changes.
* It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
* separate implementation. To do this well, we use Replace as well as
* Insert and Delete
*/
typedef struct Buflog Buflog;
struct Buflog
{
short type; /* Replace, Filename */
uint q0; /* location of change (unused in f) */
uint nd; /* # runes to delete */
uint nr; /* # runes in string or file name */
};
enum
{
Buflogsize = sizeof(Buflog)/sizeof(Rune)
};
/*
* Minstring shouldn't be very big or we will do lots of I/O for small changes.
* Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
*/
enum
{
Minstring = 16, /* distance beneath which we merge changes */
Maxstring = RBUFSIZE /* maximum length of change we will merge into one */
};
void
eloginit(File *f)
{
if(f->elog.type != Empty)
return;
f->elog.type = Null;
if(f->elogbuf == nil)
f->elogbuf = emalloc(sizeof(Buffer));
if(f->elog.r == nil)
f->elog.r = fbufalloc();
bufreset(f->elogbuf);
}
void
elogclose(File *f)
{
if(f->elogbuf){
bufclose(f->elogbuf);
free(f->elogbuf);
f->elogbuf = nil;
}
}
void
elogreset(File *f)
{
f->elog.type = Null;
f->elog.nd = 0;
f->elog.nr = 0;
}
void
elogterm(File *f)
{
elogreset(f);
if(f->elogbuf)
bufreset(f->elogbuf);
f->elog.type = Empty;
fbuffree(f->elog.r);
f->elog.r = nil;
warned = FALSE;
}
void
elogflush(File *f)
{
Buflog b;
b.type = f->elog.type;
b.q0 = f->elog.q0;
b.nd = f->elog.nd;
b.nr = f->elog.nr;
switch(f->elog.type){
default:
warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
break;
case Null:
break;
case Insert:
case Replace:
if(f->elog.nr > 0)
bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
/* fall through */
case Delete:
bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
break;
}
elogreset(f);
}
void
elogreplace(File *f, int q0, int q1, Rune *r, int nr)
{
uint gap;
if(q0==q1 && nr==0)
return;
eloginit(f);
if(f->elog.type!=Null && q0<f->elog.q0){
if(warned++ == 0)
warning(nil, Wsequence);
elogflush(f);
}
/* try to merge with previous */
gap = q0 - (f->elog.q0+f->elog.nd); /* gap between previous and this */
if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
if(gap < Minstring){
if(gap > 0){
bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
f->elog.nr += gap;
}
f->elog.nd += gap + q1-q0;
runemove(f->elog.r+f->elog.nr, r, nr);
f->elog.nr += nr;
return;
}
}
elogflush(f);
f->elog.type = Replace;
f->elog.q0 = q0;
f->elog.nd = q1-q0;
f->elog.nr = nr;
if(nr > RBUFSIZE)
editerror("internal error: replacement string too large(%d)", nr);
runemove(f->elog.r, r, nr);
}
void
eloginsert(File *f, int q0, Rune *r, int nr)
{
int n;
if(nr == 0)
return;
eloginit(f);
if(f->elog.type!=Null && q0<f->elog.q0){
if(warned++ == 0)
warning(nil, Wsequence);
elogflush(f);
}
/* try to merge with previous */
if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
runemove(f->elog.r+f->elog.nr, r, nr);
f->elog.nr += nr;
return;
}
while(nr > 0){
elogflush(f);
f->elog.type = Insert;
f->elog.q0 = q0;
n = nr;
if(n > RBUFSIZE)
n = RBUFSIZE;
f->elog.nr = n;
runemove(f->elog.r, r, n);
r += n;
nr -= n;
}
}
void
elogdelete(File *f, int q0, int q1)
{
if(q0 == q1)
return;
eloginit(f);
if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
if(warned++ == 0)
warning(nil, Wsequence);
elogflush(f);
}
/* try to merge with previous */
if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
f->elog.nd += q1-q0;
return;
}
elogflush(f);
f->elog.type = Delete;
f->elog.q0 = q0;
f->elog.nd = q1-q0;
}
#define tracelog 0
void
elogapply(File *f)
{
Buflog b;
Rune *buf;
uint i, n, up, mod;
uint tq0, tq1;
Buffer *log;
Text *t;
int owner;
elogflush(f);
log = f->elogbuf;
t = f->curtext;
buf = fbufalloc();
mod = FALSE;
owner = 0;
if(t->w){
owner = t->w->owner;
if(owner == 0)
t->w->owner = 'E';
}
/*
* The edit commands have already updated the selection in t->q0, t->q1,
* but using coordinates relative to the unmodified buffer. As we apply the log,
* we have to update the coordinates to be relative to the modified buffer.
* Textinsert and textdelete will do this for us; our only work is to apply the
* convention that an insertion at t->q0==t->q1 is intended to select the
* inserted text.
*/
/*
* We constrain the addresses in here (with textconstrain()) because
* overlapping changes will generate bogus addresses. We will warn
* about changes out of sequence but proceed anyway; here we must
* keep things in range.
*/
while(log->nc > 0){
up = log->nc-Buflogsize;
bufread(log, up, (Rune*)&b, Buflogsize);
switch(b.type){
default:
fprint(2, "elogapply: 0x%ux\n", b.type);
abort();
break;
case Replace:
if(tracelog)
warning(nil, "elog replace %d %d (%d %d)\n",
b.q0, b.q0+b.nd, t->q0, t->q1);
if(!mod){
mod = TRUE;
filemark(f);
}
textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
textdelete(t, tq0, tq1, TRUE);
up -= b.nr;
for(i=0; i<b.nr; i+=n){
n = b.nr - i;
if(n > RBUFSIZE)
n = RBUFSIZE;
bufread(log, up+i, buf, n);
textinsert(t, tq0+i, buf, n, TRUE);
}
if(t->q0 == b.q0 && t->q1 == b.q0)
t->q1 += b.nr;
break;
case Delete:
if(tracelog)
warning(nil, "elog delete %d %d (%d %d)\n",
b.q0, b.q0+b.nd, t->q0, t->q1);
if(!mod){
mod = TRUE;
filemark(f);
}
textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
textdelete(t, tq0, tq1, TRUE);
break;
case Insert:
if(tracelog)
warning(nil, "elog insert %d %d (%d %d)\n",
b.q0, b.q0+b.nr, t->q0, t->q1);
if(!mod){
mod = TRUE;
filemark(f);
}
textconstrain(t, b.q0, b.q0, &tq0, &tq1);
up -= b.nr;
for(i=0; i<b.nr; i+=n){
n = b.nr - i;
if(n > RBUFSIZE)
n = RBUFSIZE;
bufread(log, up+i, buf, n);
textinsert(t, tq0+i, buf, n, TRUE);
}
if(t->q0 == b.q0 && t->q1 == b.q0)
t->q1 += b.nr;
break;
/* case Filename:
f->seq = u.seq;
fileunsetname(f, epsilon);
f->mod = u.mod;
up -= u.n;
free(f->name);
if(u.n == 0)
f->name = nil;
else
f->name = runemalloc(u.n);
bufread(delta, up, f->name, u.n);
f->nname = u.n;
break;
*/
}
bufdelete(log, up, log->nc);
}
fbuffree(buf);
elogterm(f);
/*
* Bad addresses will cause bufload to crash, so double check.
* If changes were out of order, we expect problems so don't complain further.
*/
if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){
if(!warned)
warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc);
t->q1 = min(t->q1, f->b.nc);
t->q0 = min(t->q0, t->q1);
}
if(t->w)
t->w->owner = owner;
}

1803
exec.c Normal file

File diff suppressed because it is too large Load diff

311
file.c Normal file
View file

@ -0,0 +1,311 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
/*
* Structure of Undo list:
* The Undo structure follows any associated data, so the list
* can be read backwards: read the structure, then read whatever
* data is associated (insert string, file name) and precedes it.
* The structure includes the previous value of the modify bit
* and a sequence number; successive Undo structures with the
* same sequence number represent simultaneous changes.
*/
typedef struct Undo Undo;
struct Undo
{
short type; /* Delete, Insert, Filename */
short mod; /* modify bit */
uint seq; /* sequence number */
uint p0; /* location of change (unused in f) */
uint n; /* # runes in string or file name */
};
enum
{
Undosize = sizeof(Undo)/sizeof(Rune)
};
File*
fileaddtext(File *f, Text *t)
{
if(f == nil){
f = emalloc(sizeof(File));
f->unread = TRUE;
}
f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
f->text[f->ntext++] = t;
f->curtext = t;
return f;
}
void
filedeltext(File *f, Text *t)
{
int i;
for(i=0; i<f->ntext; i++)
if(f->text[i] == t)
goto Found;
error("can't find text in filedeltext");
Found:
f->ntext--;
if(f->ntext == 0){
fileclose(f);
return;
}
memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
if(f->curtext == t)
f->curtext = f->text[0];
}
void
fileinsert(File *f, uint p0, Rune *s, uint ns)
{
if(p0 > f->b.nc)
error("internal error: fileinsert");
if(f->seq > 0)
fileuninsert(f, &f->delta, p0, ns);
bufinsert(&f->b, p0, s, ns);
if(ns)
f->mod = TRUE;
}
void
fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
{
Undo u;
/* undo an insertion by deleting */
u.type = Delete;
u.mod = f->mod;
u.seq = f->seq;
u.p0 = p0;
u.n = ns;
bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
}
void
filedelete(File *f, uint p0, uint p1)
{
if(!(p0<=p1 && p0<=f->b.nc && p1<=f->b.nc))
error("internal error: filedelete");
if(f->seq > 0)
fileundelete(f, &f->delta, p0, p1);
bufdelete(&f->b, p0, p1);
if(p1 > p0)
f->mod = TRUE;
}
void
fileundelete(File *f, Buffer *delta, uint p0, uint p1)
{
Undo u;
Rune *buf;
uint i, n;
/* undo a deletion by inserting */
u.type = Insert;
u.mod = f->mod;
u.seq = f->seq;
u.p0 = p0;
u.n = p1-p0;
buf = fbufalloc();
for(i=p0; i<p1; i+=n){
n = p1 - i;
if(n > RBUFSIZE)
n = RBUFSIZE;
bufread(&f->b, i, buf, n);
bufinsert(delta, delta->nc, buf, n);
}
fbuffree(buf);
bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
}
void
filesetname(File *f, Rune *name, int n)
{
if(f->seq > 0)
fileunsetname(f, &f->delta);
free(f->name);
f->name = runemalloc(n);
runemove(f->name, name, n);
f->nname = n;
f->unread = TRUE;
}
void
fileunsetname(File *f, Buffer *delta)
{
Undo u;
/* undo a file name change by restoring old name */
u.type = Filename;
u.mod = f->mod;
u.seq = f->seq;
u.p0 = 0; /* unused */
u.n = f->nname;
if(f->nname)
bufinsert(delta, delta->nc, f->name, f->nname);
bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
}
uint
fileload(File *f, uint p0, int fd, int *nulls, DigestState *h)
{
if(f->seq > 0)
error("undo in file.load unimplemented");
return bufload(&f->b, p0, fd, nulls, h);
}
/* return sequence number of pending redo */
uint
fileredoseq(File *f)
{
Undo u;
Buffer *delta;
delta = &f->epsilon;
if(delta->nc == 0)
return 0;
bufread(delta, delta->nc-Undosize, (Rune*)&u, Undosize);
return u.seq;
}
void
fileundo(File *f, int isundo, uint *q0p, uint *q1p)
{
Undo u;
Rune *buf;
uint i, j, n, up;
uint stop;
Buffer *delta, *epsilon;
if(isundo){
/* undo; reverse delta onto epsilon, seq decreases */
delta = &f->delta;
epsilon = &f->epsilon;
stop = f->seq;
}else{
/* redo; reverse epsilon onto delta, seq increases */
delta = &f->epsilon;
epsilon = &f->delta;
stop = 0; /* don't know yet */
}
buf = fbufalloc();
while(delta->nc > 0){
up = delta->nc-Undosize;
bufread(delta, up, (Rune*)&u, Undosize);
if(isundo){
if(u.seq < stop){
f->seq = u.seq;
goto Return;
}
}else{
if(stop == 0)
stop = u.seq;
if(u.seq > stop)
goto Return;
}
switch(u.type){
default:
fprint(2, "undo: 0x%ux\n", u.type);
abort();
break;
case Delete:
f->seq = u.seq;
fileundelete(f, epsilon, u.p0, u.p0+u.n);
f->mod = u.mod;
bufdelete(&f->b, u.p0, u.p0+u.n);
for(j=0; j<f->ntext; j++)
textdelete(f->text[j], u.p0, u.p0+u.n, FALSE);
*q0p = u.p0;
*q1p = u.p0;
break;
case Insert:
f->seq = u.seq;
fileuninsert(f, epsilon, u.p0, u.n);
f->mod = u.mod;
up -= u.n;
for(i=0; i<u.n; i+=n){
n = u.n - i;
if(n > RBUFSIZE)
n = RBUFSIZE;
bufread(delta, up+i, buf, n);
bufinsert(&f->b, u.p0+i, buf, n);
for(j=0; j<f->ntext; j++)
textinsert(f->text[j], u.p0+i, buf, n, FALSE);
}
*q0p = u.p0;
*q1p = u.p0+u.n;
break;
case Filename:
f->seq = u.seq;
fileunsetname(f, epsilon);
f->mod = u.mod;
up -= u.n;
free(f->name);
if(u.n == 0)
f->name = nil;
else
f->name = runemalloc(u.n);
bufread(delta, up, f->name, u.n);
f->nname = u.n;
break;
}
bufdelete(delta, up, delta->nc);
}
if(isundo)
f->seq = 0;
Return:
fbuffree(buf);
}
void
filereset(File *f)
{
bufreset(&f->delta);
bufreset(&f->epsilon);
f->seq = 0;
}
void
fileclose(File *f)
{
free(f->name);
f->nname = 0;
f->name = nil;
free(f->text);
f->ntext = 0;
f->text = nil;
bufclose(&f->b);
bufclose(&f->delta);
bufclose(&f->epsilon);
elogclose(f);
free(f);
}
void
filemark(File *f)
{
if(f->epsilon.nc)
bufdelete(&f->epsilon, 0, f->epsilon.nc);
f->seq = seq;
}

106
fns.h Normal file
View file

@ -0,0 +1,106 @@
/*
#pragma varargck argpos warning 2
#pragma varargck argpos warningew 2
*/
void warning(Mntdir*, char*, ...);
void warningew(Window*, Mntdir*, char*, ...);
#define fbufalloc() emalloc(BUFSIZE)
#define fbuffree(x) free(x)
void plumblook(Plumbmsg *m);
void plumbshow(Plumbmsg*m);
void acmeputsnarf(void);
void acmegetsnarf(void);
int tempfile(void);
void scrlresize(void);
Font* getfont(int, int, char*);
char* getarg(Text*, int, int, Rune**, int*);
char* getbytearg(Text*, int, int, char**);
void new(Text*, Text*, Text*, int, int, Rune*, int);
void undo(Text*, Text*, Text*, int, int, Rune*, int);
void scrsleep(uint);
void savemouse(Window*);
int restoremouse(Window*);
void clearmouse(void);
void allwindows(void(*)(Window*, void*), void*);
uint loadfile(int, uint, int*, int(*)(void*, uint, Rune*, int), void*, DigestState*);
void movetodel(Window*);
Window* errorwin(Mntdir*, int);
Window* errorwinforwin(Window*);
Runestr cleanrname(Runestr);
void run(Window*, char*, Rune*, int, int, char*, char*, int);
void fsysclose(void);
void setcurtext(Text*, int);
int isfilec(Rune);
void rxinit(void);
int rxnull(void);
Runestr dirname(Text*, Rune*, int);
void error(char*);
void cvttorunes(char*, int, Rune*, int*, int*, int*);
void* tmalloc(uint);
void tfree(void);
void killprocs(void);
void killtasks(void);
int runeeq(Rune*, uint, Rune*, uint);
int ALEF_tid(void);
void iconinit(void);
Timer* timerstart(int);
void timerstop(Timer*);
void timercancel(Timer*);
void timerinit(void);
void cut(Text*, Text*, Text*, int, int, Rune*, int);
void paste(Text*, Text*, Text*, int, int, Rune*, int);
void get(Text*, Text*, Text*, int, int, Rune*, int);
void put(Text*, Text*, Text*, int, int, Rune*, int);
void putfile(File*, int, int, Rune*, int);
void fontx(Text*, Text*, Text*, int, int, Rune*, int);
#undef isalnum
#define isalnum acmeisalnum
int isalnum(Rune);
void execute(Text*, uint, uint, int, Text*);
int search(Text*, Rune*, uint);
void look3(Text*, uint, uint, int);
void editcmd(Text*, Rune*, uint);
uint min(uint, uint);
uint max(uint, uint);
Window* lookfile(Rune*, int);
Window* lookid(int, int);
char* runetobyte(Rune*, int);
Rune* bytetorune(char*, int*);
void fsysinit(void);
Mntdir* fsysmount(Rune*, int, Rune**, int);
void fsysdelid(Mntdir*);
void fsysincid(Mntdir*);
Xfid* respond(Xfid*, Fcall*, char*);
int rxcompile(Rune*);
int rgetc(void*, uint);
int tgetc(void*, uint);
int isaddrc(int);
int isregexc(int);
void *emalloc(uint);
void *erealloc(void*, uint);
char *estrdup(char*);
Range address(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*);
int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
int rxbexecute(Text*, uint, Rangeset*);
Window* makenewwindow(Text *t);
int expand(Text*, uint, uint, Expand*);
Rune* skipbl(Rune*, int, int*);
Rune* findbl(Rune*, int, int*);
char* edittext(Window*, int, Rune*, int);
void flushwarnings(void);
void startplumbing(void);
long nlcount(Text*, long, long, long*);
long nlcounttopos(Text*, long, long, long);
Runestr runestr(Rune*, uint);
Range range(int, int);
#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune))
#define runerealloc(a, b) (Rune*)erealloc((a), (b)*sizeof(Rune))
#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune))
int ismtpt(char*);

749
fsys.c Normal file
View file

@ -0,0 +1,749 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static int sfd;
enum
{
Nhash = 16,
DEBUG = 0
};
static Fid *fids[Nhash];
Fid *newfid(int);
static Xfid* fsysflush(Xfid*, Fid*);
static Xfid* fsysauth(Xfid*, Fid*);
static Xfid* fsysversion(Xfid*, Fid*);
static Xfid* fsysattach(Xfid*, Fid*);
static Xfid* fsyswalk(Xfid*, Fid*);
static Xfid* fsysopen(Xfid*, Fid*);
static Xfid* fsyscreate(Xfid*, Fid*);
static Xfid* fsysread(Xfid*, Fid*);
static Xfid* fsyswrite(Xfid*, Fid*);
static Xfid* fsysclunk(Xfid*, Fid*);
static Xfid* fsysremove(Xfid*, Fid*);
static Xfid* fsysstat(Xfid*, Fid*);
static Xfid* fsyswstat(Xfid*, Fid*);
Xfid* (*fcall[Tmax])(Xfid*, Fid*);
static void
initfcall(void)
{
fcall[Tflush] = fsysflush;
fcall[Tversion] = fsysversion;
fcall[Tauth] = fsysauth;
fcall[Tattach] = fsysattach;
fcall[Twalk] = fsyswalk;
fcall[Topen] = fsysopen;
fcall[Tcreate] = fsyscreate;
fcall[Tread] = fsysread;
fcall[Twrite] = fsyswrite;
fcall[Tclunk] = fsysclunk;
fcall[Tremove]= fsysremove;
fcall[Tstat] = fsysstat;
fcall[Twstat] = fsyswstat;
}
char Eperm[] = "permission denied";
char Eexist[] = "file does not exist";
char Enotdir[] = "not a directory";
Dirtab dirtab[]=
{
{ ".", QTDIR, Qdir, 0500|DMDIR },
{ "acme", QTDIR, Qacme, 0500|DMDIR },
{ "cons", QTFILE, Qcons, 0600 },
{ "consctl", QTFILE, Qconsctl, 0000 },
{ "draw", QTDIR, Qdraw, 0000|DMDIR }, /* to suppress graphics progs started in acme */
{ "editout", QTFILE, Qeditout, 0200 },
{ "index", QTFILE, Qindex, 0400 },
{ "label", QTFILE, Qlabel, 0600 },
{ "log", QTFILE, Qlog, 0400 },
{ "new", QTDIR, Qnew, 0500|DMDIR },
{ nil, }
};
Dirtab dirtabw[]=
{
{ ".", QTDIR, Qdir, 0500|DMDIR },
{ "addr", QTFILE, QWaddr, 0600 },
{ "body", QTAPPEND, QWbody, 0600|DMAPPEND },
{ "ctl", QTFILE, QWctl, 0600 },
{ "data", QTFILE, QWdata, 0600 },
{ "editout", QTFILE, QWeditout, 0200 },
{ "errors", QTFILE, QWerrors, 0200 },
{ "event", QTFILE, QWevent, 0600 },
{ "rdsel", QTFILE, QWrdsel, 0400 },
{ "wrsel", QTFILE, QWwrsel, 0200 },
{ "tag", QTAPPEND, QWtag, 0600|DMAPPEND },
{ "xdata", QTFILE, QWxdata, 0600 },
{ nil, }
};
typedef struct Mnt Mnt;
struct Mnt
{
QLock lk;
int id;
Mntdir *md;
};
Mnt mnt;
Xfid* respond(Xfid*, Fcall*, char*);
int dostat(int, Dirtab*, uchar*, int, uint);
uint getclock(void);
char *user = "Wile E. Coyote";
static int closing = 0;
int messagesize = Maxblock+IOHDRSZ; /* good start */
void fsysproc(void *);
void
fsysinit(void)
{
int p[2];
char *u;
initfcall();
if(pipe(p) < 0)
error("can't create pipe");
if(post9pservice(p[0], "acme", mtpt) < 0)
error("can't post service");
sfd = p[1];
fmtinstall('F', fcallfmt);
if((u = getuser()) != nil)
user = estrdup(u);
proccreate(fsysproc, nil, STACK);
}
void
fsysproc(void *v)
{
int n;
Xfid *x;
Fid *f;
Fcall t;
uchar *buf;
threadsetname("fsysproc");
USED(v);
x = nil;
for(;;){
buf = emalloc(messagesize+UTFmax); /* overflow for appending partial rune in xfidwrite */
n = read9pmsg(sfd, buf, messagesize);
if(n <= 0){
if(closing)
break;
error("i/o error on server channel");
}
if(x == nil){
sendp(cxfidalloc, nil);
x = recvp(cxfidalloc);
}
x->buf = buf;
if(convM2S(buf, n, &x->fcall) != n)
error("convert error in convM2S");
if(DEBUG)
fprint(2, "%F\n", &x->fcall);
if(fcall[x->fcall.type] == nil)
x = respond(x, &t, "bad fcall type");
else{
switch(x->fcall.type){
case Tversion:
case Tauth:
case Tflush:
f = nil;
break;
case Tattach:
f = newfid(x->fcall.fid);
break;
default:
f = newfid(x->fcall.fid);
if(!f->busy){
x->f = f;
x = respond(x, &t, "fid not in use");
continue;
}
break;
}
x->f = f;
x = (*fcall[x->fcall.type])(x, f);
}
}
}
Mntdir*
fsysaddid(Rune *dir, int ndir, Rune **incl, int nincl)
{
Mntdir *m;
int id;
qlock(&mnt.lk);
id = ++mnt.id;
m = emalloc(sizeof *m);
m->id = id;
m->dir = dir;
m->ref = 1; /* one for Command, one will be incremented in attach */
m->ndir = ndir;
m->next = mnt.md;
m->incl = incl;
m->nincl = nincl;
mnt.md = m;
qunlock(&mnt.lk);
return m;
}
void
fsysincid(Mntdir *m)
{
qlock(&mnt.lk);
m->ref++;
qunlock(&mnt.lk);
}
void
fsysdelid(Mntdir *idm)
{
Mntdir *m, *prev;
int i;
char buf[64];
if(idm == nil)
return;
qlock(&mnt.lk);
if(--idm->ref > 0){
qunlock(&mnt.lk);
return;
}
prev = nil;
for(m=mnt.md; m; m=m->next){
if(m == idm){
if(prev)
prev->next = m->next;
else
mnt.md = m->next;
for(i=0; i<m->nincl; i++)
free(m->incl[i]);
free(m->incl);
free(m->dir);
free(m);
qunlock(&mnt.lk);
return;
}
prev = m;
}
qunlock(&mnt.lk);
sprint(buf, "fsysdelid: can't find id %d\n", idm->id);
sendp(cerr, estrdup(buf));
}
/*
* Called only in exec.c:/^run(), from a different FD group
*/
Mntdir*
fsysmount(Rune *dir, int ndir, Rune **incl, int nincl)
{
return fsysaddid(dir, ndir, incl, nincl);
}
void
fsysclose(void)
{
closing = 1;
/*
* apparently this is not kosher on openbsd.
* perhaps because fsysproc is reading from sfd right now,
* the close hangs indefinitely.
close(sfd);
*/
}
Xfid*
respond(Xfid *x, Fcall *t, char *err)
{
int n;
if(err){
t->type = Rerror;
t->ename = err;
}else
t->type = x->fcall.type+1;
t->fid = x->fcall.fid;
t->tag = x->fcall.tag;
if(x->buf == nil)
x->buf = emalloc(messagesize);
n = convS2M(t, x->buf, messagesize);
if(n <= 0)
error("convert error in convS2M");
if(write(sfd, x->buf, n) != n)
error("write error in respond");
free(x->buf);
x->buf = nil;
if(DEBUG)
fprint(2, "r: %F\n", t);
return x;
}
static
Xfid*
fsysversion(Xfid *x, Fid *f)
{
Fcall t;
USED(f);
if(x->fcall.msize < 256)
return respond(x, &t, "version: message size too small");
messagesize = x->fcall.msize;
t.msize = messagesize;
if(strncmp(x->fcall.version, "9P2000", 6) != 0)
return respond(x, &t, "unrecognized 9P version");
t.version = "9P2000";
return respond(x, &t, nil);
}
static
Xfid*
fsysauth(Xfid *x, Fid *f)
{
Fcall t;
USED(f);
return respond(x, &t, "acme: authentication not required");
}
static
Xfid*
fsysflush(Xfid *x, Fid *f)
{
USED(f);
sendp(x->c, (void*)xfidflush);
return nil;
}
static
Xfid*
fsysattach(Xfid *x, Fid *f)
{
Fcall t;
int id;
Mntdir *m;
char buf[128];
if(strcmp(x->fcall.uname, user) != 0)
return respond(x, &t, Eperm);
f->busy = TRUE;
f->open = FALSE;
f->qid.path = Qdir;
f->qid.type = QTDIR;
f->qid.vers = 0;
f->dir = dirtab;
f->nrpart = 0;
f->w = nil;
t.qid = f->qid;
f->mntdir = nil;
id = atoi(x->fcall.aname);
qlock(&mnt.lk);
for(m=mnt.md; m; m=m->next)
if(m->id == id){
f->mntdir = m;
m->ref++;
break;
}
if(m == nil && x->fcall.aname[0]){
snprint(buf, sizeof buf, "unknown id '%s' in attach", x->fcall.aname);
sendp(cerr, estrdup(buf));
}
qunlock(&mnt.lk);
return respond(x, &t, nil);
}
static
Xfid*
fsyswalk(Xfid *x, Fid *f)
{
Fcall t;
int c, i, j, id;
Qid q;
uchar type;
ulong path;
Fid *nf;
Dirtab *d, *dir;
Window *w;
char *err;
nf = nil;
w = nil;
if(f->open)
return respond(x, &t, "walk of open file");
if(x->fcall.fid != x->fcall.newfid){
nf = newfid(x->fcall.newfid);
if(nf->busy)
return respond(x, &t, "newfid already in use");
nf->busy = TRUE;
nf->open = FALSE;
nf->mntdir = f->mntdir;
if(f->mntdir)
f->mntdir->ref++;
nf->dir = f->dir;
nf->qid = f->qid;
nf->w = f->w;
nf->nrpart = 0; /* not open, so must be zero */
if(nf->w)
incref(&nf->w->ref);
f = nf; /* walk f */
}
t.nwqid = 0;
err = nil;
dir = nil;
id = WIN(f->qid);
q = f->qid;
if(x->fcall.nwname > 0){
for(i=0; i<x->fcall.nwname; i++){
if((q.type & QTDIR) == 0){
err = Enotdir;
break;
}
if(strcmp(x->fcall.wname[i], "..") == 0){
type = QTDIR;
path = Qdir;
id = 0;
if(w){
winclose(w);
w = nil;
}
Accept:
if(i == MAXWELEM){
err = "name too long";
break;
}
q.type = type;
q.vers = 0;
q.path = QID(id, path);
t.wqid[t.nwqid++] = q;
continue;
}
/* is it a numeric name? */
for(j=0; (c=x->fcall.wname[i][j]); j++)
if(c<'0' || '9'<c)
goto Regular;
/* yes: it's a directory */
if(w) /* name has form 27/23; get out before losing w */
break;
id = atoi(x->fcall.wname[i]);
qlock(&row.lk);
w = lookid(id, FALSE);
if(w == nil){
qunlock(&row.lk);
break;
}
incref(&w->ref); /* we'll drop reference at end if there's an error */
path = Qdir;
type = QTDIR;
qunlock(&row.lk);
dir = dirtabw;
goto Accept;
Regular:
if(strcmp(x->fcall.wname[i], "new") == 0){
if(w)
error("w set in walk to new");
sendp(cnewwindow, nil); /* signal newwindowthread */
w = recvp(cnewwindow); /* receive new window */
incref(&w->ref);
type = QTDIR;
path = QID(w->id, Qdir);
id = w->id;
dir = dirtabw;
goto Accept;
}
if(id == 0)
d = dirtab;
else
d = dirtabw;
d++; /* skip '.' */
for(; d->name; d++)
if(strcmp(x->fcall.wname[i], d->name) == 0){
path = d->qid;
type = d->type;
dir = d;
goto Accept;
}
break; /* file not found */
}
if(i==0 && err == nil)
err = Eexist;
}
if(err!=nil || t.nwqid<x->fcall.nwname){
if(nf){
nf->busy = FALSE;
fsysdelid(nf->mntdir);
}
}else if(t.nwqid == x->fcall.nwname){
if(w){
f->w = w;
w = nil; /* don't drop the reference */
}
if(dir)
f->dir = dir;
f->qid = q;
}
if(w != nil)
winclose(w);
return respond(x, &t, err);
}
static
Xfid*
fsysopen(Xfid *x, Fid *f)
{
Fcall t;
int m;
/* can't truncate anything, so just disregard */
x->fcall.mode &= ~(OTRUNC|OCEXEC);
/* can't execute or remove anything */
if(x->fcall.mode==OEXEC || (x->fcall.mode&ORCLOSE))
goto Deny;
switch(x->fcall.mode){
default:
goto Deny;
case OREAD:
m = 0400;
break;
case OWRITE:
m = 0200;
break;
case ORDWR:
m = 0600;
break;
}
if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
goto Deny;
sendp(x->c, (void*)xfidopen);
return nil;
Deny:
return respond(x, &t, Eperm);
}
static
Xfid*
fsyscreate(Xfid *x, Fid *f)
{
Fcall t;
USED(f);
return respond(x, &t, Eperm);
}
static
int
idcmp(const void *a, const void *b)
{
return *(int*)a - *(int*)b;
}
static
Xfid*
fsysread(Xfid *x, Fid *f)
{
Fcall t;
uchar *b;
int i, id, n, o, e, j, k, *ids, nids;
Dirtab *d, dt;
Column *c;
uint clock, len;
char buf[16];
if(f->qid.type & QTDIR){
if(FILE(f->qid) == Qacme){ /* empty dir */
t.data = nil;
t.count = 0;
respond(x, &t, nil);
return x;
}
o = x->fcall.offset;
e = x->fcall.offset+x->fcall.count;
clock = getclock();
b = emalloc(messagesize);
id = WIN(f->qid);
n = 0;
if(id > 0)
d = dirtabw;
else
d = dirtab;
d++; /* first entry is '.' */
for(i=0; d->name!=nil && i<e; i+=len){
len = dostat(WIN(x->f->qid), d, b+n, x->fcall.count-n, clock);
if(len <= BIT16SZ)
break;
if(i >= o)
n += len;
d++;
}
if(id == 0){
qlock(&row.lk);
nids = 0;
ids = nil;
for(j=0; j<row.ncol; j++){
c = row.col[j];
for(k=0; k<c->nw; k++){
ids = realloc(ids, (nids+1)*sizeof(int));
ids[nids++] = c->w[k]->id;
}
}
qunlock(&row.lk);
qsort(ids, nids, sizeof ids[0], idcmp);
j = 0;
dt.name = buf;
for(; j<nids && i<e; i+=len){
k = ids[j];
sprint(dt.name, "%d", k);
dt.qid = QID(k, Qdir);
dt.type = QTDIR;
dt.perm = DMDIR|0700;
len = dostat(k, &dt, b+n, x->fcall.count-n, clock);
if(len == 0)
break;
if(i >= o)
n += len;
j++;
}
free(ids);
}
t.data = (char*)b;
t.count = n;
respond(x, &t, nil);
free(b);
return x;
}
sendp(x->c, (void*)xfidread);
return nil;
}
static
Xfid*
fsyswrite(Xfid *x, Fid *f)
{
USED(f);
sendp(x->c, (void*)xfidwrite);
return nil;
}
static
Xfid*
fsysclunk(Xfid *x, Fid *f)
{
fsysdelid(f->mntdir);
sendp(x->c, (void*)xfidclose);
return nil;
}
static
Xfid*
fsysremove(Xfid *x, Fid *f)
{
Fcall t;
USED(f);
return respond(x, &t, Eperm);
}
static
Xfid*
fsysstat(Xfid *x, Fid *f)
{
Fcall t;
t.stat = emalloc(messagesize-IOHDRSZ);
t.nstat = dostat(WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock());
x = respond(x, &t, nil);
free(t.stat);
return x;
}
static
Xfid*
fsyswstat(Xfid *x, Fid *f)
{
Fcall t;
USED(f);
return respond(x, &t, Eperm);
}
Fid*
newfid(int fid)
{
Fid *f, *ff, **fh;
ff = nil;
fh = &fids[fid&(Nhash-1)];
for(f=*fh; f; f=f->next)
if(f->fid == fid)
return f;
else if(ff==nil && f->busy==FALSE)
ff = f;
if(ff){
ff->fid = fid;
return ff;
}
f = emalloc(sizeof *f);
f->fid = fid;
f->next = *fh;
*fh = f;
return f;
}
uint
getclock(void)
{
return time(0);
}
int
dostat(int id, Dirtab *dir, uchar *buf, int nbuf, uint clock)
{
Dir d;
d.qid.path = QID(id, dir->qid);
d.qid.vers = 0;
d.qid.type = dir->type;
d.mode = dir->perm;
d.length = 0; /* would be nice to do better */
d.name = dir->name;
d.uid = user;
d.gid = user;
d.muid = user;
d.atime = clock;
d.mtime = clock;
return convD2M(&d, buf, nbuf);
}

199
logf.c Normal file
View file

@ -0,0 +1,199 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
// State for global log file.
typedef struct Log Log;
struct Log
{
QLock lk;
Rendez r;
vlong start; // msg[0] corresponds to 'start' in the global sequence of events
// queued events (nev=entries in ev, mev=capacity of p)
char **ev;
int nev;
int mev;
// open acme/put files that need to read events
Fid **f;
int nf;
int mf;
// active (blocked) reads waiting for events
Xfid **read;
int nread;
int mread;
};
static Log eventlog;
void
xfidlogopen(Xfid *x)
{
qlock(&eventlog.lk);
if(eventlog.nf >= eventlog.mf) {
eventlog.mf = eventlog.mf*2;
if(eventlog.mf == 0)
eventlog.mf = 8;
eventlog.f = erealloc(eventlog.f, eventlog.mf*sizeof eventlog.f[0]);
}
eventlog.f[eventlog.nf++] = x->f;
x->f->logoff = eventlog.start + eventlog.nev;
qunlock(&eventlog.lk);
}
void
xfidlogclose(Xfid *x)
{
int i;
qlock(&eventlog.lk);
for(i=0; i<eventlog.nf; i++) {
if(eventlog.f[i] == x->f) {
eventlog.f[i] = eventlog.f[--eventlog.nf];
break;
}
}
qunlock(&eventlog.lk);
}
void
xfidlogread(Xfid *x)
{
char *p;
int i;
Fcall fc;
qlock(&eventlog.lk);
if(eventlog.nread >= eventlog.mread) {
eventlog.mread = eventlog.mread*2;
if(eventlog.mread == 0)
eventlog.mread = 8;
eventlog.read = erealloc(eventlog.read, eventlog.mread*sizeof eventlog.read[0]);
}
eventlog.read[eventlog.nread++] = x;
if(eventlog.r.l == nil)
eventlog.r.l = &eventlog.lk;
x->flushed = FALSE;
while(x->f->logoff >= eventlog.start+eventlog.nev && !x->flushed)
rsleep(&eventlog.r);
for(i=0; i<eventlog.nread; i++) {
if(eventlog.read[i] == x) {
eventlog.read[i] = eventlog.read[--eventlog.nread];
break;
}
}
if(x->flushed) {
qunlock(&eventlog.lk);
return;
}
i = x->f->logoff - eventlog.start;
p = estrdup(eventlog.ev[i]);
x->f->logoff++;
qunlock(&eventlog.lk);
fc.data = p;
fc.count = strlen(p);
respond(x, &fc, nil);
free(p);
}
void
xfidlogflush(Xfid *x)
{
int i;
Xfid *rx;
qlock(&eventlog.lk);
for(i=0; i<eventlog.nread; i++) {
rx = eventlog.read[i];
if(rx->fcall.tag == x->fcall.oldtag) {
rx->flushed = TRUE;
rwakeupall(&eventlog.r);
}
}
qunlock(&eventlog.lk);
}
/*
* add a log entry for op on w.
* expected calls:
*
* op == "new" for each new window
* - caller of coladd or makenewwindow responsible for calling
* xfidlog after setting window name
* - exception: zerox
*
* op == "zerox" for new window created via zerox
* - called from zeroxx
*
* op == "get" for Get executed on window
* - called from get
*
* op == "put" for Put executed on window
* - called from put
*
* op == "del" for deleted window
* - called from winclose
*/
void
xfidlog(Window *w, char *op)
{
int i, n;
vlong min;
File *f;
char *name;
qlock(&eventlog.lk);
if(eventlog.nev >= eventlog.mev) {
// Remove and free any entries that all readers have read.
min = eventlog.start + eventlog.nev;
for(i=0; i<eventlog.nf; i++) {
if(min > eventlog.f[i]->logoff)
min = eventlog.f[i]->logoff;
}
if(min > eventlog.start) {
n = min - eventlog.start;
for(i=0; i<n; i++)
free(eventlog.ev[i]);
eventlog.nev -= n;
eventlog.start += n;
memmove(eventlog.ev, eventlog.ev+n, eventlog.nev*sizeof eventlog.ev[0]);
}
// Otherwise grow.
if(eventlog.nev >= eventlog.mev) {
eventlog.mev = eventlog.mev*2;
if(eventlog.mev == 0)
eventlog.mev = 8;
eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]);
}
}
f = w->body.file;
name = runetobyte(f->name, f->nname);
if(name == nil)
name = estrdup("");
eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name);
free(name);
if(eventlog.r.l == nil)
eventlog.r.l = &eventlog.lk;
rwakeupall(&eventlog.r);
qunlock(&eventlog.lk);
}

872
look.c Normal file
View file

@ -0,0 +1,872 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <regexp.h>
#include <9pclient.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
CFid *plumbsendfid;
CFid *plumbeditfid;
Window* openfile(Text*, Expand*);
int nuntitled;
void
plumbthread(void *v)
{
CFid *fid;
Plumbmsg *m;
Timer *t;
USED(v);
threadsetname("plumbproc");
/*
* Loop so that if plumber is restarted, acme need not be.
*/
for(;;){
/*
* Connect to plumber.
*/
plumbunmount();
while((fid = plumbopenfid("edit", OREAD|OCEXEC)) == nil){
t = timerstart(2000);
recv(t->c, nil);
timerstop(t);
}
plumbeditfid = fid;
plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC);
/*
* Relay messages.
*/
for(;;){
m = plumbrecvfid(plumbeditfid);
if(m == nil)
break;
sendp(cplumb, m);
}
/*
* Lost connection.
*/
fid = plumbsendfid;
plumbsendfid = nil;
fsclose(fid);
fid = plumbeditfid;
plumbeditfid = nil;
fsclose(fid);
}
}
void
startplumbing(void)
{
cplumb = chancreate(sizeof(Plumbmsg*), 0);
chansetname(cplumb, "cplumb");
threadcreate(plumbthread, nil, STACK);
}
void
look3(Text *t, uint q0, uint q1, int external)
{
int n, c, f, expanded;
Text *ct;
Expand e;
Rune *r;
uint p;
Plumbmsg *m;
Runestr dir;
char buf[32];
ct = seltext;
if(ct == nil)
seltext = t;
expanded = expand(t, q0, q1, &e);
if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
/* send alphanumeric expansion to external client */
if(expanded == FALSE)
return;
f = 0;
if((e.u.at!=nil && t->w!=nil) || (e.nname>0 && lookfile(e.name, e.nname)!=nil))
f = 1; /* acme can do it without loading a file */
if(q0!=e.q0 || q1!=e.q1)
f |= 2; /* second (post-expand) message follows */
if(e.nname)
f |= 4; /* it's a file name */
c = 'l';
if(t->what == Body)
c = 'L';
n = q1-q0;
if(n <= EVENTSIZE){
r = runemalloc(n);
bufread(&t->file->b, q0, r, n);
winevent(t->w, "%c%d %d %d %d %.*S\n", c, q0, q1, f, n, n, r);
free(r);
}else
winevent(t->w, "%c%d %d %d 0 \n", c, q0, q1, f, n);
if(q0==e.q0 && q1==e.q1)
return;
if(e.nname){
n = e.nname;
if(e.a1 > e.a0)
n += 1+(e.a1-e.a0);
r = runemalloc(n);
runemove(r, e.name, e.nname);
if(e.a1 > e.a0){
r[e.nname] = ':';
bufread(&e.u.at->file->b, e.a0, r+e.nname+1, e.a1-e.a0);
}
}else{
n = e.q1 - e.q0;
r = runemalloc(n);
bufread(&t->file->b, e.q0, r, n);
}
f &= ~2;
if(n <= EVENTSIZE)
winevent(t->w, "%c%d %d %d %d %.*S\n", c, e.q0, e.q1, f, n, n, r);
else
winevent(t->w, "%c%d %d %d 0 \n", c, e.q0, e.q1, f, n);
free(r);
goto Return;
}
if(plumbsendfid != nil){
/* send whitespace-delimited word to plumber */
m = emalloc(sizeof(Plumbmsg));
m->src = estrdup("acme");
m->dst = nil;
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
if(dir.nr == 0)
m->wdir = estrdup(wdir);
else
m->wdir = runetobyte(dir.r, dir.nr);
free(dir.r);
m->type = estrdup("text");
m->attr = nil;
buf[0] = '\0';
if(q1 == q0){
if(t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
q0 = t->q0;
q1 = t->q1;
}else{
p = q0;
while(q0>0 && (c=tgetc(t, q0-1))!=' ' && c!='\t' && c!='\n')
q0--;
while(q1<t->file->b.nc && (c=tgetc(t, q1))!=' ' && c!='\t' && c!='\n')
q1++;
if(q1 == q0){
plumbfree(m);
goto Return;
}
sprint(buf, "click=%d", p-q0);
m->attr = plumbunpackattr(buf);
}
}
r = runemalloc(q1-q0);
bufread(&t->file->b, q0, r, q1-q0);
m->data = runetobyte(r, q1-q0);
m->ndata = strlen(m->data);
free(r);
if(m->ndata<messagesize-1024 && plumbsendtofid(plumbsendfid, m) >= 0){
plumbfree(m);
goto Return;
}
plumbfree(m);
/* plumber failed to match; fall through */
}
/* interpret alphanumeric string ourselves */
if(expanded == FALSE)
return;
if(e.name || e.u.at)
openfile(t, &e);
else{
if(t->w == nil)
return;
ct = &t->w->body;
if(t->w != ct->w)
winlock(ct->w, 'M');
if(t == ct)
textsetselect(ct, e.q1, e.q1);
n = e.q1 - e.q0;
r = runemalloc(n);
bufread(&t->file->b, e.q0, r, n);
if(search(ct, r, n) && e.jump)
moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4)));
if(t->w != ct->w)
winunlock(ct->w);
free(r);
}
Return:
free(e.name);
free(e.bname);
}
int
plumbgetc(void *a, uint n)
{
Rune *r;
r = a;
if(n>runestrlen(r))
return 0;
return r[n];
}
void
plumblook(Plumbmsg *m)
{
Expand e;
char *addr;
if(m->ndata >= BUFSIZE){
warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
return;
}
e.q0 = 0;
e.q1 = 0;
if(m->data[0] == '\0')
return;
e.u.ar = nil;
e.bname = m->data;
e.name = bytetorune(e.bname, &e.nname);
e.jump = TRUE;
e.a0 = 0;
e.a1 = 0;
addr = plumblookup(m->attr, "addr");
if(addr != nil){
e.u.ar = bytetorune(addr, &e.a1);
e.agetc = plumbgetc;
}
drawtopwindow();
openfile(nil, &e);
free(e.name);
free(e.u.at);
}
void
plumbshow(Plumbmsg *m)
{
Window *w;
Rune rb[256], *r;
int nb, nr;
Runestr rs;
char *name, *p, namebuf[16];
drawtopwindow();
w = makenewwindow(nil);
name = plumblookup(m->attr, "filename");
if(name == nil){
name = namebuf;
nuntitled++;
snprint(namebuf, sizeof namebuf, "Untitled-%d", nuntitled);
}
p = nil;
if(name[0]!='/' && m->wdir!=nil && m->wdir[0]!='\0'){
nb = strlen(m->wdir) + 1 + strlen(name) + 1;
p = emalloc(nb);
snprint(p, nb, "%s/%s", m->wdir, name);
name = p;
}
cvttorunes(name, strlen(name), rb, &nb, &nr, nil);
free(p);
rs = cleanrname(runestr(rb, nr));
winsetname(w, rs.r, rs.nr);
r = runemalloc(m->ndata);
cvttorunes(m->data, m->ndata, r, &nb, &nr, nil);
textinsert(&w->body, 0, r, nr, TRUE);
free(r);
w->body.file->mod = FALSE;
w->dirty = FALSE;
winsettag(w);
textscrdraw(&w->body);
textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
xfidlog(w, "new");
}
int
search(Text *ct, Rune *r, uint n)
{
uint q, nb, maxn;
int around;
Rune *s, *b, *c;
if(n==0 || n>ct->file->b.nc)
return FALSE;
if(2*n > RBUFSIZE){
warning(nil, "string too long\n");
return FALSE;
}
maxn = max(2*n, RBUFSIZE);
s = fbufalloc();
b = s;
nb = 0;
b[nb] = 0;
around = 0;
q = ct->q1;
for(;;){
if(q >= ct->file->b.nc){
q = 0;
around = 1;
nb = 0;
b[nb] = 0;
}
if(nb > 0){
c = runestrchr(b, r[0]);
if(c == nil){
q += nb;
nb = 0;
b[nb] = 0;
if(around && q>=ct->q1)
break;
continue;
}
q += (c-b);
nb -= (c-b);
b = c;
}
/* reload if buffer covers neither string nor rest of file */
if(nb<n && nb!=ct->file->b.nc-q){
nb = ct->file->b.nc-q;
if(nb >= maxn)
nb = maxn-1;
bufread(&ct->file->b, q, s, nb);
b = s;
b[nb] = '\0';
}
/* this runeeq is fishy but the null at b[nb] makes it safe */
if(runeeq(b, n, r, n)==TRUE){
if(ct->w){
textshow(ct, q, q+n, 1);
winsettag(ct->w);
}else{
ct->q0 = q;
ct->q1 = q+n;
}
seltext = ct;
fbuffree(s);
return TRUE;
}
--nb;
b++;
q++;
if(around && q>=ct->q1)
break;
}
fbuffree(s);
return FALSE;
}
int
isfilec(Rune r)
{
static Rune Lx[] = { '.', '-', '+', '/', ':', 0 };
if(isalnum(r))
return TRUE;
if(runestrchr(Lx, r))
return TRUE;
return FALSE;
}
/* Runestr wrapper for cleanname */
Runestr
cleanrname(Runestr rs)
{
char *s;
int nb, nulls;
s = runetobyte(rs.r, rs.nr);
cleanname(s);
cvttorunes(s, strlen(s), rs.r, &nb, &rs.nr, &nulls);
free(s);
return rs;
}
Runestr
includefile(Rune *dir, Rune *file, int nfile)
{
int m, n;
char *a;
Rune *r;
static Rune Lslash[] = { '/', 0 };
m = runestrlen(dir);
a = emalloc((m+1+nfile)*UTFmax+1);
sprint(a, "%S/%.*S", dir, nfile, file);
n = access(a, 0);
free(a);
if(n < 0)
return runestr(nil, 0);
r = runemalloc(m+1+nfile);
runemove(r, dir, m);
runemove(r+m, Lslash, 1);
runemove(r+m+1, file, nfile);
free(file);
return cleanrname(runestr(r, m+1+nfile));
}
static Rune *objdir;
Runestr
includename(Text *t, Rune *r, int n)
{
Window *w;
char buf[128];
Rune Lsysinclude[] = { '/', 's', 'y', 's', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
Rune Lusrinclude[] = { '/', 'u', 's', 'r', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
Rune Lusrlocalinclude[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
'/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
Rune Lusrlocalplan9include[] = { '/', 'u', 's', 'r', '/', 'l', 'o', 'c', 'a', 'l',
'/', 'p', 'l', 'a', 'n', '9', '/', 'i', 'n', 'c', 'l', 'u', 'd', 'e', 0 };
Runestr file;
int i;
if(objdir==nil && objtype!=nil){
sprint(buf, "/%s/include", objtype);
objdir = bytetorune(buf, &i);
objdir = runerealloc(objdir, i+1);
objdir[i] = '\0';
}
w = t->w;
if(n==0 || r[0]=='/' || w==nil)
goto Rescue;
if(n>2 && r[0]=='.' && r[1]=='/')
goto Rescue;
file.r = nil;
file.nr = 0;
for(i=0; i<w->nincl && file.r==nil; i++)
file = includefile(w->incl[i], r, n);
if(file.r == nil)
file = includefile(Lsysinclude, r, n);
if(file.r == nil)
file = includefile(Lusrlocalplan9include, r, n);
if(file.r == nil)
file = includefile(Lusrlocalinclude, r, n);
if(file.r == nil)
file = includefile(Lusrinclude, r, n);
if(file.r==nil && objdir!=nil)
file = includefile(objdir, r, n);
if(file.r == nil)
goto Rescue;
return file;
Rescue:
return runestr(r, n);
}
Runestr
dirname(Text *t, Rune *r, int n)
{
Rune *b, c;
uint m, nt;
int slash;
Runestr tmp;
b = nil;
if(t==nil || t->w==nil)
goto Rescue;
nt = t->w->tag.file->b.nc;
if(nt == 0)
goto Rescue;
if(n>=1 && r[0]=='/')
goto Rescue;
b = runemalloc(nt+n+1);
bufread(&t->w->tag.file->b, 0, b, nt);
slash = -1;
for(m=0; m<nt; m++){
c = b[m];
if(c == '/')
slash = m;
if(c==' ' || c=='\t')
break;
}
if(slash < 0)
goto Rescue;
runemove(b+slash+1, r, n);
free(r);
return cleanrname(runestr(b, slash+1+n));
Rescue:
free(b);
tmp = runestr(r, n);
if(r)
return cleanrname(tmp);
return tmp;
}
static int
texthas(Text *t, uint q0, Rune *r)
{
int i;
if((int)q0 < 0)
return FALSE;
for(i=0; r[i]; i++)
if(q0+i >= t->file->b.nc || textreadc(t, q0+i) != r[i])
return FALSE;
return TRUE;
}
int
expandfile(Text *t, uint q0, uint q1, Expand *e)
{
int i, n, nname, colon, eval;
uint amin, amax;
Rune *r, c;
Window *w;
Runestr rs;
Rune Lhttpcss[] = {'h', 't', 't', 'p', ':', '/', '/', 0};
Rune Lhttpscss[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0};
amax = q1;
if(q1 == q0){
colon = -1;
while(q1<t->file->b.nc && isfilec(c=textreadc(t, q1))){
if(c == ':' && !texthas(t, q1-4, Lhttpcss) && !texthas(t, q1-5, Lhttpscss)){
colon = q1;
break;
}
q1++;
}
while(q0>0 && (isfilec(c=textreadc(t, q0-1)) || isaddrc(c) || isregexc(c))){
q0--;
if(colon<0 && c==':' && !texthas(t, q0-4, Lhttpcss) && !texthas(t, q0-5, Lhttpscss))
colon = q0;
}
/*
* if it looks like it might begin file: , consume address chars after :
* otherwise terminate expansion at :
*/
if(colon >= 0){
q1 = colon;
if(colon<t->file->b.nc-1 && isaddrc(textreadc(t, colon+1))){
q1 = colon+1;
while(q1<t->file->b.nc && isaddrc(textreadc(t, q1)))
q1++;
}
}
if(q1 > q0)
if(colon >= 0){ /* stop at white space */
for(amax=colon+1; amax<t->file->b.nc; amax++)
if((c=textreadc(t, amax))==' ' || c=='\t' || c=='\n')
break;
}else
amax = t->file->b.nc;
}
amin = amax;
e->q0 = q0;
e->q1 = q1;
n = q1-q0;
if(n == 0)
return FALSE;
/* see if it's a file name */
r = runemalloc(n+1);
bufread(&t->file->b, q0, r, n);
r[n] = 0;
/* is it a URL? look for http:// and https:// prefix */
if(runestrncmp(r, Lhttpcss, 7) == 0 || runestrncmp(r, Lhttpscss, 8) == 0){
// Avoid capturing end-of-sentence punctuation.
if(r[n-1] == '.') {
e->q1--;
n--;
}
e->name = r;
e->nname = n;
e->u.at = t;
e->a0 = e->q1;
e->a1 = e->q1;
return TRUE;
}
/* first, does it have bad chars? */
nname = -1;
for(i=0; i<n; i++){
c = r[i];
if(c==':' && nname<0){
if(q0+i+1<t->file->b.nc && (i==n-1 || isaddrc(textreadc(t, q0+i+1))))
amin = q0+i;
else
goto Isntfile;
nname = i;
}
}
if(nname == -1)
nname = n;
for(i=0; i<nname; i++)
if(!isfilec(r[i]))
goto Isntfile;
/*
* See if it's a file name in <>, and turn that into an include
* file name if so. Should probably do it for "" too, but that's not
* restrictive enough syntax and checking for a #include earlier on the
* line would be silly.
*/
if(q0>0 && textreadc(t, q0-1)=='<' && q1<t->file->b.nc && textreadc(t, q1)=='>'){
rs = includename(t, r, nname);
r = rs.r;
nname = rs.nr;
}
else if(amin == q0)
goto Isfile;
else{
rs = dirname(t, r, nname);
r = rs.r;
nname = rs.nr;
}
e->bname = runetobyte(r, nname);
/* if it's already a window name, it's a file */
w = lookfile(r, nname);
if(w != nil)
goto Isfile;
/* if it's the name of a file, it's a file */
if(ismtpt(e->bname) || access(e->bname, 0) < 0){
free(e->bname);
e->bname = nil;
goto Isntfile;
}
Isfile:
e->name = r;
e->nname = nname;
e->u.at = t;
e->a0 = amin+1;
eval = FALSE;
address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
return TRUE;
Isntfile:
free(r);
return FALSE;
}
int
expand(Text *t, uint q0, uint q1, Expand *e)
{
memset(e, 0, sizeof *e);
e->agetc = tgetc;
/* if in selection, choose selection */
e->jump = TRUE;
if(q1==q0 && t->q1>t->q0 && t->q0<=q0 && q0<=t->q1){
q0 = t->q0;
q1 = t->q1;
if(t->what == Tag)
e->jump = FALSE;
}
if(expandfile(t, q0, q1, e))
return TRUE;
if(q0 == q1){
while(q1<t->file->b.nc && isalnum(textreadc(t, q1)))
q1++;
while(q0>0 && isalnum(textreadc(t, q0-1)))
q0--;
}
e->q0 = q0;
e->q1 = q1;
return q1 > q0;
}
Window*
lookfile(Rune *s, int n)
{
int i, j, k;
Window *w;
Column *c;
Text *t;
/* avoid terminal slash on directories */
if(n>1 && s[n-1] == '/')
--n;
for(j=0; j<row.ncol; j++){
c = row.col[j];
for(i=0; i<c->nw; i++){
w = c->w[i];
t = &w->body;
k = t->file->nname;
if(k>1 && t->file->name[k-1] == '/')
k--;
if(runeeq(t->file->name, k, s, n)){
w = w->body.file->curtext->w;
if(w->col != nil) /* protect against race deleting w */
return w;
}
}
}
return nil;
}
Window*
lookid(int id, int dump)
{
int i, j;
Window *w;
Column *c;
for(j=0; j<row.ncol; j++){
c = row.col[j];
for(i=0; i<c->nw; i++){
w = c->w[i];
if(dump && w->dumpid == id)
return w;
if(!dump && w->id == id)
return w;
}
}
return nil;
}
Window*
openfile(Text *t, Expand *e)
{
Range r;
Window *w, *ow;
int eval, i, n;
Rune *rp;
Runestr rs;
uint dummy;
r.q0 = 0;
r.q1 = 0;
if(e->nname == 0){
w = t->w;
if(w == nil)
return nil;
}else{
w = lookfile(e->name, e->nname);
if(w == nil && e->name[0] != '/'){
/*
* Unrooted path in new window.
* This can happen if we type a pwd-relative path
* in the topmost tag or the column tags.
* Most of the time plumber takes care of these,
* but plumber might not be running or might not
* be configured to accept plumbed directories.
* Make the name a full path, just like we would if
* opening via the plumber.
*/
n = utflen(wdir)+1+e->nname+1;
rp = runemalloc(n);
runesnprint(rp, n, "%s/%.*S", wdir, e->nname, e->name);
rs = cleanrname(runestr(rp, n-1));
free(e->name);
e->name = rs.r;
e->nname = rs.nr;
w = lookfile(e->name, e->nname);
}
}
if(w){
t = &w->body;
if(!t->col->safe && t->fr.maxlines==0) /* window is obscured by full-column window */
colgrow(t->col, t->col->w[0], 1);
}else{
ow = nil;
if(t)
ow = t->w;
w = makenewwindow(t);
t = &w->body;
winsetname(w, e->name, e->nname);
if(textload(t, 0, e->bname, 1) >= 0)
t->file->unread = FALSE;
t->file->mod = FALSE;
t->w->dirty = FALSE;
winsettag(t->w);
textsetselect(&t->w->tag, t->w->tag.file->b.nc, t->w->tag.file->b.nc);
if(ow != nil){
for(i=ow->nincl; --i>=0; ){
n = runestrlen(ow->incl[i]);
rp = runemalloc(n);
runemove(rp, ow->incl[i], n);
winaddincl(w, rp, n);
}
w->autoindent = ow->autoindent;
}else
w->autoindent = globalautoindent;
xfidlog(w, "new");
}
if(e->a1 == e->a0)
eval = FALSE;
else{
eval = TRUE;
r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy);
if(r.q0 > r.q1) {
eval = FALSE;
warning(nil, "addresses out of order\n");
}
if(eval == FALSE)
e->jump = FALSE; /* don't jump if invalid address */
}
if(eval == FALSE){
r.q0 = t->q0;
r.q1 = t->q1;
}
textshow(t, r.q0, r.q1, 1);
winsettag(t->w);
seltext = t;
if(e->jump)
moveto(mousectl, addpt(frptofchar(&t->fr, t->fr.p0), Pt(4, font->height-4)));
return w;
}
void
new(Text *et, Text *t, Text *argt, int flag1, int flag2, Rune *arg, int narg)
{
int ndone;
Rune *a, *f;
int na, nf;
Expand e;
Runestr rs;
Window *w;
getarg(argt, FALSE, TRUE, &a, &na);
if(a){
new(et, t, nil, flag1, flag2, a, na);
if(narg == 0)
return;
}
/* loop condition: *arg is not a blank */
for(ndone=0; ; ndone++){
a = findbl(arg, narg, &na);
if(a == arg){
if(ndone==0 && et->col!=nil) {
w = coladd(et->col, nil, nil, -1);
winsettag(w);
xfidlog(w, "new");
}
break;
}
nf = narg-na;
f = runemalloc(nf);
runemove(f, arg, nf);
rs = dirname(et, f, nf);
memset(&e, 0, sizeof e);
e.name = rs.r;
e.nname = rs.nr;
e.bname = runetobyte(rs.r, rs.nr);
e.jump = TRUE;
openfile(et, &e);
free(e.name);
free(e.bname);
arg = skipbl(a, na, &narg);
}
}

181
mail/dat.h Normal file
View file

@ -0,0 +1,181 @@
typedef struct Event Event;
typedef struct Exec Exec;
typedef struct Message Message;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* coordinate wineventproc and window thread */
QLock lk;
int ref;
/* file descriptors */
CFid* ctl;
CFid* event;
CFid* addr;
CFid* data;
CFid* body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent;
};
struct Message
{
Window *w;
CFid* ctlfd;
char *name;
char *replyname;
uchar opened;
uchar dirty;
uchar isreply;
uchar deleted;
uchar writebackdel;
uchar tagposted;
uchar recursed;
uchar level;
uint replywinid;
/* header info */
char *from;
char *fromcolon;
char *to;
char *cc;
char *replyto;
char *sender;
char *date;
char *subject;
char *type;
char *disposition;
char *filename;
char *digest;
Message *next; /* next in this mailbox */
Message *prev; /* prev in this mailbox */
Message *head; /* first subpart */
Message *tail; /* last subpart */
};
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char *prog;
char **argv;
int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
Channel *sync;
};
extern Window* newwindow(void);
extern CFid* winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern char* winselection(Window*);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern void winincref(Window*);
extern void windecref(Window*);
extern void readmbox(Message*, char*, char*);
extern void rewritembox(Window*, Message*);
extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
extern void delreply(Message*);
extern int mesgadd(Message*, char*, Dir*, char*);
extern void mesgmenu(Window*, Message*);
extern void mesgmenunew(Window*, Message*);
extern int mesgopen(Message*, char*, char*, Message*, int, char*);
extern void mesgctl(void*);
extern void mesgsend(Message*);
extern void mesgdel(Message*, Message*);
extern void mesgmenudel(Window*, Message*, Message*);
extern void mesgmenumark(Window*, char*, char*);
extern void mesgmenumarkdel(Window*, Message*, Message*, int);
extern Message* mesglookup(Message*, char*, char*);
extern Message* mesglookupfile(Message*, char*, char*);
extern void mesgfreeparts(Message*);
extern int mesgcommand(Message*, char*);
extern char* info(Message*, int, int);
extern char* readfile(char*, char*, int*);
extern char* readbody(char*, char*, int*);
extern void ctlprint(CFid*, char*, ...);
extern void* emalloc(uint);
extern void* erealloc(void*, uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
extern void execproc(void*);
extern int fsprint(CFid*, char*, ...);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
extern Window *wbox;
extern Message mbox;
extern Message replies;
extern char *fsname;
extern CFid *plumbsendfd;
extern CFid *plumbseemailfd;
extern char *home;
extern char *outgoing;
extern char *mailboxdir;
extern char *mboxname;
extern char *user;
extern char *srvname;
extern char deleted[];
extern int wctlfd;
extern int shortmenu;
extern CFsys *mailfs;
extern CFsys *acmefs;

4
mail/guide Normal file
View file

@ -0,0 +1,4 @@
Mail stored
plumb /mail/box/$user/names
mail -'x' someaddress
mkbox /mail/box/$user/new_box

75
mail/html.c Normal file
View file

@ -0,0 +1,75 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
char*
formathtml(char *body, int *np)
{
int i, j, p[2], q[2];
Exec *e;
char buf[1024];
Channel *sync;
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0 || pipe(q) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->q[0] = q[0];
e->q[1] = q[1];
e->argv = emalloc(3*sizeof(char*));
e->argv[0] = estrdup("htmlfmt");
e->argv[1] = estrdup("-cutf-8");
e->argv[2] = nil;
e->prog = "htmlfmt";
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
close(p[0]);
close(q[1]);
if((i=write(p[1], body, *np)) != *np){
fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
close(p[1]);
close(q[0]);
return body;
}
close(p[1]);
free(body);
body = nil;
i = 0;
for(;;){
j = read(q[0], buf, sizeof buf);
if(j <= 0)
break;
body = realloc(body, i+j+1);
if(body == nil)
error("realloc failed: %r");
memmove(body+i, buf, j);
i += j;
body[i] = '\0';
}
close(q[0]);
*np = i;
return body;
}
char*
readbody(char *type, char *dir, int *np)
{
char *body;
body = readfile(dir, "body", np);
if(body != nil && strcmp(type, "text/html") == 0)
return formathtml(body, np);
return body;
}

BIN
mail/html.o Normal file

Binary file not shown.

644
mail/mail.c Normal file
View file

@ -0,0 +1,644 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <9pclient.h>
#include <plumb.h>
#include <ctype.h>
#include "dat.h"
char *maildir = "Mail/"; /* mountpoint of mail file system */
char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
char *mailboxdir = nil; /* nil == /mail/box/$user */
char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
char *user;
char *outgoing;
char *srvname;
Window *wbox;
Message mbox;
Message replies;
char *home;
CFid *plumbsendfd;
CFid *plumbseemailfd;
CFid *plumbshowmailfd;
CFid *plumbsendmailfd;
Channel *cplumb;
Channel *cplumbshow;
Channel *cplumbsend;
int wctlfd;
void mainctl(void*);
void plumbproc(void*);
void plumbshowproc(void*);
void plumbsendproc(void*);
void plumbthread(void);
void plumbshowthread(void*);
void plumbsendthread(void*);
int shortmenu;
CFsys *mailfs;
CFsys *acmefs;
void
usage(void)
{
fprint(2, "usage: Mail [-sS] [-n srvname] [-o outgoing] [mailboxname [directoryname]]\n");
threadexitsall("usage");
}
void
removeupasfs(void)
{
char buf[256];
if(strcmp(mboxname, "mbox") == 0)
return;
snprint(buf, sizeof buf, "close %s", mboxname);
fswrite(mbox.ctlfd, buf, strlen(buf));
}
int
ismaildir(char *s)
{
Dir *d;
int ret;
d = fsdirstat(mailfs, s);
if(d == nil)
return 0;
ret = d->qid.type & QTDIR;
free(d);
return ret;
}
void
threadmain(int argc, char *argv[])
{
char *s, *name;
char err[ERRMAX], *cmd;
int i, newdir;
Fmt fmt;
doquote = needsrcquote;
quotefmtinstall();
/* open these early so we won't miss notification of new mail messages while we read mbox */
if((plumbsendfd = plumbopenfid("send", OWRITE|OCEXEC)) == nil)
fprint(2, "warning: open plumb/send: %r\n");
if((plumbseemailfd = plumbopenfid("seemail", OREAD|OCEXEC)) == nil)
fprint(2, "warning: open plumb/seemail: %r\n");
if((plumbshowmailfd = plumbopenfid("showmail", OREAD|OCEXEC)) == nil)
fprint(2, "warning: open plumb/showmail: %r\n");
shortmenu = 0;
srvname = "mail";
ARGBEGIN{
case 's':
shortmenu = 1;
break;
case 'S':
shortmenu = 2;
break;
case 'o':
outgoing = EARGF(usage());
break;
case 'm':
smprint(maildir, "%s/", EARGF(usage()));
break;
case 'n':
srvname = EARGF(usage());
break;
default:
usage();
}ARGEND
acmefs = nsmount("acme",nil);
if(acmefs == nil)
error("cannot mount acme: %r");
mailfs = nsmount(srvname, nil);
if(mailfs == nil)
error("cannot mount %s: %r", srvname);
name = "mbox";
newdir = 1;
if(argc > 0){
i = strlen(argv[0]);
if(argc>2 || i==0)
usage();
/* see if the name is that of an existing /mail/fs directory */
if(argc==1 && argv[0][0] != '/' && ismaildir(argv[0])){
name = argv[0];
mboxname = estrdup(name);
newdir = 0;
}else{
if(argv[0][i-1] == '/')
argv[0][i-1] = '\0';
s = strrchr(argv[0], '/');
if(s == nil)
mboxname = estrdup(argv[0]);
else{
*s++ = '\0';
if(*s == '\0')
usage();
mailboxdir = argv[0];
mboxname = estrdup(s);
}
if(argc > 1)
name = argv[1];
else
name = mboxname;
}
}
user = getenv("user");
if(user == nil)
user = "none";
home = getenv("home");
if(home == nil)
home = getenv("HOME");
if(home == nil)
error("can't find $home");
if(mailboxdir == nil)
mailboxdir = estrstrdup(home, "/mail");
if(outgoing == nil)
outgoing = estrstrdup(mailboxdir, "/outgoing");
mbox.ctlfd = fsopen(mailfs, estrstrdup(mboxname, "/ctl"), OWRITE);
if(mbox.ctlfd == nil)
error("can't open %s: %r", estrstrdup(mboxname, "/ctl"));
fsname = estrdup(name);
if(newdir && argc > 0){
s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
for(i=0; i<10; i++){
sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
if(fswrite(mbox.ctlfd, s, strlen(s)) >= 0)
break;
err[0] = '\0';
errstr(err, sizeof err);
if(strstr(err, "mbox name in use") == nil)
error("can't create directory %s for mail: %s", name, err);
free(fsname);
fsname = emalloc(strlen(name)+10);
sprint(fsname, "%s-%d", name, i);
}
if(i == 10)
error("can't open %s/%s: %r", mailboxdir, mboxname);
free(s);
}
s = estrstrdup(fsname, "/");
mbox.name = estrstrdup(maildir, s);
mbox.level= 0;
readmbox(&mbox, maildir, s);
home = getenv("home");
if(home == nil)
home = "/";
wbox = newwindow();
winname(wbox, mbox.name);
wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
threadcreate(mainctl, wbox, STACK);
fmtstrinit(&fmt);
fmtprint(&fmt, "Mail");
if(shortmenu)
fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
if(outgoing)
fmtprint(&fmt, " -o %s", outgoing);
fmtprint(&fmt, " %s", name);
cmd = fmtstrflush(&fmt);
if(cmd == nil)
sysfatal("out of memory");
winsetdump(wbox, "/acme/mail", cmd);
mbox.w = wbox;
mesgmenu(wbox, &mbox);
winclean(wbox);
/* wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
wctlfd = -1;
cplumb = chancreate(sizeof(Plumbmsg*), 0);
cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
if(strcmp(name, "mbox") == 0){
/*
* Avoid creating multiple windows to send mail by only accepting
* sendmail plumb messages if we're reading the main mailbox.
*/
plumbsendmailfd = plumbopenfid("sendmail", OREAD|OCEXEC);
cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
proccreate(plumbsendproc, nil, STACK);
threadcreate(plumbsendthread, nil, STACK);
}
/* start plumb reader as separate proc ... */
proccreate(plumbproc, nil, STACK);
proccreate(plumbshowproc, nil, STACK);
threadcreate(plumbshowthread, nil, STACK);
fswrite(mbox.ctlfd, "refresh", 7);
/* ... and use this thread to read the messages */
plumbthread();
}
void
plumbproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbproc");
for(;;){
m = plumbrecvfid(plumbseemailfd);
sendp(cplumb, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbshowproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbshowproc");
for(;;){
m = plumbrecvfid(plumbshowmailfd);
sendp(cplumbshow, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbsendproc(void* v)
{
Plumbmsg *m;
threadsetname("plumbsendproc");
for(;;){
m = plumbrecvfid(plumbsendmailfd);
sendp(cplumbsend, m);
if(m == nil)
threadexits(nil);
}
}
void
newmesg(char *name, char *digest)
{
Dir *d;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
if(mesglookupfile(&mbox, name, digest) != nil)
return;
if(strncmp(name, "Mail/", 5) == 0)
name += 5;
d = fsdirstat(mailfs, name);
if(d == nil)
return;
if(mesgadd(&mbox, mbox.name, d, digest))
mesgmenunew(wbox, &mbox);
free(d);
}
void
showmesg(char *name, char *digest)
{
char *n;
char *mb;
mb = mbox.name;
if(strncmp(name, mb, strlen(mb)) != 0)
return; /* message is about another mailbox */
n = estrdup(name+strlen(mb));
if(n[strlen(n)-1] != '/')
n = egrow(n, "/", nil);
mesgopen(&mbox, mbox.name, name+strlen(mb), nil, 1, digest);
free(n);
}
void
delmesg(char *name, char *digest, int dodel, char *save)
{
Message *m;
m = mesglookupfile(&mbox, name, digest);
if(m != nil){
if(save)
mesgcommand(m, estrstrdup("Save ", save));
if(dodel)
mesgmenumarkdel(wbox, &mbox, m, 1);
else{
/* notification came from plumber - message is gone */
mesgmenudel(wbox, &mbox, m);
if(!m->opened)
mesgdel(&mbox, m);
}
}
}
void
plumbthread(void)
{
Plumbmsg *m;
Plumbattr *a;
char *type, *digest;
threadsetname("plumbthread");
while((m = recvp(cplumb)) != nil){
a = m->attr;
digest = plumblookup(a, "digest");
type = plumblookup(a, "mailtype");
if(type == nil)
fprint(2, "Mail: plumb message with no mailtype attribute\n");
else if(strcmp(type, "new") == 0)
newmesg(m->data, digest);
else if(strcmp(type, "delete") == 0)
delmesg(m->data, digest, 0, nil);
else
fprint(2, "Mail: unknown plumb attribute %s\n", type);
plumbfree(m);
}
threadexits(nil);
}
void
plumbshowthread(void *v)
{
Plumbmsg *m;
USED(v);
threadsetname("plumbshowthread");
while((m = recvp(cplumbshow)) != nil){
showmesg(m->data, plumblookup(m->attr, "digest"));
plumbfree(m);
}
threadexits(nil);
}
void
plumbsendthread(void *v)
{
Plumbmsg *m;
USED(v);
threadsetname("plumbsendthread");
while((m = recvp(cplumbsend)) != nil){
mkreply(nil, "Mail", m->data, m->attr, nil);
plumbfree(m);
}
threadexits(nil);
}
int
mboxcommand(Window *w, char *s)
{
char *args[10], **targs, *save;
Window *sbox;
Message *m, *next;
int ok, nargs, i, j;
CFid *searchfd;
char buf[128], *res;
nargs = tokenize(s, args, nelem(args));
if(nargs == 0)
return 0;
if(strcmp(args[0], "Mail") == 0){
if(nargs == 1)
mkreply(nil, "Mail", "", nil, nil);
else
mkreply(nil, "Mail", args[1], nil, nil);
return 1;
}
if(strcmp(s, "Del") == 0){
if(mbox.dirty){
mbox.dirty = 0;
fprint(2, "mail: mailbox not written\n");
return 1;
}
if(w != mbox.w){
windel(w, 1);
return 1;
}
ok = 1;
for(m=mbox.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
for(m=replies.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
if(ok){
windel(w, 1);
removeupasfs();
threadexitsall(nil);
}
return 1;
}
if(strcmp(s, "Put") == 0){
rewritembox(wbox, &mbox);
return 1;
}
if(strcmp(s, "Get") == 0){
fswrite(mbox.ctlfd, "refresh", 7);
return 1;
}
if(strcmp(s, "Delmesg") == 0){
save = nil;
if(nargs > 1)
save = args[1];
s = winselection(w);
if(s == nil)
return 1;
nargs = 1;
for(i=0; s[i]; i++)
if(s[i] == '\n')
nargs++;
targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
nargs = getfields(s, targs, nargs, 1, "\n");
for(i=0; i<nargs; i++){
if(!isdigit(targs[i][0]))
continue;
j = atoi(targs[i]); /* easy way to parse the number! */
if(j == 0)
continue;
snprint(buf, sizeof buf, "%s%d", mbox.name, j);
delmesg(buf, nil, 1, save);
}
free(s);
free(targs);
return 1;
}
if(strcmp(s, "Search") == 0){
if(nargs <= 1)
return 1;
s = estrstrdup(mboxname, "/search");
searchfd = fsopen(mailfs, s, ORDWR);
if(searchfd == nil)
return 1;
save = estrdup(args[1]);
for(i=2; i<nargs; i++)
save = eappend(save, " ", args[i]);
fswrite(searchfd, save, strlen(save));
fsseek(searchfd, 0, 0);
j = fsread(searchfd, buf, sizeof buf - 1);
if(j == 0){
fprint(2, "[%s] search %s: no results found\n", mboxname, save);
fsclose(searchfd);
free(save);
return 1;
}
free(save);
buf[j] = '\0';
res = estrdup(buf);
j = fsread(searchfd, buf, sizeof buf - 1);
for(; j != 0; j = fsread(searchfd, buf, sizeof buf - 1), buf[j] = '\0')
res = eappend(res, "", buf);
fsclose(searchfd);
sbox = newwindow();
winname(sbox, s);
free(s);
threadcreate(mainctl, sbox, STACK);
winopenbody(sbox, OWRITE);
/* show results in reverse order */
m = mbox.tail;
save = nil;
for(s=strrchr(res, ' '); s!=nil || save!=res; s=strrchr(res, ' ')){
if(s != nil){
save = s+1;
*s = '\0';
}
else save = res;
save = estrstrdup(save, "/");
for(; m && strcmp(save, m->name) != 0; m=m->prev);
free(save);
if(m == nil)
break;
fsprint(sbox->body, "%s%s\n", m->name, info(m, 0, 0));
m = m->prev;
}
free(res);
winclean(sbox);
winclosebody(sbox);
return 1;
}
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e, *e2, *eq, *ea;
int na, nopen;
char *s, *t, *buf;
w = v;
winincref(w);
proccreate(wineventproc, w, STACK);
for(;;){
e = recvp(w->cevent);
switch(e->c1){
default:
Unknown:
print("unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* type away; we don't care */
break;
case 'M':
switch(e->c2){
case 'x':
case 'X':
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
s = e->b;
/* if it's a known command, do it */
if((e->flag&2) && e->nb==0)
s = e2->b;
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!mboxcommand(w, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l':
case 'L':
buf = nil;
eq = e;
if(e->flag & 2){
e2 = recvp(w->cevent);
eq = e2;
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
nopen = 0;
do{
/* skip 'deleted' string if present' */
if(strncmp(s, deleted, strlen(deleted)) == 0)
s += strlen(deleted);
/* skip mail box name if present */
if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
s += strlen(mbox.name);
nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
while(*s!='\0' && *s++!='\n')
;
}while(*s);
if(nopen == 0) /* send it back */
winwriteevent(w, e);
free(buf);
break;
case 'I': /* modify away; we don't care */
case 'D':
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
}
}

BIN
mail/mail.o Normal file

Binary file not shown.

1424
mail/mesg.c Normal file

File diff suppressed because it is too large Load diff

BIN
mail/mesg.o Normal file

Binary file not shown.

11
mail/mkbox Normal file
View file

@ -0,0 +1,11 @@
#!/bin/rc
for(i){
if(! test -f $i){
if(cp /dev/null $i){
chmod 600 $i
chmod +al $i
}
}
if not echo $i already exists
}

15
mail/mkfile Normal file
View file

@ -0,0 +1,15 @@
<$PLAN9/src/mkhdr
TARG=Mail
OFILES=\
html.$O\
mail.$O\
mesg.$O\
reply.$O\
util.$O\
win.$O
HFILES=dat.h
<$PLAN9/src/mkone

BIN
mail/o.Mail Executable file

Binary file not shown.

57
mail/readme Normal file
View file

@ -0,0 +1,57 @@
The Acme Mail program uses upas/fs to parse the mail box, and then
presents a file-browser-like user interface to reading and sending
messages. The Mail window presents each numbered message like the
contents of a directory presented one per line. If a message has a
Subject: line, that is shown indented on the following line.
Multipart MIME-encoded messages are presented in the obvious
hierarchical format.
Mail uses upas/fs to access the mail box. By default it reads "mbox",
the standard user mail box. If Mail is given an argument, it is
passed to upas/fs as the name of the mail box (or upas/fs directory)
to open.
Although Mail works if the plumber is not running, it's designed to be
run with plumbing enabled and many of its features work best if it is.
The mailbox window has a few commands: Put writes back the mailbox;
Mail creates a new window in which to compose a message; and Delmesg
deletes messages by number. The number may be given as argument or
indicated by selecting the header line in the mailbox window.
(Delmesg does not expand null selections, in the interest of safety.)
Clicking the right button on a message number opens it; clicking on
any of the subparts of a message opens that (and also opens the
message itself). Each message window has a few commands in the tag
with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
sender of the message, "Reply all" or "Replyall" replies to everyone
in the From:, To:, and CC: lines.
Message parts with recognized MIME types such as image/jpeg are sent
to the plumber for further dispatch. Acme Mail also listens to
messages on the seemail and showmail plumbing ports, to report the
arrival of new messages (highlighting the entry; right-click on the
entry to open the message) and open them if you right-click on the
face in the faces window.
When composing a mail message or replying to a message, the first line
of the text is a list of recipients of the message. To:, and CC:, and BCC:
lines are interpreted in the usual way. Two other header lines are
special to Acme Mail:
Include: file places a copy of file in the message as an
inline MIME attachment.
Attach: file places a copy of file in the message as a regular
MIME attachment.
Acme Mail uses these conventions when replying to messages,
constructing headers for the default behavior. You may edit these to
change behavior. Most important, when replying to a message Mail will
always Include: the original message; delete that line if you don't
want to include it.
If the mailbox
/mail/box/$user/outgoing
exists, Acme Mail will save your a copy of your outgoing messages
there. Attachments are described in the copy but not included.
The -m mntpoint flag specifies a different mount point for /upas/fs.

580
mail/reply.c Normal file
View file

@ -0,0 +1,580 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
static int replyid;
int
quote(Message *m, CFid *fid, char *dir, char *quotetext)
{
char *body, *type;
int i, n, nlines;
char **lines;
if(quotetext){
body = quotetext;
n = strlen(body);
type = nil;
}else{
/* look for first textual component to quote */
type = readfile(dir, "type", &n);
if(type == nil){
print("no type in %s\n", dir);
return 0;
}
if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
dir = estrstrdup(dir, "1/");
if(quote(m, fid, dir, nil)){
free(type);
free(dir);
return 1;
}
free(dir);
}
if(strncmp(type, "text", 4) != 0){
free(type);
return 0;
}
body = readbody(m->type, dir, &n);
if(body == nil)
return 0;
}
nlines = 0;
for(i=0; i<n; i++)
if(body[i] == '\n')
nlines++;
nlines++;
lines = emalloc(nlines*sizeof(char*));
nlines = getfields(body, lines, nlines, 0, "\n");
/* delete leading and trailing blank lines */
i = 0;
while(i<nlines && lines[i][0]=='\0')
i++;
while(i<nlines && lines[nlines-1][0]=='\0')
nlines--;
while(i < nlines){
fsprint(fid, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
i++;
}
free(lines);
free(body); /* will free quotetext if non-nil */
free(type);
return 1;
}
void
mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
{
char buf[100];
CFid *fd;
Message *r;
char *dir, *t;
int quotereply;
Plumbattr *a;
quotereply = (label[0] == 'Q');
if(quotereply && m && m->replywinid > 0){
snprint(buf, sizeof buf, "%d/body", m->replywinid);
if((fd = fsopen(acmefs, buf, OWRITE)) != nil){
dir = estrstrdup(mbox.name, m->name);
quote(m, fd, dir, quotetext);
free(dir);
return;
}
}
r = emalloc(sizeof(Message));
r->isreply = 1;
if(m != nil)
r->replyname = estrdup(m->name);
r->next = replies.head;
r->prev = nil;
if(replies.head != nil)
replies.head->prev = r;
replies.head = r;
if(replies.tail == nil)
replies.tail = r;
r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
r->w = newwindow();
if(m)
m->replywinid = r->w->id;
winname(r->w, r->name);
ctlprint(r->w->ctl, "cleartag");
wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
r->tagposted = 1;
threadcreate(mesgctl, r, STACK);
winopenbody(r->w, OWRITE);
if(to!=nil && to[0]!='\0')
fsprint(r->w->body, "%s\n", to);
for(a=attr; a; a=a->next)
fsprint(r->w->body, "%s: %s\n", a->name, a->value);
dir = nil;
if(m != nil){
dir = estrstrdup(mbox.name, m->name);
if(to == nil && attr == nil){
/* Reply goes to replyto; Reply all goes to From and To and CC */
if(strstr(label, "all") == nil)
fsprint(r->w->body, "To: %s\n", m->replyto);
else{ /* Replyall */
if(strlen(m->from) > 0)
fsprint(r->w->body, "To: %s\n", m->from);
if(strlen(m->to) > 0)
fsprint(r->w->body, "To: %s\n", m->to);
if(strlen(m->cc) > 0)
fsprint(r->w->body, "CC: %s\n", m->cc);
}
}
if(strlen(m->subject) > 0){
t = "Subject: Re: ";
if(strlen(m->subject) >= 3)
if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
t = "Subject: ";
fsprint(r->w->body, "%s%s\n", t, m->subject);
}
if(!quotereply){
fsprint(r->w->body, "Include: %sraw\n", dir);
free(dir);
}
}
fsprint(r->w->body, "\n");
if(m == nil)
fsprint(r->w->body, "\n");
else if(quotereply){
quote(m, r->w->body, dir, quotetext);
free(dir);
}
winclosebody(r->w);
if(m==nil && (to==nil || to[0]=='\0'))
winselect(r->w, "0", 0);
else
winselect(r->w, "$", 0);
winclean(r->w);
windormant(r->w);
}
void
delreply(Message *m)
{
if(m->next == nil)
replies.tail = m->prev;
else
m->next->prev = m->prev;
if(m->prev == nil)
replies.head = m->next;
else
m->prev->next = m->next;
mesgfreeparts(m);
free(m);
}
/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
void
buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
{
int i, n;
char *s, *a;
s = args;
for(i=0; i<NARGS; i++){
a = inargv[i];
if(a == nil)
break;
n = strlen(a)+1;
if((s-args)+n >= NARGCHAR) /* too many characters */
break;
argv[i] = s;
memmove(s, a, n);
s += n;
free(a);
}
argv[i] = nil;
}
void
execproc(void *v)
{
struct Exec *e;
int p[2], q[2];
char *prog;
char *argv[NARGS+1], args[NARGCHAR];
int fd[3];
e = v;
p[0] = e->p[0];
p[1] = e->p[1];
q[0] = e->q[0];
q[1] = e->q[1];
prog = e->prog; /* known not to be malloc'ed */
fd[0] = dup(p[0], -1);
if(q[0])
fd[1] = dup(q[1], -1);
else
fd[1] = dup(1, -1);
fd[2] = dup(2, -2);
sendul(e->sync, 1);
buildargv(e->argv, argv, args);
free(e->argv);
chanfree(e->sync);
free(e);
threadexec(nil, fd, prog, argv);
close(fd[0]);
close(fd[1]);
close(fd[2]);
fprint(2, "Mail: can't exec %s: %r\n", prog);
threadexits("can't exec");
}
enum{
ATTACH,
BCC,
CC,
FROM,
INCLUDE,
TO
};
char *headers[] = {
"attach:",
"bcc:",
"cc:",
"from:",
"include:",
"to:",
nil
};
int
whichheader(char *h)
{
int i;
for(i=0; headers[i]!=nil; i++)
if(cistrcmp(h, headers[i]) == 0)
return i;
return -1;
}
char *tolist[200];
char *cclist[200];
char *bcclist[200];
int ncc, nbcc, nto;
char *attlist[200];
char included[200];
int
addressed(char *name)
{
int i;
for(i=0; i<nto; i++)
if(strcmp(name, tolist[i]) == 0)
return 1;
for(i=0; i<ncc; i++)
if(strcmp(name, cclist[i]) == 0)
return 1;
for(i=0; i<nbcc; i++)
if(strcmp(name, bcclist[i]) == 0)
return 1;
return 0;
}
char*
skipbl(char *s, char *e)
{
while(s < e){
if(*s!=' ' && *s!='\t' && *s!=',')
break;
s++;
}
return s;
}
char*
findbl(char *s, char *e)
{
while(s < e){
if(*s==' ' || *s=='\t' || *s==',')
break;
s++;
}
return s;
}
/*
* comma-separate possibly blank-separated strings in line; e points before newline
*/
void
commas(char *s, char *e)
{
char *t;
/* may have initial blanks */
s = skipbl(s, e);
while(s < e){
s = findbl(s, e);
if(s == e)
break;
t = skipbl(s, e);
if(t == e) /* no more words */
break;
/* patch comma */
*s++ = ',';
while(s < t)
*s++ = ' ';
}
}
int
print2(int fd, int ofd, char *fmt, ...)
{
int m, n;
char *s;
va_list arg;
va_start(arg, fmt);
s = vsmprint(fmt, arg);
va_end(arg);
if(s == nil)
return -1;
m = strlen(s);
n = write(fd, s, m);
if(ofd > 0)
write(ofd, s, m);
return n;
}
void
write2(int fd, int ofd, char *buf, int n, int nofrom)
{
char *from, *p;
int m;
write(fd, buf, n);
if(ofd <= 0)
return;
if(nofrom == 0){
write(ofd, buf, n);
return;
}
/* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
for(p=buf; *p; p+=m){
from = cistrstr(p, "from");
if(from == nil)
m = n;
else
m = from - p;
if(m > 0)
write(ofd, p, m);
if(from){
if(p==buf || from[-1]=='\n')
write(ofd, " ", 1); /* escape with space if From is at start of line */
write(ofd, from, 4);
m += 4;
}
n -= m;
}
}
void
mesgsend(Message *m)
{
char *s, *body, *to;
int i, j, h, n, natt, p[2];
struct Exec *e;
Channel *sync;
int first, nfld, delit, ofd;
char *copy, *fld[100], *now;
body = winreadbody(m->w, &n);
/* assemble to: list from first line, to: line, and cc: line */
nto = 0;
natt = 0;
ncc = 0;
nbcc = 0;
first = 1;
to = body;
for(;;){
for(s=to; *s!='\n'; s++)
if(*s == '\0'){
free(body);
return;
}
if(s++ == to) /* blank line */
break;
/* make copy of line to tokenize */
copy = emalloc(s-to);
memmove(copy, to, s-to);
copy[s-to-1] = '\0';
nfld = tokenizec(copy, fld, nelem(fld), ", \t");
if(nfld == 0){
free(copy);
break;
}
n -= s-to;
switch(h = whichheader(fld[0])){
case TO:
case FROM:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nto<nelem(tolist); i++)
if(!addressed(fld[i]))
tolist[nto++] = estrdup(fld[i]);
break;
case BCC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
if(!addressed(fld[i]))
bcclist[nbcc++] = estrdup(fld[i]);
break;
case CC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && ncc<nelem(cclist); i++)
if(!addressed(fld[i]))
cclist[ncc++] = estrdup(fld[i]);
break;
case ATTACH:
case INCLUDE:
delit = 1;
for(i=1; i<nfld && natt<nelem(attlist); i++){
attlist[natt] = estrdup(fld[i]);
included[natt++] = (h == INCLUDE);
}
break;
default:
if(first){
delit = 1;
for(i=0; i<nfld && nto<nelem(tolist); i++)
tolist[nto++] = estrdup(fld[i]);
}else /* ignore it */
delit = 0;
break;
}
if(delit){
/* delete line from body */
memmove(to, s, n+1);
}else
to = s;
free(copy);
first = 0;
}
ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
if(ofd > 0){
/* From dhog Fri Aug 24 22:13:00 EDT 2001 */
now = ctime(time(0));
seek(ofd, 0, 2);
fprint(ofd, "From %s %s", user, now);
fprint(ofd, "From: %s\n", user);
fprint(ofd, "Date: %s", now);
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "Include: %s\n", attlist[i]);
else
fprint(ofd, "Attach: %s\n", attlist[i]);
/* needed because mail is by default Latin-1 */
fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
}
e = emalloc(sizeof(struct Exec));
if(pipe(p) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->prog = unsharp("#9/bin/upas/marshal");
e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
e->argv[0] = estrdup("marshal");
e->argv[1] = estrdup("-8");
j = 2;
if(m->replyname){
e->argv[j++] = estrdup("-R");
e->argv[j++] = estrstrdup(mbox.name, m->replyname);
}
for(i=0; i<natt; i++){
if(included[i])
e->argv[j++] = estrdup("-A");
else
e->argv[j++] = estrdup("-a");
e->argv[j++] = estrdup(attlist[i]);
}
sync = chancreate(sizeof(int), 0);
e->sync = sync;
proccreate(execproc, e, EXECSTACK);
recvul(sync);
/* close(p[0]); */
/* using marshal -8, so generate rfc822 headers */
if(nto > 0){
print2(p[1], ofd, "To: ");
for(i=0; i<nto-1; i++)
print2(p[1], ofd, "%s, ", tolist[i]);
print2(p[1], ofd, "%s\n", tolist[i]);
}
if(ncc > 0){
print2(p[1], ofd, "CC: ");
for(i=0; i<ncc-1; i++)
print2(p[1], ofd, "%s, ", cclist[i]);
print2(p[1], ofd, "%s\n", cclist[i]);
}
if(nbcc > 0){
print2(p[1], ofd, "BCC: ");
for(i=0; i<nbcc-1; i++)
print2(p[1], ofd, "%s, ", bcclist[i]);
print2(p[1], ofd, "%s\n", bcclist[i]);
}
i = strlen(body);
if(i > 0)
write2(p[1], ofd, body, i, 1);
/* guarantee a blank line, to ensure attachments are separated from body */
if(i==0 || body[i-1]!='\n')
write2(p[1], ofd, "\n\n", 2, 0);
else if(i>1 && body[i-2]!='\n')
write2(p[1], ofd, "\n", 1, 0);
/* these look like pseudo-attachments in the "outgoing" box */
if(ofd>0 && natt>0){
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "=====> Include: %s\n", attlist[i]);
else
fprint(ofd, "=====> Attach: %s\n", attlist[i]);
}
if(ofd > 0)
write(ofd, "\n", 1);
for(i=0; i<natt; i++)
free(attlist[i]);
close(ofd);
close(p[1]);
free(body);
if(m->replyname != nil)
mesgmenumark(mbox.w, m->replyname, "\t[replied]");
if(m->name[0] == '/')
s = estrdup(m->name);
else
s = estrstrdup(mbox.name, m->name);
s = egrow(s, "-R", nil);
winname(m->w, s);
free(s);
winclean(m->w);
/* mark message unopened because it's no longer the original message */
m->opened = 0;
}

BIN
mail/reply.o Normal file

Binary file not shown.

107
mail/util.c Normal file
View file

@ -0,0 +1,107 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
setmalloctag(p, getcallerpc(&n));
return p;
}
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
error("can't realloc: %r");
setmalloctag(p, getcallerpc(&n));
return p;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
return s;
}
void
error(char *fmt, ...)
{
Fmt f;
char buf[64];
va_list arg;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "Mail: ");
va_start(arg, fmt);
fmtvprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
void
ctlprint(CFid *fd, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = fsvprint(fd, fmt, arg);
va_end(arg);
if(n <= 0)
error("control file write error: %r");
}

BIN
mail/util.o Normal file

Binary file not shown.

379
mail/win.c Normal file
View file

@ -0,0 +1,379 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <9pclient.h>
#include "dat.h"
Window*
newwindow(void)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = fsopen(acmefs, "new/ctl", ORDWR|OCEXEC);
if(w->ctl == nil || fsread(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = nil; /* will be opened when needed */
w->body = nil;
w->data = nil;
w->cevent = chancreate(sizeof(Event*), 0);
w->ref = 1;
return w;
}
void
winincref(Window *w)
{
qlock(&w->lk);
++w->ref;
qunlock(&w->lk);
}
void
windecref(Window *w)
{
qlock(&w->lk);
if(--w->ref > 0){
qunlock(&w->lk);
return;
}
fsclose(w->event);
chanfree(w->cevent);
free(w);
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
}
static CFid*
winopenfile1(Window *w, char *f, int m)
{
char buf[64];
CFid* fd;
sprint(buf, "%d/%s", w->id, f);
fd = fsopen(acmefs, buf, m|OCEXEC);
if(fd == nil)
error("can't open window file %s: %r", f);
return fd;
}
CFid*
winopenfile(Window *w, char *f)
{
return winopenfile1(w, f, ORDWR);
}
void
wintagwrite(Window *w, char *s, int n)
{
CFid* fid;
fid = winopenfile(w, "tag");
if(fswrite(fid, s, n) != n)
error("tag write: %r");
fsclose(fid);
}
void
winname(Window *w, char *s)
{
int len;
char *ns, *sp;
Rune r = L''; /* visible space */
len = 0;
ns = emalloc(strlen(s)*runelen(r) + 1);
for(sp = s; *sp != '\0'; sp++, len++){
if(isspace(*sp)){
len += runetochar(ns+len, &r)-1;
continue;
}
*(ns+len) = *sp;
}
ctlprint(w->ctl, "name %s\n", ns);
free(ns);
return;
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
CFid* fid;
sprint(buf, "%d/body", w->id);
fid = fsopen(acmefs, buf, mode|OCEXEC);
w->body = fid;
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
fsclose(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(fswrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = fsread(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
windecref(w);
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
{
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
}
void
winwriteevent(Window *w, Event *e)
{
fsprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
}
void
winread(Window *w, uint q0, uint q1, char *data)
{
int m, n, nr;
char buf[256];
if(w->addr == nil)
w->addr = winopenfile(w, "addr");
if(w->data == nil)
w->data = winopenfile(w, "data");
m = q0;
while(m < q1){
n = sprint(buf, "#%d", m);
if(fswrite(w->addr, buf, n) != n)
error("error writing addr: %r");
n = fsread(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
nr = utfnlen(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
memmove(data, buf, n);
data += n;
*data = 0;
m += nr;
}
}
void
windormant(Window *w)
{
if(w->addr != nil){
fsclose(w->addr);
w->addr = nil;
}
if(w->body != nil){
fsclose(w->body);
w->body = nil;
}
if(w->data != nil){
fsclose(w->data);
w->data = nil;
}
}
int
windel(Window *w, int sure)
{
if(sure)
fswrite(w->ctl, "delete\n", 7);
else if(fswrite(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
fsclose(w->ctl);
w->ctl = nil;
return 1;
}
void
winclean(Window *w)
{
ctlprint(w->ctl, "clean\n");
}
int
winsetaddr(Window *w, char *addr, int errok)
{
if(w->addr == nil)
w->addr = winopenfile(w, "addr");
if(fswrite(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = fsread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}
char*
winselection(Window *w)
{
int m, n;
char *buf;
char tmp[256];
CFid* fid;
fid = winopenfile1(w, "rdsel", OREAD);
if(fid == nil)
error("can't open rdsel: %r");
n = 0;
buf = nil;
for(;;){
m = fsread(fid, tmp, sizeof tmp);
if(m <= 0)
break;
buf = erealloc(buf, n+m+1);
memmove(buf+n, tmp, m);
n += m;
buf[n] = '\0';
}
fsclose(fid);
return buf;
}

BIN
mail/win.o Normal file

Binary file not shown.

52
mkfile Normal file
View file

@ -0,0 +1,52 @@
<$PLAN9/src/mkhdr
TARG=acme
DIRS=mail
OFILES=\
acme.$O\
addr.$O\
buff.$O\
cols.$O\
disk.$O\
ecmd.$O\
edit.$O\
elog.$O\
exec.$O\
file.$O\
fsys.$O\
logf.$O\
look.$O\
regx.$O\
rows.$O\
scrl.$O\
text.$O\
time.$O\
util.$O\
wind.$O\
xfid.$O\
HFILES=dat.h\
edit.h\
fns.h\
<$PLAN9/src/mkone
<$PLAN9/src/mkdirs
edit.$O ecmd.$O elog.$O: edit.h
likeplan9:V:
mkdir -p likeplan9
rm -f likeplan9/*
for i in *.c
do
9 sed 's/->(fcall|lk|b|fr|ref|m|u|u1)\./->/g;
s/\.(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\2/g
s/&(([a-zA-Z0-9_]|->|\.)*)->(fcall|lk|b|fr|ref|m|u|u1)([^a-zA-Z0-9_])/\1\4/g
s/range\(([^,()]+), ([^,()]+)\)/(Range){\1, \2}/g
' $i >likeplan9/$i
done
diffplan9:V:
mk likeplan9
9 diff -n plan9 likeplan9 | sed 's;likeplan9/;;'

BIN
nilix@zero. Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

840
regx.c Normal file
View file

@ -0,0 +1,840 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
Rangeset sel;
Rune *lastregexp;
/*
* Machine Information
*/
typedef struct Inst Inst;
struct Inst
{
uint type; /* < OPERATOR ==> literal, otherwise action */
union {
int sid;
int subid;
int class;
Inst *other;
Inst *right;
} u;
union{
Inst *left;
Inst *next;
} u1;
};
#define NPROG 1024
Inst program[NPROG];
Inst *progp;
Inst *startinst; /* First inst. of program; might not be program[0] */
Inst *bstartinst; /* same for backwards machine */
Channel *rechan; /* chan(Inst*) */
typedef struct Ilist Ilist;
struct Ilist
{
Inst *inst; /* Instruction of the thread */
Rangeset se;
uint startp; /* first char of match */
};
#define NLIST 127
Ilist *tl, *nl; /* This list, next list */
Ilist list[2][NLIST+1]; /* +1 for trailing null */
static Rangeset sempty;
/*
* Actions and Tokens
*
* 0x10000xx are operators, value == precedence
* 0x20000xx are tokens, i.e. operands for operators
*/
#define OPERATOR 0x1000000 /* Bit set in all operators */
#define START (OPERATOR+0) /* Start, used for marker on stack */
#define RBRA (OPERATOR+1) /* Right bracket, ) */
#define LBRA (OPERATOR+2) /* Left bracket, ( */
#define OR (OPERATOR+3) /* Alternation, | */
#define CAT (OPERATOR+4) /* Concatentation, implicit operator */
#define STAR (OPERATOR+5) /* Closure, * */
#define PLUS (OPERATOR+6) /* a+ == aa* */
#define QUEST (OPERATOR+7) /* a? == a|nothing, i.e. 0 or 1 a's */
#define ANY 0x2000000 /* Any character but newline, . */
#define NOP (ANY+1) /* No operation, internal use only */
#define BOL (ANY+2) /* Beginning of line, ^ */
#define EOL (ANY+3) /* End of line, $ */
#define CCLASS (ANY+4) /* Character class, [] */
#define NCCLASS (ANY+5) /* Negated character class, [^] */
#define END (ANY+0x77) /* Terminate: match found */
#define ISATOR OPERATOR
#define ISAND ANY
#define QUOTED 0x4000000 /* Bit set for \-ed lex characters */
/*
* Parser Information
*/
typedef struct Node Node;
struct Node
{
Inst *first;
Inst *last;
};
#define NSTACK 20
Node andstack[NSTACK];
Node *andp;
int atorstack[NSTACK];
int *atorp;
int lastwasand; /* Last token was operand */
int cursubid;
int subidstack[NSTACK];
int *subidp;
int backwards;
int nbra;
Rune *exprp; /* pointer to next character in source expression */
#define DCLASS 10 /* allocation increment */
int nclass; /* number active */
int Nclass; /* high water mark */
Rune **class;
int negateclass;
int addinst(Ilist *l, Inst *inst, Rangeset *sep);
void newmatch(Rangeset*);
void bnewmatch(Rangeset*);
void pushand(Inst*, Inst*);
void pushator(int);
Node *popand(int);
int popator(void);
void startlex(Rune*);
int lex(void);
void operator(int);
void operand(int);
void evaluntil(int);
void optimize(Inst*);
void bldcclass(void);
void
rxinit(void)
{
rechan = chancreate(sizeof(Inst*), 0);
chansetname(rechan, "rechan");
lastregexp = runemalloc(1);
}
void
regerror(char *e)
{
lastregexp[0] = 0;
warning(nil, "regexp: %s\n", e);
sendp(rechan, nil);
threadexits(nil);
}
Inst *
newinst(int t)
{
if(progp >= &program[NPROG])
regerror("expression too long");
progp->type = t;
progp->u1.left = nil;
progp->u.right = nil;
return progp++;
}
void
realcompile(void *arg)
{
int token;
Rune *s;
threadsetname("regcomp");
s = arg;
startlex(s);
atorp = atorstack;
andp = andstack;
subidp = subidstack;
cursubid = 0;
lastwasand = FALSE;
/* Start with a low priority operator to prime parser */
pushator(START-1);
while((token=lex()) != END){
if((token&ISATOR) == OPERATOR)
operator(token);
else
operand(token);
}
/* Close with a low priority operator */
evaluntil(START);
/* Force END */
operand(END);
evaluntil(START);
if(nbra)
regerror("unmatched `('");
--andp; /* points to first and only operand */
sendp(rechan, andp->first);
threadexits(nil);
}
/* r is null terminated */
int
rxcompile(Rune *r)
{
int i, nr;
Inst *oprogp;
nr = runestrlen(r)+1;
if(runeeq(lastregexp, runestrlen(lastregexp)+1, r, nr)==TRUE)
return TRUE;
lastregexp[0] = 0;
for(i=0; i<nclass; i++)
free(class[i]);
nclass = 0;
progp = program;
backwards = FALSE;
bstartinst = nil;
threadcreate(realcompile, r, STACK);
startinst = recvp(rechan);
if(startinst == nil)
return FALSE;
optimize(program);
oprogp = progp;
backwards = TRUE;
threadcreate(realcompile, r, STACK);
bstartinst = recvp(rechan);
if(bstartinst == nil)
return FALSE;
optimize(oprogp);
lastregexp = runerealloc(lastregexp, nr);
runemove(lastregexp, r, nr);
return TRUE;
}
void
operand(int t)
{
Inst *i;
if(lastwasand)
operator(CAT); /* catenate is implicit */
i = newinst(t);
if(t == CCLASS){
if(negateclass)
i->type = NCCLASS; /* UGH */
i->u.class = nclass-1; /* UGH */
}
pushand(i, i);
lastwasand = TRUE;
}
void
operator(int t)
{
if(t==RBRA && --nbra<0)
regerror("unmatched `)'");
if(t==LBRA){
cursubid++; /* silently ignored */
nbra++;
if(lastwasand)
operator(CAT);
}else
evaluntil(t);
if(t!=RBRA)
pushator(t);
lastwasand = FALSE;
if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
lastwasand = TRUE; /* these look like operands */
}
void
pushand(Inst *f, Inst *l)
{
if(andp >= &andstack[NSTACK])
error("operand stack overflow");
andp->first = f;
andp->last = l;
andp++;
}
void
pushator(int t)
{
if(atorp >= &atorstack[NSTACK])
error("operator stack overflow");
*atorp++=t;
if(cursubid >= NRange)
*subidp++= -1;
else
*subidp++=cursubid;
}
Node *
popand(int op)
{
char buf[64];
if(andp <= &andstack[0])
if(op){
sprint(buf, "missing operand for %c", op);
regerror(buf);
}else
regerror("malformed regexp");
return --andp;
}
int
popator()
{
if(atorp <= &atorstack[0])
error("operator stack underflow");
--subidp;
return *--atorp;
}
void
evaluntil(int pri)
{
Node *op1, *op2, *t;
Inst *inst1, *inst2;
while(pri==RBRA || atorp[-1]>=pri){
switch(popator()){
case LBRA:
op1 = popand('(');
inst2 = newinst(RBRA);
inst2->u.subid = *subidp;
op1->last->u1.next = inst2;
inst1 = newinst(LBRA);
inst1->u.subid = *subidp;
inst1->u1.next = op1->first;
pushand(inst1, inst2);
return; /* must have been RBRA */
default:
error("unknown regexp operator");
break;
case OR:
op2 = popand('|');
op1 = popand('|');
inst2 = newinst(NOP);
op2->last->u1.next = inst2;
op1->last->u1.next = inst2;
inst1 = newinst(OR);
inst1->u.right = op1->first;
inst1->u1.left = op2->first;
pushand(inst1, inst2);
break;
case CAT:
op2 = popand(0);
op1 = popand(0);
if(backwards && op2->first->type!=END){
t = op1;
op1 = op2;
op2 = t;
}
op1->last->u1.next = op2->first;
pushand(op1->first, op2->last);
break;
case STAR:
op2 = popand('*');
inst1 = newinst(OR);
op2->last->u1.next = inst1;
inst1->u.right = op2->first;
pushand(inst1, inst1);
break;
case PLUS:
op2 = popand('+');
inst1 = newinst(OR);
op2->last->u1.next = inst1;
inst1->u.right = op2->first;
pushand(op2->first, inst1);
break;
case QUEST:
op2 = popand('?');
inst1 = newinst(OR);
inst2 = newinst(NOP);
inst1->u1.left = inst2;
inst1->u.right = op2->first;
op2->last->u1.next = inst2;
pushand(inst1, inst2);
break;
}
}
}
void
optimize(Inst *start)
{
Inst *inst, *target;
for(inst=start; inst->type!=END; inst++){
target = inst->u1.next;
while(target->type == NOP)
target = target->u1.next;
inst->u1.next = target;
}
}
void
startlex(Rune *s)
{
exprp = s;
nbra = 0;
}
int
lex(void){
int c;
c = *exprp++;
switch(c){
case '\\':
if(*exprp)
if((c= *exprp++)=='n')
c='\n';
break;
case 0:
c = END;
--exprp; /* In case we come here again */
break;
case '*':
c = STAR;
break;
case '?':
c = QUEST;
break;
case '+':
c = PLUS;
break;
case '|':
c = OR;
break;
case '.':
c = ANY;
break;
case '(':
c = LBRA;
break;
case ')':
c = RBRA;
break;
case '^':
c = BOL;
break;
case '$':
c = EOL;
break;
case '[':
c = CCLASS;
bldcclass();
break;
}
return c;
}
int
nextrec(void)
{
if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
regerror("malformed `[]'");
if(exprp[0] == '\\'){
exprp++;
if(*exprp=='n'){
exprp++;
return '\n';
}
return *exprp++|QUOTED;
}
return *exprp++;
}
void
bldcclass(void)
{
int c1, c2, n, na;
Rune *classp;
classp = runemalloc(DCLASS);
n = 0;
na = DCLASS;
/* we have already seen the '[' */
if(*exprp == '^'){
classp[n++] = '\n'; /* don't match newline in negate case */
negateclass = TRUE;
exprp++;
}else
negateclass = FALSE;
while((c1 = nextrec()) != ']'){
if(c1 == '-'){
Error:
free(classp);
regerror("malformed `[]'");
}
if(n+4 >= na){ /* 3 runes plus NUL */
na += DCLASS;
classp = runerealloc(classp, na);
}
if(*exprp == '-'){
exprp++; /* eat '-' */
if((c2 = nextrec()) == ']')
goto Error;
classp[n+0] = Runemax;
classp[n+1] = c1;
classp[n+2] = c2;
n += 3;
}else
classp[n++] = c1 & ~QUOTED;
}
classp[n] = 0;
if(nclass == Nclass){
Nclass += DCLASS;
class = realloc(class, Nclass*sizeof(Rune*));
}
class[nclass++] = classp;
}
int
classmatch(int classno, int c, int negate)
{
Rune *p;
p = class[classno];
while(*p){
if(*p == Runemax){
if(p[1]<=c && c<=p[2])
return !negate;
p += 3;
}else if(*p++ == c)
return !negate;
}
return negate;
}
/*
* Note optimization in addinst:
* *l must be pending when addinst called; if *l has been looked
* at already, the optimization is a bug.
*/
int
addinst(Ilist *l, Inst *inst, Rangeset *sep)
{
Ilist *p;
for(p = l; p->inst; p++){
if(p->inst==inst){
if((sep)->r[0].q0 < p->se.r[0].q0)
p->se= *sep; /* this would be bug */
return 0; /* It's already there */
}
}
p->inst = inst;
p->se= *sep;
(p+1)->inst = nil;
return 1;
}
int
rxnull(void)
{
return startinst==nil || bstartinst==nil;
}
/* either t!=nil or r!=nil, and we match the string in the appropriate place */
int
rxexecute(Text *t, Rune *r, uint startp, uint eof, Rangeset *rp)
{
int flag;
Inst *inst;
Ilist *tlp;
uint p;
int nnl, ntl;
int nc, c;
int wrapped;
int startchar;
flag = 0;
p = startp;
startchar = 0;
wrapped = 0;
nnl = 0;
if(startinst->type<OPERATOR)
startchar = startinst->type;
list[0][0].inst = list[1][0].inst = nil;
sel.r[0].q0 = -1;
if(t != nil)
nc = t->file->b.nc;
else
nc = runestrlen(r);
/* Execute machine once for each character */
for(;;p++){
doloop:
if(p>=eof || p>=nc){
switch(wrapped++){
case 0: /* let loop run one more click */
case 2:
break;
case 1: /* expired; wrap to beginning */
if(sel.r[0].q0>=0 || eof!=Infinity)
goto Return;
list[0][0].inst = list[1][0].inst = nil;
p = 0;
goto doloop;
default:
goto Return;
}
c = 0;
}else{
if(((wrapped && p>=startp) || sel.r[0].q0>0) && nnl==0)
break;
if(t != nil)
c = textreadc(t, p);
else
c = r[p];
}
/* fast check for first char */
if(startchar && nnl==0 && c!=startchar)
continue;
tl = list[flag];
nl = list[flag^=1];
nl->inst = nil;
ntl = nnl;
nnl = 0;
if(sel.r[0].q0<0 && (!wrapped || p<startp || startp==eof)){
/* Add first instruction to this list */
sempty.r[0].q0 = p;
if(addinst(tl, startinst, &sempty))
if(++ntl >= NLIST){
Overflow:
warning(nil, "regexp list overflow\n");
sel.r[0].q0 = -1;
goto Return;
}
}
/* Execute machine until this list is empty */
for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
Switchstmt:
switch(inst->type){
default: /* regular character */
if(inst->type==c){
Addinst:
if(addinst(nl, inst->u1.next, &tlp->se))
if(++nnl >= NLIST)
goto Overflow;
}
break;
case LBRA:
if(inst->u.subid>=0)
tlp->se.r[inst->u.subid].q0 = p;
inst = inst->u1.next;
goto Switchstmt;
case RBRA:
if(inst->u.subid>=0)
tlp->se.r[inst->u.subid].q1 = p;
inst = inst->u1.next;
goto Switchstmt;
case ANY:
if(c!='\n')
goto Addinst;
break;
case BOL:
if(p==0 || (t!=nil && textreadc(t, p-1)=='\n') || (r!=nil && r[p-1]=='\n')){
Step:
inst = inst->u1.next;
goto Switchstmt;
}
break;
case EOL:
if(c == '\n')
goto Step;
break;
case CCLASS:
if(c>=0 && classmatch(inst->u.class, c, 0))
goto Addinst;
break;
case NCCLASS:
if(c>=0 && classmatch(inst->u.class, c, 1))
goto Addinst;
break;
case OR:
/* evaluate right choice later */
if(addinst(tlp, inst->u.right, &tlp->se))
if(++ntl >= NLIST)
goto Overflow;
/* efficiency: advance and re-evaluate */
inst = inst->u1.left;
goto Switchstmt;
case END: /* Match! */
tlp->se.r[0].q1 = p;
newmatch(&tlp->se);
break;
}
}
}
Return:
*rp = sel;
return sel.r[0].q0 >= 0;
}
void
newmatch(Rangeset *sp)
{
if(sel.r[0].q0<0 || sp->r[0].q0<sel.r[0].q0 ||
(sp->r[0].q0==sel.r[0].q0 && sp->r[0].q1>sel.r[0].q1))
sel = *sp;
}
int
rxbexecute(Text *t, uint startp, Rangeset *rp)
{
int flag;
Inst *inst;
Ilist *tlp;
int p;
int nnl, ntl;
int c;
int wrapped;
int startchar;
flag = 0;
nnl = 0;
wrapped = 0;
p = startp;
startchar = 0;
if(bstartinst->type<OPERATOR)
startchar = bstartinst->type;
list[0][0].inst = list[1][0].inst = nil;
sel.r[0].q0= -1;
/* Execute machine once for each character, including terminal NUL */
for(;;--p){
doloop:
if(p <= 0){
switch(wrapped++){
case 0: /* let loop run one more click */
case 2:
break;
case 1: /* expired; wrap to end */
if(sel.r[0].q0>=0)
goto Return;
list[0][0].inst = list[1][0].inst = nil;
p = t->file->b.nc;
goto doloop;
case 3:
default:
goto Return;
}
c = 0;
}else{
if(((wrapped && p<=startp) || sel.r[0].q0>0) && nnl==0)
break;
c = textreadc(t, p-1);
}
/* fast check for first char */
if(startchar && nnl==0 && c!=startchar)
continue;
tl = list[flag];
nl = list[flag^=1];
nl->inst = nil;
ntl = nnl;
nnl = 0;
if(sel.r[0].q0<0 && (!wrapped || p>startp)){
/* Add first instruction to this list */
/* the minus is so the optimizations in addinst work */
sempty.r[0].q0 = -p;
if(addinst(tl, bstartinst, &sempty))
if(++ntl >= NLIST){
Overflow:
warning(nil, "regexp list overflow\n");
sel.r[0].q0 = -1;
goto Return;
}
}
/* Execute machine until this list is empty */
for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
Switchstmt:
switch(inst->type){
default: /* regular character */
if(inst->type == c){
Addinst:
if(addinst(nl, inst->u1.next, &tlp->se))
if(++nnl >= NLIST)
goto Overflow;
}
break;
case LBRA:
if(inst->u.subid>=0)
tlp->se.r[inst->u.subid].q0 = p;
inst = inst->u1.next;
goto Switchstmt;
case RBRA:
if(inst->u.subid >= 0)
tlp->se.r[inst->u.subid].q1 = p;
inst = inst->u1.next;
goto Switchstmt;
case ANY:
if(c != '\n')
goto Addinst;
break;
case BOL:
if(c=='\n' || p==0){
Step:
inst = inst->u1.next;
goto Switchstmt;
}
break;
case EOL:
if(p<t->file->b.nc && textreadc(t, p)=='\n')
goto Step;
break;
case CCLASS:
if(c>0 && classmatch(inst->u.class, c, 0))
goto Addinst;
break;
case NCCLASS:
if(c>0 && classmatch(inst->u.class, c, 1))
goto Addinst;
break;
case OR:
/* evaluate right choice later */
if(addinst(tl, inst->u.right, &tlp->se))
if(++ntl >= NLIST)
goto Overflow;
/* efficiency: advance and re-evaluate */
inst = inst->u1.left;
goto Switchstmt;
case END: /* Match! */
tlp->se.r[0].q0 = -tlp->se.r[0].q0; /* minus sign */
tlp->se.r[0].q1 = p;
bnewmatch(&tlp->se);
break;
}
}
}
Return:
*rp = sel;
return sel.r[0].q0 >= 0;
}
void
bnewmatch(Rangeset *sp)
{
int i;
if(sel.r[0].q0<0 || sp->r[0].q0>sel.r[0].q1 || (sp->r[0].q0==sel.r[0].q1 && sp->r[0].q1<sel.r[0].q0))
for(i = 0; i<NRange; i++){ /* note the reversal; q0<=q1 */
sel.r[i].q0 = sp->r[i].q1;
sel.r[i].q1 = sp->r[i].q0;
}
}

809
rows.c Normal file
View file

@ -0,0 +1,809 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <bio.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Rune Lcolhdr[] = {
'N', 'e', 'w', 'c', 'o', 'l', ' ',
'K', 'i', 'l', 'l', ' ',
'P', 'u', 't', 'a', 'l', 'l', ' ',
'D', 'u', 'm', 'p', ' ',
'E', 'x', 'i', 't', ' ',
0
};
void
rowinit(Row *row, Rectangle r)
{
Rectangle r1;
Text *t;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
row->r = r;
row->col = nil;
row->ncol = 0;
r1 = r;
r1.max.y = r1.min.y + font->height;
t = &row->tag;
textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
t->what = Rowtag;
t->row = row;
t->w = nil;
t->col = nil;
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
textinsert(t, 0, Lcolhdr, 29, TRUE);
textsetselect(t, t->file->b.nc, t->file->b.nc);
}
Column*
rowadd(Row *row, Column *c, int x)
{
Rectangle r, r1;
Column *d;
int i;
d = nil;
r = row->r;
r.min.y = row->tag.fr.r.max.y+Border;
if(x<r.min.x && row->ncol>0){ /*steal 40% of last column by default */
d = row->col[row->ncol-1];
x = d->r.min.x + 3*Dx(d->r)/5;
}
/* look for column we'll land on */
for(i=0; i<row->ncol; i++){
d = row->col[i];
if(x < d->r.max.x)
break;
}
if(row->ncol > 0){
if(i < row->ncol)
i++; /* new column will go after d */
r = d->r;
if(Dx(r) < 100)
return nil;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r1 = r;
r1.max.x = min(x-Border, r.max.x-50);
if(Dx(r1) < 50)
r1.max.x = r1.min.x+50;
colresize(d, r1);
r1.min.x = r1.max.x;
r1.max.x = r1.min.x+Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.min.x = r1.max.x;
}
if(c == nil){
c = emalloc(sizeof(Column));
colinit(c, r);
incref(&reffont.ref);
}else
colresize(c, r);
c->row = row;
c->tag.row = row;
row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
row->col[i] = c;
row->ncol++;
clearmouse();
return c;
}
void
rowresize(Row *row, Rectangle r)
{
int i, deltax;
Rectangle or, r1, r2;
Column *c;
or = row->r;
deltax = r.min.x - or.min.x;
row->r = r;
r1 = r;
r1.max.y = r1.min.y + font->height;
textresize(&row->tag, r1, TRUE);
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.min.y = r1.max.y;
r1 = r;
r1.max.x = r1.min.x;
for(i=0; i<row->ncol; i++){
c = row->col[i];
r1.min.x = r1.max.x;
/* the test should not be necessary, but guarantee we don't lose a pixel */
if(i == row->ncol-1)
r1.max.x = r.max.x;
else
r1.max.x = (c->r.max.x-or.min.x)*Dx(r)/Dx(or) + deltax;
if(i > 0){
r2 = r1;
r2.max.x = r2.min.x+Border;
draw(screen, r2, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r1.min.x = r2.max.x;
}
colresize(c, r1);
}
}
void
rowdragcol(Row *row, Column *c, int _0)
{
Rectangle r;
int i, b, x;
Point p, op;
Column *d;
USED(_0);
clearmouse();
setcursor2(mousectl, &boxcursor, &boxcursor2);
b = mouse->buttons;
op = mouse->xy;
while(mouse->buttons == b)
readmouse(mousectl);
setcursor(mousectl, nil);
if(mouse->buttons){
while(mouse->buttons)
readmouse(mousectl);
return;
}
for(i=0; i<row->ncol; i++)
if(row->col[i] == c)
goto Found;
error("can't find column");
Found:
p = mouse->xy;
if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
return;
if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
/* shuffle */
x = c->r.min.x;
rowclose(row, c, FALSE);
if(rowadd(row, c, p.x) == nil) /* whoops! */
if(rowadd(row, c, x) == nil) /* WHOOPS! */
if(rowadd(row, c, -1)==nil){ /* shit! */
rowclose(row, c, TRUE);
return;
}
colmousebut(c);
return;
}
if(i == 0)
return;
d = row->col[i-1];
if(p.x < d->r.min.x+80+Scrollwid)
p.x = d->r.min.x+80+Scrollwid;
if(p.x > c->r.max.x-80-Scrollwid)
p.x = c->r.max.x-80-Scrollwid;
r = d->r;
r.max.x = c->r.max.x;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.max.x = p.x;
colresize(d, r);
r = c->r;
r.min.x = p.x;
r.max.x = r.min.x;
r.max.x += Border;
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
r.min.x = r.max.x;
r.max.x = c->r.max.x;
colresize(c, r);
colmousebut(c);
}
void
rowclose(Row *row, Column *c, int dofree)
{
Rectangle r;
int i;
for(i=0; i<row->ncol; i++)
if(row->col[i] == c)
goto Found;
error("can't find column");
Found:
r = c->r;
if(dofree)
colcloseall(c);
row->ncol--;
memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
row->col = realloc(row->col, row->ncol*sizeof(Column*));
if(row->ncol == 0){
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
return;
}
if(i == row->ncol){ /* extend last column right */
c = row->col[i-1];
r.min.x = c->r.min.x;
r.max.x = row->r.max.x;
}else{ /* extend next window left */
c = row->col[i];
r.max.x = c->r.max.x;
}
draw(screen, r, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
colresize(c, r);
}
Column*
rowwhichcol(Row *row, Point p)
{
int i;
Column *c;
for(i=0; i<row->ncol; i++){
c = row->col[i];
if(ptinrect(p, c->r))
return c;
}
return nil;
}
Text*
rowwhich(Row *row, Point p)
{
Column *c;
if(ptinrect(p, row->tag.all))
return &row->tag;
c = rowwhichcol(row, p);
if(c)
return colwhich(c, p);
return nil;
}
Text*
rowtype(Row *row, Rune r, Point p)
{
Window *w;
Text *t;
if(r == 0)
r = Runeerror;
clearmouse();
qlock(&row->lk);
if(bartflag)
t = barttext;
else
t = rowwhich(row, p);
if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
w = t->w;
if(w == nil)
texttype(t, r);
else{
winlock(w, 'K');
wintype(w, t, r);
/* Expand tag if necessary */
if(t->what == Tag){
t->w->tagsafe = FALSE;
if(r == '\n')
t->w->tagexpand = TRUE;
winresize(w, w->r, TRUE, TRUE);
}
winunlock(w);
}
}
qunlock(&row->lk);
return t;
}
int
rowclean(Row *row)
{
int clean;
int i;
clean = TRUE;
for(i=0; i<row->ncol; i++)
clean &= colclean(row->col[i]);
return clean;
}
void
rowdump(Row *row, char *file)
{
int i, j, fd, m, n, dumped;
uint q0, q1;
Biobuf *b;
char *buf, *a, *fontname;
Rune *r;
Column *c;
Window *w, *w1;
Text *t;
if(row->ncol == 0)
return;
buf = fbufalloc();
if(file == nil){
if(home == nil){
warning(nil, "can't find file for dump: $home not defined\n");
goto Rescue;
}
sprint(buf, "%s/acme.dump", home);
file = buf;
}
fd = create(file, OWRITE, 0600);
if(fd < 0){
warning(nil, "can't open %s: %r\n", file);
goto Rescue;
}
b = emalloc(sizeof(Biobuf));
Binit(b, fd, OWRITE);
r = fbufalloc();
Bprint(b, "%s\n", wdir);
Bprint(b, "%s\n", fontnames[0]);
Bprint(b, "%s\n", fontnames[1]);
for(i=0; i<row->ncol; i++){
c = row->col[i];
Bprint(b, "%11.7f", 100.0*(c->r.min.x-row->r.min.x)/Dx(row->r));
if(i == row->ncol-1)
Bputc(b, '\n');
else
Bputc(b, ' ');
}
for(i=0; i<row->ncol; i++){
c = row->col[i];
for(j=0; j<c->nw; j++)
c->w[j]->body.file->dumpid = 0;
}
m = min(RBUFSIZE, row->tag.file->b.nc);
bufread(&row->tag.file->b, 0, r, m);
n = 0;
while(n<m && r[n]!='\n')
n++;
Bprint(b, "w %.*S\n", n, r);
for(i=0; i<row->ncol; i++){
c = row->col[i];
m = min(RBUFSIZE, c->tag.file->b.nc);
bufread(&c->tag.file->b, 0, r, m);
n = 0;
while(n<m && r[n]!='\n')
n++;
Bprint(b, "c%11d %.*S\n", i, n, r);
}
for(i=0; i<row->ncol; i++){
c = row->col[i];
for(j=0; j<c->nw; j++){
w = c->w[j];
wincommit(w, &w->tag);
t = &w->body;
/* windows owned by others get special treatment */
if(w->nopen[QWevent] > 0)
if(w->dumpstr == nil)
continue;
/* zeroxes of external windows are tossed */
if(t->file->ntext > 1)
for(n=0; n<t->file->ntext; n++){
w1 = t->file->text[n]->w;
if(w == w1)
continue;
if(w1->nopen[QWevent])
goto Continue2;
}
fontname = "";
if(t->reffont->f != font)
fontname = t->reffont->f->name;
if(t->file->nname)
a = runetobyte(t->file->name, t->file->nname);
else
a = emalloc(1);
if(t->file->dumpid){
dumped = FALSE;
Bprint(b, "x%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid,
w->body.q0, w->body.q1,
100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
fontname);
}else if(w->dumpstr){
dumped = FALSE;
Bprint(b, "e%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid,
0, 0,
100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
fontname);
}else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
dumped = FALSE;
t->file->dumpid = w->id;
Bprint(b, "f%11d %11d %11d %11d %11.7f %s\n", i, w->id,
w->body.q0, w->body.q1,
100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
fontname);
}else{
dumped = TRUE;
t->file->dumpid = w->id;
Bprint(b, "F%11d %11d %11d %11d %11.7f %11d %s\n", i, j,
w->body.q0, w->body.q1,
100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
w->body.file->b.nc, fontname);
}
free(a);
winctlprint(w, buf, 0);
Bwrite(b, buf, strlen(buf));
m = min(RBUFSIZE, w->tag.file->b.nc);
bufread(&w->tag.file->b, 0, r, m);
n = 0;
while(n<m && r[n]!='\n')
n++;
Bprint(b, "%.*S\n", n, r);
if(dumped){
q0 = 0;
q1 = t->file->b.nc;
while(q0 < q1){
n = q1 - q0;
if(n > BUFSIZE/UTFmax)
n = BUFSIZE/UTFmax;
bufread(&t->file->b, q0, r, n);
Bprint(b, "%.*S", n, r);
q0 += n;
}
}
if(w->dumpstr){
if(w->dumpdir)
Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
else
Bprint(b, "\n%s\n", w->dumpstr);
}
Continue2:;
}
}
Bterm(b);
close(fd);
free(b);
fbuffree(r);
Rescue:
fbuffree(buf);
}
static
char*
rdline(Biobuf *b, int *linep)
{
char *l;
l = Brdline(b, '\n');
if(l)
(*linep)++;
return l;
}
/*
* Get font names from load file so we don't load fonts we won't use
*/
void
rowloadfonts(char *file)
{
int i;
Biobuf *b;
char *l;
b = Bopen(file, OREAD);
if(b == nil)
return;
/* current directory */
l = Brdline(b, '\n');
if(l == nil)
goto Return;
/* global fonts */
for(i=0; i<2; i++){
l = Brdline(b, '\n');
if(l == nil)
goto Return;
l[Blinelen(b)-1] = 0;
if(*l && strcmp(l, fontnames[i])!=0){
free(fontnames[i]);
fontnames[i] = estrdup(l);
}
}
Return:
Bterm(b);
}
int
rowload(Row *row, char *file, int initing)
{
int i, j, line, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd, done;
double percent;
Biobuf *b, *bout;
char *buf, *l, *t, *fontname;
Rune *r, *fontr;
int rune;
Column *c, *c1, *c2;
uint q0, q1;
Rectangle r1, r2;
Window *w;
buf = fbufalloc();
if(file == nil){
if(home == nil){
warning(nil, "can't find file for load: $home not defined\n");
goto Rescue1;
}
sprint(buf, "%s/acme.dump", home);
file = buf;
}
b = Bopen(file, OREAD);
if(b == nil){
warning(nil, "can't open load file %s: %r\n", file);
goto Rescue1;
}
/* current directory */
line = 0;
l = rdline(b, &line);
if(l == nil)
goto Rescue2;
l[Blinelen(b)-1] = 0;
if(chdir(l) < 0){
warning(nil, "can't chdir %s\n", l);
goto Rescue2;
}
/* global fonts */
for(i=0; i<2; i++){
l = rdline(b, &line);
if(l == nil)
goto Rescue2;
l[Blinelen(b)-1] = 0;
if(*l && strcmp(l, fontnames[i])!=0)
rfget(i, TRUE, i==0 && initing, l);
}
if(initing && row->ncol==0)
rowinit(row, screen->clipr);
l = rdline(b, &line);
if(l == nil)
goto Rescue2;
j = Blinelen(b)/12;
if(j<=0 || j>10)
goto Rescue2;
for(i=0; i<j; i++){
percent = atof(l+i*12);
if(percent<0 || percent>=100)
goto Rescue2;
x = row->r.min.x+percent*Dx(row->r)/100+0.5;
if(i < row->ncol){
if(i == 0)
continue;
c1 = row->col[i-1];
c2 = row->col[i];
r1 = c1->r;
r2 = c2->r;
if(x<Border)
x = Border;
r1.max.x = x-Border;
r2.min.x = x;
if(Dx(r1) < 50 || Dx(r2) < 50)
continue;
draw(screen, Rpt(r1.min, r2.max), allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
colresize(c1, r1);
colresize(c2, r2);
r2.min.x = x-Border;
r2.max.x = x;
draw(screen, r2, allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x222222FF), nil, ZP);
}
if(i >= row->ncol)
rowadd(row, nil, x);
}
done = 0;
while(!done){
l = rdline(b, &line);
if(l == nil)
break;
switch(l[0]){
case 'c':
l[Blinelen(b)-1] = 0;
i = atoi(l+1+0*12);
r = bytetorune(l+1*12, &nr);
ns = -1;
for(n=0; n<nr; n++){
if(r[n] == '/')
ns = n;
if(r[n] == ' ')
break;
}
textdelete(&row->col[i]->tag, 0, row->col[i]->tag.file->b.nc, TRUE);
textinsert(&row->col[i]->tag, 0, r+n+1, nr-(n+1), TRUE);
free(r);
break;
case 'w':
l[Blinelen(b)-1] = 0;
r = bytetorune(l+2, &nr);
ns = -1;
for(n=0; n<nr; n++){
if(r[n] == '/')
ns = n;
if(r[n] == ' ')
break;
}
textdelete(&row->tag, 0, row->tag.file->b.nc, TRUE);
textinsert(&row->tag, 0, r, nr, TRUE);
free(r);
break;
default:
done = 1;
break;
}
}
for(;;){
if(l == nil)
break;
dumpid = 0;
switch(l[0]){
case 'e':
if(Blinelen(b) < 1+5*12+1)
goto Rescue2;
l = rdline(b, &line); /* ctl line; ignored */
if(l == nil)
goto Rescue2;
l = rdline(b, &line); /* directory */
if(l == nil)
goto Rescue2;
l[Blinelen(b)-1] = 0;
if(*l == '\0'){
if(home == nil)
r = bytetorune("./", &nr);
else{
t = emalloc(strlen(home)+1+1);
sprint(t, "%s/", home);
r = bytetorune(t, &nr);
free(t);
}
}else
r = bytetorune(l, &nr);
l = rdline(b, &line); /* command */
if(l == nil)
goto Rescue2;
t = emalloc(Blinelen(b)+1);
memmove(t, l, Blinelen(b));
run(nil, t, r, nr, TRUE, nil, nil, FALSE);
/* r is freed in run() */
goto Nextline;
case 'f':
if(Blinelen(b) < 1+5*12+1)
goto Rescue2;
fontname = l+1+5*12;
ndumped = -1;
break;
case 'F':
if(Blinelen(b) < 1+6*12+1)
goto Rescue2;
fontname = l+1+6*12;
ndumped = atoi(l+1+5*12+1);
break;
case 'x':
if(Blinelen(b) < 1+5*12+1)
goto Rescue2;
fontname = l+1+5*12;
ndumped = -1;
dumpid = atoi(l+1+1*12);
break;
default:
goto Rescue2;
}
l[Blinelen(b)-1] = 0;
fontr = nil;
nfontr = 0;
if(*fontname)
fontr = bytetorune(fontname, &nfontr);
i = atoi(l+1+0*12);
j = atoi(l+1+1*12);
q0 = atoi(l+1+2*12);
q1 = atoi(l+1+3*12);
percent = atof(l+1+4*12);
if(i<0 || i>10)
goto Rescue2;
if(i > row->ncol)
i = row->ncol;
c = row->col[i];
y = c->r.min.y+(percent*Dy(c->r))/100+0.5;
if(y<c->r.min.y || y>=c->r.max.y)
y = -1;
if(dumpid == 0)
w = coladd(c, nil, nil, y);
else
w = coladd(c, nil, lookid(dumpid, TRUE), y);
if(w == nil)
goto Nextline;
w->dumpid = j;
l = rdline(b, &line);
if(l == nil)
goto Rescue2;
l[Blinelen(b)-1] = 0;
r = bytetorune(l+5*12, &nr);
ns = -1;
for(n=0; n<nr; n++){
if(r[n] == '/')
ns = n;
if(r[n] == ' ')
break;
}
if(dumpid == 0)
winsetname(w, r, n);
for(; n<nr; n++)
if(r[n] == '|')
break;
wincleartag(w);
textinsert(&w->tag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE);
if(ndumped >= 0){
/* simplest thing is to put it in a file and load that */
sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser());
fd = create(buf, OWRITE, 0600);
if(fd < 0){
free(r);
warning(nil, "can't create temp file: %r\n");
goto Rescue2;
}
bout = emalloc(sizeof(Biobuf));
Binit(bout, fd, OWRITE);
for(n=0; n<ndumped; n++){
rune = Bgetrune(b);
if(rune == '\n')
line++;
if(rune == Beof){
free(r);
Bterm(bout);
free(bout);
close(fd);
remove(buf);
goto Rescue2;
}
Bputrune(bout, rune);
}
Bterm(bout);
free(bout);
textload(&w->body, 0, buf, 1);
remove(buf);
close(fd);
w->body.file->mod = TRUE;
for(n=0; n<w->body.file->ntext; n++)
w->body.file->text[n]->w->dirty = TRUE;
winsettag(w);
}else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
get(&w->body, nil, nil, FALSE, XXX, nil, 0);
if(fontr){
fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
free(fontr);
}
free(r);
if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1)
q0 = q1 = 0;
textshow(&w->body, q0, q1, 1);
w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
xfidlog(w, "new");
Nextline:
l = rdline(b, &line);
}
Bterm(b);
fbuffree(buf);
return TRUE;
Rescue2:
warning(nil, "bad load file %s:%d\n", file, line);
Bterm(b);
Rescue1:
fbuffree(buf);
return FALSE;
}
void
allwindows(void (*f)(Window*, void*), void *arg)
{
int i, j;
Column *c;
for(i=0; i<row.ncol; i++){
c = row.col[i];
for(j=0; j<c->nw; j++)
(*f)(c->w[j], arg);
}
}

159
scrl.c Normal file
View file

@ -0,0 +1,159 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Image *scrtmp;
static
Rectangle
scrpos(Rectangle r, uint p0, uint p1, uint tot)
{
Rectangle q;
int h;
q = r;
h = q.max.y-q.min.y;
if(tot == 0)
return q;
if(tot > 1024*1024){
tot>>=10;
p0>>=10;
p1>>=10;
}
if(p0 > 0)
q.min.y += h*p0/tot;
if(p1 < tot)
q.max.y -= h*(tot-p1)/tot;
if(q.max.y < q.min.y+2){
if(q.min.y+2 <= r.max.y)
q.max.y = q.min.y+2;
else
q.min.y = q.max.y-2;
}
return q;
}
void
scrlresize(void)
{
freeimage(scrtmp);
scrtmp = allocimage(display, Rect(0, 0, 32, screen->r.max.y), screen->chan, 0, DNofill);
if(scrtmp == nil)
error("scroll alloc");
}
void
textscrdraw(Text *t)
{
Rectangle r, r1, r2;
Image *b;
if(t->w==nil || t!=&t->w->body)
return;
if(scrtmp == nil)
scrlresize();
r = t->scrollr;
b = scrtmp;
r1 = r;
r1.min.x = 0;
r1.max.x = Dx(r);
r2 = scrpos(r1, t->org, t->org+t->fr.nchars, t->file->b.nc);
if(!eqrect(r2, t->lastsr)){
t->lastsr = r2;
draw(b, r1, t->fr.cols[BORD], nil, ZP);
draw(b, r2, t->fr.cols[BACK], nil, ZP);
r2.min.x = r2.max.x-1;
draw(b, r2, t->fr.cols[BORD], nil, ZP);
draw(t->fr.b, r, b, nil, Pt(0, r1.min.y));
/*flushimage(display, 1); // BUG? */
}
}
void
scrsleep(uint dt)
{
Timer *timer;
static Alt alts[3];
timer = timerstart(dt);
alts[0].c = timer->c;
alts[0].v = nil;
alts[0].op = CHANRCV;
alts[1].c = mousectl->c;
alts[1].v = &mousectl->m;
alts[1].op = CHANRCV;
alts[2].op = CHANEND;
for(;;)
switch(alt(alts)){
case 0:
timerstop(timer);
return;
case 1:
timercancel(timer);
return;
}
}
void
textscroll(Text *t, int but)
{
uint p0, oldp0;
Rectangle s;
int x, y, my, h, first;
s = insetrect(t->scrollr, 1);
h = s.max.y-s.min.y;
x = (s.min.x+s.max.x)/2;
oldp0 = ~0;
first = TRUE;
do{
flushimage(display, 1);
my = mouse->xy.y;
if(my < s.min.y)
my = s.min.y;
if(my >= s.max.y)
my = s.max.y;
if(!eqpt(mouse->xy, Pt(x, my))){
moveto(mousectl, Pt(x, my));
readmouse(mousectl); /* absorb event generated by moveto() */
}
if(but == 2){
y = my;
p0 = (vlong)t->file->b.nc*(y-s.min.y)/h;
if(p0 >= t->q1)
p0 = textbacknl(t, p0, 2);
if(oldp0 != p0)
textsetorigin(t, p0, FALSE);
oldp0 = p0;
readmouse(mousectl);
continue;
}
if(but == 1)
p0 = textbacknl(t, t->org, (my-s.min.y)/t->fr.font->height);
else
p0 = t->org+frcharofpt(&t->fr, Pt(s.max.x, my));
if(oldp0 != p0)
textsetorigin(t, p0, TRUE);
oldp0 = p0;
/* debounce */
if(first){
flushimage(display, 1);
sleep(200);
nbrecv(mousectl->c, &mousectl->m);
first = FALSE;
}
scrsleep(80);
}while(mouse->buttons & (1<<(but-1)));
while(mouse->buttons)
readmouse(mousectl);
}

1664
text.c Normal file

File diff suppressed because it is too large Load diff

124
time.c Normal file
View file

@ -0,0 +1,124 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Channel* ctimer; /* chan(Timer*)[100] */
static Timer *timer;
static
uint
msec(void)
{
return nsec()/1000000;
}
void
timerstop(Timer *t)
{
t->next = timer;
timer = t;
}
void
timercancel(Timer *t)
{
t->cancel = TRUE;
}
static
void
timerproc(void *v)
{
int i, nt, na, dt, del;
Timer **t, *x;
uint old, new;
USED(v);
threadsetname("timerproc");
rfork(RFFDG);
t = nil;
na = 0;
nt = 0;
old = msec();
for(;;){
sleep(10); /* longer sleeps here delay recv on ctimer, but 10ms should not be noticeable */
new = msec();
dt = new-old;
old = new;
if(dt < 0) /* timer wrapped; go around, losing a tick */
continue;
for(i=0; i<nt; i++){
x = t[i];
x->dt -= dt;
del = FALSE;
if(x->cancel){
timerstop(x);
del = TRUE;
}else if(x->dt <= 0){
/*
* avoid possible deadlock if client is
* now sending on ctimer
*/
if(nbsendul(x->c, 0) > 0)
del = TRUE;
}
if(del){
memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
--nt;
--i;
}
}
if(nt == 0){
x = recvp(ctimer);
gotit:
if(nt == na){
na += 10;
t = realloc(t, na*sizeof(Timer*));
if(t == nil)
error("timer realloc failed");
}
t[nt++] = x;
old = msec();
}
if(nbrecv(ctimer, &x) > 0)
goto gotit;
}
}
void
timerinit(void)
{
ctimer = chancreate(sizeof(Timer*), 100);
chansetname(ctimer, "ctimer");
proccreate(timerproc, nil, STACK);
}
Timer*
timerstart(int dt)
{
Timer *t;
t = timer;
if(t)
timer = timer->next;
else{
t = emalloc(sizeof(Timer));
t->c = chancreate(sizeof(int), 0);
chansetname(t->c, "tc%p", t->c);
}
t->next = nil;
t->dt = dt;
t->cancel = FALSE;
sendp(ctimer, t);
return t;
}

495
util.c Normal file
View file

@ -0,0 +1,495 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
static Point prevmouse;
static Window *mousew;
Range
range(int q0, int q1)
{
Range r;
r.q0 = q0;
r.q1 = q1;
return r;
}
Runestr
runestr(Rune *r, uint n)
{
Runestr rs;
rs.r = r;
rs.nr = n;
return rs;
}
void
cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
{
uchar *q;
Rune *s;
int j, w;
/*
* Always guaranteed that n bytes may be interpreted
* without worrying about partial runes. This may mean
* reading up to UTFmax-1 more bytes than n; the caller
* knows this. If n is a firm limit, the caller should
* set p[n] = 0.
*/
q = (uchar*)p;
s = r;
for(j=0; j<n; j+=w){
if(*q < Runeself){
w = 1;
*s = *q++;
}else{
w = chartorune(s, (char*)q);
q += w;
}
if(*s)
s++;
else if(nulls)
*nulls = TRUE;
}
*nb = (char*)q-p;
*nr = s-r;
}
void
error(char *s)
{
fprint(2, "acme: %s: %r\n", s);
threadexitsall(nil);
}
Window*
errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
{
Window *w;
Rune *r;
int i, n;
static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
r = runemalloc(ndir+8);
if((n = ndir) != 0){
runemove(r, dir, ndir);
r[n++] = L'/';
}
runemove(r+n, Lpluserrors, 7);
n += 7;
w = lookfile(r, n);
if(w == nil){
if(row.ncol == 0)
if(rowadd(&row, nil, -1) == nil)
error("can't create column to make error window");
w = coladd(row.col[row.ncol-1], nil, nil, -1);
w->filemenu = FALSE;
winsetname(w, r, n);
xfidlog(w, "new");
}
free(r);
for(i=nincl; --i>=0; ){
n = runestrlen(incl[i]);
r = runemalloc(n);
runemove(r, incl[i], n);
winaddincl(w, r, n);
}
w->autoindent = globalautoindent;
return w;
}
/* make new window, if necessary; return with it locked */
Window*
errorwin(Mntdir *md, int owner)
{
Window *w;
for(;;){
if(md == nil)
w = errorwin1(nil, 0, nil, 0);
else
w = errorwin1(md->dir, md->ndir, md->incl, md->nincl);
winlock(w, owner);
if(w->col != nil)
break;
/* window was deleted too fast */
winunlock(w);
}
return w;
}
/*
* Incoming window should be locked.
* It will be unlocked and returned window
* will be locked in its place.
*/
Window*
errorwinforwin(Window *w)
{
int i, n, nincl, owner;
Rune **incl;
Runestr dir;
Text *t;
t = &w->body;
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
incl = nil;
nincl = w->nincl;
if(nincl > 0){
incl = emalloc(nincl*sizeof(Rune*));
for(i=0; i<nincl; i++){
n = runestrlen(w->incl[i]);
incl[i] = runemalloc(n+1);
runemove(incl[i], w->incl[i], n);
}
}
owner = w->owner;
winunlock(w);
for(;;){
w = errorwin1(dir.r, dir.nr, incl, nincl);
winlock(w, owner);
if(w->col != nil)
break;
/* window deleted too fast */
winunlock(w);
}
return w;
}
typedef struct Warning Warning;
struct Warning{
Mntdir *md;
Buffer buf;
Warning *next;
};
static Warning *warnings;
static
void
addwarningtext(Mntdir *md, Rune *r, int nr)
{
Warning *warn;
for(warn = warnings; warn; warn=warn->next){
if(warn->md == md){
bufinsert(&warn->buf, warn->buf.nc, r, nr);
return;
}
}
warn = emalloc(sizeof(Warning));
warn->next = warnings;
warn->md = md;
if(md)
fsysincid(md);
warnings = warn;
bufinsert(&warn->buf, 0, r, nr);
nbsendp(cwarn, 0);
}
/* called while row is locked */
void
flushwarnings(void)
{
Warning *warn, *next;
Window *w;
Text *t;
int owner, nr, q0, n;
Rune *r;
for(warn=warnings; warn; warn=next) {
w = errorwin(warn->md, 'E');
t = &w->body;
owner = w->owner;
if(owner == 0)
w->owner = 'E';
wincommit(w, t);
/*
* Most commands don't generate much output. For instance,
* Edit ,>cat goes through /dev/cons and is already in blocks
* because of the i/o system, but a few can. Edit ,p will
* put the entire result into a single hunk. So it's worth doing
* this in blocks (and putting the text in a buffer in the first
* place), to avoid a big memory footprint.
*/
r = fbufalloc();
q0 = t->file->b.nc;
for(n = 0; n < warn->buf.nc; n += nr){
nr = warn->buf.nc - n;
if(nr > RBUFSIZE)
nr = RBUFSIZE;
bufread(&warn->buf, n, r, nr);
textbsinsert(t, t->file->b.nc, r, nr, TRUE, &nr);
}
textshow(t, q0, t->file->b.nc, 1);
free(r);
winsettag(t->w);
textscrdraw(t);
w->owner = owner;
w->dirty = FALSE;
winunlock(w);
bufclose(&warn->buf);
next = warn->next;
if(warn->md)
fsysdelid(warn->md);
free(warn);
}
warnings = nil;
}
void
warning(Mntdir *md, char *s, ...)
{
Rune *r;
va_list arg;
va_start(arg, s);
r = runevsmprint(s, arg);
va_end(arg);
if(r == nil)
error("runevsmprint failed");
addwarningtext(md, r, runestrlen(r));
free(r);
}
int
runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
{
if(n1 != n2)
return FALSE;
return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
}
uint
min(uint a, uint b)
{
if(a < b)
return a;
return b;
}
uint
max(uint a, uint b)
{
if(a > b)
return a;
return b;
}
char*
runetobyte(Rune *r, int n)
{
char *s;
if(r == nil)
return nil;
s = emalloc(n*UTFmax+1);
setmalloctag(s, getcallerpc(&r));
snprint(s, n*UTFmax+1, "%.*S", n, r);
return s;
}
Rune*
bytetorune(char *s, int *ip)
{
Rune *r;
int nb, nr;
nb = strlen(s);
r = runemalloc(nb+1);
cvttorunes(s, nb, r, &nb, &nr, nil);
r[nr] = '\0';
*ip = nr;
return r;
}
int
isalnum(Rune c)
{
/*
* Hard to get absolutely right. Use what we know about ASCII
* and assume anything above the Latin control characters is
* potentially an alphanumeric.
*/
if(c <= ' ')
return FALSE;
if(0x7F<=c && c<=0xA0)
return FALSE;
if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
return FALSE;
return TRUE;
}
int
rgetc(void *v, uint n)
{
return ((Rune*)v)[n];
}
int
tgetc(void *a, uint n)
{
Text *t;
t = a;
if(n >= t->file->b.nc)
return 0;
return textreadc(t, n);
}
Rune*
skipbl(Rune *r, int n, int *np)
{
while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
--n;
r++;
}
*np = n;
return r;
}
Rune*
findbl(Rune *r, int n, int *np)
{
while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
--n;
r++;
}
*np = n;
return r;
}
void
savemouse(Window *w)
{
prevmouse = mouse->xy;
mousew = w;
}
int
restoremouse(Window *w)
{
int did;
did = 0;
if(mousew!=nil && mousew==w) {
moveto(mousectl, prevmouse);
did = 1;
}
mousew = nil;
return did;
}
void
clearmouse()
{
mousew = nil;
}
char*
estrdup(char *s)
{
char *t;
t = strdup(s);
if(t == nil)
error("strdup failed");
setmalloctag(t, getcallerpc(&s));
return t;
}
void*
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
error("malloc failed");
setmalloctag(p, getcallerpc(&n));
memset(p, 0, n);
return p;
}
void*
erealloc(void *p, uint n)
{
p = realloc(p, n);
if(p == nil)
error("realloc failed");
setmalloctag(p, getcallerpc(&n));
return p;
}
/*
* Heuristic city.
*/
Window*
makenewwindow(Text *t)
{
Column *c;
Window *w, *bigw, *emptyw;
Text *emptyb;
int i, y, el;
if(activecol)
c = activecol;
else if(seltext && seltext->col)
c = seltext->col;
else if(t && t->col)
c = t->col;
else{
if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
error("can't make column");
c = row.col[row.ncol-1];
}
activecol = c;
if(t==nil || t->w==nil || c->nw==0)
return coladd(c, nil, nil, -1);
/* find biggest window and biggest blank spot */
emptyw = c->w[0];
bigw = emptyw;
for(i=1; i<c->nw; i++){
w = c->w[i];
/* use >= to choose one near bottom of screen */
if(w->body.fr.maxlines >= bigw->body.fr.maxlines)
bigw = w;
if(w->body.fr.maxlines-w->body.fr.nlines >= emptyw->body.fr.maxlines-emptyw->body.fr.nlines)
emptyw = w;
}
emptyb = &emptyw->body;
el = emptyb->fr.maxlines-emptyb->fr.nlines;
/* if empty space is big, use it */
if(el>15 || (el>3 && el>(bigw->body.fr.maxlines-1)/2))
y = emptyb->fr.r.min.y+emptyb->fr.nlines*font->height;
else{
/* if this window is in column and isn't much smaller, split it */
if(t->col==c && Dy(t->w->r)>2*Dy(bigw->r)/3)
bigw = t->w;
y = (bigw->r.min.y + bigw->r.max.y)/2;
}
w = coladd(c, nil, nil, y);
if(w->body.fr.maxlines < 2)
colgrow(w->col, w, 1);
return w;
}

708
wind.c Normal file
View file

@ -0,0 +1,708 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
#include "fns.h"
int winid;
void
wininit(Window *w, Window *clone, Rectangle r)
{
Rectangle r1, br;
File *f;
Reffont *rf;
Rune *rp;
int nc;
w->tag.w = w;
w->taglines = 1;
w->tagexpand = TRUE;
w->body.w = w;
w->id = ++winid;
incref(&w->ref);
if(globalincref)
incref(&w->ref);
w->ctlfid = ~0;
w->utflastqid = -1;
r1 = r;
w->tagtop = r;
w->tagtop.max.y = r.min.y + font->height;
r1.max.y = r1.min.y + w->taglines*font->height;
incref(&reffont.ref);
f = fileaddtext(nil, &w->tag);
textinit(&w->tag, f, r1, &reffont, tagcols);
w->tag.what = Tag;
/* tag is a copy of the contents, not a tracked image */
if(clone){
textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE);
nc = clone->tag.file->b.nc;
rp = runemalloc(nc);
bufread(&clone->tag.file->b, 0, rp, nc);
textinsert(&w->tag, 0, rp, nc, TRUE);
free(rp);
filereset(w->tag.file);
textsetselect(&w->tag, nc, nc);
}
r1 = r;
r1.min.y += w->taglines*font->height + 1;
if(r1.max.y < r1.min.y)
r1.max.y = r1.min.y;
f = nil;
if(clone){
f = clone->body.file;
w->body.org = clone->body.org;
w->isscratch = clone->isscratch;
rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
}else
rf = rfget(FALSE, FALSE, FALSE, nil);
f = fileaddtext(f, &w->body);
w->body.what = Body;
textinit(&w->body, f, r1, rf, textcols);
r1.min.y -= 1;
r1.max.y = r1.min.y+1;
draw(screen, r1, tagcols[BORD], nil, ZP);
textscrdraw(&w->body);
w->r = r;
br.min = w->tag.scrollr.min;
br.max.x = br.min.x + Dx(button->r);
br.max.y = br.min.y + Dy(button->r);
draw(screen, br, button, nil, button->r.min);
w->filemenu = TRUE;
w->maxlines = w->body.fr.maxlines;
w->autoindent = globalautoindent;
if(clone){
w->dirty = clone->dirty;
w->autoindent = clone->autoindent;
textsetselect(&w->body, clone->body.q0, clone->body.q1);
winsettag(w);
}
}
/*
* Draw the appropriate button.
*/
void
windrawbutton(Window *w)
{
Image *b;
Rectangle br;
b = button;
if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
b = modbutton;
br.min = w->tag.scrollr.min;
br.max.x = br.min.x + Dx(b->r);
br.max.y = br.min.y + Dy(b->r);
draw(screen, br, b, nil, b->r.min);
}
int
delrunepos(Window *w)
{
int n;
Rune rune;
for(n=0; n<w->tag.file->b.nc; n++) {
bufread(&w->tag.file->b, n, &rune, 1);
if(rune == ' ')
break;
}
n += 2;
if(n >= w->tag.file->b.nc)
return -1;
return n;
}
void
movetodel(Window *w)
{
int n;
n = delrunepos(w);
if(n < 0)
return;
moveto(mousectl, addpt(frptofchar(&w->tag.fr, n), Pt(4, w->tag.fr.font->height-4)));
}
/*
* Compute number of tag lines required
* to display entire tag text.
*/
int
wintaglines(Window *w, Rectangle r)
{
int n;
Rune rune;
Point p;
if(!w->tagexpand && !w->showdel)
return 1;
w->showdel = FALSE;
w->tag.fr.noredraw = 1;
textresize(&w->tag, r, TRUE);
w->tag.fr.noredraw = 0;
w->tagsafe = FALSE;
if(!w->tagexpand) {
/* use just as many lines as needed to show the Del */
n = delrunepos(w);
if(n < 0)
return 1;
p = subpt(frptofchar(&w->tag.fr, n), w->tag.fr.r.min);
return 1 + p.y / w->tag.fr.font->height;
}
/* can't use more than we have */
if(w->tag.fr.nlines >= w->tag.fr.maxlines)
return w->tag.fr.maxlines;
/* if tag ends with \n, include empty line at end for typing */
n = w->tag.fr.nlines;
if(w->tag.file->b.nc > 0){
bufread(&w->tag.file->b, w->tag.file->b.nc-1, &rune, 1);
if(rune == '\n')
n++;
}
if(n == 0)
n = 1;
return n;
}
int
winresize(Window *w, Rectangle r, int safe, int keepextra)
{
int oy, y, mouseintag, mouseinbody;
Point p;
Rectangle r1;
mouseintag = ptinrect(mouse->xy, w->tag.all);
mouseinbody = ptinrect(mouse->xy, w->body.all);
/* tagtop is first line of tag */
w->tagtop = r;
w->tagtop.max.y = r.min.y+font->height;
r1 = r;
r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
/* If needed, recompute number of lines in tag. */
if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
w->taglines = wintaglines(w, r);
r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
}
/* If needed, resize & redraw tag. */
y = r1.max.y;
if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
textresize(&w->tag, r1, TRUE);
y = w->tag.fr.r.max.y;
windrawbutton(w);
w->tagsafe = TRUE;
/* If mouse is in tag, pull up as tag closes. */
if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){
p = mouse->xy;
p.y = w->tag.all.max.y-3;
moveto(mousectl, p);
}
/* If mouse is in body, push down as tag expands. */
if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){
p = mouse->xy;
p.y = w->tag.all.max.y+3;
moveto(mousectl, p);
}
}
/* If needed, resize & redraw body. */
r1 = r;
r1.min.y = y;
if(!safe || !eqrect(w->body.all, r1)){
oy = y;
if(y+1+w->body.fr.font->height <= r.max.y){ /* room for one line */
r1.min.y = y;
r1.max.y = y+1;
draw(screen, r1, tagcols[BORD], nil, ZP);
y++;
r1.min.y = min(y, r.max.y);
r1.max.y = r.max.y;
}else{
r1.min.y = y;
r1.max.y = y;
}
y = textresize(&w->body, r1, keepextra);
w->r = r;
w->r.max.y = y;
textscrdraw(&w->body);
w->body.all.min.y = oy;
}
w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
return w->r.max.y;
}
void
winlock1(Window *w, int owner)
{
incref(&w->ref);
qlock(&w->lk);
w->owner = owner;
}
void
winlock(Window *w, int owner)
{
int i;
File *f;
f = w->body.file;
for(i=0; i<f->ntext; i++)
winlock1(f->text[i]->w, owner);
}
void
winunlock(Window *w)
{
int i;
File *f;
/*
* subtle: loop runs backwards to avoid tripping over
* winclose indirectly editing f->text and freeing f
* on the last iteration of the loop.
*/
f = w->body.file;
for(i=f->ntext-1; i>=0; i--){
w = f->text[i]->w;
w->owner = 0;
qunlock(&w->lk);
winclose(w);
}
}
void
winmousebut(Window *w)
{
moveto(mousectl, addpt(w->tag.scrollr.min,
divpt(Pt(Dx(w->tag.scrollr), font->height), 2)));
}
void
windirfree(Window *w)
{
int i;
Dirlist *dl;
if(w->isdir){
for(i=0; i<w->ndl; i++){
dl = w->dlp[i];
free(dl->r);
free(dl);
}
free(w->dlp);
}
w->dlp = nil;
w->ndl = 0;
}
void
winclose(Window *w)
{
int i;
if(decref(&w->ref) == 0){
xfidlog(w, "del");
windirfree(w);
textclose(&w->tag);
textclose(&w->body);
if(activewin == w)
activewin = nil;
for(i=0; i<w->nincl; i++)
free(w->incl[i]);
free(w->incl);
free(w->events);
free(w);
}
}
void
windelete(Window *w)
{
Xfid *x;
x = w->eventx;
if(x){
w->nevents = 0;
free(w->events);
w->events = nil;
w->eventx = nil;
sendp(x->c, nil); /* wake him up */
}
}
void
winundo(Window *w, int isundo)
{
Text *body;
int i;
File *f;
Window *v;
w->utflastqid = -1;
body = &w->body;
fileundo(body->file, isundo, &body->q0, &body->q1);
textshow(body, body->q0, body->q1, 1);
f = body->file;
for(i=0; i<f->ntext; i++){
v = f->text[i]->w;
v->dirty = (f->seq != v->putseq);
if(v != w){
v->body.q0 = v->body.fr.p0+v->body.org;
v->body.q1 = v->body.fr.p1+v->body.org;
}
}
winsettag(w);
}
void
winsetname(Window *w, Rune *name, int n)
{
Text *t;
Window *v;
int i;
static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 };
static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
t = &w->body;
if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
return;
w->isscratch = FALSE;
if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6))
w->isscratch = TRUE;
else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7))
w->isscratch = TRUE;
filesetname(t->file, name, n);
for(i=0; i<t->file->ntext; i++){
v = t->file->text[i]->w;
winsettag(v);
v->isscratch = w->isscratch;
}
}
void
wintype(Window *w, Text *t, Rune r)
{
int i;
texttype(t, r);
if(t->what == Body)
for(i=0; i<t->file->ntext; i++)
textscrdraw(t->file->text[i]);
winsettag(w);
}
void
wincleartag(Window *w)
{
int i, n;
Rune *r;
/* w must be committed */
n = w->tag.file->b.nc;
r = runemalloc(n);
bufread(&w->tag.file->b, 0, r, n);
for(i=0; i<n; i++)
if(r[i]==' ' || r[i]=='\t')
break;
for(; i<n; i++)
if(r[i] == '|')
break;
if(i == n)
return;
i++;
textdelete(&w->tag, i, n, TRUE);
free(r);
w->tag.file->mod = FALSE;
if(w->tag.q0 > i)
w->tag.q0 = i;
if(w->tag.q1 > i)
w->tag.q1 = i;
textsetselect(&w->tag, w->tag.q0, w->tag.q1);
}
void
winsettag1(Window *w)
{
int i, j, k, n, bar, dirty, resize;
Rune *new, *old, *r;
uint q0, q1;
static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ',
'S', 'n', 'a', 'r', 'f', 0 };
static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 };
static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 };
static Rune Lget[] = { ' ', 'G', 'e', 't', 0 };
static Rune Lput[] = { ' ', 'P', 'u', 't', 0 };
static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 };
static Rune Lpipe[] = { ' ', '|', 0 };
/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
if(w->tag.ncache!=0 || w->tag.file->mod)
wincommit(w, &w->tag); /* check file name; also guarantees we can modify tag contents */
old = runemalloc(w->tag.file->b.nc+1);
bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
old[w->tag.file->b.nc] = '\0';
for(i=0; i<w->tag.file->b.nc; i++)
if(old[i]==' ' || old[i]=='\t')
break;
if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
textdelete(&w->tag, 0, i, TRUE);
textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
free(old);
old = runemalloc(w->tag.file->b.nc+1);
bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
old[w->tag.file->b.nc] = '\0';
}
/* compute the text for the whole tag, replacing current only if it differs */
new = runemalloc(w->body.file->nname+100);
i = 0;
runemove(new+i, w->body.file->name, w->body.file->nname);
i += w->body.file->nname;
runemove(new+i, Ldelsnarf, 10);
i += 10;
if(w->filemenu){
if(w->body.needundo || w->body.file->delta.nc>0 || w->body.ncache){
runemove(new+i, Lundo, 5);
i += 5;
}
if(w->body.file->epsilon.nc > 0){
runemove(new+i, Lredo, 5);
i += 5;
}
dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
if(!w->isdir && dirty){
runemove(new+i, Lput, 4);
i += 4;
}
}
if(w->isdir){
runemove(new+i, Lget, 4);
i += 4;
}
runemove(new+i, Lpipe, 2);
i += 2;
r = runestrchr(old, '|');
if(r)
k = r-old+1;
else{
k = w->tag.file->b.nc;
if(w->body.file->seq == 0){
runemove(new+i, Llook, 6);
i += 6;
}
}
new[i] = 0;
/* replace tag if the new one is different */
resize = 0;
if(runeeq(new, i, old, k) == FALSE){
resize = 1;
n = k;
if(n > i)
n = i;
for(j=0; j<n; j++)
if(old[j] != new[j])
break;
q0 = w->tag.q0;
q1 = w->tag.q1;
textdelete(&w->tag, j, k, TRUE);
textinsert(&w->tag, j, new+j, i-j, TRUE);
/* try to preserve user selection */
r = runestrchr(old, '|');
if(r){
bar = r-old;
if(q0 > bar){
bar = (runestrchr(new, '|')-new)-bar;
w->tag.q0 = q0+bar;
w->tag.q1 = q1+bar;
}
}
}
free(old);
free(new);
w->tag.file->mod = FALSE;
n = w->tag.file->b.nc+w->tag.ncache;
if(w->tag.q0 > n)
w->tag.q0 = n;
if(w->tag.q1 > n)
w->tag.q1 = n;
textsetselect(&w->tag, w->tag.q0, w->tag.q1);
windrawbutton(w);
if(resize){
w->tagsafe = 0;
winresize(w, w->r, TRUE, TRUE);
}
}
void
winsettag(Window *w)
{
int i;
File *f;
Window *v;
f = w->body.file;
for(i=0; i<f->ntext; i++){
v = f->text[i]->w;
if(v->col->safe || v->body.fr.maxlines>0)
winsettag1(v);
}
}
void
wincommit(Window *w, Text *t)
{
Rune *r;
int i;
File *f;
textcommit(t, TRUE);
f = t->file;
if(f->ntext > 1)
for(i=0; i<f->ntext; i++)
textcommit(f->text[i], FALSE); /* no-op for t */
if(t->what == Body)
return;
r = runemalloc(w->tag.file->b.nc);
bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc);
for(i=0; i<w->tag.file->b.nc; i++)
if(r[i]==' ' || r[i]=='\t')
break;
if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
seq++;
filemark(w->body.file);
w->body.file->mod = TRUE;
w->dirty = TRUE;
winsetname(w, r, i);
winsettag(w);
}
free(r);
}
void
winaddincl(Window *w, Rune *r, int n)
{
char *a;
Dir *d;
Runestr rs;
a = runetobyte(r, n);
d = dirstat(a);
if(d == nil){
if(a[0] == '/')
goto Rescue;
rs = dirname(&w->body, r, n);
r = rs.r;
n = rs.nr;
free(a);
a = runetobyte(r, n);
d = dirstat(a);
if(d == nil)
goto Rescue;
r = runerealloc(r, n+1);
r[n] = 0;
}
free(a);
if((d->qid.type&QTDIR) == 0){
free(d);
warning(nil, "%s: not a directory\n", a);
free(r);
return;
}
free(d);
w->nincl++;
w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
w->incl[0] = runemalloc(n+1);
runemove(w->incl[0], r, n);
free(r);
return;
Rescue:
warning(nil, "%s: %r\n", a);
free(r);
free(a);
return;
}
int
winclean(Window *w, int conservative)
{
if(w->isscratch || w->isdir) /* don't whine if it's a guide file, error window, etc. */
return TRUE;
if(!conservative && w->nopen[QWevent]>0)
return TRUE;
if(w->dirty){
if(w->body.file->nname)
warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
else{
if(w->body.file->b.nc < 100) /* don't whine if it's too small */
return TRUE;
warning(nil, "unnamed file modified\n");
}
w->dirty = FALSE;
return FALSE;
}
return TRUE;
}
char*
winctlprint(Window *w, char *buf, int fonts)
{
sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc,
w->body.file->b.nc, w->isdir, w->dirty);
if(fonts)
return smprint("%s%11d %q %11d ", buf, Dx(w->body.fr.r),
w->body.reffont->f->name, w->body.fr.maxtab);
return buf;
}
void
winevent(Window *w, char *fmt, ...)
{
int n;
char *b;
Xfid *x;
va_list arg;
if(w->nopen[QWevent] == 0)
return;
if(w->owner == 0)
error("no window owner");
va_start(arg, fmt);
b = vsmprint(fmt, arg);
va_end(arg);
if(b == nil)
error("vsmprint failed");
n = strlen(b);
w->events = erealloc(w->events, w->nevents+1+n);
w->events[w->nevents++] = w->owner;
memmove(w->events+w->nevents, b, n);
free(b);
w->nevents += n;
x = w->eventx;
if(x){
w->eventx = nil;
sendp(x->c, nil);
}
}

1125
xfid.c Normal file

File diff suppressed because it is too large Load diff