blob: bdcaf9f5a72d5a08c21b0e3d3e3616e9444da61a [file] [log] [blame]
#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 "dat.h"
#include "edit.h"
#include "fns.h"
int Glooping;
int nest;
char Enoname[] = "no file name given";
Address addr;
File *menu;
Rangeset sel;
extern Text* curtext;
Rune *collection;
int ncollection;
int append(File*, Cmd*, long);
int pdisplay(File*);
void pfilename(File*);
void looper(File*, Cmd*, int);
void filelooper(Cmd*, int);
void linelooper(File*, Cmd*);
Address lineaddr(long, Address, int);
int filematch(File*, String*);
File *tofile(String*);
Rune* cmdname(File *f, String *s, int);
void runpipe(Text*, int, Rune*, int, int);
void
clearcollection(void)
{
free(collection);
collection = nil;
ncollection = 0;
}
void
resetxec(void)
{
Glooping = nest = 0;
clearcollection();
}
void
mkaddr(Address *a, File *f)
{
a->r.q0 = f->curtext->q0;
a->r.q1 = f->curtext->q1;
a->f = f;
}
int
cmdexec(Text *t, Cmd *cp)
{
int i;
Addr *ap;
File *f;
Window *w;
Address dot;
if(t == nil)
w = nil;
else
w = t->w;
if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
!utfrune("bBnqUXY!", cp->cmdc) &&
!(cp->cmdc=='D' && cp->u.text))
editerror("no current window");
i = cmdlookup(cp->cmdc); /* will be -1 for '{' */
f = nil;
if(t && t->w){
t = &t->w->body;
f = t->file;
f->curtext = t;
}
if(i>=0 && cmdtab[i].defaddr != aNo){
if((ap=cp->addr)==0 && cp->cmdc!='\n'){
cp->addr = ap = newaddr();
ap->type = '.';
if(cmdtab[i].defaddr == aAll)
ap->type = '*';
}else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
ap->next = newaddr();
ap->next->type = '.';
if(cmdtab[i].defaddr == aAll)
ap->next->type = '*';
}
if(cp->addr){ /* may be false for '\n' (only) */
static Address none = {0,0,nil};
if(f){
mkaddr(&dot, f);
addr = cmdaddress(ap, dot, 0);
}else /* a " */
addr = cmdaddress(ap, none, 0);
f = addr.f;
t = f->curtext;
}
}
switch(cp->cmdc){
case '{':
mkaddr(&dot, f);
if(cp->addr != nil)
dot = cmdaddress(cp->addr, dot, 0);
for(cp = cp->u.cmd; cp; cp = cp->next){
if(dot.r.q1 > t->file->b.nc)
editerror("dot extends past end of buffer during { command");
t->q0 = dot.r.q0;
t->q1 = dot.r.q1;
cmdexec(t, cp);
}
break;
default:
if(i < 0)
editerror("unknown command %c in cmdexec", cp->cmdc);
i = (*cmdtab[i].fn)(t, cp);
return i;
}
return 1;
}
char*
edittext(Window *w, int q, Rune *r, int nr)
{
File *f;
f = w->body.file;
switch(editing){
case Inactive:
return "permission denied";
case Inserting:
eloginsert(f, q, r, nr);
return nil;
case Collecting:
collection = runerealloc(collection, ncollection+nr+1);
runemove(collection+ncollection, r, nr);
ncollection += nr;
collection[ncollection] = '\0';
return nil;
default:
return "unknown state in edittext";
}
}
/* string is known to be NUL-terminated */
Rune*
filelist(Text *t, Rune *r, int nr)
{
if(nr == 0)
return nil;
r = skipbl(r, nr, &nr);
if(r[0] != '<')
return runestrdup(r);
/* use < command to collect text */
clearcollection();
runpipe(t, '<', r+1, nr-1, Collecting);
return collection;
}
int
a_cmd(Text *t, Cmd *cp)
{
return append(t->file, cp, addr.r.q1);
}
int
b_cmd(Text *t, Cmd *cp)
{
File *f;
USED(t);
f = tofile(cp->u.text);
if(nest == 0)
pfilename(f);
curtext = f->curtext;
return TRUE;
}
int
B_cmd(Text *t, Cmd *cp)
{
Rune *list, *r, *s;
int nr;
list = filelist(t, cp->u.text->r, cp->u.text->n);
if(list == nil)
editerror(Enoname);
r = list;
nr = runestrlen(r);
r = skipbl(r, nr, &nr);
if(nr == 0)
new(t, t, nil, 0, 0, r, 0);
else while(nr > 0){
s = findbl(r, nr, &nr);
*s = '\0';
new(t, t, nil, 0, 0, r, runestrlen(r));
if(nr > 0)
r = skipbl(s+1, nr-1, &nr);
}
clearcollection();
return TRUE;
}
int
c_cmd(Text *t, Cmd *cp)
{
elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
t->q0 = addr.r.q0;
t->q1 = addr.r.q1;
return TRUE;
}
int
d_cmd(Text *t, Cmd *cp)
{
USED(cp);
if(addr.r.q1 > addr.r.q0)
elogdelete(t->file, addr.r.q0, addr.r.q1);
t->q0 = addr.r.q0;
t->q1 = addr.r.q0;
return TRUE;
}
void
D1(Text *t)
{
if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
colclose(t->col, t->w, TRUE);
}
int
D_cmd(Text *t, Cmd *cp)
{
Rune *list, *r, *s, *n;
int nr, nn;
Window *w;
Runestr dir, rs;
char buf[128];
list = filelist(t, cp->u.text->r, cp->u.text->n);
if(list == nil){
D1(t);
return TRUE;
}
dir = dirname(t, nil, 0);
r = list;
nr = runestrlen(r);
r = skipbl(r, nr, &nr);
do{
s = findbl(r, nr, &nr);
*s = '\0';
/* first time through, could be empty string, meaning delete file empty name */
nn = runestrlen(r);
if(r[0]=='/' || nn==0 || dir.nr==0){
rs.r = runestrdup(r);
rs.nr = nn;
}else{
n = runemalloc(dir.nr+1+nn);
runemove(n, dir.r, dir.nr);
n[dir.nr] = '/';
runemove(n+dir.nr+1, r, nn);
rs = cleanrname(runestr(n, dir.nr+1+nn));
}
w = lookfile(rs.r, rs.nr);
if(w == nil){
snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
free(rs.r);
editerror(buf);
}
free(rs.r);
D1(&w->body);
if(nr > 0)
r = skipbl(s+1, nr-1, &nr);
}while(nr > 0);
clearcollection();
free(dir.r);
return TRUE;
}
static int
readloader(void *v, uint q0, Rune *r, int nr)
{
if(nr > 0)
eloginsert(v, q0, r, nr);
return 0;
}
int
e_cmd(Text *t, Cmd *cp)
{
Rune *name;
File *f;
int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
char *s, tmp[128];
Dir *d;
f = t->file;
q0 = addr.r.q0;
q1 = addr.r.q1;
if(cp->cmdc == 'e'){
if(winclean(t->w, TRUE)==FALSE)
editerror(""); /* winclean generated message already */
q0 = 0;
q1 = f->b.nc;
}
allreplaced = (q0==0 && q1==f->b.nc);
name = cmdname(f, cp->u.text, cp->cmdc=='e');
if(name == nil)
editerror(Enoname);
i = runestrlen(name);
samename = runeeq(name, i, t->file->name, t->file->nname);
s = runetobyte(name, i);
free(name);
fd = open(s, OREAD);
if(fd < 0){
snprint(tmp, sizeof tmp, "can't open %s: %r", s);
free(s);
editerror(tmp);
}
d = dirfstat(fd);
isdir = (d!=nil && (d->qid.type&QTDIR));
free(d);
if(isdir){
close(fd);
snprint(tmp, sizeof tmp, "%s is a directory", s);
free(s);
editerror(tmp);
}
elogdelete(f, q0, q1);
nulls = 0;
loadfile(fd, q1, &nulls, readloader, f);
free(s);
close(fd);
if(nulls)
warning(nil, "%s: NUL bytes elided\n", s);
else if(allreplaced && samename)
f->editclean = TRUE;
return TRUE;
}
static Rune Lempty[] = { 0 };
int
f_cmd(Text *t, Cmd *cp)
{
Rune *name;
String *str;
String empty;
if(cp->u.text == nil){
empty.n = 0;
empty.r = Lempty;
str = &empty;
}else
str = cp->u.text;
name = cmdname(t->file, str, TRUE);
free(name);
pfilename(t->file);
return TRUE;
}
int
g_cmd(Text *t, Cmd *cp)
{
if(t->file != addr.f){
warning(nil, "internal error: g_cmd f!=addr.f\n");
return FALSE;
}
if(rxcompile(cp->re->r) == FALSE)
editerror("bad regexp in g command");
if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
t->q0 = addr.r.q0;
t->q1 = addr.r.q1;
return cmdexec(t, cp->u.cmd);
}
return TRUE;
}
int
i_cmd(Text *t, Cmd *cp)
{
return append(t->file, cp, addr.r.q0);
}
void
copy(File *f, Address addr2)
{
long p;
int ni;
Rune *buf;
buf = fbufalloc();
for(p=addr.r.q0; p<addr.r.q1; p+=ni){
ni = addr.r.q1-p;
if(ni > RBUFSIZE)
ni = RBUFSIZE;
bufread(&f->b, p, buf, ni);
eloginsert(addr2.f, addr2.r.q1, buf, ni);
}
fbuffree(buf);
}
void
move(File *f, Address addr2)
{
if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
elogdelete(f, addr.r.q0, addr.r.q1);
copy(f, addr2);
}else if(addr.r.q0 >= addr2.r.q1){
copy(f, addr2);
elogdelete(f, addr.r.q0, addr.r.q1);
}else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
; /* move to self; no-op */
}else
editerror("move overlaps itself");
}
int
m_cmd(Text *t, Cmd *cp)
{
Address dot, addr2;
mkaddr(&dot, t->file);
addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
if(cp->cmdc == 'm')
move(t->file, addr2);
else
copy(t->file, addr2);
return TRUE;
}
int
p_cmd(Text *t, Cmd *cp)
{
USED(cp);
return pdisplay(t->file);
}
int
s_cmd(Text *t, Cmd *cp)
{
int i, j, k, c, m, n, nrp, didsub;
long p1, op, delta;
String *buf;
Rangeset *rp;
char *err;
Rune *rbuf;
n = cp->num;
op= -1;
if(rxcompile(cp->re->r) == FALSE)
editerror("bad regexp in s command");
nrp = 0;
rp = nil;
delta = 0;
didsub = FALSE;
for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
if(sel.r[0].q0 == op){
p1++;
continue;
}
p1 = sel.r[0].q1+1;
}else
p1 = sel.r[0].q1;
op = sel.r[0].q1;
if(--n>0)
continue;
nrp++;
rp = erealloc(rp, nrp*sizeof(Rangeset));
rp[nrp-1] = sel;
}
rbuf = fbufalloc();
buf = allocstring(0);
for(m=0; m<nrp; m++){
buf->n = 0;
buf->r[0] = '\0';
sel = rp[m];
for(i = 0; i<cp->u.text->n; i++)
if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
c = cp->u.text->r[++i];
if('1'<=c && c<='9') {
j = c-'0';
if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
err = "replacement string too long";
goto Err;
}
bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
Straddc(buf, rbuf[k]);
}else
Straddc(buf, c);
}else if(c!='&')
Straddc(buf, c);
else{
if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
err = "right hand side too long in substitution";
goto Err;
}
bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
Straddc(buf, rbuf[k]);
}
elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
delta -= sel.r[0].q1-sel.r[0].q0;
delta += buf->n;
didsub = 1;
if(!cp->flag)
break;
}
free(rp);
freestring(buf);
fbuffree(rbuf);
if(!didsub && nest==0)
editerror("no substitution");
t->q0 = addr.r.q0;
t->q1 = addr.r.q1;
return TRUE;
Err:
free(rp);
freestring(buf);
fbuffree(rbuf);
editerror(err);
return FALSE;
}
int
u_cmd(Text *t, Cmd *cp)
{
int n, oseq, flag;
n = cp->num;
flag = TRUE;
if(n < 0){
n = -n;
flag = FALSE;
}
oseq = -1;
while(n-->0 && t->file->seq!=oseq){
oseq = t->file->seq;
undo(t, nil, nil, flag, 0, nil, 0);
}
return TRUE;
}
int
w_cmd(Text *t, Cmd *cp)
{
Rune *r;
File *f;
f = t->file;
if(f->seq == seq)
editerror("can't write file with pending modifications");
r = cmdname(f, cp->u.text, FALSE);
if(r == nil)
editerror("no name specified for 'w' command");
putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
/* r is freed by putfile */
return TRUE;
}
int
x_cmd(Text *t, Cmd *cp)
{
if(cp->re)
looper(t->file, cp, cp->cmdc=='x');
else
linelooper(t->file, cp);
return TRUE;
}
int
X_cmd(Text *t, Cmd *cp)
{
USED(t);
filelooper(cp, cp->cmdc=='X');
return TRUE;
}
void
runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
{
Rune *r, *s;
int n;
Runestr dir;
Window *w;
QLock *q;
r = skipbl(cr, ncr, &n);
if(n == 0)
editerror("no command specified for %c", cmd);
w = nil;
if(state == Inserting){
w = t->w;
t->q0 = addr.r.q0;
t->q1 = addr.r.q1;
if(cmd == '<' || cmd=='|')
elogdelete(t->file, t->q0, t->q1);
}
s = runemalloc(n+2);
s[0] = cmd;
runemove(s+1, r, n);
n++;
dir.r = nil;
dir.nr = 0;
if(t != nil)
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
free(dir.r);
dir.r = nil;
dir.nr = 0;
}
editing = state;
if(t!=nil && t->w!=nil)
incref(&t->w->ref); /* run will decref */
run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
free(s);
if(t!=nil && t->w!=nil)
winunlock(t->w);
qunlock(&row.lk);
recvul(cedit);
/*
* The editoutlk exists only so that we can tell when
* the editout file has been closed. It can get closed *after*
* the process exits because, since the process cannot be
* connected directly to editout (no 9P kernel support),
* the process is actually connected to a pipe to another
* process (arranged via 9pserve) that reads from the pipe
* and then writes the data in the pipe to editout using
* 9P transactions. This process might still have a couple
* writes left to copy after the original process has exited.
*/
if(w)
q = &w->editoutlk;
else
q = &editoutlk;
qlock(q); /* wait for file to close */
qunlock(q);
qlock(&row.lk);
editing = Inactive;
if(t!=nil && t->w!=nil)
winlock(t->w, 'M');
}
int
pipe_cmd(Text *t, Cmd *cp)
{
runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
return TRUE;
}
long
nlcount(Text *t, long q0, long q1)
{
long nl;
Rune *buf;
int i, nbuf;
buf = fbufalloc();
nbuf = 0;
i = nl = 0;
while(q0 < q1){
if(i == nbuf){
nbuf = q1-q0;
if(nbuf > RBUFSIZE)
nbuf = RBUFSIZE;
bufread(&t->file->b, q0, buf, nbuf);
i = 0;
}
if(buf[i++] == '\n')
nl++;
q0++;
}
fbuffree(buf);
return nl;
}
void
printposn(Text *t, int charsonly)
{
long l1, l2;
if (t != nil && t->file != nil && t->file->name != nil)
warning(nil, "%.*S:", t->file->nname, t->file->name);
if(!charsonly){
l1 = 1+nlcount(t, 0, addr.r.q0);
l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
/* check if addr ends with '\n' */
if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
--l2;
warning(nil, "%lud", l1);
if(l2 != l1)
warning(nil, ",%lud", l2);
warning(nil, "\n");
return;
}
warning(nil, "#%d", addr.r.q0);
if(addr.r.q1 != addr.r.q0)
warning(nil, ",#%d", addr.r.q1);
warning(nil, "\n");
}
int
eq_cmd(Text *t, Cmd *cp)
{
int charsonly;
switch(cp->u.text->n){
case 0:
charsonly = FALSE;
break;
case 1:
if(cp->u.text->r[0] == '#'){
charsonly = TRUE;
break;
}
default:
SET(charsonly);
editerror("newline expected");
}
printposn(t, charsonly);
return TRUE;
}
int
nl_cmd(Text *t, Cmd *cp)
{
Address a;
File *f;
f = t->file;
if(cp->addr == 0){
/* First put it on newline boundaries */
mkaddr(&a, f);
addr = lineaddr(0, a, -1);
a = lineaddr(0, a, 1);
addr.r.q1 = a.r.q1;
if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
mkaddr(&a, f);
addr = lineaddr(1, a, 1);
}
}
textshow(t, addr.r.q0, addr.r.q1, 1);
return TRUE;
}
int
append(File *f, Cmd *cp, long p)
{
if(cp->u.text->n > 0)
eloginsert(f, p, cp->u.text->r, cp->u.text->n);
f->curtext->q0 = p;
f->curtext->q1 = p;
return TRUE;
}
int
pdisplay(File *f)
{
long p1, p2;
int np;
Rune *buf;
p1 = addr.r.q0;
p2 = addr.r.q1;
if(p2 > f->b.nc)
p2 = f->b.nc;
buf = fbufalloc();
while(p1 < p2){
np = p2-p1;
if(np>RBUFSIZE-1)
np = RBUFSIZE-1;
bufread(&f->b, p1, buf, np);
buf[np] = '\0';
warning(nil, "%S", buf);
p1 += np;
}
fbuffree(buf);
f->curtext->q0 = addr.r.q0;
f->curtext->q1 = addr.r.q1;
return TRUE;
}
void
pfilename(File *f)
{
int dirty;
Window *w;
w = f->curtext->w;
/* same check for dirty as in settag, but we know ncache==0 */
dirty = !w->isdir && !w->isscratch && f->mod;
warning(nil, "%c%c%c %.*S\n", " '"[dirty],
'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
}
void
loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
{
long i;
for(i=0; i<nrp; i++){
f->curtext->q0 = rp[i].q0;
f->curtext->q1 = rp[i].q1;
cmdexec(f->curtext, cp);
}
}
void
looper(File *f, Cmd *cp, int xy)
{
long p, op, nrp;
Range r, tr;
Range *rp;
r = addr.r;
op= xy? -1 : r.q0;
nest++;
if(rxcompile(cp->re->r) == FALSE)
editerror("bad regexp in %c command", cp->cmdc);
nrp = 0;
rp = nil;
for(p = r.q0; p<=r.q1; ){
if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
if(xy || op>r.q1)
break;
tr.q0 = op, tr.q1 = r.q1;
p = r.q1+1; /* exit next loop */
}else{
if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
if(sel.r[0].q0==op){
p++;
continue;
}
p = sel.r[0].q1+1;
}else
p = sel.r[0].q1;
if(xy)
tr = sel.r[0];
else
tr.q0 = op, tr.q1 = sel.r[0].q0;
}
op = sel.r[0].q1;
nrp++;
rp = erealloc(rp, nrp*sizeof(Range));
rp[nrp-1] = tr;
}
loopcmd(f, cp->u.cmd, rp, nrp);
free(rp);
--nest;
}
void
linelooper(File *f, Cmd *cp)
{
long nrp, p;
Range r, linesel;
Address a, a3;
Range *rp;
nest++;
nrp = 0;
rp = nil;
r = addr.r;
a3.f = f;
a3.r.q0 = a3.r.q1 = r.q0;
a = lineaddr(0, a3, 1);
linesel = a.r;
for(p = r.q0; p<r.q1; p = a3.r.q1){
a3.r.q0 = a3.r.q1;
if(p!=r.q0 || linesel.q1==p){
a = lineaddr(1, a3, 1);
linesel = a.r;
}
if(linesel.q0 >= r.q1)
break;
if(linesel.q1 >= r.q1)
linesel.q1 = r.q1;
if(linesel.q1 > linesel.q0)
if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
a3.r = linesel;
nrp++;
rp = erealloc(rp, nrp*sizeof(Range));
rp[nrp-1] = linesel;
continue;
}
break;
}
loopcmd(f, cp->u.cmd, rp, nrp);
free(rp);
--nest;
}
struct Looper
{
Cmd *cp;
int XY;
Window **w;
int nw;
} loopstruct; /* only one; X and Y can't nest */
void
alllooper(Window *w, void *v)
{
Text *t;
struct Looper *lp;
Cmd *cp;
lp = v;
cp = lp->cp;
/* if(w->isscratch || w->isdir) */
/* return; */
t = &w->body;
/* only use this window if it's the current window for the file */
if(t->file->curtext != t)
return;
/* if(w->nopen[QWevent] > 0) */
/* return; */
/* no auto-execute on files without names */
if(cp->re==nil && t->file->nname==0)
return;
if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
lp->w[lp->nw++] = w;
}
}
void
alllocker(Window *w, void *v)
{
if(v)
incref(&w->ref);
else
winclose(w);
}
void
filelooper(Cmd *cp, int XY)
{
int i;
if(Glooping++)
editerror("can't nest %c command", "YX"[XY]);
nest++;
loopstruct.cp = cp;
loopstruct.XY = XY;
if(loopstruct.w) /* error'ed out last time */
free(loopstruct.w);
loopstruct.w = nil;
loopstruct.nw = 0;
allwindows(alllooper, &loopstruct);
/*
* add a ref to all windows to keep safe windows accessed by X
* that would not otherwise have a ref to hold them up during
* the shenanigans. note this with globalincref so that any
* newly created windows start with an extra reference.
*/
allwindows(alllocker, (void*)1);
globalincref = 1;
for(i=0; i<loopstruct.nw; i++)
cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
allwindows(alllocker, (void*)0);
globalincref = 0;
free(loopstruct.w);
loopstruct.w = nil;
--Glooping;
--nest;
}
void
nextmatch(File *f, String *r, long p, int sign)
{
if(rxcompile(r->r) == FALSE)
editerror("bad regexp in command address");
if(sign >= 0){
if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
editerror("no match for regexp");
if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
if(++p>f->b.nc)
p = 0;
if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
editerror("address");
}
}else{
if(!rxbexecute(f->curtext, p, &sel))
editerror("no match for regexp");
if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
if(--p<0)
p = f->b.nc;
if(!rxbexecute(f->curtext, p, &sel))
editerror("address");
}
}
}
File *matchfile(String*);
Address charaddr(long, Address, int);
Address lineaddr(long, Address, int);
Address
cmdaddress(Addr *ap, Address a, int sign)
{
File *f = a.f;
Address a1, a2;
do{
switch(ap->type){
case 'l':
case '#':
a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
break;
case '.':
mkaddr(&a, f);
break;
case '$':
a.r.q0 = a.r.q1 = f->b.nc;
break;
case '\'':
editerror("can't handle '");
/* a.r = f->mark; */
break;
case '?':
sign = -sign;
if(sign == 0)
sign = -1;
/* fall through */
case '/':
nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
a.r = sel.r[0];
break;
case '"':
f = matchfile(ap->u.re);
mkaddr(&a, f);
break;
case '*':
a.r.q0 = 0, a.r.q1 = f->b.nc;
return a;
case ',':
case ';':
if(ap->u.left)
a1 = cmdaddress(ap->u.left, a, 0);
else
a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
if(ap->type == ';'){
f = a1.f;
a = a1;
f->curtext->q0 = a1.r.q0;
f->curtext->q1 = a1.r.q1;
}
if(ap->next)
a2 = cmdaddress(ap->next, a, 0);
else
a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
if(a1.f != a2.f)
editerror("addresses in different files");
a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
if(a.r.q1 < a.r.q0)
editerror("addresses out of order");
return a;
case '+':
case '-':
sign = 1;
if(ap->type == '-')
sign = -1;
if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
a = lineaddr(1L, a, sign);
break;
default:
error("cmdaddress");
return a;
}
}while(ap = ap->next); /* assign = */
return a;
}
struct Tofile{
File *f;
String *r;
};
void
alltofile(Window *w, void *v)
{
Text *t;
struct Tofile *tp;
tp = v;
if(tp->f != nil)
return;
if(w->isscratch || w->isdir)
return;
t = &w->body;
/* only use this window if it's the current window for the file */
if(t->file->curtext != t)
return;
/* if(w->nopen[QWevent] > 0) */
/* return; */
if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
tp->f = t->file;
}
File*
tofile(String *r)
{
struct Tofile t;
String rr;
rr.r = skipbl(r->r, r->n, &rr.n);
t.f = nil;
t.r = &rr;
allwindows(alltofile, &t);
if(t.f == nil)
editerror("no such file\"%S\"", rr.r);
return t.f;
}
void
allmatchfile(Window *w, void *v)
{
struct Tofile *tp;
Text *t;
tp = v;
if(w->isscratch || w->isdir)
return;
t = &w->body;
/* only use this window if it's the current window for the file */
if(t->file->curtext != t)
return;
/* if(w->nopen[QWevent] > 0) */
/* return; */
if(filematch(w->body.file, tp->r)){
if(tp->f != nil)
editerror("too many files match \"%S\"", tp->r->r);
tp->f = w->body.file;
}
}
File*
matchfile(String *r)
{
struct Tofile tf;
tf.f = nil;
tf.r = r;
allwindows(allmatchfile, &tf);
if(tf.f == nil)
editerror("no file matches \"%S\"", r->r);
return tf.f;
}
int
filematch(File *f, String *r)
{
char *buf;
Rune *rbuf;
Window *w;
int match, i, dirty;
Rangeset s;
/* compile expr first so if we get an error, we haven't allocated anything */
if(rxcompile(r->r) == FALSE)
editerror("bad regexp in file match");
buf = fbufalloc();
w = f->curtext->w;
/* same check for dirty as in settag, but we know ncache==0 */
dirty = !w->isdir && !w->isscratch && f->mod;
snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
'+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
rbuf = bytetorune(buf, &i);
fbuffree(buf);
match = rxexecute(nil, rbuf, 0, i, &s);
free(rbuf);
return match;
}
Address
charaddr(long l, Address addr, int sign)
{
if(sign == 0)
addr.r.q0 = addr.r.q1 = l;
else if(sign < 0)
addr.r.q1 = addr.r.q0 -= l;
else if(sign > 0)
addr.r.q0 = addr.r.q1 += l;
if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
editerror("address out of range");
return addr;
}
Address
lineaddr(long l, Address addr, int sign)
{
int n;
int c;
File *f = addr.f;
Address a;
long p;
a.f = f;
if(sign >= 0){
if(l == 0){
if(sign==0 || addr.r.q1==0){
a.r.q0 = a.r.q1 = 0;
return a;
}
a.r.q0 = addr.r.q1;
p = addr.r.q1-1;
}else{
if(sign==0 || addr.r.q1==0){
p = 0;
n = 1;
}else{
p = addr.r.q1-1;
n = textreadc(f->curtext, p++)=='\n';
}
while(n < l){
if(p >= f->b.nc)
editerror("address out of range");
if(textreadc(f->curtext, p++) == '\n')
n++;
}
a.r.q0 = p;
}
while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
;
a.r.q1 = p;
}else{
p = addr.r.q0;
if(l == 0)
a.r.q1 = addr.r.q0;
else{
for(n = 0; n<l; ){ /* always runs once */
if(p == 0){
if(++n != l)
editerror("address out of range");
}else{
c = textreadc(f->curtext, p-1);
if(c != '\n' || ++n != l)
p--;
}
}
a.r.q1 = p;
if(p > 0)
p--;
}
while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
p--;
a.r.q0 = p;
}
return a;
}
struct Filecheck
{
File *f;
Rune *r;
int nr;
};
void
allfilecheck(Window *w, void *v)
{
struct Filecheck *fp;
File *f;
fp = v;
f = w->body.file;
if(w->body.file == fp->f)
return;
if(runeeq(fp->r, fp->nr, f->name, f->nname))
warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
}
Rune*
cmdname(File *f, String *str, int set)
{
Rune *r, *s;
int n;
struct Filecheck fc;
Runestr newname;
r = nil;
n = str->n;
s = str->r;
if(n == 0){
/* no name; use existing */
if(f->nname == 0)
return nil;
r = runemalloc(f->nname+1);
runemove(r, f->name, f->nname);
return r;
}
s = skipbl(s, n, &n);
if(n == 0)
goto Return;
if(s[0] == '/'){
r = runemalloc(n+1);
runemove(r, s, n);
}else{
newname = dirname(f->curtext, runestrdup(s), n);
n = newname.nr;
r = runemalloc(n+1); /* NUL terminate */
runemove(r, newname.r, n);
free(newname.r);
}
fc.f = f;
fc.r = r;
fc.nr = n;
allwindows(allfilecheck, &fc);
if(f->nname == 0)
set = TRUE;
Return:
if(set && !runeeq(r, n, f->name, f->nname)){
filemark(f);
f->mod = TRUE;
f->curtext->w->dirty = TRUE;
winsetname(f->curtext->w, r, n);
}
return r;
}