ref: 5e14b38c8f501895068c5ee37ebf235e9c8e2351
author: Aidan K. Wiggins <[email protected]>
date: Tue Dec 10 00:10:14 EST 2024
Add sam(term)? sources from 9front (ddaadd65b44ee8f745965d8f3ecdb565f48850fb)
--- /dev/null
+++ b/sam/acid.lib
@@ -1,0 +1,104 @@
+defn saminit()
+{
+ if !access("/sys/src/cmd/sam/cmd.acid") then
+ rc("cd /sys/src/cmd/sam && mk cmd.acid");
+ include("/sys/src/cmd/sam/cmd.acid");
+}
+
+defn printrunestring(addr, len)
+{
+ loop 1,len do {
+ print(*(addr\r));
+ addr = addr+2;
+ }
+}
+
+defn runestring(addr, len)
+{
+ local s;
+
+ s = "";
+ loop 1,len do {
+ s = s + *(addr\r);
+ addr = addr+2;
+ }
+ return s;
+}
+
+defn string(s)
+{
+ complex String s;
+ return runestring(s.s, s.n);
+}
+
+defn filemenu(f)
+{
+ complex File f;
+ local s;
+
+ s = "";
+ if f != *cmd then {
+ if f.mod then
+ s = s + "'";
+ else
+ s = s + " ";
+ if f.rasp != 0 then
+ s = s + "+";
+ else
+ s = s + "-";
+ if f == *curfile then
+ s = s + ".";
+ else
+ s = s + " ";
+ s = s + " ";
+ }
+ s = s + string(f.name);
+ print("file(", f\X, ") // ", s, "\n");
+}
+
+defn files()
+{
+ local i, f, fx, fp;
+
+ fx = (List)file;
+ if fx.nused == 0 then
+ filemenu(*cmd);
+ i = 0;
+ loop 1,fx.nused do {
+ f = (File)fx.g.filep[i];
+ filemenu(f);
+ i = i+1;
+ }
+}
+
+defn range(r)
+{
+ complex Range r;
+
+ if r.p1 == r.p2 then
+ return itoa(r.p1, "#%d");
+ return itoa(r.p1, "#%d")+"-"+itoa(r.p2, "#%d");
+}
+
+defn file(f)
+{
+ complex File f;
+
+ filemenu(f);
+ print("\t$=#", f.nc\D, " .=", range(f.dot.r), " buffer(", f\X, ")\n");
+ if f == *cmd then {
+ print("\tcmdpt=#", *cmdpt\D, " cmdptadv=#", *cmdptadv\D, "\n");
+ }
+}
+
+defn buffer(b)
+{
+ complex Buffer b;
+
+ print("buffer(", b\X, "): c=", b.c\X, " nc=", b.nc\D, "\n");
+ print("cache: ==(");
+ print(runestring(b.c, b.nc));
+ print(")==\n");
+}
+
+print("/sys/lib/acid/sam");
--- /dev/null
+++ b/sam/address.c
@@ -1,0 +1,243 @@
+#include "sam.h"
+#include "parse.h"
+
+Address addr;
+String lastpat;
+int patset;
+File *menu;
+
+File *matchfile(String*);
+Address charaddr(Posn, Address, int);
+
+Address
+address(Addr *ap, Address a, int sign)
+{
+ File *f = a.f;
+ Address a1, a2;
+
+ do{
+ switch(ap->type){
+ case 'l':
+ case '#':
+ a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
+ break;
+
+ case '.':
+ a = f->dot;
+ break;
+
+ case '$':
+ a.r.p1 = a.r.p2 = f->nc;
+ break;
+
+ case '\'':
+ a.r = f->mark;
+ break;
+
+ case '?':
+ sign = -sign;
+ if(sign == 0)
+ sign = -1;
+ /* fall through */
+ case '/':
+ nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
+ a.r = sel.p[0];
+ break;
+
+ case '"':
+ a = matchfile(ap->are)->dot;
+ f = a.f;
+ if(f->unread)
+ load(f);
+ break;
+
+ case '*':
+ a.r.p1 = 0, a.r.p2 = f->nc;
+ return a;
+
+ case ',':
+ case ';':
+ if(ap->left)
+ a1 = address(ap->left, a, 0);
+ else
+ a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
+ if(ap->type == ';'){
+ f = a1.f;
+ a = a1;
+ f->dot = a1;
+ }
+ if(ap->next)
+ a2 = address(ap->next, a, 0);
+ else
+ a2.f = a.f, a2.r.p1 = a2.r.p2 = f->nc;
+ if(a1.f != a2.f)
+ error(Eorder);
+ a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
+ if(a.r.p2 < a.r.p1)
+ error(Eorder);
+ return a;
+
+ case '+':
+ case '-':
+ sign = 1;
+ if(ap->type == '-')
+ sign = -1;
+ if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
+ a = lineaddr(1L, a, sign);
+ break;
+ default:
+ panic("address");
+ return a;
+ }
+ }while(ap = ap->next); /* assign = */
+ return a;
+}
+
+void
+nextmatch(File *f, String *r, Posn p, int sign)
+{
+ compile(r);
+ if(sign >= 0){
+ if(!execute(f, p, INFINITY))
+ error(Esearch);
+ if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
+ if(++p>f->nc)
+ p = 0;
+ if(!execute(f, p, INFINITY))
+ panic("address");
+ }
+ }else{
+ if(!bexecute(f, p))
+ error(Esearch);
+ if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
+ if(--p<0)
+ p = f->nc;
+ if(!bexecute(f, p))
+ panic("address");
+ }
+ }
+}
+
+File *
+matchfile(String *r)
+{
+ File *f;
+ File *match = 0;
+ int i;
+
+ for(i = 0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f == cmd)
+ continue;
+ if(filematch(f, r)){
+ if(match)
+ error(Emanyfiles);
+ match = f;
+ }
+ }
+ if(!match)
+ error(Efsearch);
+ return match;
+}
+
+int
+filematch(File *f, String *r)
+{
+ char *c, *s;
+ String *t;
+
+ c = Strtoc(&f->name);
+ s = smprint("%c%c%c %s\n", " '"[f->mod],
+ "-+"[f->rasp!=0], " ."[f==curfile], c);
+ if(s == nil)
+ error(Etoolong);
+ free(c);
+ t = tmpcstr(s);
+ Strduplstr(&genstr, t);
+ freetmpstr(t);
+ /* A little dirty... */
+ if(menu == 0)
+ menu = fileopen();
+ bufreset(menu);
+ bufinsert(menu, 0, genstr.s, genstr.n);
+ compile(r);
+ free(s);
+ return execute(menu, 0, menu->nc);
+}
+
+Address
+charaddr(Posn l, Address addr, int sign)
+{
+ if(sign == 0)
+ addr.r.p1 = addr.r.p2 = l;
+ else if(sign < 0)
+ addr.r.p2 = addr.r.p1-=l;
+ else if(sign > 0)
+ addr.r.p1 = addr.r.p2+=l;
+ if(addr.r.p1<0 || addr.r.p2>addr.f->nc)
+ error(Erange);
+ return addr;
+}
+
+Address
+lineaddr(Posn l, Address addr, int sign)
+{
+ int n;
+ int c;
+ File *f = addr.f;
+ Address a;
+ Posn p;
+
+ a.f = f;
+ if(sign >= 0){
+ if(l == 0){
+ if(sign==0 || addr.r.p2==0){
+ a.r.p1 = a.r.p2 = 0;
+ return a;
+ }
+ a.r.p1 = addr.r.p2;
+ p = addr.r.p2-1;
+ }else{
+ if(sign==0 || addr.r.p2==0){
+ p = (Posn)0;
+ n = 1;
+ }else{
+ p = addr.r.p2-1;
+ n = filereadc(f, p++)=='\n';
+ }
+ while(n < l){
+ if(p >= f->nc)
+ error(Erange);
+ if(filereadc(f, p++) == '\n')
+ n++;
+ }
+ a.r.p1 = p;
+ }
+ while(p < f->nc && filereadc(f, p++)!='\n')
+ ;
+ a.r.p2 = p;
+ }else{
+ p = addr.r.p1;
+ if(l == 0)
+ a.r.p2 = addr.r.p1;
+ else{
+ for(n = 0; n<l; ){ /* always runs once */
+ if(p == 0){
+ if(++n != l)
+ error(Erange);
+ }else{
+ c = filereadc(f, p-1);
+ if(c != '\n' || ++n != l)
+ p--;
+ }
+ }
+ a.r.p2 = p;
+ if(p > 0)
+ p--;
+ }
+ while(p > 0 && filereadc(f, p-1)!='\n') /* lines start after a newline */
+ p--;
+ a.r.p1 = p;
+ }
+ return a;
+}
--- /dev/null
+++ b/sam/buff.c
@@ -1,0 +1,302 @@
+#include "sam.h"
+
+enum
+{
+ Slop = 100, /* room to grow with reallocation */
+};
+
+static
+void
+sizecache(Buffer *b, uint n)
+{
+ if(n <= b->cmax)
+ return;
+ b->cmax = n+Slop;
+ b->c = runerealloc(b->c, b->cmax);
+}
+
+static
+void
+addblock(Buffer *b, uint i, uint n)
+{
+ if(i > b->nbl)
+ panic("internal error: addblock");
+
+ b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
+ if(i < b->nbl)
+ memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
+ b->bl[i] = disknewblock(disk, n);
+ b->nbl++;
+}
+
+
+static
+void
+delblock(Buffer *b, uint i)
+{
+ if(i >= b->nbl)
+ panic("internal error: delblock");
+
+ diskrelease(disk, b->bl[i]);
+ b->nbl--;
+ if(i < b->nbl)
+ memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
+ b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
+}
+
+/*
+ * Move cache so b->cq <= q0 < b->cq+b->cnc.
+ * If at very end, q0 will fall on end of cache block.
+ */
+
+static
+void
+flush(Buffer *b)
+{
+ if(b->cdirty || b->cnc==0){
+ if(b->cnc == 0)
+ delblock(b, b->cbi);
+ else
+ diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
+ b->cdirty = FALSE;
+ }
+}
+
+static
+void
+setcache(Buffer *b, uint q0)
+{
+ Block **blp, *bl;
+ uint i, q;
+
+ if(q0 > b->nc)
+ panic("internal error: setcache");
+ /*
+ * flush and reload if q0 is not in cache.
+ */
+ if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
+ return;
+ /*
+ * if q0 is at end of file and end of cache, continue to grow this block
+ */
+ if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<=Maxblock)
+ return;
+ flush(b);
+ /* find block */
+ if(q0 < b->cq){
+ q = 0;
+ i = 0;
+ }else{
+ q = b->cq;
+ i = b->cbi;
+ }
+ blp = &b->bl[i];
+ while(q+(*blp)->n <= q0 && q+(*blp)->n < b->nc){
+ q += (*blp)->n;
+ i++;
+ blp++;
+ if(i >= b->nbl)
+ panic("block not found");
+ }
+ bl = *blp;
+ /* remember position */
+ b->cbi = i;
+ b->cq = q;
+ sizecache(b, bl->n);
+ b->cnc = bl->n;
+ /*read block*/
+ diskread(disk, bl, b->c, b->cnc);
+}
+
+void
+bufinsert(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint i, m, t, off;
+
+ if(q0 > b->nc)
+ panic("internal error: bufinsert");
+
+ while(n > 0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(b->cnc+n <= Maxblock){
+ /* Everything fits in one block. */
+ t = b->cnc+n;
+ m = n;
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ panic("internal error: bufinsert1 cnc!=0");
+ addblock(b, 0, t);
+ b->cbi = 0;
+ }
+ sizecache(b, t);
+ runemove(b->c+off+m, b->c+off, b->cnc-off);
+ runemove(b->c+off, s, m);
+ b->cnc = t;
+ goto Tail;
+ }
+ /*
+ * We must make a new block. If q0 is at
+ * the very beginning or end of this block,
+ * just make a new block and fill it.
+ */
+ if(q0==b->cq || q0==b->cq+b->cnc){
+ if(b->cdirty)
+ flush(b);
+ m = min(n, Maxblock);
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ panic("internal error: bufinsert2 cnc!=0");
+ i = 0;
+ }else{
+ i = b->cbi;
+ if(q0 > b->cq)
+ i++;
+ }
+ addblock(b, i, m);
+ sizecache(b, m);
+ runemove(b->c, s, m);
+ b->cq = q0;
+ b->cbi = i;
+ b->cnc = m;
+ goto Tail;
+ }
+ /*
+ * Split the block; cut off the right side and
+ * let go of it.
+ */
+ m = b->cnc-off;
+ if(m > 0){
+ i = b->cbi+1;
+ addblock(b, i, m);
+ diskwrite(disk, &b->bl[i], b->c+off, m);
+ b->cnc -= m;
+ }
+ /*
+ * Now at end of block. Take as much input
+ * as possible and tack it on end of block.
+ */
+ m = min(n, Maxblock-b->cnc);
+ sizecache(b, b->cnc+m);
+ runemove(b->c+b->cnc, s, m);
+ b->cnc += m;
+ Tail:
+ b->nc += m;
+ q0 += m;
+ s += m;
+ n -= m;
+ b->cdirty = TRUE;
+ }
+}
+
+void
+bufdelete(Buffer *b, uint q0, uint q1)
+{
+ uint m, n, off;
+
+ if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
+ panic("internal error: bufdelete");
+ while(q1 > q0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(q1 > b->cq+b->cnc)
+ n = b->cnc - off;
+ else
+ n = q1-q0;
+ m = b->cnc - (off+n);
+ if(m > 0)
+ runemove(b->c+off, b->c+off+n, m);
+ b->cnc -= n;
+ b->cdirty = TRUE;
+ q1 -= n;
+ b->nc -= n;
+ }
+}
+
+uint
+bufload(Buffer *b, uint q0, int fd, int *nulls)
+{
+ char *p;
+ Rune *r;
+ int l, m, n, nb, nr;
+ uint q1;
+
+ if(q0 > b->nc)
+ panic("internal error: bufload");
+ p = malloc((Maxblock+UTFmax+1)*sizeof p[0]);
+ if(p == nil)
+ panic("bufload: malloc failed");
+ r = runemalloc(Maxblock);
+ m = 0;
+ n = 1;
+ q1 = q0;
+ /*
+ * At top of loop, may have m bytes left over from
+ * last pass, possibly representing a partial rune.
+ */
+ while(n > 0){
+ n = read(fd, p+m, Maxblock);
+ if(n < 0){
+ error(Ebufload);
+ break;
+ }
+ m += n;
+ p[m] = 0;
+ l = m;
+ if(n > 0)
+ l -= UTFmax;
+ cvttorunes(p, l, r, &nb, &nr, nulls);
+ memmove(p, p+nb, m-nb);
+ m -= nb;
+ bufinsert(b, q1, r, nr);
+ q1 += nr;
+ }
+ free(p);
+ free(r);
+ return q1-q0;
+}
+
+void
+bufread(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint m;
+
+ if(!(q0<=b->nc && q0+n<=b->nc))
+ panic("bufread: internal error");
+
+ while(n > 0){
+ setcache(b, q0);
+ m = min(n, b->cnc-(q0-b->cq));
+ runemove(s, b->c+(q0-b->cq), m);
+ q0 += m;
+ s += m;
+ n -= m;
+ }
+}
+
+void
+bufreset(Buffer *b)
+{
+ int i;
+
+ b->nc = 0;
+ b->cnc = 0;
+ b->cq = 0;
+ b->cdirty = 0;
+ b->cbi = 0;
+ /* delete backwards to avoid n² behavior */
+ for(i=b->nbl-1; --i>=0; )
+ delblock(b, i);
+}
+
+void
+bufclose(Buffer *b)
+{
+ bufreset(b);
+ free(b->c);
+ b->c = nil;
+ b->cnc = 0;
+ free(b->bl);
+ b->bl = nil;
+ b->nbl = 0;
+}
--- /dev/null
+++ b/sam/cmd.c
@@ -1,0 +1,616 @@
+#include "sam.h"
+#include "parse.h"
+
+static char linex[]="\n";
+static char wordx[]=" \t\n";
+Cmdtab cmdtab[]={
+/* cmdc text regexp addr defcmd defaddr count token fn */
+ '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd,
+ 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd,
+ 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
+ 'B', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
+ 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd,
+ 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd,
+ 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd,
+ 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd,
+ 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd,
+ 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd,
+ 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd,
+ 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'M', 0, 0, 0, 0, aNo, 0, linex, M_cmd,
+ 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd,
+ 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd,
+ 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd,
+ 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd,
+ 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd,
+ 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd,
+ 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd,
+ 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd,
+ '>', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '<', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '|', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '^', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd,
+ '_', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd,
+ 'c'|0x100,0, 0, 0, 0, aNo, 0, wordx, cd_cmd,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+Cmd *parsecmd(int);
+Addr *compoundaddr(void);
+Addr *simpleaddr(void);
+void freecmd(void);
+void okdelim(int);
+
+Rune line[BLOCKSIZE];
+Rune termline[BLOCKSIZE];
+Rune *linep = line;
+Rune *terminp = termline;
+Rune *termoutp = termline;
+
+List cmdlist = { 'p' };
+List addrlist = { 'p' };
+List relist = { 'p' };
+List stringlist = { 'p' };
+
+int eof;
+
+void
+resetcmd(void)
+{
+ linep = line;
+ *linep = 0;
+ terminp = termoutp = termline;
+ freecmd();
+}
+
+int
+inputc(void)
+{
+ int n, nbuf;
+ char buf[UTFmax];
+ Rune r;
+
+ Again:
+ nbuf = 0;
+ if(cmdbufpos > cmdbuf.nc && cmdbuf.nc > 0){
+ cmdbufpos = 0;
+ bufreset(&cmdbuf);
+ }
+ if(cmdbufpos < cmdbuf.nc && cmdbuf.nc > 0)
+ bufread(&cmdbuf, cmdbufpos++, &r, 1);
+ else if(downloaded){
+ while(termoutp == terminp){
+ cmdupdate();
+ if(patset)
+ tellpat();
+ while(termlocked > 0){
+ outT0(Hunlock);
+ termlocked--;
+ }
+ if(rcv() == 0)
+ return -1;
+ }
+ r = *termoutp++;
+ if(termoutp == terminp)
+ terminp = termoutp = termline;
+ }else{
+ do{
+ n = read(0, buf+nbuf, 1);
+ if(n <= 0)
+ return -1;
+ nbuf += n;
+ }while(!fullrune(buf, nbuf));
+ chartorune(&r, buf);
+ }
+ if(r == 0){
+ warn(Wnulls);
+ goto Again;
+ }
+ return r;
+}
+
+int
+inputline(void)
+{
+ int i, c, start;
+
+ /*
+ * Could set linep = line and i = 0 here and just
+ * error(Etoolong) below, but this way we keep
+ * old input buffer history around for a while.
+ * This is useful only for debugging.
+ */
+ i = linep - line;
+ do{
+ if((c = inputc())<=0)
+ return -1;
+ if(i == nelem(line)-1){
+ if(linep == line)
+ error(Etoolong);
+ start = linep - line;
+ runemove(line, linep, i-start);
+ i -= start;
+ linep = line;
+ }
+ }while((line[i++]=c) != '\n');
+ line[i] = 0;
+ return 1;
+}
+
+int
+getch(void)
+{
+ if(eof)
+ return -1;
+ if(*linep==0 && inputline()<0){
+ eof = TRUE;
+ return -1;
+ }
+ return *linep++;
+}
+
+int
+nextc(void)
+{
+ if(*linep == 0)
+ return -1;
+ return *linep;
+}
+
+void
+ungetch(void)
+{
+ if(--linep < line)
+ panic("ungetch");
+}
+
+Posn
+getnum(int signok)
+{
+ Posn n=0;
+ int c, sign;
+
+ sign = 1;
+ if(signok>1 && nextc()=='-'){
+ sign = -1;
+ getch();
+ }
+ if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
+ return sign;
+ while('0'<=(c=getch()) && c<='9')
+ n = n*10 + (c-'0');
+ ungetch();
+ return sign*n;
+}
+
+int
+skipbl(void)
+{
+ int c;
+ do
+ c = getch();
+ while(c==' ' || c=='\t');
+ if(c >= 0)
+ ungetch();
+ return c;
+}
+
+void
+termcommand(void)
+{
+ Posn p;
+
+ for(p=cmdpt; p<cmd->nc; p++){
+ if(terminp >= termline+nelem(termline)){
+ cmdpt = cmd->nc;
+ error(Etoolong);
+ }
+ *terminp++ = filereadc(cmd, p);
+ }
+ cmdpt = cmd->nc;
+}
+
+void
+cmdloop(void)
+{
+ Cmd *cmdp;
+ File *ocurfile;
+ int loaded;
+
+ for(;;){
+ if(!downloaded && curfile && curfile->unread)
+ load(curfile);
+ if((cmdp = parsecmd(0))==0){
+ if(downloaded){
+ rescue();
+ exits("eof");
+ }
+ break;
+ }
+ ocurfile = curfile;
+ loaded = curfile && !curfile->unread;
+ if(cmdexec(curfile, cmdp) == 0)
+ break;
+ freecmd();
+ cmdupdate();
+ update();
+ if(downloaded && curfile &&
+ (ocurfile!=curfile || (!loaded && !curfile->unread)))
+ outTs(Hcurrent, curfile->tag);
+ /* don't allow type ahead on files that aren't bound */
+ if(downloaded && curfile && curfile->rasp == 0)
+ terminp = termoutp;
+ }
+}
+
+Cmd *
+newcmd(void){
+ Cmd *p;
+
+ p = emalloc(sizeof(Cmd));
+ inslist(&cmdlist, cmdlist.nused, p);
+ return p;
+}
+
+Addr*
+newaddr(void)
+{
+ Addr *p;
+
+ p = emalloc(sizeof(Addr));
+ inslist(&addrlist, addrlist.nused, p);
+ return p;
+}
+
+String*
+newre(void)
+{
+ String *p;
+
+ p = emalloc(sizeof(String));
+ inslist(&relist, relist.nused, p);
+ Strinit(p);
+ return p;
+}
+
+String*
+newstring(void)
+{
+ String *p;
+
+ p = emalloc(sizeof(String));
+ inslist(&stringlist, stringlist.nused, p);
+ Strinit(p);
+ return p;
+}
+
+void
+freecmd(void)
+{
+ int i;
+
+ while(cmdlist.nused > 0)
+ free(cmdlist.voidpptr[--cmdlist.nused]);
+ while(addrlist.nused > 0)
+ free(addrlist.voidpptr[--addrlist.nused]);
+ while(relist.nused > 0){
+ i = --relist.nused;
+ Strclose(relist.stringpptr[i]);
+ free(relist.stringpptr[i]);
+ }
+ while(stringlist.nused>0){
+ i = --stringlist.nused;
+ Strclose(stringlist.stringpptr[i]);
+ free(stringlist.stringpptr[i]);
+ }
+}
+
+int
+lookup(int c)
+{
+ int i;
+
+ for(i=0; cmdtab[i].cmdc; i++)
+ if(cmdtab[i].cmdc == c)
+ return i;
+ return -1;
+}
+
+void
+okdelim(int c)
+{
+ if(c=='\\' || ('a'<=c && c<='z')
+ || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+ error_c(Edelim, c);
+}
+
+void
+atnl(void)
+{
+ skipbl();
+ if(getch() != '\n')
+ error(Enewline);
+}
+
+void
+getrhs(String *s, int delim, int cmd)
+{
+ int c;
+
+ while((c = getch())>0 && c!=delim && c!='\n'){
+ if(c == '\\'){
+ if((c=getch()) <= 0)
+ error(Ebadrhs);
+ if(c == '\n'){
+ ungetch();
+ c='\\';
+ }else if(c == 'n')
+ c='\n';
+ else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */
+ Straddc(s, '\\');
+ }
+ Straddc(s, c);
+ }
+ ungetch(); /* let client read whether delimeter, '\n' or whatever */
+}
+
+String *
+collecttoken(char *end)
+{
+ String *s = newstring();
+ int c;
+
+ while((c=nextc())==' ' || c=='\t')
+ Straddc(s, getch()); /* blanks significant for getname() */
+ while((c=getch())>0 && utfrune(end, c)==0)
+ Straddc(s, c);
+ Straddc(s, 0);
+ if(c != '\n')
+ atnl();
+ return s;
+}
+
+String *
+collecttext(void)
+{
+ String *s = newstring();
+ int begline, i, c, delim;
+
+ if(skipbl()=='\n'){
+ getch();
+ i = 0;
+ do{
+ begline = i;
+ while((c = getch())>0 && c!='\n')
+ i++, Straddc(s, c);
+ i++, Straddc(s, '\n');
+ if(c < 0)
+ goto Return;
+ }while(s->s[begline]!='.' || s->s[begline+1]!='\n');
+ Strdelete(s, s->n-2, s->n);
+ }else{
+ okdelim(delim = getch());
+ getrhs(s, delim, 'a');
+ if(nextc()==delim)
+ getch();
+ atnl();
+ }
+ Return:
+ Straddc(s, 0); /* JUST FOR CMDPRINT() */
+ return s;
+}
+
+Cmd *
+parsecmd(int nest)
+{
+ int i, c;
+ Cmdtab *ct;
+ Cmd *cp, *ncp;
+ Cmd cmd;
+
+ cmd.next = cmd.ccmd = 0;
+ cmd.re = 0;
+ cmd.flag = cmd.num = 0;
+ cmd.addr = compoundaddr();
+ if(skipbl() == -1)
+ return 0;
+ if((c=getch())==-1)
+ return 0;
+ cmd.cmdc = c;
+ if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */
+ getch(); /* the 'd' */
+ cmd.cmdc='c'|0x100;
+ }
+ i = lookup(cmd.cmdc);
+ if(i >= 0){
+ if(cmd.cmdc == '\n')
+ goto Return; /* let nl_cmd work it all out */
+ ct = &cmdtab[i];
+ if(ct->defaddr==aNo && cmd.addr)
+ error(Enoaddr);
+ if(ct->count)
+ cmd.num = getnum(ct->count);
+ if(ct->regexp){
+ /* x without pattern -> .*\n, indicated by cmd.re==0 */
+ /* X without pattern is all files */
+ if((ct->cmdc!='x' && ct->cmdc!='X') ||
+ ((c = nextc())!=' ' && c!='\t' && c!='\n')){
+ skipbl();
+ if((c = getch())=='\n' || c<0)
+ error(Enopattern);
+ okdelim(c);
+ cmd.re = getregexp(c);
+ if(ct->cmdc == 's'){
+ cmd.ctext = newstring();
+ getrhs(cmd.ctext, c, 's');
+ if(nextc() == c){
+ getch();
+ if(nextc() == 'g')
+ cmd.flag = getch();
+ }
+
+ }
+ }
+ }
+ if(ct->addr && (cmd.caddr=simpleaddr())==0)
+ error(Eaddress);
+ if(ct->defcmd){
+ if(skipbl() == '\n'){
+ getch();
+ cmd.ccmd = newcmd();
+ cmd.ccmd->cmdc = ct->defcmd;
+ }else if((cmd.ccmd = parsecmd(nest))==0)
+ panic("defcmd");
+ }else if(ct->text)
+ cmd.ctext = collecttext();
+ else if(ct->token)
+ cmd.ctext = collecttoken(ct->token);
+ else
+ atnl();
+ }else
+ switch(cmd.cmdc){
+ case '{':
+ cp = 0;
+ do{
+ if(skipbl()=='\n')
+ getch();
+ ncp = parsecmd(nest+1);
+ if(cp)
+ cp->next = ncp;
+ else
+ cmd.ccmd = ncp;
+ }while(cp = ncp);
+ break;
+ case '}':
+ atnl();
+ if(nest==0)
+ error(Enolbrace);
+ return 0;
+ default:
+ error_c(Eunk, cmd.cmdc);
+ }
+ Return:
+ cp = newcmd();
+ *cp = cmd;
+ return cp;
+}
+
+String* /* BUGGERED */
+getregexp(int delim)
+{
+ String *r = newre();
+ int c;
+
+ for(Strzero(&genstr); ; Straddc(&genstr, c))
+ if((c = getch())=='\\'){
+ if(nextc()==delim)
+ c = getch();
+ else if(nextc()=='\\'){
+ Straddc(&genstr, c);
+ c = getch();
+ }
+ }else if(c==delim || c=='\n')
+ break;
+ if(c!=delim && c)
+ ungetch();
+ if(genstr.n > 0){
+ patset = TRUE;
+ Strduplstr(&lastpat, &genstr);
+ Straddc(&lastpat, '\0');
+ }
+ if(lastpat.n <= 1)
+ error(Epattern);
+ Strduplstr(r, &lastpat);
+ return r;
+}
+
+Addr *
+simpleaddr(void)
+{
+ Addr addr;
+ Addr *ap, *nap;
+
+ addr.next = 0;
+ addr.left = 0;
+ switch(skipbl()){
+ case '#':
+ addr.type = getch();
+ addr.num = getnum(1);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ addr.num = getnum(1);
+ addr.type='l';
+ break;
+ case '/': case '?': case '"':
+ addr.are = getregexp(addr.type = getch());
+ break;
+ case '.':
+ case '$':
+ case '+':
+ case '-':
+ case '\'':
+ addr.type = getch();
+ break;
+ default:
+ return 0;
+ }
+ if(addr.next = simpleaddr())
+ switch(addr.next->type){
+ case '.':
+ case '$':
+ case '\'':
+ if(addr.type!='"')
+ case '"':
+ error(Eaddress);
+ break;
+ case 'l':
+ case '#':
+ if(addr.type=='"')
+ break;
+ /* fall through */
+ case '/':
+ case '?':
+ if(addr.type!='+' && addr.type!='-'){
+ /* insert the missing '+' */
+ nap = newaddr();
+ nap->type='+';
+ nap->next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ case '+':
+ case '-':
+ break;
+ default:
+ panic("simpleaddr");
+ }
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+
+Addr *
+compoundaddr(void)
+{
+ Addr addr;
+ Addr *ap, *next;
+
+ addr.left = simpleaddr();
+ if((addr.type = skipbl())!=',' && addr.type!=';')
+ return addr.left;
+ getch();
+ next = addr.next = compoundaddr();
+ if(next && (next->type==',' || next->type==';') && next->left==0)
+ error(Eaddress);
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
--- /dev/null
+++ b/sam/disk.c
@@ -1,0 +1,121 @@
+#include "sam.h"
+
+static Block *blist;
+
+static int
+tempdisk(void)
+{
+ char buf[128];
+ int i, fd;
+
+ snprint(buf, sizeof buf, "/tmp/X%d.%.4ssam", getpid(), getuser());
+ for(i='A'; i<='Z'; i++){
+ buf[5] = i;
+ if(access(buf, AEXIST) == 0)
+ continue;
+ fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
+ if(fd >= 0)
+ return fd;
+ }
+ return -1;
+}
+
+Disk*
+diskinit()
+{
+ Disk *d;
+
+ d = emalloc(sizeof(Disk));
+ d->fd = tempdisk();
+ if(d->fd < 0){
+ fprint(2, "sam: can't create temp file: %r\n");
+ exits("diskinit");
+ }
+ return d;
+}
+
+static
+uint
+ntosize(uint n, uint *ip)
+{
+ uint size;
+
+ if(n > Maxblock)
+ panic("internal error: ntosize");
+ size = n;
+ if(size & (Blockincr-1))
+ size += Blockincr - (size & (Blockincr-1));
+ /* last bucket holds blocks of exactly Maxblock */
+ if(ip)
+ *ip = size/Blockincr;
+ return size * sizeof(Rune);
+}
+
+Block*
+disknewblock(Disk *d, uint n)
+{
+ uint i, j, size;
+ Block *b;
+
+ size = ntosize(n, &i);
+ b = d->free[i];
+ if(b)
+ d->free[i] = b->next;
+ else{
+ /* allocate in chunks to reduce malloc overhead */
+ if(blist == nil){
+ blist = emalloc(100*sizeof(Block));
+ for(j=0; j<100-1; j++)
+ blist[j].next = &blist[j+1];
+ }
+ b = blist;
+ blist = b->next;
+ b->addr = d->addr;
+ if(d->addr+size < d->addr){
+ panic("temp file overflow");
+ }
+ d->addr += size;
+ }
+ b->n = n;
+ return b;
+}
+
+void
+diskrelease(Disk *d, Block *b)
+{
+ uint i;
+
+ ntosize(b->n, &i);
+ b->next = d->free[i];
+ d->free[i] = b;
+}
+
+void
+diskwrite(Disk *d, Block **bp, Rune *r, uint n)
+{
+ int size, nsize;
+ Block *b;
+
+ b = *bp;
+ size = ntosize(b->n, nil);
+ nsize = ntosize(n, nil);
+ if(size != nsize){
+ diskrelease(d, b);
+ b = disknewblock(d, n);
+ *bp = b;
+ }
+ if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+ panic("write error to temp file");
+ b->n = n;
+}
+
+void
+diskread(Disk *d, Block *b, Rune *r, uint n)
+{
+ if(n > b->n)
+ panic("internal error: diskread");
+
+ ntosize(b->n, nil); /* called only for sanity check on Maxblock */
+ if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+ panic("read error from temp file");
+}
--- /dev/null
+++ b/sam/error.c
@@ -1,0 +1,144 @@
+#include "sam.h"
+
+static char *emsg[]={
+ /* error_s */
+ "can't open",
+ "can't create",
+ "not in menu:",
+ "changes to",
+ "I/O error:",
+ "can't write while changing:",
+ /* error_c */
+ "unknown command",
+ "no operand for",
+ "bad delimiter",
+ /* error */
+ "can't fork",
+ "interrupt",
+ "address",
+ "search",
+ "pattern",
+ "newline expected",
+ "blank expected",
+ "pattern expected",
+ "can't nest X or Y",
+ "unmatched `}'",
+ "command takes no address",
+ "addresses overlap",
+ "substitution",
+ "& match too long",
+ "bad \\ in rhs",
+ "address range",
+ "changes not in sequence",
+ "addresses out of order",
+ "no file name",
+ "unmatched `('",
+ "unmatched `)'",
+ "malformed `[]'",
+ "malformed regexp",
+ "reg. exp. list overflow",
+ "plan 9 command",
+ "can't pipe",
+ "no current file",
+ "string too long",
+ "changed files",
+ "empty string",
+ "file search",
+ "non-unique match for \"\"",
+ "tag match too long",
+ "too many subexpressions",
+ "temporary file too large",
+ "file is append-only",
+ "no destination for plumb message",
+ "internal read error in buffer load",
+};
+static char *wmsg[]={
+ /* warn_s */
+ "duplicate file name",
+ "no such file",
+ "write might change good version of",
+ /* warn_S */
+ "files might be aliased",
+ /* warn */
+ "null characters elided",
+ "can't run pwd",
+ "last char not newline",
+ "exit status",
+};
+
+void
+error(Err s)
+{
+ char buf[512];
+
+ sprint(buf, "?%s", emsg[s]);
+ hiccough(buf);
+}
+
+void
+error_s(Err s, char *a)
+{
+ char buf[512];
+
+ sprint(buf, "?%s \"%s\"", emsg[s], a);
+ hiccough(buf);
+}
+
+void
+error_r(Err s, char *a)
+{
+ char buf[512];
+
+ sprint(buf, "?%s \"%s\": %r", emsg[s], a);
+ hiccough(buf);
+}
+
+void
+error_c(Err s, int c)
+{
+ char buf[512];
+
+ sprint(buf, "?%s `%C'", emsg[s], c);
+ hiccough(buf);
+}
+
+void
+warn(Warn s)
+{
+ dprint("?warning: %s\n", wmsg[s]);
+}
+
+void
+warn_S(Warn s, String *a)
+{
+ print_s(wmsg[s], a);
+}
+
+void
+warn_SS(Warn s, String *a, String *b)
+{
+ print_ss(wmsg[s], a, b);
+}
+
+void
+warn_s(Warn s, char *a)
+{
+ dprint("?warning: %s `%s'\n", wmsg[s], a);
+}
+
+void
+termwrite(char *s)
+{
+ String *p;
+
+ if(downloaded){
+ p = tmpcstr(s);
+ if(cmd)
+ loginsert(cmd, cmdpt, p->s, p->n);
+ else
+ Strinsert(&cmdstr, p, cmdstr.n);
+ cmdptadv += p->n;
+ freetmpstr(p);
+ }else
+ Write(2, s, strlen(s));
+}
--- /dev/null
+++ b/sam/errors.h
@@ -1,0 +1,65 @@
+typedef enum Err{
+ /* error_s */
+ Eopen,
+ Ecreate,
+ Emenu,
+ Emodified,
+ Eio,
+ Ewseq,
+ /* error_c */
+ Eunk,
+ Emissop,
+ Edelim,
+ /* error */
+ Efork,
+ Eintr,
+ Eaddress,
+ Esearch,
+ Epattern,
+ Enewline,
+ Eblank,
+ Enopattern,
+ EnestXY,
+ Enolbrace,
+ Enoaddr,
+ Eoverlap,
+ Enosub,
+ Elongrhs,
+ Ebadrhs,
+ Erange,
+ Esequence,
+ Eorder,
+ Enoname,
+ Eleftpar,
+ Erightpar,
+ Ebadclass,
+ Ebadregexp,
+ Eoverflow,
+ Enocmd,
+ Epipe,
+ Enofile,
+ Etoolong,
+ Echanges,
+ Eempty,
+ Efsearch,
+ Emanyfiles,
+ Elongtag,
+ Esubexp,
+ Etmpovfl,
+ Eappend,
+ Ecantplumb,
+ Ebufload,
+}Err;
+typedef enum Warn{
+ /* warn_s */
+ Wdupname,
+ Wfile,
+ Wdate,
+ /* warn_ss */
+ Wdupfile,
+ /* warn */
+ Wnulls,
+ Wpwd,
+ Wnotnewline,
+ Wbadstatus,
+}Warn;
--- /dev/null
+++ b/sam/file.c
@@ -1,0 +1,610 @@
+#include "sam.h"
+
+/*
+ * Structure of Undo list:
+ * The Undo structure follows any associated data, so the list
+ * can be read backwards: read the structure, then read whatever
+ * data is associated (insert string, file name) and precedes it.
+ * The structure includes the previous value of the modify bit
+ * and a sequence number; successive Undo structures with the
+ * same sequence number represent simultaneous changes.
+ */
+
+typedef struct Undo Undo;
+typedef struct Merge Merge;
+
+struct Undo
+{
+ short type; /* Delete, Insert, Filename, Dot, Mark */
+ short mod; /* modify bit */
+ uint seq; /* sequence number */
+ uint p0; /* location of change (unused in f) */
+ uint n; /* # runes in string or file name */
+};
+
+struct Merge
+{
+ File *f;
+ uint seq; /* of logged change */
+ uint p0; /* location of change (unused in f) */
+ uint n; /* # runes to delete */
+ uint nbuf; /* # runes to insert */
+ Rune buf[RBUFSIZE];
+};
+
+enum
+{
+ Maxmerge = 50,
+ Undosize = sizeof(Undo)/sizeof(Rune),
+};
+
+static Merge merge;
+
+File*
+fileopen(void)
+{
+ File *f;
+
+ f = emalloc(sizeof(File));
+ f->dot.f = f;
+ f->ndot.f = f;
+ f->seq = 0;
+ f->mod = FALSE;
+ f->unread = TRUE;
+ Strinit0(&f->name);
+ return f;
+}
+
+int
+fileisdirty(File *f)
+{
+ return f->seq != f->cleanseq;
+}
+
+static void
+wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns)
+{
+ Undo u;
+
+ u.type = Insert;
+ u.mod = mod;
+ u.seq = seq;
+ u.p0 = p0;
+ u.n = ns;
+ bufinsert(delta, delta->nc, s, ns);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+static void
+wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1)
+{
+ Undo u;
+
+ u.type = Delete;
+ u.mod = mod;
+ u.seq = seq;
+ u.p0 = p0;
+ u.n = p1 - p0;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+flushmerge(void)
+{
+ File *f;
+
+ f = merge.f;
+ if(f == nil)
+ return;
+ if(merge.seq != f->seq)
+ panic("flushmerge seq mismatch");
+ if(merge.n != 0)
+ wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n);
+ if(merge.nbuf != 0)
+ wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf);
+ merge.f = nil;
+ merge.n = 0;
+ merge.nbuf = 0;
+}
+
+void
+mergeextend(File *f, uint p0)
+{
+ uint mp0n;
+
+ mp0n = merge.p0+merge.n;
+ if(mp0n != p0){
+ bufread(f, mp0n, merge.buf+merge.nbuf, p0-mp0n);
+ merge.nbuf += p0-mp0n;
+ merge.n = p0-merge.p0;
+ }
+}
+
+/*
+ * like fileundelete, but get the data from arguments
+ */
+void
+loginsert(File *f, uint p0, Rune *s, uint ns)
+{
+ if(f->rescuing)
+ return;
+ if(ns == 0)
+ return;
+ if(ns>STRSIZE)
+ panic("loginsert");
+ if(f->seq < seq)
+ filemark(f);
+ if(p0 < f->hiposn)
+ error(Esequence);
+
+ if(merge.f != f
+ || p0-(merge.p0+merge.n)>Maxmerge /* too far */
+ || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>=RBUFSIZE) /* too long */
+ flushmerge();
+
+ if(ns>=RBUFSIZE){
+ if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil))
+ panic("loginsert bad merge state");
+ wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns);
+ }else{
+ if(merge.f != f){
+ merge.f = f;
+ merge.p0 = p0;
+ merge.seq = f->seq;
+ }
+ mergeextend(f, p0);
+
+ /* append string to merge */
+ runemove(merge.buf+merge.nbuf, s, ns);
+ merge.nbuf += ns;
+ }
+
+ f->hiposn = p0;
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+void
+logdelete(File *f, uint p0, uint p1)
+{
+ if(f->rescuing)
+ return;
+ if(p0 == p1)
+ return;
+ if(f->seq < seq)
+ filemark(f);
+ if(p0 < f->hiposn)
+ error(Esequence);
+
+ if(merge.f != f
+ || p0-(merge.p0+merge.n)>Maxmerge /* too far */
+ || merge.nbuf+(p0-(merge.p0+merge.n))>=RBUFSIZE){ /* too long */
+ flushmerge();
+ merge.f = f;
+ merge.p0 = p0;
+ merge.seq = f->seq;
+ }
+
+ mergeextend(f, p0);
+
+ /* add to deletion */
+ merge.n = p1-merge.p0;
+
+ f->hiposn = p1;
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+/*
+ * like fileunsetname, but get the data from arguments
+ */
+void
+logsetname(File *f, String *s)
+{
+ Undo u;
+ Buffer *delta;
+
+ if(f->rescuing)
+ return;
+
+ if(f->unread){ /* This is setting initial file name */
+ filesetname(f, s);
+ return;
+ }
+
+ if(f->seq < seq)
+ filemark(f);
+
+ /* undo a file name change by restoring old name */
+ delta = &f->epsilon;
+ u.type = Filename;
+ u.mod = TRUE;
+ u.seq = f->seq;
+ u.p0 = 0; /* unused */
+ u.n = s->n;
+ if(s->n)
+ bufinsert(delta, delta->nc, s->s, s->n);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+#ifdef NOTEXT
+File*
+fileaddtext(File *f, Text *t)
+{
+ if(f == nil){
+ f = emalloc(sizeof(File));
+ f->unread = TRUE;
+ }
+ f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
+ f->text[f->ntext++] = t;
+ f->curtext = t;
+ return f;
+}
+
+void
+filedeltext(File *f, Text *t)
+{
+ int i;
+
+ for(i=0; i<f->ntext; i++)
+ if(f->text[i] == t)
+ goto Found;
+ panic("can't find text in filedeltext");
+
+ Found:
+ f->ntext--;
+ if(f->ntext == 0){
+ fileclose(f);
+ return;
+ }
+ memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
+ if(f->curtext == t)
+ f->curtext = f->text[0];
+}
+#endif
+
+void
+fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
+{
+ Undo u;
+
+ /* undo an insertion by deleting */
+ u.type = Delete;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = p0;
+ u.n = ns;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+fileundelete(File *f, Buffer *delta, uint p0, uint p1)
+{
+ Undo u;
+ Rune *buf;
+ uint i, n;
+
+ /* undo a deletion by inserting */
+ u.type = Insert;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = p0;
+ u.n = p1-p0;
+ buf = fbufalloc();
+ for(i=p0; i<p1; i+=n){
+ n = p1 - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(f, i, buf, n);
+ bufinsert(delta, delta->nc, buf, n);
+ }
+ fbuffree(buf);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+
+}
+
+int
+filereadc(File *f, uint q)
+{
+ Rune r;
+
+ if(q >= f->nc)
+ return -1;
+ bufread(f, q, &r, 1);
+ return r;
+}
+
+void
+filesetname(File *f, String *s)
+{
+ if(!f->unread) /* This is setting initial file name */
+ fileunsetname(f, &f->delta);
+ Strduplstr(&f->name, s);
+ sortname(f);
+ f->unread = TRUE;
+}
+
+void
+fileunsetname(File *f, Buffer *delta)
+{
+ String s;
+ Undo u;
+
+ /* undo a file name change by restoring old name */
+ u.type = Filename;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = 0; /* unused */
+ Strinit(&s);
+ Strduplstr(&s, &f->name);
+ fullname(&s);
+ u.n = s.n;
+ if(s.n)
+ bufinsert(delta, delta->nc, s.s, s.n);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+ Strclose(&s);
+}
+
+void
+fileunsetdot(File *f, Buffer *delta, Range dot)
+{
+ Undo u;
+
+ u.type = Dot;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = dot.p1;
+ u.n = dot.p2 - dot.p1;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+fileunsetmark(File *f, Buffer *delta, Range mark)
+{
+ Undo u;
+
+ u.type = Mark;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = mark.p1;
+ u.n = mark.p2 - mark.p1;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+uint
+fileload(File *f, uint p0, int fd, int *nulls)
+{
+ if(f->seq > 0)
+ panic("undo in file.load unimplemented");
+ return bufload(f, p0, fd, nulls);
+}
+
+int
+fileupdate(File *f, int notrans, int toterm)
+{
+ uint p1, p2;
+ int mod;
+
+ if(f->rescuing)
+ return FALSE;
+
+ flushmerge();
+
+ /*
+ * fix the modification bit
+ * subtle point: don't save it away in the log.
+ *
+ * if another change is made, the correct f->mod
+ * state is saved in the undo log by filemark
+ * when setting the dot and mark.
+ *
+ * if the change is undone, the correct state is
+ * saved from f in the fileun... routines.
+ */
+ mod = f->mod;
+ f->mod = f->prevmod;
+ if(f == cmd)
+ notrans = TRUE;
+ else{
+ fileunsetdot(f, &f->delta, f->prevdot);
+ fileunsetmark(f, &f->delta, f->prevmark);
+ }
+ f->dot = f->ndot;
+ fileundo(f, FALSE, !notrans, &p1, &p2, toterm);
+ f->mod = mod;
+
+ if(f->delta.nc == 0)
+ f->seq = 0;
+
+ if(f == cmd)
+ return FALSE;
+
+ if(f->mod){
+ f->closeok = 0;
+ quitok = 0;
+ }else
+ f->closeok = 1;
+ return TRUE;
+}
+
+long
+prevseq(Buffer *b)
+{
+ Undo u;
+ uint up;
+
+ up = b->nc;
+ if(up == 0)
+ return 0;
+ up -= Undosize;
+ bufread(b, up, (Rune*)&u, Undosize);
+ return u.seq;
+}
+
+long
+undoseq(File *f, int isundo)
+{
+ if(isundo)
+ return f->seq;
+
+ return prevseq(&f->epsilon);
+}
+
+void
+fileundo(File *f, int isundo, int canredo, uint *q0p, uint *q1p, int flag)
+{
+ Undo u;
+ Rune *buf;
+ uint i, n, up;
+ uint stop;
+ Buffer *delta, *epsilon;
+
+ if(isundo){
+ /* undo; reverse delta onto epsilon, seq decreases */
+ delta = &f->delta;
+ epsilon = &f->epsilon;
+ stop = f->seq;
+ }else{
+ /* redo; reverse epsilon onto delta, seq increases */
+ delta = &f->epsilon;
+ epsilon = &f->delta;
+ stop = 0; /* don't know yet */
+ }
+
+ raspstart(f);
+ while(delta->nc > 0){
+ /* rasp and buffer are in sync; sync with wire if needed */
+ if(needoutflush())
+ raspflush(f);
+ up = delta->nc-Undosize;
+ bufread(delta, up, (Rune*)&u, Undosize);
+ if(isundo){
+ if(u.seq < stop){
+ f->seq = u.seq;
+ raspdone(f, flag);
+ return;
+ }
+ }else{
+ if(stop == 0)
+ stop = u.seq;
+ if(u.seq > stop){
+ raspdone(f, flag);
+ return;
+ }
+ }
+ switch(u.type){
+ default:
+ panic("undo unknown u.type");
+ break;
+
+ case Delete:
+ f->seq = u.seq;
+ if(canredo)
+ fileundelete(f, epsilon, u.p0, u.p0+u.n);
+ f->mod = u.mod;
+ bufdelete(f, u.p0, u.p0+u.n);
+ raspdelete(f, u.p0, u.p0+u.n, flag);
+ *q0p = u.p0;
+ *q1p = u.p0;
+ break;
+
+ case Insert:
+ f->seq = u.seq;
+ if(canredo)
+ fileuninsert(f, epsilon, u.p0, u.n);
+ f->mod = u.mod;
+ up -= u.n;
+ buf = fbufalloc();
+ for(i=0; i<u.n; i+=n){
+ n = u.n - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(delta, up+i, buf, n);
+ bufinsert(f, u.p0+i, buf, n);
+ raspinsert(f, u.p0+i, buf, n, flag);
+ }
+ fbuffree(buf);
+ *q0p = u.p0;
+ *q1p = u.p0+u.n;
+ break;
+
+ case Filename:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetname(f, epsilon);
+ f->mod = u.mod;
+ up -= u.n;
+
+ Strinsure(&f->name, u.n+1);
+ bufread(delta, up, f->name.s, u.n);
+ f->name.s[u.n] = 0;
+ f->name.n = u.n;
+ fixname(&f->name);
+ sortname(f);
+ break;
+ case Dot:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetdot(f, epsilon, f->dot.r);
+ f->mod = u.mod;
+ f->dot.r.p1 = u.p0;
+ f->dot.r.p2 = u.p0 + u.n;
+ break;
+ case Mark:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetmark(f, epsilon, f->mark);
+ f->mod = u.mod;
+ f->mark.p1 = u.p0;
+ f->mark.p2 = u.p0 + u.n;
+ break;
+ }
+ bufdelete(delta, up, delta->nc);
+ }
+ if(isundo)
+ f->seq = 0;
+ raspdone(f, flag);
+}
+
+void
+filereset(File *f)
+{
+ bufreset(&f->delta);
+ bufreset(&f->epsilon);
+ f->seq = 0;
+}
+
+void
+fileclose(File *f)
+{
+ Strclose(&f->name);
+ bufclose(f);
+ bufclose(&f->delta);
+ bufclose(&f->epsilon);
+ if(f->rasp)
+ listfree(f->rasp);
+ free(f);
+}
+
+void
+filemark(File *f)
+{
+
+ if(f->unread)
+ return;
+ if(f->epsilon.nc)
+ bufdelete(&f->epsilon, 0, f->epsilon.nc);
+
+ if(f != cmd){
+ f->prevdot = f->dot.r;
+ f->prevmark = f->mark;
+ f->prevseq = f->seq;
+ f->prevmod = f->mod;
+ }
+
+ f->ndot = f->dot;
+ f->seq = seq;
+ f->hiposn = 0;
+}
--- /dev/null
+++ b/sam/io.c
@@ -1,0 +1,283 @@
+#include "sam.h"
+
+#define NSYSFILE 3
+#define NOFILE 128
+
+void
+checkqid(File *f)
+{
+ int i, w;
+ File *g;
+
+ w = whichmenu(f);
+ for(i=1; i<file.nused; i++){
+ g = file.filepptr[i];
+ if(w == i)
+ continue;
+ if(f->dev==g->dev && f->qidpath==g->qidpath)
+ warn_SS(Wdupfile, &f->name, &g->name);
+ }
+}
+
+void
+writef(File *f)
+{
+ Posn n;
+ char *name;
+ int i, samename, newfile;
+ ulong dev;
+ uvlong qid;
+ long mtime, appendonly, length;
+
+ newfile = 0;
+ samename = Strcmp(&genstr, &f->name) == 0;
+ name = Strtoc(&f->name);
+ i = statfile(name, &dev, &qid, &mtime, 0, 0);
+ if(i == -1)
+ newfile++;
+ else if(samename &&
+ (f->dev!=dev || f->qidpath!=qid || f->mtime<mtime)){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ warn_S(Wdate, &genstr);
+ free(name);
+ return;
+ }
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ if((io=create(genc, 1, 0666L)) < 0)
+ error_r(Ecreate, genc);
+ dprint("%s: ", genc);
+ if(statfd(io, 0, 0, 0, &length, &appendonly) > 0 && appendonly && length>0){
+ free(name);
+ error(Eappend);
+ }
+ n = writeio(f);
+ if(f->name.s[0]==0 || samename){
+ if(addr.r.p1==0 && addr.r.p2==f->nc)
+ f->cleanseq = f->seq;
+ state(f, f->cleanseq==f->seq? Clean : Dirty);
+ }
+ if(newfile)
+ dprint("(new file) ");
+ if(addr.r.p2>0 && filereadc(f, addr.r.p2-1)!='\n')
+ warn(Wnotnewline);
+ closeio(n);
+ if(f->name.s[0]==0 || samename){
+ if(statfile(genc, &dev, &qid, &mtime, 0, 0) > 0){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ checkqid(f);
+ }
+ }
+ free(name);
+}
+
+Posn
+readio(File *f, int *nulls, int setdate, int toterm)
+{
+ int n, b, w;
+ Rune *r;
+ Posn nt;
+ Posn p = addr.r.p2;
+ ulong dev;
+ uvlong qid;
+ long mtime;
+ char buf[BLOCKSIZE+1], *s;
+
+ *nulls = FALSE;
+ b = 0;
+ if(f->unread){
+ nt = bufload(f, 0, io, nulls);
+ if(toterm)
+ raspload(f);
+ }else
+ for(nt = 0; (n = read(io, buf+b, BLOCKSIZE-b))>0; nt+=(r-genbuf)){
+ n += b;
+ b = 0;
+ r = genbuf;
+ s = buf;
+ while(n > 0){
+ if((*r = *(uchar*)s) < Runeself){
+ if(*r)
+ r++;
+ else
+ *nulls = TRUE;
+ --n;
+ s++;
+ continue;
+ }
+ if(fullrune(s, n)){
+ w = chartorune(r, s);
+ if(*r)
+ r++;
+ else
+ *nulls = TRUE;
+ n -= w;
+ s += w;
+ continue;
+ }
+ b = n;
+ memmove(buf, s, b);
+ break;
+ }
+ loginsert(f, p, genbuf, r-genbuf);
+ }
+ if(b)
+ *nulls = TRUE;
+ if(*nulls)
+ warn(Wnulls);
+ if(setdate){
+ if(statfd(io, &dev, &qid, &mtime, 0, 0) > 0){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ checkqid(f);
+ }
+ }
+ return nt;
+}
+
+Posn
+writeio(File *f)
+{
+ int m, n;
+ Posn p = addr.r.p1;
+ char *c;
+
+ while(p < addr.r.p2){
+ if(addr.r.p2-p>BLOCKSIZE)
+ n = BLOCKSIZE;
+ else
+ n = addr.r.p2-p;
+ bufread(f, p, genbuf, n);
+ c = Strtoc(tmprstr(genbuf, n));
+ m = strlen(c);
+ if(Write(io, c, m) != m){
+ free(c);
+ if(p > 0)
+ p += n;
+ break;
+ }
+ free(c);
+ p += n;
+ }
+ return p-addr.r.p1;
+}
+void
+closeio(Posn p)
+{
+ close(io);
+ io = 0;
+ if(p >= 0)
+ dprint("#%lud\n", p);
+}
+
+int remotefd0 = 0;
+int remotefd1 = 1;
+
+void
+bootterm(char *machine, char **argv)
+{
+ int ph2t[2], pt2h[2];
+
+ if(machine){
+ dup(remotefd0, 0);
+ dup(remotefd1, 1);
+ close(remotefd0);
+ close(remotefd1);
+ argv[0] = "samterm";
+ exec(samterm, argv);
+ fprint(2, "can't exec: ");
+ perror(samterm);
+ _exits("damn");
+ }
+ if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
+ panic("pipe");
+ switch(fork()){
+ case 0:
+ dup(ph2t[0], 0);
+ dup(pt2h[1], 1);
+ close(ph2t[0]);
+ close(ph2t[1]);
+ close(pt2h[0]);
+ close(pt2h[1]);
+ argv[0] = "samterm";
+ exec(samterm, argv);
+ fprint(2, "can't exec: ");
+ perror(samterm);
+ _exits("damn");
+ case -1:
+ panic("can't fork samterm");
+ }
+ dup(pt2h[0], 0);
+ dup(ph2t[1], 1);
+ close(ph2t[0]);
+ close(ph2t[1]);
+ close(pt2h[0]);
+ close(pt2h[1]);
+}
+
+void
+connectto(char *machine, char **argv)
+{
+ int p1[2], p2[2];
+ char **av;
+ int ac;
+
+ // count args
+ for(av = argv; *av; av++)
+ ;
+ av = malloc(sizeof(char*)*((av-argv) + 5));
+ if(av == nil){
+ dprint("out of memory\n");
+ exits("fork/exec");
+ }
+ ac = 0;
+ av[ac++] = RX;
+ av[ac++] = machine;
+ av[ac++] = rsamname;
+ av[ac++] = "-R";
+ while(*argv)
+ av[ac++] = *argv++;
+ av[ac] = 0;
+ if(pipe(p1)<0 || pipe(p2)<0){
+ dprint("can't pipe\n");
+ exits("pipe");
+ }
+ remotefd0 = p1[0];
+ remotefd1 = p2[1];
+ switch(fork()){
+ case 0:
+ dup(p2[0], 0);
+ dup(p1[1], 1);
+ close(p1[0]);
+ close(p1[1]);
+ close(p2[0]);
+ close(p2[1]);
+ exec(RXPATH, av);
+ dprint("can't exec %s\n", RXPATH);
+ exits("exec");
+
+ case -1:
+ dprint("can't fork\n");
+ exits("fork");
+ }
+ free(av);
+ close(p1[1]);
+ close(p2[0]);
+}
+
+void
+startup(char *machine, int Rflag, char **argv, char **files)
+{
+ if(machine)
+ connectto(machine, files);
+ if(!Rflag)
+ bootterm(machine, argv);
+ downloaded = 1;
+ outTs(Hversion, VERSION);
+}
--- /dev/null
+++ b/sam/list.c
@@ -1,0 +1,96 @@
+#include "sam.h"
+
+/*
+ * Check that list has room for one more element.
+ */
+static void
+growlist(List *l, int esize)
+{
+ uchar *p;
+
+ if(l->listptr == nil || l->nalloc == 0){
+ l->nalloc = INCR;
+ l->listptr = emalloc(INCR*esize);
+ l->nused = 0;
+ }
+ else if(l->nused == l->nalloc){
+ p = erealloc(l->listptr, (l->nalloc+INCR)*esize);
+ l->listptr = p;
+ memset(p+l->nalloc*esize, 0, INCR*esize);
+ l->nalloc += INCR;
+ }
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+ Posn *pp;
+ void **vpp;
+
+ l->nused--;
+
+ switch(l->type){
+ case 'P':
+ pp = l->posnptr+i;
+ memmove(pp, pp+1, (l->nused-i)*sizeof(*pp));
+ break;
+ case 'p':
+ vpp = l->voidpptr+i;
+ memmove(vpp, vpp+1, (l->nused-i)*sizeof(*vpp));
+ break;
+ }
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, ...)
+{
+ Posn *pp;
+ void **vpp;
+ va_list list;
+
+
+ va_start(list, i);
+ switch(l->type){
+ case 'P':
+ growlist(l, sizeof(*pp));
+ pp = l->posnptr+i;
+ memmove(pp+1, pp, (l->nused-i)*sizeof(*pp));
+ *pp = va_arg(list, Posn);
+ break;
+ case 'p':
+ growlist(l, sizeof(*vpp));
+ vpp = l->voidpptr+i;
+ memmove(vpp+1, vpp, (l->nused-i)*sizeof(*vpp));
+ *vpp = va_arg(list, void*);
+ break;
+ }
+ va_end(list);
+
+ l->nused++;
+}
+
+void
+listfree(List *l)
+{
+ free(l->listptr);
+ free(l);
+}
+
+List*
+listalloc(int type)
+{
+ List *l;
+
+ l = emalloc(sizeof(List));
+ l->type = type;
+ l->nalloc = 0;
+ l->nused = 0;
+
+ return l;
+}
--- /dev/null
+++ b/sam/mesg.c
@@ -1,0 +1,871 @@
+#include "sam.h"
+
+Header h;
+uchar indata[DATASIZE];
+uchar outdata[2*DATASIZE+3]; /* room for overflow message */
+uchar *inp;
+uchar *outp;
+uchar *outmsg = outdata;
+Posn cmdpt;
+Posn cmdptadv;
+Buffer snarfbuf;
+int waitack;
+int outbuffered;
+int tversion;
+
+int inshort(void);
+long inlong(void);
+vlong invlong(void);
+int inmesg(Tmesg);
+
+void outshort(int);
+void outlong(long);
+void outvlong(vlong);
+void outcopy(int, void*);
+void outsend(void);
+void outstart(Hmesg);
+
+void setgenstr(File*, Posn, Posn);
+
+#ifdef DEBUG
+char *hname[] = {
+ [Hversion] "Hversion",
+ [Hbindname] "Hbindname",
+ [Hcurrent] "Hcurrent",
+ [Hnewname] "Hnewname",
+ [Hmovname] "Hmovname",
+ [Hgrow] "Hgrow",
+ [Hcheck0] "Hcheck0",
+ [Hcheck] "Hcheck",
+ [Hunlock] "Hunlock",
+ [Hdata] "Hdata",
+ [Horigin] "Horigin",
+ [Hunlockfile] "Hunlockfile",
+ [Hsetdot] "Hsetdot",
+ [Hgrowdata] "Hgrowdata",
+ [Hmoveto] "Hmoveto",
+ [Hclean] "Hclean",
+ [Hdirty] "Hdirty",
+ [Hcut] "Hcut",
+ [Hsetpat] "Hsetpat",
+ [Hdelname] "Hdelname",
+ [Hclose] "Hclose",
+ [Hsetsnarf] "Hsetsnarf",
+ [Hsnarflen] "Hsnarflen",
+ [Hack] "Hack",
+ [Hexit] "Hexit",
+ [Hplumb] "Hplumb",
+ [Hmenucmd] "Hmenucmd",
+};
+
+char *tname[] = {
+ [Tversion] "Tversion",
+ [Tstartcmdfile] "Tstartcmdfile",
+ [Tcheck] "Tcheck",
+ [Trequest] "Trequest",
+ [Torigin] "Torigin",
+ [Tstartfile] "Tstartfile",
+ [Tworkfile] "Tworkfile",
+ [Ttype] "Ttype",
+ [Tcut] "Tcut",
+ [Tpaste] "Tpaste",
+ [Tsnarf] "Tsnarf",
+ [Tstartnewfile] "Tstartnewfile",
+ [Twrite] "Twrite",
+ [Tclose] "Tclose",
+ [Tlook] "Tlook",
+ [Tsearch] "Tsearch",
+ [Tsend] "Tsend",
+ [Tdclick] "Tdclick",
+ [Ttclick] "Ttclick",
+ [Tstartsnarf] "Tstartsnarf",
+ [Tsetsnarf] "Tsetsnarf",
+ [Tack] "Tack",
+ [Texit] "Texit",
+ [Tplumb] "Tplumb",
+ [Tmenucmd] "Tmenucmd",
+ [Tmenucmdsend] "Tmenucmdsend",
+};
+
+void
+journal(int out, char *s)
+{
+ static int fd = -1;
+
+ if(fd < 0)
+ fd = create("/tmp/sam.out", 1, 0666L);
+ if(fd >= 0)
+ fprint(fd, "%s%s\n", out? "out: " : "in: ", s);
+}
+
+void
+journaln(int out, long n)
+{
+ char buf[32];
+
+ snprint(buf, sizeof(buf), "%ld", n);
+ journal(out, buf);
+}
+
+void
+journalv(int out, vlong v)
+{
+ char buf[32];
+
+ sprint(buf, sizeof(buf), "%lld", v);
+ journal(out, buf);
+}
+#else
+#define journal(a, b)
+#define journaln(a, b)
+#define journalv(a, b)
+#endif
+
+int
+rcvchar(void){
+ static uchar buf[64];
+ static i, nleft = 0;
+
+ if(nleft <= 0){
+ nleft = read(0, (char *)buf, sizeof buf);
+ if(nleft <= 0)
+ return -1;
+ i = 0;
+ }
+ --nleft;
+ return buf[i++];
+}
+
+int
+rcv(void){
+ int c;
+ static state = 0;
+ static count = 0;
+ static i = 0;
+
+ while((c=rcvchar()) != -1)
+ switch(state){
+ case 0:
+ h.type = c;
+ state++;
+ break;
+
+ case 1:
+ h.count0 = c;
+ state++;
+ break;
+
+ case 2:
+ h.count1 = c;
+ count = h.count0|(h.count1<<8);
+ i = 0;
+ if(count > DATASIZE)
+ panic("count>DATASIZE");
+ if(count == 0)
+ goto zerocount;
+ state++;
+ break;
+
+ case 3:
+ indata[i++] = c;
+ if(i == count){
+ zerocount:
+ indata[i] = 0;
+ state = count = 0;
+ return inmesg(h.type);
+ }
+ break;
+ }
+ return 0;
+}
+
+File *
+whichfile(int tag)
+{
+ int i;
+
+ for(i = 0; i<file.nused; i++)
+ if(file.filepptr[i]->tag==tag)
+ return file.filepptr[i];
+ hiccough((char *)0);
+ return 0;
+}
+
+int
+inmesg(Tmesg type)
+{
+ Rune buf[1025];
+ char cbuf[64];
+ int i, m;
+ short s;
+ long l, l1;
+ vlong v;
+ File *f;
+ Posn p0, p1, p;
+ Range r;
+ String *str;
+ char *c, *wdir;
+ Rune *rp;
+ Plumbmsg *pm;
+
+ if(type > TMAX)
+ panic("inmesg");
+
+ journal(0, tname[type]);
+
+ inp = indata;
+ switch(type){
+ case -1:
+ panic("rcv error");
+
+ default:
+ fprint(2, "unknown type %d\n", type);
+ panic("rcv unknown");
+
+ case Tversion:
+ tversion = inshort();
+ journaln(0, tversion);
+ break;
+
+ case Tstartcmdfile:
+ v = invlong(); /* for 64-bit pointers */
+ journalv(0, v);
+ Strdupl(&genstr, samname);
+ cmd = newfile();
+ cmd->unread = 0;
+ outTsv(Hbindname, cmd->tag, v);
+ outTs(Hcurrent, cmd->tag);
+ logsetname(cmd, &genstr);
+ cmd->rasp = listalloc('P');
+ cmd->mod = 0;
+ if(cmdstr.n){
+ loginsert(cmd, 0L, cmdstr.s, cmdstr.n);
+ Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
+ }
+ fileupdate(cmd, FALSE, TRUE);
+ outT0(Hunlock);
+ break;
+
+ case Tcheck:
+ /* go through whichfile to check the tag */
+ outTs(Hcheck, whichfile(inshort())->tag);
+ break;
+
+ case Trequest:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = p0+inshort();
+ journaln(0, p0);
+ journaln(0, p1-p0);
+ if(f->unread)
+ panic("Trequest: unread");
+ if(p1>f->nc)
+ p1 = f->nc;
+ if(p0>f->nc) /* can happen e.g. scrolling during command */
+ p0 = f->nc;
+ if(p0 == p1){
+ i = 0;
+ r.p1 = r.p2 = p0;
+ }else{
+ r = rdata(f->rasp, p0, p1-p0);
+ i = r.p2-r.p1;
+ bufread(f, r.p1, buf, i);
+ }
+ buf[i]=0;
+ outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
+ break;
+
+ case Torigin:
+ s = inshort();
+ l = inlong();
+ l1 = inlong();
+ journaln(0, l1);
+ lookorigin(whichfile(s), l, l1);
+ break;
+
+ case Tstartfile:
+ termlocked++;
+ f = whichfile(inshort());
+ if(!f->rasp) /* this might be a duplicate message */
+ f->rasp = listalloc('P');
+ current(f);
+ outTsv(Hbindname, f->tag, invlong()); /* for 64-bit pointers */
+ outTs(Hcurrent, f->tag);
+ journaln(0, f->tag);
+ if(f->unread)
+ load(f);
+ else{
+ if(f->nc>0){
+ rgrow(f->rasp, 0L, f->nc);
+ outTsll(Hgrow, f->tag, 0L, f->nc);
+ }
+ outTs(Hcheck0, f->tag);
+ moveto(f, f->dot.r);
+ }
+ break;
+
+ case Tworkfile:
+ i = inshort();
+ f = whichfile(i);
+ current(f);
+ f->dot.r.p1 = inlong();
+ f->dot.r.p2 = inlong();
+ f->tdot = f->dot.r;
+ journaln(0, i);
+ journaln(0, f->dot.r.p1);
+ journaln(0, f->dot.r.p2);
+ break;
+
+ case Ttype:
+ f = whichfile(inshort());
+ p0 = inlong();
+ journaln(0, p0);
+ journal(0, (char*)inp);
+ str = tmpcstr((char*)inp);
+ i = str->n;
+ loginsert(f, p0, str->s, str->n);
+ if(fileupdate(f, FALSE, FALSE))
+ seq++;
+ if(f==cmd && p0==f->nc-i && i>0 && str->s[i-1]=='\n'){
+ freetmpstr(str);
+ termlocked++;
+ termcommand();
+ }else
+ freetmpstr(str);
+ f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
+ f->tdot = f->dot.r;
+ break;
+
+ case Tcut:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = inlong();
+ journaln(0, p0);
+ journaln(0, p1);
+ logdelete(f, p0, p1);
+ if(fileupdate(f, FALSE, FALSE))
+ seq++;
+ f->dot.r.p1 = f->dot.r.p2 = p0;
+ f->tdot = f->dot.r; /* terminal knows the value of dot already */
+ break;
+
+ case Tpaste:
+ f = whichfile(inshort());
+ p0 = inlong();
+ journaln(0, p0);
+ for(l=0; l<snarfbuf.nc; l+=m){
+ m = snarfbuf.nc-l;
+ if(m>BLOCKSIZE)
+ m = BLOCKSIZE;
+ bufread(&snarfbuf, l, genbuf, m);
+ loginsert(f, p0, tmprstr(genbuf, m)->s, m);
+ }
+ if(fileupdate(f, FALSE, TRUE))
+ seq++;
+ f->dot.r.p1 = p0;
+ f->dot.r.p2 = p0+snarfbuf.nc;
+ f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
+ telldot(f);
+ outTs(Hunlockfile, f->tag);
+ break;
+
+ case Tsnarf:
+ i = inshort();
+ p0 = inlong();
+ p1 = inlong();
+ snarf(whichfile(i), p0, p1, &snarfbuf, 0);
+ break;
+
+ case Tstartnewfile:
+ v = invlong();
+ Strdupl(&genstr, empty);
+ f = newfile();
+ f->rasp = listalloc('P');
+ outTsv(Hbindname, f->tag, v);
+ logsetname(f, &genstr);
+ outTs(Hcurrent, f->tag);
+ current(f);
+ load(f);
+ break;
+
+ case Twrite:
+ termlocked++;
+ i = inshort();
+ journaln(0, i);
+ f = whichfile(i);
+ addr.r.p1 = 0;
+ addr.r.p2 = f->nc;
+ if(f->name.s[0] == 0)
+ error(Enoname);
+ Strduplstr(&genstr, &f->name);
+ writef(f);
+ break;
+
+ case Tclose:
+ termlocked++;
+ i = inshort();
+ journaln(0, i);
+ f = whichfile(i);
+ current(f);
+ trytoclose(f);
+ /* if trytoclose fails, will error out */
+ delete(f);
+ break;
+
+ case Tlook:
+ f = whichfile(inshort());
+ termlocked++;
+ p0 = inlong();
+ p1 = inlong();
+ journaln(0, p0);
+ journaln(0, p1);
+ setgenstr(f, p0, p1);
+ for(l = 0; l<genstr.n; l++){
+ i = genstr.s[l];
+ if(utfrune(".*+?(|)\\[]^$", i)){
+ str = tmpcstr("\\");
+ Strinsert(&genstr, str, l++);
+ freetmpstr(str);
+ }
+ }
+ Straddc(&genstr, '\0');
+ nextmatch(f, &genstr, p1, 1);
+ moveto(f, sel.p[0]);
+ break;
+
+ case Tsearch:
+ termlocked++;
+ if(curfile == 0)
+ error(Enofile);
+ if(lastpat.s[0] == 0)
+ panic("Tsearch");
+ nextmatch(curfile, &lastpat, curfile->dot.r.p2, 1);
+ moveto(curfile, sel.p[0]);
+ break;
+
+ case Tsend:
+ termlocked++;
+ inshort(); /* ignored */
+ p0 = inlong();
+ p1 = inlong();
+ setgenstr(cmd, p0, p1);
+ bufreset(&snarfbuf);
+ bufinsert(&snarfbuf, (Posn)0, genstr.s, genstr.n);
+ outTl(Hsnarflen, genstr.n);
+ if(genstr.s[genstr.n-1] != '\n')
+ Straddc(&genstr, '\n');
+ loginsert(cmd, cmd->nc, genstr.s, genstr.n);
+ fileupdate(cmd, FALSE, TRUE);
+ cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nc;
+ telldot(cmd);
+ termcommand();
+ break;
+
+ case Tdclick:
+ case Ttclick:
+ f = whichfile(inshort());
+ p1 = inlong();
+ stretchsel(f, p1, type == Ttclick);
+ f->tdot.p1 = f->tdot.p2 = p1;
+ telldot(f);
+ outTs(Hunlockfile, f->tag);
+ break;
+
+ case Tstartsnarf:
+ if (snarfbuf.nc <= 0) { /* nothing to export */
+ outTs(Hsetsnarf, 0);
+ break;
+ }
+ c = 0;
+ i = 0;
+ m = snarfbuf.nc;
+ if(m > SNARFSIZE) {
+ m = SNARFSIZE;
+ dprint("?warning: snarf buffer truncated\n");
+ }
+ rp = malloc(m*sizeof(Rune));
+ if(rp){
+ bufread(&snarfbuf, 0, rp, m);
+ c = Strtoc(tmprstr(rp, m));
+ free(rp);
+ i = strlen(c);
+ }
+ outTs(Hsetsnarf, i);
+ if(c){
+ Write(1, c, i);
+ free(c);
+ } else
+ dprint("snarf buffer too long\n");
+ break;
+
+ case Tsetsnarf:
+ m = inshort();
+ if(m > SNARFSIZE)
+ error(Etoolong);
+ c = malloc(m+1);
+ if(c){
+ for(i=0; i<m; i++)
+ c[i] = rcvchar();
+ c[m] = 0;
+ str = tmpcstr(c);
+ free(c);
+ bufreset(&snarfbuf);
+ bufinsert(&snarfbuf, (Posn)0, str->s, str->n);
+ freetmpstr(str);
+ outT0(Hunlock);
+ }
+ break;
+
+ case Tack:
+ waitack = 0;
+ break;
+
+ case Tplumb:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = inlong();
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = strdup("sam");
+ pm->dst = 0;
+ /* construct current directory */
+ c = Strtoc(&f->name);
+ if(c[0] == '/')
+ pm->wdir = c;
+ else{
+ wdir = emalloc(1024);
+ getwd(wdir, 1024);
+ pm->wdir = emalloc(1024);
+ snprint(pm->wdir, 1024, "%s/%s", wdir, c);
+ cleanname(pm->wdir);
+ free(wdir);
+ free(c);
+ }
+ c = strrchr(pm->wdir, '/');
+ if(c)
+ *c = '\0';
+ pm->type = strdup("text");
+ if(p1 > p0)
+ pm->attr = nil;
+ else{
+ p = p0;
+ while(p0>0 && (i=filereadc(f, p0 - 1))!=' ' && i!='\t' && i!='\n')
+ p0--;
+ while(p1<f->nc && (i=filereadc(f, p1))!=' ' && i!='\t' && i!='\n')
+ p1++;
+ sprint(cbuf, "click=%ld", p-p0);
+ pm->attr = plumbunpackattr(cbuf);
+ }
+ if(p0==p1 || p1-p0>=BLOCKSIZE){
+ plumbfree(pm);
+ break;
+ }
+ setgenstr(f, p0, p1);
+ pm->data = Strtoc(&genstr);
+ pm->ndata = strlen(pm->data);
+ c = plumbpack(pm, &i);
+ if(c != 0){
+ outTs(Hplumb, i);
+ Write(1, c, i);
+ free(c);
+ }
+ plumbfree(pm);
+ break;
+
+ case Tmenucmd:
+ dprint((char*)inp);
+ break;
+
+ case Tmenucmdsend:
+ termlocked++;
+ str = tmpcstr((char*)inp);
+ Straddc(str, '\n');
+ loginsert(cmd, cmd->nc, str->s, str->n);
+ freetmpstr(str);
+ fileupdate(cmd, FALSE, TRUE);
+ cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nc;
+ telldot(cmd);
+ termcommand();
+ break;
+
+ case Texit:
+ exits(0);
+ }
+ return TRUE;
+}
+
+void
+snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
+{
+ Posn l;
+ int i;
+
+ if(!emptyok && p1==p2)
+ return;
+ bufreset(buf);
+ /* Stage through genbuf to avoid compaction problems (vestigial) */
+ if(p2 > f->nc){
+ fprint(2, "bad snarf addr p1=%ld p2=%ld f->nc=%d\n", p1, p2, f->nc); /*ZZZ should never happen, can remove */
+ p2 = f->nc;
+ }
+ for(l=p1; l<p2; l+=i){
+ i = p2-l>BLOCKSIZE? BLOCKSIZE : p2-l;
+ bufread(f, l, genbuf, i);
+ bufinsert(buf, buf->nc, tmprstr(genbuf, i)->s, i);
+ }
+}
+
+int
+inshort(void)
+{
+ ushort n;
+
+ n = inp[0] | (inp[1]<<8);
+ inp += 2;
+ return n;
+}
+
+long
+inlong(void)
+{
+ ulong n;
+
+ n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
+ inp += 4;
+ return n;
+}
+
+vlong
+invlong(void)
+{
+ vlong v;
+
+ v = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
+ v = (v<<16) | (inp[3]<<8) | inp[2];
+ v = (v<<16) | (inp[1]<<8) | inp[0];
+ inp += 8;
+ return v;
+}
+
+void
+setgenstr(File *f, Posn p0, Posn p1)
+{
+ if(p0 != p1){
+ if(p1-p0 >= TBLOCKSIZE)
+ error(Etoolong);
+ Strinsure(&genstr, p1-p0);
+ bufread(f, p0, genbuf, p1-p0);
+ memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
+ genstr.n = p1-p0;
+ }else{
+ if(snarfbuf.nc == 0)
+ error(Eempty);
+ if(snarfbuf.nc > TBLOCKSIZE)
+ error(Etoolong);
+ bufread(&snarfbuf, (Posn)0, genbuf, snarfbuf.nc);
+ Strinsure(&genstr, snarfbuf.nc);
+ memmove(genstr.s, genbuf, RUNESIZE*snarfbuf.nc);
+ genstr.n = snarfbuf.nc;
+ }
+}
+
+void
+outT0(Hmesg type)
+{
+ outstart(type);
+ outsend();
+}
+
+void
+outTl(Hmesg type, long l)
+{
+ outstart(type);
+ outlong(l);
+ outsend();
+}
+
+void
+outTs(Hmesg type, int s)
+{
+ outstart(type);
+ journaln(1, s);
+ outshort(s);
+ outsend();
+}
+
+void
+outS(String *s)
+{
+ char *c;
+ int i;
+
+ c = Strtoc(s);
+ i = strlen(c);
+ outcopy(i, c);
+ if(i > 99)
+ c[99] = 0;
+ journaln(1, i);
+ journal(1, c);
+ free(c);
+}
+
+void
+outTsS(Hmesg type, int s1, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ outS(s);
+ outsend();
+}
+
+void
+outTslS(Hmesg type, int s1, Posn l1, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ journaln(1, s1);
+ outlong(l1);
+ journaln(1, l1);
+ outS(s);
+ outsend();
+}
+
+void
+outTS(Hmesg type, String *s)
+{
+ outstart(type);
+ outS(s);
+ outsend();
+}
+
+void
+outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ outlong(l2);
+ journaln(1, l1);
+ journaln(1, l2);
+ outS(s);
+ outsend();
+}
+
+void
+outTsll(Hmesg type, int s, Posn l1, Posn l2)
+{
+ outstart(type);
+ outshort(s);
+ outlong(l1);
+ outlong(l2);
+ journaln(1, l1);
+ journaln(1, l2);
+ outsend();
+}
+
+void
+outTsl(Hmesg type, int s, Posn l)
+{
+ outstart(type);
+ outshort(s);
+ outlong(l);
+ journaln(1, l);
+ outsend();
+}
+
+void
+outTsv(Hmesg type, int s, vlong v)
+{
+ outstart(type);
+ outshort(s);
+ outvlong(v);
+ journalv(1, v);
+ outsend();
+}
+
+void
+outstart(Hmesg type)
+{
+ journal(1, hname[type]);
+ outmsg[0] = type;
+ outp = outmsg+3;
+}
+
+void
+outcopy(int count, void *data)
+{
+ memmove(outp, data, count);
+ outp += count;
+}
+
+void
+outshort(int s)
+{
+ *outp++ = s;
+ *outp++ = s>>8;
+}
+
+void
+outlong(long l)
+{
+ *outp++ = l;
+ *outp++ = l>>8;
+ *outp++ = l>>16;
+ *outp++ = l>>24;
+}
+
+void
+outvlong(vlong v)
+{
+ int i;
+
+ for(i = 0; i < 8; i++){
+ *outp++ = v;
+ v >>= 8;
+ }
+}
+
+void
+outsend(void)
+{
+ int outcount;
+
+ if(outp >= outdata+nelem(outdata))
+ panic("outsend");
+ outcount = outp-outmsg;
+ outcount -= 3;
+ outmsg[1] = outcount;
+ outmsg[2] = outcount>>8;
+ outmsg = outp;
+ if(!outbuffered){
+ outcount = outmsg-outdata;
+ if (write(1, (char*) outdata, outcount) != outcount)
+ rescue();
+ outmsg = outdata;
+ return;
+ }
+}
+
+int
+needoutflush(void)
+{
+ return outmsg >= outdata+DATASIZE;
+}
+
+void
+outflush(void)
+{
+ if(outmsg == outdata)
+ return;
+ outbuffered = 0;
+ /* flow control */
+ outT0(Hack);
+ waitack = 1;
+ do
+ if(rcv() == 0){
+ rescue();
+ exits("eof");
+ }
+ while(waitack);
+ outmsg = outdata;
+ outbuffered = 1;
+}
--- /dev/null
+++ b/sam/mesg.h
@@ -1,0 +1,137 @@
+/* VERSION 1 introduces plumbing
+ 2 increases SNARFSIZE from 4096 to 32000
+ 3 adds a triple click
+ 4 adds M command (add b2 menu action)
+ */
+#define VERSION 4
+
+#define TBLOCKSIZE 512 /* largest piece of text sent to terminal */
+#define DATASIZE (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
+#define SNARFSIZE 32000 /* maximum length of exchanged snarf buffer, must fit in 15 bits */
+/*
+ * Messages originating at the terminal
+ */
+typedef enum Tmesg
+{
+ Tversion, /* version */
+ Tstartcmdfile, /* terminal just opened command frame */
+ Tcheck, /* ask host to poke with Hcheck */
+ Trequest, /* request data to fill a hole */
+ Torigin, /* gimme an Horigin near here */
+ Tstartfile, /* terminal just opened a file's frame */
+ Tworkfile, /* set file to which commands apply */
+ Ttype, /* add some characters, but terminal already knows */
+ Tcut,
+ Tpaste,
+ Tsnarf,
+ Tstartnewfile, /* terminal just opened a new frame */
+ Twrite, /* write file */
+ Tclose, /* terminal requests file close; check mod. status */
+ Tlook, /* search for literal current text */
+ Tsearch, /* search for last regular expression */
+ Tsend, /* pretend he typed stuff */
+ Tdclick, /* double click */
+ Tstartsnarf, /* initiate snarf buffer exchange */
+ Tsetsnarf, /* remember string in snarf buffer */
+ Tack, /* acknowledge Hack */
+ Texit, /* exit */
+ Tplumb, /* send plumb message */
+ Ttclick, /* triple click */
+ Tmenucmd, /* list custom cmds in b2 menu */
+ Tmenucmdsend, /* execute custom cmd from b2 menu */
+ TMAX,
+}Tmesg;
+/*
+ * Messages originating at the host
+ */
+typedef enum Hmesg
+{
+ Hversion, /* version */
+ Hbindname, /* attach name[0] to text in terminal */
+ Hcurrent, /* make named file the typing file */
+ Hnewname, /* create "" name in menu */
+ Hmovname, /* move file name in menu */
+ Hgrow, /* insert space in rasp */
+ Hcheck0, /* see below */
+ Hcheck, /* ask terminal to check whether it needs more data */
+ Hunlock, /* command is finished; user can do things */
+ Hdata, /* store this data in previously allocated space */
+ Horigin, /* set origin of file/frame in terminal */
+ Hunlockfile, /* unlock file in terminal */
+ Hsetdot, /* set dot in terminal */
+ Hgrowdata, /* Hgrow + Hdata folded together */
+ Hmoveto, /* scrolling, context search, etc. */
+ Hclean, /* named file is now 'clean' */
+ Hdirty, /* named file is now 'dirty' */
+ Hcut, /* remove space from rasp */
+ Hsetpat, /* set remembered regular expression */
+ Hdelname, /* delete file name from menu */
+ Hclose, /* close file and remove from menu */
+ Hsetsnarf, /* remember string in snarf buffer */
+ Hsnarflen, /* report length of implicit snarf */
+ Hack, /* request acknowledgement */
+ Hexit,
+ Hplumb, /* return plumb message to terminal - version 1 */
+ Hmenucmd, /* modify custom cmds in b2 menu */
+ HMAX,
+}Hmesg;
+typedef struct Header{
+ uchar type; /* one of the above */
+ uchar count0; /* low bits of data size */
+ uchar count1; /* high bits of data size */
+ uchar data[1]; /* variable size */
+}Header;
+
+/*
+ * File transfer protocol schematic, a la Holzmann
+ * #define N 6
+ *
+ * chan h = [4] of { mtype };
+ * chan t = [4] of { mtype };
+ *
+ * mtype = { Hgrow, Hdata,
+ * Hcheck, Hcheck0,
+ * Trequest, Tcheck,
+ * };
+ *
+ * active proctype host()
+ * { byte n;
+ *
+ * do
+ * :: n < N -> n++; t!Hgrow
+ * :: n == N -> n++; t!Hcheck0
+ *
+ * :: h?Trequest -> t!Hdata
+ * :: h?Tcheck -> t!Hcheck
+ * od
+ * }
+ *
+ * active proctype term()
+ * {
+ * do
+ * :: t?Hgrow -> h!Trequest
+ * :: t?Hdata -> skip
+ * :: t?Hcheck0 -> h!Tcheck
+ * :: t?Hcheck ->
+ * if
+ * :: h!Trequest -> progress: h!Tcheck
+ * :: break
+ * fi
+ * od;
+ * printf("term exits\n")
+ * }
+ *
+ * From: [email protected]
+ * Date: Tue Jul 17 13:47:23 EDT 2001
+ * To: [email protected]
+ *
+ * spin -c (or -a) spec
+ * pcc -DNP -o pan pan.c
+ * pan -l
+ *
+ * proves that there are no non-progress cycles
+ * (infinite executions *not* passing through
+ * the statement marked with a label starting
+ * with the prefix "progress")
+ *
+ */
--- /dev/null
+++ b/sam/mkfile
@@ -1,0 +1,40 @@
+</$objtype/mkfile
+
+TARG=sam
+OFILES=sam.$O\
+ address.$O\
+ buff.$O\
+ cmd.$O\
+ disk.$O\
+ error.$O\
+ file.$O\
+ io.$O\
+ list.$O\
+ mesg.$O\
+ moveto.$O\
+ multi.$O\
+ plan9.$O\
+ rasp.$O\
+ regexp.$O\
+ shell.$O\
+ string.$O\
+ sys.$O\
+ util.$O\
+ xec.$O\
+
+HFILES=sam.h\
+ errors.h\
+ mesg.h\
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ parse.h\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+address.$O cmd.$O parse.$O xec.$O unix.$O: parse.h
+
--- /dev/null
+++ b/sam/moveto.c
@@ -1,0 +1,193 @@
+#include "sam.h"
+
+void
+moveto(File *f, Range r)
+{
+ Posn p1 = r.p1, p2 = r.p2;
+
+ f->dot.r.p1 = p1;
+ f->dot.r.p2 = p2;
+ if(f->rasp){
+ telldot(f);
+ outTsl(Hmoveto, f->tag, f->dot.r.p1);
+ }
+}
+
+void
+telldot(File *f)
+{
+ if(f->rasp == 0)
+ panic("telldot");
+ if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
+ return;
+ outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
+ f->tdot = f->dot.r;
+}
+
+void
+tellpat(void)
+{
+ outTS(Hsetpat, &lastpat);
+ patset = FALSE;
+}
+
+#define CHARSHIFT 128
+
+void
+lookorigin(File *f, Posn p0, Posn ls)
+{
+ int nl, nc, c;
+ Posn p, oldp0;
+
+ if(p0 > f->nc)
+ p0 = f->nc;
+ oldp0 = p0;
+ p = p0;
+ for(nl=nc=c=0; c!=-1 && nl<ls && nc<ls*CHARSHIFT; nc++)
+ if((c=filereadc(f, --p)) == '\n'){
+ nl++;
+ oldp0 = p0-nc;
+ }
+ if(c == -1)
+ p0 = 0;
+ else if(nl==0){
+ if(p0>=CHARSHIFT/2)
+ p0-=CHARSHIFT/2;
+ else
+ p0 = 0;
+ }else
+ p0 = oldp0;
+ outTsl(Horigin, f->tag, p0);
+}
+
+int
+isalnum(int c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ */
+ if(c<=' ')
+ return 0;
+ if(0x7F<=c && c<=0xA0)
+ return 0;
+ if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+ return 0;
+ return 1;
+}
+
+int
+isspace(Rune c)
+{
+ return c == 0 || c == ' ' || c == '\t' ||
+ c == '\n' || c == '\r' || c == '\v';
+}
+
+int
+inmode(Rune r, int mode)
+{
+ return (mode == 0) ? isalnum(r) : r && !isspace(r);
+}
+
+int
+clickmatch(File *f, int cl, int cr, int dir, Posn *p)
+{
+ int c;
+ int nest = 1;
+
+ for(;;){
+ if(dir > 0){
+ if(*p >= f->nc)
+ break;
+ c = filereadc(f, (*p)++);
+ }else{
+ if(*p == 0)
+ break;
+ c = filereadc(f, --(*p));
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+Rune*
+strrune(Rune *s, Rune c)
+{
+ Rune c1;
+
+ if(c == 0) {
+ while(*s++)
+ ;
+ return s-1;
+ }
+
+ while(c1 = *s++)
+ if(c1 == c)
+ return s-1;
+ return 0;
+}
+
+/*
+ * Stretches a selection out over current text,
+ * selecting matching range if possible.
+ * If there's no matching range, mode 0 selects
+ * a single alphanumeric region. Mode 1 selects
+ * a non-whitespace region.
+ */
+void
+stretchsel(File *f, Posn p1, int mode)
+{
+ int c, i;
+ Rune *r, *l;
+ Posn p;
+
+ if(p1 > f->nc)
+ return;
+ f->dot.r.p1 = f->dot.r.p2 = p1;
+ for(i=0; left[i]; i++){
+ l = left[i];
+ r = right[i];
+ /* try left match */
+ p = p1;
+ if(p1 == 0)
+ c = '\n';
+ else
+ c = filereadc(f, p - 1);
+ if(strrune(l, c)){
+ if(clickmatch(f, c, r[strrune(l, c)-l], 1, &p)){
+ f->dot.r.p1 = p1;
+ f->dot.r.p2 = p-(c!='\n');
+ }
+ return;
+ }
+ /* try right match */
+ p = p1;
+ if(p1 == f->nc)
+ c = '\n';
+ else
+ c = filereadc(f, p);
+ if(strrune(r, c)){
+ if(clickmatch(f, c, l[strrune(r, c)-r], -1, &p)){
+ f->dot.r.p1 = p;
+ if(c!='\n' || p!=0 || filereadc(f, 0)=='\n')
+ f->dot.r.p1++;
+ f->dot.r.p2 = p1+(p1<f->nc && c=='\n');
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ p = p1;
+ while(p < f->nc && inmode(filereadc(f, p++), mode))
+ f->dot.r.p2++;
+ /* try filling out word to left */
+ p = p1;
+ while(--p >= 0 && inmode(filereadc(f, p), mode))
+ f->dot.r.p1--;
+}
+
--- /dev/null
+++ b/sam/multi.c
@@ -1,0 +1,123 @@
+#include "sam.h"
+
+List file = { 'p' };
+ushort tag;
+
+File *
+newfile(void)
+{
+ File *f;
+
+ f = fileopen();
+ inslist(&file, 0, f);
+ f->tag = tag++;
+ if(downloaded)
+ outTs(Hnewname, f->tag);
+ /* already sorted; file name is "" */
+ return f;
+}
+
+int
+whichmenu(File *f)
+{
+ int i;
+
+ for(i=0; i<file.nused; i++)
+ if(file.filepptr[i]==f)
+ return i;
+ return -1;
+}
+
+void
+delfile(File *f)
+{
+ int w = whichmenu(f);
+
+ if(w < 0) /* e.g. x/./D */
+ return;
+ if(downloaded)
+ outTs(Hdelname, f->tag);
+ dellist(&file, w);
+ fileclose(f);
+}
+
+void
+fullname(String *name)
+{
+ if(name->n > 0 && name->s[0]!='/' && name->s[0]!=0)
+ Strinsert(name, &curwd, (Posn)0);
+}
+
+void
+fixname(String *name)
+{
+ String *t;
+ char *s;
+
+ fullname(name);
+ s = Strtoc(name);
+ if(strlen(s) > 0)
+ s = cleanname(s);
+ t = tmpcstr(s);
+ Strduplstr(name, t);
+ free(s);
+ freetmpstr(t);
+
+ if(Strispre(&curwd, name))
+ Strdelete(name, 0, curwd.n);
+}
+
+void
+sortname(File *f)
+{
+ int i, cmp, w;
+ int dupwarned;
+
+ w = whichmenu(f);
+ dupwarned = FALSE;
+ dellist(&file, w);
+ if(f == cmd)
+ i = 0;
+ else{
+ for(i=0; i<file.nused; i++){
+ cmp = Strcmp(&f->name, &file.filepptr[i]->name);
+ if(cmp==0 && !dupwarned){
+ dupwarned = TRUE;
+ warn_S(Wdupname, &f->name);
+ }else if(cmp<0 && (i>0 || cmd==0))
+ break;
+ }
+ }
+ inslist(&file, i, f);
+ if(downloaded)
+ outTsS(Hmovname, f->tag, &f->name);
+}
+
+void
+state(File *f, int cleandirty)
+{
+ if(f == cmd)
+ return;
+ f->unread = FALSE;
+ if(downloaded && whichmenu(f)>=0){ /* else flist or menu */
+ if(f->mod && cleandirty!=Dirty)
+ outTs(Hclean, f->tag);
+ else if(!f->mod && cleandirty==Dirty)
+ outTs(Hdirty, f->tag);
+ }
+ if(cleandirty == Clean)
+ f->mod = FALSE;
+ else
+ f->mod = TRUE;
+}
+
+File *
+lookfile(String *s)
+{
+ int i;
+
+ for(i=0; i<file.nused; i++)
+ if(Strcmp(&file.filepptr[i]->name, s) == 0)
+ return file.filepptr[i];
+ return 0;
+}
--- /dev/null
+++ b/sam/parse.h
@@ -1,0 +1,71 @@
+typedef struct Addr Addr;
+typedef struct Cmd Cmd;
+struct Addr
+{
+ char type; /* # (char addr), l (line addr), / ? . $ + - , ; */
+ union{
+ String *re;
+ Addr *aleft; /* left side of , and ; */
+ } g;
+ Posn num;
+ Addr *next; /* or right side of , and ; */
+};
+
+#define are g.re
+#define left g.aleft
+
+struct Cmd
+{
+ Addr *addr; /* address (range of text) */
+ String *re; /* regular expression for e.g. 'x' */
+ union{
+ Cmd *cmd; /* target of x, g, {, etc. */
+ String *text; /* text of a, c, i; rhs of s */
+ Addr *addr; /* address for m, t */
+ } g;
+ Cmd *next; /* pointer to next element in {} */
+ short num;
+ ushort flag; /* whatever */
+ ushort cmdc; /* command character; 'x' etc. */
+};
+
+#define ccmd g.cmd
+#define ctext g.text
+#define caddr g.addr
+
+typedef struct Cmdtab Cmdtab;
+struct Cmdtab
+{
+ ushort cmdc; /* command character */
+ uchar text; /* takes a textual argument? */
+ uchar regexp; /* takes a regular expression? */
+ uchar addr; /* takes an address (m or t)? */
+ uchar defcmd; /* default command; 0==>none */
+ uchar defaddr; /* default address */
+ uchar count; /* takes a count e.g. s2/// */
+ char *token; /* takes text terminated by one of these */
+ int (*fn)(File*, Cmd*); /* function to call with parse tree */
+}cmdtab[];
+
+enum Defaddr{ /* default addresses */
+ aNo,
+ aDot,
+ aAll,
+};
+
+int nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
+int c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
+int D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
+int f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
+int k_cmd(File*, Cmd*), m_cmd(File*, Cmd*);
+int M_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
+int p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
+int s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
+int x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
+int eq_cmd(File*, Cmd*);
+
+
+String *getregexp(int);
+Addr *newaddr(void);
+Address address(Addr*, Address, int);
+int cmdexec(File*, Cmd*);
--- /dev/null
+++ b/sam/plan9.c
@@ -1,0 +1,154 @@
+#include "sam.h"
+
+Rune samname[] = L"~~sam~~";
+
+Rune *left[]= {
+ L"{[(<«",
+ L"\n",
+ L"'\"`",
+ 0
+};
+Rune *right[]= {
+ L"}])>»",
+ L"\n",
+ L"'\"`",
+ 0
+};
+
+char RSAM[] = "sam";
+char SAMTERM[] = "/bin/aux/samterm";
+char HOME[] = "home";
+char TMPDIR[] = "/tmp";
+char SH[] = "rc";
+char SHPATH[] = "/bin/rc";
+char RX[] = "rx";
+char RXPATH[] = "/bin/rx";
+char SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
+
+void
+dprint(char *z, ...)
+{
+ char buf[BLOCKSIZE];
+ va_list arg;
+
+ va_start(arg, z);
+ vseprint(buf, &buf[BLOCKSIZE], z, arg);
+ va_end(arg);
+ termwrite(buf);
+}
+
+void
+print_ss(char *s, String *a, String *b)
+{
+ dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
+}
+
+void
+print_s(char *s, String *a)
+{
+ dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
+}
+
+int
+statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ Dir *dirb;
+
+ dirb = dirstat(name);
+ if(dirb == nil)
+ return -1;
+ if(dev)
+ *dev = dirb->type|(dirb->dev<<16);
+ if(id)
+ *id = dirb->qid.path;
+ if(time)
+ *time = dirb->mtime;
+ if(length)
+ *length = dirb->length;
+ if(appendonly)
+ *appendonly = dirb->mode & DMAPPEND;
+ free(dirb);
+ return 1;
+}
+
+int
+statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ Dir *dirb;
+
+ dirb = dirfstat(fd);
+ if(dirb == nil)
+ return -1;
+ if(dev)
+ *dev = dirb->type|(dirb->dev<<16);
+ if(id)
+ *id = dirb->qid.path;
+ if(time)
+ *time = dirb->mtime;
+ if(length)
+ *length = dirb->length;
+ if(appendonly)
+ *appendonly = dirb->mode & DMAPPEND;
+ free(dirb);
+ return 1;
+}
+
+void
+notifyf(void *a, char *s)
+{
+ USED(a);
+ if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
+ noted(NCONT);
+ if(strcmp(s, "interrupt") == 0)
+ noted(NCONT);
+ panicking = 1;
+ rescue();
+ noted(NDFLT);
+}
+
+char*
+waitfor(int pid)
+{
+ Waitmsg *w;
+ static char msg[ERRMAX];
+
+ while((w = wait()) != nil){
+ if(w->pid != pid){
+ free(w);
+ continue;
+ }
+ strecpy(msg, msg+sizeof msg, w->msg);
+ free(w);
+ return msg;
+ }
+ rerrstr(msg, sizeof msg);
+ return msg;
+}
+
+void
+samerr(char *buf)
+{
+ sprint(buf, "%s/sam.err", TMPDIR);
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = mallocz(n, 1);
+ if(p == 0)
+ panic("malloc fails");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(p == 0)
+ panic("realloc fails");
+ setmalloctag(p, getcallerpc(&p));
+ return p;
+}
--- /dev/null
+++ b/sam/rasp.c
@@ -1,0 +1,339 @@
+#include "sam.h"
+/*
+ * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
+ * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
+ */
+#define GROWDATASIZE 50 /* if size is > this, send data with grow */
+
+void rcut(List*, Posn, Posn);
+int rterm(List*, Posn);
+void rgrow(List*, Posn, Posn);
+
+static Posn growpos;
+static Posn grown;
+static Posn shrinkpos;
+static Posn shrunk;
+
+/*
+ * rasp routines inform the terminal of changes to the file.
+ *
+ * a rasp is a list of spans within the file, and an indication
+ * of whether the terminal knows about the span.
+ *
+ * optimize by coalescing multiple updates to the same span
+ * if it is not known by the terminal.
+ *
+ * other possible optimizations: flush terminal's rasp by cut everything,
+ * insert everything if rasp gets too large.
+ */
+
+/*
+ * only called for initial load of file
+ */
+void
+raspload(File *f)
+{
+ if(f->rasp == nil)
+ return;
+ grown = f->nc;
+ growpos = 0;
+ if(f->nc)
+ rgrow(f->rasp, 0, f->nc);
+ raspdone(f, 1);
+}
+
+void
+raspstart(File *f)
+{
+ if(f->rasp == nil)
+ return;
+ grown = 0;
+ shrunk = 0;
+ outbuffered = 1;
+}
+
+void
+raspdone(File *f, int toterm)
+{
+ if(f->dot.r.p1 > f->nc)
+ f->dot.r.p1 = f->nc;
+ if(f->dot.r.p2 > f->nc)
+ f->dot.r.p2 = f->nc;
+ if(f->mark.p1 > f->nc)
+ f->mark.p1 = f->nc;
+ if(f->mark.p2 > f->nc)
+ f->mark.p2 = f->nc;
+ if(f->rasp == nil)
+ return;
+ if(grown)
+ outTsll(Hgrow, f->tag, growpos, grown);
+ else if(shrunk)
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ if(toterm)
+ outTs(Hcheck0, f->tag);
+ outflush();
+ outbuffered = 0;
+ if(f == cmd){
+ cmdpt += cmdptadv;
+ cmdptadv = 0;
+ }
+}
+
+void
+raspflush(File *f)
+{
+ if(grown){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }
+ else if(shrunk){
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ shrunk = 0;
+ }
+ outflush();
+}
+
+void
+raspdelete(File *f, uint p1, uint p2, int toterm)
+{
+ long n;
+
+ n = p2 - p1;
+ if(n == 0)
+ return;
+
+ if(p2 <= f->dot.r.p1){
+ f->dot.r.p1 -= n;
+ f->dot.r.p2 -= n;
+ }
+ if(p2 <= f->mark.p1){
+ f->mark.p1 -= n;
+ f->mark.p2 -= n;
+ }
+
+ if(f->rasp == nil)
+ return;
+
+ if(f==cmd && p1<cmdpt){
+ if(p2 <= cmdpt)
+ cmdpt -= n;
+ else
+ cmdpt = p1;
+ }
+ if(toterm){
+ if(grown){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }else if(shrunk && shrinkpos!=p1 && shrinkpos!=p2){
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ shrunk = 0;
+ }
+ if(!shrunk || shrinkpos==p2)
+ shrinkpos = p1;
+ shrunk += n;
+ }
+ rcut(f->rasp, p1, p2);
+}
+
+void
+raspinsert(File *f, uint p1, Rune *buf, uint n, int toterm)
+{
+ Range r;
+
+ if(n == 0)
+ return;
+
+ if(p1 < f->dot.r.p1){
+ f->dot.r.p1 += n;
+ f->dot.r.p2 += n;
+ }
+ if(p1 < f->mark.p1){
+ f->mark.p1 += n;
+ f->mark.p2 += n;
+ }
+
+
+ if(f->rasp == nil)
+ return;
+ if(f==cmd && p1<cmdpt)
+ cmdpt += n;
+ if(toterm){
+ if(shrunk){
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ shrunk = 0;
+ }
+ if(n>GROWDATASIZE || !rterm(f->rasp, p1)){
+ rgrow(f->rasp, p1, n);
+ if(grown && growpos+grown!=p1 && growpos!=p1){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }
+ if(!grown)
+ growpos = p1;
+ grown += n;
+ }else{
+ if(grown){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }
+ rgrow(f->rasp, p1, n);
+ r = rdata(f->rasp, p1, n);
+ if(r.p1!=p1 || r.p2!=p1+n)
+ panic("rdata in toterminal");
+ outTsllS(Hgrowdata, f->tag, p1, n, tmprstr(buf, n));
+ }
+ }else{
+ rgrow(f->rasp, p1, n);
+ r = rdata(f->rasp, p1, n);
+ if(r.p1!=p1 || r.p2!=p1+n)
+ panic("rdata in toterminal");
+ }
+}
+
+#define M 0x80000000L
+#define P(i) r->posnptr[i]
+#define T(i) (P(i)&M) /* in terminal */
+#define L(i) (P(i)&~M) /* length of this piece */
+
+void
+rcut(List *r, Posn p1, Posn p2)
+{
+ Posn p, x;
+ int i;
+
+ if(p1 == p2)
+ panic("rcut 0");
+ for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i == r->nused)
+ panic("rcut 1");
+ if(p < p1){ /* chop this piece */
+ if(p+L(i) < p2){
+ x = p1-p;
+ p += L(i);
+ }else{
+ x = L(i)-(p2-p1);
+ p = p2;
+ }
+ if(T(i))
+ P(i) = x|M;
+ else
+ P(i) = x;
+ i++;
+ }
+ while(i<r->nused && p+L(i)<=p2){
+ p += L(i);
+ dellist(r, i);
+ }
+ if(p < p2){
+ if(i == r->nused)
+ panic("rcut 2");
+ x = L(i)-(p2-p);
+ if(T(i))
+ P(i) = x|M;
+ else
+ P(i) = x;
+ }
+ /* can we merge i and i-1 ? */
+ if(i>0 && i<r->nused && T(i-1)==T(i)){
+ x = L(i-1)+L(i);
+ dellist(r, i--);
+ if(T(i))
+ P(i)=x|M;
+ else
+ P(i)=x;
+ }
+}
+
+void
+rgrow(List *r, Posn p1, Posn n)
+{
+ Posn p;
+ int i;
+
+ if(n == 0)
+ panic("rgrow 0");
+ for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i == r->nused){ /* stick on end of file */
+ if(p!=p1)
+ panic("rgrow 1");
+ if(i>0 && !T(i-1))
+ P(i-1)+=n;
+ else
+ inslist(r, i, n);
+ }else if(!T(i)) /* goes in this empty piece */
+ P(i)+=n;
+ else if(p==p1 && i>0 && !T(i-1)) /* special case; simplifies life */
+ P(i-1)+=n;
+ else if(p==p1)
+ inslist(r, i, n);
+ else{ /* must break piece in terminal */
+ inslist(r, i+1, (L(i)-(p1-p))|M);
+ inslist(r, i+1, n);
+ P(i) = (p1-p)|M;
+ }
+}
+
+int
+rterm(List *r, Posn p1)
+{
+ Posn p;
+ int i;
+
+ for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i==r->nused && (i==0 || !T(i-1)))
+ return 0;
+ return T(i);
+}
+
+Range
+rdata(List *r, Posn p1, Posn n)
+{
+ Posn p;
+ int i;
+ Range rg;
+
+ if(n==0)
+ panic("rdata 0");
+ for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i==r->nused)
+ panic("rdata 1");
+ if(T(i)){
+ n-=L(i)-(p1-p);
+ if(n<=0){
+ rg.p1 = rg.p2 = p1;
+ return rg;
+ }
+ p+=L(i++);
+ p1 = p;
+ }
+ if(T(i) || i==r->nused)
+ panic("rdata 2");
+ if(p+L(i)<p1+n)
+ n = L(i)-(p1-p);
+ rg.p1 = p1;
+ rg.p2 = p1+n;
+ if(p!=p1){
+ inslist(r, i+1, L(i)-(p1-p));
+ P(i)=p1-p;
+ i++;
+ }
+ if(L(i)!=n){
+ inslist(r, i+1, L(i)-n);
+ P(i)=n;
+ }
+ P(i)|=M;
+ /* now i is set; can we merge? */
+ if(i<r->nused-1 && T(i+1)){
+ P(i)=(n+=L(i+1))|M;
+ dellist(r, i+1);
+ }
+ if(i>0 && T(i-1)){
+ P(i)=(n+L(i-1))|M;
+ dellist(r, i-1);
+ }
+ return rg;
+}
--- /dev/null
+++ b/sam/regexp.c
@@ -1,0 +1,805 @@
+#include "sam.h"
+
+Rangeset sel;
+String lastregexp;
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+
+struct Inst
+{
+ long type; /* <= Runemax ==> literal, otherwise action */
+ union {
+ int rsid;
+ int rsubid;
+ int class;
+ struct Inst *rother;
+ struct Inst *rright;
+ } r;
+ union{
+ struct Inst *lleft;
+ struct Inst *lnext;
+ } l;
+};
+#define sid r.rsid
+#define subid r.rsubid
+#define rclass r.class
+#define other r.rother
+#define right r.rright
+#define left l.lleft
+#define next l.lnext
+
+#define NPROG 1024
+Inst program[NPROG];
+Inst *progp;
+Inst *startinst; /* First inst. of program; might not be program[0] */
+Inst *bstartinst; /* same for backwards machine */
+
+typedef struct Ilist Ilist;
+struct Ilist
+{
+ Inst *inst; /* Instruction of the thread */
+ Rangeset se;
+ Posn startp; /* first char of match */
+};
+
+#define NLIST 127
+
+Ilist *tl, *nl; /* This list, next list */
+Ilist list[2][NLIST+1]; /* +1 for trailing null */
+static Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ * 0x100xx are operators, value == precedence
+ * 0x200xx are tokens, i.e. operands for operators
+ */
+enum {
+ OPERATOR = Runemask+1, /* Bitmask of all operators */
+ START = OPERATOR, /* Start, used for marker on stack */
+ RBRA, /* Right bracket, ) */
+ LBRA, /* Left bracket, ( */
+ OR, /* Alternation, | */
+ CAT, /* Concatentation, implicit operator */
+ STAR, /* Closure, * */
+ PLUS, /* a+ == aa* */
+ QUEST, /* a? == a|nothing, i.e. 0 or 1 a's */
+
+ ANY = OPERATOR<<1, /* Any character but newline, . */
+ NOP, /* No operation, internal use only */
+ BOL, /* Beginning of line, ^ */
+ EOL, /* End of line, $ */
+ CCLASS, /* Character class, [] */
+ NCCLASS, /* Negated character class, [^] */
+ END, /* Terminate: match found */
+
+ ISATOR = OPERATOR,
+ ISAND = OPERATOR<<1,
+};
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+ Inst *first;
+ Inst *last;
+};
+
+#define NSTACK 20
+Node andstack[NSTACK];
+Node *andp;
+int atorstack[NSTACK];
+int *atorp;
+int lastwasand; /* Last token was operand */
+int cursubid;
+int subidstack[NSTACK];
+int *subidp;
+int backwards;
+int nbra;
+Rune *exprp; /* pointer to next character in source expression */
+#define DCLASS 10 /* allocation increment */
+int nclass; /* number active */
+int Nclass; /* high water mark */
+Rune **class;
+int negateclass;
+
+int addinst(Ilist *l, Inst *inst, Rangeset *sep);
+void newmatch(Rangeset*);
+void bnewmatch(Rangeset*);
+void pushand(Inst*, Inst*);
+void pushator(int);
+Node *popand(int);
+int popator(void);
+void startlex(Rune*);
+int lex(void);
+void operator(int);
+void operand(int);
+void evaluntil(int);
+void optimize(Inst*);
+void bldcclass(void);
+
+void
+regerror(Err e)
+{
+ Strzero(&lastregexp);
+ error(e);
+}
+
+void
+regerror_c(Err e, int c)
+{
+ Strzero(&lastregexp);
+ error_c(e, c);
+}
+
+Inst *
+newinst(int t)
+{
+ if(progp >= &program[NPROG])
+ regerror(Etoolong);
+ progp->type = t;
+ progp->left = 0;
+ progp->right = 0;
+ return progp++;
+}
+
+Inst *
+realcompile(Rune *s)
+{
+ int token;
+
+ startlex(s);
+ atorp = atorstack;
+ andp = andstack;
+ subidp = subidstack;
+ cursubid = 0;
+ lastwasand = FALSE;
+ /* Start with a low priority operator to prime parser */
+ pushator(START-1);
+ while((token=lex()) != END){
+ if((token&ISATOR) == OPERATOR)
+ operator(token);
+ else
+ operand(token);
+ }
+ /* Close with a low priority operator */
+ evaluntil(START);
+ /* Force END */
+ operand(END);
+ evaluntil(START);
+ if(nbra)
+ regerror(Eleftpar);
+ --andp; /* points to first and only operand */
+ return andp->first;
+}
+
+void
+compile(String *s)
+{
+ int i;
+ Inst *oprogp;
+
+ if(Strcmp(s, &lastregexp)==0)
+ return;
+ for(i=0; i<nclass; i++)
+ free(class[i]);
+ nclass = 0;
+ progp = program;
+ backwards = FALSE;
+ startinst = realcompile(s->s);
+ optimize(program);
+ oprogp = progp;
+ backwards = TRUE;
+ bstartinst = realcompile(s->s);
+ optimize(oprogp);
+ Strduplstr(&lastregexp, s);
+}
+
+void
+operand(int t)
+{
+ Inst *i;
+ if(lastwasand)
+ operator(CAT); /* catenate is implicit */
+ i = newinst(t);
+ if(t == CCLASS){
+ if(negateclass)
+ i->type = NCCLASS; /* UGH */
+ i->rclass = nclass-1; /* UGH */
+ }
+ pushand(i, i);
+ lastwasand = TRUE;
+}
+
+void
+operator(int t)
+{
+ if(t==RBRA && --nbra<0)
+ regerror(Erightpar);
+ if(t==LBRA){
+/*
+ * if(++cursubid >= NSUBEXP)
+ * regerror(Esubexp);
+ */
+ cursubid++; /* silently ignored */
+ nbra++;
+ if(lastwasand)
+ operator(CAT);
+ }else
+ evaluntil(t);
+ if(t!=RBRA)
+ pushator(t);
+ lastwasand = FALSE;
+ if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+ lastwasand = TRUE; /* these look like operands */
+}
+
+void
+cant(char *s)
+{
+ char buf[100];
+
+ sprint(buf, "regexp: can't happen: %s", s);
+ panic(buf);
+}
+
+void
+pushand(Inst *f, Inst *l)
+{
+ if(andp >= &andstack[NSTACK])
+ cant("operand stack overflow");
+ andp->first = f;
+ andp->last = l;
+ andp++;
+}
+
+void
+pushator(int t)
+{
+ if(atorp >= &atorstack[NSTACK])
+ cant("operator stack overflow");
+ *atorp++=t;
+ if(cursubid >= NSUBEXP)
+ *subidp++= -1;
+ else
+ *subidp++=cursubid;
+}
+
+Node *
+popand(int op)
+{
+ if(andp <= &andstack[0])
+ if(op)
+ regerror_c(Emissop, op);
+ else
+ regerror(Ebadregexp);
+ return --andp;
+}
+
+int
+popator(void)
+{
+ if(atorp <= &atorstack[0])
+ cant("operator stack underflow");
+ --subidp;
+ return *--atorp;
+}
+
+void
+evaluntil(int pri)
+{
+ Node *op1, *op2, *t;
+ Inst *inst1, *inst2;
+
+ while(pri==RBRA || atorp[-1]>=pri){
+ switch(popator()){
+ case LBRA:
+ op1 = popand('(');
+ inst2 = newinst(RBRA);
+ inst2->subid = *subidp;
+ op1->last->next = inst2;
+ inst1 = newinst(LBRA);
+ inst1->subid = *subidp;
+ inst1->next = op1->first;
+ pushand(inst1, inst2);
+ return; /* must have been RBRA */
+ default:
+ panic("unknown regexp operator");
+ break;
+ case OR:
+ op2 = popand('|');
+ op1 = popand('|');
+ inst2 = newinst(NOP);
+ op2->last->next = inst2;
+ op1->last->next = inst2;
+ inst1 = newinst(OR);
+ inst1->right = op1->first;
+ inst1->left = op2->first;
+ pushand(inst1, inst2);
+ break;
+ case CAT:
+ op2 = popand(0);
+ op1 = popand(0);
+ if(backwards && op2->first->type!=END)
+ t = op1, op1 = op2, op2 = t;
+ op1->last->next = op2->first;
+ pushand(op1->first, op2->last);
+ break;
+ case STAR:
+ op2 = popand('*');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(inst1, inst1);
+ break;
+ case PLUS:
+ op2 = popand('+');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(op2->first, inst1);
+ break;
+ case QUEST:
+ op2 = popand('?');
+ inst1 = newinst(OR);
+ inst2 = newinst(NOP);
+ inst1->left = inst2;
+ inst1->right = op2->first;
+ op2->last->next = inst2;
+ pushand(inst1, inst2);
+ break;
+ }
+ }
+}
+
+
+void
+optimize(Inst *start)
+{
+ Inst *inst, *target;
+
+ for(inst=start; inst->type!=END; inst++){
+ target = inst->next;
+ while(target->type == NOP)
+ target = target->next;
+ inst->next = target;
+ }
+}
+
+#ifdef DEBUG
+void
+dumpstack(void){
+ Node *stk;
+ int *ip;
+
+ dprint("operators\n");
+ for(ip = atorstack; ip<atorp; ip++)
+ dprint("0%o\n", *ip);
+ dprint("operands\n");
+ for(stk = andstack; stk<andp; stk++)
+ dprint("0%o\t0%o\n", stk->first->type, stk->last->type);
+}
+void
+dump(void){
+ Inst *l;
+
+ l = program;
+ do{
+ dprint("%d:\t0%o\t%d\t%d\n", l-program, l->type,
+ l->left-program, l->right-program);
+ }while(l++->type);
+}
+#endif
+
+void
+startlex(Rune *s)
+{
+ exprp = s;
+ nbra = 0;
+}
+
+
+int
+lex(void){
+ int c= *exprp++;
+
+ switch(c){
+ case '\\':
+ if(*exprp)
+ if((c= *exprp++)=='n')
+ c='\n';
+ break;
+ case 0:
+ c = END;
+ --exprp; /* In case we come here again */
+ break;
+ case '*':
+ c = STAR;
+ break;
+ case '?':
+ c = QUEST;
+ break;
+ case '+':
+ c = PLUS;
+ break;
+ case '|':
+ c = OR;
+ break;
+ case '.':
+ c = ANY;
+ break;
+ case '(':
+ c = LBRA;
+ break;
+ case ')':
+ c = RBRA;
+ break;
+ case '^':
+ c = BOL;
+ break;
+ case '$':
+ c = EOL;
+ break;
+ case '[':
+ c = CCLASS;
+ bldcclass();
+ break;
+ }
+ return c;
+}
+
+long
+nextrec(void){
+ if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
+ regerror(Ebadclass);
+ if(exprp[0] == '\\'){
+ exprp++;
+ if(*exprp=='n'){
+ exprp++;
+ return '\n';
+ }
+ return *exprp++|(Runemask+1);
+ }
+ return *exprp++;
+}
+
+void
+bldcclass(void)
+{
+ long c1, c2, n, na;
+ Rune *classp;
+
+ classp = emalloc(DCLASS*RUNESIZE);
+ n = 0;
+ na = DCLASS;
+ /* we have already seen the '[' */
+ if(*exprp == '^'){
+ classp[n++] = '\n'; /* don't match newline in negate case */
+ negateclass = TRUE;
+ exprp++;
+ }else
+ negateclass = FALSE;
+ while((c1 = nextrec()) != ']'){
+ if(c1 == '-'){
+ Error:
+ free(classp);
+ regerror(Ebadclass);
+ }
+ if(n+4 >= na){ /* 3 runes plus NUL */
+ na += DCLASS;
+ classp = erealloc(classp, na*RUNESIZE);
+ }
+ if(*exprp == '-'){
+ exprp++; /* eat '-' */
+ if((c2 = nextrec()) == ']')
+ goto Error;
+ classp[n+0] = Runemax;
+ classp[n+1] = c1 & Runemask;
+ classp[n+2] = c2 & Runemask;
+ n += 3;
+ }else
+ classp[n++] = c1 & Runemask;
+ }
+ classp[n] = 0;
+ if(nclass == Nclass){
+ Nclass += DCLASS;
+ class = erealloc(class, Nclass*sizeof(Rune*));
+ }
+ class[nclass++] = classp;
+}
+
+int
+classmatch(int classno, int c, int negate)
+{
+ Rune *p;
+
+ p = class[classno];
+ while(*p){
+ if(*p == Runemax){
+ if(p[1]<=c && c<=p[2])
+ return !negate;
+ p += 3;
+ }else if(*p++ == c)
+ return !negate;
+ }
+ return negate;
+}
+
+/*
+ * Note optimization in addinst:
+ * *l must be pending when addinst called; if *l has been looked
+ * at already, the optimization is a bug.
+ */
+int
+addinst(Ilist *l, Inst *inst, Rangeset *sep)
+{
+ Ilist *p;
+
+ for(p = l; p->inst; p++){
+ if(p->inst==inst){
+ if((sep)->p[0].p1 < p->se.p[0].p1)
+ p->se= *sep; /* this would be bug */
+ return 0; /* It's already there */
+ }
+ }
+ p->inst = inst;
+ p->se= *sep;
+ (p+1)->inst = 0;
+ return 1;
+}
+
+int
+execute(File *f, Posn startp, Posn eof)
+{
+ int flag = 0;
+ Inst *inst;
+ Ilist *tlp;
+ Posn p = startp;
+ int nnl = 0, ntl;
+ int c;
+ int wrapped = 0;
+ int startchar = startinst->type<OPERATOR? startinst->type : 0;
+
+ list[0][0].inst = list[1][0].inst = 0;
+ sel.p[0].p1 = -1;
+ /* Execute machine once for each character */
+ for(;;p++){
+ doloop:
+ c = filereadc(f, p);
+ if(p>=eof || c<0){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to beginning */
+ if(sel.p[0].p1>=0 || eof!=INFINITY)
+ goto Return;
+ list[0][0].inst = list[1][0].inst = 0;
+ p = 0;
+ goto doloop;
+ default:
+ goto Return;
+ }
+ }else if(((wrapped && p>=startp) || sel.p[0].p1>0) && nnl==0)
+ break;
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = 0;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.p[0].p1<0 && (!wrapped || p<startp || startp==eof)){
+ /* Add first instruction to this list */
+ sempty.p[0].p1 = p;
+ if(addinst(tl, startinst, &sempty))
+ if(++ntl >= NLIST)
+ Overflow:
+ error(Eoverflow);
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type==c){
+ Addinst:
+ if(addinst(nl, inst->next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p2 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c!='\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(p==0 || filereadc(f, p - 1)=='\n'){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(c == '\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(addinst(tlp, inst->right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.p[0].p2 = p;
+ newmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ return sel.p[0].p1>=0;
+}
+
+void
+newmatch(Rangeset *sp)
+{
+ int i;
+
+ if(sel.p[0].p1<0 || sp->p[0].p1<sel.p[0].p1 ||
+ (sp->p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
+ for(i = 0; i<NSUBEXP; i++)
+ sel.p[i] = sp->p[i];
+}
+
+int
+bexecute(File *f, Posn startp)
+{
+ int flag = 0;
+ Inst *inst;
+ Ilist *tlp;
+ Posn p = startp;
+ int nnl = 0, ntl;
+ int c;
+ int wrapped = 0;
+ int startchar = bstartinst->type<OPERATOR? bstartinst->type : 0;
+
+ list[0][0].inst = list[1][0].inst = 0;
+ sel.p[0].p1= -1;
+ /* Execute machine once for each character, including terminal NUL */
+ for(;;--p){
+ doloop:
+ if((c = filereadc(f, p - 1))==-1){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to end */
+ if(sel.p[0].p1>=0)
+ case 3:
+ goto Return;
+ list[0][0].inst = list[1][0].inst = 0;
+ p = f->nc;
+ goto doloop;
+ default:
+ goto Return;
+ }
+ }else if(((wrapped && p<=startp) || sel.p[0].p1>0) && nnl==0)
+ break;
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = 0;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.p[0].p1<0 && (!wrapped || p>startp)){
+ /* Add first instruction to this list */
+ /* the minus is so the optimizations in addinst work */
+ sempty.p[0].p1 = -p;
+ if(addinst(tl, bstartinst, &sempty))
+ if(++ntl >= NLIST)
+ Overflow:
+ error(Eoverflow);
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type == c){
+ Addinst:
+ if(addinst(nl, inst->next, &tlp->se))
+ if(++nnl >= NLIST)
+ goto Overflow;
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid >= 0)
+ tlp->se.p[inst->subid].p2 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c != '\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(c=='\n' || p==0){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(p==f->nc || filereadc(f, p)=='\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(addinst(tl, inst->right, &tlp->se))
+ if(++ntl >= NLIST)
+ goto Overflow;
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.p[0].p1 = -tlp->se.p[0].p1; /* minus sign */
+ tlp->se.p[0].p2 = p;
+ bnewmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ return sel.p[0].p1>=0;
+}
+
+void
+bnewmatch(Rangeset *sp)
+{
+ int i;
+ if(sel.p[0].p1<0 || sp->p[0].p1>sel.p[0].p2 || (sp->p[0].p1==sel.p[0].p2 && sp->p[0].p2<sel.p[0].p1))
+ for(i = 0; i<NSUBEXP; i++){ /* note the reversal; p1<=p2 */
+ sel.p[i].p1 = sp->p[i].p2;
+ sel.p[i].p2 = sp->p[i].p1;
+ }
+}
--- /dev/null
+++ b/sam/sam.c
@@ -1,0 +1,731 @@
+#include "sam.h"
+
+Rune genbuf[BLOCKSIZE];
+int io;
+int panicking;
+int rescuing;
+String genstr;
+String curwd;
+String cmdstr;
+Rune empty[] = { 0 };
+char *genc;
+File *curfile;
+File *flist;
+File *cmd;
+jmp_buf mainloop;
+List tempfile = { 'p' };
+int quitok = TRUE;
+int downloaded;
+int dflag;
+int Rflag;
+char *machine;
+char *home;
+int bpipeok;
+int termlocked;
+char *samterm = SAMTERM;
+char *rsamname = RSAM;
+File *lastfile;
+Disk *disk;
+long seq;
+
+void usage(void);
+
+void main(int argc, char *argv[])
+{
+ int i;
+ String *t;
+ char *termargs[10], **ap;
+
+ ap = termargs;
+ *ap++ = "samterm";
+ ARGBEGIN{
+ case 'd':
+ dflag++;
+ break;
+ case 'r':
+ machine = EARGF(usage());
+ break;
+ case 'R':
+ Rflag++;
+ break;
+ case 't':
+ samterm = EARGF(usage());
+ break;
+ case 's':
+ rsamname = EARGF(usage());
+ break;
+ default:
+ dprint("sam: unknown flag %c\n", ARGC());
+ usage();
+ /* options for samterm */
+ case 'a':
+ *ap++ = "-a";
+ if(ap >= termargs+nelem(termargs))
+ usage();
+ break;
+ case 'i':
+ *ap++ = "-i";
+ if(ap >= termargs+nelem(termargs))
+ usage();
+ break;
+ }ARGEND
+ *ap = nil;
+
+ Strinit(&cmdstr);
+ Strinit0(&lastpat);
+ Strinit0(&lastregexp);
+ Strinit0(&genstr);
+ Strinit0(&curwd);
+ Strinit0(&plan9cmd);
+ home = getenv(HOME);
+ disk = diskinit();
+ if(home == 0)
+ home = "/";
+ if(!dflag)
+ startup(machine, Rflag, termargs, argv);
+ notify(notifyf);
+ getcurwd();
+ if(argc>0){
+ for(i=0; i<argc; i++){
+ if(!setjmp(mainloop)){
+ t = tmpcstr(argv[i]);
+ Straddc(t, '\0');
+ Strduplstr(&genstr, t);
+ freetmpstr(t);
+ fixname(&genstr);
+ logsetname(newfile(), &genstr);
+ }
+ }
+ }else if(!downloaded)
+ newfile();
+ seq++;
+ if(file.nused)
+ current(file.filepptr[0]);
+ setjmp(mainloop);
+ cmdloop();
+ trytoquit(); /* if we already q'ed, quitok will be TRUE */
+ exits(0);
+}
+
+void
+usage(void)
+{
+ dprint("usage: sam [-d] [-t samterm] [-s sam name] -r machine\n");
+ exits("usage");
+}
+
+void
+rescue(void)
+{
+ int i, nblank = 0;
+ File *f;
+ char *c;
+ char buf[256];
+
+ if(rescuing++)
+ return;
+ io = -1;
+ for(i=0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f==cmd || f->nc==0 || !fileisdirty(f))
+ continue;
+ if(io == -1){
+ sprint(buf, "%s/sam.save", home);
+ io = create(buf, 1, 0777);
+ if(io<0)
+ return;
+ }
+ if(f->name.s[0]){
+ c = Strtoc(&f->name);
+ strncpy(buf, c, sizeof buf-1);
+ buf[sizeof buf-1] = 0;
+ free(c);
+ }else
+ sprint(buf, "nameless.%d", nblank++);
+ fprint(io, "#!%s '%s' $* <<'---%s'\n", SAMSAVECMD, buf, buf);
+ addr.r.p1 = 0, addr.r.p2 = f->nc;
+ writeio(f);
+ fprint(io, "\n---%s\n", (char *)buf);
+ }
+}
+
+void
+panic(char *s)
+{
+ int wasd;
+
+ if(!panicking++ && !setjmp(mainloop)){
+ wasd = downloaded;
+ downloaded = 0;
+ dprint("sam: panic: %s: %r\n", s);
+ if(wasd)
+ fprint(2, "sam: panic: %s: %r\n", s);
+ rescue();
+ abort();
+ }
+}
+
+void
+hiccough(char *s)
+{
+ File *f;
+ int i;
+
+ if(rescuing)
+ exits("rescue");
+ if(s)
+ dprint("%s\n", s);
+ resetcmd();
+ resetxec();
+ resetsys();
+ if(io > 0)
+ close(io);
+
+ /*
+ * back out any logged changes & restore old sequences
+ */
+ for(i=0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f==cmd)
+ continue;
+ if(f->seq==seq){
+ bufdelete(&f->epsilon, 0, f->epsilon.nc);
+ f->seq = f->prevseq;
+ f->dot.r = f->prevdot;
+ f->mark = f->prevmark;
+ state(f, f->prevmod ? Dirty: Clean);
+ }
+ }
+
+ update();
+ if (curfile) {
+ if (curfile->unread)
+ curfile->unread = FALSE;
+ else if (downloaded)
+ outTs(Hcurrent, curfile->tag);
+ }
+ longjmp(mainloop, 1);
+}
+
+void
+intr(void)
+{
+ error(Eintr);
+}
+
+void
+trytoclose(File *f)
+{
+ char *t;
+ char buf[256];
+
+ if(f == cmd) /* possible? */
+ return;
+ if(f->deleted)
+ return;
+ if(fileisdirty(f) && !f->closeok){
+ f->closeok = TRUE;
+ if(f->name.s[0]){
+ t = Strtoc(&f->name);
+ strncpy(buf, t, sizeof buf-1);
+ free(t);
+ }else
+ strcpy(buf, "nameless file");
+ error_s(Emodified, buf);
+ }
+ f->deleted = TRUE;
+}
+
+void
+trytoquit(void)
+{
+ int c;
+ File *f;
+
+ if(!quitok){
+ for(c = 0; c<file.nused; c++){
+ f = file.filepptr[c];
+ if(f!=cmd && fileisdirty(f)){
+ quitok = TRUE;
+ eof = FALSE;
+ error(Echanges);
+ }
+ }
+ }
+}
+
+void
+load(File *f)
+{
+ Address saveaddr;
+
+ Strduplstr(&genstr, &f->name);
+ filename(f);
+ if(f->name.s[0]){
+ saveaddr = addr;
+ edit(f, 'I');
+ addr = saveaddr;
+ }else{
+ f->unread = 0;
+ f->cleanseq = f->seq;
+ }
+
+ fileupdate(f, TRUE, TRUE);
+}
+
+void
+cmdupdate(void)
+{
+ if(cmd && cmd->seq!=0){
+ fileupdate(cmd, FALSE, downloaded);
+ cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nc;
+ telldot(cmd);
+ }
+}
+
+void
+delete(File *f)
+{
+ if(downloaded && f->rasp)
+ outTs(Hclose, f->tag);
+ delfile(f);
+ if(f == curfile)
+ current(0);
+}
+
+void
+update(void)
+{
+ int i, anymod;
+ File *f;
+
+ settempfile();
+ for(anymod = i=0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f==cmd) /* cmd gets done in main() */
+ continue;
+ if(f->deleted) {
+ delete(f);
+ continue;
+ }
+ if(f->seq==seq && fileupdate(f, FALSE, downloaded))
+ anymod++;
+ if(f->rasp)
+ telldot(f);
+ }
+ if(anymod)
+ seq++;
+}
+
+File *
+current(File *f)
+{
+ return curfile = f;
+}
+
+void
+edit(File *f, int cmd)
+{
+ int empty = TRUE;
+ Posn p;
+ int nulls;
+
+ if(cmd == 'r')
+ logdelete(f, addr.r.p1, addr.r.p2);
+ if(cmd=='e' || cmd=='I'){
+ logdelete(f, (Posn)0, f->nc);
+ addr.r.p2 = f->nc;
+ }else if(f->nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
+ empty = FALSE;
+ if((io = open(genc, OREAD))<0) {
+ if (curfile && curfile->unread)
+ curfile->unread = FALSE;
+ error_r(Eopen, genc);
+ }
+ p = readio(f, &nulls, empty, TRUE);
+ closeio((cmd=='e' || cmd=='I')? -1 : p);
+ if(cmd == 'r')
+ f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
+ else
+ f->ndot.r.p1 = f->ndot.r.p2 = 0;
+ f->closeok = empty;
+ if (quitok)
+ quitok = empty;
+ else
+ quitok = FALSE;
+ state(f, empty && !nulls? Clean : Dirty);
+ if(empty && !nulls)
+ f->cleanseq = f->seq;
+ if(cmd == 'e')
+ filename(f);
+}
+
+int
+getname(File *f, String *s, int save)
+{
+ int c, i;
+
+ Strzero(&genstr);
+ if(genc){
+ free(genc);
+ genc = 0;
+ }
+ if(s==0 || (c = s->s[0])==0){ /* no name provided */
+ if(f)
+ Strduplstr(&genstr, &f->name);
+ goto Return;
+ }
+ if(c!=' ' && c!='\t')
+ error(Eblank);
+ for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
+ ;
+ while(s->s[i] > ' ')
+ Straddc(&genstr, s->s[i++]);
+ if(s->s[i])
+ error(Enewline);
+ fixname(&genstr);
+ if(f && (save || f->name.s[0]==0)){
+ logsetname(f, &genstr);
+ if(Strcmp(&f->name, &genstr)){
+ quitok = f->closeok = FALSE;
+ f->qidpath = 0;
+ f->mtime = 0;
+ state(f, Dirty); /* if it's 'e', fix later */
+ }
+ }
+ Return:
+ genc = Strtoc(&genstr);
+ i = genstr.n;
+ if(i && genstr.s[i-1]==0)
+ i--;
+ return i; /* strlen(name) */
+}
+
+void
+filename(File *f)
+{
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ dprint("%c%c%c %s\n", " '"[f->mod],
+ "-+"[f->rasp!=0], " ."[f==curfile], genc);
+}
+
+void
+undostep(File *f, int isundo)
+{
+ uint p1, p2;
+ int mod;
+
+ mod = f->mod;
+ fileundo(f, isundo, 1, &p1, &p2, TRUE);
+ f->ndot = f->dot;
+ if(f->mod){
+ f->closeok = 0;
+ quitok = 0;
+ }else
+ f->closeok = 1;
+
+ if(f->mod != mod){
+ f->mod = mod;
+ if(mod)
+ mod = Clean;
+ else
+ mod = Dirty;
+ state(f, mod);
+ }
+}
+
+int
+undo(int isundo)
+{
+ File *f;
+ int i;
+ Mod max;
+
+ max = undoseq(curfile, isundo);
+ if(max == 0)
+ return 0;
+ settempfile();
+ for(i = 0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f!=cmd && undoseq(f, isundo)==max)
+ undostep(f, isundo);
+ }
+ return 1;
+}
+
+int
+readcmd(String *s)
+{
+ int retcode;
+
+ if(flist != 0)
+ fileclose(flist);
+ flist = fileopen();
+
+ addr.r.p1 = 0, addr.r.p2 = flist->nc;
+ retcode = plan9(flist, '<', s, FALSE);
+ fileupdate(flist, FALSE, FALSE);
+ flist->seq = 0;
+ if (flist->nc > BLOCKSIZE)
+ error(Etoolong);
+ Strzero(&genstr);
+ Strinsure(&genstr, flist->nc);
+ bufread(flist, (Posn)0, genbuf, flist->nc);
+ memmove(genstr.s, genbuf, flist->nc*RUNESIZE);
+ genstr.n = flist->nc;
+ Straddc(&genstr, '\0');
+ return retcode;
+}
+
+void
+getcurwd(void)
+{
+ String *t;
+ char buf[256];
+
+ buf[0] = 0;
+ getwd(buf, sizeof(buf));
+ t = tmpcstr(buf);
+ Strduplstr(&curwd, t);
+ freetmpstr(t);
+ if(curwd.n == 0)
+ warn(Wpwd);
+ else if(curwd.s[curwd.n-1] != '/')
+ Straddc(&curwd, '/');
+}
+
+void
+cd(String *str)
+{
+ int i, fd;
+ char *s;
+ File *f;
+ String owd;
+
+ getcurwd();
+ if(getname((File *)0, str, FALSE))
+ s = genc;
+ else
+ s = home;
+ if(chdir(s))
+ syserror("chdir");
+ fd = open("/dev/wdir", OWRITE);
+ if(fd >= 0)
+ write(fd, s, strlen(s));
+ dprint("!\n");
+ Strinit(&owd);
+ Strduplstr(&owd, &curwd);
+ getcurwd();
+ settempfile();
+ for(i=0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
+ Strinsert(&f->name, &owd, (Posn)0);
+ fixname(&f->name);
+ sortname(f);
+ }else if(f != cmd && Strispre(&curwd, &f->name)){
+ fixname(&f->name);
+ sortname(f);
+ }
+ }
+ Strclose(&owd);
+}
+
+int
+loadflist(String *s, int blank)
+{
+ int c, i;
+
+ c = s->s[0];
+ for(i = 0; i < s->n && (s->s[i]==' ' || s->s[i]=='\t'); i++)
+ ;
+ if(blank == 0 || ((c==' ' || c=='\t') && s->s[i]!='\n')){
+ if(s->s[i]=='<'){
+ Strdelete(s, 0L, (long)i+1);
+ readcmd(s);
+ }else{
+ Strzero(&genstr);
+ while(i < s->n && (c = s->s[i++]) && c!='\n')
+ Straddc(&genstr, c);
+ Straddc(&genstr, '\0');
+ }
+ }else{
+ if(c != '\n')
+ error(Eblank);
+ Strdupl(&genstr, empty);
+ }
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ return genstr.s[0];
+}
+
+File *
+readflist(int readall, int delete)
+{
+ Posn i;
+ int c;
+ File *f;
+ String t;
+
+ Strinit(&t);
+ for(i=0,f=0; f==0 || readall || delete; i++){ /* ++ skips blank */
+ Strdelete(&genstr, (Posn)0, i);
+ for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
+ ;
+ if(i >= genstr.n)
+ break;
+ Strdelete(&genstr, (Posn)0, i);
+ for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
+ ;
+
+ if(i == 0)
+ break;
+ genstr.s[i] = 0;
+ Strduplstr(&t, tmprstr(genstr.s, i+1));
+ fixname(&t);
+ f = lookfile(&t);
+ if(delete){
+ if(f == 0)
+ warn_S(Wfile, &t);
+ else
+ trytoclose(f);
+ }else if(f==0 && readall)
+ logsetname(f = newfile(), &t);
+ }
+ Strclose(&t);
+ return f;
+}
+
+File *
+tofile(String *s, int blank)
+{
+ File *f;
+
+ if(blank && s->s[0] != ' ')
+ error(Eblank);
+ if(loadflist(s, blank) == 0){
+ f = lookfile(&genstr); /* empty string ==> nameless file */
+ if(f == 0)
+ error_s(Emenu, genc);
+ }else if((f=readflist(FALSE, FALSE)) == 0)
+ error_s(Emenu, genc);
+ return current(f);
+}
+
+File *
+getfile(String *s)
+{
+ File *f;
+
+ if(loadflist(s, 1) == 0)
+ logsetname(f = newfile(), &genstr);
+ else if((f=readflist(TRUE, FALSE)) == 0)
+ error(Eblank);
+ return current(f);
+}
+
+void
+closefiles(File *f, String *s)
+{
+ if(s->s[0] == 0){
+ if(f == 0)
+ error(Enofile);
+ trytoclose(f);
+ return;
+ }
+ if(s->s[0] != ' ')
+ error(Eblank);
+ if(loadflist(s, 1) == 0)
+ error(Enewline);
+ readflist(FALSE, TRUE);
+}
+
+void
+copy(File *f, Address addr2)
+{
+ Posn p;
+ int ni;
+ for(p=addr.r.p1; p<addr.r.p2; p+=ni){
+ ni = addr.r.p2-p;
+ if(ni > BLOCKSIZE)
+ ni = BLOCKSIZE;
+ bufread(f, p, genbuf, ni);
+ loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
+ }
+ addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
+ addr2.f->ndot.r.p1 = addr2.r.p2;
+}
+
+void
+move(File *f, Address addr2)
+{
+ if(addr.r.p2 <= addr2.r.p2){
+ logdelete(f, addr.r.p1, addr.r.p2);
+ copy(f, addr2);
+ }else if(addr.r.p1 >= addr2.r.p2){
+ copy(f, addr2);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ }else
+ error(Eoverlap);
+}
+
+Posn
+nlcount(File *f, Posn p0, Posn p1)
+{
+ Posn nl = 0;
+
+ while(p0 < p1)
+ if(filereadc(f, p0++)=='\n')
+ nl++;
+ return nl;
+}
+
+void
+printposn(File *f, int chars)
+{
+ Posn l1, l2;
+ char *s;
+
+ if(f->name.s[0]){
+ if(f->name.s[0]!='/'){
+ getcurwd();
+ s = Strtoc(&curwd);
+ dprint("%s", s);
+ free(s);
+ }
+ s = Strtoc(&f->name);
+ dprint("%s:", s);
+ free(s);
+ }
+ if(chars){
+ dprint("#%lud", addr.r.p1);
+ if(addr.r.p2 != addr.r.p1)
+ dprint(",#%lud", addr.r.p2);
+ }else{
+ l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
+ l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
+ /* check if addr ends with '\n' */
+ if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
+ --l2;
+ dprint("%lud", l1);
+ if(l2 != l1)
+ dprint(",%lud", l2);
+ }
+ dprint("\n");
+}
+
+void
+settempfile(void)
+{
+ if(tempfile.nalloc < file.nused){
+ if(tempfile.filepptr)
+ free(tempfile.filepptr);
+ tempfile.filepptr = emalloc(sizeof(File*)*file.nused);
+ tempfile.nalloc = file.nused;
+ }
+ memmove(tempfile.filepptr, file.filepptr, sizeof(File*)*file.nused);
+ tempfile.nused = file.nused;
+}
--- /dev/null
+++ b/sam/sam.h
@@ -1,0 +1,404 @@
+#include <u.h>
+#include <libc.h>
+#include <plumb.h>
+#include "errors.h"
+
+/*
+ * BLOCKSIZE is relatively small to keep memory consumption down.
+ */
+
+#define BLOCKSIZE 2048
+#define RUNESIZE sizeof(Rune)
+#define NDISC 5
+#define NBUFFILES 3+2*NDISC /* plan 9+undo+snarf+NDISC*(transcript+buf) */
+#define NSUBEXP 10
+
+#define TRUE 1
+#define FALSE 0
+
+#define INFINITY 0x7FFFFFFFL
+#define INCR 25
+#define STRSIZE (512<<20)
+
+typedef long Posn; /* file position or address */
+typedef ushort Mod; /* modification number */
+
+typedef struct Address Address;
+typedef struct Block Block;
+typedef struct Buffer Buffer;
+typedef struct Disk Disk;
+typedef struct Discdesc Discdesc;
+typedef struct File File;
+typedef struct List List;
+typedef struct Range Range;
+typedef struct Rangeset Rangeset;
+typedef struct String String;
+
+enum State
+{
+ Clean = ' ',
+ Dirty = '\'',
+ Unread = '-',
+};
+
+struct Range
+{
+ Posn p1, p2;
+};
+
+struct Rangeset
+{
+ Range p[NSUBEXP];
+};
+
+struct Address
+{
+ Range r;
+ File *f;
+};
+
+struct String
+{
+ short n;
+ short size;
+ Rune *s;
+};
+
+struct List
+{
+ int type; /* 'p' for pointer, 'P' for Posn */
+ int nalloc;
+ int nused;
+ union{
+ void* listp;
+ void** voidp;
+ Posn* posnp;
+ String**stringp;
+ File** filep;
+ }g;
+};
+
+#define listptr g.listp
+#define voidpptr g.voidp
+#define posnptr g.posnp
+#define stringpptr g.stringp
+#define filepptr g.filep
+
+enum
+{
+ Blockincr = 256,
+ Maxblock = 8*1024,
+
+ BUFSIZE = Maxblock, /* size from fbufalloc() */
+ RBUFSIZE = BUFSIZE/sizeof(Rune),
+};
+
+
+enum
+{
+ Null = '-',
+ Delete = 'd',
+ Insert = 'i',
+ Filename = 'f',
+ Dot = 'D',
+ Mark = 'm',
+};
+
+struct Block
+{
+ uint addr; /* disk address in bytes */
+ union
+ {
+ uint n; /* number of used runes in block */
+ Block *next; /* pointer to next in free list */
+ };
+};
+
+struct Disk
+{
+ int fd;
+ vlong addr; /* length of temp file */
+ Block *free[Maxblock/Blockincr+1];
+};
+
+Disk* diskinit(void);
+Block* disknewblock(Disk*, uint);
+void diskrelease(Disk*, Block*);
+void diskread(Disk*, Block*, Rune*, uint);
+void diskwrite(Disk*, Block**, Rune*, uint);
+
+struct Buffer
+{
+ uint nc;
+ Rune *c; /* cache */
+ uint cnc; /* bytes in cache */
+ uint cmax; /* size of allocated cache */
+ uint cq; /* position of cache */
+ int cdirty; /* cache needs to be written */
+ uint cbi; /* index of cache Block */
+ Block **bl; /* array of blocks */
+ uint nbl; /* number of blocks */
+};
+void bufinsert(Buffer*, uint, Rune*, uint);
+void bufdelete(Buffer*, uint, uint);
+uint bufload(Buffer*, uint, int, int*);
+void bufread(Buffer*, uint, Rune*, uint);
+void bufclose(Buffer*);
+void bufreset(Buffer*);
+
+struct File
+{
+ Buffer; /* the data */
+ Buffer delta; /* transcript of changes */
+ Buffer epsilon; /* inversion of delta for redo */
+ String name; /* name of associated file */
+ uvlong qidpath; /* of file when read */
+ uint mtime; /* of file when read */
+ int dev; /* of file when read */
+ int unread; /* file has not been read from disk */
+
+ long seq; /* if seq==0, File acts like Buffer */
+ long cleanseq; /* f->seq at last read/write of file */
+ int mod; /* file appears modified in menu */
+ char rescuing; /* sam exiting; this file unusable */
+
+// Text *curtext; /* most recently used associated text */
+// Text **text; /* list of associated texts */
+// int ntext;
+// int dumpid; /* used in dumping zeroxed windows */
+
+ Posn hiposn; /* highest address touched this Mod */
+ Address dot; /* current position */
+ Address ndot; /* new current position after update */
+ Range tdot; /* what terminal thinks is current range */
+ Range mark; /* tagged spot in text (don't confuse with Mark) */
+ List *rasp; /* map of what terminal's got */
+ short tag; /* for communicating with terminal */
+ char closeok; /* ok to close file? */
+ char deleted; /* delete at completion of command */
+ Range prevdot; /* state before start of change */
+ Range prevmark;
+ long prevseq;
+ int prevmod;
+};
+//File* fileaddtext(File*, Text*);
+void fileclose(File*);
+void filedelete(File*, uint, uint);
+//void filedeltext(File*, Text*);
+void fileinsert(File*, uint, Rune*, uint);
+uint fileload(File*, uint, int, int*);
+void filemark(File*);
+void filereset(File*);
+void filesetname(File*, String*);
+void fileundelete(File*, Buffer*, uint, uint);
+void fileuninsert(File*, Buffer*, uint, uint);
+void fileunsetname(File*, Buffer*);
+void fileundo(File*, int, int, uint*, uint*, int);
+int fileupdate(File*, int, int);
+
+int filereadc(File*, uint);
+File *fileopen(void);
+void loginsert(File*, uint, Rune*, uint);
+void logdelete(File*, uint, uint);
+void logsetname(File*, String*);
+int fileisdirty(File*);
+long undoseq(File*, int);
+long prevseq(Buffer*);
+
+void raspload(File*);
+void raspstart(File*);
+void raspdelete(File*, uint, uint, int);
+void raspinsert(File*, uint, Rune*, uint, int);
+void raspdone(File*, int);
+void raspflush(File*);
+
+/*
+ * acme fns
+ */
+void* fbufalloc(void);
+void fbuffree(void*);
+uint min(uint, uint);
+void cvttorunes(char*, int, Rune*, int*, int*, int*);
+
+#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune))
+#define runerealloc(a, b) (Rune*)realloc((a), (b)*sizeof(Rune))
+#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune))
+
+int alnum(int);
+int Read(int, void*, int);
+void Seek(int, long, int);
+int plan9(File*, int, String*, int);
+int Write(int, void*, int);
+int bexecute(File*, Posn);
+void cd(String*);
+void closefiles(File*, String*);
+void closeio(Posn);
+void cmdloop(void);
+void cmdupdate(void);
+void compile(String*);
+void copy(File*, Address);
+File *current(File*);
+void delete(File*);
+void delfile(File*);
+void dellist(List*, int);
+void stretchsel(File*, Posn, int);
+void dprint(char*, ...);
+void edit(File*, int);
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+void error(Err);
+void error_c(Err, int);
+void error_r(Err, char*);
+void error_s(Err, char*);
+int execute(File*, Posn, Posn);
+int filematch(File*, String*);
+void filename(File*);
+void fixname(String*);
+void fullname(String*);
+void getcurwd(void);
+File *getfile(String*);
+int getname(File*, String*, int);
+long getnum(int);
+void hiccough(char*);
+void inslist(List*, int, ...);
+Address lineaddr(Posn, Address, int);
+List *listalloc(int);
+void listfree(List*);
+void load(File*);
+File *lookfile(String*);
+void lookorigin(File*, Posn, Posn);
+int lookup(int);
+void move(File*, Address);
+void moveto(File*, Range);
+File *newfile(void);
+void nextmatch(File*, String*, Posn, int);
+void notifyf(void*, char*);
+void panic(char*);
+void printposn(File*, int);
+void print_ss(char*, String*, String*);
+void print_s(char*, String*);
+int rcv(void);
+Range rdata(List*, Posn, Posn);
+Posn readio(File*, int*, int, int);
+void rescue(void);
+void resetcmd(void);
+void resetsys(void);
+void resetxec(void);
+void rgrow(List*, Posn, Posn);
+void samerr(char*);
+void settempfile(void);
+int skipbl(void);
+void snarf(File*, Posn, Posn, Buffer*, int);
+void sortname(File*);
+void startup(char*, int, char**, char**);
+void state(File*, int);
+int statfd(int, ulong*, uvlong*, long*, long*, long*);
+int statfile(char*, ulong*, uvlong*, long*, long*, long*);
+void Straddc(String*, int);
+void Strclose(String*);
+int Strcmp(String*, String*);
+void Strdelete(String*, Posn, Posn);
+void Strdupl(String*, Rune*);
+void Strduplstr(String*, String*);
+void Strinit(String*);
+void Strinit0(String*);
+void Strinsert(String*, String*, Posn);
+void Strinsure(String*, ulong);
+int Strispre(String*, String*);
+void Strzero(String*);
+int Strlen(Rune*);
+char *Strtoc(String*);
+void syserror(char*);
+void telldot(File*);
+void tellpat(void);
+String *tmpcstr(char*);
+String *tmprstr(Rune*, int);
+void freetmpstr(String*);
+void termcommand(void);
+void termwrite(char*);
+File *tofile(String*, int);
+void trytoclose(File*);
+void trytoquit(void);
+int undo(int);
+void update(void);
+char *waitfor(int);
+void warn(Warn);
+void warn_s(Warn, char*);
+void warn_SS(Warn, String*, String*);
+void warn_S(Warn, String*);
+int whichmenu(File*);
+void writef(File*);
+Posn writeio(File*);
+Discdesc *Dstart(void);
+
+extern Rune samname[]; /* compiler dependent */
+extern Rune *left[];
+extern Rune *right[];
+
+extern char RSAM[]; /* system dependent */
+extern char SAMTERM[];
+extern char HOME[];
+extern char TMPDIR[];
+extern char SH[];
+extern char SHPATH[];
+extern char RX[];
+extern char RXPATH[];
+extern char SAMSAVECMD[];
+
+/*
+ * acme globals
+ */
+extern long seq;
+extern Disk *disk;
+
+extern char *rsamname; /* globals */
+extern char *samterm;
+extern Rune genbuf[];
+extern char *genc;
+extern int io;
+extern int patset;
+extern int quitok;
+extern Address addr;
+extern Buffer snarfbuf;
+extern Buffer plan9buf;
+extern Buffer cmdbuf;
+extern int cmdbufpos;
+extern List file;
+extern List tempfile;
+extern File *cmd;
+extern File *curfile;
+extern File *lastfile;
+extern Mod modnum;
+extern Posn cmdpt;
+extern Posn cmdptadv;
+extern Rangeset sel;
+extern String curwd;
+extern String cmdstr;
+extern String genstr;
+extern String lastpat;
+extern String lastregexp;
+extern String plan9cmd;
+extern int downloaded;
+extern int eof;
+extern int bpipeok;
+extern int panicking;
+extern Rune empty[];
+extern int termlocked;
+extern int outbuffered;
+
+#include "mesg.h"
+
+void outTs(Hmesg, int);
+void outT0(Hmesg);
+void outTl(Hmesg, long);
+void outTslS(Hmesg, int, long, String*);
+void outTS(Hmesg, String*);
+void outTsS(Hmesg, int, String*);
+void outTsllS(Hmesg, int, long, long, String*);
+void outTsll(Hmesg, int, long, long);
+void outTsl(Hmesg, int, long);
+void outTsv(Hmesg, int, vlong);
+void outflush(void);
+int needoutflush(void);
+
+Posn nlcount(File *f, Posn p0, Posn p1);
--- /dev/null
+++ b/sam/shell.c
@@ -1,0 +1,191 @@
+#include "sam.h"
+#include "parse.h"
+
+extern jmp_buf mainloop;
+
+char errfile[64];
+String plan9cmd; /* null terminated */
+Buffer plan9buf;
+void checkerrs(void);
+Buffer cmdbuf;
+int cmdbufpos;
+
+static void
+updateenv(File *f)
+{
+ static int fd = -1;
+ int n;
+ char buf[64], *p, *e;
+
+ if(f == nil){
+ putenv("%", "");
+ putenv("%dot", "");
+ return;
+ }
+
+ p = Strtoc(&f->name);
+ putenv("%", p);
+ free(p);
+
+ p = buf;
+ e = buf+sizeof(buf);
+ p = seprint(p, e, "%lud", 1+nlcount(f, 0, f->dot.r.p1));
+ p = seprint(p+1, e, "%lud", f->dot.r.p1);
+ p = seprint(p+1, e, "%lud", f->dot.r.p2);
+ n = p - buf;
+ if(fd == -1)
+ if((fd = create("/env/%dot", OWRITE, 0666)) < 0)
+ fprint(2, "updateenv create: %r\n");
+ if(write(fd, buf, n) != n)
+ fprint(2, "updateenv write: %r\n");
+}
+
+int
+plan9(File *f, int type, String *s, int nest)
+{
+ long l;
+ int m;
+ int pid, fd;
+ int retcode;
+ char *retmsg;
+ int pipe1[2], pipe2[2];
+
+ if(s->s[0]==0 && plan9cmd.s[0]==0)
+ error(Enocmd);
+ else if(s->s[0])
+ Strduplstr(&plan9cmd, s);
+ if(downloaded){
+ samerr(errfile);
+ remove(errfile);
+ }
+ if(type!='!' && pipe(pipe1)==-1)
+ error(Epipe);
+ if(type=='|' || type=='_')
+ snarf(f, addr.r.p1, addr.r.p2, &plan9buf, 1);
+ if((pid=fork()) == 0){
+ if(downloaded){ /* also put nasty fd's into errfile */
+ fd = create(errfile, 1, 0666L);
+ if(fd < 0)
+ fd = create("/dev/null", 1, 0666L);
+ dup(fd, 2);
+ close(fd);
+ /* 2 now points at err file */
+ if(type == '>')
+ dup(2, 1);
+ else if(type=='!'){
+ dup(2, 1);
+ fd = open("/dev/null", 0);
+ dup(fd, 0);
+ close(fd);
+ }
+ }
+ if(type != '!') {
+ if(type == '>')
+ dup(pipe1[0], 0);
+ else
+ dup(pipe1[1], 1);
+ close(pipe1[0]);
+ close(pipe1[1]);
+ }
+ if(type == '|' || type == '_'){
+ if(pipe(pipe2) == -1)
+ exits("pipe");
+ if((pid = fork())==0){
+ /*
+ * It's ok if we get SIGPIPE here
+ */
+ close(pipe2[0]);
+ io = pipe2[1];
+ if(retcode=!setjmp(mainloop)){ /* assignment = */
+ char *c;
+ for(l = 0; l<plan9buf.nc; l+=m){
+ m = plan9buf.nc-l;
+ if(m>BLOCKSIZE-1)
+ m = BLOCKSIZE-1;
+ bufread(&plan9buf, l, genbuf, m);
+ genbuf[m] = 0;
+ c = Strtoc(tmprstr(genbuf, m+1));
+ Write(pipe2[1], c, strlen(c));
+ free(c);
+ }
+ }
+ exits(retcode? "error" : 0);
+ }
+ if(pid==-1){
+ fprint(2, "Can't fork?!\n");
+ exits("fork");
+ }
+ dup(pipe2[0], 0);
+ close(pipe2[0]);
+ close(pipe2[1]);
+ }
+ if(type=='<' || type=='^'){
+ close(0); /* so it won't read from terminal */
+ open("/dev/null", 0);
+ }
+ updateenv(f);
+ execl(SHPATH, SH, "-c", Strtoc(&plan9cmd), nil);
+ exits("exec");
+ }
+ if(pid == -1)
+ error(Efork);
+ if(type=='<' || type=='|'){
+ int nulls;
+ if(downloaded && addr.r.p1 != addr.r.p2)
+ outTl(Hsnarflen, addr.r.p2-addr.r.p1);
+ snarf(f, addr.r.p1, addr.r.p2, &snarfbuf, 0);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ close(pipe1[1]);
+ io = pipe1[0];
+ f->tdot.p1 = -1;
+ f->ndot.r.p2 = addr.r.p2+readio(f, &nulls, 0, FALSE);
+ f->ndot.r.p1 = addr.r.p2;
+ closeio((Posn)-1);
+ }else if(type=='>'){
+ close(pipe1[0]);
+ io = pipe1[1];
+ bpipeok = 1;
+ writeio(f);
+ bpipeok = 0;
+ closeio((Posn)-1);
+ }else if(type == '^' || type == '_'){
+ int nulls;
+ close(pipe1[1]);
+ bufload(&cmdbuf, cmdbufpos, pipe1[0], &nulls);
+ close(pipe1[0]);
+ }
+ retmsg = waitfor(pid);
+ if(type=='|' || type=='<' || type=='_' || type=='^')
+ if(retmsg[0]!=0)
+ warn_s(Wbadstatus, retmsg);
+ if(downloaded)
+ checkerrs();
+ if(!nest)
+ dprint("!\n");
+ return retmsg[0] ? -1 : 0;
+}
+
+void
+checkerrs(void)
+{
+ char buf[256];
+ int f, n, nl;
+ char *p;
+ long l;
+
+ if(statfile(errfile, 0, 0, 0, &l, 0) > 0 && l != 0){
+ if((f=open((char *)errfile, 0)) != -1){
+ if((n=read(f, buf, sizeof buf-1)) > 0){
+ for(nl=0,p=buf; nl<3 && p<&buf[n]; p++)
+ if(*p=='\n')
+ nl++;
+ *p = 0;
+ dprint("%s", buf);
+ if(p-buf < l-1)
+ dprint("(sam: more in %s)\n", errfile);
+ }
+ close(f);
+ }
+ }else
+ remove((char *)errfile);
+}
--- /dev/null
+++ b/sam/string.c
@@ -1,0 +1,193 @@
+#include "sam.h"
+
+#define MINSIZE 16 /* minimum number of chars allocated */
+#define MAXSIZE 256 /* maximum number of chars for an empty string */
+
+
+void
+Strinit(String *p)
+{
+ p->s = emalloc(MINSIZE*RUNESIZE);
+ p->n = 0;
+ p->size = MINSIZE;
+}
+
+void
+Strinit0(String *p)
+{
+ p->s = emalloc(MINSIZE*RUNESIZE);
+ p->s[0] = 0;
+ p->n = 1;
+ p->size = MINSIZE;
+}
+
+void
+Strclose(String *p)
+{
+ free(p->s);
+}
+
+void
+Strzero(String *p)
+{
+ if(p->size > MAXSIZE){
+ p->s = erealloc(p->s, RUNESIZE*MAXSIZE); /* throw away the garbage */
+ p->size = MAXSIZE;
+ }
+ p->n = 0;
+}
+
+int
+Strlen(Rune *r)
+{
+ Rune *s;
+
+ for(s=r; *s; s++)
+ ;
+ return s-r;
+}
+
+void
+Strdupl(String *p, Rune *s) /* copies the null */
+{
+ p->n = Strlen(s)+1;
+ Strinsure(p, p->n);
+ memmove(p->s, s, p->n*RUNESIZE);
+}
+
+void
+Strduplstr(String *p, String *q) /* will copy the null if there's one there */
+{
+ Strinsure(p, q->n);
+ p->n = q->n;
+ memmove(p->s, q->s, q->n*RUNESIZE);
+}
+
+void
+Straddc(String *p, int c)
+{
+ Strinsure(p, p->n+1);
+ p->s[p->n++] = c;
+}
+
+void
+Strinsure(String *p, ulong n)
+{
+ if(n > STRSIZE)
+ error(Etoolong);
+ if(p->size < n){ /* p needs to grow */
+ n += 100;
+ p->s = erealloc(p->s, n*RUNESIZE);
+ p->size = n;
+ }
+}
+
+void
+Strinsert(String *p, String *q, Posn p0)
+{
+ Strinsure(p, p->n+q->n);
+ memmove(p->s+p0+q->n, p->s+p0, (p->n-p0)*RUNESIZE);
+ memmove(p->s+p0, q->s, q->n*RUNESIZE);
+ p->n += q->n;
+}
+
+void
+Strdelete(String *p, Posn p1, Posn p2)
+{
+ memmove(p->s+p1, p->s+p2, (p->n-p2)*RUNESIZE);
+ p->n -= p2-p1;
+}
+
+int
+Strcmp(String *a, String *b)
+{
+ int i, c;
+
+ for(i=0; i<a->n && i<b->n; i++)
+ if(c = (a->s[i] - b->s[i])) /* assign = */
+ return c;
+ /* damn NULs confuse everything */
+ i = a->n - b->n;
+ if(i == 1){
+ if(a->s[a->n-1] == 0)
+ return 0;
+ }else if(i == -1){
+ if(b->s[b->n-1] == 0)
+ return 0;
+ }
+ return i;
+}
+
+int
+Strispre(String *a, String *b)
+{
+ int i;
+
+ for(i=0; i<a->n && i<b->n; i++){
+ if(a->s[i] - b->s[i]){ /* assign = */
+ if(a->s[i] == 0)
+ return 1;
+ return 0;
+ }
+ }
+ return i == a->n;
+}
+
+char*
+Strtoc(String *s)
+{
+ int i;
+ char *c, *d;
+ Rune *r;
+ c = emalloc(s->n*UTFmax + 1); /* worst case UTFmax bytes per rune, plus NUL */
+ d = c;
+ r = s->s;
+ for(i=0; i<s->n; i++)
+ d += runetochar(d, r++);
+ if(d==c || d[-1]!=0)
+ *d = 0;
+ return c;
+
+}
+
+/*
+ * Build very temporary String from Rune*
+ */
+String*
+tmprstr(Rune *r, int n)
+{
+ static String p;
+
+ p.s = r;
+ p.n = n;
+ p.size = n;
+ return &p;
+}
+
+/*
+ * Convert null-terminated char* into String
+ */
+String*
+tmpcstr(char *s)
+{
+ String *p;
+ Rune *r;
+ int i, n;
+
+ n = utflen(s); /* don't include NUL */
+ p = emalloc(sizeof(String));
+ r = emalloc(n*RUNESIZE);
+ p->s = r;
+ for(i=0; i<n; i++,r++)
+ s += chartorune(r, s);
+ p->n = n;
+ p->size = n;
+ return p;
+}
+
+void
+freetmpstr(String *s)
+{
+ free(s->s);
+ free(s);
+}
--- /dev/null
+++ b/sam/sys.c
@@ -1,0 +1,60 @@
+#include "sam.h"
+
+static int inerror=FALSE;
+
+/*
+ * A reasonable interface to the system calls
+ */
+
+void
+resetsys(void)
+{
+ inerror = FALSE;
+}
+
+void
+syserror(char *a)
+{
+ char buf[ERRMAX];
+
+ if(!inerror){
+ inerror=TRUE;
+ errstr(buf, sizeof buf);
+ dprint("%s: ", a);
+ error_s(Eio, buf);
+ }
+}
+
+int
+Read(int f, void *a, int n)
+{
+ char buf[ERRMAX];
+
+ if(read(f, (char *)a, n)!=n) {
+ if (lastfile)
+ lastfile->rescuing = 1;
+ errstr(buf, sizeof buf);
+ if (downloaded)
+ fprint(2, "read error: %s\n", buf);
+ rescue();
+ exits("read");
+ }
+ return n;
+}
+
+int
+Write(int f, void *a, int n)
+{
+ int m;
+
+ if((m=write(f, (char *)a, n))!=n)
+ syserror("write");
+ return m;
+}
+
+void
+Seek(int f, long n, int w)
+{
+ if(seek(f, n, w)==-1)
+ syserror("seek");
+}
--- /dev/null
+++ b/sam/util.c
@@ -1,0 +1,54 @@
+#include "sam.h"
+
+void
+cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
+{
+ uchar *q;
+ Rune *s;
+ int j, w;
+
+ /*
+ * Always guaranteed that n bytes may be interpreted
+ * without worrying about partial runes. This may mean
+ * reading up to UTFmax-1 more bytes than n; the caller
+ * knows this. If n is a firm limit, the caller should
+ * set p[n] = 0.
+ */
+ q = (uchar*)p;
+ s = r;
+ for(j=0; j<n; j+=w){
+ if(*q < Runeself){
+ w = 1;
+ *s = *q++;
+ }else{
+ w = chartorune(s, (char*)q);
+ q += w;
+ }
+ if(*s)
+ s++;
+ else if(nulls)
+ *nulls = TRUE;
+ }
+ *nb = (char*)q-p;
+ *nr = s-r;
+}
+
+void*
+fbufalloc(void)
+{
+ return emalloc(BUFSIZE);
+}
+
+void
+fbuffree(void *f)
+{
+ free(f);
+}
+
+uint
+min(uint a, uint b)
+{
+ if(a < b)
+ return a;
+ return b;
+}
--- /dev/null
+++ b/sam/xec.c
@@ -1,0 +1,532 @@
+#include "sam.h"
+#include "parse.h"
+
+int Glooping;
+int nest;
+int newcur;
+
+int append(File*, Cmd*, Posn);
+int display(File*);
+void looper(File*, Cmd*, int);
+void filelooper(Cmd*, int);
+void linelooper(File*, Cmd*);
+
+void
+resetxec(void)
+{
+ Glooping = nest = 0;
+}
+
+int
+cmdexec(File *f, Cmd *cp)
+{
+ int i;
+ Addr *ap;
+ Address a;
+
+ if(f && f->unread)
+ load(f);
+ if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
+ !utfrune("bBnqUXY!^M", cp->cmdc) &&
+ cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
+ error(Enofile);
+ i = lookup(cp->cmdc);
+ if(i >= 0 && cmdtab[i].defaddr != aNo){
+ if((ap=cp->addr)==0 && cp->cmdc!='\n'){
+ cp->addr = ap = newaddr();
+ ap->type = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap->type = '*';
+ }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
+ ap->next = newaddr();
+ ap->next->type = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap->next->type = '*';
+ }
+ if(cp->addr){ /* may be false for '\n' (only) */
+ static Address none = {0,0,0};
+ if(f)
+ addr = address(ap, f->dot, 0);
+ else /* a " */
+ addr = address(ap, none, 0);
+ f = addr.f;
+ }
+ }
+ current(f);
+ switch(cp->cmdc){
+ case '{':
+ a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
+ for(cp = cp->ccmd; cp; cp = cp->next){
+ a.f->dot = a;
+ cmdexec(a.f, cp);
+ }
+ break;
+ default:
+ i=(*cmdtab[i].fn)(f, cp);
+ return i;
+ }
+ return 1;
+}
+
+
+int
+a_cmd(File *f, Cmd *cp)
+{
+ return append(f, cp, addr.r.p2);
+}
+
+int
+b_cmd(File *f, Cmd *cp)
+{
+ String *s;
+ USED(f);
+ s = cp->ctext;
+ if(nest > 0 && s->s[0] == 0){
+ if(f == nil)
+ return TRUE;
+ tofile(&f->name, 0);
+ current(f);
+ newcur = 1;
+ }else{
+ f = cp->cmdc=='b'? tofile(s, 1) : getfile(s);
+ }
+ if(f->unread)
+ load(f);
+ else if(nest == 0 || newcur)
+ filename(f);
+ return TRUE;
+}
+
+int
+c_cmd(File *f, Cmd *cp)
+{
+ logdelete(f, addr.r.p1, addr.r.p2);
+ f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
+ return append(f, cp, addr.r.p2);
+}
+
+int
+d_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
+ return TRUE;
+}
+
+int
+D_cmd(File *f, Cmd *cp)
+{
+ closefiles(f, cp->ctext);
+ return TRUE;
+}
+
+int
+e_cmd(File *f, Cmd *cp)
+{
+ if(getname(f, cp->ctext, cp->cmdc=='e')==0)
+ error(Enoname);
+ edit(f, cp->cmdc);
+ return TRUE;
+}
+
+int
+f_cmd(File *f, Cmd *cp)
+{
+ getname(f, cp->ctext, TRUE);
+ filename(f);
+ return TRUE;
+}
+
+int
+g_cmd(File *f, Cmd *cp)
+{
+ if(f!=addr.f)panic("g_cmd f!=addr.f");
+ compile(cp->re);
+ if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
+ f->dot = addr;
+ return cmdexec(f, cp->ccmd);
+ }
+ return TRUE;
+}
+
+int
+i_cmd(File *f, Cmd *cp)
+{
+ return append(f, cp, addr.r.p1);
+}
+
+int
+k_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ f->mark = addr.r;
+ return TRUE;
+}
+
+int
+m_cmd(File *f, Cmd *cp)
+{
+ Address addr2;
+
+ addr2 = address(cp->caddr, f->dot, 0);
+ if(cp->cmdc=='m')
+ move(f, addr2);
+ else
+ copy(f, addr2);
+ return TRUE;
+}
+
+int
+M_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ if(downloaded)
+ outTS(Hmenucmd, cp->ctext);
+ else
+ dprint("not downloaded\n");
+ 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;
+}
+
+int
+p_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ return display(f);
+}
+
+int
+q_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ USED(f);
+ trytoquit();
+ if(downloaded){
+ outT0(Hexit);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+s_cmd(File *f, Cmd *cp)
+{
+ int i, j, c, n;
+ Posn p1, op, didsub = 0, delta = 0;
+
+ n = cp->num;
+ op= -1;
+ compile(cp->re);
+ for(p1 = addr.r.p1; p1<=addr.r.p2 && execute(f, p1, addr.r.p2); ){
+ if(sel.p[0].p1==sel.p[0].p2){ /* empty match? */
+ if(sel.p[0].p1==op){
+ p1++;
+ continue;
+ }
+ p1 = sel.p[0].p2+1;
+ }else
+ p1 = sel.p[0].p2;
+ op = sel.p[0].p2;
+ if(--n>0)
+ continue;
+ Strzero(&genstr);
+ for(i = 0; i<cp->ctext->n; i++)
+ if((c = cp->ctext->s[i])=='\\' && i<cp->ctext->n-1){
+ c = cp->ctext->s[++i];
+ if('1'<=c && c<='9') {
+ j = c-'0';
+ if(sel.p[j].p2-sel.p[j].p1>BLOCKSIZE)
+ error(Elongtag);
+ bufread(f, sel.p[j].p1, genbuf, sel.p[j].p2-sel.p[j].p1);
+ Strinsert(&genstr, tmprstr(genbuf, (sel.p[j].p2-sel.p[j].p1)), genstr.n);
+ }else
+ Straddc(&genstr, c);
+ }else if(c!='&')
+ Straddc(&genstr, c);
+ else{
+ if(sel.p[0].p2-sel.p[0].p1>BLOCKSIZE)
+ error(Elongrhs);
+ bufread(f, sel.p[0].p1, genbuf, sel.p[0].p2-sel.p[0].p1);
+ Strinsert(&genstr,
+ tmprstr(genbuf, (int)(sel.p[0].p2-sel.p[0].p1)),
+ genstr.n);
+ }
+ if(sel.p[0].p1!=sel.p[0].p2){
+ logdelete(f, sel.p[0].p1, sel.p[0].p2);
+ delta-=sel.p[0].p2-sel.p[0].p1;
+ }
+ if(genstr.n){
+ loginsert(f, sel.p[0].p2, genstr.s, genstr.n);
+ delta+=genstr.n;
+ }
+ didsub = 1;
+ if(!cp->flag)
+ break;
+ }
+ if(!didsub && nest==0)
+ error(Enosub);
+ f->ndot.r.p1 = addr.r.p1, f->ndot.r.p2 = addr.r.p2+delta;
+ return TRUE;
+}
+
+int
+u_cmd(File *f, Cmd *cp)
+{
+ int n;
+
+ USED(f);
+ USED(cp);
+ n = cp->num;
+ if(n >= 0)
+ while(n-- && undo(TRUE))
+ ;
+ else
+ while(n++ && undo(FALSE))
+ ;
+ moveto(f, f->dot.r);
+ return TRUE;
+}
+
+int
+w_cmd(File *f, Cmd *cp)
+{
+ int fseq;
+
+ fseq = f->seq;
+ if(getname(f, cp->ctext, FALSE)==0)
+ error(Enoname);
+ if(fseq == seq)
+ error_s(Ewseq, genc);
+ writef(f);
+ return TRUE;
+}
+
+int
+x_cmd(File *f, Cmd *cp)
+{
+ if(cp->re)
+ looper(f, cp, cp->cmdc=='x');
+ else
+ linelooper(f, cp);
+ return TRUE;
+}
+
+int
+X_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ filelooper(cp, cp->cmdc=='X');
+ return TRUE;
+}
+
+int
+plan9_cmd(File *f, Cmd *cp)
+{
+ plan9(f, cp->cmdc, cp->ctext, nest);
+ return TRUE;
+}
+
+int
+eq_cmd(File *f, Cmd *cp)
+{
+ int charsonly;
+
+ switch(cp->ctext->n){
+ case 1:
+ charsonly = FALSE;
+ break;
+ case 2:
+ if(cp->ctext->s[0]=='#'){
+ charsonly = TRUE;
+ break;
+ }
+ default:
+ SET(charsonly);
+ error(Enewline);
+ }
+ printposn(f, charsonly);
+ return TRUE;
+}
+
+int
+nl_cmd(File *f, Cmd *cp)
+{
+ Address a;
+
+ if(cp->addr == 0){
+ /* First put it on newline boundaries */
+ addr = lineaddr((Posn)0, f->dot, -1);
+ a = lineaddr((Posn)0, f->dot, 1);
+ addr.r.p2 = a.r.p2;
+ if(addr.r.p1==f->dot.r.p1 && addr.r.p2==f->dot.r.p2)
+ addr = lineaddr((Posn)1, f->dot, 1);
+ display(f);
+ }else if(downloaded)
+ moveto(f, addr.r);
+ else
+ display(f);
+ return TRUE;
+}
+
+int
+cd_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ cd(cp->ctext);
+ return TRUE;
+}
+
+int
+append(File *f, Cmd *cp, Posn p)
+{
+ if(cp->ctext->n>0 && cp->ctext->s[cp->ctext->n-1]==0)
+ --cp->ctext->n;
+ if(cp->ctext->n>0)
+ loginsert(f, p, cp->ctext->s, cp->ctext->n);
+ f->ndot.r.p1 = p;
+ f->ndot.r.p2 = p+cp->ctext->n;
+ return TRUE;
+}
+
+int
+display(File *f)
+{
+ Posn p1, p2;
+ int np;
+ char *c;
+
+ p1 = addr.r.p1;
+ p2 = addr.r.p2;
+ if(p2 > f->nc){
+ fprint(2, "bad display addr p1=%ld p2=%ld f->nc=%d\n", p1, p2, f->nc); /*ZZZ should never happen, can remove */
+ p2 = f->nc;
+ }
+ while(p1 < p2){
+ np = p2-p1;
+ if(np>BLOCKSIZE-1)
+ np = BLOCKSIZE-1;
+ bufread(f, p1, genbuf, np);
+ genbuf[np] = 0;
+ c = Strtoc(tmprstr(genbuf, np+1));
+ if(downloaded)
+ termwrite(c);
+ else
+ Write(1, c, strlen(c));
+ free(c);
+ p1 += np;
+ }
+ f->dot = addr;
+ return TRUE;
+}
+
+void
+looper(File *f, Cmd *cp, int xy)
+{
+ Posn p, op;
+ Range r;
+
+ r = addr.r;
+ op= xy? -1 : r.p1;
+ nest++;
+ compile(cp->re);
+ for(p = r.p1; p<=r.p2; ){
+ if(!execute(f, p, r.p2)){ /* no match, but y should still run */
+ if(xy || op>r.p2)
+ break;
+ f->dot.r.p1 = op, f->dot.r.p2 = r.p2;
+ p = r.p2+1; /* exit next loop */
+ }else{
+ if(sel.p[0].p1==sel.p[0].p2){ /* empty match? */
+ if(sel.p[0].p1==op){
+ p++;
+ continue;
+ }
+ p = sel.p[0].p2+1;
+ }else
+ p = sel.p[0].p2;
+ if(xy)
+ f->dot.r = sel.p[0];
+ else
+ f->dot.r.p1 = op, f->dot.r.p2 = sel.p[0].p1;
+ }
+ op = sel.p[0].p2;
+ cmdexec(f, cp->ccmd);
+ compile(cp->re);
+ }
+ --nest;
+}
+
+void
+linelooper(File *f, Cmd *cp)
+{
+ Posn p;
+ Range r, linesel;
+ Address a, a3;
+
+ nest++;
+ r = addr.r;
+ a3.f = f;
+ a3.r.p1 = a3.r.p2 = r.p1;
+ for(p = r.p1; p<r.p2; p = a3.r.p2){
+ a3.r.p1 = a3.r.p2;
+/*pjw if(p!=r.p1 || (linesel = lineaddr((Posn)0, a3, 1)).r.p2==p)*/
+ if(p!=r.p1 || (a = lineaddr((Posn)0, a3, 1), linesel = a.r, linesel.p2==p)){
+ a = lineaddr((Posn)1, a3, 1);
+ linesel = a.r;
+ }
+ if(linesel.p1 >= r.p2)
+ break;
+ if(linesel.p2 >= r.p2)
+ linesel.p2 = r.p2;
+ if(linesel.p2 > linesel.p1)
+ if(linesel.p1>=a3.r.p2 && linesel.p2>a3.r.p2){
+ f->dot.r = linesel;
+ cmdexec(f, cp->ccmd);
+ a3.r = linesel;
+ continue;
+ }
+ break;
+ }
+ --nest;
+}
+
+void
+filelooper(Cmd *cp, int XY)
+{
+ File *f, *cur;
+ int i;
+
+ if(Glooping++)
+ error(EnestXY);
+ nest++;
+ settempfile();
+ cur = curfile;
+ newcur = 0;
+ for(i = 0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f==cmd)
+ continue;
+ if(cp->re==0 || filematch(f, cp->re)==XY)
+ cmdexec(f, cp->ccmd);
+ }
+ if(newcur == 0 && cur && whichmenu(cur)>=0) /* check that cur is still a file */
+ current(cur);
+ --Glooping;
+ --nest;
+}
--- /dev/null
+++ b/samterm/flayer.c
@@ -1,0 +1,491 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+#define DELTA 10
+
+static Flayer **llist; /* front to back */
+static int nllist;
+static int nlalloc;
+static Rectangle lDrect;
+
+Vis visibility(Flayer *);
+void newvisibilities(int);
+void llinsert(Flayer*);
+void lldelete(Flayer*);
+
+Image *maincols[NCOL];
+Image *cmdcols[NCOL];
+
+void
+flstart(Rectangle r)
+{
+ lDrect = r;
+
+ /* Main text is yellowish */
+ maincols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+ maincols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+ maincols[BORD] = allocimage(display, Rect(0,0,2,2), screen->chan, 1, DYellowgreen);
+ maincols[TEXT] = display->black;
+ maincols[HTEXT] = display->black;
+
+ /* Command text is blueish */
+ cmdcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+ cmdcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+ cmdcols[BORD] = allocimage(display, Rect(0,0,2,2), screen->chan, 1, DPurpleblue);
+ cmdcols[TEXT] = display->black;
+ cmdcols[HTEXT] = display->black;
+}
+
+void
+flnew(Flayer *l, Rune *(*fn)(Flayer*, long, ulong*), int u0, void *u1)
+{
+ if(nllist == nlalloc){
+ nlalloc += DELTA;
+ llist = realloc(llist, nlalloc*sizeof(Flayer**));
+ if(llist == 0)
+ panic("flnew");
+ }
+ l->textfn = fn;
+ l->user0 = u0;
+ l->user1 = u1;
+ l->lastsr = ZR;
+ llinsert(l);
+}
+
+Rectangle
+flrect(Flayer *l, Rectangle r)
+{
+ rectclip(&r, lDrect);
+ l->entire = r;
+ l->scroll = insetrect(r, FLMARGIN);
+ r.min.x =
+ l->scroll.max.x = r.min.x+FLMARGIN+FLSCROLLWID+(FLGAP-FLMARGIN);
+ return r;
+}
+
+void
+flinit(Flayer *l, Rectangle r, Font *ft, Image **cols)
+{
+ lldelete(l);
+ llinsert(l);
+ l->visible = All;
+ l->origin = l->p0 = l->p1 = 0;
+ frinit(&l->f, insetrect(flrect(l, r), FLMARGIN), ft, screen, cols);
+ l->f.maxtab = maxtab*stringwidth(ft, "0");
+ newvisibilities(1);
+ draw(screen, l->entire, l->f.cols[BACK], nil, ZP);
+ scrdraw(l, 0L);
+ flborder(l, 0);
+}
+
+void
+flclose(Flayer *l)
+{
+ if(l->visible == All)
+ draw(screen, l->entire, display->white, nil, ZP);
+ else if(l->visible == Some){
+ if(l->f.b == 0)
+ l->f.b = allocimage(display, l->entire, screen->chan, 0, DNofill);
+ if(l->f.b){
+ draw(l->f.b, l->entire, display->white, nil, ZP);
+ flrefresh(l, l->entire, 0);
+ }
+ }
+ frclear(&l->f, 1);
+ lldelete(l);
+ if(l->f.b && l->visible!=All)
+ freeimage(l->f.b);
+ l->textfn = 0;
+ newvisibilities(1);
+}
+
+void
+flborder(Flayer *l, int wide)
+{
+ if(flprepare(l)){
+ border(l->f.b, l->entire, FLMARGIN, l->f.cols[BACK], ZP);
+ border(l->f.b, l->entire, wide? FLMARGIN : 1, l->f.cols[BORD], ZP);
+ if(l->visible==Some)
+ flrefresh(l, l->entire, 0);
+ }
+}
+
+Flayer *
+flwhich(Point p)
+{
+ int i;
+
+ if(p.x==0 && p.y==0)
+ return nllist? llist[0] : 0;
+ for(i=0; i<nllist; i++)
+ if(ptinrect(p, llist[i]->entire))
+ return llist[i];
+ return 0;
+}
+
+void
+flupfront(Flayer *l)
+{
+ int v = l->visible;
+
+ lldelete(l);
+ llinsert(l);
+ if(v!=All)
+ newvisibilities(0);
+}
+
+void
+newvisibilities(int redraw)
+ /* if redraw false, we know it's a flupfront, and needn't
+ * redraw anyone becoming partially covered */
+{
+ int i;
+ Vis ov;
+ Flayer *l;
+
+ for(i = 0; i<nllist; i++){
+ l = llist[i];
+ l->lastsr = ZR; /* make sure scroll bar gets redrawn */
+ ov = l->visible;
+ l->visible = visibility(l);
+#define V(a, b) (((a)<<2)|((b)))
+ switch(V(ov, l->visible)){
+ case V(Some, None):
+ if(l->f.b)
+ freeimage(l->f.b);
+ case V(All, None):
+ case V(All, Some):
+ l->f.b = 0;
+ frclear(&l->f, 0);
+ break;
+
+ case V(Some, Some):
+ if(l->f.b==0 && redraw)
+ case V(None, Some):
+ flprepare(l);
+ if(l->f.b && redraw){
+ flrefresh(l, l->entire, 0);
+ freeimage(l->f.b);
+ l->f.b = 0;
+ frclear(&l->f, 0);
+ }
+ case V(None, None):
+ case V(All, All):
+ break;
+
+ case V(Some, All):
+ if(l->f.b){
+ draw(screen, l->entire, l->f.b, nil, l->entire.min);
+ freeimage(l->f.b);
+ l->f.b = screen;
+ break;
+ }
+ case V(None, All):
+ flprepare(l);
+ break;
+ }
+ if(ov==None && l->visible!=None)
+ flnewlyvisible(l);
+ }
+}
+
+void
+llinsert(Flayer *l)
+{
+ int i;
+ for(i=nllist; i>0; --i)
+ llist[i]=llist[i-1];
+ llist[0]=l;
+ nllist++;
+}
+
+void
+lldelete(Flayer *l)
+{
+ int i;
+
+ for(i=0; i<nllist; i++)
+ if(llist[i]==l){
+ --nllist;
+ for(; i<nllist; i++)
+ llist[i] = llist[i+1];
+ return;
+ }
+ panic("lldelete");
+}
+
+void
+flinsert(Flayer *l, Rune *sp, Rune *ep, long p0)
+{
+ if(flprepare(l)){
+ frinsert(&l->f, sp, ep, p0-l->origin);
+ scrdraw(l, scrtotal(l));
+ if(l->visible==Some)
+ flrefresh(l, l->entire, 0);
+ }
+}
+
+void
+fldelete(Flayer *l, long p0, long p1)
+{
+ if(flprepare(l)){
+ p0 -= l->origin;
+ if(p0 < 0)
+ p0 = 0;
+ p1 -= l->origin;
+ if(p1<0)
+ p1 = 0;
+ frdelete(&l->f, p0, p1);
+ scrdraw(l, scrtotal(l));
+ if(l->visible==Some)
+ flrefresh(l, l->entire, 0);
+ }
+}
+
+int
+flselect(Flayer *l, ulong *p)
+{
+ static int clickcount;
+ static Point clickpt = {-10, -10};
+ int dt, dx, dy;
+
+ if(l->visible!=All)
+ flupfront(l);
+ dt = mousep->msec - l->click;
+ dx = abs(mousep->xy.x - clickpt.x);
+ dy = abs(mousep->xy.y - clickpt.y);
+ *p = frcharofpt(&l->f, mousep->xy) + l->origin;
+
+ l->click = mousep->msec;
+ clickpt = mousep->xy;
+
+ if(dx < 3 && dy < 3 && dt < Clicktime && clickcount < 3)
+ return ++clickcount;
+ clickcount = 0;
+
+ frselect(&l->f, mousectl);
+ l->p0 = l->f.p0+l->origin;
+ l->p1 = l->f.p1+l->origin;
+ return 0;
+}
+
+void
+flsetselect(Flayer *l, long p0, long p1)
+{
+ ulong fp0, fp1;
+
+ if(l->visible==None || !flprepare(l)){
+ l->p0 = p0, l->p1 = p1;
+ return;
+ }
+ l->p0 = p0, l->p1 = p1;
+ flfp0p1(l, &fp0, &fp1);
+ if(fp0==l->f.p0 && fp1==l->f.p1)
+ return;
+
+ if(fp1<=l->f.p0 || fp0>=l->f.p1 || l->f.p0==l->f.p1 || fp0==fp1){
+ /* no overlap or trivial repainting */
+ frdrawsel(&l->f, frptofchar(&l->f, l->f.p0), l->f.p0, l->f.p1, 0);
+ frdrawsel(&l->f, frptofchar(&l->f, fp0), fp0, fp1, 1);
+ goto Refresh;
+ }
+ /* the current selection and the desired selection overlap and are both non-empty */
+ if(fp0 < l->f.p0){
+ /* extend selection backwards */
+ frdrawsel(&l->f, frptofchar(&l->f, fp0), fp0, l->f.p0, 1);
+ }else if(fp0 > l->f.p0){
+ /* trim first part of selection */
+ frdrawsel(&l->f, frptofchar(&l->f, l->f.p0), l->f.p0, fp0, 0);
+ }
+ if(fp1 > l->f.p1){
+ /* extend selection forwards */
+ frdrawsel(&l->f, frptofchar(&l->f, l->f.p1), l->f.p1, fp1, 1);
+ }else if(fp1 < l->f.p1){
+ /* trim last part of selection */
+ frdrawsel(&l->f, frptofchar(&l->f, fp1), fp1, l->f.p1, 0);
+ }
+
+ Refresh:
+ l->f.p0 = fp0;
+ l->f.p1 = fp1;
+ if(l->visible==Some)
+ flrefresh(l, l->entire, 0);
+}
+
+void
+flfp0p1(Flayer *l, ulong *pp0, ulong *pp1)
+{
+ long p0 = l->p0-l->origin, p1 = l->p1-l->origin;
+
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > l->f.nchars)
+ p0 = l->f.nchars;
+ if(p1 > l->f.nchars)
+ p1 = l->f.nchars;
+ *pp0 = p0;
+ *pp1 = p1;
+}
+
+Rectangle
+rscale(Rectangle r, Point old, Point new)
+{
+ r.min.x = r.min.x*new.x/old.x;
+ r.min.y = r.min.y*new.y/old.y;
+ r.max.x = r.max.x*new.x/old.x;
+ r.max.y = r.max.y*new.y/old.y;
+ return r;
+}
+
+void
+flresize(Rectangle dr)
+{
+ int i;
+ Flayer *l;
+ Frame *f;
+ Rectangle r, olDrect;
+ int move;
+
+ olDrect = lDrect;
+ lDrect = dr;
+ move = 0;
+ /* no moving on rio; must repaint */
+ if(0 && Dx(dr)==Dx(olDrect) && Dy(dr)==Dy(olDrect))
+ move = 1;
+ else
+ draw(screen, lDrect, display->white, nil, ZP);
+ for(i=0; i<nllist; i++){
+ l = llist[i];
+ l->lastsr = ZR;
+ f = &l->f;
+ if(move)
+ r = rectaddpt(rectsubpt(l->entire, olDrect.min), dr.min);
+ else{
+ r = rectaddpt(rscale(rectsubpt(l->entire, olDrect.min),
+ subpt(olDrect.max, olDrect.min),
+ subpt(dr.max, dr.min)), dr.min);
+ if(l->visible==Some && f->b){
+ freeimage(f->b);
+ frclear(f, 0);
+ }
+ f->b = 0;
+ if(l->visible!=None)
+ frclear(f, 0);
+ }
+ if(!rectclip(&r, dr))
+ panic("flresize");
+ if(r.max.x-r.min.x<100)
+ r.min.x = dr.min.x;
+ if(r.max.x-r.min.x<100)
+ r.max.x = dr.max.x;
+ if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
+ r.min.y = dr.min.y;
+ if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
+ r.max.y = dr.max.y;
+ if(!move)
+ l->visible = None;
+ frsetrects(f, insetrect(flrect(l, r), FLMARGIN), f->b);
+ if(!move && f->b)
+ scrdraw(l, scrtotal(l));
+ }
+ newvisibilities(1);
+}
+
+int
+flprepare(Flayer *l)
+{
+ Frame *f;
+ ulong n;
+ Rune *r;
+
+ if(l->visible == None)
+ return 0;
+ f = &l->f;
+ if(f->b == 0){
+ if(l->visible == All)
+ f->b = screen;
+ else if((f->b = allocimage(display, l->entire, screen->chan, 0, 0))==0)
+ return 0;
+ draw(f->b, l->entire, f->cols[BACK], nil, ZP);
+ border(f->b, l->entire, l==llist[0]? FLMARGIN : 1, f->cols[BORD], ZP);
+ n = f->nchars;
+ frinit(f, f->entire, f->font, f->b, 0);
+ f->maxtab = maxtab*stringwidth(f->font, "0");
+ r = (*l->textfn)(l, n, &n);
+ frinsert(f, r, r+n, (ulong)0);
+ frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
+ flfp0p1(l, &f->p0, &f->p1);
+ frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);
+ l->lastsr = ZR;
+ scrdraw(l, scrtotal(l));
+ }
+ return 1;
+}
+
+static int somevis, someinvis, justvis;
+
+Vis
+visibility(Flayer *l)
+{
+ somevis = someinvis = 0;
+ justvis = 1;
+ flrefresh(l, l->entire, 0);
+ justvis = 0;
+ if(somevis==0)
+ return None;
+ if(someinvis==0)
+ return All;
+ return Some;
+}
+
+void
+flrefresh(Flayer *l, Rectangle r, int i)
+{
+ Flayer *t;
+ Rectangle s;
+
+ Top:
+ if((t=llist[i++]) == l){
+ if(!justvis)
+ draw(screen, r, l->f.b, nil, r.min);
+ somevis = 1;
+ }else{
+ if(!rectXrect(t->entire, r))
+ goto Top; /* avoid stacking unnecessarily */
+ if(t->entire.min.x>r.min.x){
+ s = r;
+ s.max.x = t->entire.min.x;
+ flrefresh(l, s, i);
+ r.min.x = t->entire.min.x;
+ }
+ if(t->entire.min.y>r.min.y){
+ s = r;
+ s.max.y = t->entire.min.y;
+ flrefresh(l, s, i);
+ r.min.y = t->entire.min.y;
+ }
+ if(t->entire.max.x<r.max.x){
+ s = r;
+ s.min.x = t->entire.max.x;
+ flrefresh(l, s, i);
+ r.max.x = t->entire.max.x;
+ }
+ if(t->entire.max.y<r.max.y){
+ s = r;
+ s.min.y = t->entire.max.y;
+ flrefresh(l, s, i);
+ r.max.y = t->entire.max.y;
+ }
+ /* remaining piece of r is blocked by t; forget about it */
+ someinvis = 1;
+ }
+}
--- /dev/null
+++ b/samterm/flayer.h
@@ -1,0 +1,50 @@
+typedef enum Vis{
+ None=0,
+ Some,
+ All,
+}Vis;
+
+enum{
+ Clicktime=500, /* milliseconds */
+};
+
+typedef struct Flayer Flayer;
+
+struct Flayer
+{
+ Frame f;
+ long origin; /* offset of first char in flayer */
+ long p0, p1;
+ long click; /* time at which selection click occurred, in HZ */
+ Rune *(*textfn)(Flayer*, long, ulong*);
+ int user0;
+ void *user1;
+ Rectangle entire;
+ Rectangle scroll;
+ Rectangle lastsr; /* geometry of scrollbar when last drawn */
+ Vis visible;
+};
+
+void flborder(Flayer*, int);
+void flclose(Flayer*);
+void fldelete(Flayer*, long, long);
+void flfp0p1(Flayer*, ulong*, ulong*);
+void flinit(Flayer*, Rectangle, Font*, Image**);
+void flinsert(Flayer*, Rune*, Rune*, long);
+void flnew(Flayer*, Rune *(*fn)(Flayer*, long, ulong*), int, void*);
+int flprepare(Flayer*);
+Rectangle flrect(Flayer*, Rectangle);
+void flrefresh(Flayer*, Rectangle, int);
+void flresize(Rectangle);
+int flselect(Flayer*, ulong*);
+void flsetselect(Flayer*, long, long);
+void flstart(Rectangle);
+void flupfront(Flayer*);
+Flayer *flwhich(Point);
+
+#define FLMARGIN 4
+#define FLSCROLLWID 12
+#define FLGAP 4
+
+extern Image *maincols[NCOL];
+extern Image *cmdcols[NCOL];
--- /dev/null
+++ b/samterm/icons.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+Cursor bullseye={
+ {-7, -7},
+ {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+ 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+ 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
+ {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+ 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+ 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
+};
+Cursor deadmouse={
+ {-7, -7},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C, 0x00, 0x8E, 0x1D, 0xC7,
+ 0xFF, 0xE3, 0xFF, 0xF3, 0xFF, 0xFF, 0x7F, 0xFE,
+ 0x3F, 0xF8, 0x17, 0xF0, 0x03, 0xE0, 0x00, 0x00,},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82,
+ 0x04, 0x41, 0xFF, 0xE1, 0x5F, 0xF1, 0x3F, 0xFE,
+ 0x17, 0xF0, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x00,}
+};
+Cursor lockarrow={
+ {-7, -7},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x0F, 0xC0,
+ 0x03, 0xC0, 0x07, 0xC0, 0x0E, 0xC0, 0x1C, 0xC0,
+ 0x38, 0x00, 0x70, 0x00, 0xE0, 0xDB, 0xC0, 0xDB,}
+};
+
+Image *darkgrey;
+
+void
+iconinit(void)
+{
+ darkgrey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x444444FF);
+}
--- /dev/null
+++ b/samterm/io.c
@@ -1,0 +1,286 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+int cursorfd;
+int plumbfd = -1;
+int input;
+int got;
+int block;
+int kbdc;
+int resized;
+uchar *hostp;
+uchar *hoststop;
+uchar *plumbbase;
+uchar *plumbp;
+uchar *plumbstop;
+Channel *plumbc;
+Channel *hostc;
+Mousectl *mousectl;
+Mouse *mousep;
+Keyboardctl *keyboardctl;
+void panic(char*);
+
+void
+initio(void)
+{
+ threadsetname("main");
+ mousectl = initmouse(nil, display->image);
+ if(mousectl == nil){
+ fprint(2, "samterm: mouse init failed: %r\n");
+ threadexitsall("mouse");
+ }
+ mousep = mousectl;
+ keyboardctl = initkeyboard(nil);
+ if(keyboardctl == nil){
+ fprint(2, "samterm: keyboard init failed: %r\n");
+ threadexitsall("kbd");
+ }
+ hoststart();
+ plumbstart();
+}
+
+void
+getmouse(void)
+{
+ if(readmouse(mousectl) < 0)
+ panic("mouse");
+}
+
+void
+mouseunblock(void)
+{
+ got &= ~(1<<RMouse);
+}
+
+void
+kbdblock(void)
+{ /* ca suffit */
+ block = (1<<RKeyboard)|(1<<RPlumb);
+}
+
+int
+button(int but)
+{
+ getmouse();
+ return mousep->buttons&(1<<(but-1));
+}
+
+void
+externload(int i)
+{
+ plumbbase = malloc(plumbbuf[i].n);
+ if(plumbbase == 0)
+ return;
+ memmove(plumbbase, plumbbuf[i].data, plumbbuf[i].n);
+ plumbp = plumbbase;
+ plumbstop = plumbbase + plumbbuf[i].n;
+ got |= 1<<RPlumb;
+}
+
+int
+waitforio(void)
+{
+ Alt alts[NRes+1];
+ Rune r;
+ int i;
+ ulong type;
+
+again:
+
+ alts[RPlumb].c = plumbc;
+ alts[RPlumb].v = &i;
+ alts[RPlumb].op = CHANRCV;
+ if((block & (1<<RPlumb)) || plumbc == nil)
+ alts[RPlumb].op = CHANNOP;
+
+ alts[RHost].c = hostc;
+ alts[RHost].v = &i;
+ alts[RHost].op = CHANRCV;
+ if(block & (1<<RHost))
+ alts[RHost].op = CHANNOP;
+
+ alts[RKeyboard].c = keyboardctl->c;
+ alts[RKeyboard].v = &r;
+ alts[RKeyboard].op = CHANRCV;
+ if(block & (1<<RKeyboard))
+ alts[RKeyboard].op = CHANNOP;
+
+ alts[RMouse].c = mousectl->c;
+ alts[RMouse].v = &mousectl->Mouse;
+ alts[RMouse].op = CHANRCV;
+ if(block & (1<<RMouse))
+ alts[RMouse].op = CHANNOP;
+
+ alts[RResize].c = mousectl->resizec;
+ alts[RResize].v = nil;
+ alts[RResize].op = CHANRCV;
+ if(block & (1<<RResize))
+ alts[RResize].op = CHANNOP;
+
+ alts[NRes].op = CHANEND;
+
+ if(got & ~block)
+ return got & ~block;
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ type = alt(alts);
+ switch(type){
+ case RHost:
+ hostp = hostbuf[i].data;
+ hoststop = hostbuf[i].data + hostbuf[i].n;
+ block = 0;
+ break;
+ case RPlumb:
+ externload(i);
+ break;
+ case RKeyboard:
+ kbdc = r;
+ break;
+ case RMouse:
+ break;
+ case RResize:
+ resized = 1;
+ /* do the resize in line if we've finished initializing and we're not in a blocking state */
+ if(hasunlocked && block==0 && RESIZED())
+ resize();
+ goto again;
+ }
+ got |= 1<<type;
+ return got;
+}
+
+int
+rcvchar(void)
+{
+ int c;
+
+ if(!(got & (1<<RHost)))
+ return -1;
+ c = *hostp++;
+ if(hostp == hoststop)
+ got &= ~(1<<RHost);
+ return c;
+}
+
+char*
+rcvstring(void)
+{
+ *hoststop = 0;
+ got &= ~(1<<RHost);
+ return (char*)hostp;
+}
+
+int
+getch(void)
+{
+ int c;
+
+ while((c = rcvchar()) == -1){
+ block = ~(1<<RHost);
+ waitforio();
+ block = 0;
+ }
+ return c;
+}
+
+int
+externchar(void)
+{
+ Rune r;
+
+ loop:
+ if(got & ((1<<RPlumb) & ~block)){
+ plumbp += chartorune(&r, (char*)plumbp);
+ if(plumbp >= plumbstop){
+ got &= ~(1<<RPlumb);
+ free(plumbbase);
+ }
+ if(r == 0)
+ goto loop;
+ return r;
+ }
+ return -1;
+}
+
+int kpeekc = -1;
+int
+ecankbd(void)
+{
+ Rune r;
+
+ if(kpeekc >= 0)
+ return 1;
+ if(nbrecv(keyboardctl->c, &r) > 0){
+ kpeekc = r;
+ return 1;
+ }
+ return 0;
+}
+
+int
+ekbd(void)
+{
+ int c;
+ Rune r;
+
+ if(kpeekc >= 0){
+ c = kpeekc;
+ kpeekc = -1;
+ return c;
+ }
+ if(recv(keyboardctl->c, &r) < 0){
+ fprint(2, "samterm: keybard recv error: %r\n");
+ panic("kbd");
+ }
+ return r;
+}
+
+int
+kbdchar(void)
+{
+ int c, i;
+
+ c = externchar();
+ if(c > 0)
+ return c;
+ if(got & (1<<RKeyboard)){
+ c = kbdc;
+ kbdc = -1;
+ got &= ~(1<<RKeyboard);
+ return c;
+ }
+ while(plumbc!=nil && nbrecv(plumbc, &i)>0){
+ externload(i);
+ c = externchar();
+ if(c > 0)
+ return c;
+ }
+ if(!ecankbd())
+ return -1;
+ return ekbd();
+}
+
+int
+qpeekc(void)
+{
+ return kbdc;
+}
+
+int
+RESIZED(void)
+{
+ if(resized){
+ if(getwindow(display, Refnone) < 0)
+ panic("can't reattach to window");
+ resized = 0;
+ return 1;
+ }
+ return 0;
+}
--- /dev/null
+++ b/samterm/main.c
@@ -1,0 +1,741 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+int mainstacksize = 16*1024;
+
+Text cmd;
+Rune *scratch;
+long nscralloc;
+Cursor *cursor;
+Flayer *which = 0;
+Flayer *work = 0;
+long snarflen;
+long typestart = -1;
+long typeend = -1;
+long typeesc = -1;
+long modified = 0; /* strange lookahead for menus */
+char hostlock = 1;
+char hasunlocked = 0;
+int maxtab = 8;
+int autoindent;
+int spacesindent;
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, got, nclick, scr, chord;
+ Text *t;
+ Rectangle r;
+ Flayer *nwhich;
+ ulong p;
+
+ getscreen(argc, argv);
+ iconinit();
+ initio();
+ scratch = alloc(100*RUNESIZE);
+ nscralloc = 100;
+ r = screen->r;
+ r.max.y = r.min.y+Dy(r)/5;
+ flstart(screen->clipr);
+ rinit(&cmd.rasp);
+ flnew(&cmd.l[0], gettext, 1, &cmd);
+ flinit(&cmd.l[0], r, font, cmdcols);
+ cmd.nwin = 1;
+ which = &cmd.l[0];
+ cmd.tag = Untagged;
+ outTs(Tversion, VERSION);
+ startnewfile(Tstartcmdfile, &cmd);
+
+ got = 0;
+ chord = 0;
+ for(;;got = waitforio()){
+ if(hasunlocked && RESIZED())
+ resize();
+ if(got&(1<<RHost))
+ rcv();
+ if(got&(1<<RPlumb)){
+ for(i=0; cmd.l[i].textfn==0; i++)
+ ;
+ current(&cmd.l[i]);
+ flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes);
+ type(which, RPlumb);
+ }
+ if(got&(1<<RKeyboard))
+ if(which)
+ type(which, RKeyboard);
+ else
+ kbdblock();
+ if(got&(1<<RMouse)){
+ if(hostlock==2 || !ptinrect(mousep->xy, screen->r)){
+ mouseunblock();
+ continue;
+ }
+ nwhich = flwhich(mousep->xy);
+ scr = which && (ptinrect(mousep->xy, which->scroll) ||
+ mousep->buttons&(8|16));
+ if(mousep->buttons)
+ flushtyping(1);
+ if((mousep->buttons&1)==0)
+ chord = 0;
+ if(chord && which && which==nwhich){
+ chord |= mousep->buttons;
+ t = (Text *)which->user1;
+ if(!t->lock){
+ int w = which-t->l;
+ if(chord&2){
+ cut(t, w, 1, 1);
+ chord &= ~2;
+ }
+ if(chord&4){
+ paste(t, w);
+ chord &= ~4;
+ }
+ }
+ }else if(mousep->buttons&(1|8)){
+ if(scr)
+ scroll(which, (mousep->buttons&8) ? 4 : 1);
+ else if(nwhich && nwhich!=which)
+ current(nwhich);
+ else{
+ t=(Text *)which->user1;
+ nclick = flselect(which, &p);
+ if(nclick > 0){
+ if(nclick > 1)
+ outTsl(Ttclick, t->tag, p);
+ else
+ outTsl(Tdclick, t->tag, p);
+ t->lock++;
+ }else if(t!=&cmd)
+ outcmd();
+ if(mousep->buttons&1)
+ chord = mousep->buttons;
+ }
+ }else if((mousep->buttons&2) && which){
+ if(scr)
+ scroll(which, 2);
+ else
+ menu2hit();
+ }else if(mousep->buttons&(4|16)){
+ if(scr)
+ scroll(which, (mousep->buttons&16) ? 5 : 3);
+ else
+ menu3hit();
+ }
+ mouseunblock();
+ }
+ }
+}
+
+
+void
+resize(void)
+{
+ int i;
+
+ flresize(screen->clipr);
+ for(i = 0; i<nname; i++)
+ if(text[i])
+ hcheck(text[i]->tag);
+}
+
+void
+current(Flayer *nw)
+{
+ Text *t;
+
+ if(which)
+ flborder(which, 0);
+ if(nw){
+ flushtyping(1);
+ flupfront(nw);
+ flborder(nw, 1);
+ buttons(Up);
+ t = (Text *)nw->user1;
+ t->front = nw-&t->l[0];
+ if(t != &cmd)
+ work = nw;
+ }
+ which = nw;
+}
+
+void
+closeup(Flayer *l)
+{
+ Text *t=(Text *)l->user1;
+ int m;
+
+ m = whichmenu(t->tag);
+ if(m < 0)
+ return;
+ flclose(l);
+ if(l == which){
+ which = 0;
+ current(flwhich(Pt(0, 0)));
+ }
+ if(l == work)
+ work = 0;
+ if(--t->nwin == 0){
+ rclear(&t->rasp);
+ free((uchar *)t);
+ text[m] = 0;
+ }else if(l == &t->l[t->front]){
+ for(m=0; m<NL; m++) /* find one; any one will do */
+ if(t->l[m].textfn){
+ t->front = m;
+ return;
+ }
+ panic("close");
+ }
+}
+
+Flayer *
+findl(Text *t)
+{
+ int i;
+ for(i = 0; i<NL; i++)
+ if(t->l[i].textfn==0)
+ return &t->l[i];
+ return 0;
+}
+
+void
+duplicate(Flayer *l, Rectangle r, Font *f, int close)
+{
+ Text *t=(Text *)l->user1;
+ Flayer *nl = findl(t);
+ Rune *rp;
+ ulong n;
+
+ if(nl){
+ flnew(nl, gettext, l->user0, (char *)t);
+ flinit(nl, r, f, l->f.cols);
+ nl->origin = l->origin;
+ rp = (*l->textfn)(l, l->f.nchars, &n);
+ flinsert(nl, rp, rp+n, l->origin);
+ flsetselect(nl, l->p0, l->p1);
+ if(close){
+ flclose(l);
+ if(l==which)
+ which = 0;
+ }else
+ t->nwin++;
+ current(nl);
+ hcheck(t->tag);
+ }
+ setcursor(mousectl, cursor);
+}
+
+void
+buttons(int updown)
+{
+ while(((mousep->buttons&7)!=0) != updown)
+ getmouse();
+}
+
+int
+getr(Rectangle *rp)
+{
+ Point p;
+ Rectangle r;
+
+ *rp = getrect(3, mousectl);
+ if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){
+ p = rp->min;
+ r = cmd.l[cmd.front].entire;
+ *rp = screen->r;
+ if(cmd.nwin==1){
+ if (p.y <= r.min.y)
+ rp->max.y = r.min.y;
+ else if (p.y >= r.max.y)
+ rp->min.y = r.max.y;
+ if (p.x <= r.min.x)
+ rp->max.x = r.min.x;
+ else if (p.x >= r.max.x)
+ rp->min.x = r.max.x;
+ }
+ }
+ return rectclip(rp, screen->r) &&
+ rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40;
+}
+
+void
+snarf(Text *t, int w)
+{
+ Flayer *l = &t->l[w];
+
+ if(l->p1>l->p0){
+ snarflen = l->p1-l->p0;
+ outTsll(Tsnarf, t->tag, l->p0, l->p1);
+ }
+}
+
+void
+cut(Text *t, int w, int save, int check)
+{
+ long p0, p1;
+ Flayer *l;
+
+ l = &t->l[w];
+ p0 = l->p0;
+ p1 = l->p1;
+ if(p0 == p1)
+ return;
+ if(p0 < 0)
+ panic("cut");
+ if(save)
+ snarf(t, w);
+ outTsll(Tcut, t->tag, p0, p1);
+ flsetselect(l, p0, p0);
+ t->lock++;
+ hcut(t->tag, p0, p1-p0);
+ if(check)
+ hcheck(t->tag);
+}
+
+void
+paste(Text *t, int w)
+{
+ if(snarflen){
+ cut(t, w, 0, 0);
+ t->lock++;
+ outTsl(Tpaste, t->tag, t->l[w].p0);
+ }
+}
+
+void
+scrorigin(Flayer *l, int but, long p0)
+{
+ Text *t=(Text *)l->user1;
+
+ if(t->tag == Untagged)
+ return;
+
+ switch(but){
+ case 1:
+ outTsll(Torigin, t->tag, l->origin, p0);
+ break;
+ case 2:
+ outTsll(Torigin, t->tag, p0, 1L);
+ break;
+ case 3:
+ horigin(t->tag,p0);
+ }
+}
+
+int
+alnum(int c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ */
+ if(c<=' ')
+ return 0;
+ if(0x7F<=c && c<=0xA0)
+ return 0;
+ if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+ return 0;
+ return 1;
+}
+
+int
+raspc(Rasp *r, long p)
+{
+ ulong n;
+ rload(r, p, p+1, &n);
+ if(n)
+ return scratch[0];
+ return 0;
+}
+
+int
+getcol(Rasp *r, long p)
+{
+ int col;
+
+ for(col = 0; p > 0 && raspc(r, p-1)!='\n'; p--, col++)
+ ;
+ return col;
+}
+
+long
+del(Rasp *r, long o, long p)
+{
+ int i, col, n;
+
+ if(--p < o)
+ return o;
+ if(!spacesindent || raspc(r, p)!=' ')
+ return p;
+ col = getcol(r, p) + 1;
+ if((n = col % maxtab) == 0)
+ n = maxtab;
+ for(i = 0; p-1>=o && raspc(r, p-1)==' ' && i<n-1; --p, i++)
+ ;
+ return p>=o? p : o;
+}
+
+long
+ctlw(Rasp *r, long o, long p)
+{
+ int c;
+
+ if(--p < o)
+ return o;
+ if(raspc(r, p)=='\n')
+ return p;
+ for(; p>=o && !alnum(c=raspc(r, p)); --p)
+ if(c=='\n')
+ return p+1;
+ for(; p>o && alnum(raspc(r, p-1)); --p)
+ ;
+ return p>=o? p : o;
+}
+
+long
+ctlu(Rasp *r, long o, long p)
+{
+ if(--p < o)
+ return o;
+ if(raspc(r, p)=='\n')
+ return p;
+ for(; p-1>=o && raspc(r, p-1)!='\n'; --p)
+ ;
+ return p>=o? p : o;
+}
+
+int
+center(Flayer *l, long a)
+{
+ Text *t;
+
+ t = l->user1;
+ if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
+ if(a > t->rasp.nrunes)
+ a = t->rasp.nrunes;
+ outTsll(Torigin, t->tag, a, 2L);
+ return 1;
+ }
+ return 0;
+}
+
+int
+onethird(Flayer *l, long a)
+{
+ Text *t;
+ Rectangle s;
+ long lines;
+
+ t = l->user1;
+ if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
+ if(a > t->rasp.nrunes)
+ a = t->rasp.nrunes;
+ s = insetrect(l->scroll, 1);
+ lines = ((s.max.y-s.min.y)/l->f.font->height+1)/3;
+ if (lines < 2)
+ lines = 2;
+ outTsll(Torigin, t->tag, a, lines);
+ return 1;
+ }
+ return 0;
+}
+
+void
+flushtyping(int clearesc)
+{
+ Text *t;
+ ulong n;
+
+ if(clearesc)
+ typeesc = -1;
+ if(typestart == typeend) {
+ modified = 0;
+ return;
+ }
+ t = which->user1;
+ if(t != &cmd)
+ modified = 1;
+ rload(&t->rasp, typestart, typeend, &n);
+ scratch[n] = 0;
+ if(t==&cmd && typeend==t->rasp.nrunes && scratch[typeend-typestart-1]=='\n'){
+ setlock();
+ outcmd();
+ }
+ outTslS(Ttype, t->tag, typestart, scratch);
+ typestart = -1;
+ typeend = -1;
+}
+
+int
+nontypingkey(int c)
+{
+ switch(c){
+ case Kup:
+ case Kdown:
+ case Khome:
+ case Kend:
+ case Kpgdown:
+ case Kpgup:
+ case Kleft:
+ case Kright:
+ case Ksoh:
+ case Kenq:
+ case Kstx:
+ case Kbel:
+ return 1;
+ }
+ return 0;
+}
+
+
+void
+type(Flayer *l, int res) /* what a bloody mess this is */
+{
+ Text *t = (Text *)l->user1;
+ Rune buf[100];
+ Rune *p = buf;
+ int c, backspacing;
+ long a, a0;
+ int scrollkey;
+
+ scrollkey = 0;
+ if(res == RKeyboard)
+ scrollkey = nontypingkey(qpeekc()); /* ICK */
+
+ if(hostlock || t->lock){
+ kbdblock();
+ return;
+ }
+ a = l->p0;
+ if(a!=l->p1 && !scrollkey){
+ flushtyping(1);
+ cut(t, t->front, 1, 1);
+ return; /* it may now be locked */
+ }
+ backspacing = 0;
+ while((c = kbdchar())>0){
+ if(res == RKeyboard){
+ if(nontypingkey(c) || c==Kesc)
+ break;
+ /* backspace, ctrl-u, ctrl-w, del */
+ if(c==Kbs || c==Knack || c==Ketb || c==Kdel){
+ backspacing = 1;
+ break;
+ }
+ }
+ if(spacesindent && c == '\t'){
+ int i, col, n;
+ col = getcol(&t->rasp, a);
+ n = maxtab - col % maxtab;
+ for(i = 0; i < n && p < buf+nelem(buf); i++)
+ *p++ = ' ';
+ } else
+ *p++ = c;
+ if(c == '\n' && autoindent && t != &cmd){
+ /* autoindent */
+ int cursor, ch;
+ cursor = ctlu(&t->rasp, 0, a+(p-buf)-1);
+ while(p < buf+nelem(buf)){
+ ch = raspc(&t->rasp, cursor++);
+ if(ch == ' ' || ch == '\t')
+ *p++ = ch;
+ else
+ break;
+ }
+ }
+ if(c == '\n' || p >= buf+sizeof(buf)/sizeof(buf[0]))
+ break;
+ }
+ if(p > buf){
+ if(typestart < 0)
+ typestart = a;
+ if(typeesc < 0)
+ typeesc = a;
+ hgrow(t->tag, a, p-buf, 0);
+ t->lock++; /* pretend we Trequest'ed for hdatarune*/
+ hdatarune(t->tag, a, buf, p-buf);
+ a += p-buf;
+ l->p0 = a;
+ l->p1 = a;
+ typeend = a;
+ if(c=='\n' || typeend-typestart>100)
+ flushtyping(0);
+ onethird(l, a);
+ }
+ if(c==Kdown || c==Kpgdown){
+ flushtyping(0);
+ center(l, l->origin+l->f.nchars+1);
+ /* backspacing immediately after outcmd(): sorry */
+ }else if(c==Kup || c==Kpgup){
+ flushtyping(0);
+ a0 = l->origin-l->f.nchars;
+ if(a0 < 0)
+ a0 = 0;
+ center(l, a0);
+ }else if(c == Kright){
+ flushtyping(0);
+ a0 = l->p1;
+ if(a0 < t->rasp.nrunes)
+ a0++;
+ flsetselect(l, a0, a0);
+ center(l, a0);
+ }else if(c == Kleft){
+ flushtyping(0);
+ a0 = l->p0;
+ if(a0 > 0)
+ a0--;
+ flsetselect(l, a0, a0);
+ center(l, a0);
+ }else if(c == Khome){
+ flushtyping(0);
+ center(l, 0);
+ }else if(c == Kend){
+ flushtyping(0);
+ center(l, t->rasp.nrunes);
+ }else if(c == Ksoh || c == Kenq){
+ flushtyping(1);
+ if(c == Ksoh)
+ while(a > 0 && raspc(&t->rasp, a-1)!='\n')
+ a--;
+ else
+ while(a < t->rasp.nrunes && raspc(&t->rasp, a)!='\n')
+ a++;
+ l->p0 = l->p1 = a;
+ for(l=t->l; l<&t->l[NL]; l++)
+ if(l->textfn)
+ flsetselect(l, l->p0, l->p1);
+ }else if(backspacing && !hostlock){
+ /* backspacing immediately after outcmd(): sorry */
+ if(l->f.p0>0 && a>0){
+ switch(c){
+ case Kbs:
+ case Kdel: /* del */
+ l->p0 = del(&t->rasp, l->origin, a);
+ break;
+ case Knack: /* ctrl-u */
+ l->p0 = ctlu(&t->rasp, l->origin, a);
+ break;
+ case Ketb: /* ctrl-w */
+ l->p0 = ctlw(&t->rasp, l->origin, a);
+ break;
+ }
+ l->p1 = a;
+ if(l->p1 != l->p0){
+ /* cut locally if possible */
+ if(typestart<=l->p0 && l->p1<=typeend){
+ t->lock++; /* to call hcut */
+ hcut(t->tag, l->p0, l->p1-l->p0);
+ /* hcheck is local because we know rasp is contiguous */
+ hcheck(t->tag);
+ }else{
+ flushtyping(0);
+ cut(t, t->front, 0, 1);
+ }
+ }
+ if(typeesc >= l->p0)
+ typeesc = l->p0;
+ if(typestart >= 0){
+ if(typestart >= l->p0)
+ typestart = l->p0;
+ typeend = l->p0;
+ if(typestart == typeend){
+ typestart = -1;
+ typeend = -1;
+ modified = 0;
+ }
+ }
+ }
+ }else if(c == Kstx){
+ t = &cmd;
+ for(l=t->l; l->textfn==0; l++)
+ ;
+ current(l);
+ flushtyping(0);
+ a = t->rasp.nrunes;
+ flsetselect(l, a, a);
+ center(l, a);
+ }else if(c == Kbel){
+ int i;
+ if(work == nil)
+ return;
+ if(which != work){
+ current(work);
+ return;
+ }
+ t = (Text*)work->user1;
+ l = &t->l[t->front];
+ for(i=t->front; t->nwin>1 && (i = (i+1)%NL) != t->front; )
+ if(t->l[i].textfn != 0){
+ l = &t->l[i];
+ break;
+ }
+ current(l);
+ }else{
+ if(c==Kesc && typeesc>=0){
+ l->p0 = typeesc;
+ l->p1 = a;
+ flushtyping(1);
+ }
+ for(l=t->l; l<&t->l[NL]; l++)
+ if(l->textfn)
+ flsetselect(l, l->p0, l->p1);
+ }
+}
+
+
+void
+outcmd(void){
+ if(work)
+ outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1);
+}
+
+void
+panic(char *s)
+{
+ panic1(display, s);
+}
+
+void
+panic1(Display*, char *s)
+{
+ fprint(2, "samterm:panic: ");
+ perror(s);
+ abort();
+}
+
+Rune*
+gettext(Flayer *l, long n, ulong *np)
+{
+ Text *t;
+
+ t = l->user1;
+ rload(&t->rasp, l->origin, l->origin+n, np);
+ return scratch;
+}
+
+long
+scrtotal(Flayer *l)
+{
+ return ((Text *)l->user1)->rasp.nrunes;
+}
+
+void*
+alloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == 0)
+ panic("alloc");
+ memset(p, 0, n);
+ return p;
+}
--- /dev/null
+++ b/samterm/menu.c
@@ -1,0 +1,471 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+uchar **name; /* first byte is ' ' or '\'': modified state */
+Text **text; /* pointer to Text associated with file */
+ushort *tag; /* text[i].tag, even if text[i] not defined */
+int nname;
+int mname;
+int mw;
+
+char *genmenu3(int);
+char *genmenu2(int);
+char *genmenu2c(int);
+
+enum Menu2
+{
+ Cut,
+ Paste,
+ Snarf,
+ Plumb,
+ Look,
+ Exch,
+ Search,
+};
+
+enum Menu3
+{
+ New,
+ Zerox,
+ Resize,
+ Close,
+ Write,
+ NMENU3
+};
+
+char *menu2str[] = {
+ "cut",
+ "paste",
+ "snarf",
+ "plumb",
+ "look",
+ "<rio>",
+ nil, /* storage for last pattern */
+};
+
+char *menu3str[] = {
+ "new",
+ "zerox",
+ "resize",
+ "close",
+ "write",
+};
+
+Menu menu2 = {0, genmenu2};
+Menu menu2c ={0, genmenu2c};
+Menu menu3 = {0, genmenu3};
+
+typedef struct Menucmd Menucmd;
+struct Menucmd{
+ char *cmd;
+ Menucmd *next;
+}*menucmds;
+
+char*
+findmenucmd(int n){
+ Menucmd *m;
+
+ for(m = menucmds; n > 0 && m != nil; n--)
+ m = m->next;
+ if(n == 0 && m != nil)
+ return m->cmd;
+ return nil;
+}
+
+void
+menucmdhit(char *s)
+{
+ if(s == nil)
+ return;
+ outstart(Tmenucmdsend);
+ outcopy(strlen(s), (uchar*)s);
+ outsend();
+}
+
+void
+menu2hit(void)
+{
+ Text *t=(Text *)which->user1;
+ int w = which-t->l;
+ int m;
+
+ if(hversion==0 || plumbfd<0)
+ menu2str[Plumb] = "(plumb)";
+ m = menuhit(2, mousectl, t==&cmd? &menu2c : &menu2, nil);
+ if(hostlock || t->lock)
+ return;
+
+ switch(m){
+ case Cut:
+ cut(t, w, 1, 1);
+ break;
+
+ case Paste:
+ paste(t, w);
+ break;
+
+ case Snarf:
+ snarf(t, w);
+ break;
+
+ case Plumb:
+ if(hversion > 0)
+ outTsll(Tplumb, t->tag, which->p0, which->p1);
+ break;
+
+ case Exch:
+ snarf(t, w);
+ outT0(Tstartsnarf);
+ setlock();
+ break;
+
+ case Look:
+ outTsll(Tlook, t->tag, which->p0, which->p1);
+ setlock();
+ break;
+
+ case Search:
+ if(t == &cmd || menu2str[Search] != nil){
+ outcmd();
+ if(t == &cmd)
+ outTsll(Tsend, 0 /*ignored*/, which->p0, which->p1);
+ else
+ outT0(Tsearch);
+ setlock();
+ break;
+ }
+ default:
+ m -= Search + (t == &cmd || menu2str[Search] != nil);
+ menucmdhit(findmenucmd(m));
+ break;
+ }
+}
+
+void
+menu3hit(void)
+{
+ Rectangle r;
+ Flayer *l;
+ int m, i;
+ Text *t;
+
+ mw = -1;
+ switch(m = menuhit(3, mousectl, &menu3, nil)){
+ case -1:
+ break;
+
+ case New:
+ if(!hostlock)
+ sweeptext(1, 0);
+ break;
+
+ case Zerox:
+ case Resize:
+ if(!hostlock){
+ setcursor(mousectl, &bullseye);
+ buttons(Down);
+ if((mousep->buttons&4) && (l = flwhich(mousep->xy)) && getr(&r))
+ duplicate(l, r, l->f.font, m==Resize);
+ else
+ setcursor(mousectl, cursor);
+ buttons(Up);
+ }
+ break;
+
+ case Close:
+ if(!hostlock){
+ setcursor(mousectl, &bullseye);
+ buttons(Down);
+ if((mousep->buttons&4) && (l = flwhich(mousep->xy)) && !hostlock){
+ t=(Text *)l->user1;
+ if (t->nwin>1)
+ closeup(l);
+ else if(t!=&cmd) {
+ outTs(Tclose, t->tag);
+ setlock();
+ }
+ }
+ setcursor(mousectl, cursor);
+ buttons(Up);
+ }
+ break;
+
+ case Write:
+ if(!hostlock){
+ setcursor(mousectl, &bullseye);
+ buttons(Down);
+ if((mousep->buttons&4) && (l = flwhich(mousep->xy))){
+ outTs(Twrite, ((Text *)l->user1)->tag);
+ setlock();
+ }else
+ setcursor(mousectl, cursor);
+ buttons(Up);
+ }
+ break;
+
+ default:
+ if(t = text[m-NMENU3]){
+ i = t->front;
+ if(t->nwin==0 || t->l[i].textfn==0)
+ return; /* not ready yet; try again later */
+ if(t->nwin>1 && which==&t->l[i])
+ do
+ if(++i==NL)
+ i = 0;
+ while(i!=t->front && t->l[i].textfn==0);
+ current(&t->l[i]);
+ }else if(!hostlock)
+ sweeptext(0, tag[m-NMENU3]);
+ break;
+ }
+}
+
+
+Text *
+sweeptext(int new, int tag)
+{
+ Rectangle r;
+ Text *t;
+
+ if(getr(&r) && (t = malloc(sizeof(Text)))){
+ memset((void*)t, 0, sizeof(Text));
+ current((Flayer *)0);
+ flnew(&t->l[0], gettext, 0, (char *)t);
+ flinit(&t->l[0], r, font, maincols); /*bnl*/
+ t->nwin = 1;
+ rinit(&t->rasp);
+ if(new)
+ startnewfile(Tstartnewfile, t);
+ else{
+ rinit(&t->rasp);
+ t->tag = tag;
+ startfile(t);
+ }
+ return t;
+ }
+ return 0;
+}
+
+int
+whichmenu(int tg)
+{
+ int i;
+
+ for(i=0; i<nname; i++)
+ if(tag[i] == tg)
+ return i;
+ return -1;
+}
+
+void
+menuins(int n, uchar *s, Text *t, int m, int tg)
+{
+ int i;
+
+ if(nname == mname){
+ if(mname == 0)
+ mname = 32;
+ else
+ mname *= 2;
+ name = realloc(name, sizeof(name[0])*mname);
+ text = realloc(text, sizeof(text[0])*mname);
+ tag = realloc(tag, sizeof(tag[0])*mname);
+ if(name==nil || text==nil || tag==nil)
+ panic("realloc");
+ }
+ for(i=nname; i>n; --i)
+ name[i]=name[i-1], text[i]=text[i-1], tag[i]=tag[i-1];
+ text[n] = t;
+ tag[n] = tg;
+ name[n] = alloc(strlen((char*)s)+2);
+ name[n][0] = m;
+ strcpy((char*)name[n]+1, (char*)s);
+ nname++;
+ menu3.lasthit = n+NMENU3;
+}
+
+void
+menudel(int n)
+{
+ int i;
+
+ if(nname==0 || n>=nname || text[n])
+ panic("menudel");
+ free(name[n]);
+ --nname;
+ for(i = n; i<nname; i++)
+ name[i]=name[i+1], text[i]=text[i+1], tag[i]=tag[i+1];
+}
+
+void
+setpat(char *s)
+{
+ static char pat[17];
+
+ pat[0] = '/';
+ strncpy(pat+1, s, 15);
+ menu2str[Search] = pat;
+}
+
+void
+menucmd(char *s)
+{
+ Menucmd **mp, *m;
+
+ while(*s == ' ' || *s == '\t')
+ s++;
+ if(*s == 0){
+ outstart(Tmenucmd);
+ for(m = menucmds; m != nil; m = m->next){
+ outcopy(3, (uchar*)"\tM ");
+ outcopy(strlen(m->cmd), (uchar*)m->cmd);
+ outcopy(1, (uchar*)"\n");
+ }
+ outsend();
+ return;
+ }
+ for(mp = &menucmds; *mp != nil; mp = &(*mp)->next)
+ if(!strcmp(s, (*mp)->cmd)){
+ m = *mp;
+ *mp = m->next;
+ free(m->cmd);
+ free(m);
+ return;
+ }
+ *mp = m = malloc(sizeof(Menucmd));
+ if(m == nil) panic("malloc");
+ m->cmd = strdup(s);
+ m->next = nil;
+}
+
+#define NBUF 64
+static uchar buf[NBUF*UTFmax]={' ', ' ', ' ', ' '};
+
+char *
+paren(char *s)
+{
+ uchar *t = buf;
+
+ *t++ = '(';
+ do; while(*t++ = *s++);
+ t[-1] = ')';
+ *t = 0;
+ return (char *)buf;
+}
+
+char*
+genmenu2(int n)
+{
+ Text *t=(Text *)which->user1;
+ char *p;
+ if(n < Search || n == Search && menu2str[Search] != nil)
+ p = menu2str[n];
+ else{
+ n -= Search + (menu2str[Search] != nil);
+ p = findmenucmd(n);
+ if(p == nil)
+ return nil;
+ }
+ if(!hostlock && !t->lock
+ || p == menu2str[Search]
+ || p == menu2str[Look])
+ return p;
+ return paren(p);
+}
+char*
+genmenu2c(int n)
+{
+ Text *t=(Text *)which->user1;
+ char *p;
+ if(n < Search)
+ p = menu2str[n];
+ else if(n == Search)
+ p = "send";
+ else if((p = findmenucmd(n - Search - 1)) == nil)
+ return nil;
+ if(!hostlock && !t->lock)
+ return p;
+ return paren(p);
+}
+char *
+genmenu3(int n)
+{
+ Text *t;
+ int c, i, k, l, w;
+ Rune r;
+ char *p;
+
+ if(n >= NMENU3+nname)
+ return 0;
+ if(n < NMENU3){
+ p = menu3str[n];
+ if(hostlock)
+ p = paren(p);
+ return p;
+ }
+ n -= NMENU3;
+ if(n == 0) /* unless we've been fooled, this is cmd */
+ return (char *)&name[n][1];
+ if(mw == -1){
+ mw = 7; /* strlen("~~sam~~"); */
+ for(i=1; i<nname; i++){
+ w = utflen((char*)name[i]+1)+4; /* include "'+. " */
+ if(w > mw)
+ mw = w;
+ }
+ }
+ if(mw > NBUF)
+ mw = NBUF;
+ t = text[n];
+ buf[0] = name[n][0];
+ buf[1] = '-';
+ buf[2] = ' ';
+ buf[3] = ' ';
+ if(t){
+ if(t->nwin == 1)
+ buf[1] = '+';
+ else if(t->nwin > 1)
+ buf[1] = '*';
+ if(work && t==(Text *)work->user1) {
+ buf[2]= '.';
+ if(modified)
+ buf[0] = '\'';
+ }
+ }
+ l = utflen((char*)name[n]+1);
+ if(l > NBUF-4-2){
+ i = 4;
+ k = 1;
+ while(i < NBUF/2){
+ k += chartorune(&r, (char*)name[n]+k);
+ i++;
+ }
+ c = name[n][k];
+ name[n][k] = 0;
+ strcpy((char*)buf+4, (char*)name[n]+1);
+ name[n][k] = c;
+ strcat((char*)buf, "...");
+ while((l-i) >= NBUF/2-4){
+ k += chartorune(&r, (char*)name[n]+k);
+ i++;
+ }
+ strcat((char*)buf, (char*)name[n]+k);
+ }else
+ strcpy((char*)buf+4, (char*)name[n]+1);
+ i = utflen((char*)buf);
+ k = strlen((char*)buf);
+ while(i<mw && k<sizeof buf-1){
+ buf[k++] = ' ';
+ i++;
+ }
+ buf[k] = 0;
+ return (char *)buf;
+}
--- /dev/null
+++ b/samterm/mesg.c
@@ -1,0 +1,806 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include "flayer.h"
+#include "samterm.h"
+
+#define HSIZE 3 /* Type + short count */
+Header h;
+uchar indata[DATASIZE+1]; /* room for NUL */
+uchar outdata[DATASIZE];
+short outcount;
+int hversion;
+int exiting;
+
+void inmesg(Hmesg, int);
+int inshort(int);
+long inlong(int);
+vlong invlong(int);
+void hsetdot(int, long, long);
+void hmoveto(int, long);
+void hsetsnarf(int);
+void hplumb(int);
+void clrlock(void);
+int snarfswap(char*, int, char**);
+
+void
+rcv(void)
+{
+ int c;
+ static state = 0;
+ static count = 0;
+ static i = 0;
+ static int errs = 0;
+
+ while((c=rcvchar()) != -1)
+ switch(state){
+ case 0:
+ h.type = c;
+ state++;
+ break;
+
+ case 1:
+ h.count0 = c;
+ state++;
+ break;
+
+ case 2:
+ h.count1 = c;
+ count = h.count0|(h.count1<<8);
+ i = 0;
+ if(count > DATASIZE){
+ if(++errs < 5){
+ dumperrmsg(count, h.type, h.count0, c);
+ state = 0;
+ continue;
+ }
+ fprint(2, "type %d count %d\n", h.type, count);
+ panic("count>DATASIZE");
+ }
+ if(count == 0)
+ goto zerocount;
+ state++;
+ break;
+
+ case 3:
+ indata[i++] = c;
+ if(i == count){
+ zerocount:
+ indata[i] = 0;
+ inmesg(h.type, count);
+ state = count = 0;
+ continue;
+ }
+ break;
+ }
+}
+
+Text *
+whichtext(int tg)
+{
+ int i;
+
+ for(i=0; i<nname; i++)
+ if(tag[i] == tg)
+ return text[i];
+ panic("whichtext");
+ return 0;
+}
+
+void
+inmesg(Hmesg type, int count)
+{
+ Text *t;
+ int i, m;
+ long l;
+ Flayer *lp;
+
+ m = inshort(0);
+ l = inlong(2);
+ switch(type){
+ case -1:
+ panic("rcv error");
+ default:
+ fprint(2, "type %d\n", type);
+ panic("rcv unknown");
+
+ case Hversion:
+ hversion = m;
+ break;
+
+ case Hbindname:
+ l = invlong(2); /* for 64-bit pointers */
+ if((i=whichmenu(m)) < 0)
+ break;
+ /* in case of a race, a bindname may already have occurred */
+ if((t=whichtext(m)) == 0)
+ t=(Text *)l;
+ else /* let the old one win; clean up the new one */
+ while(((Text *)l)->nwin>0)
+ closeup(&((Text *)l)->l[((Text *)l)->front]);
+ text[i] = t;
+ text[i]->tag = m;
+ break;
+
+ case Hcurrent:
+ if(whichmenu(m)<0)
+ break;
+ t = whichtext(m);
+ i = which && ((Text *)which->user1)==&cmd && m!=cmd.tag;
+ if(t==0 && (t = sweeptext(0, m))==0)
+ break;
+ if(t->l[t->front].textfn==0)
+ panic("Hcurrent");
+ lp = &t->l[t->front];
+ if(i){
+ flupfront(lp);
+ flborder(lp, 0);
+ work = lp;
+ }else
+ current(lp);
+ break;
+
+ case Hmovname:
+ if((m=whichmenu(m)) < 0)
+ break;
+ t = text[m];
+ l = tag[m];
+ i = name[m][0];
+ text[m] = 0; /* suppress panic in menudel */
+ menudel(m);
+ if(t == &cmd)
+ m = 0;
+ else{
+ if (nname>0 && text[0]==&cmd)
+ m = 1;
+ else m = 0;
+ for(; m<nname; m++)
+ if(strcmp((char*)indata+2, (char*)name[m]+1)<0)
+ break;
+ }
+ menuins(m, indata+2, t, i, (int)l);
+ break;
+
+ case Hgrow:
+ if(whichmenu(m) >= 0)
+ hgrow(m, l, inlong(6), 1);
+ break;
+
+ case Hnewname:
+ menuins(0, (uchar *)"", (Text *)0, ' ', m);
+ break;
+
+ case Hcheck0:
+ i = whichmenu(m);
+ if(i>=0) {
+ t = text[i];
+ if(t)
+ t->lock++;
+ outTs(Tcheck, m);
+ }
+ break;
+
+ case Hcheck:
+ i = whichmenu(m);
+ if(i>=0) {
+ t = text[i];
+ if(t && t->lock)
+ t->lock--;
+ hcheck(m);
+ }
+ break;
+
+ case Hunlock:
+ clrlock();
+ break;
+
+ case Hdata:
+ if(whichmenu(m) >= 0)
+ l += hdata(m, l, indata+6, count-6);
+ Checkscroll:
+ if(m == cmd.tag){
+ for(i=0; i<NL; i++){
+ lp = &cmd.l[i];
+ if(lp->textfn)
+ center(lp, l>=0? l : lp->p1);
+ }
+ }
+ break;
+
+ case Horigin:
+ if(whichmenu(m) >= 0)
+ horigin(m, l);
+ break;
+
+ case Hunlockfile:
+ if(whichmenu(m)>=0 && (t = whichtext(m))->lock){
+ --t->lock;
+ l = -1;
+ goto Checkscroll;
+ }
+ break;
+
+ case Hsetdot:
+ if(whichmenu(m) >= 0)
+ hsetdot(m, l, inlong(6));
+ break;
+
+ case Hgrowdata:
+ if(whichmenu(m)<0)
+ break;
+ hgrow(m, l, inlong(6), 0);
+ whichtext(m)->lock++; /* fake the request */
+ l += hdata(m, l, indata+10, count-10);
+ goto Checkscroll;
+
+ case Hmoveto:
+ if(whichmenu(m)>=0)
+ hmoveto(m, l);
+ break;
+
+ case Hclean:
+ if((m = whichmenu(m)) >= 0)
+ name[m][0] = ' ';
+ break;
+
+ case Hdirty:
+ if((m = whichmenu(m))>=0)
+ name[m][0] = '\'';
+ break;
+
+ case Hdelname:
+ if((m=whichmenu(m)) >= 0)
+ menudel(m);
+ break;
+
+ case Hcut:
+ if(whichmenu(m) >= 0)
+ hcut(m, l, inlong(6));
+ break;
+
+ case Hclose:
+ if(whichmenu(m)<0 || (t = whichtext(m))==0)
+ break;
+ l = t->nwin;
+ for(i = 0,lp = t->l; l>0 && i<NL; i++,lp++)
+ if(lp->textfn){
+ closeup(lp);
+ --l;
+ }
+ break;
+
+ case Hsetpat:
+ setpat((char *)indata);
+ break;
+
+ case Hsetsnarf:
+ hsetsnarf(m);
+ break;
+
+ case Hsnarflen:
+ snarflen = inlong(0);
+ break;
+
+ case Hack:
+ outT0(Tack);
+ break;
+
+ case Hexit:
+ exiting = 1;
+ outT0(Texit);
+ threadexitsall(nil);
+ break;
+
+ case Hplumb:
+ hplumb(m);
+ break;
+
+ case Hmenucmd:
+ menucmd((char *)indata);
+ break;
+ }
+}
+
+void
+setlock(void)
+{
+ hostlock++;
+ setcursor(mousectl, cursor = &lockarrow);
+}
+
+void
+clrlock(void)
+{
+ hasunlocked = 1;
+ if(hostlock > 0)
+ hostlock--;
+ if(hostlock == 0)
+ setcursor(mousectl, cursor=(Cursor *)0);
+}
+
+void
+startfile(Text *t)
+{
+ outTsv(Tstartfile, t->tag, (vlong)t); /* for 64-bit pointers */
+ setlock();
+}
+
+void
+startnewfile(int type, Text *t)
+{
+ t->tag = Untagged;
+ outTv(type, (vlong)t); /* for 64-bit pointers */
+}
+
+int
+inshort(int n)
+{
+ return indata[n]|(indata[n+1]<<8);
+}
+
+long
+inlong(int n)
+{
+ return indata[n]|(indata[n+1]<<8)|
+ ((long)indata[n+2]<<16)|((long)indata[n+3]<<24);
+}
+
+vlong
+invlong(int n)
+{
+ vlong v;
+
+ v = (indata[n+7]<<24) | (indata[n+6]<<16) | (indata[n+5]<<8) | indata[n+4];
+ v = (v<<16) | (indata[n+3]<<8) | indata[n+2];
+ v = (v<<16) | (indata[n+1]<<8) | indata[n];
+ return v;
+}
+
+void
+outT0(Tmesg type)
+{
+ outstart(type);
+ outsend();
+}
+
+void
+outTl(Tmesg type, long l)
+{
+ outstart(type);
+ outlong(l);
+ outsend();
+}
+
+void
+outTs(Tmesg type, int s)
+{
+ outstart(type);
+ outshort(s);
+ outsend();
+}
+
+void
+outTss(Tmesg type, int s1, int s2)
+{
+ outstart(type);
+ outshort(s1);
+ outshort(s2);
+ outsend();
+}
+
+void
+outTsll(Tmesg type, int s1, long l1, long l2)
+{
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ outlong(l2);
+ outsend();
+}
+
+void
+outTsl(Tmesg type, int s1, long l1)
+{
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ outsend();
+}
+
+void
+outTsv(Tmesg type, int s1, vlong v1)
+{
+ outstart(type);
+ outshort(s1);
+ outvlong(v1);
+ outsend();
+}
+
+void
+outTv(Tmesg type, vlong v1)
+{
+ outstart(type);
+ outvlong(v1);
+ outsend();
+}
+
+void
+outTslS(Tmesg type, int s1, long l1, Rune *s)
+{
+ char buf[DATASIZE*UTFmax+1];
+ char *c;
+
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ c = buf;
+ while(*s)
+ c += runetochar(c, s++);
+ *c++ = 0;
+ outcopy(c-buf, (uchar *)buf);
+ outsend();
+}
+
+void
+outTsls(Tmesg type, int s1, long l1, int s2)
+{
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ outshort(s2);
+ outsend();
+}
+
+void
+outstart(Tmesg type)
+{
+ outdata[0] = type;
+ outcount = 0;
+}
+
+void
+outcopy(int count, uchar *data)
+{
+ while(count--)
+ outdata[HSIZE+outcount++] = *data++;
+}
+
+void
+outshort(int s)
+{
+ uchar buf[2];
+
+ buf[0]=s;
+ buf[1]=s>>8;
+ outcopy(2, buf);
+}
+
+void
+outlong(long l)
+{
+ uchar buf[4];
+
+ buf[0]=l;
+ buf[1]=l>>8;
+ buf[2]=l>>16;
+ buf[3]=l>>24;
+ outcopy(4, buf);
+}
+
+void
+outvlong(vlong v)
+{
+ int i;
+ uchar buf[8];
+
+ for(i = 0; i < sizeof(buf); i++){
+ buf[i] = v;
+ v >>= 8;
+ }
+
+ outcopy(8, buf);
+}
+
+void
+outsend(void)
+{
+ if(outcount>DATASIZE-HSIZE)
+ panic("outcount>sizeof outdata");
+ outdata[1]=outcount;
+ outdata[2]=outcount>>8;
+ if(write(1, (char *)outdata, outcount+HSIZE)!=outcount+HSIZE)
+ panic("write error");
+}
+
+
+void
+hsetdot(int m, long p0, long p1)
+{
+ Text *t = whichtext(m);
+ Flayer *l = &t->l[t->front];
+
+ flushtyping(1);
+ flsetselect(l, p0, p1);
+}
+
+void
+horigin(int m, long p0)
+{
+ Text *t = whichtext(m);
+ Flayer *l = &t->l[t->front];
+ long a;
+ ulong n;
+ Rune *r;
+
+ if(!flprepare(l)){
+ l->origin = p0;
+ return;
+ }
+ a = p0-l->origin;
+ if(a>=0 && a<l->f.nchars)
+ frdelete(&l->f, 0, a);
+ else if(a<0 && -a<l->f.nchars){
+ r = rload(&t->rasp, p0, l->origin, &n);
+ frinsert(&l->f, r, r+n, 0);
+ }else
+ frdelete(&l->f, 0, l->f.nchars);
+ l->origin = p0;
+ scrdraw(l, t->rasp.nrunes);
+ if(l->visible==Some)
+ flrefresh(l, l->entire, 0);
+ hcheck(m);
+}
+
+void
+hmoveto(int m, long p0)
+{
+ Text *t = whichtext(m);
+ Flayer *l = &t->l[t->front];
+
+ if(p0<l->origin || p0-l->origin>l->f.nchars*9/10)
+ outTsll(Torigin, m, p0, 2L);
+}
+
+void
+hcheck(int m)
+{
+ Flayer *l;
+ Text *t;
+ int reqd = 0, i;
+ long n, nl, a;
+ Rune *r;
+
+ if(m == Untagged)
+ return;
+ t = whichtext(m);
+ if(t == 0) /* possible in a half-built window */
+ return;
+ for(l = &t->l[0], i = 0; i<NL; i++, l++){
+ if(l->textfn==0 || !flprepare(l)) /* BUG: don't
+ need this if BUG below
+ is fixed */
+ continue;
+ a = t->l[i].origin;
+ n = rcontig(&t->rasp, a, a+l->f.nchars, 1);
+ if(n<l->f.nchars) /* text missing in middle of screen */
+ a+=n;
+ else{ /* text missing at end of screen? */
+ Again:
+ if(l->f.lastlinefull)
+ goto Checksel; /* all's well */
+ a = t->l[i].origin+l->f.nchars;
+ n = t->rasp.nrunes-a;
+ if(n==0)
+ goto Checksel;
+ if(n>TBLOCKSIZE)
+ n = TBLOCKSIZE;
+ n = rcontig(&t->rasp, a, a+n, 1);
+ if(n>0){
+ rload(&t->rasp, a, a+n, 0);
+ nl = l->f.nchars;
+ r = scratch;
+ flinsert(l, r, r+n, l->origin+nl);
+ if(nl == l->f.nchars) /* made no progress */
+ goto Checksel;
+ goto Again;
+ }
+ }
+ if(!reqd){
+ n = rcontig(&t->rasp, a, a+TBLOCKSIZE, 0);
+ if(n <= 0)
+ panic("hcheck request==0");
+ outTsls(Trequest, m, a, (int)n);
+ outTs(Tcheck, m);
+ t->lock++; /* for the Trequest */
+ t->lock++; /* for the Tcheck */
+ reqd++;
+ }
+ Checksel:
+ flsetselect(l, l->p0, l->p1);
+ }
+}
+
+void
+flnewlyvisible(Flayer *l)
+{
+ hcheck(((Text *)l->user1)->tag);
+}
+
+void
+hsetsnarf(int nc)
+{
+ char *s2;
+ char *s1;
+ int i;
+ int n;
+
+ setcursor(mousectl, &deadmouse);
+ s2 = alloc(nc+1);
+ for(i=0; i<nc; i++)
+ s2[i] = getch();
+ s2[nc] = 0;
+ n = snarfswap(s2, nc, &s1);
+ if(n >= 0){
+ if(!s1)
+ n = 0;
+ s1 = realloc(s1, n+1);
+ if (!s1)
+ panic("realloc");
+ s1[n] = 0;
+ snarflen = n;
+ outTs(Tsetsnarf, n);
+ if(n>0 && write(1, s1, n)!=n)
+ panic("snarf write error");
+ free(s1);
+ }else
+ outTs(Tsetsnarf, 0);
+ free(s2);
+ setcursor(mousectl, cursor);
+}
+
+void
+hplumb(int nc)
+{
+ int i;
+ char *s;
+ Plumbmsg *m;
+
+ s = alloc(nc);
+ for(i=0; i<nc; i++)
+ s[i] = getch();
+ if(plumbfd >= 0){
+ m = plumbunpack(s, nc);
+ if(m != 0)
+ plumbsend(plumbfd, m);
+ plumbfree(m);
+ }
+ free(s);
+}
+
+void
+hgrow(int m, long a, long new, int req)
+{
+ int i;
+ Flayer *l;
+ Text *t = whichtext(m);
+ long o, b;
+
+ if(new <= 0)
+ panic("hgrow");
+ rresize(&t->rasp, a, 0L, new);
+ for(l = &t->l[0], i = 0; i<NL; i++, l++){
+ if(l->textfn == 0)
+ continue;
+ o = l->origin;
+ b = a-o-rmissing(&t->rasp, o, a);
+ if(a < o)
+ l->origin+=new;
+ if(a < l->p0)
+ l->p0+=new;
+ if(a < l->p1)
+ l->p1+=new;
+ /* must prevent b temporarily becoming unsigned */
+ if(!req || a<o || (b>0 && b>l->f.nchars) ||
+ (l->f.nchars==0 && a-o>0))
+ continue;
+ if(new>TBLOCKSIZE)
+ new = TBLOCKSIZE;
+ outTsls(Trequest, m, a, (int)new);
+ t->lock++;
+ req = 0;
+ }
+}
+
+int
+hdata1(Text *t, long a, Rune *r, int len)
+{
+ int i;
+ Flayer *l;
+ long o, b;
+
+ for(l = &t->l[0], i=0; i<NL; i++, l++){
+ if(l->textfn==0)
+ continue;
+ o = l->origin;
+ b = a-o-rmissing(&t->rasp, o, a);
+ /* must prevent b temporarily becoming unsigned */
+ if(a<o || (b>0 && b>l->f.nchars))
+ continue;
+ flinsert(l, r, r+len, o+b);
+ }
+ rdata(&t->rasp, a, a+len, r);
+ rclean(&t->rasp);
+ return len;
+}
+
+int
+hdata(int m, long a, uchar *s, int len)
+{
+ int i, w;
+ Text *t = whichtext(m);
+ Rune buf[DATASIZE], *r;
+
+ if(t->lock)
+ --t->lock;
+ if(len == 0)
+ return 0;
+ r = buf;
+ for(i=0; i<len; i+=w,s+=w)
+ w = chartorune(r++, (char*)s);
+ return hdata1(t, a, buf, r-buf);
+}
+
+int
+hdatarune(int m, long a, Rune *r, int len)
+{
+ Text *t = whichtext(m);
+
+ if(t->lock)
+ --t->lock;
+ if(len == 0)
+ return 0;
+ return hdata1(t, a, r, len);
+}
+
+void
+hcut(int m, long a, long old)
+{
+ Flayer *l;
+ Text *t = whichtext(m);
+ int i;
+ long o, b;
+
+ if(t->lock)
+ --t->lock;
+ for(l = &t->l[0], i = 0; i<NL; i++, l++){
+ if(l->textfn == 0)
+ continue;
+ o = l->origin;
+ b = a-o-rmissing(&t->rasp, o, a);
+ /* must prevent b temporarily becoming unsigned */
+ if((b<0 || b<l->f.nchars) && a+old>=o){
+ fldelete(l, b<0? o : o+b,
+ a+old-rmissing(&t->rasp, o, a+old));
+ }
+ if(a+old<o)
+ l->origin-=old;
+ else if(a<=o)
+ l->origin = a;
+ if(a+old<l->p0)
+ l->p0-=old;
+ else if(a<=l->p0)
+ l->p0 = a;
+ if(a+old<l->p1)
+ l->p1-=old;
+ else if(a<=l->p1)
+ l->p1 = a;
+ }
+ rresize(&t->rasp, a, old, 0L);
+ rclean(&t->rasp);
+}
--- /dev/null
+++ b/samterm/mkfile
@@ -1,0 +1,36 @@
+</$objtype/mkfile
+
+TARG=samterm
+OFILES=main.$O\
+ icons.$O\
+ menu.$O\
+ mesg.$O\
+ rasp.$O\
+ scroll.$O\
+ flayer.$O\
+ io.$O\
+ plan9.$O\
+
+HFILES=samterm.h\
+ flayer.h\
+ /sys/include/frame.h\
+
+LIB= /$objtype/lib/libdraw.a\
+ /$objtype/lib/libframe.a\
+
+BIN=/$objtype/bin/aux
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+CFLAGS=-I../sam $CFLAGS
+
+mesg.$O: ../sam/mesg.h
+
+syms:V:
+ $CC -a $CFLAGS main.c > syms
+ for(i in *.c) $CC -aa $CFLAGS $i >> syms
--- /dev/null
+++ b/samterm/plan9.c
@@ -1,0 +1,275 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include "flayer.h"
+#include "samterm.h"
+
+enum {
+ STACK = 4096,
+};
+
+static char exname[64];
+
+void
+usage(void)
+{
+ fprint(2, "usage: samterm [-ai]\n");
+ threadexitsall("usage");
+}
+
+void
+getscreen(int argc, char **argv)
+{
+ char *t;
+ uint tlen;
+
+ ARGBEGIN{
+ case 'a':
+ autoindent = 1;
+ break;
+ case 'i':
+ spacesindent = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(initdraw(panic1, nil, "sam") < 0){
+ fprint(2, "samterm: initdraw: %r\n");
+ threadexitsall("init");
+ }
+ t = getenv("tabstop");
+ if(t != nil && (tlen = strtoul(t, nil, 0)) > 0)
+ maxtab = tlen;
+ free(t);
+ draw(screen, screen->clipr, display->white, nil, ZP);
+}
+
+int
+screensize(int *w, int *h)
+{
+ int fd, n;
+ char buf[5*12+1];
+
+ fd = open("/dev/screen", OREAD);
+ if(fd < 0)
+ return 0;
+ n = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if (n != sizeof(buf)-1)
+ return 0;
+ buf[n] = 0;
+ if (h) {
+ *h = atoi(buf+4*12)-atoi(buf+2*12);
+ if (*h < 0)
+ return 0;
+ }
+ if (w) {
+ *w = atoi(buf+3*12)-atoi(buf+1*12);
+ if (*w < 0)
+ return 0;
+ }
+ return 1;
+}
+
+int
+snarfswap(char *fromsam, int nc, char **tosam)
+{
+ char *s1;
+ int f, n, ss;
+
+ f = open("/dev/snarf", 0);
+ if(f < 0)
+ return -1;
+ ss = SNARFSIZE;
+ if(hversion < 2)
+ ss = 4096;
+ *tosam = s1 = alloc(ss);
+ n = read(f, s1, ss-1);
+ close(f);
+ if(n < 0)
+ n = 0;
+ if (n == 0) {
+ *tosam = 0;
+ free(s1);
+ } else
+ s1[n] = 0;
+ f = create("/dev/snarf", 1, 0666);
+ if(f >= 0){
+ write(f, fromsam, nc);
+ close(f);
+ }
+ return n;
+}
+
+void
+dumperrmsg(int count, int type, int count0, int c)
+{
+ fprint(2, "samterm: host mesg: count %d %ux %ux %ux %s...ignored\n",
+ count, type, count0, c, rcvstring());
+}
+
+void
+removeextern(void)
+{
+ remove(exname);
+}
+
+Readbuf hostbuf[2];
+Readbuf plumbbuf[2];
+
+void
+extproc(void *argv)
+{
+ Channel *c;
+ int i, n, which, *fdp;
+ void **arg;
+
+ arg = argv;
+ c = arg[0];
+ fdp = arg[1];
+
+ i = 0;
+ for(;;){
+ i = 1-i; /* toggle */
+ n = read(*fdp, plumbbuf[i].data, sizeof plumbbuf[i].data);
+ if(n <= 0){
+ fprint(2, "samterm: extern read error: %r\n");
+ threadexits("extern"); /* not a fatal error */
+ }
+ plumbbuf[i].n = n;
+ which = i;
+ send(c, &which);
+ }
+}
+
+int
+plumbformat(int i)
+{
+ Plumbmsg *m;
+ char *addr, *data, *act;
+ int n;
+
+ data = (char*)plumbbuf[i].data;
+ m = plumbunpack(data, plumbbuf[i].n);
+ if(m == nil)
+ return 0;
+ n = m->ndata;
+ if(n == 0){
+ plumbfree(m);
+ return 0;
+ }
+ act = plumblookup(m->attr, "action");
+ if(act!=nil && strcmp(act, "showfile")!=0){
+ /* can't handle other cases yet */
+ plumbfree(m);
+ return 0;
+ }
+ addr = plumblookup(m->attr, "addr");
+ if(addr){
+ if(addr[0] == '\0')
+ addr = nil;
+ else
+ addr = strdup(addr); /* copy to safe storage; we'll overwrite data */
+ }
+ memmove(data, "B ", 2); /* we know there's enough room for this */
+ memmove(data+2, m->data, n);
+ n += 2;
+ if(data[n-1] != '\n')
+ data[n++] = '\n';
+ if(addr != nil){
+ if(n+strlen(addr)+1+1 <= READBUFSIZE)
+ n += sprint(data+n, "%s\n", addr);
+ free(addr);
+ }
+ plumbbuf[i].n = n;
+ plumbfree(m);
+ return 1;
+}
+
+void
+plumbproc(void *argv)
+{
+ Channel *c;
+ int i, n, which, *fdp;
+ void **arg;
+
+ arg = argv;
+ c = arg[0];
+ fdp = arg[1];
+
+ i = 0;
+ for(;;){
+ i = 1-i; /* toggle */
+ n = read(*fdp, plumbbuf[i].data, READBUFSIZE);
+ if(n <= 0){
+ fprint(2, "samterm: plumb read error: %r\n");
+ threadexits("plumb"); /* not a fatal error */
+ }
+ plumbbuf[i].n = n;
+ if(plumbformat(i)){
+ which = i;
+ send(c, &which);
+ }
+ }
+}
+
+int
+plumbstart(void)
+{
+ static int fd;
+ static void *arg[2];
+
+ plumbfd = plumbopen("send", OWRITE|OCEXEC); /* not open is ok */
+ fd = plumbopen("edit", OREAD|OCEXEC);
+ if(fd < 0)
+ return -1;
+ plumbc = chancreate(sizeof(int), 0);
+ if(plumbc == nil){
+ close(fd);
+ return -1;
+ }
+ arg[0] =plumbc;
+ arg[1] = &fd;
+ proccreate(plumbproc, arg, STACK);
+ return 1;
+}
+
+void
+hostproc(void *arg)
+{
+ Channel *c;
+ int i, n, which;
+
+ c = arg;
+
+ i = 0;
+ for(;;){
+ i = 1-i; /* toggle */
+ n = read(0, hostbuf[i].data, sizeof hostbuf[i].data);
+ if(n <= 0){
+ if(n==0){
+ if(exiting)
+ threadexits(nil);
+ werrstr("unexpected eof");
+ }
+ fprint(2, "samterm: host read error: %r\n");
+ threadexitsall("host");
+ }
+ hostbuf[i].n = n;
+ which = i;
+ send(c, &which);
+ }
+}
+
+void
+hoststart(void)
+{
+ hostc = chancreate(sizeof(int), 0);
+ proccreate(hostproc, hostc, STACK);
+}
--- /dev/null
+++ b/samterm/rasp.c
@@ -1,0 +1,265 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+void
+rinit(Rasp *r)
+{
+ r->nrunes=0;
+ r->sect=0;
+}
+
+void
+rclear(Rasp *r)
+{
+ Section *s, *ns;
+
+ for(s=r->sect; s; s=ns){
+ ns = s->next;
+ free(s->text);
+ free(s);
+ }
+ r->sect = 0;
+}
+
+Section*
+rsinsert(Rasp *r, Section *s) /* insert before s */
+{
+ Section *t;
+ Section *u;
+
+ t = alloc(sizeof(Section));
+ if(r->sect == s){ /* includes empty list case: r->sect==s==0 */
+ r->sect = t;
+ t->next = s;
+ }else{
+ u = r->sect;
+ if(u == 0)
+ panic("rsinsert 1");
+ do{
+ if(u->next == s){
+ t->next = s;
+ u->next = t;
+ goto Return;
+ }
+ u=u->next;
+ }while(u);
+ panic("rsinsert 2");
+ }
+ Return:
+ return t;
+}
+
+void
+rsdelete(Rasp *r, Section *s)
+{
+ Section *t;
+
+ if(s == 0)
+ panic("rsdelete");
+ if(r->sect == s){
+ r->sect = s->next;
+ goto Free;
+ }
+ for(t=r->sect; t; t=t->next)
+ if(t->next == s){
+ t->next = s->next;
+ Free:
+ if(s->text)
+ free(s->text);
+ free(s);
+ return;
+ }
+ panic("rsdelete 2");
+}
+
+void
+splitsect(Rasp *r, Section *s, long n0)
+{
+ if(s == 0)
+ panic("splitsect");
+ rsinsert(r, s->next);
+ if(s->text == 0)
+ s->next->text = 0;
+ else{
+ s->next->text = alloc(RUNESIZE*(TBLOCKSIZE+1));
+ Strcpy(s->next->text, s->text+n0);
+ s->text[n0] = 0;
+ }
+ s->next->nrunes = s->nrunes-n0;
+ s->nrunes = n0;
+}
+
+Section *
+findsect(Rasp *r, Section *s, long p, long q) /* find sect containing q and put q on a sect boundary */
+{
+ if(s==0 && p!=q)
+ panic("findsect");
+ for(; s && p+s->nrunes<=q; s=s->next)
+ p += s->nrunes;
+ if(p != q){
+ splitsect(r, s, q-p);
+ s = s->next;
+ }
+ return s;
+}
+
+void
+rresize(Rasp *r, long a, long old, long new)
+{
+ Section *s, *t, *ns;
+
+ s = findsect(r, r->sect, 0L, a);
+ t = findsect(r, s, a, a+old);
+ for(; s!=t; s=ns){
+ ns=s->next;
+ rsdelete(r, s);
+ }
+ /* now insert the new piece before t */
+ if(new > 0){
+ ns=rsinsert(r, t);
+ ns->nrunes=new;
+ ns->text=0;
+ }
+ r->nrunes += new-old;
+}
+
+void
+rdata(Rasp *r, long p0, long p1, Rune *cp)
+{
+ Section *s, *t, *ns;
+
+ s = findsect(r, r->sect, 0L, p0);
+ t = findsect(r, s, p0, p1);
+ for(; s!=t; s=ns){
+ ns=s->next;
+ if(s->text)
+ panic("rdata");
+ rsdelete(r, s);
+ }
+ p1 -= p0;
+ s = rsinsert(r, t);
+ s->text = alloc(RUNESIZE*(TBLOCKSIZE+1));
+ memmove(s->text, cp, RUNESIZE*p1);
+ s->text[p1] = 0;
+ s->nrunes = p1;
+}
+
+void
+rclean(Rasp *r)
+{
+ Section *s;
+
+ for(s=r->sect; s; s=s->next)
+ while(s->next && (s->text!=0)==(s->next->text!=0)){
+ if(s->text){
+ if(s->nrunes+s->next->nrunes>TBLOCKSIZE)
+ break;
+ Strcpy(s->text+s->nrunes, s->next->text);
+ }
+ s->nrunes += s->next->nrunes;
+ rsdelete(r, s->next);
+ }
+}
+
+void
+Strcpy(Rune *to, Rune *from)
+{
+ do; while(*to++ = *from++);
+}
+
+Rune*
+rload(Rasp *r, ulong p0, ulong p1, ulong *nrp)
+{
+ Section *s;
+ long p;
+ int n, nb;
+
+ nb = 0;
+ Strgrow(&scratch, &nscralloc, p1-p0+1);
+ scratch[0] = 0;
+ for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
+ p += s->nrunes;
+ while(p<p1 && s){
+ /*
+ * Subtle and important. If we are preparing to handle an 'rdata'
+ * call, it's because we have an 'rresize' hole here, so the
+ * screen doesn't have data for that space anyway (it got cut
+ * first). So pretend it isn't there.
+ */
+ if(s->text){
+ n = s->nrunes-(p0-p);
+ if(n>p1-p0) /* all in this section */
+ n = p1-p0;
+ memmove(scratch+nb, s->text+(p0-p), n*RUNESIZE);
+ nb += n;
+ scratch[nb] = 0;
+ }
+ p += s->nrunes;
+ p0 = p;
+ s = s->next;
+ }
+ if(nrp)
+ *nrp = nb;
+ return scratch;
+}
+
+int
+rmissing(Rasp *r, ulong p0, ulong p1)
+{
+ Section *s;
+ long p;
+ int n, nm=0;
+
+ for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
+ p += s->nrunes;
+ while(p<p1 && s){
+ if(s->text == 0){
+ n = s->nrunes-(p0-p);
+ if(n > p1-p0) /* all in this section */
+ n = p1-p0;
+ nm += n;
+ }
+ p += s->nrunes;
+ p0 = p;
+ s = s->next;
+ }
+ return nm;
+}
+
+int
+rcontig(Rasp *r, ulong p0, ulong p1, int text)
+{
+ Section *s;
+ long p, n;
+ int np=0;
+
+ for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
+ p += s->nrunes;
+ while(p<p1 && s && (text? (s->text!=0) : (s->text==0))){
+ n = s->nrunes-(p0-p);
+ if(n > p1-p0) /* all in this section */
+ n = p1-p0;
+ np += n;
+ p += s->nrunes;
+ p0 = p;
+ s = s->next;
+ }
+ return np;
+}
+
+void
+Strgrow(Rune **s, long *n, int want) /* can always toss the old data when called */
+{
+ if(*n >= want)
+ return;
+ free(*s);
+ *s = alloc(RUNESIZE*want);
+ *n = want;
+}
--- /dev/null
+++ b/samterm/samterm.h
@@ -1,0 +1,182 @@
+#define SAMTERM
+
+#define RUNESIZE sizeof(Rune)
+#define MAXFILES 256
+#define READBUFSIZE 8192
+#define NL 5
+
+enum{
+ Up,
+ Down,
+
+ Kbel=0x7,
+};
+
+typedef struct Text Text;
+typedef struct Section Section;
+typedef struct Rasp Rasp;
+typedef struct Readbuf Readbuf;
+
+struct Section
+{
+ long nrunes;
+ Rune *text; /* if null, we haven't got it */
+ Section *next;
+};
+
+struct Rasp
+{
+ long nrunes;
+ Section *sect;
+};
+
+#define Untagged ((ushort)65535)
+
+struct Text
+{
+ Rasp rasp;
+ short nwin;
+ short front; /* input window */
+ ushort tag;
+ char lock;
+ Flayer l[NL]; /* screen storage */
+};
+
+struct Readbuf
+{
+ short n; /* # bytes in buf */
+ uchar data[READBUFSIZE]; /* data bytes */
+};
+
+enum Resource
+{
+ RHost,
+ RKeyboard,
+ RMouse,
+ RPlumb,
+ RResize,
+ NRes,
+};
+
+extern Text **text;
+extern uchar **name;
+extern ushort *tag;
+extern int nname;
+extern int mname;
+extern Cursor bullseye;
+extern Cursor deadmouse;
+extern Cursor lockarrow;
+extern Cursor *cursor;
+extern Flayer *which;
+extern Flayer *work;
+extern Text cmd;
+extern Rune *scratch;
+extern long nscralloc;
+extern char hostlock;
+extern char hasunlocked;
+extern long snarflen;
+extern Mousectl* mousectl;
+extern Keyboardctl* keyboardctl;
+extern Mouse* mousep;
+extern long modified;
+extern int maxtab;
+extern Readbuf hostbuf[2]; /* double buffer; it's synchronous communication */
+extern Readbuf plumbbuf[2]; /* double buffer; it's synchronous communication */
+extern Channel *plumbc;
+extern Channel *hostc;
+extern int hversion;
+extern int plumbfd;
+extern int exiting;
+extern int autoindent;
+extern int spacesindent;
+
+Rune *gettext(Flayer*, long, ulong*);
+void *alloc(ulong n);
+
+void iconinit(void);
+void getscreen(int, char**);
+void initio(void);
+void setlock(void);
+void outcmd(void);
+void rinit(Rasp*);
+void startnewfile(int, Text*);
+void getmouse(void);
+void mouseunblock(void);
+void kbdblock(void);
+void hoststart(void);
+int plumbstart(void);
+int button(int but);
+int load(char*, int);
+int waitforio(void);
+int rcvchar(void);
+int getch(void);
+int kbdchar(void);
+int qpeekc(void);
+void cut(Text*, int, int, int);
+void paste(Text*, int);
+void snarf(Text*, int);
+int center(Flayer*, long);
+int xmenuhit(int, Menu*);
+void buttons(int);
+int getr(Rectangle*);
+void current(Flayer*);
+void duplicate(Flayer*, Rectangle, Font*, int);
+void startfile(Text*);
+void panic(char*);
+void panic1(Display*, char*);
+void closeup(Flayer*);
+void Strgrow(Rune**, long*, int);
+int RESIZED(void);
+void resize(void);
+void rcv(void);
+void type(Flayer*, int);
+void menu2hit(void);
+void menu3hit(void);
+void scroll(Flayer*, int);
+void hcheck(int);
+void rclear(Rasp*);
+int whichmenu(int);
+void hcut(int, long, long);
+void horigin(int, long);
+void hgrow(int, long, long, int);
+int hdata(int, long, uchar*, int);
+int hdatarune(int, long, Rune*, int);
+Rune *rload(Rasp*, ulong, ulong, ulong*);
+void menuins(int, uchar*, Text*, int, int);
+void menudel(int);
+Text *sweeptext(int, int);
+void setpat(char*);
+void menucmd(char*);
+void scrdraw(Flayer*, long tot);
+int rcontig(Rasp*, ulong, ulong, int);
+int rmissing(Rasp*, ulong, ulong);
+void rresize(Rasp *, long, long, long);
+void rdata(Rasp*, long, long, Rune*);
+void rclean(Rasp*);
+void scrorigin(Flayer*, int, long);
+long scrtotal(Flayer*);
+void flnewlyvisible(Flayer*);
+char *rcvstring(void);
+void Strcpy(Rune*, Rune*);
+void Strncpy(Rune*, Rune*, long);
+void flushtyping(int);
+void dumperrmsg(int, int, int, int);
+int screensize(int*,int*);
+void getmouse(void);
+
+#include "mesg.h"
+
+void outTs(Tmesg, int);
+void outT0(Tmesg);
+void outTl(Tmesg, long);
+void outTslS(Tmesg, int, long, Rune*);
+void outTsll(Tmesg, int, long, long);
+void outTsl(Tmesg, int, long);
+void outTsv(Tmesg, int, vlong);
+void outTv(Tmesg, vlong);
+void outstart(Tmesg);
+void outcopy(int, uchar*);
+void outshort(int);
+void outlong(long);
+void outvlong(vlong);
+void outsend(void);
--- /dev/null
+++ b/samterm/scroll.c
@@ -1,0 +1,177 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+static Image *scrtmp;
+static Image *scrback;
+
+void
+scrtemps(void)
+{
+ int h;
+
+ if(scrtmp)
+ return;
+ if(screensize(0, &h) == 0)
+ h = 2048;
+ scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, 0);
+ scrback = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, 0);
+ if(scrtmp==0 || scrback==0)
+ panic("scrtemps");
+}
+
+Rectangle
+scrpos(Rectangle r, long p0, long p1, long tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024L*1024L)
+ tot>>=10, p0>>=10, p1>>=10;
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+scrmark(Flayer *l, Rectangle r)
+{
+ r.max.x--;
+ if(rectclip(&r, l->scroll)) {
+ if (l->f.b == nil)
+ panic("scrmark: nil l->f.b");
+ draw(l->f.b, r, l->f.cols[HIGH], nil, ZP);
+ }
+}
+
+void
+scrunmark(Flayer *l, Rectangle r)
+{
+ if(rectclip(&r, l->scroll)) {
+ if (l->f.b == nil)
+ panic("scrunmark: nil l->f.b");
+ draw(l->f.b, r, scrback, nil, Pt(0, r.min.y-l->scroll.min.y));
+ }
+}
+
+void
+scrdraw(Flayer *l, long tot)
+{
+ Rectangle r, r1, r2;
+ Image *b;
+
+ scrtemps();
+ if(l->f.b == 0)
+ panic("scrdraw");
+ r = l->scroll;
+ r1 = r;
+ if(l->visible == All){
+ b = scrtmp;
+ r1.min.x = 0;
+ r1.max.x = Dx(r);
+ }else
+ b = l->f.b;
+ r2 = scrpos(r1, l->origin, l->origin+l->f.nchars, tot);
+ if(!eqrect(r2, l->lastsr)){
+ l->lastsr = r2;
+ draw(b, r1, l->f.cols[BORD], nil, ZP);
+ draw(b, r2, l->f.cols[BACK], nil, r2.min);
+ r2 = r1;
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, l->f.cols[BORD], nil, ZP);
+ if(b!=l->f.b)
+ draw(l->f.b, r, b, nil, r1.min);
+ }
+}
+
+void
+scroll(Flayer *l, int but)
+{
+ int in = 0, oin;
+ long tot = scrtotal(l);
+ Rectangle scr, r, s, rt;
+ int x, y, my, oy, h;
+ long p0;
+
+ if(l->visible==None)
+ return;
+
+ s = l->scroll;
+ x = s.min.x+FLSCROLLWID/2;
+ scr = scrpos(l->scroll, l->origin, l->origin+l->f.nchars, tot);
+ r = scr;
+ y = scr.min.y;
+ my = mousep->xy.y;
+ draw(scrback, Rect(0,0,Dx(l->scroll), Dy(l->scroll)), l->f.b, nil, l->scroll.min);
+ do{
+ oin = in;
+ in = (but > 3) || (but == 2) || abs(x-mousep->xy.x)<=FLSCROLLWID/2;
+ if(oin && !in)
+ scrunmark(l, r);
+ if(in){
+ scrmark(l, r);
+ oy = y;
+ my = mousep->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 1 || but == 4){
+ p0 = l->origin-frcharofpt(&l->f, Pt(s.max.x, my));
+ rt = scrpos(l->scroll, p0, p0+l->f.nchars, tot);
+ y = rt.min.y;
+ }else if(but == 2){
+ y = my;
+ if(y > s.max.y-2)
+ y = s.max.y-2;
+ }else if(but == 3 || but == 5){
+ p0 = l->origin+frcharofpt(&l->f, Pt(s.max.x, my));
+ rt = scrpos(l->scroll, p0, p0+l->f.nchars, tot);
+ y = rt.min.y;
+ }
+ if(y != oy){
+ scrunmark(l, r);
+ r = rectaddpt(scr, Pt(0, y-scr.min.y));
+ scrmark(l, r);
+ }
+ }
+ }while(but <= 3 && button(but));
+ if(in){
+ h = s.max.y-s.min.y;
+ scrunmark(l, r);
+ p0 = 0;
+ if(but == 1 || but == 4){
+ but = 1;
+ p0 = (long)(my-s.min.y)/l->f.font->height+1;
+ }else if(but == 2){
+ if(tot > 1024L*1024L)
+ p0 = ((tot>>10)*(y-s.min.y)/h)<<10;
+ else
+ p0 = tot*(y-s.min.y)/h;
+ }else if(but == 3 || but == 5){
+ but = 3;
+ p0 = l->origin+frcharofpt(&l->f, Pt(s.max.x, my));
+ if(p0 > tot)
+ p0 = tot;
+ }
+ scrorigin(l, but, p0);
+ }
+}
--- /dev/null
+++ b/samterm/syms
@@ -1,0 +1,1055 @@
+sizeof_1_ = 8;
+aggr _1_
+{
+ 'D' 0 hlength;
+ 'D' 4 llength;
+};
+
+defn
+_1_(addr) {
+ complex _1_ addr;
+ print(" hlength ", addr.hlength, "\n");
+ print(" llength ", addr.llength, "\n");
+};
+
+sizeof_2_ = 8;
+aggr _2_
+{
+ 'V' 0 length;
+ {
+ 'D' 0 hlength;
+ 'D' 4 llength;
+ };
+};
+
+defn
+_2_(addr) {
+ complex _2_ addr;
+ print(" length ", addr.length, "\n");
+ print("_1_ {\n");
+ _1_(addr+0);
+ print("}\n");
+};
+
+UTFmax = 3;
+Runesync = 128;
+Runeself = 128;
+Runeerror = 128;
+sizeofFconv = 24;
+aggr Fconv
+{
+ 'X' 0 out;
+ 'X' 4 eout;
+ 'D' 8 f1;
+ 'D' 12 f2;
+ 'D' 16 f3;
+ 'D' 20 chr;
+};
+
+defn
+Fconv(addr) {
+ complex Fconv addr;
+ print(" out ", addr.out\X, "\n");
+ print(" eout ", addr.eout\X, "\n");
+ print(" f1 ", addr.f1, "\n");
+ print(" f2 ", addr.f2, "\n");
+ print(" f3 ", addr.f3, "\n");
+ print(" chr ", addr.chr, "\n");
+};
+
+sizeofTm = 40;
+aggr Tm
+{
+ 'D' 0 sec;
+ 'D' 4 min;
+ 'D' 8 hour;
+ 'D' 12 mday;
+ 'D' 16 mon;
+ 'D' 20 year;
+ 'D' 24 wday;
+ 'D' 28 yday;
+ 'a' 32 zone;
+ 'D' 36 tzoff;
+};
+
+defn
+Tm(addr) {
+ complex Tm addr;
+ print(" sec ", addr.sec, "\n");
+ print(" min ", addr.min, "\n");
+ print(" hour ", addr.hour, "\n");
+ print(" mday ", addr.mday, "\n");
+ print(" mon ", addr.mon, "\n");
+ print(" year ", addr.year, "\n");
+ print(" wday ", addr.wday, "\n");
+ print(" yday ", addr.yday, "\n");
+ print(" zone ", addr.zone, "\n");
+ print(" tzoff ", addr.tzoff, "\n");
+};
+
+PNPROC = 1;
+PNGROUP = 2;
+sizeofLock = 4;
+aggr Lock
+{
+ 'D' 0 val;
+};
+
+defn
+Lock(addr) {
+ complex Lock addr;
+ print(" val ", addr.val, "\n");
+};
+
+sizeofQLp = 12;
+aggr QLp
+{
+ 'D' 0 inuse;
+ 'A' QLp 4 next;
+ 'C' 8 state;
+};
+
+defn
+QLp(addr) {
+ complex QLp addr;
+ print(" inuse ", addr.inuse, "\n");
+ print(" next ", addr.next\X, "\n");
+ print(" state ", addr.state, "\n");
+};
+
+sizeofQLock = 16;
+aggr QLock
+{
+ Lock 0 lock;
+ 'D' 4 locked;
+ 'A' QLp 8 $head;
+ 'A' QLp 12 $tail;
+};
+
+defn
+QLock(addr) {
+ complex QLock addr;
+ print("Lock lock {\n");
+ Lock(addr.lock);
+ print("}\n");
+ print(" locked ", addr.locked, "\n");
+ print(" $head ", addr.$head\X, "\n");
+ print(" $tail ", addr.$tail\X, "\n");
+};
+
+sizeofRWLock = 20;
+aggr RWLock
+{
+ Lock 0 lock;
+ 'D' 4 readers;
+ 'D' 8 writer;
+ 'A' QLp 12 $head;
+ 'A' QLp 16 $tail;
+};
+
+defn
+RWLock(addr) {
+ complex RWLock addr;
+ print("Lock lock {\n");
+ Lock(addr.lock);
+ print("}\n");
+ print(" readers ", addr.readers, "\n");
+ print(" writer ", addr.writer, "\n");
+ print(" $head ", addr.$head\X, "\n");
+ print(" $tail ", addr.$tail\X, "\n");
+};
+
+RFNAMEG = 1;
+RFENVG = 2;
+RFFDG = 4;
+RFNOTEG = 8;
+RFPROC = 16;
+RFMEM = 32;
+RFNOWAIT = 64;
+RFCNAMEG = 1024;
+RFCENVG = 2048;
+RFCFDG = 4096;
+RFREND = 8192;
+sizeofQid = 8;
+aggr Qid
+{
+ 'U' 0 path;
+ 'U' 4 vers;
+};
+
+defn
+Qid(addr) {
+ complex Qid addr;
+ print(" path ", addr.path, "\n");
+ print(" vers ", addr.vers, "\n");
+};
+
+sizeofDir = 116;
+aggr Dir
+{
+ 'a' 0 name;
+ 'a' 28 uid;
+ 'a' 56 gid;
+ Qid 84 qid;
+ 'U' 92 mode;
+ 'D' 96 atime;
+ 'D' 100 mtime;
+ {
+ 'V' 104 length;
+ {
+ 'D' 104 hlength;
+ 'D' 108 llength;
+ };
+ };
+ 'u' 112 type;
+ 'u' 114 dev;
+};
+
+defn
+Dir(addr) {
+ complex Dir addr;
+ print(" name ", addr.name, "\n");
+ print(" uid ", addr.uid, "\n");
+ print(" gid ", addr.gid, "\n");
+ print("Qid qid {\n");
+ Qid(addr.qid);
+ print("}\n");
+ print(" mode ", addr.mode, "\n");
+ print(" atime ", addr.atime, "\n");
+ print(" mtime ", addr.mtime, "\n");
+ print("_2_ {\n");
+ _2_(addr+104);
+ print("}\n");
+ print(" type ", addr.type, "\n");
+ print(" dev ", addr.dev, "\n");
+};
+
+sizeofWaitmsg = 112;
+aggr Waitmsg
+{
+ 'a' 0 pid;
+ 'a' 12 time;
+ 'a' 48 msg;
+};
+
+defn
+Waitmsg(addr) {
+ complex Waitmsg addr;
+ print(" pid ", addr.pid, "\n");
+ print(" time ", addr.time, "\n");
+ print(" msg ", addr.msg, "\n");
+};
+
+DBlack = 255;
+DBlue = 201;
+DRed = 15;
+DYellow = 3;
+DGreen = 192;
+DWhite = 0;
+Displaybufsize = 8000;
+ICOSSCALE = 1024;
+Borderwidth = 4;
+Refbackup = 0;
+Refnone = 1;
+Refmesg = 2;
+Endsquare = 0;
+Enddisc = 1;
+Endarrow = 2;
+Endmask = 31;
+sizeofPoint = 8;
+aggr Point
+{
+ 'D' 0 x;
+ 'D' 4 y;
+};
+
+defn
+Point(addr) {
+ complex Point addr;
+ print(" x ", addr.x, "\n");
+ print(" y ", addr.y, "\n");
+};
+
+sizeofRectangle = 16;
+aggr Rectangle
+{
+ Point 0 min;
+ Point 8 max;
+};
+
+defn
+Rectangle(addr) {
+ complex Rectangle addr;
+ print("Point min {\n");
+ Point(addr.min);
+ print("}\n");
+ print("Point max {\n");
+ Point(addr.max);
+ print("}\n");
+};
+
+sizeofScreen = 16;
+aggr Screen
+{
+ 'X' 0 display;
+ 'D' 4 id;
+ 'X' 8 image;
+ 'X' 12 fill;
+};
+
+defn
+Screen(addr) {
+ complex Screen addr;
+ print(" display ", addr.display\X, "\n");
+ print(" id ", addr.id, "\n");
+ print(" image ", addr.image\X, "\n");
+ print(" fill ", addr.fill\X, "\n");
+};
+
+sizeofDisplay = 8156;
+aggr Display
+{
+ QLock 0 qlock;
+ 'D' 16 dirno;
+ 'D' 20 fd;
+ 'D' 24 reffd;
+ 'D' 28 ctlfd;
+ 'D' 32 imageid;
+ 'D' 36 $local;
+ 'D' 40 ldepth;
+ 'X' 44 error;
+ 'X' 48 devdir;
+ 'X' 52 windir;
+ 'a' 56 oldlabel;
+ 'U' 120 dataqid;
+ 'X' 124 ones;
+ 'X' 128 zeros;
+ 'X' 132 image;
+ 'a' 136 buf;
+ 'X' 8140 bufp;
+ 'X' 8144 defaultfont;
+ 'X' 8148 defaultsubfont;
+ 'X' 8152 windows;
+};
+
+defn
+Display(addr) {
+ complex Display addr;
+ print("QLock qlock {\n");
+ QLock(addr.qlock);
+ print("}\n");
+ print(" dirno ", addr.dirno, "\n");
+ print(" fd ", addr.fd, "\n");
+ print(" reffd ", addr.reffd, "\n");
+ print(" ctlfd ", addr.ctlfd, "\n");
+ print(" imageid ", addr.imageid, "\n");
+ print(" $local ", addr.$local, "\n");
+ print(" ldepth ", addr.ldepth, "\n");
+ print(" error ", addr.error\X, "\n");
+ print(" devdir ", addr.devdir\X, "\n");
+ print(" windir ", addr.windir\X, "\n");
+ print(" oldlabel ", addr.oldlabel, "\n");
+ print(" dataqid ", addr.dataqid, "\n");
+ print(" ones ", addr.ones\X, "\n");
+ print(" zeros ", addr.zeros\X, "\n");
+ print(" image ", addr.image\X, "\n");
+ print(" buf ", addr.buf, "\n");
+ print(" bufp ", addr.bufp\X, "\n");
+ print(" defaultfont ", addr.defaultfont\X, "\n");
+ print(" defaultsubfont ", addr.defaultsubfont\X, "\n");
+ print(" windows ", addr.windows\X, "\n");
+};
+
+sizeofImage = 56;
+aggr Image
+{
+ 'A' Display 0 display;
+ 'D' 4 id;
+ Rectangle 8 r;
+ Rectangle 24 clipr;
+ 'D' 40 ldepth;
+ 'D' 44 repl;
+ 'A' Screen 48 screen;
+ 'A' Image 52 next;
+};
+
+defn
+Image(addr) {
+ complex Image addr;
+ print(" display ", addr.display\X, "\n");
+ print(" id ", addr.id, "\n");
+ print("Rectangle r {\n");
+ Rectangle(addr.r);
+ print("}\n");
+ print("Rectangle clipr {\n");
+ Rectangle(addr.clipr);
+ print("}\n");
+ print(" ldepth ", addr.ldepth, "\n");
+ print(" repl ", addr.repl, "\n");
+ print(" screen ", addr.screen\X, "\n");
+ print(" next ", addr.next\X, "\n");
+};
+
+sizeofRGB = 12;
+aggr RGB
+{
+ 'U' 0 red;
+ 'U' 4 green;
+ 'U' 8 blue;
+};
+
+defn
+RGB(addr) {
+ complex RGB addr;
+ print(" red ", addr.red, "\n");
+ print(" green ", addr.green, "\n");
+ print(" blue ", addr.blue, "\n");
+};
+
+sizeofFontchar = 8;
+aggr Fontchar
+{
+ 'D' 0 x;
+ 'b' 4 top;
+ 'b' 5 bottom;
+ 'C' 6 left;
+ 'b' 7 width;
+};
+
+defn
+Fontchar(addr) {
+ complex Fontchar addr;
+ print(" x ", addr.x, "\n");
+ print(" top ", addr.top, "\n");
+ print(" bottom ", addr.bottom, "\n");
+ print(" left ", addr.left, "\n");
+ print(" width ", addr.width, "\n");
+};
+
+sizeofSubfont = 16;
+aggr Subfont
+{
+ 'X' 0 name;
+ 'd' 4 n;
+ 'b' 6 height;
+ 'C' 7 ascent;
+ 'A' Fontchar 8 info;
+ 'A' Image 12 bits;
+};
+
+defn
+Subfont(addr) {
+ complex Subfont addr;
+ print(" name ", addr.name\X, "\n");
+ print(" n ", addr.n, "\n");
+ print(" height ", addr.height, "\n");
+ print(" ascent ", addr.ascent, "\n");
+ print(" info ", addr.info\X, "\n");
+ print(" bits ", addr.bits\X, "\n");
+};
+
+LOG2NFCACHE = 6;
+NFCACHE = 64;
+NFLOOK = 5;
+NFSUBF = 2;
+MAXFCACHE = 1029;
+MAXSUBF = 50;
+DSUBF = 4;
+SUBFAGE = 10000;
+CACHEAGE = 10000;
+sizeofCachefont = 16;
+aggr Cachefont
+{
+ 'u' 0 min;
+ 'u' 2 max;
+ 'D' 4 offset;
+ 'X' 8 name;
+ 'X' 12 subfontname;
+};
+
+defn
+Cachefont(addr) {
+ complex Cachefont addr;
+ print(" min ", addr.min, "\n");
+ print(" max ", addr.max, "\n");
+ print(" offset ", addr.offset, "\n");
+ print(" name ", addr.name\X, "\n");
+ print(" subfontname ", addr.subfontname\X, "\n");
+};
+
+sizeofCacheinfo = 8;
+aggr Cacheinfo
+{
+ 'u' 0 x;
+ 'b' 2 width;
+ 'C' 3 left;
+ 'u' 4 value;
+ 'u' 6 age;
+};
+
+defn
+Cacheinfo(addr) {
+ complex Cacheinfo addr;
+ print(" x ", addr.x, "\n");
+ print(" width ", addr.width, "\n");
+ print(" left ", addr.left, "\n");
+ print(" value ", addr.value, "\n");
+ print(" age ", addr.age, "\n");
+};
+
+sizeofCachesubf = 12;
+aggr Cachesubf
+{
+ 'U' 0 age;
+ 'A' Cachefont 4 cf;
+ 'A' Subfont 8 f;
+};
+
+defn
+Cachesubf(addr) {
+ complex Cachesubf addr;
+ print(" age ", addr.age, "\n");
+ print(" cf ", addr.cf\X, "\n");
+ print(" f ", addr.f\X, "\n");
+};
+
+sizeofFont = 52;
+aggr Font
+{
+ 'X' 0 name;
+ 'A' Display 4 display;
+ 'd' 8 height;
+ 'd' 10 ascent;
+ 'D' 12 maxldepth;
+ 'd' 16 width;
+ 'd' 18 ldepth;
+ 'd' 20 nsub;
+ 'U' 24 age;
+ 'D' 28 ncache;
+ 'D' 32 nsubf;
+ 'A' Cacheinfo 36 cache;
+ 'A' Cachesubf 40 subf;
+ 'A' Cachefont 44 sub;
+ 'A' Image 48 cacheimage;
+};
+
+defn
+Font(addr) {
+ complex Font addr;
+ print(" name ", addr.name\X, "\n");
+ print(" display ", addr.display\X, "\n");
+ print(" height ", addr.height, "\n");
+ print(" ascent ", addr.ascent, "\n");
+ print(" maxldepth ", addr.maxldepth, "\n");
+ print(" width ", addr.width, "\n");
+ print(" ldepth ", addr.ldepth, "\n");
+ print(" nsub ", addr.nsub, "\n");
+ print(" age ", addr.age, "\n");
+ print(" ncache ", addr.ncache, "\n");
+ print(" nsubf ", addr.nsubf, "\n");
+ print(" cache ", addr.cache\X, "\n");
+ print(" subf ", addr.subf\X, "\n");
+ print(" sub ", addr.sub\X, "\n");
+ print(" cacheimage ", addr.cacheimage\X, "\n");
+};
+
+complex Point ZP;
+complex Rectangle ZR;
+complex Display display;
+complex Font font;
+complex Image screen;
+sizeofAlt = 20;
+aggr Alt
+{
+ 'X' 0 c;
+ 'X' 4 v;
+ 'D' 8 op;
+ 'X' 12 tag;
+ 'U' 16 q;
+};
+
+defn
+Alt(addr) {
+ complex Alt addr;
+ print(" c ", addr.c\X, "\n");
+ print(" v ", addr.v\X, "\n");
+ print(" op ", addr.op, "\n");
+ print(" tag ", addr.tag\X, "\n");
+ print(" q ", addr.q, "\n");
+};
+
+sizeofRef = 4;
+aggr Ref
+{
+ 'D' 0 ref;
+};
+
+defn
+Ref(addr) {
+ complex Ref addr;
+ print(" ref ", addr.ref, "\n");
+};
+
+sizeofCursor = 72;
+aggr Cursor
+{
+ Point 0 offset;
+ 'a' 8 clr;
+ 'a' 40 set;
+};
+
+defn
+Cursor(addr) {
+ complex Cursor addr;
+ print("Point offset {\n");
+ Point(addr.offset);
+ print("}\n");
+ print(" clr ", addr.clr, "\n");
+ print(" set ", addr.set, "\n");
+};
+
+sizeofMouse = 16;
+aggr Mouse
+{
+ 'D' 0 buttons;
+ Point 4 xy;
+ 'U' 12 msec;
+};
+
+defn
+Mouse(addr) {
+ complex Mouse addr;
+ print(" buttons ", addr.buttons, "\n");
+ print("Point xy {\n");
+ Point(addr.xy);
+ print("}\n");
+ print(" msec ", addr.msec, "\n");
+};
+
+sizeofMousectl = 44;
+aggr Mousectl
+{
+ {
+ 'D' 0 buttons;
+ Point 4 xy;
+ 'U' 12 msec;
+ };
+ 'X' 16 c;
+ 'X' 20 reshapec;
+ 'X' 24 file;
+ 'D' 28 mfd;
+ 'D' 32 cfd;
+ 'D' 36 pid;
+ 'A' Image 40 image;
+};
+
+defn
+Mousectl(addr) {
+ complex Mousectl addr;
+ print("Mouse {\n");
+ Mouse(addr+0);
+ print("}\n");
+ print(" c ", addr.c\X, "\n");
+ print(" reshapec ", addr.reshapec\X, "\n");
+ print(" file ", addr.file\X, "\n");
+ print(" mfd ", addr.mfd, "\n");
+ print(" cfd ", addr.cfd, "\n");
+ print(" pid ", addr.pid, "\n");
+ print(" image ", addr.image\X, "\n");
+};
+
+sizeofMenu = 12;
+aggr Menu
+{
+ 'X' 0 item;
+ 'X' 4 gen;
+ 'D' 8 lasthit;
+};
+
+defn
+Menu(addr) {
+ complex Menu addr;
+ print(" item ", addr.item\X, "\n");
+ print(" gen ", addr.gen\X, "\n");
+ print(" lasthit ", addr.lasthit, "\n");
+};
+
+sizeofKeyboardctl = 20;
+aggr Keyboardctl
+{
+ 'X' 0 c;
+ 'X' 4 file;
+ 'D' 8 consfd;
+ 'D' 12 ctlfd;
+ 'D' 16 pid;
+};
+
+defn
+Keyboardctl(addr) {
+ complex Keyboardctl addr;
+ print(" c ", addr.c\X, "\n");
+ print(" file ", addr.file\X, "\n");
+ print(" consfd ", addr.consfd, "\n");
+ print(" ctlfd ", addr.ctlfd, "\n");
+ print(" pid ", addr.pid, "\n");
+};
+
+BACK = 0;
+HIGH = 1;
+BORD = 2;
+TEXT = 3;
+HTEXT = 4;
+NCOL = 5;
+sizeof_3_ = 4;
+aggr _3_
+{
+ 'd' 0 bc;
+ 'd' 2 minwid;
+};
+
+defn
+_3_(addr) {
+ complex _3_ addr;
+ print(" bc ", addr.bc, "\n");
+ print(" minwid ", addr.minwid, "\n");
+};
+
+sizeof_4_ = 4;
+aggr _4_
+{
+ 'X' 0 ptr;
+ {
+ 'd' 0 bc;
+ 'd' 2 minwid;
+ };
+};
+
+defn
+_4_(addr) {
+ complex _4_ addr;
+ print(" ptr ", addr.ptr\X, "\n");
+ print("_3_ {\n");
+ _3_(addr+0);
+ print("}\n");
+};
+
+sizeofFrbox = 12;
+aggr Frbox
+{
+ 'D' 0 wid;
+ 'D' 4 nrune;
+ {
+ 'X' 8 ptr;
+ {
+ 'd' 8 bc;
+ 'd' 10 minwid;
+ };
+ };
+};
+
+defn
+Frbox(addr) {
+ complex Frbox addr;
+ print(" wid ", addr.wid, "\n");
+ print(" nrune ", addr.nrune, "\n");
+ print("_4_ {\n");
+ _4_(addr+8);
+ print("}\n");
+};
+
+complex Font font;
+complex Display display;
+sizeofFrame = 108;
+aggr Frame
+{
+ 'A' Font 0 font;
+ 'A' Display 4 display;
+ 'A' Image 8 b;
+ 'a' 12 cols;
+ Rectangle 32 r;
+ Rectangle 48 entire;
+ 'X' 64 scroll;
+ 'A' Frbox 68 box;
+ 'U' 72 p0;
+ 'U' 76 p1;
+ 'u' 80 nbox;
+ 'u' 82 nalloc;
+ 'u' 84 maxtab;
+ 'u' 86 nchars;
+ 'u' 88 nlines;
+ 'u' 90 maxlines;
+ 'u' 92 lastlinefull;
+ 'u' 94 modified;
+ 'A' Image 96 tick;
+ 'A' Image 100 tickback;
+ 'D' 104 ticked;
+};
+
+defn
+Frame(addr) {
+ complex Frame addr;
+ print(" font ", addr.font\X, "\n");
+ print(" display ", addr.display\X, "\n");
+ print(" b ", addr.b\X, "\n");
+ print(" cols ", addr.cols, "\n");
+ print("Rectangle r {\n");
+ Rectangle(addr.r);
+ print("}\n");
+ print("Rectangle entire {\n");
+ Rectangle(addr.entire);
+ print("}\n");
+ print(" scroll ", addr.scroll\X, "\n");
+ print(" box ", addr.box\X, "\n");
+ print(" p0 ", addr.p0, "\n");
+ print(" p1 ", addr.p1, "\n");
+ print(" nbox ", addr.nbox, "\n");
+ print(" nalloc ", addr.nalloc, "\n");
+ print(" maxtab ", addr.maxtab, "\n");
+ print(" nchars ", addr.nchars, "\n");
+ print(" nlines ", addr.nlines, "\n");
+ print(" maxlines ", addr.maxlines, "\n");
+ print(" lastlinefull ", addr.lastlinefull, "\n");
+ print(" modified ", addr.modified, "\n");
+ print(" tick ", addr.tick\X, "\n");
+ print(" tickback ", addr.tickback\X, "\n");
+ print(" ticked ", addr.ticked, "\n");
+};
+
+None = 0;
+Some = 1;
+All = 2;
+Clicktime = 1000;
+sizeofFlayer = 172;
+aggr Flayer
+{
+ Frame 0 f;
+ 'D' 108 origin;
+ 'D' 112 p0;
+ 'D' 116 p1;
+ 'D' 120 click;
+ 'X' 124 textfn;
+ 'D' 128 user0;
+ 'X' 132 user1;
+ Rectangle 136 entire;
+ Rectangle 152 scroll;
+ 'D' 168 visible;
+};
+
+defn
+Flayer(addr) {
+ complex Flayer addr;
+ print("Frame f {\n");
+ Frame(addr.f);
+ print("}\n");
+ print(" origin ", addr.origin, "\n");
+ print(" p0 ", addr.p0, "\n");
+ print(" p1 ", addr.p1, "\n");
+ print(" click ", addr.click, "\n");
+ print(" textfn ", addr.textfn\X, "\n");
+ print(" user0 ", addr.user0, "\n");
+ print(" user1 ", addr.user1\X, "\n");
+ print("Rectangle entire {\n");
+ Rectangle(addr.entire);
+ print("}\n");
+ print("Rectangle scroll {\n");
+ Rectangle(addr.scroll);
+ print("}\n");
+ print(" visible ", addr.visible, "\n");
+};
+
+Up = 0;
+Down = 1;
+sizeofSection = 12;
+aggr Section
+{
+ 'D' 0 nrunes;
+ 'X' 4 text;
+ 'A' Section 8 next;
+};
+
+defn
+Section(addr) {
+ complex Section addr;
+ print(" nrunes ", addr.nrunes, "\n");
+ print(" text ", addr.text\X, "\n");
+ print(" next ", addr.next\X, "\n");
+};
+
+sizeofRasp = 8;
+aggr Rasp
+{
+ 'D' 0 nrunes;
+ 'A' Section 4 sect;
+};
+
+defn
+Rasp(addr) {
+ complex Rasp addr;
+ print(" nrunes ", addr.nrunes, "\n");
+ print(" sect ", addr.sect\X, "\n");
+};
+
+sizeofText = 876;
+aggr Text
+{
+ Rasp 0 rasp;
+ 'd' 8 nwin;
+ 'd' 10 front;
+ 'u' 12 tag;
+ 'C' 14 lock;
+ 'a' 16 l;
+};
+
+defn
+Text(addr) {
+ complex Text addr;
+ print("Rasp rasp {\n");
+ Rasp(addr.rasp);
+ print("}\n");
+ print(" nwin ", addr.nwin, "\n");
+ print(" front ", addr.front, "\n");
+ print(" tag ", addr.tag, "\n");
+ print(" lock ", addr.lock, "\n");
+ print(" l ", addr.l, "\n");
+};
+
+sizeofReadbuf = 8196;
+aggr Readbuf
+{
+ 'd' 0 n;
+ 'a' 2 data;
+};
+
+defn
+Readbuf(addr) {
+ complex Readbuf addr;
+ print(" n ", addr.n, "\n");
+ print(" data ", addr.data, "\n");
+};
+
+RHost = 0;
+RKeyboard = 1;
+RMouse = 2;
+RPlumb = 3;
+RReshape = 4;
+NRes = 5;
+complex Cursor bullseye;
+complex Cursor deadmouse;
+complex Cursor lockarrow;
+complex Cursor cursor;
+complex Flayer which;
+complex Flayer work;
+complex Text cmd;
+complex Mousectl mousectl;
+complex Keyboardctl keyboardctl;
+complex Mouse mousep;
+Tversion = 0;
+Tstartcmdfile = 1;
+Tcheck = 2;
+Trequest = 3;
+Torigin = 4;
+Tstartfile = 5;
+Tworkfile = 6;
+Ttype = 7;
+Tcut = 8;
+Tpaste = 9;
+Tsnarf = 10;
+Tstartnewfile = 11;
+Twrite = 12;
+Tclose = 13;
+Tlook = 14;
+Tsearch = 15;
+Tsend = 16;
+Tdclick = 17;
+Tstartsnarf = 18;
+Tsetsnarf = 19;
+Tack = 20;
+Texit = 21;
+Tplumb = 22;
+TMAX = 23;
+Hversion = 0;
+Hbindname = 1;
+Hcurrent = 2;
+Hnewname = 3;
+Hmovname = 4;
+Hgrow = 5;
+Hcheck0 = 6;
+Hcheck = 7;
+Hunlock = 8;
+Hdata = 9;
+Horigin = 10;
+Hunlockfile = 11;
+Hsetdot = 12;
+Hgrowdata = 13;
+Hmoveto = 14;
+Hclean = 15;
+Hdirty = 16;
+Hcut = 17;
+Hsetpat = 18;
+Hdelname = 19;
+Hclose = 20;
+Hsetsnarf = 21;
+Hsnarflen = 22;
+Hack = 23;
+Hexit = 24;
+Hplumb = 25;
+HMAX = 26;
+sizeofHeader = 4;
+aggr Header
+{
+ 'b' 0 type;
+ 'b' 1 count0;
+ 'b' 2 count1;
+ 'a' 3 data;
+};
+
+defn
+Header(addr) {
+ complex Header addr;
+ print(" type ", addr.type, "\n");
+ print(" count0 ", addr.count0, "\n");
+ print(" count1 ", addr.count1, "\n");
+ print(" data ", addr.data, "\n");
+};
+
+complex Text cmd;
+complex Cursor cursor;
+complex Flayer which;
+complex Flayer work;
+complex Text threadmain:t;
+complex Rectangle threadmain:r;
+complex Flayer threadmain:nwhich;
+complex Flayer current:nw;
+complex Text current:t;
+complex Flayer closeup:l;
+complex Text closeup:t;
+complex Text findl:t;
+complex Flayer duplicate:l;
+complex Rectangle duplicate:r;
+complex Font duplicate:f;
+complex Text duplicate:t;
+complex Flayer duplicate:nl;
+complex Rectangle getr:rp;
+complex Point getr:p;
+complex Rectangle getr:r;
+complex Text snarf:t;
+complex Flayer snarf:l;
+complex Text cut:t;
+complex Flayer cut:l;
+complex Text paste:t;
+complex Flayer scrorigin:l;
+complex Text scrorigin:t;
+complex Rasp raspc:r;
+complex Rasp ctlw:r;
+complex Rasp ctlu:r;
+complex Flayer center:l;
+complex Text center:t;
+complex Flayer onethird:l;
+complex Text onethird:t;
+complex Rectangle onethird:s;
+complex Text flushtyping:t;
+complex Flayer type:l;
+complex Text type:t;
+complex Flayer gettext:l;
+complex Text gettext:t;
+complex Flayer scrtotal:l;