mirror of
https://hacklab.nilfm.cc/acme
synced 2024-10-22 06:21:49 +00:00
Derek Stevens
2908f1ff9b
window tag/body with keyboard focus will render tick in hilighted text color; doesn't work yet for column/row tags
1303 lines
28 KiB
C
1303 lines
28 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <draw.h>
|
|
#include <thread.h>
|
|
#include <cursor.h>
|
|
#include <mouse.h>
|
|
#include <keyboard.h>
|
|
#include "libframe/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(Text*, 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(t, 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(Text* t, Cmd* cp, int XY) {
|
|
int i;
|
|
Text* targ;
|
|
|
|
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;
|
|
|
|
/*
|
|
* Unlock the window running the X command.
|
|
* We'll need to lock and unlock each target window in turn.
|
|
*/
|
|
if (t && t->w)
|
|
winunlock(t->w);
|
|
|
|
for (i = 0; i < loopstruct.nw; i++) {
|
|
targ = &loopstruct.w[i]->body;
|
|
if (targ && targ->w)
|
|
winlock(targ->w, cp->cmdc);
|
|
cmdexec(targ, cp->u.cmd);
|
|
if (targ && targ->w)
|
|
winunlock(targ->w);
|
|
}
|
|
|
|
if (t && t->w)
|
|
winlock(t->w, cp->cmdc);
|
|
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;
|
|
}
|