summaryrefslogtreecommitdiff
path: root/appl/acme/ecmd.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/acme/ecmd.b')
-rw-r--r--appl/acme/ecmd.b1349
1 files changed, 1349 insertions, 0 deletions
diff --git a/appl/acme/ecmd.b b/appl/acme/ecmd.b
new file mode 100644
index 00000000..c7cc2408
--- /dev/null
+++ b/appl/acme/ecmd.b
@@ -0,0 +1,1349 @@
+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);
+ 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);
+ 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+delta;
+ 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);
+ 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;
+}
+ \ No newline at end of file