diff options
Diffstat (limited to 'appl/acme/ecmd.b')
| -rw-r--r-- | appl/acme/ecmd.b | 1349 |
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 |
