ref: 866d74c0c4bb50e85e9e8bb95140c10d409e53be
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);
}
}