ref: e59ffed426f628794d4669f152eff9a6239b99db
dir: /appl/acme/ecmd.b/
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 if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){ ; # move to self; no-op }else editerror("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, TRUE); 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, 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, len buf); 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; }