implement Editcmd;
include "common.m";
sys: Sys;
utils: Utils;
edit: Edit;
editlog: Editlog;
windowm: Windowm;
look: Look;
columnm: Columnm;
bufferm: Bufferm;
exec: Exec;
dat: Dat;
textm: Textm;
regx: Regx;
filem: Filem;
rowm: Rowm;
Dir: import Sys;
Allwin, Filecheck, Tofile, Looper, Astring: import Dat;
aNo, aDot, aAll: import Edit;
C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: import Edit;
TRUE, FALSE: import Dat;
Inactive, Inserting, Collecting: import Dat;
BUFSIZE, Runestr: import Dat;
Addr, Address, String, Cmd: import Edit;
Window: import windowm;
File: import filem;
NRange, Range, Rangeset: import Dat;
Text: import textm;
Column: import columnm;
Buffer: import bufferm;
sprint: import sys;
elogterm, elogclose, eloginsert, elogdelete, elogreplace, elogapply: import editlog;
cmdtab, allocstring, freestring, Straddc, curtext, editing, newaddr, cmdlookup, editerror: import edit;
error, stralloc, strfree, warning, skipbl, findbl: import utils;
lookfile, cleanname, dirname: import look;
undo, run: import exec;
Ref, Lock, row, cedit: import dat;
rxcompile, rxexecute, rxbexecute: import regx;
allwindows: import rowm;
init(mods : ref Dat->Mods)
{
sys = mods.sys;
utils = mods.utils;
edit = mods.edit;
editlog = mods.editlog;
windowm = mods.windowm;
look = mods.look;
columnm = mods.columnm;
bufferm = mods.bufferm;
exec = mods.exec;
dat = mods.dat;
textm = mods.textm;
regx = mods.regx;
filem = mods.filem;
rowm = mods.rowm;
none.r.q0 = none.r.q1 = 0;
none.f = nil;
}
cmdtabexec(i: int, t: ref Text, cp: ref Cmd): int
{
case (cmdtab[i].fnc){
C_nl => i = nl_cmd(t, cp);
C_a => i = a_cmd(t, cp);
C_b => i = b_cmd(t, cp);
C_c => i = c_cmd(t, cp);
C_d => i = d_cmd(t, cp);
C_e => i = e_cmd(t, cp);
C_f => i = f_cmd(t, cp);
C_g => i = g_cmd(t, cp);
C_i => i = i_cmd(t, cp);
C_m => i = m_cmd(t, cp);
C_p => i = p_cmd(t, cp);
C_s => i = s_cmd(t, cp);
C_u => i = u_cmd(t, cp);
C_w => i = w_cmd(t, cp);
C_x => i = x_cmd(t, cp);
C_eq => i = eq_cmd(t, cp);
C_B => i = B_cmd(t, cp);
C_D => i = D_cmd(t, cp);
C_X => i = X_cmd(t, cp);
C_pipe => i = pipe_cmd(t, cp);
* => error("bad case in cmdtabexec");
}
return i;
}
Glooping: int;
nest: int;
Enoname := "no file name given";
addr: Address;
menu: ref File;
sel: Rangeset;
collection: string;
ncollection: int;
clearcollection()
{
collection = nil;
ncollection = 0;
}
resetxec()
{
Glooping = nest = 0;
clearcollection();
}
mkaddr(f: ref File): Address
{
a: Address;
a.r.q0 = f.curtext.q0;
a.r.q1 = f.curtext.q1;
a.f = f;
return a;
}
none: Address;
cmdexec(t: ref Text, cp: ref Cmd): int
{
i: int;
ap: ref Addr;
f: ref File;
w: ref Window;
dot: Address;
if(t == nil)
w = nil;
else
w = t.w;
if(w==nil && (cp.addr==nil || cp.addr.typex!='"') &&
utils->strchr("bBnqUXY!", cp.cmdc) < 0&&
!(cp.cmdc=='D' && cp.text!=nil))
editerror("no current window");
i = cmdlookup(cp.cmdc); # will be -1 for '{'
f = nil;
if(t!=nil && t.w!=nil){
t = t.w.body;
f = t.file;
f.curtext = t;
}
if(i>=0 && cmdtab[i].defaddr != aNo){
if((ap=cp.addr)==nil && cp.cmdc!='\n'){
cp.addr = ap = newaddr();
ap.typex = '.';
if(cmdtab[i].defaddr == aAll)
ap.typex = '*';
}else if(ap!=nil && ap.typex=='"' && ap.next==nil && cp.cmdc!='\n'){
ap.next = newaddr();
ap.next.typex = '.';
if(cmdtab[i].defaddr == aAll)
ap.next.typex = '*';
}
if(cp.addr!=nil){ # may be false for '\n' (only)
if(f!=nil){
dot = mkaddr(f);
addr = cmdaddress(ap, dot, 0);
}else # a "
addr = cmdaddress(ap, none, 0);
f = addr.f;
t = f.curtext;
}
}
case(cp.cmdc){
'{' =>
dot = mkaddr(f);
if(cp.addr != nil)
dot = cmdaddress(cp.addr, dot, 0);
for(cp = cp.cmd; cp!=nil; cp = cp.next){
t.q0 = dot.r.q0;
t.q1 = dot.r.q1;
cmdexec(t, cp);
}
break;
* =>
if(i < 0)
editerror(sprint("unknown command %c in cmdexec", cp.cmdc));
i = cmdtabexec(i, t, cp);
return i;
}
return 1;
}
edittext(f: ref File, q: int, r: string, nr: int): string
{
case(editing){
Inactive =>
return "permission denied";
Inserting =>
eloginsert(f, q, r, nr);
return nil;
Collecting =>
collection += r[0: nr];
ncollection += nr;
return nil;
* =>
return "unknown state in edittext";
}
}
# string is known to be NUL-terminated
filelist(t: ref Text, r: string, nr: int): string
{
if(nr == 0)
return nil;
(r, nr) = skipbl(r, nr);
if(r[0] != '<')
return r;
# use < command to collect text
clearcollection();
runpipe(t, '<', r[1:], nr-1, Collecting);
return collection;
}
a_cmd(t: ref Text, cp: ref Cmd): int
{
return append(t.file, cp, addr.r.q1);
}
b_cmd(nil: ref Text, cp: ref Cmd): int
{
f: ref File;
f = tofile(cp.text);
if(nest == 0)
pfilename(f);
curtext = f.curtext;
return TRUE;
}
B_cmd(t: ref Text, cp: ref Cmd): int
{
listx, r, s: string;
nr: int;
listx = filelist(t, cp.text.r, cp.text.n);
if(listx == nil)
editerror(Enoname);
r = listx;
nr = len r;
(r, nr) = skipbl(r, nr);
if(nr == 0)
look->new(t, t, nil, 0, 0, r, 0);
else while(nr > 0){
(s, nr) = findbl(r, nr);
look->new(t, t, nil, 0, 0, r, len r);
if(nr > 0)
(r, nr) = skipbl(s[1:], nr-1);
}
clearcollection();
return TRUE;
}
c_cmd(t: ref Text, cp: ref Cmd): int
{
elogreplace(t.file, addr.r.q0, addr.r.q1, cp.text.r, cp.text.n);
t.q0 = addr.r.q0;
t.q1 = addr.r.q1;
return TRUE;
}
d_cmd(t: ref Text, nil: ref Cmd): int
{
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.q1;
return TRUE;
}
D1(t: ref Text)
{
if(t.w.body.file.ntext>1 || t.w.clean(FALSE, FALSE))
t.col.close(t.w, TRUE);
}
D_cmd(t: ref Text, cp: ref Cmd): int
{
listx, r, s, n: string;
nr, nn: int;
w: ref Window;
dir, rs: Runestr;
buf: string;
listx = filelist(t, cp.text.r, cp.text.n);
if(listx == nil){
D1(t);
return TRUE;
}
dir = dirname(t, nil, 0);
r = listx;
nr = len r;
(r, nr) = skipbl(r, nr);
do{
(s, nr) = findbl(r, nr);
# first time through, could be empty string, meaning delete file empty name
nn = len r;
if(r[0]=='/' || nn==0 || dir.nr==0){
rs.r = r;
rs.nr = nn;
}else{
n = dir.r + "/" + r;
rs = cleanname(n, dir.nr+1+nn);
}
w = lookfile(rs.r, rs.nr);
if(w == nil){
buf = sprint("no such file %s", rs.r);
rs.r = nil;
editerror(buf);
}
rs.r = nil;
D1(w.body);
if(nr > 0)
(r, nr) = skipbl(s[1:], nr-1);
}while(nr > 0);
clearcollection();
dir.r = nil;
return TRUE;
}
readloader(f: ref File, q0: int, r: string, nr: int): int
{
if(nr > 0)
eloginsert(f, q0, r, nr);
return 0;
}
e_cmd(t: ref Text , cp: ref Cmd): int
{
name: string;
f: ref File;
i, q0, q1, nulls, samename, allreplaced, ok: int;
fd: ref Sys->FD;
s, tmp: string;
d: Dir;
f = t.file;
q0 = addr.r.q0;
q1 = addr.r.q1;
if(cp.cmdc == 'e'){
if(t.w.clean(TRUE, FALSE)==FALSE)
editerror(""); # winclean generated message already
q0 = 0;
q1 = f.buf.nc;
}
allreplaced = (q0==0 && q1==f.buf.nc);
name = cmdname(f, cp.text, cp.cmdc=='e');
if(name == nil)
editerror(Enoname);
i = len name;
samename = name == t.file.name;
s = name;
name = nil;
fd = sys->open(s, Sys->OREAD);
if(fd == nil){
tmp = sprint("can't open %s: %r", s);
s = nil;
editerror(tmp);
}
(ok, d) = sys->fstat(fd);
if(ok >=0 && (d.mode&Sys->DMDIR)){
fd = nil;
tmp = sprint("%s is a directory", s);
s = nil;
editerror(tmp);
}
elogdelete(f, q0, q1);
nulls = 0;
bufferm->loadfile(fd, q1, Dat->READL, nil, f);
s = nil;
fd = nil;
if(nulls)
warning(nil, sprint("%s: NUL bytes elided\n", s));
else if(allreplaced && samename)
f.editclean = TRUE;
return TRUE;
}
f_cmd(t: ref Text, cp: ref Cmd): int
{
name: string;
name = cmdname(t.file, cp.text, TRUE);
name = nil;
pfilename(t.file);
return TRUE;
}
g_cmd(t: ref Text, cp: ref Cmd): int
{
ok: int;
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");
(ok, sel) = rxexecute(t, nil, addr.r.q0, addr.r.q1);
if(ok ^ cp.cmdc=='v'){
t.q0 = addr.r.q0;
t.q1 = addr.r.q1;
return cmdexec(t, cp.cmd);
}
return TRUE;
}
i_cmd(t: ref Text, cp: ref Cmd): int
{
return append(t.file, cp, addr.r.q0);
}
# int
# k_cmd(File *f, Cmd *cp)
# {
# USED(cp);
# f->mark = addr.r;
# return TRUE;
# }
copy(f: ref File, addr2: Address)
{
p: int;
ni: int;
buf: ref Astring;
buf = stralloc(BUFSIZE);
for(p=addr.r.q0; p<addr.r.q1; p+=ni){
ni = addr.r.q1-p;
if(ni > BUFSIZE)
ni = BUFSIZE;
f.buf.read(p, buf, 0, ni);
eloginsert(addr2.f, addr2.r.q1, buf.s, ni);
}
strfree(buf);
}
move(f: ref File, addr2: Address)
{
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
error("move overlaps itself");
}
m_cmd(t: ref Text, cp: ref Cmd): int
{
dot, addr2: Address;
dot = mkaddr(t.file);
addr2 = cmdaddress(cp.mtaddr, dot, 0);
if(cp.cmdc == 'm')
move(t.file, addr2);
else
copy(t.file, addr2);
return TRUE;
}
# int
# n_cmd(File *f, Cmd *cp)
# {
# int i;
# USED(f);
# USED(cp);
# for(i = 0; i<file.nused; i++){
# if(file.filepptr[i] == cmd)
# continue;
# f = file.filepptr[i];
# Strduplstr(&genstr, &f->name);
# filename(f);
# }
# return TRUE;
#}
p_cmd(t: ref Text, nil: ref Cmd): int
{
return pdisplay(t.file);
}
s_cmd(t: ref Text, cp: ref Cmd): int
{
i, j, k, c, m, n, nrp, didsub, ok: int;
p1, op, delta: int;
buf: ref String;
rp: array of Rangeset;
err: string;
rbuf: ref Astring;
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; ){
(ok, sel) = rxexecute(t, nil, p1, addr.r.q1);
if(!ok)
break;
if(sel[0].q0 == sel[0].q1){ # empty match?
if(sel[0].q0 == op){
p1++;
continue;
}
p1 = sel[0].q1+1;
}else
p1 = sel[0].q1;
op = sel[0].q1;
if(--n>0)
continue;
nrp++;
orp := rp;
rp = array[nrp] of Rangeset;
rp[0: ] = orp[0:nrp-1];
rp[nrp-1] = copysel(sel);
orp = nil;
}
rbuf = stralloc(BUFSIZE);
buf = allocstring(0);
for(m=0; m<nrp; m++){
buf.n = 0;
buf.r = nil;
sel = rp[m];
for(i = 0; i<cp.text.n; i++)
if((c = cp.text.r[i])=='\\' && i<cp.text.n-1){
c = cp.text.r[++i];
if('1'<=c && c<='9') {
j = c-'0';
if(sel[j].q1-sel[j].q0>BUFSIZE){
err = "replacement string too long";
rp = nil;
freestring(buf);
strfree(rbuf);
editerror(err);
return FALSE;
}
t.file.buf.read(sel[j].q0, rbuf, 0, sel[j].q1-sel[j].q0);
for(k=0; k<sel[j].q1-sel[j].q0; k++)
Straddc(buf, rbuf.s[k]);
}else
Straddc(buf, c);
}else if(c!='&')
Straddc(buf, c);
else{
if(sel[0].q1-sel[0].q0>BUFSIZE){
err = "right hand side too long in substitution";
rp = nil;
freestring(buf);
strfree(rbuf);
editerror(err);
return FALSE;
}
t.file.buf.read(sel[0].q0, rbuf, 0, sel[0].q1-sel[0].q0);
for(k=0; k<sel[0].q1-sel[0].q0; k++)
Straddc(buf, rbuf.s[k]);
}
elogreplace(t.file, sel[0].q0, sel[0].q1, buf.r, buf.n);
delta -= sel[0].q1-sel[0].q0;
delta += buf.n;
didsub = 1;
if(!cp.flag)
break;
}
rp = nil;
freestring(buf);
strfree(rbuf);
if(!didsub && nest==0)
editerror("no substitution");
t.q0 = addr.r.q0;
t.q1 = addr.r.q1;
return TRUE;
}
u_cmd(t: ref Text, cp: ref Cmd): int
{
n, oseq, flag: int;
n = cp.num;
flag = TRUE;
if(n < 0){
n = -n;
flag = FALSE;
}
oseq = -1;
while(n-->0 && t.file.seq!=0 && t.file.seq!=oseq){
oseq = t.file.seq;
warning(nil, sprint("seq %d\n", t.file.seq));
undo(t, flag);
}
return TRUE;
}
w_cmd(t: ref Text, cp: ref Cmd): int
{
r: string;
f: ref File;
f = t.file;
if(f.seq == dat->seq)
editerror("can't write file with pending modifications");
r = cmdname(f, cp.text, FALSE);
if(r == nil)
editerror("no name specified for 'w' command");
exec->putfile(f, addr.r.q0, addr.r.q1, r);
# r is freed by putfile
return TRUE;
}
x_cmd(t: ref Text, cp: ref Cmd): int
{
if(cp.re!=nil)
looper(t.file, cp, cp.cmdc=='x');
else
linelooper(t.file, cp);
return TRUE;
}
X_cmd(nil: ref Text, cp: ref Cmd): int
{
filelooper(cp, cp.cmdc=='X');
return TRUE;
}
runpipe(t: ref Text, cmd: int, cr: string, ncr: int, state: int)
{
r, s: string;
n: int;
dir: Runestr;
w: ref Window;
(r, n) = skipbl(cr, ncr);
if(n == 0)
editerror("no command specified for >");
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);
}
tmps := "z";
tmps[0] = cmd;
s = tmps + r;
n++;
dir.r = nil;
dir.nr = 0;
if(t != nil)
dir = dirname(t, nil, 0);
if(dir.nr==1 && dir.r[0]=='.'){ # sigh
dir.r = nil;
dir.nr = 0;
}
editing = state;
if(t!=nil && t.w!=nil)
t.w.refx.inc(); # run will decref
spawn run(w, s, dir.r, dir.nr, TRUE, nil, nil, TRUE);
s = nil;
if(t!=nil && t.w!=nil)
t.w.unlock();
row.qlock.unlock();
<- cedit;
row.qlock.lock();
editing = Inactive;
if(t!=nil && t.w!=nil)
t.w.lock('M');
}
pipe_cmd(t: ref Text, cp: ref Cmd): int
{
runpipe(t, cp.cmdc, cp.text.r, cp.text.n, Inserting);
return TRUE;
}
nlcount(t: ref Text, q0: int, q1: int): int
{
nl: int;
buf: ref Astring;
i, nbuf: int;
buf = stralloc(BUFSIZE);
nbuf = 0;
i = nl = 0;
while(q0 < q1){
if(i == nbuf){
nbuf = q1-q0;
if(nbuf > BUFSIZE)
nbuf = BUFSIZE;
t.file.buf.read(q0, buf, 0, nbuf);
i = 0;
}
if(buf.s[i++] == '\n')
nl++;
q0++;
}
strfree(buf);
return nl;
}
printposn(t: ref Text, charsonly: int)
{
l1, l2: int;
if(t != nil && t.file != nil && t.file.name != nil)
warning(nil, 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 && t.readc(addr.r.q1-1)=='\n')
--l2;
warning(nil, sprint("%ud", l1));
if(l2 != l1)
warning(nil, sprint(",%ud", l2));
warning(nil, "\n");
# warning(nil, "; ");
return;
}
warning(nil, sprint("#%d", addr.r.q0));
if(addr.r.q1 != addr.r.q0)
warning(nil, sprint(",#%d", addr.r.q1));
warning(nil, "\n");
}
eq_cmd(t: ref Text, cp: ref Cmd): int
{
charsonly: int;
case(cp.text.n){
0 =>
charsonly = FALSE;
break;
1 =>
if(cp.text.r[0] == '#'){
charsonly = TRUE;
break;
}
* =>
charsonly = TRUE;
editerror("newline expected");
}
printposn(t, charsonly);
return TRUE;
}
nl_cmd(t: ref Text, cp: ref Cmd): int
{
a: Address;
f: ref File;
f = t.file;
if(cp.addr == nil){
# First put it on newline boundaries
a = mkaddr(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){
a = mkaddr(f);
addr = lineaddr(1, a, 1);
}
}
t.show(addr.r.q0, addr.r.q1);
return TRUE;
}
append(f: ref File, cp: ref Cmd, p: int): int
{
if(cp.text.n > 0)
eloginsert(f, p, cp.text.r, cp.text.n);
f.curtext.q0 = p;
f.curtext.q1 = p;
return TRUE;
}
pdisplay(f: ref File): int
{
p1, p2: int;
np: int;
buf: ref Astring;
p1 = addr.r.q0;
p2 = addr.r.q1;
if(p2 > f.buf.nc)
p2 = f.buf.nc;
buf = stralloc(BUFSIZE);
while(p1 < p2){
np = p2-p1;
if(np>BUFSIZE-1)
np = BUFSIZE-1;
f.buf.read(p1, buf, 0, np);
warning(nil, sprint("%s", buf.s[0:np]));
p1 += np;
}
strfree(buf);
f.curtext.q0 = addr.r.q0;
f.curtext.q1 = addr.r.q1;
return TRUE;
}
pfilename(f: ref File)
{
dirty: int;
w: ref Window;
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, sprint("%c%c%c %s\n", " '"[dirty],
'+', " ."[curtext!=nil && curtext.file==f], f.name));
}
loopcmd(f: ref File, cp: ref Cmd, rp: array of Range, nrp: int)
{
i: int;
for(i=0; i<nrp; i++){
f.curtext.q0 = rp[i].q0;
f.curtext.q1 = rp[i].q1;
cmdexec(f.curtext, cp);
}
}
looper(f: ref File, cp: ref Cmd, xy: int)
{
p, op, nrp, ok: int;
r, tr: Range;
rp: array of Range;
r = addr.r;
if(xy)
op = -1;
else
op = r.q0;
nest++;
if(rxcompile(cp.re.r) == FALSE)
editerror(sprint("bad regexp in %c command", cp.cmdc));
nrp = 0;
rp = nil;
for(p = r.q0; p<=r.q1; ){
(ok, sel) = rxexecute(f.curtext, nil, p, r.q1);
if(!ok){ # 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[0].q0==sel[0].q1){ # empty match?
if(sel[0].q0==op){
p++;
continue;
}
p = sel[0].q1+1;
}else
p = sel[0].q1;
if(xy)
tr = sel[0];
else{
tr.q0 = op;
tr.q1 = sel[0].q0;
}
}
op = sel[0].q1;
nrp++;
orp := rp;
rp = array[nrp] of Range;
rp[0: ] = orp[0: nrp-1];
rp[nrp-1] = tr;
orp = nil;
}
loopcmd(f, cp.cmd, rp, nrp);
rp = nil;
--nest;
}
linelooper(f: ref File, cp: ref Cmd)
{
nrp, p: int;
r, linesel: Range;
a, a3: Address;
rp: array of Range;
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++;
orp := rp;
rp = array[nrp] of Range;
rp[0: ] = orp[0: nrp-1];
rp[nrp-1] = linesel;
orp = nil;
continue;
}
break;
}
loopcmd(f, cp.cmd, rp, nrp);
rp = nil;
--nest;
}
loopstruct: ref Looper;
alllooper(w: ref Window, lp: ref Looper)
{
t: ref Text;
cp: ref Cmd;
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.name==nil)
return;
if(cp.re==nil || filematch(t.file, cp.re)==lp.XY){
olpw := lp.w;
lp.w = array[lp.nw+1] of ref Window;
lp.w[0: ] = olpw[0: lp.nw];
lp.w[lp.nw++] = w;
olpw = nil;
}
}
filelooper(cp: ref Cmd, XY: int)
{
i: int;
if(Glooping++)
editerror(sprint("can't nest %c command", "YX"[XY]));
nest++;
if(loopstruct == nil)
loopstruct = ref Looper;
loopstruct.cp = cp;
loopstruct.XY = XY;
if(loopstruct.w != nil) # error'ed out last time
loopstruct.w = nil;
loopstruct.w = nil;
loopstruct.nw = 0;
aw := ref Allwin.LP(loopstruct);
allwindows(Edit->ALLLOOPER, aw);
aw = nil;
for(i=0; i<loopstruct.nw; i++)
cmdexec(loopstruct.w[i].body, cp.cmd);
loopstruct.w = nil;
--Glooping;
--nest;
}
nextmatch(f: ref File, r: ref String, p: int, sign: int)
{
ok: int;
if(rxcompile(r.r) == FALSE)
editerror("bad regexp in command address");
if(sign >= 0){
(ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
if(!ok)
editerror("no match for regexp");
if(sel[0].q0==sel[0].q1 && sel[0].q0==p){
if(++p>f.buf.nc)
p = 0;
(ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
if(!ok)
editerror("address");
}
}else{
(ok, sel) = rxbexecute(f.curtext, p);
if(!ok)
editerror("no match for regexp");
if(sel[0].q0==sel[0].q1 && sel[0].q1==p){
if(--p<0)
p = f.buf.nc;
(ok, sel) = rxbexecute(f.curtext, p);
if(!ok)
editerror("address");
}
}
}
cmdaddress(ap: ref Addr, a: Address, sign: int): Address
{
f := a.f;
a1, a2: Address;
do{
case(ap.typex){
'l' or
'#' =>
if(ap.typex == '#')
a = charaddr(ap.num, a, sign);
else
a = lineaddr(ap.num, a, sign);
break;
'.' =>
a = mkaddr(f);
break;
'$' =>
a.r.q0 = a.r.q1 = f.buf.nc;
break;
'\'' =>
editerror("can't handle '");
# a.r = f.mark;
break;
'?' =>
sign = -sign;
if(sign == 0)
sign = -1;
if(sign >= 0)
v := a.r.q1;
else
v = a.r.q0;
nextmatch(f, ap.re, v, sign);
a.r = sel[0];
break;
'/' =>
if(sign >= 0)
v := a.r.q1;
else
v = a.r.q0;
nextmatch(f, ap.re, v, sign);
a.r = sel[0];
break;
'"' =>
f = matchfile(ap.re);
a = mkaddr(f);
break;
'*' =>
a.r.q0 = 0;
a.r.q1 = f.buf.nc;
return a;
',' or
';' =>
if(ap.left!=nil)
a1 = cmdaddress(ap.left, a, 0);
else{
a1.f = a.f;
a1.r.q0 = a1.r.q1 = 0;
}
if(ap.typex == ';'){
f = a1.f;
a = a1;
f.curtext.q0 = a1.r.q0;
f.curtext.q1 = a1.r.q1;
}
if(ap.next!=nil)
a2 = cmdaddress(ap.next, a, 0);
else{
a2.f = a.f;
a2.r.q0 = a2.r.q1 = f.buf.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;
'+' or
'-' =>
sign = 1;
if(ap.typex == '-')
sign = -1;
if(ap.next==nil || ap.next.typex=='+' || ap.next.typex=='-')
a = lineaddr(1, a, sign);
break;
* =>
error("cmdaddress");
return a;
}
}while((ap = ap.next)!=nil); # assign =
return a;
}
alltofile(w: ref Window, tp: ref Tofile)
{
t: ref Text;
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(tp.r.r == t.file.name)
tp.f = t.file;
}
tofile(r: ref String): ref File
{
t: ref Tofile;
rr: String;
(rr.r, r.n) = skipbl(r.r, r.n);
t = ref Tofile;
t.f = nil;
t.r = ref String;
*t.r = rr;
aw := ref Allwin.FF(t);
allwindows(Edit->ALLTOFILE, aw);
aw = nil;
if(t.f == nil)
editerror(sprint("no such file\"%s\"", rr.r));
return t.f;
}
allmatchfile(w: ref Window, tp: ref Tofile)
{
t: ref Text;
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(sprint("too many files match \"%s\"", tp.r.r));
tp.f = w.body.file;
}
}
matchfile(r: ref String): ref File
{
tf: ref Tofile;
tf = ref Tofile;
tf.f = nil;
tf.r = r;
aw := ref Allwin.FF(tf);
allwindows(Edit->ALLMATCHFILE, aw);
aw = nil;
if(tf.f == nil)
editerror(sprint("no file matches \"%s\"", r.r));
return tf.f;
}
filematch(f: ref File, r: ref String): int
{
buf: string;
w: ref Window;
match, i, dirty: int;
s: Rangeset;
# 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");
w = f.curtext.w;
# same check for dirty as in settag, but we know ncache==0
dirty = !w.isdir && !w.isscratch && f.mod;
buf = sprint("%c%c%c %s\n", " '"[dirty],
'+', " ."[curtext!=nil && curtext.file==f], f.name);
(match, s) = rxexecute(nil, buf, 0, i);
buf = nil;
return match;
}
charaddr(l: int, addr: Address, sign: int): Address
{
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.buf.nc)
editerror("address out of range");
return addr;
}
lineaddr(l: int, addr: Address, sign: int): Address
{
n: int;
c: int;
f := addr.f;
a: Address;
p: int;
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 = f.curtext.readc(p++)=='\n';
}
while(n < l){
if(p >= f.buf.nc)
editerror("address out of range");
if(f.curtext.readc(p++) == '\n')
n++;
}
a.r.q0 = p;
}
while(p < f.buf.nc && f.curtext.readc(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 = f.curtext.readc(p-1);
if(c != '\n' || ++n != l)
p--;
}
}
a.r.q1 = p;
if(p > 0)
p--;
}
while(p > 0 && f.curtext.readc(p-1)!='\n') # lines start after a newline
p--;
a.r.q0 = p;
}
return a;
}
allfilecheck(w: ref Window, fp: ref Filecheck)
{
f: ref File;
f = w.body.file;
if(w.body.file == fp.f)
return;
if(fp.r == f.name)
warning(nil, sprint("warning: duplicate file name \"%s\"\n", fp.r));
}
cmdname(f: ref File, str: ref String , set: int): string
{
r, s: string;
n: int;
fc: ref Filecheck;
newname: Runestr;
r = nil;
n = str.n;
s = str.r;
if(n == 0){
# no name; use existing
if(f.name == nil)
return nil;
return f.name;
}
(s, n) = skipbl(s, n);
if(n == 0)
;
else{
if(s[0] == '/'){
r = s;
}else{
newname = dirname(f.curtext, s, n);
r = newname.r;
n = newname.nr;
}
fc = ref Filecheck;
fc.f = f;
fc.r = r;
fc.nr = n;
aw := ref Allwin.FC(fc);
allwindows(Edit->ALLFILECHECK, aw);
aw = nil;
if(f.name == nil)
set = TRUE;
}
if(set && r[0: n] != f.name){
f.mark();
f.mod = TRUE;
f.curtext.w.dirty = TRUE;
f.curtext.w.setname(r, n);
}
return r;
}
copysel(rs: Rangeset): Rangeset
{
nrs := array[NRange] of Range;
for(i := 0; i < NRange; i++)
nrs[i] = rs[i];
return nrs;
}
|