ref: 53aac62a54ebbcc1781c0000d3e2384ed038c7bb
dir: /appl/acme/look.b/
implement Look; include "common.m"; sys : Sys; draw : Draw; utils : Utils; dat : Dat; graph : Graph; acme : Acme; framem : Framem; regx : Regx; bufferm : Bufferm; textm : Textm; windowm : Windowm; columnm : Columnm; exec : Exec; scrl : Scroll; plumbmsg : Plumbmsg; sprint : import sys; Point : import draw; warning, isalnum, stralloc, strfree, strchr, tgetc : import utils; Range, TRUE, FALSE, XXX, BUFSIZE, Astring : import Dat; Expand, seltext, row : import dat; cursorset : import graph; frptofchar : import framem; isaddrc, isregexc, address : import regx; Buffer : import bufferm; Text : import textm; Window : import windowm; Column : import columnm; Msg : import plumbmsg; init(mods : ref Dat->Mods) { sys = mods.sys; draw = mods.draw; utils = mods.utils; graph = mods.graph; acme = mods.acme; framem = mods.framem; regx = mods.regx; dat = mods.dat; bufferm = mods.bufferm; textm = mods.textm; windowm = mods.windowm; columnm = mods.columnm; exec = mods.exec; scrl = mods.scroll; plumbmsg = mods.plumbmsg; } nuntitled : int; look3(t : ref Text, q0 : int, q1 : int, external : int) { n, c, f : int; ct : ref Text; e : Expand; r : ref Astring; expanded : int; ct = seltext; if(ct == nil) seltext = t; (expanded, e) = expand(t, q0, q1); if(!external && t.w!=nil && t.w.nopen[Dat->QWevent]>byte 0){ if(!expanded) return; f = 0; if((e.at!=nil && t.w!=nil) || (e.name!=nil && lookfile(e.name, len e.name)!=nil)) f = 1; # acme can do it without loading a file if(q0!=e.q0 || q1!=e.q1) f |= 2; # second (post-expand) message follows if(e.name != nil) f |= 4; # it's a file name c = 'l'; if(t.what == Textm->Body) c = 'L'; n = q1-q0; if(n <= Dat->EVENTSIZE){ r = stralloc(n); t.file.buf.read(q0, r, 0, n); t.w.event(sprint("%c%d %d %d %d %s\n", c, q0, q1, f, n, r.s[0:n])); strfree(r); r = nil; }else t.w.event(sprint("%c%d %d %d 0 \n", c, q0, q1, f)); if(q0==e.q0 && q1==e.q1) return; if(e.name != nil){ n = len e.name; if(e.a1 > e.a0) n += 1+(e.a1-e.a0); r = stralloc(n); for (i := 0; i < len e.name; i++) r.s[i] = e.name[i]; if(e.a1 > e.a0){ r.s[len e.name] = ':'; e.at.file.buf.read(e.a0, r, len e.name+1, e.a1-e.a0); } }else{ n = e.q1 - e.q0; r = stralloc(n); t.file.buf.read(e.q0, r, 0, n); } f &= ~2; if(n <= Dat->EVENTSIZE) t.w.event(sprint("%c%d %d %d %d %s\n", c, e.q0, e.q1, f, n, r.s[0:n])); else t.w.event(sprint("%c%d %d %d 0 \n", c, e.q0, e.q1, f)); strfree(r); r = nil; return; } if(0 && dat->plumbed){ # don't do yet : 2 acmes running => only 1 receives msg m := ref Msg; m.src = "acme"; m.dst = nil; (dir, nil) := dirname(t, nil, 0); if(dir == ".") # sigh dir = nil; if(dir == nil) dir = acme->wdir; m.dir = dir; m.kind = "text"; m.attr = nil; if(q1 == q0){ if(t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){ q0 = t.q0; q1 = t.q1; }else{ p := q0; while(q0 > 0 && (c = tgetc(t, q0-1)) != ' ' && c != '\t' && c != '\n') q0--; while(q1 < t.file.buf.nc && (c = tgetc(t, q1)) != ' ' && c != '\t' && c != '\n') q1++; if(q1 == q0) return; m.attr = "click=" + string (p-q0); } } r = stralloc(q1-q0); t.file.buf.read(q0, r, 0, q1-q0); m.data = array of byte r.s; strfree(r); if(m.send() >= 0) return; # plumber failed to match : fall through } if(!expanded) return; if(e.name != nil || e.at != nil) (nil, e) = openfile(t, e); else{ if(t.w == nil) return; ct = t.w.body; if(t.w != ct.w) ct.w.lock('M'); if(t == ct) ct.setselect(e.q1, e.q1); n = e.q1 - e.q0; r = stralloc(n); t.file.buf.read(e.q0, r, 0, n); if(search(ct, r.s, n) && e.jump) cursorset(frptofchar(ct.frame, ct.frame.p0).add((4, ct.frame.font.height-4))); if(t.w != ct.w) ct.w.unlock(); strfree(r); r = nil; } e.name = nil; e.bname = nil; } plumblook(m : ref Msg) { e : Expand; if (len m.data > Dat->PLUMBSIZE) { warning(nil, sys->sprint("plumb message too long : %s\n", string m.data)); return; } e.q0 = e.q1 = 0; if (len m.data == 0) return; e.ar = nil; e.name = string m.data; if(e.name[0] != '/' && m.dir != nil) e.name = m.dir + "/" + e.name; (e.name, nil) = cleanname(e.name, len e.name); e.bname = e.name; e.jump = TRUE; e.a0 = e.a1 = 0; (found, addr) := plumbmsg->lookup(plumbmsg->string2attrs(m.attr), "addr"); if (found && addr != nil) { e.ar = addr; e.a1 = len addr; } openfile(nil, e); e.at = nil; } plumbshow(m : ref Msg) { w := utils->newwindow(nil); (found, name) := plumbmsg->lookup(plumbmsg->string2attrs(m.attr), "filename"); if (!found || name == nil) { nuntitled++; name = "Untitled-" + string nuntitled; } if (name[0] != '/' && m.dir != nil) name = m.dir + "/" + name; (name, nil) = cleanname(name, len name); w.setname(name, len name); d := string m.data; w.body.insert(0, d, len d, TRUE, FALSE); w.body.file.mod = FALSE; w.dirty = FALSE; w.settag(); scrl->scrdraw(w.body); w.tag.setselect(w.tag.file.buf.nc, w.tag.file.buf.nc); } search(ct : ref Text, r : string, n : int) : int { q, nb, maxn : int; around : int; s : ref Astring; b, c : int; if(n==0 || n>ct.file.buf.nc) return FALSE; if(2*n > BUFSIZE){ warning(nil, "string too long\n"); return FALSE; } maxn = utils->max(2*n, BUFSIZE); s = utils->stralloc(BUFSIZE); b = nb = 0; around = 0; q = ct.q1; for(;;){ if(q >= ct.file.buf.nc){ q = 0; around = 1; nb = 0; } if(nb > 0){ for (c = 0; c < nb; c++) if (s.s[b+c] == r[0]) break; if(c >= nb){ q += nb; nb = 0; if(around && q>=ct.q1) break; continue; } q += c; nb -= c; b += c; } # reload if buffer covers neither string nor rest of file if(nb<n && nb!=ct.file.buf.nc-q){ nb = ct.file.buf.nc-q; if(nb >= maxn) nb = maxn-1; ct.file.buf.read(q, s, 0, nb); b = 0; } if(n <= nb && s.s[b:b+n] == r[0:n]){ if(ct.w != nil){ ct.show(q, q+n, TRUE); ct.w.settag(); }else{ ct.q0 = q; ct.q1 = q+n; } seltext = ct; utils->strfree(s); s = nil; return TRUE; } if(around && q>=ct.q1) break; --nb; b++; q++; } utils->strfree(s); s = nil; return FALSE; } isfilec(r : int) : int { if(isalnum(r)) return TRUE; if(strchr(".-+/:", r) >= 0) return TRUE; return FALSE; } cleanname(b : string, n : int) : (string, int) { i, j, found : int; b = b[0:n]; # compress multiple slashes for(i=0; i<n-1; i++) if(b[i]=='/' && b[i+1]=='/'){ b = b[0:i] + b[i+1:]; --n; --i; } # eliminate ./ for(i=0; i<n-1; i++) if(b[i]=='.' && b[i+1]=='/' && (i==0 || b[i-1]=='/')){ b = b[0:i] + b[i+2:]; n -= 2; --i; } # eliminate trailing . if(n>=2 && b[n-2]=='/' && b[n-1]=='.') { --n; b = b[0:n]; } do{ # compress xx/.. found = FALSE; for(i=1; i<=n-3; i++) if(b[i:i+3] == "/.."){ if(i==n-3 || b[i+3]=='/'){ found = TRUE; break; } } if(found) for(j=i-1; j>=0; --j) if(j==0 || b[j-1]=='/'){ i += 3; # character beyond .. if(i<n && b[i]=='/') ++i; b = b[0:j] + b[i:]; n -= (i-j); break; } }while(found); if(n == 0){ b = "."; n = 1; } return (b, n); } includefile(dir : string, file : string, nfile : int) : (string, int) { m, n : int; a : string; if (dir == ".") { m = 0; a = file; } else { m = 1 + len dir; a = dir + "/" + file; } n = utils->access(a); if(n < 0) { a = nil; return (nil, 0); } file = nil; return cleanname(a, m+nfile); } objdir : string; includename(t : ref Text , r : string, n : int) : (string, int) { file : string; i, nfile : int; w : ref Window; { w = t.w; if(n==0 || r[0]=='/' || w==nil) raise "e"; if(n>2 && r[0]=='.' && r[1]=='/') raise "e"; file = nil; nfile = 0; (file, nfile) = includefile(".", r, n); if (file == nil) { (dr, dn) := dirname(t, r, n); (file, nfile) = includefile(".", dr, dn); } if (file == nil) { for(i=0; i<w.nincl && file==nil; i++) (file, nfile) = includefile(w.incl[i], r, n); } if(file == nil) (file, nfile) = includefile("/module", r, n); if(file == nil) (file, nfile) = includefile("/include", r, n); if(file==nil && objdir!=nil) (file, nfile) = includefile(objdir, r, n); if(file == nil) raise "e"; return (file, nfile); } exception{ * => return (r, n); } return (nil, 0); } dirname(t : ref Text, r : string, n : int) : (string, int) { b : ref Astring; c : int; m, nt : int; slash : int; { b = nil; if(t == nil || t.w == nil) raise "e"; nt = t.w.tag.file.buf.nc; if(nt == 0) raise "e"; if(n>=1 && r[0]=='/') raise "e"; b = stralloc(nt+n+1); t.w.tag.file.buf.read(0, b, 0, nt); slash = -1; for(m=0; m<nt; m++){ c = b.s[m]; if(c == '/') slash = m; if(c==' ' || c=='\t') break; } if(slash < 0) raise "e"; for (i := 0; i < n; i++) b.s[slash+1+i] = r[i]; r = nil; return cleanname(b.s, slash+1+n); } exception{ * => b = nil; if(r != nil) return cleanname(r, n); return (r, n); } return (nil, 0); } expandfile(t : ref Text, q0 : int, q1 : int, e : Expand) : (int, Expand) { i, n, nname, colon : int; amin, amax : int; r : ref Astring; c : int; w : ref Window; amax = q1; if(q1 == q0){ colon = -1; while(q1<t.file.buf.nc && isfilec(c=t.readc(q1))){ if(c == ':'){ colon = q1; break; } q1++; } while(q0>0 && (isfilec(c=t.readc(q0-1)) || isaddrc(c) || isregexc(c))){ q0--; if(colon==-1 && c==':') colon = q0; } # # if it looks like it might begin file: , consume address chars after : # otherwise terminate expansion at : # if(colon>=0 && colon<t.file.buf.nc-1 && isaddrc(t.readc(colon+1))){ q1 = colon+1; while(q1<t.file.buf.nc-1 && isaddrc(t.readc(q1))) q1++; }else if(colon >= 0) q1 = colon; if(q1 > q0) if(colon >= 0){ # stop at white space for(amax=colon+1; amax<t.file.buf.nc; amax++) if((c=t.readc(amax))==' ' || c=='\t' || c=='\n') break; }else amax = t.file.buf.nc; } amin = amax; e.q0 = q0; e.q1 = q1; n = q1-q0; if(n == 0) return (FALSE, e); # see if it's a file name r = stralloc(n); t.file.buf.read(q0, r, 0, n); # first, does it have bad chars? nname = -1; for(i=0; i<n; i++){ c = r.s[i]; if(c==':' && nname<0){ if(q0+i+1<t.file.buf.nc && (i==n-1 || isaddrc(t.readc(q0+i+1)))) amin = q0+i; else { strfree(r); r = nil; return (FALSE, e); } nname = i; } } if(nname == -1) nname = n; for(i=0; i<nname; i++) if(!isfilec(r.s[i])) { strfree(r); r = nil; return (FALSE, e); } # # See if it's a file name in <>, and turn that into an include # file name if so. Should probably do it for "" too, but that's not # restrictive enough syntax and checking for a #include earlier on the # line would be silly. # isfile := 0; if(q0>0 && t.readc(q0-1)=='<' && q1<t.file.buf.nc && t.readc(q1)=='>') (r.s, nname) = includename(t, r.s, nname); else if(q0>0 && t.readc(q0-1)=='"' && q1<t.file.buf.nc && t.readc(q1)=='"') (r.s, nname) = includename(t, r.s, nname); else if(amin == q0) isfile = 1; else (r.s, nname) = dirname(t, r.s, nname); if (!isfile) { e.bname = r.s; # if it's already a window name, it's a file w = lookfile(r.s, nname); # if it's the name of a file, it's a file if(w == nil && utils->access(e.bname) < 0){ e.bname = nil; strfree(r); r = nil; return (FALSE, e); } } e.name = r.s[0:nname]; e.at = t; e.a0 = amin+1; (nil, e.a1, nil) = address(nil, nil, (Range)(-1,-1), (Range)(0, 0), t, nil, e.a0, amax, FALSE); strfree(r); r = nil; return (TRUE, e); } expand(t : ref Text, q0 : int, q1 : int) : (int, Expand) { e : Expand; ok : int; e.q0 = e.q1 = e.a0 = e.a1 = 0; e.name = e.bname = nil; e.at = nil; # if in selection, choose selection e.jump = TRUE; if(q1==q0 && t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){ q0 = t.q0; q1 = t.q1; if(t.what == Textm->Tag) e.jump = FALSE; } (ok, e) = expandfile(t, q0, q1, e); if (ok) return (TRUE, e); if(q0 == q1){ while(q1<t.file.buf.nc && isalnum(t.readc(q1))) q1++; while(q0>0 && isalnum(t.readc(q0-1))) q0--; } e.q0 = q0; e.q1 = q1; return (q1 > q0, e); } lookfile(s : string, n : int) : ref Window { i, j, k : int; w : ref Window; c : ref Column; t : ref Text; # avoid terminal slash on directories if(n > 1 && s[n-1] == '/') --n; for(j=0; j<row.ncol; j++){ c = row.col[j]; for(i=0; i<c.nw; i++){ w = c.w[i]; t = w.body; k = len t.file.name; if(k>0 && t.file.name[k-1] == '/') k--; if(t.file.name[0:k] == s[0:n]){ w = w.body.file.curtext.w; if(w.col != nil) # protect against race deleting w return w; } } } return nil; } lookid(id : int, dump : int) : ref Window { i, j : int; w : ref Window; c : ref Column; for(j=0; j<row.ncol; j++){ c = row.col[j]; for(i=0; i<c.nw; i++){ w = c.w[i]; if(dump && w.dumpid == id) return w; if(!dump && w.id == id) return w; } } return nil; } openfile(t : ref Text, e : Expand) : (ref Window, Expand) { r : Range; w, ow : ref Window; eval, i, n : int; if(e.name == nil){ w = t.w; if(w == nil) return (nil, e); }else w = lookfile(e.name, len e.name); if(w != nil){ t = w.body; if(!t.col.safe && t.frame.maxlines==0) # window is obscured by full-column window t.col.grow(t.col.w[0], 1, 1); } else{ ow = nil; if(t != nil) ow = t.w; w = utils->newwindow(t); t = w.body; w.setname(e.name, len e.name); t.loadx(0, e.bname, 1); t.file.mod = FALSE; t.w.dirty = FALSE; t.w.settag(); t.w.tag.setselect(t.w.tag.file.buf.nc, t.w.tag.file.buf.nc); if(ow != nil) for(i=ow.nincl; --i>=0; ){ n = len ow.incl[i]; w.addincl(ow.incl[i], n); # really do want to copy here } } if(e.a1 == e.a0) eval = FALSE; else (eval, nil, r) = address(nil, t, (Range)(-1, -1), (Range)(t.q0, t.q1), e.at, e.ar, e.a0, e.a1, TRUE); # was (eval, nil, r) = address(nil, t, (Range)(-1, -1), (Range)(t.q0, t.q1), e.at, nil, e.a0, e.a1, TRUE); if(eval == FALSE){ r.q0 = t.q0; r.q1 = t.q1; } t.show(r.q0, r.q1, TRUE); t.w.settag(); seltext = t; if(e.jump) cursorset(frptofchar(t.frame, t.frame.p0).add((4, t.frame.font.height-4))); return (w, e); } new(et : ref Text, t : ref Text, argt : ref Text, flag1 : int, flag2 : int, arg : string, narg : int) { ndone : int; a, f : string; na, nf : int; e : Expand; (nil, a, na) = exec->getarg(argt, FALSE, TRUE); if(a != nil){ new(et, t, nil, flag1, flag2, a, na); if(narg == 0) return; } # loop condition: *arg is not a blank for(ndone=0; ; ndone++){ (a, na) = utils->findbl(arg, narg); if(a == arg){ if(ndone==0 && et.col!=nil) et.col.add(nil, nil, -1).settag(); break; } nf = narg-na; f = arg[0:nf]; # want a copy (f, nf) = dirname(et, f, nf); e.q0 = e.q1 = e.a0 = e.a1 = 0; e.at = nil; e.name = f; e.bname = f; e.jump = TRUE; (nil, e) = openfile(et, e); f = nil; e.bname = nil; (arg, narg) = utils->skipbl(a, na); } }