1378 lines
25 KiB
C
1378 lines
25 KiB
C
#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"
|
|
|
|
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, nil);
|
|
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 = ∅
|
|
}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 *pnr)
|
|
{
|
|
long nl, start;
|
|
Rune *buf;
|
|
int i, nbuf;
|
|
|
|
buf = fbufalloc();
|
|
nbuf = 0;
|
|
i = nl = 0;
|
|
start = q0;
|
|
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') {
|
|
start = q0+1;
|
|
nl++;
|
|
}
|
|
q0++;
|
|
}
|
|
fbuffree(buf);
|
|
if(pnr != nil)
|
|
*pnr = q0 - start;
|
|
return nl;
|
|
}
|
|
|
|
enum {
|
|
PosnLine = 0,
|
|
PosnChars = 1,
|
|
PosnLineChars = 2,
|
|
};
|
|
|
|
void
|
|
printposn(Text *t, int mode)
|
|
{
|
|
long l1, l2, r1, r2;
|
|
|
|
if (t != nil && t->file != nil && t->file->name != nil)
|
|
warning(nil, "%.*S:", t->file->nname, t->file->name);
|
|
|
|
switch(mode) {
|
|
case PosnChars:
|
|
warning(nil, "#%d", addr.r.q0);
|
|
if(addr.r.q1 != addr.r.q0)
|
|
warning(nil, ",#%d", addr.r.q1);
|
|
warning(nil, "\n");
|
|
return;
|
|
|
|
default:
|
|
case PosnLine:
|
|
l1 = 1+nlcount(t, 0, addr.r.q0, nil);
|
|
l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
|
|
/* 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;
|
|
|
|
case PosnLineChars:
|
|
l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
|
|
l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
|
|
if(l2 == l1)
|
|
r2 += r1;
|
|
warning(nil, "%lud+#%d", l1, r1);
|
|
if(l2 != l1)
|
|
warning(nil, ",%lud+#%d", l2, r2);
|
|
warning(nil, "\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int
|
|
eq_cmd(Text *t, Cmd *cp)
|
|
{
|
|
int mode;
|
|
|
|
switch(cp->u.text->n){
|
|
case 0:
|
|
mode = PosnLine;
|
|
break;
|
|
case 1:
|
|
if(cp->u.text->r[0] == '#'){
|
|
mode = PosnChars;
|
|
break;
|
|
}
|
|
if(cp->u.text->r[0] == '+'){
|
|
mode = PosnLineChars;
|
|
break;
|
|
}
|
|
default:
|
|
SET(mode);
|
|
editerror("newline expected");
|
|
}
|
|
printposn(t, mode);
|
|
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;
|
|
}
|