ref: a600cca5ff367ca7e7c64a14a8e87ba376780f6d
parent: d990c25d5795b16c181e875bf2f55aa06c2f75f9
author: henesy <devnull@localhost>
date: Sun Nov 4 12:16:24 EST 2018
init 3
--- /dev/null
+++ b/appl/NOTICE
@@ -1,0 +1,25 @@
+This copyright NOTICE applies to all files in this directory and
+subdirectories, unless another copyright notice appears in a given
+file or subdirectory. If you take substantial code from this software to use in
+other programs, you must somehow include with it an appropriate
+copyright notice that includes the copyright notice and the other
+notices below. It is fine (and often tidier) to do that in a separate
+file such as NOTICE, LICENCE or COPYING.
+
+Copyright © 1995-1999 Lucent Technologies Inc.
+Portions Copyright © 1997-2000 Vita Nuova Limited
+Portions Copyright © 2000-2010 Vita Nuova Holdings Limited
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--- /dev/null
+++ b/appl/acme/acme.b
@@ -1,0 +1,1125 @@
+implement Acme;
+
+include "common.m";
+
+sys : Sys;
+bufio : Bufio;
+workdir : Workdir;
+drawm : Draw;
+styx : Styx;
+acme : Acme;
+gui : Gui;
+graph : Graph;
+dat : Dat;
+framem : Framem;
+utils : Utils;
+regx : Regx;
+scrl : Scroll;
+textm : Textm;
+filem : Filem;
+windowm : Windowm;
+rowm : Rowm;
+columnm : Columnm;
+bufferm : Bufferm;
+diskm : Diskm;
+exec : Exec;
+look : Look;
+timerm : Timerm;
+fsys : Fsys;
+xfidm : Xfidm;
+plumbmsg : Plumbmsg;
+editm: Edit;
+editlog: Editlog;
+editcmd: Editcmd;
+styxaux: Styxaux;
+
+sprint : import sys;
+BACK, HIGH, BORD, TEXT, HTEXT, NCOL : import Framem;
+Point, Rect, Font, Image, Display, Pointer: import drawm;
+TRUE, FALSE, maxtab : import dat;
+Ref, Reffont, Command, Timer, Lock, Cursor : import dat;
+row, reffont, activecol, mouse, typetext, mousetext, barttext, argtext, seltext, button, modbutton, colbutton, arrowcursor, boxcursor, plumbed : import dat;
+Xfid : import xfidm;
+cmouse, ckeyboard, cwait, ccommand, ckill, cxfidalloc, cxfidfree, cerr, cplumb, cedit : import dat;
+font, bflush, balloc, draw : import graph;
+Arg, PNPROC, PNGROUP : import utils;
+arginit, argopt, argf, error, warning, postnote : import utils;
+yellow, green, red, blue, black, white, mainwin, display : import gui;
+Disk : import diskm;
+Row : import rowm;
+Column : import columnm;
+Window : import windowm;
+Text, Tag, Body, Columntag : import textm;
+Buffer : import bufferm;
+snarfbuf : import exec;
+Msg : import plumbmsg;
+
+tfd : ref Sys->FD;
+lasttime : int;
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ acmectxt = ctxt;
+
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ {
+ # tfd = sys->create("./time", Sys->OWRITE, 8r600);
+ # lasttime = sys->millisec();
+ bufio = load Bufio Bufio->PATH;
+ workdir = load Workdir Workdir->PATH;
+ drawm = load Draw Draw->PATH;
+
+ styx = load Styx Styx->PATH;
+
+ acme = load Acme SELF;
+
+ gui = load Gui path(Gui->PATH);
+ graph = load Graph path(Graph->PATH);
+ dat = load Dat path(Dat->PATH);
+ framem = load Framem path(Framem->PATH);
+ utils = load Utils path(Utils->PATH);
+ regx = load Regx path(Regx->PATH);
+ scrl = load Scroll path(Scroll->PATH);
+ textm = load Textm path(Textm->PATH);
+ filem = load Filem path(Filem->PATH);
+ windowm = load Windowm path(Windowm->PATH);
+ rowm = load Rowm path(Rowm->PATH);
+ columnm = load Columnm path(Columnm->PATH);
+ bufferm = load Bufferm path(Bufferm->PATH);
+ diskm = load Diskm path(Diskm->PATH);
+ exec = load Exec path(Exec->PATH);
+ look = load Look path(Look->PATH);
+ timerm = load Timerm path(Timerm->PATH);
+ fsys = load Fsys path(Fsys->PATH);
+ xfidm = load Xfidm path(Xfidm->PATH);
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ editm = load Edit path(Edit->PATH);
+ editlog = load Editlog path(Editlog->PATH);
+ editcmd = load Editcmd path(Editcmd->PATH);
+ styxaux = load Styxaux path(Styxaux->PATH);
+
+ mods := ref Dat->Mods(sys, bufio, drawm, styx, styxaux,
+ acme, gui, graph, dat, framem,
+ utils, regx, scrl,
+ textm, filem, windowm, rowm, columnm,
+ bufferm, diskm, exec, look, timerm,
+ fsys, xfidm, plumbmsg, editm, editlog, editcmd);
+
+ styx->init();
+ styxaux->init();
+
+ utils->init(mods);
+ gui->init(mods);
+ graph->init(mods);
+ dat->init(mods);
+ framem->init(mods);
+ regx->init(mods);
+ scrl->init(mods);
+ textm->init(mods);
+ filem->init(mods);
+ windowm->init(mods);
+ rowm->init(mods);
+ columnm->init(mods);
+ bufferm->init(mods);
+ diskm->init(mods);
+ exec->init(mods);
+ look->init(mods);
+ timerm->init(mods);
+ fsys->init(mods);
+ xfidm->init(mods);
+ editm->init(mods);
+ editlog->init(mods);
+ editcmd->init(mods);
+
+ utils->debuginit();
+
+ if (plumbmsg->init(1, "edit", Dat->PLUMBSIZE) >= 0)
+ plumbed = 1;
+
+ main(argl);
+
+ }
+# exception{
+# * =>
+# sys->fprint(sys->fildes(2), "acme: fatal: %s\n", utils->getexc());
+# sys->print("acme: fatal: %s\n", utils->getexc());
+# shutdown("error");
+# }
+}
+
+timing(s : string)
+{
+ thistime := sys->millisec();
+ sys->fprint(tfd, "%s %d\n", s, thistime-lasttime);
+ lasttime = thistime;
+}
+
+path(p : string) : string
+{
+ if (RELEASECOPY)
+ return p;
+ else {
+ # inlined strrchr since not loaded yet
+ for (n := len p - 1; n >= 0; n--)
+ if (p[n] == '/')
+ break;
+ if (n >= 0)
+ p = p[n+1:];
+ return "/usr/jrf/acme/" + p;
+ }
+}
+
+waitpid0, waitpid1 : int;
+mainpid : int;
+
+fontcache : array of ref Reffont;
+nfontcache : int;
+reffonts : array of ref Reffont;
+deffontnames := array[2] of {
+ "/fonts/lucidasans/euro.8.font",
+ "/fonts/lucm/unicode.9.font",
+};
+
+command : ref Command;
+
+WPERCOL : con 8;
+
+NSnarf : con 32;
+snarfrune : ref Dat->Astring;
+
+main(argl : list of string)
+{
+ i, ac : int;
+ loadfile : string;
+ p : int;
+ c : ref Column;
+ arg : ref Arg;
+ ncol : int;
+
+ ncol = -1;
+
+ mainpid = sys->pctl(0, nil);
+ loadfile = nil;
+ fontnames = array[2] of string;
+ fontnames[0:] = deffontnames[0:2];
+ f := utils->getenv("acme-font");
+ if (f != nil)
+ fontnames[0] = f;
+ f = utils->getenv("acme-Font");
+ if (f != nil)
+ fontnames[1] = f;
+ arg = arginit(argl);
+ while(ac = argopt(arg)) case(ac){
+ 'b' =>
+ dat->bartflag = TRUE;
+ 'c' =>
+ ncol = int argf(arg);
+ 'f' =>
+ fontnames[0] = argf(arg);
+ 'F' =>
+ fontnames[1] = argf(arg);
+ 'l' =>
+ loadfile = argf(arg);
+ }
+
+ dat->home = utils->getenv("home");
+ if (dat->home == nil)
+ dat->home = utils->gethome(utils->getuser());
+ ts := utils->getenv("tabstop");
+ if (ts != nil)
+ maxtab = int ts;
+ if (maxtab <= 0)
+ maxtab = 4;
+ snarfrune = utils->stralloc(NSnarf);
+ sys->pctl(Sys->FORKNS|Sys->FORKENV, nil);
+ utils->setenv("font", fontnames[0]);
+ sys->bind("/acme/dis", "/dis", Sys->MBEFORE);
+ wdir = workdir->init();
+ if (wdir == nil)
+ wdir = ".";
+ workdir = nil;
+
+ graph->binit();
+ font = Font.open(display, fontnames[0]);
+ if(font == nil){
+ fontnames[0] = deffontnames[0];
+ font = Font.open(display, fontnames[0]);
+ if (font == nil) {
+ warning(nil, sprint("can't open font file %s: %r\n", fontnames[0]));
+ return;
+ }
+ }
+ reffont = ref Reffont;
+ reffont.r = Ref.init();
+ reffont.f = font;
+ reffonts = array[2] of ref Reffont;
+ reffonts[0] = reffont;
+ reffont.r.inc(); # one to hold up 'font' variable
+ reffont.r.inc(); # one to hold up reffonts[0]
+ fontcache = array[1] of ref Reffont;
+ nfontcache = 1;
+ fontcache[0] = reffont;
+
+ iconinit();
+ usercolinit();
+ timerm->timerinit();
+ regx->rxinit();
+
+ cwait = chan of string;
+ ccommand = chan of ref Command;
+ ckill = chan of string;
+ cxfidalloc = chan of ref Xfid;
+ cxfidfree = chan of ref Xfid;
+ cerr = chan of string;
+ cplumb = chan of ref Msg;
+ cedit = chan of int;
+
+ gui->spawnprocs();
+ # spawn keyboardproc();
+ # spawn mouseproc();
+ sync := chan of int;
+ spawn waitproc(sys->pctl(0, nil), sync);
+ <- sync;
+ spawn plumbproc();
+
+ fsys->fsysinit();
+ dat->disk = (dat->disk).init();
+ row = rowm->newrow();
+ if(loadfile != nil) {
+ row.qlock.lock(); # tasks->procs now
+ row.loadx(loadfile, TRUE);
+ row.qlock.unlock();
+ }
+ else{
+ row.init(mainwin.clipr);
+ if(ncol < 0){
+ if(arg.av == nil)
+ ncol = 2;
+ else{
+ ncol = (len arg.av+(WPERCOL-1))/WPERCOL;
+ if(ncol < 2)
+ ncol = 2;
+ }
+ }
+ if(ncol == 0)
+ ncol = 2;
+ for(i=0; i<ncol; i++){
+ c = row.add(nil, -1);
+ if(c==nil && i==0)
+ error("initializing columns");
+ }
+ c = row.col[row.ncol-1];
+ if(arg.av == nil)
+ readfile(c, wdir);
+ else
+ i = 0;
+ for( ; arg.av != nil; arg.av = tl arg.av){
+ filen := hd arg.av;
+ p = utils->strrchr(filen, '/');
+ if((p>=0 && filen[p:] == "/guide") || i/WPERCOL>=row.ncol)
+ readfile(c, filen);
+ else
+ readfile(row.col[i/WPERCOL], filen);
+ i++;
+ }
+ }
+ bflush();
+
+ spawn keyboardtask();
+ spawn mousetask();
+ spawn waittask();
+ spawn xfidalloctask();
+
+ # notify(shutdown);
+ # waitc := chan of int;
+ # <-waitc;
+ # killprocs();
+ exit;
+}
+
+readfile(c : ref Column, s : string)
+{
+ w : ref Window;
+ r : string;
+ nr : int;
+
+ w = c.add(nil, nil, -1);
+ (r, nr) = look->cleanname(s, len s);
+ w.setname(r, nr);
+ w.body.loadx(0, s, 1);
+ w.body.file.mod = FALSE;
+ w.dirty = FALSE;
+ w.settag();
+ scrl->scrdraw(w.body);
+ w.tag.setselect(w.tag.file.buf.nc, w.tag.file.buf.nc);
+}
+
+oknotes := array[6] of {
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ "error",
+ nil
+};
+
+dumping : int;
+
+shutdown(msg : string)
+{
+ i : int;
+
+ # notify(nil);
+ if(!dumping && msg != "kill" && msg != "exit" && (1 || sys->pctl(0, nil)==mainpid) && row != nil){
+ dumping = TRUE;
+ row.dump(nil);
+ }
+ for(i=0; oknotes[i] != nil; i++)
+ if(utils->strncmp(oknotes[i], msg, len oknotes[i]) == 0) {
+ killprocs();
+ exit;
+ }
+ # killprocs();
+ sys->fprint(sys->fildes(2), "acme: %s\n", msg);
+ sys->print("acme: %s\n", msg);
+ # exit;
+}
+
+acmeexit(err: string)
+{
+ if(err != nil)
+ shutdown(err);
+ graph->cursorswitch(nil);
+ if (plumbed)
+ plumbmsg->shutdown();
+ killprocs();
+ gui->killwins();
+ exit;
+}
+
+killprocs()
+{
+ c : ref Command;
+ kill := "kill";
+ thispid := sys->pctl(0, nil);
+ fsys->fsysclose();
+
+ postnote(PNPROC, thispid, mousepid, kill);
+ postnote(PNPROC, thispid, keyboardpid, kill);
+ postnote(PNPROC, thispid, timerpid, kill);
+ postnote(PNPROC, thispid, waitpid0, kill);
+ postnote(PNPROC, thispid, waitpid1, kill);
+ postnote(PNPROC, thispid, fsyspid, kill);
+ postnote(PNPROC, thispid, mainpid, kill);
+ postnote(PNPROC, thispid, keytid, kill);
+ postnote(PNPROC, thispid, mousetid, kill);
+ postnote(PNPROC, thispid, waittid, kill);
+ postnote(PNPROC, thispid, xfidalloctid, kill);
+ # postnote(PNPROC, thispid, lockpid, kill);
+ postnote(PNPROC, thispid, plumbpid, kill);
+
+ # draw(mainwin, mainwin.r, white, nil, mainwin.r.min);
+
+ for(c=command; c != nil; c=c.next)
+ postnote(PNGROUP, thispid, c.pid, "kill");
+
+ xfidm->xfidkill();
+}
+
+keytid : int;
+mousetid : int;
+waittid : int;
+xfidalloctid : int;
+
+keyboardtask()
+{
+ r : int;
+ timer : ref Timer;
+ null : ref Timer;
+ t : ref Text;
+
+ {
+ keytid = sys->pctl(0, nil);
+ null = ref Timer;
+ null.c = chan of int;
+ timer = null;
+ typetext = nil;
+ for(;;){
+ alt{
+ <-(timer.c) =>
+ timerm->timerstop(timer);
+ t = typetext;
+ if(t!=nil && t.what==Tag && !t.w.qlock.locked()){
+ t.w.lock('K');
+ t.w.commit(t);
+ t.w.unlock();
+ bflush();
+ }
+ timer = null;
+ r = <-ckeyboard =>
+ gotkey := 1;
+ while (gotkey) {
+ typetext = row.typex(r, mouse.xy);
+ t = typetext;
+ if(t!=nil && t.col!=nil)
+ activecol = t.col;
+ if(t!=nil && t.w!=nil)
+ t.w.body.file.curtext = t.w.body;
+ if(timer != null)
+ spawn timerm->timerwaittask(timer);
+ if(t!=nil && t.what==Tag)
+ timer = timerm->timerstart(500);
+ else
+ timer = null;
+ alt {
+ r = <- ckeyboard =>
+ gotkey = 1; # do this case again
+ * =>
+ gotkey = 0;
+ }
+ bflush();
+ }
+ }
+ }
+ }
+ exception{
+ * =>
+ shutdown(utils->getexc());
+ raise;
+ # acmeexit(nil);
+ }
+}
+
+mousetask()
+{
+ t, argt : ref Text;
+ but, ok : int;
+ q0, q1 : int;
+ w : ref Window;
+ m : ref Msg;
+
+ {
+ mousetid = sys->pctl(0, nil);
+ sync := chan of int;
+ spawn waitproc(mousetid, sync);
+ <- sync;
+ for(;;){
+ alt{
+ *mouse = *<-cmouse =>
+ row.qlock.lock();
+ if (mouse.buttons & M_QUIT) {
+ if (row.clean(TRUE))
+ acmeexit(nil);
+ # shutdown("kill");
+ row.qlock.unlock();
+ break;
+ }
+ if (mouse.buttons & M_HELP) {
+ warning(nil, "no help provided (yet)");
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ if(mouse.buttons & M_RESIZE){
+ draw(mainwin, mainwin.r, white, nil, mainwin.r.min);
+ scrl->scrresize();
+ row.reshape(mainwin.clipr);
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ t = row.which(mouse.xy);
+ if(t!=mousetext && mousetext!=nil && mousetext.w!=nil){
+ mousetext.w.lock('M');
+ mousetext.eq0 = ~0;
+ mousetext.w.commit(mousetext);
+ mousetext.w.unlock();
+ }
+ mousetext = t;
+ if(t == nil) {
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ w = t.w;
+ if(t==nil || mouse.buttons==0) {
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ if(w != nil)
+ w.body.file.curtext = w.body;
+ but = 0;
+ if(mouse.buttons == 1)
+ but = 1;
+ else if(mouse.buttons == 2)
+ but = 2;
+ else if(mouse.buttons == 4)
+ but = 3;
+ barttext = t;
+ if(t.what==Body && mouse.xy.in(t.scrollr)){
+ if(but){
+ w.lock('M');
+ t.eq0 = ~0;
+ scrl->scroll(t, but);
+ t.w.unlock();
+ }
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ if(w != nil && (mouse.buttons &(8|16))){
+ if(mouse.buttons & 8)
+ but = Dat->Kscrollup;
+ else
+ but = Dat->Kscrolldown;
+ w.lock('M');
+ t.eq0 = ~0;
+ t.typex(but, 0);
+ w.unlock();
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ if(mouse.xy.in(t.scrollr)){
+ if(but){
+ if(t.what == Columntag)
+ row.dragcol(t.col);
+ else if(t.what == Tag){
+ t.col.dragwin(t.w, but);
+ if(t.w != nil)
+ barttext = t.w.body;
+ }
+ if(t.col != nil)
+ activecol = t.col;
+ }
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ if(mouse.buttons){
+ if(w != nil)
+ w.lock('M');
+ t.eq0 = ~0;
+ if(w != nil)
+ w.commit(t);
+ else
+ t.commit(TRUE);
+ if(mouse.buttons & 1){
+ t.select(0);
+ if(w != nil)
+ w.settag();
+ argtext = t;
+ seltext = t;
+ if(t.col != nil)
+ activecol = t.col; # button 1 only
+ if(t.w != nil && t == t.w.body)
+ dat->activewin = t.w;
+ }else if(mouse.buttons & 2){
+ (ok, argt, q0, q1) = t.select2(q0, q1);
+ if(ok)
+ exec->execute(t, q0, q1, FALSE, argt);
+ }else if(mouse.buttons & 4){
+ (ok, q0, q1) = t.select3(q0, q1);
+ if(ok)
+ look->look3(t, q0, q1, FALSE);
+ }
+ if(w != nil)
+ w.unlock();
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ m = <- cplumb =>
+ if (m.kind == "text") {
+ attrs := plumbmsg->string2attrs(m.attr);
+ (found, act) := plumbmsg->lookup(attrs, "action");
+ if (!found || act == nil || act == "showfile")
+ look->plumblook(m);
+ else if (act == "showdata")
+ look->plumbshow(m);
+ }
+ bflush();
+ }
+ }
+ }
+ exception{
+ * =>
+ shutdown(utils->getexc());
+ raise;
+ # acmeexit(nil);
+ }
+}
+
+# list of processes that have exited but we have not heard of yet
+Pid : adt {
+ pid : int;
+ msg : string;
+ next : cyclic ref Pid;
+};
+
+waittask()
+{
+ status : string;
+ c, lc : ref Command;
+ pid : int;
+ found : int;
+ cmd : string;
+ err : string;
+ t : ref Text;
+ pids : ref Pid;
+
+ waittid = sys->pctl(0, nil);
+ command = nil;
+ for(;;){
+ alt{
+ err = <-cerr =>
+ row.qlock.lock();
+ warning(nil, err);
+ err = nil;
+ bflush();
+ row.qlock.unlock();
+ break;
+ cmd = <-ckill =>
+ found = FALSE;
+ for(c=command; c != nil; c=c.next){
+ # -1 for blank
+ if(c.name[0:len c.name - 1] == cmd){
+ if(postnote(PNGROUP, waittid, c.pid, "kill") < 0)
+ warning(nil, sprint("kill %s: %r\n", cmd));
+ found = TRUE;
+ }
+ }
+ if(!found)
+ warning(nil, sprint("Kill: no process %s\n", cmd));
+ cmd = nil;
+ break;
+ status = <-cwait =>
+ pid = int status;
+ lc = nil;
+ for(c=command; c != nil; c=c.next){
+ if(c.pid == pid){
+ if(lc != nil)
+ lc.next = c.next;
+ else
+ command = c.next;
+ break;
+ }
+ lc = c;
+ }
+ row.qlock.lock();
+ t = row.tag;
+ t.commit(TRUE);
+ if(c == nil){
+ # warning(nil, sprint("unknown child pid %d\n", pid));
+ p := ref Pid;
+ p.pid = pid;
+ p.msg = status;
+ p.next = pids;
+ pids = p;
+ }
+ else{
+ if(look->search(t, c.name, len c.name)){
+ t.delete(t.q0, t.q1, TRUE);
+ t.setselect(0, 0);
+ }
+ if(status[len status - 1] != ':')
+ warning(c.md, sprint("%s\n", status));
+ bflush();
+ }
+ row.qlock.unlock();
+ if(c != nil){
+ if(c.iseditcmd)
+ cedit <- = 0;
+ fsys->fsysdelid(c.md);
+ c = nil;
+ }
+ break;
+ c = <-ccommand =>
+ lastp : ref Pid = nil;
+ for(p := pids; p != nil; p = p.next){
+ if(p.pid == c.pid){
+ status = p.msg;
+ if(status[len status - 1] != ':')
+ warning(c.md, sprint("%s\n", status));
+ if(lastp == nil)
+ pids = p.next;
+ else
+ lastp.next = p.next;
+ if(c.iseditcmd)
+ cedit <- = 0;
+ fsys->fsysdelid(c.md);
+ c = nil;
+ break;
+ }
+ lastp = p;
+ }
+ c.next = command;
+ command = c;
+ row.qlock.lock();
+ t = row.tag;
+ t.commit(TRUE);
+ t.insert(0, c.name, len c.name, TRUE, 0);
+ t.setselect(0, 0);
+ bflush();
+ row.qlock.unlock();
+ break;
+ }
+ }
+}
+
+xfidalloctask()
+{
+ xfree, x : ref Xfid;
+
+ xfidalloctid = sys->pctl(0, nil);
+ xfree = nil;
+ for(;;){
+ alt{
+ <-cxfidalloc =>
+ x = xfree;
+ if(x != nil)
+ xfree = x.next;
+ else{
+ x = xfidm->newxfid();
+ x.c = chan of int;
+ spawn x.ctl();
+ }
+ cxfidalloc <-= x;
+ break;
+ x = <-cxfidfree =>
+ x.next = xfree;
+ xfree = x;
+ break;
+ }
+ }
+}
+
+frgetmouse()
+{
+ bflush();
+ *mouse = *<-cmouse;
+}
+
+waitproc(pid : int, sync: chan of int)
+{
+ fd : ref Sys->FD;
+ n : int;
+
+ if (waitpid0 == 0)
+ waitpid0 = sys->pctl(0, nil);
+ else
+ waitpid1 = sys->pctl(0, nil);
+ sys->pctl(Sys->FORKFD, nil);
+ # w := sprint("/prog/%d/wait", pid);
+ w := sprint("#p/%d/wait", pid);
+ fd = sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ error("fd == nil in waitproc");
+ sync <-= 0;
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;){
+ if ((n = sys->read(fd, buf, len buf))<0)
+ error("bad read in waitproc");
+ status = string buf[0:n];
+ cwait <-= status;
+ }
+}
+
+get(fix : int, save : int, setfont : int, name : string) : ref Reffont
+{
+ r : ref Reffont;
+ f : ref Font;
+ i : int;
+
+ r = nil;
+ if(name == nil){
+ name = fontnames[fix];
+ r = reffonts[fix];
+ }
+ if(r == nil){
+ for(i=0; i<nfontcache; i++)
+ if(name == fontcache[i].f.name){
+ r = fontcache[i];
+ break;
+ }
+ if (i >= nfontcache) {
+ f = Font.open(display, name);
+ if(f == nil){
+ warning(nil, sprint("can't open font file %s: %r\n", name));
+ return nil;
+ }
+ r = ref Reffont;
+ r.r = Ref.init();
+ r.f = f;
+ ofc := fontcache;
+ fontcache = array[nfontcache+1] of ref Reffont;
+ fontcache[0:] = ofc[0:nfontcache];
+ ofc = nil;
+ fontcache[nfontcache++] = r;
+ }
+ }
+ if(save){
+ r.r.inc();
+ if(reffonts[fix] != nil)
+ reffonts[fix].close();
+ reffonts[fix] = r;
+ fontnames[fix] = name;
+ }
+ if(setfont){
+ reffont.f = r.f;
+ r.r.inc();
+ reffonts[0].close();
+ font = r.f;
+ reffonts[0] = r;
+ r.r.inc();
+ iconinit();
+ }
+ r.r.inc();
+ return r;
+}
+
+close(r : ref Reffont)
+{
+ i : int;
+
+ if(r.r.dec() == 0){
+ for(i=0; i<nfontcache; i++)
+ if(r == fontcache[i])
+ break;
+ if(i >= nfontcache)
+ warning(nil, "internal error: can't find font in cache\n");
+ else{
+ fontcache[i:] = fontcache[i+1:nfontcache];
+ nfontcache--;
+ }
+ r.f = nil;
+ r = nil;
+ }
+}
+
+arrowbits := array[64] of {
+ byte 16rFF, byte 16rE0, byte 16rFF, byte 16rE0,
+ byte 16rFF, byte 16rC0, byte 16rFF, byte 16r00,
+ byte 16rFF, byte 16r00, byte 16rFF, byte 16r80,
+ byte 16rFF, byte 16rC0, byte 16rFF, byte 16rE0,
+ byte 16rE7, byte 16rF0, byte 16rE3, byte 16rF8,
+ byte 16rC1, byte 16rFC, byte 16r00, byte 16rFE,
+ byte 16r00, byte 16r7F, byte 16r00, byte 16r3E,
+ byte 16r00, byte 16r1C, byte 16r00, byte 16r08,
+
+ byte 16r00, byte 16r00, byte 16r7F, byte 16rC0,
+ byte 16r7F, byte 16r00, byte 16r7C, byte 16r00,
+ byte 16r7E, byte 16r00, byte 16r7F, byte 16r00,
+ byte 16r6F, byte 16r80, byte 16r67, byte 16rC0,
+ byte 16r43, byte 16rE0, byte 16r41, byte 16rF0,
+ byte 16r00, byte 16rF8, byte 16r00, byte 16r7C,
+ byte 16r00, byte 16r3E, byte 16r00, byte 16r1C,
+ byte 16r00, byte 16r08, byte 16r00, byte 16r00,
+};
+
+# outer boundary of width 1 is white
+# next boundary of width 3 is black
+# next boundary of width 1 is white
+# inner boundary of width 4 is transparent
+boxbits := array[64] of {
+ byte 16rFF, byte 16rFF, byte 16rFF, byte 16rFF,
+ byte 16rFF, byte 16rFF, byte 16rFF, byte 16rFF,
+ byte 16rFF, byte 16rFF, byte 16rF8, byte 16r1F,
+ byte 16rF8, byte 16r1F, byte 16rF8, byte 16r1F,
+ byte 16rF8, byte 16r1F, byte 16rF8, byte 16r1F,
+ byte 16rF8, byte 16r1F, byte 16rFF, byte 16rFF,
+ byte 16rFF, byte 16rFF, byte 16rFF, byte 16rFF,
+ byte 16rFF, byte 16rFF, byte 16rFF, byte 16rFF,
+
+
+ byte 16r00, byte 16r00, byte 16r7F, byte 16rFE,
+ byte 16r7F, byte 16rFE, byte 16r7F, byte 16rFE,
+ byte 16r70, byte 16r0E, byte 16r70, byte 16r0E,
+ byte 16r70, byte 16r0E, byte 16r70, byte 16r0E,
+ byte 16r70, byte 16r0E, byte 16r70, byte 16r0E,
+ byte 16r70, byte 16r0E, byte 16r70, byte 16r0E,
+ byte 16r7F, byte 16rFE, byte 16r7F, byte 16rFE,
+ byte 16r7F, byte 16rFE, byte 16r00, byte 16r00,
+};
+
+iconinit()
+{
+ r : Rect;
+
+ # Blue
+ tagcols = array[NCOL] of ref Draw->Image;
+ tagcols[BACK] = display.colormix(Draw->Palebluegreen, Draw->White);
+ tagcols[HIGH] = display.color(Draw->Palegreygreen);
+ tagcols[BORD] = display.color(Draw->Purpleblue);
+ tagcols[TEXT] = black;
+ tagcols[HTEXT] = black;
+
+ # Yellow
+ textcols = array[NCOL] of ref Draw->Image;
+ textcols[BACK] = display.colormix(Draw->Paleyellow, Draw->White);
+ textcols[HIGH] = display.color(Draw->Darkyellow);
+ textcols[BORD] = display.color(Draw->Yellowgreen);
+ textcols[TEXT] = black;
+ textcols[HTEXT] = black;
+
+ if(button != nil)
+ button = modbutton = colbutton = nil;
+
+ r = ((0, 0), (Dat->Scrollwid+2, font.height+1));
+ button = balloc(r, mainwin.chans, Draw->White);
+ draw(button, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ draw(button, r, tagcols[BORD], nil, (0, 0));
+ r = r.inset(2);
+ draw(button, r, tagcols[BACK], nil, (0, 0));
+
+ r = button.r;
+ modbutton = balloc(r, mainwin.chans, Draw->White);
+ draw(modbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ draw(modbutton, r, tagcols[BORD], nil, (0, 0));
+ r = r.inset(2);
+ draw(modbutton, r, display.rgb(16r00, 16r00, 16r99), nil, (0, 0)); # was DMedblue
+
+ r = button.r;
+ colbutton = balloc(r, mainwin.chans, Draw->White);
+ draw(colbutton, r, tagcols[BACK], nil, r.min);
+ r.max.x -= 2;
+ draw(colbutton, r, tagcols[BORD], nil, (0, 0));
+
+# arrowcursor = ref Cursor((-1, -1), (16, 32), arrowbits);
+ boxcursor = ref Cursor((-7, -7), (16, 32), boxbits);
+
+ but2col = display.rgb(16raa, 16r00, 16r00);
+ but3col = display.rgb(16r00, 16r66, 16r00);
+ but2colt = white;
+ but3colt = white;
+
+ graph->cursorswitch(arrowcursor);
+}
+
+colrec : adt {
+ name : string;
+ image : ref Image;
+};
+
+coltab : array of colrec;
+
+cinit()
+{
+ coltab = array[6] of colrec;
+ coltab[0].name = "yellow"; coltab[0].image = yellow;
+ coltab[1].name = "green"; coltab[1].image = green;
+ coltab[2].name = "red"; coltab[2].image = red;
+ coltab[3].name = "blue"; coltab[3].image = blue;
+ coltab[4].name = "black"; coltab[4].image = black;
+ coltab[5].name = "white"; coltab[5].image = white;
+}
+
+col(s : string, n : int) : int
+{
+ return ((s[n]-'0') << 4) | (s[n+1]-'0');
+}
+
+rgb(s : string, n : int) : (int, int, int)
+{
+ return (col(s, n), col(s, n+2), col(s, n+4));
+}
+
+cenv(s : string, t : string, but : int, i : ref Image) : ref Image
+{
+ c := utils->getenv("acme-" + s + "-" + t + "-" + string but);
+ if (c == nil)
+ c = utils->getenv("acme-" + s + "-" + string but);
+ if (c == nil && but != 0)
+ c = utils->getenv("acme-" + s);
+ if (c != nil) {
+ if (c[0] == '#' && len c >= 7) {
+ (r1, g1, b1) := rgb(c, 1);
+ if (len c >= 15 && c[7] == '/' && c[8] == '#') {
+ (r2, g2, b2) := rgb(c, 9);
+ return display.colormix((r1<<24)|(g1<<16)|(b1<<8)|16rFF, (r2<<24)|(g2<<16)|(b2<<8)|16rFF);
+ }
+ return display.color((r1<<24)|(g1<<16)|(b1<<8)|16rFF);
+ }
+ for (j := 0; j < len c; j++)
+ if (c[j] >= 'A' && c[j] <= 'Z')
+ c[j] += 'a'-'A';
+ for (j = 0; j < len coltab; j++)
+ if (c == coltab[j].name)
+ return coltab[j].image;
+ }
+ return i;
+}
+
+usercolinit()
+{
+ cinit();
+ textcols[TEXT] = cenv("fg", "text", 0, textcols[TEXT]);
+ textcols[BACK] = cenv("bg", "text", 0, textcols[BACK]);
+ textcols[HTEXT] = cenv("fg", "text", 1, textcols[HTEXT]);
+ textcols[HIGH] = cenv("bg", "text", 1, textcols[HIGH]);
+ but2colt= cenv("fg", "text", 2, but2colt);
+ but2col = cenv("bg", "text", 2, but2col);
+ but3colt = cenv("fg", "text", 3, but3colt);
+ but3col = cenv("bg", "text", 3, but3col);
+ tagcols[TEXT] = cenv("fg", "tag", 0, tagcols[TEXT]);
+ tagcols[BACK] = cenv("bg", "tag", 0, tagcols[BACK]);
+ tagcols[HTEXT] = cenv("fg", "tag", 1, tagcols[HTEXT]);
+ tagcols[HIGH] = cenv("bg", "tag", 1, tagcols[HIGH]);
+}
+
+getsnarf()
+{
+ # return;
+ fd := sys->open("/chan/snarf", sys->OREAD);
+ if(fd == nil)
+ return;
+ snarfbuf.reset();
+ snarfbuf.loadx(0, fd);
+}
+
+putsnarf()
+{
+ n : int;
+
+ # return;
+ if(snarfbuf.nc == 0)
+ return;
+ fd := sys->open("/chan/snarf", sys->OWRITE);
+ if(fd == nil)
+ return;
+ for(i:=0; i<snarfbuf.nc; i+=n){
+ n = snarfbuf.nc-i;
+ if(n >= NSnarf)
+ n = NSnarf;
+ snarfbuf.read(i, snarfrune, 0, n);
+ sys->fprint(fd, "%s", snarfrune.s[0:n]);
+ }
+}
+
+plumbpid : int;
+
+plumbproc()
+{
+ plumbpid = sys->pctl(0, nil);
+ for(;;){
+ msg := Msg.recv();
+ if(msg == nil){
+ sys->print("Acme: can't read /chan/plumb.edit: %r\n");
+ plumbpid = 0;
+ plumbed = 0;
+ return;
+ }
+ if(msg.kind != "text"){
+ sys->print("Acme: can't interpret '%s' kind of message\n", msg.kind);
+ continue;
+ }
+# sys->print("msg %s\n", string msg.data);
+ cplumb <-= msg;
+ }
+}
--- /dev/null
+++ b/appl/acme/acme.m
@@ -1,0 +1,32 @@
+Acme : module {
+ PATH : con "/dis/acme.dis";
+
+ RELEASECOPY : con 1;
+
+ M_LBUT : con 1;
+ M_MBUT : con 2;
+ M_RBUT : con 4;
+ M_TBS : con 8;
+ M_PLUMB : con 16;
+ M_QUIT : con 32;
+ M_HELP : con 64;
+ M_RESIZE : con 128;
+ M_DOUBLE : con 256;
+
+ textcols, tagcols : array of ref Draw->Image;
+ but2col, but3col, but2colt, but3colt : ref Draw->Image;
+
+ acmectxt : ref Draw->Context;
+ keyboardpid, mousepid, timerpid, fsyspid : int;
+ fontnames : array of string;
+ wdir : string;
+
+ init : fn(ctxt : ref Draw->Context, argv : list of string);
+ timing : fn(s : string);
+ frgetmouse : fn();
+ get : fn(p, q, r : int, b : string) : ref Dat->Reffont;
+ close : fn(r : ref Dat->Reffont);
+ acmeexit : fn(err : string);
+ getsnarf : fn();
+ putsnarf : fn();
+};
--- /dev/null
+++ b/appl/acme/acme/acid/guide
@@ -1,0 +1,2 @@
+Acid pid
+Acid -l alef -l symsfile pid
--- /dev/null
+++ b/appl/acme/acme/acid/mkfile
@@ -1,0 +1,24 @@
+<../../../../mkconfig
+
+BIN=$ROOT/acme/acid
+
+DIRS=\
+ src\
+
+TARG=\
+ guide\
+ readme\
+
+BINTARG=${TARG:%=$BIN/%}
+
+all:V: $TARG
+
+install:V: $BINTARG
+
+$BIN/guide : guide
+ rm -f $BIN/guide && cp guide $BIN/guide
+
+$BIN/readme : readme
+ rm -f $BIN/readme && cp readme $BIN/readme
+
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/appl/acme/acme/acid/readme
@@ -1,0 +1,12 @@
+Capital A Acid is a rudimentary acme interface to the debugger acid.
+It uses a win to provide an interactive window for acid. In that window,
+a couple of extra acme-specific features are enabled:
+
+w(command)
+ runs the command and places its output in a new window.
+ e.g. w(lstk()) places the stack trace in a distinct window.
+
+Also, in any such window, text executed with button 2 is
+presented as input to acid in the main Acid window. Thus, for
+example, one may evaluate variables presented in a stack trace
+by `executing' it with button 2.
--- /dev/null
+++ b/appl/acme/acme/acid/src/Acid.b
@@ -1,0 +1,31 @@
+implement Acid;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+
+Acid : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys := load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ if (len argl < 2) {
+ sys->fprint(stderr, "usage : Acid pid\n");
+ return;
+ }
+ cmd := "/acme/dis/win";
+ file := cmd + ".dis";
+ c := load Command file;
+ if(c == nil) {
+ sys->fprint(stderr, "%s: %r\n", cmd);
+ return;
+ }
+ argl = "-l" :: argl;
+ argl = "acid" :: argl;
+ argl = "/acme/dis/Acid0" :: argl;
+ argl = cmd :: argl;
+ c->init(ctxt, argl);
+}
--- /dev/null
+++ b/appl/acme/acme/acid/src/Acid0.b
@@ -1,0 +1,86 @@
+implement Acidb;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+FD : import sys;
+Iobuf : import bufio;
+
+Acidb : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+init(nil : ref Draw->Context, nil : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ # TBS main(argl);
+}
+
+False : con 0;
+True : con 1;
+
+EVENTSIZE : con 256;
+
+Event : adt {
+ c1, c2, q0, q1, flag, nb, nr : int;
+ b : array of byte;
+ r : array of int;
+ # TBS byte b[EVENTSIZE*UTFmax+1];
+ # TBS Rune r[EVENTSIZE+1];
+};
+
+Win : adt {
+ winid : int;
+ addr : int;
+ body : ref Iobuf;
+ ctl : int;
+ data : int;
+ event : int;
+ buf : array of byte;
+ # TBS byte buf[512];
+ bufp : int;
+ nbuf : int;
+
+ wnew : fn(w : ref Win);
+ wwritebody : fn(w : ref Win, s : array of byte, n : int);
+ wread : fn(w : ref Win, m : int, n : int, s : array of byte);
+ wclean : fn(w : ref Win);
+ wname : fn(w : ref Win, s : array of byte);
+ wdormant : fn(w : ref Win);
+ wevent : fn(w : ref Win, e : ref Event);
+ wtagwrite : fn(w : ref Win, s : array of byte, n : int);
+ wwriteevent : fn(w : ref Win, e : ref Event);
+ wslave : fn(w : ref Win, c : chan of Event);
+ wreplace : fn(w : ref Win, s : array of byte, b : array of byte, n : int);
+ wselect : fn(w : ref Win, s : array of byte);
+ wdel : fn(w : ref Win, n : int) : int;
+ wreadall : fn(w : ref Win) : (int, array of byte);
+
+ ctlwrite : fn(w : ref Win, s : array of byte);
+ getec : fn(w : ref Win) : int;
+ geten : fn(w : ref Win) : int;
+ geter : fn(w : ref Win, s : array of byte, r : array of int) : int;
+ openfile : fn(w : ref Win, b : array of byte) : int;
+ openbody : fn(w : ref Win, n : int);
+};
+
+Awin : adt {
+ w : Win;
+
+ slave : fn(w : ref Awin, s : array of byte, c : chan of int);
+ new : fn(w : ref Awin, s : array of byte);
+ command : fn(w : ref Awin, s : array of byte) : int;
+ send : fn(w : ref Awin, m : int, s : array of byte, n : int);
+};
+
+srvfd : ref FD;
+stdin : ref FD;
+srvenv : array of byte;
+# TBS byte srvenv[64];
+
+srvc : chan of array of byte;
--- /dev/null
+++ b/appl/acme/acme/acid/src/mkfile
@@ -1,0 +1,20 @@
+<../../../../../mkconfig
+
+TARG=\
+ Acid.dis\
+ Acid0.dis\
+# acid.dis\
+# awin.dis\
+# util.dis\
+# win.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ sh.m\
+ sys.m\
+ draw.m\
+
+DISBIN=$ROOT/acme/acid
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/acme/acme/bin/guide
@@ -1,0 +1,5 @@
+win
+new command ...
+aspell file
+adiff file1 file2
+adict -d oed
--- /dev/null
+++ b/appl/acme/acme/bin/mkfile
@@ -1,0 +1,24 @@
+<../../../../mkconfig
+
+BIN=$ROOT/acme/bin
+
+DIRS=\
+ src\
+
+TARG=\
+ guide\
+ readme\
+
+BINTARG=${TARG:%=$BIN/%}
+
+all:V: $TARG
+
+install:V: $BINTARG
+
+$BIN/guide : guide
+ rm -f $BIN/guide && cp guide $BIN/guide
+
+$BIN/readme : readme
+ rm -f $BIN/readme && cp readme $BIN/readme
+
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/appl/acme/acme/bin/readme
@@ -1,0 +1,25 @@
+This directory and its subdirectory $cputype are always mounted at
+the end of /bin for programs run from acme. They hold a collection
+of small acme-specific applications:
+
+win [command]
+ Create an acme window to serve as a terminal, analogous
+ to xterm. By default, it runs the shell, rc, but it works with
+ any interactive program, e.g. hoc. Within the window,
+ commands executed with button 2 are 'executed' by sending
+ their text to the standard input of the command, appending
+ a newline if necessary.
+new command
+ Run the non-interactive command, placing its standard and
+ diagnostic output in a new window.
+aspell file
+ Run spell on the file, labeling the output with addresses so
+ misspelled words can be found in context using button 3.
+adiff file1 file2
+ Run diff on the files, labeling the output with addresses so
+ changes can be found in context using button 3.
+adict
+ Interactive version of dict(1). Button 3 looks up words and
+ may be applied to any word in any adict window.
+ When a word has multiple definitions, indicate the number
+ (as in acme Mail) to disambiguate.
--- /dev/null
+++ b/appl/acme/acme/bin/src/adiff.b
@@ -1,0 +1,155 @@
+implement Adiff;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+include "workdir.m";
+include "bufio.m";
+
+Adiff : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+
+Context : import Draw;
+OREAD, OWRITE, QTDIR, FD, FORKFD, open, read, write, sprint, fprint, stat, fildes, dup, pctl : import sys;
+
+init(ctxt : ref Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir := load Workdir Workdir->PATH;
+ stderr := fildes(2);
+ if (len argl != 3) {
+ fprint(stderr, "usage: adiff file1 file2\n");
+ return;
+ }
+ ncfd := open("/chan/new/ctl", OREAD);
+ if (ncfd == nil) {
+ fprint(stderr, "cannot open ctl file\n");
+ return;
+ }
+ b := array[128] of byte;
+ n := read(ncfd, b, len b);
+ id := string int string b[0:n];
+ f1 := hd tl argl;
+ f2 := hd tl tl argl;
+ (ok1, d1) := stat(f1);
+ if (ok1 < 0) {
+ fprint(stderr, "cannot stat %s\n", f1);
+ return;
+ }
+ (ok2, d2) := stat(f2);
+ if (ok2 < 0) {
+ fprint(stderr, "cannot stat %s\n", f2);
+ return;
+ }
+ if (d1.qid.qtype & QTDIR)
+ f1 = f1 + "/" + basename(f2);
+ else if (d2.qid.qtype & QTDIR)
+ f2 = f2 + "/" + basename(f1);
+ buf := "/chan/" + id + "/ctl";
+ icfd := open(buf, OWRITE);
+ if (icfd == nil) {
+ fprint(stderr, "cannot open control file\n");
+ return;
+ }
+ buf = "name " + workdir->init() + "/-diff-" + f1 + "\n";
+ b = array of byte buf;
+ write(icfd, b, len b);
+
+ fds := array[2] of ref FD;
+ if (sys->pipe(fds) < 0) {
+ fprint(stderr, "can't pipe\n");
+ return;
+ }
+ buf = "/chan/" + id + "/body";
+ bfd := open(buf, OWRITE);
+ if (bfd == nil) {
+ fprint(stderr, "cannot open body file\n");
+ return;
+ }
+ spawn diff(fds[1], f1, f2, ctxt);
+ fds[1] = nil;
+ awk(fds[0], bfd, f1, f2);
+ b = array of byte "clean\n";
+ write(icfd, b, len b);
+}
+
+strchr(s : string, c : int) : int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strchrs(s, pat : string) : int
+{
+ for (i := 0; i < len s; i++)
+ if (strchr(pat, s[i]) >= 0)
+ return i;
+ return -1;
+}
+
+awk(ifd, ofd : ref FD, f1, f2 : string)
+{
+ bufio := load Bufio Bufio->PATH;
+ Iobuf : import bufio;
+ b := bufio->fopen(ifd, OREAD);
+ while ((s := b.gets('\n')) != nil) {
+ if (s[0] >= '1' && s[0] <= '9') {
+ if ((n := strchrs(s, "acd")) >= 0)
+ s = f1 + ":" + s[0:n] + " " + s[n:n+1] + " " + f2 + ":" + s[n+1:];
+ }
+ fprint(ofd, "%s", s);
+ }
+}
+
+diff(ofd : ref FD, f1, f2 : string, ctxt : ref Context)
+{
+ args : list of string;
+
+ pctl(FORKFD, nil);
+ fd := open("/dev/null", OREAD);
+ dup(fd.fd, 0);
+ fd = nil;
+ dup(ofd.fd, 1);
+ dup(1, 2);
+ ofd = nil;
+ args = nil;
+ args = f2 :: args;
+ args = f1 :: args;
+ args = "diff" :: args;
+ exec("diff", args, ctxt);
+ exit;
+}
+
+exec(cmd : string, argl : list of string, ctxt : ref Context)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(fildes(2), "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(ctxt, argl);
+}
+
+basename(s : string) : string
+{
+ for (i := len s -1; i >= 0; --i)
+ if (s[i] == '/')
+ return s[i+1:];
+ return s;
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/agrep.b
@@ -1,0 +1,46 @@
+implement Agrep;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+
+Agrep : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys := load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ cmd := "grep";
+ file := cmd + ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sys->fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ argl = tl argl;
+ argl = rev(argl);
+ argl = "/dev/null" :: argl;
+ argl = rev(argl);
+ argl = "-n" :: argl;
+ argl = cmd :: argl;
+ c->init(ctxt, argl);
+}
+
+rev(a : list of string) : list of string
+{
+ b : list of string;
+
+ for ( ; a != nil; a = tl a)
+ b = hd a :: b;
+ return b;
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/awd.b
@@ -1,0 +1,45 @@
+implement Awd;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+
+Awd : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+workdir : Workdir;
+
+FD, OWRITE, open, write : import sys;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ n : int;
+ fd : ref FD;
+ buf, dir, str : string;
+ ab : array of byte;
+
+ sys = load Sys Sys->PATH;
+ workdir = load Workdir Workdir->PATH;
+ fd = open("/dev/acme/ctl", OWRITE);
+ if(fd == nil)
+ exit;
+ dir = workdir->init();
+ buf = "name " + dir;
+ n = len buf;
+ if(n>0 && buf[n-1] !='/')
+ buf[n++] = '/';
+ buf[n++] = '-';
+ if(tl argl != nil)
+ str = hd tl argl;
+ else
+ str = "rc";
+ buf += str + "\n";
+ ab = array of byte buf;
+ write(fd, ab, len ab);
+ buf = "dumpdir " + dir + "\n";
+ ab = array of byte buf;
+ write(fd, ab, len ab);
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/cd.b
@@ -1,0 +1,70 @@
+implement Cd;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+
+Cd : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+workdir : Workdir;
+
+FD, OREAD, OWRITE, open, read, write, chdir, fildes, fprint : import sys;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ n : int;
+ fd, stderr : ref FD;
+ buf, dir, str : string;
+ ab : array of byte;
+
+ sys = load Sys Sys->PATH;
+ stderr = fildes(2);
+ argl = tl argl;
+ if (argl == nil)
+ argl = "/usr/" + user() :: nil;
+ if (tl argl != nil) {
+ fprint(stderr, "Usage: cd [directory]\n");
+ exit;
+ }
+ if (chdir(hd argl) < 0) {
+ fprint(stderr, "cd: %s: %r\n", hd argl);
+ exit;
+ }
+
+ workdir = load Workdir Workdir->PATH;
+ fd = open("/dev/acme/ctl", OWRITE);
+ if(fd == nil)
+ exit;
+ dir = workdir->init();
+ buf = "name " + dir;
+ n = len buf;
+ if(n>0 && buf[n-1] !='/')
+ buf[n++] = '/';
+ buf[n++] = '-';
+ if(tl argl != nil)
+ str = hd tl argl;
+ else
+ str = "sh";
+ buf += str + "\n";
+ ab = array of byte buf;
+ write(fd, ab, len ab);
+ buf = "dumpdir " + dir + "\n";
+ ab = array of byte buf;
+ write(fd, ab, len ab);
+ exit;
+}
+
+user(): string
+{
+ fd := open("/dev/user", OREAD);
+ if(fd == nil)
+ return "inferno";
+ buf := array[Sys->NAMEMAX] of byte;
+ n := read(fd, buf, len buf);
+ if(n <= 0)
+ return "inferno";
+ return string buf[0:n];
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/mkfile
@@ -1,0 +1,22 @@
+<../../../../../mkconfig
+
+TARG=\
+ win.dis\
+ winm.dis\
+ adiff.dis\
+ agrep.dis\
+ new.dis\
+ spout.dis\
+ awd.dis\
+ cd.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ sh.m\
+ sys.m\
+ draw.m\
+
+DISBIN=$ROOT/acme/dis
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/acme/acme/bin/src/new.b
@@ -1,0 +1,70 @@
+implement New;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+include "workdir.m";
+
+New : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir := load Workdir Workdir->PATH;
+ if (len argl <= 1)
+ return;
+ ncfd := sys->open("/mnt/acme/new/ctl", Sys->OREAD);
+ if (ncfd == nil)
+ return;
+ b := array[128] of byte;
+ n := sys->read(ncfd, b, len b);
+ id := string int string b[0:n];
+ buf := "/mnt/acme/" + id + "/ctl";
+ icfd := sys->open(buf, Sys->OWRITE);
+ if (icfd == nil)
+ return;
+ base := hd tl argl;
+ for (i := len base - 1; i >= 0; --i)
+ if (base[i] == '/') {
+ base = base[i+1:];
+ break;
+ }
+ buf = "name " + workdir->init() + "/-" + base + "\n";
+ b = array of byte buf;
+ sys->write(icfd, b, len b);
+ buf = "/mnt/acme/" + id + "/body";
+ bfd := sys->open(buf, Sys->OWRITE);
+ if (bfd == nil)
+ return;
+ sys->dup(bfd.fd, 1);
+ sys->dup(1, 2);
+ spawn exec(hd tl argl, tl argl, ctxt);
+ b = array of byte "clean\n";
+ sys->write(icfd, b, len b);
+}
+
+exec(cmd : string, argl : list of string, ctxt : ref Draw->Context)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sys->fprint(sys->fildes(2), "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(ctxt, argl);
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/spout.b
@@ -1,0 +1,143 @@
+implement Spout;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+Spout : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+
+OREAD, OWRITE, ORDWR, FORKNS, FORKFD, NEWPGRP, MREPL, FD, UTFmax, pctl, open, read, write, fprint, sprint, fildes, bind, dup, byte2char, utfbytes : import sys;
+Iobuf : import bufio;
+
+stdin, stdout, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+bout : ref Iobuf;
+
+main(argv : list of string)
+{
+ fd : ref FD;
+
+ bout = bufio->fopen(stdout, OWRITE);
+ if(len argv == 1)
+ spout(stdin, "");
+ else
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ fd = open(hd argv, OREAD);
+ if(fd == nil){
+ fprint(stderr, "spell: can't open %s: %r\n", hd argv);
+ continue;
+ }
+ spout(fd, hd argv);
+ fd = nil;
+ }
+ exit;
+}
+
+alpha(c : int) : int
+{
+ return ('a'<=(c) && (c)<='z') || ('A'<=(c) && (c)<='Z');
+}
+
+b : ref Iobuf;
+
+spout(fd : ref FD, name : string)
+{
+ s, buf : string;
+ t, w : int;
+ inword, wordchar : int;
+ n, wn, c, m : int;
+
+ b = bufio->fopen(fd, OREAD);
+ n = 0;
+ wn = 0;
+ while((s = b.gets('\n')) != nil){
+ if(s[len s-1] != '\n')
+ s[len s] = '\n';
+ if(s[0] == '.') {
+ for(c=0; c<3 && c < len s && s[c]>' '; c++)
+ n++;
+ s = s[c:];
+ }
+ inword = 0;
+ w = 0;
+ t = 0;
+ do{
+ c = s[t];
+ wordchar = 0;
+ if(alpha(c))
+ wordchar = 1;
+ if(inword && !wordchar){
+ if(c=='\'' && alpha(s[t+1])) {
+ n++;
+ t++;
+ continue;
+ }
+ m = t-w;
+ if(m > 1){
+ buf = s[w:w+m];
+ bout.puts(sprint("%s:#%d,#%d:%s\n", name, wn, n, buf));
+ }
+ inword = 0;
+ }else if(!inword && wordchar){
+ wn = n;
+ w = t;
+ inword = 1;
+ }
+ if(c=='\\' && (alpha(s[t+1]) || s[t+1]=='(')){
+ case(s[t+1]){
+ '(' =>
+ m = 4;
+ break;
+ 'f' =>
+ if(s[t+2] == '(')
+ m = 5;
+ else
+ m = 3;
+ break;
+ 's' =>
+ if(s[t+2] == '+' || s[t+2]=='-'){
+ if(s[t+3] == '(')
+ m = 6;
+ else
+ m = 4;
+ }else{
+ if(s[t+2] == '(')
+ m = 5;
+ else if(s[t+2]=='1' || s[t+2]=='2' || s[t+2]=='3')
+ m = 4;
+ else
+ m = 3;
+ }
+ break;
+ * =>
+ m = 2;
+ }
+ while(m-- > 0){
+ if(s[t] == '\n')
+ break;
+ n++;
+ t++;
+ }
+ continue;
+ }
+ n++;
+ t ++;
+ }while(c != '\n');
+ }
+ bout.flush();
+}
--- /dev/null
+++ b/appl/acme/acme/bin/src/win.b
@@ -1,0 +1,770 @@
+implement Win;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+include "sh.m";
+include "env.m";
+
+sys : Sys;
+workdir : Workdir;
+env: Env;
+
+OREAD, OWRITE, ORDWR, FORKNS, FORKENV, FORKFD, NEWPGRP, MREPL, FD, UTFmax, pctl, open, read, write, fprint, sprint, fildes, bind, dup, byte2char, utfbytes : import sys;
+
+Win : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+Runeself : con 16r80;
+
+PNPROC, PNGROUP : con iota;
+
+stdout, stderr : ref FD;
+
+drawctxt : ref Draw->Context;
+finish : chan of int;
+
+Lock : adt {
+ c : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+};
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir = load Workdir Workdir->PATH;
+ env = load Env Env->PATH;
+ drawctxt = ctxt;
+ stdout = fildes(1);
+ stderr = fildes(2);
+ debuginit();
+ finish = chan[1] of int;
+ spawn main(argl);
+ <-finish;
+}
+
+Lock.init() : ref Lock
+{
+ return ref Lock(chan[1] of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ l.c <-= 1;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ <-l.c;
+}
+
+dlock : ref Lock;
+debfd : ref Sys->FD;
+
+debuginit()
+{
+ # debfd = sys->create("./debugwin", Sys->OWRITE, 8r600);
+ # dlock = Lock.init();
+}
+
+debugpr(nil : string)
+{
+ # fprint(debfd, "%s", s);
+}
+
+debug(nil : string)
+{
+ # dlock.lock();
+ # fprint(debfd, "%s", s);
+ # dlock.unlock();
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(drawctxt, argl);
+}
+
+postnote(t : int, pid : int, note : string) : int
+{
+ # fd := open("/prog/" + string pid + "/ctl", OWRITE);
+ fd := open("#p/" + string pid + "/ctl", OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ fprint(fd, "%s", note);
+
+ fd = nil;
+ return 0;
+}
+
+sysname(): string
+{
+ fd := sys->open("#c/sysname", sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+ return string buf[0:n];
+}
+
+EVENTSIZE : con 256;
+
+Event : adt {
+ c1 : int;
+ c2 : int;
+ q0 : int;
+ q1 : int;
+ flag : int;
+ nb : int;
+ nr : int;
+ b : array of byte;
+ r : array of int;
+};
+
+blank : ref Event;
+
+pid : int;
+# pgrpfd : ref FD;
+parentpid : int;
+
+typing : array of byte;
+ntypeb : int;
+ntyper : int;
+ntypebreak : int;
+
+Q : adt {
+ l : ref Lock;
+ p : int;
+ k : int;
+};
+
+q : Q;
+
+newevent(n : int) : ref Event
+{
+ e := ref Event;
+ e.b = array[n*UTFmax+1] of byte;
+ e.r = array[n+1] of int;
+ return e;
+}
+
+main(argv : list of string)
+{
+ program : list of string;
+ fd, ctlfd, eventfd, addrfd, datafd : ref FD;
+ id : int;
+ c : chan of int;
+ name : string;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ q.l = Lock.init();
+ blank = newevent(2);
+ blank.c1 = 'M';
+ blank.c2 = 'X';
+ blank.q0 = blank.q1 = blank.flag = 0;
+ blank.nb = blank.nr = 1;
+ blank.b[0] = byte ' ';
+ blank.b[1] = byte 0;
+ blank.r[0] = ' ';
+ blank.r[1] = 0;
+ pctl(FORKNS|NEWPGRP, nil);
+ parentpid = pctl(0, nil);
+ program = nil;
+ if(tl argv != nil)
+ program = tl argv;
+ name = nil;
+ if(program == nil){
+ # program = "-i" :: program;
+ program = "sh" :: program;
+ name = sysname();
+ }
+ if(name == nil){
+ prog := hd program;
+ for (n := len prog - 1; n >= 0; n--)
+ if (prog[n] == '/')
+ break;
+ if(n >= 0)
+ name = prog[n+1:];
+ else
+ name = prog;
+ argl := tl argv;
+ if (argl != nil) {
+ for(argl = tl argl; argl != nil && len(name)+1+len(hd argl)<16; argl = tl argl)
+ name += "_" + hd argl;
+ }
+ }
+ if(bind("#|", "/dev/acme", MREPL) < 0)
+ error("pipe");
+ ctlfd = open("/chan/new/ctl", ORDWR);
+ buf := array[12] of byte;
+ if(ctlfd==nil || read(ctlfd, buf, 12)!=12)
+ error("ctl");
+ id = int string buf;
+ buf = nil;
+ env->setenv("acmewin", string id);
+ b := sprint("/chan/%d/tag", id);
+ fd = open(b, OWRITE);
+ write(fd, array of byte " Send Delete", 12);
+ fd = nil;
+ b = sprint("/chan/%d/event", id);
+ eventfd = open(b, ORDWR);
+ b = sprint("/chan/%d/addr", id);
+ addrfd = open(b, ORDWR);
+ b = sprint("/chan/%d/data", id);
+ datafd = open(b, ORDWR); # OCEXEC
+ if(eventfd==nil || addrfd==nil || datafd==nil)
+ error("data files");
+ c = chan of int;
+ spawn run(program, id, c);
+ pid = <-c;
+ # b = sprint("/prog/%d/notepg", pid);
+ # pgrpfd = open(b, OWRITE); # OCEXEC
+ # if(pgrpfd == nil)
+ # fprint(stdout, "warning: win can't open notepg: %r\n");
+ c <-= 1;
+ fd = open("/dev/acme/data", ORDWR);
+ if(fd == nil)
+ error("/dev/acme/data");
+ wd := workdir->init();
+ # b = sprint("name %s/-%s\n0\n", wd, name);
+ b = sprint("name %s/-%s\n", wd, name);
+ ab := array of byte b;
+ write(ctlfd, ab, len ab);
+ b = sprint("dumpdir %s/\n", wd);
+ ab = array of byte b;
+ write(ctlfd, ab, len ab);
+ b = sprint("dump %s\n", onestring(argv));
+ ab = array of byte b;
+ write(ctlfd, ab, len ab);
+ ab = nil;
+ spawn stdinx(fd, ctlfd, eventfd, addrfd, datafd);
+ stdoutx(fd, addrfd, datafd);
+}
+
+run(argv : list of string, id : int, c : chan of int)
+{
+ fd0, fd1 : ref FD;
+
+ pctl(FORKENV|FORKFD|NEWPGRP, nil); # had RFMEM
+ c <-= pctl(0, nil);
+ <-c;
+ pctl(FORKNS, nil);
+ if(bind("/dev/acme/data1", "/dev/cons", MREPL) < 0){
+ fprint(stderr, "can't bind /dev/cons: %r\n");
+ exit;
+ }
+ fd0 = open("/dev/cons", OREAD);
+ fd1 = open("/dev/cons", OWRITE);
+ if(fd0==nil || fd1==nil){
+ fprint(stderr, "can't open /dev/cons: %r\n");
+ exit;
+ }
+ dup(fd0.fd, 0);
+ dup(fd1.fd, 1);
+ dup(fd1.fd, 2);
+ fd0 = fd1 = nil;
+ b := sprint("/chan/%d", id);
+ if(bind(b, "/dev/acme", MREPL) < 0)
+ error("bind /dev/acme");
+ if(bind(sprint("/chan/%d/consctl", id), "/dev/consctl", MREPL) < 0)
+ error("bind /dev/consctl");
+ exec(hd argv, argv);
+ exit;
+}
+
+killing : int = 0;
+
+error(s : string)
+{
+ if(s != nil)
+ fprint(stderr, "win: %s: %r\n", s);
+ if (killing)
+ return;
+ killing = 1;
+ s = "kill";
+ if(pid)
+ postnote(PNGROUP, pid, s);
+ # write(pgrpfd, array of byte "hangup", 6);
+ postnote(PNGROUP, parentpid, s);
+ finish <-= 1;
+ exit;
+}
+
+buff := array[8192] of byte;
+bufp : int;
+nbuf : int;
+
+onestring(argv : list of string) : string
+{
+ s : string;
+
+ if(argv == nil)
+ return "";
+ for( ; argv != nil; argv = tl argv){
+ s += hd argv;
+ if (tl argv != nil)
+ s += " ";
+ }
+ return s;
+}
+
+getec(efd : ref FD) : int
+{
+ if(nbuf == 0){
+ nbuf = read(efd, buff, len buff);
+ if(nbuf <= 0)
+ error(nil);
+ bufp = 0;
+ }
+ --nbuf;
+ return int buff[bufp++];
+}
+
+geten(efd : ref FD) : int
+{
+ n, c : int;
+
+ n = 0;
+ while('0'<=(c=getec(efd)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+geter(efd : ref FD, buf : array of byte) : (int, int)
+{
+ r, m, n, ok : int;
+
+ r = getec(efd);
+ buf[0] = byte r;
+ n = 1;
+ if(r < Runeself)
+ return (r, n);
+ for (;;) {
+ (r, m, ok) = byte2char(buf[0:n], 0);
+ if (m > 0)
+ return (r, n);
+ buf[n++] = byte getec(efd);
+ }
+ return (0, 0);
+}
+
+gete(efd : ref FD, e : ref Event)
+{
+ i, nb : int;
+
+ e.c1 = getec(efd);
+ e.c2 = getec(efd);
+ e.q0 = geten(efd);
+ e.q1 = geten(efd);
+ e.flag = geten(efd);
+ e.nr = geten(efd);
+ if(e.nr > EVENTSIZE)
+ error("event string too long");
+ e.nb = 0;
+ for(i=0; i<e.nr; i++){
+ (e.r[i], nb) = geter(efd, e.b[e.nb:]);
+ e.nb += nb;
+ }
+ e.r[e.nr] = 0;
+ e.b[e.nb] = byte 0;
+ if(getec(efd) != '\n')
+ error("event syntax 2");
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n, r, b, ok : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) = byte2char(s, i);
+ if (b == 0)
+ error("not full string in nrunes()");
+ i += b;
+ }
+ return n;
+}
+
+stdinx(fd0 : ref FD, cfd : ref FD, efd : ref FD, afd : ref FD, dfd : ref FD)
+{
+ e, e2, e3, e4 : ref Event;
+
+ e = newevent(EVENTSIZE);
+ e2 = newevent(EVENTSIZE);
+ e3 = newevent(EVENTSIZE);
+ e4 = newevent(EVENTSIZE);
+ for(;;){
+ gete(efd, e);
+ q.l.lock();
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' or 'M' =>
+ case(e.c2){
+ 'R' =>
+ addtype(' ', ntyper, e.b, e.nb, e.nr);
+ sendtype(fd0, 1);
+ break;
+ 'I' =>
+ if(e.q0 < q.p)
+ q.p += e.q1-e.q0;
+ else if(e.q0 <= q.p+ntyper)
+ typex(e, fd0, afd, dfd);
+ break;
+ 'D' =>
+ q.p -= delete(e);
+ break;
+ 'x' or 'X' =>
+ if(e.flag & 2)
+ gete(efd, e2);
+ if(e.flag & 8){
+ gete(efd, e3);
+ gete(efd, e4);
+ }
+ if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){
+ # send it straight back
+ fprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+ break;
+ }
+ if(e.q0==e.q1 && (e.flag&2)){
+ e2.flag = e.flag;
+ *e = *e2;
+ }
+ if(e.flag & 8){
+ if(e.q1 != e.q0){
+ send(e, fd0, cfd, afd, dfd, 0);
+ send(blank, fd0, cfd, afd, dfd, 0);
+ }
+ send(e3, fd0, cfd, afd, dfd, 1);
+ }else if(e.q1 != e.q0)
+ send(e, fd0, cfd, afd, dfd, 1);
+ break;
+ 'l' or 'L' =>
+ # just send it back
+ if(e.flag & 2)
+ gete(efd, e2);
+ fprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+ break;
+ 'd' or 'i' =>
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ q.l.unlock();
+ }
+}
+
+stdoutx(fd1 : ref FD, afd : ref FD, dfd : ref FD)
+{
+ n, m, w, npart : int;
+ s, t : int;
+ buf, hold, x : array of byte;
+ r, ok : int;
+
+ buf = array[8192+UTFmax+1] of byte;
+ hold = array[UTFmax] of byte;
+ npart = 0;
+ for(;;){
+ n = read(fd1, buf[npart:], 8192);
+ if(n < 0)
+ error(nil);
+ if(n == 0)
+ continue;
+
+ # squash NULs
+ for (s = 0; s < n; s++)
+ if (buf[npart+s] == byte 0)
+ break;
+ if(s < n){
+ for(t=s; s<n; s++)
+ if(buf[npart+t] == buf[npart+s]) # assign =
+ t++;
+ n = t;
+ }
+
+ n += npart;
+
+ # hold on to final partial rune
+ npart = 0;
+ while(n>0 && (int buf[n-1]&16rC0)){
+ --n;
+ npart++;
+ if((int buf[n]&16rC0)!=16r80){
+ if(utfbytes(buf[n:], npart) > 0){
+ (r, w, ok) = byte2char(buf, n);
+ n += w;
+ npart -= w;
+ }
+ break;
+ }
+ }
+ if(n > 0){
+ hold[0:] = buf[n:n+npart];
+ buf[n] = byte 0;
+ q.l.lock();
+ str := sprint("#%d", q.p);
+ x = array of byte str;
+ m = len x;
+ if(write(afd, x, m) != m)
+ error("stdout writing address");
+ x = nil;
+ if(write(dfd, buf, n) != n)
+ error("stdout writing body");
+ q.p += nrunes(buf, n);
+ q.l.unlock();
+ buf[0:] = hold[0:npart];
+ }
+ }
+}
+
+delete(e : ref Event) : int
+{
+ q0, q1 : int;
+ deltap : int;
+
+ q0 = e.q0;
+ q1 = e.q1;
+ if(q1 <= q.p)
+ return e.q1-e.q0;
+ if(q0 >= q.p+ntyper)
+ return 0;
+ deltap = 0;
+ if(q0 < q.p){
+ deltap = q.p-q0;
+ q0 = 0;
+ }else
+ q0 -= q.p;
+ if(q1 > q.p+ntyper)
+ q1 = ntyper;
+ else
+ q1 -= q.p;
+ deltype(q0, q1);
+ return deltap;
+}
+
+addtype(c : int, p0 : int, b : array of byte, nb : int, nr : int)
+{
+ i, w : int;
+ r, ok : int;
+ p : int;
+ b0 : int;
+
+ for(i=0; i<nb; i+=w){
+ (r, w, ok) = byte2char(b, i);
+ if(r==16r7F && c=='K'){
+ if (pid)
+ postnote(PNGROUP, pid, "kill");
+ # write(pgrpfd, array of byte "interrupt", 9);
+ # toss all typing
+ q.p += ntyper+nr;
+ ntypebreak = 0;
+ ntypeb = 0;
+ ntyper = 0;
+ # buglet: more than one delete ignored
+ return;
+ }
+ if(r=='\n' || r==16r04)
+ ntypebreak++;
+ }
+ ot := typing;
+ typing = array[ntypeb+nb] of byte;
+ if(typing == nil)
+ error("realloc");
+ if (ot != nil)
+ typing[0:] = ot[0:ntypeb];
+ ot = nil;
+ if(p0 == ntyper)
+ typing[ntypeb:] = b[0:nb];
+ else{
+ b0 = 0;
+ for(p=0; p<p0 && b0<ntypeb; p++){
+ (r, w, ok) = byte2char(typing[b0:], i);
+ b0 += w;
+ }
+ if(p != p0)
+ error("typing: findrune");
+ typing[b0+nb:] = typing[b0:ntypeb];
+ typing[b0:] = b[0:nb];
+ }
+ ntypeb += nb;
+ ntyper += nr;
+}
+
+sendtype(fd0 : ref FD, raw : int)
+{
+ while(ntypebreak){
+ brkc := 0;
+ i := 0;
+ while(i<ntypeb){
+ if(typing[i]==byte '\n' || typing[i]==byte 16r04){
+ n := i + (typing[i] == byte '\n');
+ i++;
+ if(write(fd0, typing, n) != n)
+ error("sending to program");
+ nr := nrunes(typing, i);
+ if (!raw)
+ q.p += nr;
+ ntyper -= nr;
+ ntypeb -= i;
+ typing[0:] = typing[i:i+ntypeb];
+ i = 0;
+ ntypebreak--;
+ brkc = 1;
+ }else
+ i++;
+ }
+ if (!brkc) {
+ fprint(stdout, "no breakchar\n");
+ ntypebreak = 0;
+ }
+ }
+}
+
+deltype(p0 : int, p1 : int)
+{
+ w : int;
+ p, b0, b1 : int;
+ r, ok : int;
+
+ # advance to p0
+ b0 = 0;
+ for(p=0; p<p0 && b0<ntypeb; p++){
+ (r, w, ok) = byte2char(typing, b0);
+ b0 += w;
+ }
+ if(p != p0)
+ error("deltype 1");
+ # advance to p1
+ b1 = b0;
+ for(; p<p1 && b1<ntypeb; p++){
+ (r, w, ok) = byte2char(typing, b1);
+ b1 += w;
+ if(r=='\n' || r==16r04)
+ ntypebreak--;
+ }
+ if(p != p1)
+ error("deltype 2");
+ typing[b0:] = typing[b1:ntypeb];
+ ntypeb -= b1-b0;
+ ntyper -= p1-p0;
+}
+
+typex(e : ref Event, fd0 : ref FD, afd : ref FD, dfd : ref FD)
+{
+ m, n, nr : int;
+ buf : array of byte;
+
+ if(e.nr > 0)
+ addtype(e.c1, e.q0-q.p, e.b, e.nb, e.nr);
+ else{
+ buf = array[128] of byte;
+ m = e.q0;
+ while(m < e.q1){
+ str := sprint("#%d", m);
+ b := array of byte str;
+ n = len b;
+ write(afd, b, n);
+ b = nil;
+ n = read(dfd, buf, len buf);
+ nr = nrunes(buf, n);
+ while(m+nr > e.q1){
+ do; while(n>0 && (int buf[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ addtype(e.c1, m-q.p, buf, n, nr);
+ m += nr;
+ }
+ }
+ buf = nil;
+ sendtype(fd0, 0);
+}
+
+send(e : ref Event, fd0 : ref FD, cfd : ref FD, afd : ref FD, dfd : ref FD, donl : int)
+{
+ l, m, n, nr, lastc, end : int;
+ abuf, buf : array of byte;
+
+ buf = array[128] of byte;
+ end = q.p+ntyper;
+ str := sprint("#%d", end);
+ abuf = array of byte str;
+ l = len abuf;
+ write(afd, abuf, l);
+ abuf = nil;
+ if(e.nr > 0){
+ write(dfd, e.b, e.nb);
+ addtype(e.c1, ntyper, e.b, e.nb, e.nr);
+ lastc = e.r[e.nr-1];
+ }else{
+ m = e.q0;
+ lastc = 0;
+ while(m < e.q1){
+ str = sprint("#%d", m);
+ abuf = array of byte str;
+ n = len abuf;
+ write(afd, abuf, n);
+ abuf = nil;
+ n = read(dfd, buf, len buf);
+ nr = nrunes(buf, n);
+ while(m+nr > e.q1){
+ do; while(n>0 && (int buf[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ str = sprint("#%d", end);
+ abuf = array of byte str;
+ l = len abuf;
+ write(afd, abuf, l);
+ abuf = nil;
+ write(dfd, buf, n);
+ addtype(e.c1, ntyper, buf, n, nr);
+ lastc = int buf[n-1];
+ m += nr;
+ end += nr;
+ }
+ }
+ if(donl && lastc!='\n'){
+ write(dfd, array of byte "\n", 1);
+ addtype(e.c1, ntyper, array of byte "\n", 1, 1);
+ }
+ write(cfd, array of byte "dot=addr", 8);
+ sendtype(fd0, 0);
+ buf = nil;
+}
+
--- /dev/null
+++ b/appl/acme/acme/bin/src/winm.b
@@ -1,0 +1,791 @@
+implement Win;
+
+include "sys.m";
+include "draw.m";
+include "workdir.m";
+include "sh.m";
+
+sys : Sys;
+workdir : Workdir;
+
+OREAD, OWRITE, ORDWR, FORKNS, FORKENV, FORKFD, NEWPGRP, MREPL, FD, UTFmax, pctl, open, read, write, fprint, sprint, fildes, bind, dup, byte2char, utfbytes : import sys;
+
+Win : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+Runeself : con 16r80;
+
+PNPROC, PNGROUP : con iota;
+
+stdout, stderr : ref FD;
+
+drawctxt : ref Draw->Context;
+
+Lock : adt {
+ cnt : int;
+ chann : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+};
+
+lc, uc : chan of ref Lock;
+lockpid : int;
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ workdir = load Workdir Workdir->PATH;
+ drawctxt = ctxt;
+ stdout = fildes(1);
+ stderr = fildes(2);
+ lc = chan of ref Lock;
+ uc = chan of ref Lock;
+ spawn lockmgr();
+ debuginit();
+ main(argl);
+}
+
+lockmgr()
+{
+ l : ref Lock;
+
+ lockpid = pctl(0, nil);
+ for (;;) {
+ alt {
+ l = <- lc =>
+ if (l.cnt++ == 0)
+ l.chann <-= 1;
+ l = <- uc =>
+ if (--l.cnt > 0)
+ l.chann <-= 1;
+ }
+ }
+}
+
+Lock.init() : ref Lock
+{
+ return ref Lock(0, chan of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ lc <-= l;
+ <- l.chann;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ uc <-= l;
+}
+
+dlock : ref Lock;
+debfd : ref Sys->FD;
+
+debuginit()
+{
+ # debfd = sys->create("./debugwin", Sys->OWRITE, 8r600);
+ # dlock = Lock.init();
+}
+
+debugpr(nil : string)
+{
+ # fprint(debfd, "%s", s);
+}
+
+debug(nil : string)
+{
+ # dlock.lock();
+ # fprint(debfd, "%s", s);
+ # dlock.unlock();
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(drawctxt, argl);
+}
+
+postnote(t : int, pid : int, note : string) : int
+{
+ # fd := open("/prog/" + string pid + "/ctl", OWRITE);
+ fd := open("#p/" + string pid + "/ctl", OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ fprint(fd, "%s", note);
+
+ fd = nil;
+ return 0;
+}
+
+sysname(): string
+{
+ fd := sys->open("#c/sysname", sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return nil;
+ return string buf[0:n];
+}
+
+EVENTSIZE : con 256;
+
+Event : adt {
+ c1 : int;
+ c2 : int;
+ q0 : int;
+ q1 : int;
+ flag : int;
+ nb : int;
+ nr : int;
+ b : array of byte;
+ r : array of int;
+};
+
+blank : ref Event;
+
+pid : int;
+# pgrpfd : ref FD;
+parentpid : int;
+
+typing : array of byte;
+ntypeb : int;
+ntyper : int;
+ntypebreak : int;
+
+Q : adt {
+ l : ref Lock;
+ p : int;
+ k : int;
+};
+
+q : Q;
+
+newevent(n : int) : ref Event
+{
+ e := ref Event;
+ e.b = array[n*UTFmax+1] of byte;
+ e.r = array[n+1] of int;
+ return e;
+}
+
+main(argv : list of string)
+{
+ spawn main1(argv);
+ exit;
+}
+
+main1(argv : list of string)
+{
+ program : list of string;
+ fd, ctlfd, eventfd, addrfd, datafd : ref FD;
+ id : int;
+ c : chan of int;
+ name : string;
+
+ q.l = Lock.init();
+ blank = newevent(2);
+ blank.c1 = 'M';
+ blank.c2 = 'X';
+ blank.q0 = blank.q1 = blank.flag = 0;
+ blank.nb = blank.nr = 1;
+ blank.b[0] = byte ' ';
+ blank.b[1] = byte 0;
+ blank.r[0] = ' ';
+ blank.r[1] = 0;
+ pctl(FORKNS|NEWPGRP, nil);
+ parentpid = pctl(0, nil);
+ program = nil;
+ if(tl argv != nil)
+ program = tl argv;
+ name = nil;
+ if(program == nil){
+ # program = "-i" :: program;
+ program = "sh" :: program;
+ name = sysname();
+ }
+ if(name == nil){
+ prog := hd program;
+ for (n := len prog - 1; n >= 0; n--)
+ if (prog[n] == '/')
+ break;
+ if(n >= 0)
+ name = prog[n+1:];
+ else
+ name = prog;
+ argl := tl argv;
+ if (argl != nil) {
+ for(argl = tl argl; argl != nil && len(name)+1+len(hd argl)<16; argl = tl argl)
+ name += "_" + hd argl;
+ }
+ }
+ if(bind("#|", "/dev/acme", MREPL) < 0)
+ error("pipe");
+ ctlfd = open("/chan/new/ctl", ORDWR);
+ buf := array[12] of byte;
+ if(ctlfd==nil || read(ctlfd, buf, 12)!=12)
+ error("ctl");
+ id = int string buf;
+ buf = nil;
+ b := sprint("/chan/%d/tag", id);
+ fd = open(b, OWRITE);
+ write(fd, array of byte " Send Delete", 12);
+ fd = nil;
+ b = sprint("/chan/%d/event", id);
+ eventfd = open(b, ORDWR);
+ b = sprint("/chan/%d/addr", id);
+ addrfd = open(b, ORDWR);
+ b = sprint("/chan/%d/data", id);
+ datafd = open(b, ORDWR); # OCEXEC
+ if(eventfd==nil || addrfd==nil || datafd==nil)
+ error("data files");
+ c = chan of int;
+ spawn run(program, id, c);
+ pid = <-c;
+ # b = sprint("/prog/%d/notepg", pid);
+ # pgrpfd = open(b, OWRITE); # OCEXEC
+ # if(pgrpfd == nil)
+ # fprint(stdout, "warning: win can't open notepg: %r\n");
+ c <-= 1;
+ fd = open("/dev/acme/data", ORDWR);
+ if(fd == nil)
+ error("/dev/acme/data");
+ wd := workdir->init();
+ # b = sprint("name %s/-%s\n0\n", wd, name);
+ b = sprint("name %s/-%s\n", wd, name);
+ ab := array of byte b;
+ write(ctlfd, ab, len ab);
+ b = sprint("dumpdir %s/\n", wd);
+ ab = array of byte b;
+ write(ctlfd, ab, len ab);
+ b = sprint("dump %s\n", onestring(argv));
+ ab = array of byte b;
+ write(ctlfd, ab, len ab);
+ ab = nil;
+ spawn stdinx(fd, ctlfd, eventfd, addrfd, datafd);
+ stdoutx(fd, addrfd, datafd);
+}
+
+run(argv : list of string, id : int, c : chan of int)
+{
+ fd0, fd1 : ref FD;
+
+ pctl(FORKENV|FORKFD|NEWPGRP, nil); # had RFMEM
+ c <-= pctl(0, nil);
+ <-c;
+ pctl(FORKNS, nil);
+ if(bind("/dev/acme/data1", "/dev/cons", MREPL) < 0){
+ fprint(stderr, "can't bind /dev/cons: %r\n");
+ exit;
+ }
+ fd0 = open("/dev/cons", OREAD);
+ fd1 = open("/dev/cons", OWRITE);
+ if(fd0==nil || fd1==nil){
+ fprint(stderr, "can't open /dev/cons: %r\n");
+ exit;
+ }
+ dup(fd0.fd, 0);
+ dup(fd1.fd, 1);
+ dup(fd1.fd, 2);
+ fd0 = fd1 = nil;
+ b := sprint("/chan/%d", id);
+ if(bind(b, "/dev/acme", MREPL) < 0)
+ error("bind /dev/acme");
+ if(bind(sprint("/chan/%d/consctl", id), "/dev/consctl", MREPL) < 0)
+ error("bind /dev/consctl");
+ exec(hd argv, argv);
+ exit;
+}
+
+killing : int = 0;
+
+error(s : string)
+{
+ if(s != nil)
+ fprint(stderr, "win: %s: %r\n", s);
+ if (killing)
+ return;
+ killing = 1;
+ s = "kill";
+ if(pid)
+ postnote(PNGROUP, pid, s);
+ # write(pgrpfd, array of byte "hangup", 6);
+ postnote(PNPROC, lockpid, s);
+ postnote(PNGROUP, parentpid, s);
+ exit;
+}
+
+buff := array[8192] of byte;
+bufp : int;
+nbuf : int;
+
+onestring(argv : list of string) : string
+{
+ s : string;
+
+ if(argv == nil)
+ return "";
+ for( ; argv != nil; argv = tl argv){
+ s += hd argv;
+ if (tl argv != nil)
+ s += " ";
+ }
+ return s;
+}
+
+getec(efd : ref FD) : int
+{
+ if(nbuf == 0){
+ nbuf = read(efd, buff, len buff);
+ if(nbuf <= 0)
+ error(nil);
+ bufp = 0;
+ }
+ --nbuf;
+ return int buff[bufp++];
+}
+
+geten(efd : ref FD) : int
+{
+ n, c : int;
+
+ n = 0;
+ while('0'<=(c=getec(efd)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+geter(efd : ref FD, buf : array of byte) : (int, int)
+{
+ r, m, n, ok : int;
+
+ r = getec(efd);
+ buf[0] = byte r;
+ n = 1;
+ if(r < Runeself)
+ return (r, n);
+ for (;;) {
+ (r, m, ok) = byte2char(buf[0:n], 0);
+ if (m > 0)
+ return (r, n);
+ buf[n++] = byte getec(efd);
+ }
+ return (0, 0);
+}
+
+gete(efd : ref FD, e : ref Event)
+{
+ i, nb : int;
+
+ e.c1 = getec(efd);
+ e.c2 = getec(efd);
+ e.q0 = geten(efd);
+ e.q1 = geten(efd);
+ e.flag = geten(efd);
+ e.nr = geten(efd);
+ if(e.nr > EVENTSIZE)
+ error("event string too long");
+ e.nb = 0;
+ for(i=0; i<e.nr; i++){
+ (e.r[i], nb) = geter(efd, e.b[e.nb:]);
+ e.nb += nb;
+ }
+ e.r[e.nr] = 0;
+ e.b[e.nb] = byte 0;
+ if(getec(efd) != '\n')
+ error("event syntax 2");
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n, r, b, ok : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) = byte2char(s, i);
+ if (b == 0)
+ error("not full string in nrunes()");
+ i += b;
+ }
+ return n;
+}
+
+stdinx(fd0 : ref FD, cfd : ref FD, efd : ref FD, afd : ref FD, dfd : ref FD)
+{
+ e, e2, e3, e4 : ref Event;
+
+ e = newevent(EVENTSIZE);
+ e2 = newevent(EVENTSIZE);
+ e3 = newevent(EVENTSIZE);
+ e4 = newevent(EVENTSIZE);
+ for(;;){
+ gete(efd, e);
+ q.l.lock();
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' or 'M' =>
+ case(e.c2){
+ 'R' =>
+ addtype(' ', ntyper, e.b, e.nb, e.nr);
+ sendtype(fd0, 1);
+ break;
+ 'I' =>
+ if(e.q0 < q.p)
+ q.p += e.q1-e.q0;
+ else if(e.q0 <= q.p+ntyper)
+ typex(e, fd0, afd, dfd);
+ break;
+ 'D' =>
+ q.p -= delete(e);
+ break;
+ 'x' or 'X' =>
+ if(e.flag & 2)
+ gete(efd, e2);
+ if(e.flag & 8){
+ gete(efd, e3);
+ gete(efd, e4);
+ }
+ if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){
+ # send it straight back
+ fprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+ break;
+ }
+ if(e.q0==e.q1 && (e.flag&2)){
+ e2.flag = e.flag;
+ *e = *e2;
+ }
+ if(e.flag & 8){
+ if(e.q1 != e.q0){
+ send(e, fd0, cfd, afd, dfd, 0);
+ send(blank, fd0, cfd, afd, dfd, 0);
+ }
+ send(e3, fd0, cfd, afd, dfd, 1);
+ }else if(e.q1 != e.q0)
+ send(e, fd0, cfd, afd, dfd, 1);
+ break;
+ 'l' or 'L' =>
+ # just send it back
+ if(e.flag & 2)
+ gete(efd, e2);
+ fprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+ break;
+ 'd' or 'i' =>
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ q.l.unlock();
+ }
+}
+
+stdoutx(fd1 : ref FD, afd : ref FD, dfd : ref FD)
+{
+ n, m, w, npart : int;
+ s, t : int;
+ buf, hold, x : array of byte;
+ r, ok : int;
+
+ buf = array[8192+UTFmax+1] of byte;
+ hold = array[UTFmax] of byte;
+ npart = 0;
+ for(;;){
+ n = read(fd1, buf[npart:], 8192);
+ if(n < 0)
+ error(nil);
+ if(n == 0)
+ continue;
+
+ # squash NULs
+ for (s = 0; s < n; s++)
+ if (buf[npart+s] == byte 0)
+ break;
+ if(s < n){
+ for(t=s; s<n; s++)
+ if(buf[npart+t] == buf[npart+s]) # assign =
+ t++;
+ n = t;
+ }
+
+ n += npart;
+
+ # hold on to final partial rune
+ npart = 0;
+ while(n>0 && (int buf[n-1]&16rC0)){
+ --n;
+ npart++;
+ if((int buf[n]&16rC0)!=16r80){
+ if(utfbytes(buf[n:], npart) > 0){
+ (r, w, ok) = byte2char(buf, n);
+ n += w;
+ npart -= w;
+ }
+ break;
+ }
+ }
+ if(n > 0){
+ hold[0:] = buf[n:n+npart];
+ buf[n] = byte 0;
+ q.l.lock();
+ str := sprint("#%d", q.p);
+ x = array of byte str;
+ m = len x;
+ if(write(afd, x, m) != m)
+ error("stdout writing address");
+ x = nil;
+ if(write(dfd, buf, n) != n)
+ error("stdout writing body");
+ q.p += nrunes(buf, n);
+ q.l.unlock();
+ buf[0:] = hold[0:npart];
+ }
+ }
+}
+
+delete(e : ref Event) : int
+{
+ q0, q1 : int;
+ deltap : int;
+
+ q0 = e.q0;
+ q1 = e.q1;
+ if(q1 <= q.p)
+ return e.q1-e.q0;
+ if(q0 >= q.p+ntyper)
+ return 0;
+ deltap = 0;
+ if(q0 < q.p){
+ deltap = q.p-q0;
+ q0 = 0;
+ }else
+ q0 -= q.p;
+ if(q1 > q.p+ntyper)
+ q1 = ntyper;
+ else
+ q1 -= q.p;
+ deltype(q0, q1);
+ return deltap;
+}
+
+addtype(c : int, p0 : int, b : array of byte, nb : int, nr : int)
+{
+ i, w : int;
+ r, ok : int;
+ p : int;
+ b0 : int;
+
+ for(i=0; i<nb; i+=w){
+ (r, w, ok) = byte2char(b, i);
+ if(r==16r7F && c=='K'){
+ if (pid)
+ postnote(PNGROUP, pid, "kill");
+ # write(pgrpfd, array of byte "interrupt", 9);
+ # toss all typing
+ q.p += ntyper+nr;
+ ntypebreak = 0;
+ ntypeb = 0;
+ ntyper = 0;
+ # buglet: more than one delete ignored
+ return;
+ }
+ if(r=='\n' || r==16r04)
+ ntypebreak++;
+ }
+ ot := typing;
+ typing = array[ntypeb+nb] of byte;
+ if(typing == nil)
+ error("realloc");
+ if (ot != nil)
+ typing[0:] = ot[0:ntypeb];
+ ot = nil;
+ if(p0 == ntyper)
+ typing[ntypeb:] = b[0:nb];
+ else{
+ b0 = 0;
+ for(p=0; p<p0 && b0<ntypeb; p++){
+ (r, w, ok) = byte2char(typing[b0:], i);
+ b0 += w;
+ }
+ if(p != p0)
+ error("typing: findrune");
+ typing[b0+nb:] = typing[b0:ntypeb];
+ typing[b0:] = b[0:nb];
+ }
+ ntypeb += nb;
+ ntyper += nr;
+}
+
+sendtype(fd0 : ref FD, raw : int)
+{
+ i, n, nr : int;
+
+ while(ntypebreak){
+ brkc := 0;
+ for(i=0; i<ntypeb; i++)
+ if(typing[i]==byte '\n' || typing[i]==byte 16r04){
+ n = i + (typing[i] == byte '\n');
+ i++;
+ if(write(fd0, typing, n) != n)
+ error("sending to program");
+ nr = nrunes(typing, i);
+ if (!raw)
+ q.p += nr;
+ ntyper -= nr;
+ ntypeb -= i;
+ typing[0:] = typing[i:i+ntypeb];
+ ntypebreak--;
+ brkc = 1;
+ }
+ if (!brkc) {
+ fprint(stdout, "no breakchar\n");
+ ntypebreak = 0;
+ }
+ }
+}
+
+deltype(p0 : int, p1 : int)
+{
+ w : int;
+ p, b0, b1 : int;
+ r, ok : int;
+
+ # advance to p0
+ b0 = 0;
+ for(p=0; p<p0 && b0<ntypeb; p++){
+ (r, w, ok) = byte2char(typing, b0);
+ b0 += w;
+ }
+ if(p != p0)
+ error("deltype 1");
+ # advance to p1
+ b1 = b0;
+ for(; p<p1 && b1<ntypeb; p++){
+ (r, w, ok) = byte2char(typing, b1);
+ b1 += w;
+ if(r=='\n' || r==16r04)
+ ntypebreak--;
+ }
+ if(p != p1)
+ error("deltype 2");
+ typing[b0:] = typing[b1:ntypeb];
+ ntypeb -= b1-b0;
+ ntyper -= p1-p0;
+}
+
+typex(e : ref Event, fd0 : ref FD, afd : ref FD, dfd : ref FD)
+{
+ m, n, nr : int;
+ buf : array of byte;
+
+ if(e.nr > 0)
+ addtype(e.c1, e.q0-q.p, e.b, e.nb, e.nr);
+ else{
+ buf = array[128] of byte;
+ m = e.q0;
+ while(m < e.q1){
+ str := sprint("#%d", m);
+ b := array of byte str;
+ n = len b;
+ write(afd, b, n);
+ b = nil;
+ n = read(dfd, buf, len buf);
+ nr = nrunes(buf, n);
+ while(m+nr > e.q1){
+ do; while(n>0 && (int buf[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ addtype(e.c1, m-q.p, buf, n, nr);
+ m += nr;
+ }
+ }
+ buf = nil;
+ sendtype(fd0, 0);
+}
+
+send(e : ref Event, fd0 : ref FD, cfd : ref FD, afd : ref FD, dfd : ref FD, donl : int)
+{
+ l, m, n, nr, lastc, end : int;
+ abuf, buf : array of byte;
+
+ buf = array[128] of byte;
+ end = q.p+ntyper;
+ str := sprint("#%d", end);
+ abuf = array of byte str;
+ l = len abuf;
+ write(afd, abuf, l);
+ abuf = nil;
+ if(e.nr > 0){
+ write(dfd, e.b, e.nb);
+ addtype(e.c1, ntyper, e.b, e.nb, e.nr);
+ lastc = e.r[e.nr-1];
+ }else{
+ m = e.q0;
+ lastc = 0;
+ while(m < e.q1){
+ str = sprint("#%d", m);
+ abuf = array of byte str;
+ n = len abuf;
+ write(afd, abuf, n);
+ abuf = nil;
+ n = read(dfd, buf, len buf);
+ nr = nrunes(buf, n);
+ while(m+nr > e.q1){
+ do; while(n>0 && (int buf[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ str = sprint("#%d", end);
+ abuf = array of byte str;
+ l = len abuf;
+ write(afd, abuf, l);
+ abuf = nil;
+ write(dfd, buf, n);
+ addtype(e.c1, ntyper, buf, n, nr);
+ lastc = int buf[n-1];
+ m += nr;
+ end += nr;
+ }
+ }
+ if(donl && lastc!='\n'){
+ write(dfd, array of byte "\n", 1);
+ addtype(e.c1, ntyper, array of byte "\n", 1, 1);
+ }
+ write(cfd, array of byte "dot=addr", 8);
+ sendtype(fd0, 0);
+ buf = nil;
+}
+
--- /dev/null
+++ b/appl/acme/acme/edit/guide
@@ -1,0 +1,4 @@
+e file | x '/regexp/' | c 'replacement'
+e 'file:0,$' | x '/.*word.*\n/' | p -n
+e file | pipe command args ...
+New /absolute/file/name
--- /dev/null
+++ b/appl/acme/acme/edit/mkfile
@@ -1,0 +1,24 @@
+<../../../../mkconfig
+
+BIN=$ROOT/acme/edit
+
+DIRS=\
+ src\
+
+TARG=\
+ guide\
+ readme\
+
+BINTARG=${TARG:%=$BIN/%}
+
+all:V: $TARG
+
+install:V: $BINTARG
+
+$BIN/guide : guide
+ rm -f $BIN/guide && cp guide $BIN/guide
+
+$BIN/readme : readme
+ rm -f $BIN/readme && cp readme $BIN/readme
+
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/appl/acme/acme/edit/readme
@@ -1,0 +1,31 @@
+The programs collected in /acme/edit offer a sam-like command interface
+to acme windows. The guide file
+ /acme/edit/guide
+holds templates for several editing operations implemented
+by external programs. These programs, composed in
+a pipeline, refine the sections of a file to be modified.
+Thus in sam when one says
+ x/.*\n/ g/foo/ p
+in /acme/edit one runs
+ x '/.*\n/' | g '/foo/' | p
+The e command, unrelated to e in sam, disambiguates file names, collects
+lists of names, etc., and produces input suitable for the other tools.
+For example:
+ e '/usr/rob/acme:0,$' | x /oldname/ | c /newname/
+changes oldname to newname in all the files loaded in acme whose names match
+the literal text /usr/rob/acme.
+
+The commands in /acme/edit are
+ e
+ x
+ g
+ c
+ d
+ p
+ pipe (like sam's | , which can't be used for syntactic reasons)
+
+p takes a -n flag analogous to grep's -n. There is no s command.
+e has a -l flag to produce line numbers instead of the default character numbers.
+Its implementation is poor but sufficient for the mundane job of recreating
+the occasional line number for tools like acid; its use with the other commands
+in this directory is discouraged.
--- /dev/null
+++ b/appl/acme/acme/edit/src/a.b
@@ -1,0 +1,89 @@
+implement Aa;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Aa : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stderr = fildes(2);
+ main(argl);
+}
+
+include "findfile.b";
+
+prog := "a";
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, id : int;
+ nf, n, seq, rlen : int;
+ f, tf : array of File;
+ buf, s : string;
+
+ if(len argv != 2){
+ fprint(stderr, "usage: %s 'replacement'\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # sort back to original order, backwards
+ qsort(f, nf, BSCMP);
+
+ # change
+ id = -1;
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ ab := array of byte hd tl argv;
+ rlen = len ab;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "mark\nnomark\n", 12) != 12)
+ rerror("setting nomark");
+ }
+ if(fprint(afd, "#%d", f[i].q1) < 0)
+ rerror("writing address");
+ if(write(dfd, ab, rlen) != rlen)
+ rerror("writing replacement");
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/c.b
@@ -1,0 +1,104 @@
+implement Cc;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Cc : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stderr = fildes(2);
+ main(argl);
+}
+
+prog := "c";
+
+include "findfile.b";
+
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, j, id : int;
+ r : string;
+ nf, n, seq, rlen : int;
+ f, tf : array of File;
+ buf, s : string;
+
+ if(len argv != 2){
+ fprint(stderr, "usage: %s 'replacement'\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # sort back to original order, backwards
+ qsort(f, nf, BSCMP);
+
+ # change
+ id = -1;
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ argv1 := hd tl argv;
+ rlen = len argv1;
+ r = argv1;
+ i = 0;
+ for(j=0; j<rlen; j++){
+ r[i] = argv1[j];
+ if(i>0 && r[i-1]=='\\'){
+ if(r[i] == 'n')
+ r[--i] = '\n';
+ else if(r[i]=='\\')
+ r[--i] = '\\';
+ }
+ i++;
+ }
+ rlen = i;
+ ab := array of byte r[0:rlen];
+ rlen = len ab;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "mark\nnomark\n", 12) != 12)
+ rerror("setting nomark");
+ }
+ if(fprint(afd, "#%d,#%d", f[i].q0, f[i].q1) < 0)
+ rerror("writing address");
+ if(write(dfd, ab, rlen) != rlen)
+ rerror("writing replacement");
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/d.b
@@ -1,0 +1,30 @@
+implement Dd;
+
+include "sys.m";
+include "draw.m";
+include "sh.m";
+
+Dd : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys := load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ if (len argl != 1) {
+ sys->fprint(stderr, "usage : d\n");
+ return;
+ }
+ cmd := "/acme/edit/c";
+ file := cmd + ".dis";
+ c := load Command file;
+ if(c == nil) {
+ sys->fprint(stderr, "%s: %r\n", cmd);
+ return;
+ }
+ argl = nil;
+ argl = "" :: argl;
+ argl = cmd :: argl;
+ c->init(ctxt, argl);
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/e.b
@@ -1,0 +1,154 @@
+implement Ee;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Ee : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stdout, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+include "findfile.b";
+
+prog := "e";
+
+main(argv : list of string)
+{
+ afd, cfd : ref FD;
+ i, id : int;
+ buf : string;
+ nf, n, lines, l0, l1 : int;
+ f, tf : array of File;
+
+ lines = 0;
+ if(len argv>1 && hd tl argv == "-l"){
+ lines = 1;
+ argv = tl argv;
+ }
+ if(len argv < 2){
+ fprint(stderr, "usage: %s 'file[:address]' ...\n", prog);
+ exit;
+ }
+ nf = 0;
+ f = nil;
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ (n, tf) = findfile(hd argv);
+ if(n == 0)
+ errors("no files match pattern", hd argv);
+ oldf := f;
+ f = array[n+nf] of File;
+ if(f == nil)
+ rerror("out of memory");
+ if (oldf != nil) {
+ f[0:] = oldf[0:nf];
+ oldf = nil;
+ }
+ f[nf:] = tf[0:n];
+ nf += n;
+ tf = nil;
+ }
+
+ # convert to character positions
+ for(i=0; i<nf; i++){
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "addr=dot\n", 9) != 9)
+ rerror("setting address to dot");
+ ab := array of byte f[i].addr;
+ if(write(afd, ab, len ab) != len ab){
+ fprint(stderr, "%s: %s:%s is invalid address\n", prog, f[i].name, f[i].addr);
+ f[i].ok = 0;
+ afd = nil;
+ cfd = nil;
+ continue;
+ }
+ seek(afd, big 0, 0);
+ ab = array[24] of byte;
+ if(read(afd, ab, len ab) != 2*12)
+ rerror("reading address");
+ afd = nil;
+ cfd = nil;
+ buf = string ab;
+ ab = nil;
+ f[i].q0 = int buf;
+ f[i].q1 = int buf[12:];
+ f[i].ok = 1;
+ }
+
+ # sort
+ qsort(f, nf, FCMP);
+
+ for(i=0; i<nf; i++){
+ if(f[i].ok)
+ if(lines){
+ (l0, l1) = lineno(f[i]);
+ if(l1 > l0)
+ fprint(stdout, "%s:%d,%d\n", f[i].name, l0, l1);
+ else
+ fprint(stdout, "%s:%d\n", f[i].name, l0);
+ }else{
+ if(f[i].q1 > f[i].q0)
+ fprint(stdout, "%s:#%d,#%d\n", f[i].name, f[i].q0, f[i].q1);
+ else
+ fprint(stdout, "%s:#%d\n", f[i].name, f[i].q0);
+ }
+ }
+ exit;
+}
+
+lineno(f : File) : (int, int)
+{
+ b : ref Iobuf;
+ n0, n1, q, r : int;
+ buf : string;
+
+ buf = sprint("/mnt/acme/%d/body", f.id);
+ b = bufio->open(buf, bufio->OREAD);
+ if(b == nil){
+ fprint(stderr, "%s: can't open %s: %r\n", prog, buf);
+ exit;
+ }
+ n0 = 1;
+ n1 = 1;
+ for(q=0; q<f.q1; q++){
+ r = b.getc();
+ if(r == bufio->EOF){
+ fprint(stderr, "%s: early EOF on %s\n", prog, buf);
+ exit;
+ }
+ if(r=='\n'){
+ if(q < f.q0)
+ n0++;
+ if(q+1 < f.q1)
+ n1++;
+ }
+ }
+ b.close();
+ return (n0, n1);
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/findfile.b
@@ -1,0 +1,210 @@
+File : adt {
+ id : int;
+ seq : int;
+ ok : int;
+ q0, q1 : int;
+ name : string;
+ addr : string;
+};
+
+BSCMP, SCMP, NCMP, FCMP : con iota;
+
+indexfile := "/mnt/acme/index";
+
+dfd: ref Sys->FD;
+debug(s : string)
+{
+ if (dfd == nil)
+ dfd = sys->create("/usr/jrf/acme/debugedit", Sys->OWRITE, 8r600);
+ sys->fprint(dfd, "%s", s);
+}
+
+error(s : string)
+{
+ fprint(stderr, "%s: %s\n", prog, s);
+ exit;
+}
+
+errors(s, t : string)
+{
+ fprint(stderr, "%s: %s %s\n", prog, s, t);
+ exit;
+}
+
+rerror(s : string)
+{
+ fprint(stderr, "%s: %s: %r\n", prog, s);
+ exit;
+}
+
+strcmp(s, t : string) : int
+{
+ if (s < t) return -1;
+ if (s > t) return 1;
+ return 0;
+}
+
+strstr(s, t : string) : int
+{
+ if (t == nil)
+ return 0;
+ n := len t;
+ if (n > len s)
+ return -1;
+ e := len s - n;
+ for (p := 0; p <= e; p++)
+ if (s[p:p+n] == t)
+ return p;
+ return -1;
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n, r, b, ok : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) = byte2char(s, i);
+ i += b;
+ }
+ return n;
+}
+
+index : ref Iobuf;
+
+findfile(pat : string) : (int, array of File)
+{
+ line, pat1, pat2 : string;
+ colon, blank : int;
+ n : int;
+ f : array of File;
+
+ if(index == nil)
+ index = bufio->open(indexfile, bufio->OREAD);
+ else
+ index.seek(big 0, 0);
+ if(index == nil)
+ rerror(indexfile);
+ for(colon=0; colon < len pat && pat[colon]!=':'; colon++)
+ ;
+ if (colon == len pat) {
+ pat1 = pat;
+ pat2 = ".";
+ }
+ else {
+ pat1 = pat[0:colon];
+ pat2 = pat[colon+1:];
+ }
+ n = 0;
+ f = nil;
+ while((line=index.gets('\n')) != nil){
+ if(len line < 5*12)
+ rerror("bad index file format");
+ line = line[0:len line - 1];
+ for(blank=5*12; blank < len line && line[blank]!=' '; blank++)
+ ;
+ if (blank < len line)
+ line = line[0:blank];
+ if(strcmp(line[5*12:], pat1) == 0){
+ # exact match: take that
+ f = nil; # should also free t->addr's
+ f = array[1] of File;
+ if(f == nil)
+ rerror("out of memory");
+ f[0].id = int line;
+ f[0].name = line[5*12:];
+ f[0].addr = pat2;
+ n = 1;
+ break;
+ }
+ if(strstr(line[5*12:], pat1) >= 0){
+ # partial match: add to list
+ off := f;
+ f = array[n+1] of File;
+ if(f == nil)
+ rerror("out of memory");
+ f[0:] = off[0:n];
+ off = nil;
+ f[n].id = int line;
+ f[n].name = line[5*12:];
+ f[n].addr = pat2;
+ n++;
+ }
+ }
+ return (n, f);
+}
+
+bscmp(a : File, b : File) : int
+{
+ return b.seq - a.seq;
+}
+
+scmp(a : File, b : File) : int
+{
+ return a.seq - b.seq;
+}
+
+ncmp(a : File, b : File) : int
+{
+ return strcmp(a.name, b.name);
+}
+
+fcmp(a : File, b : File) : int
+{
+ x : int;
+
+ if (a.name < b.name)
+ return -1;
+ if (a.name > b.name)
+ return 1;
+ x = a.q0 - b.q0;
+ if(x != 0)
+ return x;
+ return a.q1-b.q1;
+}
+
+gencmp(a : File, b : File, c : int) : int
+{
+ if (c == BSCMP)
+ return bscmp(a, b);
+ if (c == SCMP)
+ return scmp(a, b);
+ if (c == NCMP)
+ return ncmp(a, b);
+ if (c == FCMP)
+ return fcmp(a, b);
+ return 0;
+}
+
+qsort(a : array of File, n : int, c : int)
+{
+ i, j : int;
+ t : File;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && gencmp(a[i], a[0], c) < 0);
+ do
+ j--;
+ while(j > 0 && gencmp(a[j], a[0], c) > 0);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j, c);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n, c);
+ n = j;
+ }
+ }
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/g.b
@@ -1,0 +1,95 @@
+implement Gg;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Gg : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stdout, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+prog := "g";
+
+include "findfile.b";
+
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, id, seq : int;
+ nf, n, plen : int;
+ f, tf : array of File;
+ buf, s : string;
+
+ if(len argv!=2 || len hd tl argv==0 || (hd tl argv)[0]!='/'){
+ fprint(stderr, "usage: %s '/regexp/'\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # execute regexp
+ id = -1;
+ afd = nil;
+ dfd = nil;
+ cfd = nil;
+ bufb := array of byte hd tl argv;
+ plen = len bufb;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ }
+ ab := array of byte f[i].addr;
+ n = len ab;
+ if(write(afd, ab, n)!=n || fprint(cfd, "limit=addr\n")<0){
+ buf = sprint("%s:%s is invalid limit", f[i].name, f[i].addr);
+ rerror(buf);
+ }
+ if(fprint(afd, "#%d", f[i].q0) < 0)
+ rerror("can't set dot");
+ # look for match
+ if(write(afd, bufb, plen) == plen){
+ if(f[i].q0 == f[i].q1)
+ fprint(stdout, "%s:#%d\n", f[i].name, f[i].q0);
+ else
+ fprint(stdout, "%s:#%d,#%d\n", f[i].name, f[i].q0, f[i].q1);
+ }
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/i.b
@@ -1,0 +1,88 @@
+implement Ii;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Ii : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stderr = fildes(2);
+ main(argl);
+}
+
+prog := "i";
+
+include "findfile.b";
+
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, id : int;
+ nf, n, seq, rlen : int;
+ f, tf : array of File;
+ s, buf : string;
+
+ if(len argv != 2){
+ fprint(stderr, "usage: %s 'replacement'\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # sort back to original order, backwards
+ qsort(f, nf, BSCMP);
+
+ # change
+ id = -1;
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ ab := array of byte hd tl argv;
+ rlen = len ab;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "mark\nnomark\n", 12) != 12)
+ rerror("setting nomark");
+ }
+ if(fprint(afd, "#%d", f[i].q0) < 0)
+ rerror("writing address");
+ if(write(dfd, ab, rlen) != rlen)
+ rerror("writing replacement");
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/input.b
@@ -1,0 +1,84 @@
+ bin = bufio->fopen(stdin, bufio->OREAD);
+ seq = 0;
+ nf = 0;
+ f = nil;
+ while((s=bin.gets('\n')) != nil){
+ s = s[0:len s - 1];
+ (n, tf) = findfile(s);
+ if(n == 0)
+ errors("no files match input", s);
+ for(i=0; i<n; i++)
+ tf[i].seq = seq++;
+ off := f;
+ f = array[n+nf] of File;
+ if(f == nil)
+ rerror("out of memory");
+ if (off != nil) {
+ f[0:] = off[0:nf];
+ off = nil;
+ }
+ f[nf:] = tf[0:n];
+ nf += n;
+ tf = nil;
+ }
+
+ # sort by file name
+ qsort(f, nf, NCMP);
+
+ # convert to character positions if necessary
+ for(i=0; i<nf; i++){
+ f[i].ok = 1;
+ # see if it's easy
+ s = f[i].addr;
+ if(s[0]=='#'){
+ s = s[1:];
+ n = 0;
+ while(len s > 0 && '0'<=s[0] && s[0]<='9'){
+ n = n*10+(s[0]-'0');
+ s = s[1:];
+ }
+ f[i].q0 = n;
+ if(len s == 0){
+ f[i].q1 = n;
+ continue;
+ }
+ if(s[0] == ',') {
+ s = s[1:];
+ n = 0;
+ while(len s > 0 && '0'<=s[0] && s[0]<='9'){
+ n = n*10+(s[0]-'0');
+ s = s[1:];
+ }
+ f[i].q1 = n;
+ if(len s == 0)
+ continue;
+ }
+ }
+ id = f[i].id;
+ buf = sprint("/chan/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/chan/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "addr=dot\n", 9) != 9)
+ rerror("setting address to dot");
+ ab := array of byte f[i].addr;
+ if(write(afd, ab, len ab) != len ab){
+ fprint(stderr, "%s: %s:%s is invalid address\n", prog, f[i].name, f[i].addr);
+ f[i].ok = 0;
+ afd = cfd = nil;
+ continue;
+ }
+ seek(afd, big 0, 0);
+ bbuf := array[2*12] of byte;
+ if(read(afd, bbuf, len bbuf) != 2*12)
+ rerror("reading address");
+ afd = cfd = nil;
+ buf = string bbuf;
+ bbuf = nil;
+ f[i].q0 = int buf;
+ f[i].q1 = int buf[12:];
+ }
--- /dev/null
+++ b/appl/acme/acme/edit/src/mkfile
@@ -1,0 +1,23 @@
+<../../../../../mkconfig
+
+TARG=\
+ a.dis\
+ c.dis\
+ d.dis\
+ e.dis\
+ g.dis\
+ i.dis\
+ p.dis\
+ x.dis\
+ pipe.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ sh.m\
+ sys.m\
+ draw.m\
+
+DISBIN=$ROOT/acme/edit
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/acme/acme/edit/src/p.b
@@ -1,0 +1,109 @@
+implement Pp;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Pp : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stdout, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+include "findfile.b";
+
+prog := "p";
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, id : int;
+ m, nr, nf, n, nflag, seq : int;
+ f, tf : array of File;
+ buf, s : string;
+
+ nflag = 0;
+ if(len argv==2 && hd tl argv == "-n"){
+ argv = tl argv;
+ nflag = 1;
+ }
+ if(len argv != 1){
+ fprint(stderr, "usage: %s [-n]\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # sort back to original order
+ qsort(f, nf, SCMP);
+
+ id = -1;
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ }
+ if(nflag){
+ if(f[i].q1 > f[i].q0)
+ fprint(stdout, "%s:#%d,#%d: ", f[i].name, f[i].q0, f[i].q1);
+ else
+ fprint(stdout, "%s:#%d: ", f[i].name, f[i].q0);
+ }
+ m = f[i].q0;
+ while(m < f[i].q1){
+ if(fprint(afd, "#%d", m) < 0){
+ fprint(stderr, "%s: %s:%s is invalid address\n", prog, f[i].name, f[i].addr);
+ continue;
+ }
+ bbuf := array[512] of byte;
+ n = read(dfd, bbuf, len buf);
+ nr = nrunes(bbuf, n);
+ while(m+nr > f[i].q1){
+ do; while(n>0 && (int bbuf[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ write(stdout, bbuf, n);
+ m += nr;
+ }
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/pipe.b
@@ -1,0 +1,212 @@
+implement Pipe;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "sh.m";
+
+sys : Sys;
+bufio : Bufio;
+
+UTFmax, ORDWR, NEWFD, FD, open, read, write, seek, sprint, fprint, fildes, byte2char, pipe, dup, pctl : import sys;
+Iobuf : import bufio;
+
+Pipe : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stderr : ref FD;
+pipectxt : ref Draw->Context;
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ pipectxt = ctxt;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stderr = fildes(2);
+ main(argl);
+}
+
+include "findfile.b";
+
+prog := "pipe";
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, dfd, cfd : ref FD;
+ nf, nc, nr, npart : int;
+ p1, p2 : array of ref FD;
+ i, n, id, seq : int;
+ buf : string;
+ tmp, data : array of byte;
+ s : string;
+ r, s0 : int;
+ f, tf : array of File;
+ q, q0, q1 : int;
+ cpid : chan of int;
+ w, ok : int;
+
+ if(len argv < 2){
+ fprint(stderr, "usage: pipe command\n");
+ exit;
+ }
+
+include "input.b";
+
+ # sort back to original order
+ qsort(f, nf, SCMP);
+
+ # pipe
+ id = -1;
+ afd = nil;
+ cfd = nil;
+ dfd = nil;
+ tmp = array[8192+UTFmax] of byte;
+ if(tmp == nil)
+ error("malloc");
+ cpid = chan of int;
+ for(i=0; i<nf; i++){
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ if(write(cfd, array of byte "mark\nnomark\n", 12) != 12)
+ rerror("setting nomark");
+ }
+
+ if(fprint(afd, "#%ud", f[i].q0) < 0)
+ rerror("writing address");
+
+ q0 = f[i].q0;
+ q1 = f[i].q1;
+ # suck up data
+ data = array[(q1-q0)*UTFmax+1] of byte;
+ if(data == nil)
+ error("malloc failed\n");
+ s0 = 0;
+ q = q0;
+ bbuf := array[12] of byte;
+ while(q < q1){
+ nc = read(dfd, data[s0:], (q1-q)*UTFmax);
+ if(nc <= 0)
+ error("read error from acme");
+ seek(afd, big 0, 0);
+ if(read(afd, bbuf, 12) != 12)
+ rerror("reading address");
+ q = int string bbuf;
+ s0 += nc;
+ }
+ bbuf = nil;
+ s0 = 0;
+ for(nr=0; nr<q1-q0; nr++) {
+ (r, w, ok) = byte2char(data, s0);
+ s0 += w;
+ }
+
+ p1 = array[2] of ref FD;
+ p2 = array[2] of ref FD;
+ if(pipe(p1)<0 || pipe(p2)<0)
+ error("pipe");
+
+ spawn run(tl argv, p1[0], p2[1], cpid);
+ <-cpid;
+ p1[0] = nil;
+ p2[1] = nil;
+
+ spawn send(data, s0, p1[1]);
+ p1[1] = nil;
+
+ # put back data
+ if(fprint(afd, "#%d,#%d", q0, q1) < 0)
+ rerror("writing address");
+
+ npart = 0;
+ q1 = q0;
+ while((nc = read(p2[0], tmp[npart:], 8192)) > 0){
+ nc += npart;
+ s0 = 0;
+ while(s0 <= nc-UTFmax){
+ (r, w, ok) = byte2char(tmp, s0);
+ s0 += w;
+ q1++;
+ }
+ if(s0 > 0)
+ if(write(dfd, tmp, s0) != s0)
+ error("write error to acme");
+ npart = nc - s0;
+ tmp[0:] = tmp[s0:s0+npart];
+ }
+ p2[0] = nil;
+ if(npart){
+ s0 = 0;
+ while(s0 < npart){
+ (r, w, ok) = byte2char(tmp, s0);
+ s0 += w;
+ q1++;
+ }
+ if(write(dfd, tmp, npart) != npart)
+ error("write error to acme");
+ }
+ if(fprint(afd, "#%d,#%d", q0, q1) < 0)
+ rerror("writing address");
+ if(fprint(cfd, "dot=addr\n") < 0)
+ rerror("writing dot");
+ data = nil;
+ }
+}
+
+run(argv : list of string, p1, p2 : ref FD, c : chan of int)
+{
+ pctl(NEWFD, 0::1::2::p1.fd::p2.fd::nil);
+ dup(p1.fd, 0);
+ dup(p2.fd, 1);
+ c <-= pctl(0, nil);
+ exec(hd argv, argv);
+ fprint(stderr, "can't exec");
+ exit;
+}
+
+send(buf : array of byte, nbuf : int, fd : ref FD)
+{
+ if(write(fd, buf, nbuf) != nbuf)
+ error("write error to process");
+ fd = nil;
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sys->fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+
+ c->init(pipectxt, argl);
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/x.b
@@ -1,0 +1,123 @@
+implement Xx;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Xx : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stdout, stderr : ref FD;
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+include "findfile.b";
+
+prog := "x";
+bin : ref Iobuf;
+
+main(argv : list of string)
+{
+ afd, cfd, dfd : ref FD;
+ i, id, seq : int;
+ nf, n, plen : int;
+ addr, aq0, aq1, matched : int;
+ f, tf : array of File;
+ buf, s : string;
+ bbuf0 : array of byte;
+
+ if(len argv!=2 || len hd tl argv==0 || (hd tl argv)[0]!='/'){
+ fprint(stderr, "usage: %s '/regexp/'\n", prog);
+ exit;
+ }
+
+include "input.b";
+
+ # execute regexp
+ id = -1;
+ afd = nil;
+ dfd = nil;
+ cfd = nil;
+ for(i=0; i<nf; i++){
+ if(f[i].ok == 0)
+ continue;
+ if(f[i].id != id){
+ if(id > 0){
+ afd = cfd = dfd = nil;
+ }
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ afd = open(buf, ORDWR);
+ if(afd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ cfd = open(buf, ORDWR);
+ if(cfd == nil)
+ rerror(buf);
+ buf = sprint("/mnt/acme/%d/data", id);
+ dfd = open(buf, ORDWR);
+ if(dfd == nil)
+ rerror(buf);
+ }
+ bbuf0 = array of byte f[i].addr;
+ n = len bbuf0;
+ if(write(afd, bbuf0, n)!=n || fprint(cfd, "limit=addr\n")<0){
+ buf = sprint("%s:%s is invalid limit", f[i].name, f[i].addr);
+ rerror(buf);
+ }
+ if(fprint(afd, "#%d", f[i].q0) < 0)
+ rerror("can't set address");
+ if(fprint(cfd, "dot=addr") < 0)
+ rerror("can't unset dot");
+ addr = f[i].q0-1;
+ bbuf := array of byte hd tl argv;
+ plen = len bbuf;
+ matched = 0;
+ # scan for matches
+ for(;;){
+ if(write(afd, bbuf, plen) != plen)
+ break;
+ seek(afd, big 0, 0);
+ bbuf0 = array[2*12] of byte;
+ if(read(afd, bbuf0, len bbuf0) != 2*12)
+ rerror("reading address");
+ buf = string bbuf0;
+ bbuf0 = nil;
+ aq0 = int buf;
+ aq1 = int buf[12:];
+ if(matched && aq1==aq0 && addr==aq1){ # repeated null match; advance
+ matched = 0;
+ addr++;
+ if(addr > f[i].q1)
+ break;
+ if(fprint(afd, "#%d", addr) < 0)
+ rerror("writing address");
+ continue;
+ }
+ matched = 1;
+ if(aq0<addr || aq0>=f[i].q1 || aq1>f[i].q1)
+ break;
+ addr = aq1;
+ if(aq0 == aq1)
+ fprint(stdout, "%s:#%d\n", f[i].name, aq0);
+ else
+ fprint(stdout, "%s:#%d,#%d\n", f[i].name, aq0, aq1);
+ }
+ }
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/edit/src/xxx.b
@@ -1,0 +1,327 @@
+implement Ee;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+sys : Sys;
+bufio : Bufio;
+
+ORDWR, FD, open, read, write, seek, sprint, fprint, fildes, byte2char : import sys;
+Iobuf : import bufio;
+
+Ee : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+stdin, stdout, stderr : ref FD;
+
+init(ctxt : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ stdin = fildes(0);
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+File : adt {
+ id : int;
+ seq : int;
+ ok : int;
+ q0, q1 : int;
+ name : string;
+ addr : string;
+};
+
+BSCMP, SCMP, NCMP, FCMP : con iota;
+
+indexfile := "/usr/jrf/tmp/index";
+
+dfd: ref Sys->FD;
+debug(s : string)
+{
+ if (dfd == nil)
+ dfd = sys->create("/usr/jrf/acme/debugedit", Sys->OWRITE, 8r600);
+ sys->fprint(dfd, "%s", s);
+}
+
+error(s : string)
+{
+debug(sys->sprint("error %s\n", s));
+ fprint(stderr, "%s: %s\n", prog, s);
+ exit;
+}
+
+errors(s, t : string)
+{
+debug(sys->sprint("errors %s %s\n", s, t));
+ fprint(stderr, "%s: %s %s\n", prog, s, t);
+ exit;
+}
+
+rerror(s : string)
+{
+debug(sys->sprint("rerror %s\n", s));
+ fprint(stderr, "%s: %s: %r\n", prog, s);
+ exit;
+}
+
+strcmp(s, t : string) : int
+{
+ if (s < t) return -1;
+ if (s > t) return 1;
+ return 0;
+}
+
+strstr(s, t : string) : int
+{
+ if (t == nil)
+ return 0;
+ n := len t;
+ if (n > len s)
+ return -1;
+ e := len s - n;
+ for (p := 0; p <= e; p++)
+ if (s[p:p+n] == t)
+ return p;
+ return -1;
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n, r, b, ok : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) = byte2char(s, i);
+ i += b;
+ }
+ return n;
+}
+
+index : ref Iobuf;
+
+findfile(pat : string) : (int, array of File)
+{
+ line, pat1, pat2 : string;
+ colon, blank : int;
+ n : int;
+ f : array of File;
+
+ if(index == nil)
+ index = bufio->open(indexfile, bufio->OREAD);
+ else
+ index.seek(0, 0);
+ if(index == nil)
+ rerror(indexfile);
+ for(colon=0; colon < len pat && pat[colon]!=':'; colon++)
+ ;
+ if (colon == len pat) {
+ pat1 = pat;
+ pat2 = ".";
+ }
+ else {
+ pat1 = pat[0:colon];
+ pat2 = pat[colon+1:];
+ }
+ n = 0;
+ f = nil;
+ while((line=index.gets('\n')) != nil){
+ if(len line < 5*12)
+ rerror("bad index file format");
+ line = line[0:len line - 1];
+ for(blank=5*12; blank < len line && line[blank]!=' '; blank++)
+ ;
+ if (blank < len line)
+ line = line[0:blank];
+ if(strcmp(line[5*12:], pat1) == 0){
+ # exact match: take that
+ f = nil; # should also free t->addr's
+ f = array[1] of File;
+ if(f == nil)
+ rerror("out of memory");
+ f[0].id = int line;
+ f[0].name = line[5*12:];
+ f[0].addr = pat2;
+ n = 1;
+ break;
+ }
+ if(strstr(line[5*12:], pat1) >= 0){
+ # partial match: add to list
+ off := f;
+ f = array[n+1] of File;
+ if(f == nil)
+ rerror("out of memory");
+ f[0:] = off[0:n];
+ off = nil;
+ f[n].id = int line;
+ f[n].name = line[5*12:];
+ f[n].addr = pat2;
+ n++;
+ }
+ }
+ return (n, f);
+}
+
+bscmp(a : File, b : File) : int
+{
+ return b.seq - a.seq;
+}
+
+scmp(a : File, b : File) : int
+{
+ return a.seq - b.seq;
+}
+
+ncmp(a : File, b : File) : int
+{
+ return strcmp(a.name, b.name);
+}
+
+fcmp(a : File, b : File) : int
+{
+ x : int;
+
+ if (a.name < b.name)
+ return -1;
+ if (a.name > b.name)
+ return 1;
+ x = a.q0 - b.q0;
+ if(x != 0)
+ return x;
+ return a.q1-b.q1;
+}
+
+gencmp(a : File, b : File, c : int) : int
+{
+ if (c == BSCMP)
+ return bscmp(a, b);
+ if (c == SCMP)
+ return scmp(a, b);
+ if (c == NCMP)
+ return ncmp(a, b);
+ if (c == FCMP)
+ return fcmp(a, b);
+ return 0;
+}
+
+qsort(a : array of File, n : int, c : int)
+{
+ i, j : int;
+ t : File;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && gencmp(a[i], a[0], c) < 0);
+ do
+ j--;
+ while(j > 0 && gencmp(a[j], a[0], c) > 0);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j, c);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n, c);
+ n = j;
+ }
+ }
+}
+
+prog := "e";
+
+main(argv : list of string)
+{
+ afd, cfd : ref FD;
+ i, id : int;
+ buf : string;
+ nf, n, lines, l0, l1 : int;
+ f, tf : array of File;
+
+ if(len argv < 2){
+debug(sys->sprint("usage\n"));
+ fprint(stderr, "usage: %s 'file[:address]' ...\n", prog);
+ exit;
+ }
+ nf = 0;
+ f = nil;
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ (n, tf) = findfile(hd argv);
+ if(n == 0)
+ errors("no files match pattern", hd argv);
+ oldf := f;
+ f = array[n+nf] of File;
+ if(f == nil)
+ rerror("out of memory");
+ if (oldf != nil) {
+ f[0:] = oldf[0:nf];
+ oldf = nil;
+ }
+ f[nf:] = tf[0:n];
+ nf += n;
+ tf = nil;
+ }
+debug(sys->sprint("nf=%d\n", nf));
+ # convert to character positions
+ for(i=0; i<nf; i++){
+ id = f[i].id;
+ buf = sprint("/mnt/acme/%d/addr", id);
+ # afd = open(buf, ORDWR);
+ # if(afd == nil)
+ # rerror(buf);
+ buf = sprint("/mnt/acme/%d/ctl", id);
+ # cfd = open(buf, ORDWR);
+ # if(cfd == nil)
+ # rerror(buf);
+ if(0 && write(cfd, array of byte "addr=dot\n", 9) != 9)
+ rerror("setting address to dot");
+ ab := array of byte f[i].addr;
+ if(0 && write(afd, ab, len ab) != len ab){
+ fprint(stderr, "%s: %s:%s is invalid address\n", prog, f[i].name, f[i].addr);
+ f[i].ok = 0;
+ afd = nil;
+ cfd = nil;
+ continue;
+ }
+ # seek(afd, 0, 0);
+ ab = array[24] of byte;
+ if(0 && read(afd, ab, len ab) != 2*12)
+ rerror("reading address");
+ afd = nil;
+ cfd = nil;
+ # buf = string ab;
+ ab = nil;
+ f[i].q0 = 0; # int buf;
+ f[i].q1 = 5; # int buf[12:];
+ f[i].ok = 1;
+debug(sys->sprint("q0=%d q1=%d\n", f[i].q0, f[i].q1));
+ }
+
+ # sort
+ # qsort(f, nf, FCMP);
+
+ for(i=0; i<nf; i++){
+ if(f[i].ok)
+ {
+ if(f[i].q1 > f[i].q0)
+ fprint(stdout, "%s:#%d,#%d\n", f[i].name, f[i].q0, f[i].q1);
+ else
+ fprint(stdout, "%s:#%d\n", f[i].name, f[i].q0);
+ }
+ }
+debug("e exiting\n");
+ exit;
+}
--- /dev/null
+++ b/appl/acme/acme/mail/guide
@@ -1,0 +1,5 @@
+Mail /mail/box/$user/stored
+Mailpop3
+mkbox /mail/box/$user/new_box
+mail -'x' someaddress
--- /dev/null
+++ b/appl/acme/acme/mail/mkbox.b
@@ -1,0 +1,25 @@
+implement Mkbox;
+
+include "sys.m";
+include "draw.m";
+
+sys : Sys;
+
+FD : import sys;
+
+Mkbox : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+init(nil : ref Draw->Context, argl : list of string)
+{
+ sys = load Sys Sys->PATH;
+ for (argl = tl argl; argl != nil; argl = tl argl) {
+ nm := hd argl;
+ (ok, dir) := sys->stat(nm);
+ if (ok < 0) {
+ fd := sys->create(nm, Sys->OREAD, 8r600);
+ fd = nil;
+ }
+ }
+}
--- /dev/null
+++ b/appl/acme/acme/mail/mkfile
@@ -1,0 +1,34 @@
+<../../../../mkconfig
+
+BIN=$ROOT/acme/mail
+
+DIRS=\
+ src\
+
+TARG=\
+ guide\
+ readme\
+ mkbox.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+
+BINTARG=${TARG:%=$BIN/%}
+
+DISBIN=$ROOT/acme/mail
+
+all:V: $TARG
+
+install:V: $BINTARG
+
+$BIN/guide : guide
+ rm -f $BIN/guide && cp guide $BIN/guide
+
+$BIN/readme : readme
+ rm -f $BIN/readme && cp readme $BIN/readme
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/appl/acme/acme/mail/readme
@@ -1,0 +1,29 @@
+Mail is the single program in this directory. Its argument specifies
+the mail box to read, default /mail/box/$user/mbox.
+For example, running
+ Mail /mail/box/$user/stored
+(a line in the guide file) looks at saved mail.
+
+Mail maintains a window containing headers for all the
+messages in the mailbox and monitors the mailbox for new messages.
+Using button 3 to indicate a message number opens
+a window on that message. commands in the mailbox window are
+ Put Write the mailbox back to the file (never done automatically)
+ Mail Make a new message window ready to mail someone.
+ Takes argument names analogously to acme's New.
+ Del Exit Mail, after checking that mailbox isn't modified.
+New messages appear at the top of the window and are highlighted upon arrival.
+(The messages are numbered oldest to newest, the opposite of regular mail.)
+
+Message windows have a simple format: the first line, up to the first tab or newline,
+holds the sender or, when sending, the addressee. Edit the line to change who the
+message goes to. Message windows contain the commands
+ Reply Make a new window to compose a reply to this message
+ Delmesg Delete the message from the screen and from the mailbox
+ Del Delete the window, leaving the message in the mailbox
+ Post Send the message to the addressee
+ Save Save to the named mailbox, default/mail/box/$user/stored
+Save takes a full file name; if that name has no slashes, the file is taken
+to be in /mail/box/$user and must already exist. Use mkbox in the guide to
+create target mailboxes in /mail/box/$user.
+Reply and mail windows contain an obvious subset of the commands.
--- /dev/null
+++ b/appl/acme/acme/mail/src/Mail.b
@@ -1,0 +1,1715 @@
+implement mail;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "daytime.m";
+include "sh.m";
+
+mail : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+daytime : Daytime;
+
+OREAD, OWRITE, ORDWR, NEWFD, FORKFD, FORKENV, NEWPGRP, UTFmax : import Sys;
+FD, Dir : import sys;
+fprint, sprint, sleep, create, open, read, write, remove, stat, fstat, fwstat, fildes, pctl, pipe, dup, byte2char : import sys;
+Context : import Draw;
+EOF : import Bufio;
+Iobuf : import bufio;
+time : import daytime;
+
+DIRLEN : con 116;
+PNPROC, PNGROUP : con iota;
+False : con 0;
+True : con 1;
+EVENTSIZE : con 256;
+Runeself : con 16r80;
+OCEXEC : con 0;
+CHEXCL : con 0; # 16r20000000; # for the moment
+CHAPPEND : con 0; # 16r40000000;
+
+MAILDIR : con "/mail";
+
+Win : adt {
+ winid : int;
+ addr : ref FD;
+ body : ref Iobuf;
+ ctl : ref FD;
+ data : ref FD;
+ event : ref FD;
+ buf : array of byte;
+ bufp : int;
+ nbuf : int;
+
+ wnew : fn() : ref Win;
+ wwritebody : fn(w : self ref Win, s : string);
+ wread : fn(w : self ref Win, m : int, n : int) : string;
+ wclean : fn(w : self ref Win);
+ wname : fn(w : self ref Win, s : string);
+ wdormant : fn(w : self ref Win);
+ wevent : fn(w : self ref Win, e : ref Event);
+ wshow : fn(w : self ref Win);
+ wtagwrite : fn(w : self ref Win, s : string);
+ wwriteevent : fn(w : self ref Win, e : ref Event);
+ wslave : fn(w : self ref Win, c : chan of Event);
+ wreplace : fn(w : self ref Win, s : string, t : string);
+ wselect : fn(w : self ref Win, s : string);
+ wsetdump : fn(w : self ref Win, s : string, t : string);
+ wdel : fn(w : self ref Win, n : int) : int;
+ wreadall : fn(w : self ref Win) : string;
+
+ ctlwrite : fn(w : self ref Win, s : string);
+ getec : fn(w : self ref Win) : int;
+ geten : fn(w : self ref Win) : int;
+ geter : fn(w : self ref Win, s : array of byte) : (int, int);
+ openfile : fn(w : self ref Win, s : string) : ref FD;
+ openbody : fn(w : self ref Win, n : int);
+};
+
+Mesg : adt {
+ w : ref Win;
+ id : int;
+ hdr : string;
+ realhdr : string;
+ replyto : string;
+ text : string;
+ subj : string;
+ next : cyclic ref Mesg;
+ lline1 : int;
+ box : cyclic ref Box;
+ isopen : int;
+ posted : int;
+
+ read : fn(b : ref Box) : ref Mesg;
+ open : fn(m : self ref Mesg);
+ slave : fn(m : self ref Mesg);
+ free : fn(m : self ref Mesg);
+ save : fn(m : self ref Mesg, s : string);
+ mkreply : fn(m : self ref Mesg);
+ mkmail : fn(b : ref Box, s : string);
+ putpost : fn(m : self ref Mesg, e : ref Event);
+
+ command : fn(m : self ref Mesg, s : string) : int;
+ send : fn(m : self ref Mesg);
+};
+
+Box : adt {
+ w : ref Win;
+ nm : int;
+ readonly : int;
+ m : cyclic ref Mesg;
+ file : string;
+ io : ref Iobuf;
+ clean : int;
+ leng : big;
+ cdel : chan of ref Mesg;
+ cevent : chan of Event;
+ cmore : chan of int;
+
+ line : string;
+ peekline : string;
+
+ read : fn(s : string, n : int) : ref Box;
+ readmore : fn(b : self ref Box);
+ readline : fn(b : self ref Box) : string;
+ unreadline : fn(b : self ref Box);
+ slave : fn(b : self ref Box);
+ mopen : fn(b : self ref Box, n : int);
+ rewrite : fn(b : self ref Box);
+ mdel : fn(b : self ref Box, m : ref Mesg);
+ event : fn(b : self ref Box, e : ref Event);
+
+ command : fn(b : self ref Box, s : string) : int;
+};
+
+Event : adt {
+ c1 : int;
+ c2 : int;
+ q0 : int;
+ q1 : int;
+ flag : int;
+ nb : int;
+ nr : int;
+ b : array of byte;
+ r : array of int;
+};
+
+Lock : adt {
+ cnt : int;
+ chann : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+};
+
+Ref : adt {
+ l : ref Lock;
+ cnt : int;
+
+ init : fn() : ref Ref;
+ inc : fn(r : self ref Ref) : int;
+};
+
+mbox : ref Box;
+mboxfile : string;
+usermboxfile : string;
+usermboxdir : string;
+lockfile : string;
+user : string;
+date : string;
+mailctxt : ref Context;
+stdout, stderr : ref FD;
+
+killing : int = 0;
+
+init(ctxt : ref Context, argl : list of string)
+{
+ mailctxt = ctxt;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main(argl);
+}
+
+dlock : ref Lock;
+dfd : ref Sys->FD;
+
+debug(s : string)
+{
+ if (dfd == nil) {
+ dfd = sys->create("/usr/jrf/acme/debugmail", Sys->OWRITE, 8r600);
+ dlock = Lock.init();
+ }
+ if (dfd == nil)
+ return;
+ dlock.lock();
+ sys->fprint(dfd, "%s", s);
+ dlock.unlock();
+}
+
+postnote(t : int, pid : int, note : string) : int
+{
+ fd := open("#p/" + string pid + "/ctl", OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ fprint(fd, "%s", note);
+ fd = nil;
+ return 0;
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(mailctxt, argl);
+}
+
+swrite(fd : ref FD, s : string) : int
+{
+ ab := array of byte s;
+ m := len ab;
+ p := write(fd, ab, m);
+ if (p == m)
+ return len s;
+ if (p <= 0)
+ return p;
+ return 0;
+}
+
+strchr(s : string, c : int) : int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strrchr(s : string, c : int) : int
+{
+ for (i := len s - 1; i >= 0; i--)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strtoi(s : string) : (int, int)
+{
+ m := 0;
+ neg := 0;
+ t := 0;
+ ls := len s;
+ while (t < ls && (s[t] == ' ' || s[t] == '\t'))
+ t++;
+ if (t < ls && s[t] == '+')
+ t++;
+ else if (t < ls && s[t] == '-') {
+ neg = 1;
+ t++;
+ }
+ while (t < ls && (s[t] >= '0' && s[t] <= '9')) {
+ m = 10*m + s[t]-'0';
+ t++;
+ }
+ if (neg)
+ m = -m;
+ return (m, t);
+}
+
+access(s : string) : int
+{
+ fd := open(s, 0);
+ if (fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+newevent() : ref Event
+{
+ e := ref Event;
+ e.b = array[EVENTSIZE*UTFmax+1] of byte;
+ e.r = array[EVENTSIZE+1] of int;
+ return e;
+}
+
+newmesg() : ref Mesg
+{
+ m := ref Mesg;
+ m.id = m.lline1 = m.isopen = m.posted = 0;
+ return m;
+}
+
+lc, uc : chan of ref Lock;
+
+initlock()
+{
+ lc = chan of ref Lock;
+ uc = chan of ref Lock;
+ spawn lockmgr();
+}
+
+lockmgr()
+{
+ l : ref Lock;
+
+ for (;;) {
+ alt {
+ l = <- lc =>
+ if (l.cnt++ == 0)
+ l.chann <-= 1;
+ l = <- uc =>
+ if (--l.cnt > 0)
+ l.chann <-= 1;
+ }
+ }
+}
+
+Lock.init() : ref Lock
+{
+ return ref Lock(0, chan of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ lc <-= l;
+ <- l.chann;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ uc <-= l;
+}
+
+Ref.init() : ref Ref
+{
+ r := ref Ref;
+ r.l = Lock.init();
+ r.cnt = 0;
+ return r;
+}
+
+Ref.inc(r : self ref Ref) : int
+{
+ r.l.lock();
+ i := r.cnt;
+ r.cnt++;
+ r.l.unlock();
+ return i;
+}
+
+error(s : string)
+{
+ if(s != nil)
+ fprint(stderr, "mail: %s\n", s);
+ rmlockfile();
+# debug(sprint("error %s\n", s));
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ exit;
+}
+
+rmlockfile()
+{
+ if (lockfile != nil) {
+ remove(lockfile);
+ lockfile = nil;
+ }
+}
+
+#
+# try opening a lock file. If it doesn't exist try creating it.
+#
+openlockfile(path : string) : ref FD
+{
+ try : int;
+ fd : ref FD;
+
+ try = 0;
+ for(;;){
+ # fd = open(path, OWRITE);
+ # if(fd!=nil || ++try>3)
+ # return fd;
+ if(++try > 3)
+ return fd;
+ (ok, d) := stat(path);
+ if(ok >= 0)
+ sleep(1000);
+ else{
+ fd = create(path, OWRITE, CHEXCL|8r666);
+ if(fd != nil){
+ (ok, d) = fstat(fd);
+ if(ok >= 0){
+ d.mode |= CHEXCL|8r666;
+ fwstat(fd, d);
+ }
+ return fd;
+ }
+ break;
+ }
+ }
+ return nil;
+}
+
+tryopen(s : string, mode : int) : ref FD
+{
+ fd : ref FD;
+ try : int;
+
+ for(try=0; try<3; try++){
+ fd = open(s, mode);
+ if(fd != nil)
+ return fd;
+ sleep(1000);
+ }
+ return nil;
+}
+
+run(argv : list of string, c : chan of int, p0 : ref FD)
+{
+ # pctl(FORKFD|NEWPGRP, nil); # had RFMEM
+ pctl(FORKENV|NEWFD|NEWPGRP, 0::1::2::p0.fd::nil);
+ c <-= pctl(0, nil);
+ dup(p0.fd, 0);
+ p0 = nil;
+ exec(hd argv, argv);
+ exit;
+}
+
+getuser() : string
+{
+ fd := open("/dev/user", OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[128] of byte;
+ n := read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0:n];
+}
+
+main(argv : list of string)
+{
+ fd : ref FD;
+ readonly : int;
+ buf : string;
+
+ initlock();
+ initreply();
+ date = time();
+ if(date==nil)
+ error("can't get current time");
+ user = getuser();
+ if(user == nil)
+ user = "Wile.E.Coyote";
+ usermboxdir = MAILDIR + "/box/" + user + "/";
+ usermboxfile = MAILDIR + "/box/" + user + "/mbox";
+ if(len argv > 1)
+ mboxfile = hd tl argv;
+ else
+ mboxfile = usermboxfile;
+
+ fd = nil;
+ readonly = False;
+ if(mboxfile == usermboxfile){
+ buf = MAILDIR + "/box/" + user + "/L.reading";
+ fd = openlockfile(buf);
+ if(fd == nil){
+ fprint(stderr, "Mail: %s in use; opened read-only\n", mboxfile);
+ readonly = True;
+ }
+ else
+ lockfile = buf;
+ }
+ mbox = mbox.read(mboxfile, readonly);
+ spawn timeslave(fd, mbox, mbox.cmore);
+ mbox.slave();
+ error(nil);
+}
+
+timeslave(rlock : ref FD, b : ref Box, c : chan of int)
+{
+ buf := array[DIRLEN] of byte;
+ for(;;){
+ sleep(30*1000);
+ if(rlock != nil && write(rlock, buf, 0)<0)
+ error("can't maintain L.reading: %r");
+ (ok, d) := stat(mboxfile);
+ if (ok >= 0 && d.length > b.leng)
+ c <-= 0;
+ }
+}
+
+Win.wnew() : ref Win
+{
+ w := ref Win;
+ buf := array[12] of byte;
+ w.ctl = open("/chan/new/ctl", ORDWR);
+ if(w.ctl==nil || read(w.ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ w.ctlwrite("noscroll\n");
+ w.winid = int string buf;
+ w.event = w.openfile("event");
+ w.addr = nil; # will be opened when needed
+ w.body = nil;
+ w.data = nil;
+ w.bufp = w.nbuf = 0;
+ w.buf = array[512] of byte;
+ return w;
+}
+
+Win.openfile(w : self ref Win, f : string) : ref FD
+{
+ buf := sprint("/chan/%d/%s", w.winid, f);
+ fd := open(buf, ORDWR|OCEXEC);
+ if(fd == nil)
+ error(sprint("can't open window %s file: %r", f));
+ return fd;
+}
+
+Win.openbody(w : self ref Win, mode : int)
+{
+ buf := sprint("/chan/%d/body", w.winid);
+ w.body = bufio->open(buf, mode|OCEXEC);
+ if(w.body == nil)
+ error("can't open window body file: %r");
+}
+
+Win.wwritebody(w : self ref Win, s : string)
+{
+ n := len s;
+ if(w.body == nil)
+ w.openbody(OWRITE);
+ if(w.body.puts(s) != n)
+ error("write error to window: %r");
+}
+
+Win.wreplace(w : self ref Win, addr : string, repl : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ if(swrite(w.addr, addr) < 0){
+ fprint(stderr, "mail: warning: bad address %s:%r\n", addr);
+ return;
+ }
+ if(swrite(w.data, repl) != len repl)
+ error("writing data: %r");
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) := byte2char(s, i);
+ if (!ok)
+ error("help needed in nrunes()");
+ i += b;
+ }
+ return n;
+}
+
+Win.wread(w : self ref Win, q0 : int, q1 : int) : string
+{
+ m, n, nr : int;
+ s, buf : string;
+ b : array of byte;
+
+ b = array[256] of byte;
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ s = nil;
+ m = q0;
+ while(m < q1){
+ buf = sprint("#%d", m);
+ if(swrite(w.addr, buf) != len buf)
+ error("writing addr: %r");
+ n = read(w.data, b, len b);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(b, n);
+ while(m+nr >q1){
+ do; while(n>0 && (int b[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ s += string b[0:n];
+ m += nr;
+ }
+ return s;
+}
+
+Win.wshow(w : self ref Win)
+{
+ w.ctlwrite("show\n");
+}
+
+Win.wsetdump(w : self ref Win, dir : string, cmd : string)
+{
+ t : string;
+
+ if(dir != nil){
+ t = "dumpdir " + dir + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+ if(cmd != nil){
+ t = "dump " + cmd + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+}
+
+Win.wselect(w : self ref Win, addr : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(swrite(w.addr, addr) < 0)
+ error("writing addr");
+ w.ctlwrite("dot=addr\n");
+}
+
+Win.wtagwrite(w : self ref Win, s : string)
+{
+ fd : ref FD;
+
+ fd = w.openfile("tag");
+ if(swrite(fd, s) != len s)
+ error("tag write: %r");
+ fd = nil;
+}
+
+Win.ctlwrite(w : self ref Win, s : string)
+{
+ if(swrite(w.ctl, s) != len s)
+ error("write error to ctl file: %r");
+}
+
+Win.wdel(w : self ref Win, sure : int) : int
+{
+ if (w == nil)
+ return False;
+ if(sure)
+ swrite(w.ctl, "delete\n");
+ else if(swrite(w.ctl, "del\n") != 4)
+ return False;
+ w.wdormant();
+ w.ctl = nil;
+ w.event = nil;
+ return True;
+}
+
+Win.wname(w : self ref Win, s : string)
+{
+ w.ctlwrite("name " + s + "\n");
+}
+
+Win.wclean(w : self ref Win)
+{
+ if(w.body != nil)
+ w.body.flush();
+ w.ctlwrite("clean\n");
+}
+
+Win.wdormant(w : self ref Win)
+{
+ w.addr = nil;
+ if(w.body != nil){
+ w.body.close();
+ w.body = nil;
+ }
+ w.data = nil;
+}
+
+Win.getec(w : self ref Win) : int
+{
+ if(w.nbuf == 0){
+ w.nbuf = read(w.event, w.buf, len w.buf);
+ if(w.nbuf <= 0 && !killing) {
+ error("event read error: %r");
+ }
+ w.bufp = 0;
+ }
+ w.nbuf--;
+ return int w.buf[w.bufp++];
+}
+
+Win.geten(w : self ref Win) : int
+{
+ n, c : int;
+
+ n = 0;
+ while('0'<=(c=w.getec()) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+Win.geter(w : self ref Win, buf : array of byte) : (int, int)
+{
+ r, m, n, ok : int;
+
+ r = w.getec();
+ buf[0] = byte r;
+ n = 1;
+ if(r >= Runeself) {
+ for (;;) {
+ (r, m, ok) = byte2char(buf[0:n], 0);
+ if (m > 0)
+ return (r, n);
+ buf[n++] = byte w.getec();
+ }
+ }
+ return (r, n);
+}
+
+Win.wevent(w : self ref Win, e : ref Event)
+{
+ i, nb : int;
+
+ e.c1 = w.getec();
+ e.c2 = w.getec();
+ e.q0 = w.geten();
+ e.q1 = w.geten();
+ e.flag = w.geten();
+ e.nr = w.geten();
+ if(e.nr > EVENTSIZE)
+ error("event string too long");
+ e.nb = 0;
+ for(i=0; i<e.nr; i++){
+ (e.r[i], nb) = w.geter(e.b[e.nb:]);
+ e.nb += nb;
+ }
+ e.r[e.nr] = 0;
+ e.b[e.nb] = byte 0;
+ c := w.getec();
+ if(c != '\n')
+ error("event syntax 2");
+}
+
+Win.wslave(w : self ref Win, ce : chan of Event)
+{
+ e : ref Event;
+
+ e = newevent();
+ for(;;){
+ w.wevent(e);
+ ce <-= *e;
+ }
+}
+
+Win.wwriteevent(w : self ref Win, e : ref Event)
+{
+ fprint(w.event, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+}
+
+Win.wreadall(w : self ref Win) : string
+{
+ s, t : string;
+
+ if(w.body != nil)
+ w.body.close();
+ w.openbody(OREAD);
+ s = nil;
+ while ((t = w.body.gets('\n')) != nil)
+ s += t;
+ w.body.close();
+ w.body = nil;
+ return s;
+}
+
+ignored : int;
+
+None,Unknown,Ignore,CC,From,ReplyTo,Sender,Subject,Re,To, Date : con iota;
+NHeaders : con 200;
+
+Hdrs : adt {
+ name : string;
+ typex : int;
+};
+
+
+hdrs := array[NHeaders+1] of {
+ Hdrs ( "CC:", CC ),
+ Hdrs ( "From:", From ),
+ Hdrs ( "Reply-To:", ReplyTo ),
+ Hdrs ( "Sender:", Sender ),
+ Hdrs ( "Subject:", Subject ),
+ Hdrs ( "Re:", Re ),
+ Hdrs ( "To:", To ),
+ Hdrs ( "Date:", Date),
+ * => Hdrs ( "", 0 ),
+};
+
+StRnCmP(s : string, t : string, n : int) : int
+{
+ c, d, i, j : int;
+
+ i = j = 0;
+ if (len s < n || len t < n)
+ return -1;
+ while(n > 0){
+ c = s[i++];
+ d = t[j++];
+ --n;
+ if(c != d){
+ if('a'<=c && c<='z')
+ c -= 'a'-'A';
+ if('a'<=d && d<='z')
+ d -= 'a'-'A';
+ if(c != d)
+ return c-d;
+ }
+ }
+ return 0;
+}
+
+ignore()
+{
+ b : ref Iobuf;
+ s : string;
+ i : int;
+
+ ignored = True;
+ b = bufio->open(MAILDIR + "/lib/ignore", OREAD);
+ if(b == nil)
+ return;
+ for(i=0; hdrs[i].name != nil; i++)
+ ;
+ while((s = b.gets('\n')) != nil){
+ s = s[0:len s - 1];
+ hdrs[i].name = s;
+ hdrs[i].typex = Ignore;
+ if(++i >= NHeaders){
+ fprint(stderr, "%s/lib/ignore has more than %d headers\n", MAILDIR, NHeaders);
+ break;
+ }
+ }
+ b.close();
+}
+
+readhdr(b : ref Box) : (string, int)
+{
+ i, j, n, m, typex : int;
+ s, t : string;
+
+ {
+ if(!ignored)
+ ignore();
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ raise("e");
+ for(i=0; i<n; i++){
+ j = s[i];
+ if(i>0 && j == ':')
+ break;
+ if(j<'!' || '~'<j){
+ b.unreadline();
+ raise("e");
+ }
+ }
+ typex = Unknown;
+ for(i=0; hdrs[i].name != nil; i++){
+ j = len hdrs[i].name;
+ if(StRnCmP(hdrs[i].name, s, j) == 0){
+ typex = hdrs[i].typex;
+ break;
+ }
+ }
+ # scan for multiple sublines
+ for(;;){
+ t = b.readline();
+ m = len t;
+ if(m<=0 || (t[0]!=' ' && t[0]!='\t')){
+ b.unreadline();
+ break;
+ }
+ # absorb
+ s += t;
+ }
+ return(s, typex);
+ }
+ exception{
+ "*" =>
+ return (nil, None);
+ }
+}
+
+Mesg.read(b : ref Box) : ref Mesg
+{
+ m : ref Mesg;
+ s : string;
+ n, typex : int;
+
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ return nil;
+
+{
+ if(n < 5 || s[0:5] !="From ")
+ raise("e");
+ m = newmesg();
+ m.realhdr = s;
+ # toss 'From '
+ s = s[5:];
+ n -= 5;
+ # toss spaces/tabs
+ while (n > 0 && (s[0] == ' ' || s[0] == '\t')) {
+ s = s[1:];
+ n--;
+ }
+ m.hdr = s;
+ # convert first blank to tab
+ s0 := strchr(m.hdr, ' ');
+ if(s0 >= 0){
+ m.hdr[s0] = '\t';
+ # drop trailing seconds, time zone, and year if match local year
+ t := n-6;
+ if(t <= 0)
+ raise("e");
+ if(m.hdr[t:n-1] == date[23:]){
+ m.hdr = m.hdr[0:t] + "\n"; # drop year for sure
+ t = -1;
+ s1 := strchr(m.hdr[s0:], ':');
+ if(s1 >= 0)
+ t = strchr(m.hdr[s0+s1+1:], ':');
+ if(t >= 0) # drop seconds and time zone
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ else{ # drop time zone
+ t = strchr(m.hdr[s0+s1+1:], ' ');
+ if(t >= 0)
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ }
+ n = len m.hdr;
+ }
+ }
+ m.lline1 = n;
+ m.text = nil;
+ # read header
+loop:
+ for(;;){
+ (s, typex) = readhdr(b);
+ case(typex){
+ None =>
+ break loop;
+ ReplyTo =>
+ m.replyto = s[9:];
+ break;
+ From =>
+ if(m.replyto == nil)
+ m.replyto = s[5:];
+ break;
+ Subject =>
+ m.subj = s[8:];
+ break;
+ Re =>
+ m.subj = s[3:];
+ break;
+ Date =>
+ break;
+ }
+ m.realhdr += s;
+ if(typex != Ignore)
+ m.hdr += s;
+ }
+ # read body
+ for(;;){
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ break;
+ if(len s >= 5 && s[0:5] == "From "){
+ b.unreadline();
+ break;
+ }
+ m.text += s;
+ }
+ # remove trailing "morF\n"
+ l := len m.text;
+ if(l>6 && m.text[l-6:] == "\nmorF\n")
+ m.text = m.text[0:l-5];
+ m.box = b;
+ return m;
+}
+exception{
+ "*" =>
+ error("malformed header " + s);
+ return nil;
+}
+}
+
+Mesg.mkmail(b : ref Box, hdr : string)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ r.hdr = hdr + "\n";
+ r.lline1 = len r.hdr;
+ r.text = nil;
+ r.box = b;
+ r.open();
+ r.w.wdormant();
+}
+
+replyaddr(r : string) : string
+{
+ p, q, rr : int;
+
+ rr = 0;
+ while(r[rr]==' ' || r[rr]=='\t')
+ rr++;
+ r = r[rr:];
+ p = strchr(r, '<');
+ if(p >= 0){
+ q = strchr(r[p+1:], '>');
+ if(q < 0)
+ r = r[p+1:];
+ else
+ r = r[p+1:p+q] + "\n";
+ return r;
+ }
+ p = strchr(r, '(');
+ if(p >= 0){
+ q = strchr(r[p:], ')');
+ if(q < 0)
+ r = r[0:p];
+ else
+ r = r[0:p] + r[p+q+1:];
+ }
+ return r;
+}
+
+Mesg.mkreply(m : self ref Mesg)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ if(m.replyto != nil){
+ r.hdr = replyaddr(m.replyto);
+ r.lline1 = len r.hdr;
+ }else{
+ r.hdr = m.hdr[0:m.lline1];
+ r.lline1 = m.lline1; # was len m.hdr;
+ }
+ if(m.subj != nil){
+ if(StRnCmP(m.subj, "re:", 3)==0 || StRnCmP(m.subj, " re:", 4)==0)
+ r.text = "Subject:" + m.subj + "\n";
+ else
+ r.text = "Subject: Re:" + m.subj + "\n";
+ }
+ else
+ r.text = nil;
+ r.box = m.box;
+ r.open();
+ r.w.wselect("$");
+ r.w.wdormant();
+}
+
+Mesg.free(m : self ref Mesg)
+{
+ m.text = nil;
+ m.hdr = nil;
+ m.subj = nil;
+ m.realhdr = nil;
+ m.replyto = nil;
+ m = nil;
+}
+
+replyid : ref Ref;
+
+initreply()
+{
+ replyid = Ref.init();
+}
+
+Mesg.open(m : self ref Mesg)
+{
+ buf: string;
+
+ if(m.isopen)
+ return;
+ m.w = Win.wnew();
+ if(m.id != 0)
+ m.w.wwritebody("From ");
+ m.w.wwritebody(m.hdr);
+ m.w.wwritebody(m.text);
+ if(m.id){
+ buf = sprint("%s/%d", m.box.file , m.id);
+ m.w.wtagwrite("Reply Delmesg Save");
+ }else{
+ buf = sprint("%s/Reply%d", m.box.file, replyid.inc());
+ m.w.wtagwrite("Post");
+ }
+ m.w.wname(buf);
+ m.w.wclean();
+ m.w.wselect("0");
+ m.isopen = True;
+ m.posted = False;
+ spawn m.slave();
+}
+
+Mesg.putpost(m : self ref Mesg, e : ref Event)
+{
+ if(m.posted || m.id==0)
+ return;
+ if(e.q0 >= len m.hdr+5) # include "From "
+ return;
+ m.w.wtagwrite(" Post");
+ m.posted = True;
+ return;
+}
+
+Mesg.slave(m : self ref Mesg)
+{
+ e, e2, ea, etoss, eq : ref Event;
+ s : string;
+ na : int;
+
+ e = newevent();
+ e2 = newevent();
+ ea = newevent();
+ etoss = newevent();
+ for(;;){
+ m.w.wevent(e);
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' or 'M' => # type away; we don't care
+ case(e.c2){
+ 'x' or 'X' => # mouse only
+ eq = e;
+ if(e.flag & 2){
+ m.w.wevent(e2);
+ eq = e2;
+ }
+ if(e.flag & 8){
+ m.w.wevent(ea);
+ m.w.wevent(etoss);
+ na = ea.nb;
+ }else
+ na = 0;
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = m.w.wread(eq.q0, eq.q1);
+ else
+ s = string eq.b[0:eq.nb];
+ if(na)
+ s = s + " " + string ea.b[0:ea.nb];
+ if(!m.command(s)) # send it back
+ m.w.wwriteevent(e);
+ s = nil;
+ break;
+ 'l' or 'L' => # mouse only
+ if(e.flag & 2)
+ m.w.wevent(e2);
+ # just send it back
+ m.w.wwriteevent(e);
+ break;
+ 'I' or 'D' => # modify away; we don't care
+ m.putpost(e);
+ break;
+ 'd' or 'i' =>
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ }
+}
+
+Mesg.command(m : self ref Mesg, s : string) : int
+{
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(s == "Post"){
+ m.send();
+ return True;
+ }
+ if(len s >= 4 && s[0:4] == "Save"){
+ s = s[4:];
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(s == nil)
+ m.save("stored");
+ else{
+ ss := 0;
+ while(ss < len s && s[ss]!=' ' && s[ss]!='\t' && s[ss]!='\n')
+ ss++;
+ m.save(s[0:ss]);
+ }
+ return True;
+ }
+ if(s == "Reply"){
+ m.mkreply();
+ return True;
+ }
+ if(s == "Del"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ exit;
+ }
+ return True;
+ }
+ if(s == "Delmesg"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ m.box.cdel <-= m;
+ exit;
+ }
+ return True;
+ }
+ return False;
+}
+
+Mesg.save(m : self ref Mesg, base : string)
+{
+ s, buf : string;
+ n : int;
+ fd : ref FD;
+ b : ref Iobuf;
+
+ if(m.id <= 0){
+ fprint(stderr, "can't save reply message; mail it to yourself\n");
+ return;
+ }
+ buf = nil;
+ if(strchr(base, '/') >= 0)
+ s = base;
+ else
+ s = usermboxdir + base;
+ {
+ if(access(s) < 0)
+ raise("e");
+ fd = tryopen(s, OWRITE);
+ if(fd == nil)
+ raise("e");
+ buf = nil;
+ b = bufio->fopen(fd, OWRITE);
+ # seek to end in case file isn't append-only
+ b.seek(big 0, 2);
+ # use edited headers: first line of real header followed by remainder of selected ones
+ for(n=0; n<len m.realhdr && m.realhdr[n++]!='\n'; )
+ ;
+ b.puts(m.realhdr[0:n]);
+ b.puts(m.hdr[m.lline1:]);
+ b.puts(m.text);
+ b.close();
+ b = nil;
+ fd = nil;
+ }
+ exception{
+ "*" =>
+ buf = nil;
+ fprint(stderr, "mail: can't open %s: %r\n", base);
+ return;
+ }
+}
+
+Mesg.send(m : self ref Mesg)
+{
+ s, buf : string;
+ t, u : int;
+ a, b : list of string;
+ n : int;
+ p : array of ref FD;
+ c : chan of int;
+
+ p = array[2] of ref FD;
+ s = m.w.wreadall();
+ a = "sendmail" :: nil;
+ if(len s >= 5 && s[0:5] == "From ")
+ s = s[5:];
+ for(t=0; t < len s && s[t]!='\n' && s[t]!='\t';){
+ while(t < len s && (s[t]==' ' || s[t]==','))
+ t++;
+ u = t;
+ while(t < len s && s[t]!=' ' && s[t]!=',' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ if(t == u)
+ break;
+ a = s[u:t] :: a;
+ }
+ b = nil;
+ for ( ; a != nil; a = tl a)
+ b = hd a :: b;
+ a = b;
+ while(t < len s && s[t]!='\n')
+ t++;
+ if(s[t] == '\n')
+ t++;
+ if(pipe(p) < 0)
+ error("can't pipe: %r");
+ c = chan of int;
+ spawn run(a, c, p[0]);
+ <-c;
+ c = nil;
+ p[0] = nil;
+ n = len s - t;
+ if(swrite(p[1], s[t:]) != n)
+ fprint(stderr, "write to pipe failed: %r\n");
+ p[1] = nil;
+ # run() frees the arg list
+ buf = sprint("%s/%d-R", m.box.file, m.id);
+ m.w.wname(buf);
+ m.w.wclean();
+}
+
+Box.read(f : string, readonly : int) : ref Box
+{
+ b : ref Box;
+ m : ref Mesg;
+ s, buf : string;
+
+ b = ref Box;
+ b.nm = 0;
+ b.readonly = readonly;
+ b.file = f;
+ b.io = bufio->open(f, OREAD|OCEXEC);
+ if(b.io == nil)
+ error(sprint("can't open %s: %r", f));
+ b.w = Win.wnew();
+ if (!readonly)
+ spawn lockfilemon(b.w.winid);
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ m.id = b.nm;
+ }
+ b.leng = b.io.offset();
+ b.io.close();
+ b.io = nil;
+ # b.w = Win.wnew();
+ for(m=b.m; m != nil; m=m.next){
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wwritebody(buf);
+ }
+ buf = sprint("Mail/%s/", s);
+ b.w.wname(f);
+ if(b.readonly)
+ b.w.wtagwrite("Mail");
+ else
+ b.w.wtagwrite("Put Mail");
+ buf = "Mail " + f;
+ b.w.wsetdump("/acme/mail", buf);
+ b.w.wclean();
+ b.w.wselect("0");
+ b.w.wdormant();
+ b.cdel= chan of ref Mesg;
+ b.cevent = chan of Event;
+ b.cmore = chan of int;
+ spawn b.w.wslave(b.cevent);
+ b.clean = True;
+ return b;
+}
+
+Box.readmore(b : self ref Box)
+{
+ m : ref Mesg;
+ new : int;
+ buf : string;
+ doclose : int;
+
+ doclose = False;
+ if(b.io == nil){
+ b.io = bufio->open(b.file, OREAD|OCEXEC);
+ if(b.io == nil)
+ error(sprint("can't open %s: %r", b.file));
+ b.io.seek(b.leng, 0);
+ doclose = True;
+ }
+ new = False;
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ m.id = b.nm;
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wreplace("0", buf);
+ new = True;
+ }
+ b.leng = b.io.offset();
+ if(doclose){
+ b.io.close();
+ b.io = nil;
+ }
+ if(new){
+ if(b.clean)
+ b.w.wclean();
+ b.w.wselect("0;/.*(\\n[ \t].*)*");
+ b.w.wshow();
+ }
+ b.w.wdormant();
+}
+
+Box.readline(b : self ref Box) : string
+{
+ for (;;) {
+ if(b.peekline != nil){
+ b.line = b.peekline;
+ b.peekline = nil;
+ }else
+ b.line = b.io.gets('\n');
+ # nulls appear in mailboxes!
+ if(b.line != nil && strchr(b.line, 0) >= 0)
+ ;
+ else
+ break;
+ }
+ return b.line;
+}
+
+Box.unreadline(b : self ref Box)
+{
+ b.peekline = b.line;
+}
+
+Box.slave(b : self ref Box)
+{
+ e : ref Event;
+ m : ref Mesg;
+
+ e = newevent();
+ for(;;){
+ alt{
+ *e = <-b.cevent =>
+ b.event(e);
+ break;
+ <-b.cmore =>
+ b.readmore();
+ break;
+ m = <-b.cdel =>
+ b.mdel(m);
+ break;
+ }
+ }
+}
+
+Box.event(b : self ref Box, e : ref Event)
+{
+ e2, ea, eq : ref Event;
+ s : string;
+ t : int;
+ n, na, nopen : int;
+
+ e2 = newevent();
+ ea = newevent();
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' => # type away; we don't care
+ break;
+ 'M' =>
+ case(e.c2){
+ 'x' or 'X' =>
+ if(e.flag & 2)
+ *e2 = <-b.cevent;
+ if(e.flag & 8){
+ *ea = <-b.cevent;
+ na = ea.nb;
+ <- b.cevent;
+ }else
+ na = 0;
+ s = string e.b[0:e.nb];
+ # if it's a known command, do it
+ if((e.flag&2) && e.nb==0)
+ s = string e2.b[0:e2.nb];
+ if(na)
+ s = sprint("%s %s", s, string ea.b[0:ea.nb]);
+ # if it's a long message, it can't be for us anyway
+ if(!b.command(s)) # send it back
+ b.w.wwriteevent(e);
+ if(na)
+ s = nil;
+ break;
+ 'l' or 'L' =>
+ eq = e;
+ if(e.flag & 2){
+ *e2 = <-b.cevent;
+ eq = e2;
+ }
+ s = string eq.b[0:eq.nb];
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = b.w.wread(eq.q0, eq.q1);
+ nopen = 0;
+ do{
+ t = 0;
+ (n, t) = strtoi(s);
+ if(n>0 && (t == len s || s[t]==' ' || s[t]=='\t' || s[t]=='\n')){
+ b.mopen(n);
+ nopen++;
+ s = s[t:];
+ }
+ while(s != nil && s[0]!='\n')
+ s = s[1:];
+ }while(s != nil);
+ if(nopen == 0) # send it back
+ b.w.wwriteevent(e);
+ break;
+ 'I' or 'D' or 'd' or 'i' => # modify away; we don't care
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+}
+
+Box.mopen(b : self ref Box, id : int)
+{
+ m : ref Mesg;
+
+ for(m=b.m; m != nil; m=m.next)
+ if(m.id == id){
+ m.open();
+ break;
+ }
+}
+
+Box.mdel(b : self ref Box, dm : ref Mesg)
+{
+ prev, m : ref Mesg;
+ buf : string;
+
+ if(dm.id){
+ prev = nil;
+ for(m=b.m; m!=nil && m!=dm; m=m.next)
+ prev = m;
+ if(m == nil)
+ error(sprint("message %d not found", dm.id));
+ if(prev == nil)
+ b.m = m.next;
+ else
+ prev.next = m.next;
+ # remove from screen: use acme to help
+ buf = sprint("/^%d .*\\n(^[ \t].*\\n)*/", m.id);
+ b.w.wreplace(buf, "");
+ }
+ dm.free();
+ b.clean = False;
+}
+
+Box.command(b : self ref Box, s : string) : int
+{
+ t : int;
+ m : ref Mesg;
+
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(len s >= 4 && s[0:4] == "Mail"){
+ s = s[4:];
+ while(s != nil && (s[0]==' ' || s[0]=='\t' || s[0]=='\n'))
+ s = s[1:];
+ t = 0;
+ while(t < len s && s[t] && s[t]!=' ' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ m = b.m; # avoid warning message on b.m.mkmail(...)
+ m.mkmail(b, s[0:t]);
+ return True;
+ }
+ if(s == "Del"){
+
+ if(!b.clean){
+ b.clean = True;
+ fprint(stderr, "mail: mailbox not written\n");
+ return True;
+ }
+ rmlockfile();
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ pctl(NEWPGRP, nil);
+ b.w.wdel(True);
+ for(m=b.m; m != nil; m=m.next)
+ m.w.wdel(False);
+ exit;
+ return True;
+ }
+ if(s == "Put"){
+ if(b.readonly)
+ fprint(stderr, "Mail: %s is read-only\n", b.file);
+ else
+ b.rewrite();
+ return True;
+ }
+ return False;
+}
+
+Box.rewrite(b : self ref Box)
+{
+ mbox, Lmbox, mboxtmp : ref FD;
+ i, t, ok : int;
+ buf : string;
+ s : string;
+ m : ref Mesg;
+ d : Dir;
+ Lmboxs : string = nil;
+
+ if(b.clean){
+ b.w.wclean();
+ return;
+ }
+ t = strrchr(b.file, '/');
+ if(t >= 0)
+ s = b.file[t+1:];
+ else
+ s = b.file;
+ if(mboxfile == usermboxfile){
+ buf = sprint("%sL.%s", b.file[0:t+1], s);
+ Lmbox = openlockfile(buf);
+ if(Lmbox == nil)
+ error(sprint("can't open lock file %s: %r", buf));
+ else
+ Lmboxs = buf;
+ }else
+ Lmbox = nil;
+ buf = sprint("%s.tmp", b.file);
+ mbox = tryopen(mboxfile, OREAD);
+ if(mbox != nil){
+ b.io = bufio->fopen(mbox, OREAD);
+ b.io.seek(b.leng, 0);
+ b.readmore();
+ }else if(access(buf)){
+ fprint(stderr, "mail: mailbox missing; using %s\n", buf);
+ mboxtmp = tryopen(buf, ORDWR);
+ b.io = bufio->fopen(mboxtmp, OREAD);
+ b.readmore();
+ b.io.close();
+ }else
+ error(sprint("can't open %s to rewrite: %r", s));
+ remove(buf);
+ mboxtmp = create(buf, OWRITE, 0622|CHAPPEND|CHEXCL);
+ if(mboxtmp == nil)
+ error(sprint("can't create %s: %r", buf));
+ (ok, d) = fstat(mboxtmp);
+ if(ok < 0)
+ error(sprint("can't fstat %s: %r", buf));
+ d.mode |= 0622;
+ if(fwstat(mboxtmp, d) < 0)
+ error(sprint("can't change mode of %s: %r", buf));
+ b.io = bufio->fopen(mboxtmp, OWRITE);
+ # write it backwards: stupid code
+ for(i=1; i<=b.nm; i++){
+ for(m=b.m; m!=nil && m.id!=i; m=m.next)
+ ;
+ if(m != nil){
+ b.io.puts(m.realhdr);
+ b.io.puts(m.text);
+ }
+ }
+ if(remove(mboxfile) < 0)
+ error(sprint("can't unlink %s: %r", mboxfile));
+ d.name = s;
+ if(fwstat(mboxtmp, d) < 0)
+ error(sprint("can't change name of %s: %r", buf));
+ b.leng = b.io.offset();
+ b.io.close();
+ mboxtmp = nil;
+ b.io = nil;
+ Lmbox = nil;
+ if (Lmboxs != nil)
+ remove(Lmboxs);
+ b.w.wclean();
+ b.clean = True;
+}
+
+lockfilemon(id : int)
+{
+ pctl(NEWPGRP, nil);
+ p := "/chan/" + string id;
+ for (;;) {
+ if (lockfile == nil)
+ error(nil);
+ sleep(60*1000);
+ (ok, d) := stat(p);
+ if (ok < 0)
+ error(nil);
+ }
+}
--- /dev/null
+++ b/appl/acme/acme/mail/src/Mailp.b
@@ -1,0 +1,1684 @@
+implement mailpop3;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "daytime.m";
+include "sh.m";
+include "pop3.m";
+
+mailpop3 : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+daytime : Daytime;
+pop3 : Pop3;
+
+OREAD, OWRITE, ORDWR, NEWFD, FORKENV, FORKFD, NEWPGRP, UTFmax, EXCEPTION, ONCE : import Sys;
+FD, Dir, Exception : import sys;
+fprint, sprint, sleep, create, open, read, write, remove, stat, fstat, fwstat, fildes, pctl, pipe, dup, byte2char : import sys;
+Context : import Draw;
+EOF : import Bufio;
+Iobuf : import bufio;
+time : import daytime;
+
+DIRLEN : con 116;
+PNPROC, PNGROUP : con iota;
+False : con 0;
+True : con 1;
+EVENTSIZE : con 256;
+Runeself : con 16r80;
+OCEXEC : con 0;
+CHEXCL : con 0; # 16r20000000;
+CHAPPEND : con 0; # 16r40000000;
+
+Win : adt {
+ winid : int;
+ addr : ref FD;
+ body : ref Iobuf;
+ ctl : ref FD;
+ data : ref FD;
+ event : ref FD;
+ buf : array of byte;
+ bufp : int;
+ nbuf : int;
+
+ wnew : fn() : ref Win;
+ wwritebody : fn(w : self ref Win, s : string);
+ wread : fn(w : self ref Win, m : int, n : int) : string;
+ wclean : fn(w : self ref Win);
+ wname : fn(w : self ref Win, s : string);
+ wdormant : fn(w : self ref Win);
+ wevent : fn(w : self ref Win, e : ref Event);
+ wshow : fn(w : self ref Win);
+ wtagwrite : fn(w : self ref Win, s : string);
+ wwriteevent : fn(w : self ref Win, e : ref Event);
+ wslave : fn(w : self ref Win, c : chan of Event);
+ wreplace : fn(w : self ref Win, s : string, t : string);
+ wselect : fn(w : self ref Win, s : string);
+ wsetdump : fn(w : self ref Win, s : string, t : string);
+ wdel : fn(w : self ref Win, n : int) : int;
+ wreadall : fn(w : self ref Win) : string;
+
+ ctlwrite : fn(w : self ref Win, s : string);
+ getec : fn(w : self ref Win) : int;
+ geten : fn(w : self ref Win) : int;
+ geter : fn(w : self ref Win, s : array of byte) : (int, int);
+ openfile : fn(w : self ref Win, s : string) : ref FD;
+ openbody : fn(w : self ref Win, n : int);
+};
+
+Mesg : adt {
+ w : ref Win;
+ id : int;
+ popno : int;
+ hdr : string;
+ realhdr : string;
+ replyto : string;
+ text : string;
+ subj : string;
+ next : cyclic ref Mesg;
+ lline1 : int;
+ box : cyclic ref Box;
+ isopen : int;
+ posted : int;
+ deleted : int;
+
+ read : fn(b : ref Box) : ref Mesg;
+ open : fn(m : self ref Mesg);
+ slave : fn(m : self ref Mesg);
+ free : fn(m : self ref Mesg);
+ save : fn(m : self ref Mesg, s : string);
+ mkreply : fn(m : self ref Mesg);
+ mkmail : fn(b : ref Box, s : string);
+ putpost : fn(m : self ref Mesg, e : ref Event);
+
+ command : fn(m : self ref Mesg, s : string) : int;
+ send : fn(m : self ref Mesg);
+};
+
+Box : adt {
+ w : ref Win;
+ nm : int;
+ readonly : int;
+ m : cyclic ref Mesg;
+# io : ref Iobuf;
+ clean : int;
+ leng : int;
+ cdel : chan of ref Mesg;
+ cevent : chan of Event;
+ cmore : chan of int;
+ lst : list of int;
+ s : string;
+
+ line : string;
+ popno : int;
+ peekline : string;
+
+ read : fn(n : int) : ref Box;
+ readmore : fn(b : self ref Box);
+ readline : fn(b : self ref Box) : string;
+ unreadline : fn(b : self ref Box);
+ slave : fn(b : self ref Box);
+ mopen : fn(b : self ref Box, n : int);
+ rewrite : fn(b : self ref Box);
+ mdel : fn(b : self ref Box, m : ref Mesg);
+ event : fn(b : self ref Box, e : ref Event);
+
+ command : fn(b : self ref Box, s : string) : int;
+};
+
+Event : adt {
+ c1 : int;
+ c2 : int;
+ q0 : int;
+ q1 : int;
+ flag : int;
+ nb : int;
+ nr : int;
+ b : array of byte;
+ r : array of int;
+};
+
+Lock : adt {
+ cnt : int;
+ chann : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+};
+
+Ref : adt {
+ l : ref Lock;
+ cnt : int;
+
+ init : fn() : ref Ref;
+ inc : fn(r : self ref Ref) : int;
+};
+
+mbox : ref Box;
+user : string;
+date : string;
+mailctxt : ref Context;
+stdout, stderr : ref FD;
+
+killing : int = 0;
+
+init(ctxt : ref Context, argl : list of string)
+{
+ mailctxt = ctxt;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ pop3 = load Pop3 Pop3->PATH;
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main();
+}
+
+dlock : ref Lock;
+dfd : ref Sys->FD;
+
+debug(s : string)
+{
+ if (dfd == nil) {
+ dfd = sys->create("/usr/jrf/acme/debugmail", Sys->OWRITE, 8r600);
+ dlock = Lock.init();
+ }
+ if (dfd == nil)
+ return;
+ dlock.lock();
+ sys->fprint(dfd, "%s", s);
+ dlock.unlock();
+}
+
+postnote(t : int, pid : int, note : string) : int
+{
+ fd := open("#p/" + string pid + "/ctl", OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ fprint(fd, "%s", note);
+ fd = nil;
+ return 0;
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(mailctxt, argl);
+}
+
+swrite(fd : ref FD, s : string) : int
+{
+ ab := array of byte s;
+ m := len ab;
+ p := write(fd, ab, m);
+ if (p == m)
+ return len s;
+ if (p <= 0)
+ return p;
+ return 0;
+}
+
+strchr(s : string, c : int) : int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strrchr(s : string, c : int) : int
+{
+ for (i := len s - 1; i >= 0; i--)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strtoi(s : string) : (int, int)
+{
+ m := 0;
+ neg := 0;
+ t := 0;
+ ls := len s;
+ while (t < ls && (s[t] == ' ' || s[t] == '\t'))
+ t++;
+ if (t < ls && s[t] == '+')
+ t++;
+ else if (t < ls && s[t] == '-') {
+ neg = 1;
+ t++;
+ }
+ while (t < ls && (s[t] >= '0' && s[t] <= '9')) {
+ m = 10*m + s[t]-'0';
+ t++;
+ }
+ if (neg)
+ m = -m;
+ return (m, t);
+}
+
+access(s : string) : int
+{
+ fd := open(s, 0);
+ if (fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+newevent() : ref Event
+{
+ e := ref Event;
+ e.b = array[EVENTSIZE*UTFmax+1] of byte;
+ e.r = array[EVENTSIZE+1] of int;
+ return e;
+}
+
+newmesg() : ref Mesg
+{
+ m := ref Mesg;
+ m.id = m.lline1 = m.isopen = m.posted = m.deleted = 0;
+ return m;
+}
+
+lc, uc : chan of ref Lock;
+
+initlock()
+{
+ lc = chan of ref Lock;
+ uc = chan of ref Lock;
+ spawn lockmgr();
+}
+
+lockmgr()
+{
+ l : ref Lock;
+
+ for (;;) {
+ alt {
+ l = <- lc =>
+ if (l.cnt++ == 0)
+ l.chann <-= 1;
+ l = <- uc =>
+ if (--l.cnt > 0)
+ l.chann <-= 1;
+ }
+ }
+}
+
+Lock.init() : ref Lock
+{
+ return ref Lock(0, chan of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ lc <-= l;
+ <- l.chann;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ uc <-= l;
+}
+
+Ref.init() : ref Ref
+{
+ r := ref Ref;
+ r.l = Lock.init();
+ r.cnt = 0;
+ return r;
+}
+
+Ref.inc(r : self ref Ref) : int
+{
+ r.l.lock();
+ i := r.cnt;
+ r.cnt++;
+ r.l.unlock();
+ return i;
+}
+
+error(s : string)
+{
+ if(s != nil)
+ fprint(stderr, "mail: %s\n", s);
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ exit;
+}
+
+tryopen(s : string, mode : int) : ref FD
+{
+ fd : ref FD;
+ try : int;
+
+ for(try=0; try<3; try++){
+ fd = open(s, mode);
+ if(fd != nil)
+ return fd;
+ sleep(1000);
+ }
+ return nil;
+}
+
+run(argv : list of string, c : chan of int, p0 : ref FD)
+{
+ # pctl(FORKFD|NEWPGRP, nil); # had RFMEM
+ pctl(FORKENV|NEWFD|NEWPGRP, 0::1::2::p0.fd::nil);
+ c <-= pctl(0, nil);
+ dup(p0.fd, 0);
+ p0 = nil;
+ exec(hd argv, argv);
+ exit;
+}
+
+getuser() : string
+{
+ fd := open("/dev/user", OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[128] of byte;
+ n := read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0:n];
+}
+
+pop3conn : int = 0;
+pop3bad : int = 0;
+pop3lock : ref Lock;
+
+pop3open()
+{
+ pop3lock.lock();
+ if (!pop3conn) {
+ (ok, s) := pop3->open(user, "********", nil); # password now got from user in Mailpop3.b
+ if (ok < 0) {
+ if (!pop3bad) {
+ fprint(stderr, "mail: could not connect to POP3 mail server : %s\n", s);
+ pop3bad = 1;
+ }
+ return;
+ }
+ }
+ pop3conn = 1;
+ pop3bad = 0;
+}
+
+pop3close()
+{
+ if (pop3conn) {
+ (ok, s) := pop3->close();
+ if (ok < 0) {
+ fprint(stderr, "mail: could not close POP3 connection : %s\n", s);
+ pop3lock.unlock();
+ return;
+ }
+ }
+ pop3conn = 0;
+ pop3lock.unlock();
+}
+
+pop3stat(b : ref Box) : int
+{
+ (ok, s, nm, nil) := pop3->stat();
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not stat POP3 server : %s\n", s);
+ return b.leng;
+ }
+ return nm;
+}
+
+pop3list() : list of int
+{
+ (ok, s, l) := pop3->msgnolist();
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not get list from POP3 server : %s\n", s);
+ return nil;
+ }
+ return l;
+}
+
+pop3mesg(mno : int) : string
+{
+ (ok, s, msg) := pop3->get(mno);
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not retrieve a message from server : %s\n", s);
+ return "Acme Mail : FAILED TO RETRIEVE MESSAGE\n";
+ }
+ return msg;
+}
+
+pop3del(mno : int) : int
+{
+ (ok, s) := pop3->delete(mno);
+ if (ok < 0)
+ fprint(stderr, "mail: could not delete message : %s\n", s);
+ return ok;
+}
+
+pop3init(b : ref Box)
+{
+ b.leng = pop3stat(b);
+ b.lst = pop3list();
+ b.s = nil;
+ b.popno = 0;
+}
+
+pop3more(b : ref Box)
+{
+ nl : list of int;
+
+ leng := b.leng;
+ b.leng = pop3stat(b);
+ b.lst = pop3list();
+ b.s = nil;
+ b.popno = 0;
+ if (len b.lst != b.leng || b.leng <= leng)
+ error("bad lengths in pop3more()");
+ # is this ok ?
+ nl = nil;
+ for (i := 0; i < leng; i++) {
+ nl = hd b.lst :: nl;
+ b.lst = tl b.lst;
+ }
+ # now update pop nos.
+ for (m := b.m; m != nil; m = m.next) {
+ # opopno := m.popno;
+ if (nl == nil)
+ error("message list too big");
+ m.popno = hd nl;
+ nl = tl nl;
+ # debug(sys->sprint("%d : popno from %d to %d\n", m.id, opopno, m.popno));
+ }
+ if (nl != nil)
+ error("message list too small");
+}
+
+pop3next(b : ref Box) : string
+{
+ mno : int = 0;
+ r : string;
+
+ if (b.s == nil) {
+ if (b.lst == nil)
+ return nil; # end of box
+ first := b.popno == 0;
+ mno = hd b.lst;
+ b.lst = tl b.lst;
+ b.s = pop3mesg(mno);
+ b.popno = mno;
+ if (!first)
+ return nil; # end of message
+ }
+ t := strchr(b.s, '\n');
+ if (t >= 0) {
+ r = b.s[0:t+1];
+ b.s = b.s[t+1:];
+ }
+ else {
+ r = b.s;
+ b.s = nil;
+ }
+ return r;
+}
+
+main()
+{
+ readonly : int;
+
+ initlock();
+ initreply();
+ date = time();
+ if(date==nil)
+ error("can't get current time");
+ user = getuser();
+ if(user == nil)
+ user = "Wile.E.Coyote";
+ readonly = False;
+ pop3lock = Lock.init();
+ mbox = mbox.read(readonly);
+ spawn timeslave(mbox, mbox.cmore);
+ mbox.slave();
+ error(nil);
+}
+
+timeslave(b : ref Box, c : chan of int)
+{
+ for(;;){
+ sleep(30*1000);
+ pop3open();
+ leng := pop3stat(b);
+ pop3close();
+ if (leng > b.leng)
+ c <-= 0;
+ }
+}
+
+Win.wnew() : ref Win
+{
+ w := ref Win;
+ buf := array[12] of byte;
+ w.ctl = open("/chan/new/ctl", ORDWR);
+ if(w.ctl==nil || read(w.ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ w.ctlwrite("noscroll\n");
+ w.winid = int string buf;
+ w.event = w.openfile("event");
+ w.addr = nil; # will be opened when needed
+ w.body = nil;
+ w.data = nil;
+ w.bufp = w.nbuf = 0;
+ w.buf = array[512] of byte;
+ return w;
+}
+
+Win.openfile(w : self ref Win, f : string) : ref FD
+{
+ buf := sprint("/chan/%d/%s", w.winid, f);
+ fd := open(buf, ORDWR|OCEXEC);
+ if(fd == nil)
+ error(sprint("can't open window %s file: %r", f));
+ return fd;
+}
+
+Win.openbody(w : self ref Win, mode : int)
+{
+ buf := sprint("/chan/%d/body", w.winid);
+ w.body = bufio->open(buf, mode|OCEXEC);
+ if(w.body == nil)
+ error("can't open window body file: %r");
+}
+
+Win.wwritebody(w : self ref Win, s : string)
+{
+ n := len s;
+ if(w.body == nil)
+ w.openbody(OWRITE);
+ if(w.body.puts(s) != n)
+ error("write error to window: %r");
+}
+
+Win.wreplace(w : self ref Win, addr : string, repl : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ if(swrite(w.addr, addr) < 0){
+ fprint(stderr, "mail: warning: bad address %s:%r\n", addr);
+ return;
+ }
+ if(swrite(w.data, repl) != len repl)
+ error("writing data: %r");
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (r, b, ok) := byte2char(s, i);
+ if (!ok)
+ error("help needed in nrunes()");
+ i += b;
+ }
+ return n;
+}
+
+Win.wread(w : self ref Win, q0 : int, q1 : int) : string
+{
+ m, n, nr : int;
+ s, buf : string;
+ b : array of byte;
+
+ b = array[256] of byte;
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ s = nil;
+ m = q0;
+ while(m < q1){
+ buf = sprint("#%d", m);
+ if(swrite(w.addr, buf) != len buf)
+ error("writing addr: %r");
+ n = read(w.data, b, len b);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(b, n);
+ while(m+nr >q1){
+ do; while(n>0 && (int b[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ s += string b[0:n];
+ m += nr;
+ }
+ return s;
+}
+
+Win.wshow(w : self ref Win)
+{
+ w.ctlwrite("show\n");
+}
+
+Win.wsetdump(w : self ref Win, dir : string, cmd : string)
+{
+ t : string;
+
+ if(dir != nil){
+ t = "dumpdir " + dir + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+ if(cmd != nil){
+ t = "dump " + cmd + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+}
+
+Win.wselect(w : self ref Win, addr : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(swrite(w.addr, addr) < 0)
+ error("writing addr");
+ w.ctlwrite("dot=addr\n");
+}
+
+Win.wtagwrite(w : self ref Win, s : string)
+{
+ fd : ref FD;
+
+ fd = w.openfile("tag");
+ if(swrite(fd, s) != len s)
+ error("tag write: %r");
+ fd = nil;
+}
+
+Win.ctlwrite(w : self ref Win, s : string)
+{
+ if(swrite(w.ctl, s) != len s)
+ error("write error to ctl file: %r");
+}
+
+Win.wdel(w : self ref Win, sure : int) : int
+{
+ if (w == nil)
+ return False;
+ if(sure)
+ swrite(w.ctl, "delete\n");
+ else if(swrite(w.ctl, "del\n") != 4)
+ return False;
+ w.wdormant();
+ w.ctl = nil;
+ w.event = nil;
+ return True;
+}
+
+Win.wname(w : self ref Win, s : string)
+{
+ w.ctlwrite("name " + s + "\n");
+}
+
+Win.wclean(w : self ref Win)
+{
+ if(w.body != nil)
+ w.body.flush();
+ w.ctlwrite("clean\n");
+}
+
+Win.wdormant(w : self ref Win)
+{
+ w.addr = nil;
+ if(w.body != nil){
+ w.body.close();
+ w.body = nil;
+ }
+ w.data = nil;
+}
+
+Win.getec(w : self ref Win) : int
+{
+ if(w.nbuf == 0){
+ w.nbuf = read(w.event, w.buf, len w.buf);
+ if(w.nbuf <= 0 && !killing) {
+ error("event read error: %r");
+ }
+ w.bufp = 0;
+ }
+ w.nbuf--;
+ return int w.buf[w.bufp++];
+}
+
+Win.geten(w : self ref Win) : int
+{
+ n, c : int;
+
+ n = 0;
+ while('0'<=(c=w.getec()) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+Win.geter(w : self ref Win, buf : array of byte) : (int, int)
+{
+ r, m, n, ok : int;
+
+ r = w.getec();
+ buf[0] = byte r;
+ n = 1;
+ if(r >= Runeself) {
+ for (;;) {
+ (r, m, ok) = byte2char(buf[0:n], 0);
+ if (m > 0)
+ return (r, n);
+ buf[n++] = byte w.getec();
+ }
+ }
+ return (r, n);
+}
+
+Win.wevent(w : self ref Win, e : ref Event)
+{
+ i, nb : int;
+
+ e.c1 = w.getec();
+ e.c2 = w.getec();
+ e.q0 = w.geten();
+ e.q1 = w.geten();
+ e.flag = w.geten();
+ e.nr = w.geten();
+ if(e.nr > EVENTSIZE)
+ error("event string too long");
+ e.nb = 0;
+ for(i=0; i<e.nr; i++){
+ (e.r[i], nb) = w.geter(e.b[e.nb:]);
+ e.nb += nb;
+ }
+ e.r[e.nr] = 0;
+ e.b[e.nb] = byte 0;
+ c := w.getec();
+ if(c != '\n')
+ error("event syntax 2");
+}
+
+Win.wslave(w : self ref Win, ce : chan of Event)
+{
+ e : ref Event;
+
+ e = newevent();
+ for(;;){
+ w.wevent(e);
+ ce <-= *e;
+ }
+}
+
+Win.wwriteevent(w : self ref Win, e : ref Event)
+{
+ fprint(w.event, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+}
+
+Win.wreadall(w : self ref Win) : string
+{
+ s, t : string;
+
+ if(w.body != nil)
+ w.body.close();
+ w.openbody(OREAD);
+ s = nil;
+ while ((t = w.body.gets('\n')) != nil)
+ s += t;
+ w.body.close();
+ w.body = nil;
+ return s;
+}
+
+None,Unknown,Ignore,CC,From,ReplyTo,Sender,Subject,Re,To, Date : con iota;
+NHeaders : con 200;
+
+Hdrs : adt {
+ name : string;
+ typex : int;
+};
+
+
+hdrs := array[NHeaders+1] of {
+ Hdrs ( "CC:", CC ),
+ Hdrs ( "From:", From ),
+ Hdrs ( "Reply-To:", ReplyTo ),
+ Hdrs ( "Sender:", Sender ),
+ Hdrs ( "Subject:", Subject ),
+ Hdrs ( "Re:", Re ),
+ Hdrs ( "To:", To ),
+ Hdrs ( "Date:", Date),
+ * => Hdrs ( "", 0 ),
+};
+
+StRnCmP(s : string, t : string, n : int) : int
+{
+ c, d, i, j : int;
+
+ i = j = 0;
+ if (len s < n || len t < n)
+ return -1;
+ while(n > 0){
+ c = s[i++];
+ d = t[j++];
+ --n;
+ if(c != d){
+ if('a'<=c && c<='z')
+ c -= 'a'-'A';
+ if('a'<=d && d<='z')
+ d -= 'a'-'A';
+ if(c != d)
+ return c-d;
+ }
+ }
+ return 0;
+}
+
+readhdr(b : ref Box) : (string, int)
+{
+ i, j, n, m, typex : int;
+ s, t : string;
+
+{
+ s = b.readline();
+ n = len s;
+ if(n <= 0) {
+ b.unreadline();
+ raise("e");
+ }
+ for(i=0; i<n; i++){
+ j = s[i];
+ if(i>0 && j == ':')
+ break;
+ if(j<'!' || '~'<j){
+ b.unreadline();
+ raise("e");
+ }
+ }
+ typex = Unknown;
+ for(i=0; hdrs[i].name != nil; i++){
+ j = len hdrs[i].name;
+ if(StRnCmP(hdrs[i].name, s, j) == 0){
+ typex = hdrs[i].typex;
+ break;
+ }
+ }
+ # scan for multiple sublines
+ for(;;){
+ t = b.readline();
+ m = len t;
+ if(m<=0 || (t[0]!=' ' && t[0]!='\t')){
+ b.unreadline();
+ break;
+ }
+ # absorb
+ s += t;
+ }
+ return(s, typex);
+}
+exception{
+ "*" =>
+ return (nil, None);
+}
+}
+
+Mesg.read(b : ref Box) : ref Mesg
+{
+ m : ref Mesg;
+ s : string;
+ n, typex : int;
+
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ return nil;
+
+{
+ if(n < 5 || (s[0:5] !="From " && s[0:5] != "From:"))
+ raise("e");
+ m = newmesg();
+ m.popno = b.popno;
+ if (m.popno == 0)
+ error("bad pop3 id");
+ m.realhdr = s;
+ # toss 'From '
+ s = s[5:];
+ n -= 5;
+ # toss spaces/tabs
+ while (n > 0 && (s[0] == ' ' || s[0] == '\t')) {
+ s = s[1:];
+ n--;
+ }
+ m.hdr = s;
+ # convert first blank to tab
+ s0 := strchr(m.hdr, ' ');
+ if(s0 >= 0){
+ m.hdr[s0] = '\t';
+ # drop trailing seconds, time zone, and year if match local year
+ t := n-6;
+ if(t <= 0)
+ raise("e");
+ if(m.hdr[t:n-1] == date[23:]){
+ m.hdr = m.hdr[0:t] + "\n"; # drop year for sure
+ t = -1;
+ s1 := strchr(m.hdr[s0:], ':');
+ if(s1 >= 0)
+ t = strchr(m.hdr[s0+s1+1:], ':');
+ if(t >= 0) # drop seconds and time zone
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ else{ # drop time zone
+ t = strchr(m.hdr[s0+s1+1:], ' ');
+ if(t >= 0)
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ }
+ n = len m.hdr;
+ }
+ }
+ m.lline1 = n;
+ m.text = nil;
+ # read header
+loop:
+ for(;;){
+ (s, typex) = readhdr(b);
+ case(typex){
+ None =>
+ break loop;
+ ReplyTo =>
+ m.replyto = s[9:];
+ break;
+ From =>
+ if(m.replyto == nil)
+ m.replyto = s[5:];
+ break;
+ Subject =>
+ m.subj = s[8:];
+ break;
+ Re =>
+ m.subj = s[3:];
+ break;
+ Date =>
+ break;
+ }
+ m.realhdr += s;
+ if(typex != Ignore)
+ m.hdr += s;
+ }
+ # read body
+ for(;;){
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ break;
+# if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:")){
+# b.unreadline();
+# break;
+# }
+ m.text += s;
+ }
+ # remove trailing "morF\n"
+ l := len m.text;
+ if(l>6 && m.text[l-6:] == "\nmorF\n")
+ m.text = m.text[0:l-5];
+ m.box = b;
+ return m;
+}
+exception{
+ "*" =>
+ error("malformed header " + s);
+ return nil;
+}
+}
+
+Mesg.mkmail(b : ref Box, hdr : string)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ r.hdr = hdr + "\n";
+ r.lline1 = len r.hdr;
+ r.text = nil;
+ r.box = b;
+ r.open();
+ r.w.wdormant();
+}
+
+replyaddr(r : string) : string
+{
+ p, q, rr : int;
+
+ rr = 0;
+ while(r[rr]==' ' || r[rr]=='\t')
+ rr++;
+ r = r[rr:];
+ p = strchr(r, '<');
+ if(p >= 0){
+ q = strchr(r[p+1:], '>');
+ if(q < 0)
+ r = r[p+1:];
+ else
+ r = r[p+1:p+q] + "\n";
+ return r;
+ }
+ p = strchr(r, '(');
+ if(p >= 0){
+ q = strchr(r[p:], ')');
+ if(q < 0)
+ r = r[0:p];
+ else
+ r = r[0:p] + r[p+q+1:];
+ }
+ return r;
+}
+
+Mesg.mkreply(m : self ref Mesg)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ if(m.replyto != nil){
+ r.hdr = replyaddr(m.replyto);
+ r.lline1 = len r.hdr;
+ }else{
+ r.hdr = m.hdr[0:m.lline1];
+ r.lline1 = m.lline1; # was len m.hdr;
+ }
+ if(m.subj != nil){
+ if(StRnCmP(m.subj, "re:", 3)==0 || StRnCmP(m.subj, " re:", 4)==0)
+ r.text = "Subject:" + m.subj + "\n";
+ else
+ r.text = "Subject: Re:" + m.subj + "\n";
+ }
+ else
+ r.text = nil;
+ r.box = m.box;
+ r.open();
+ r.w.wselect("$");
+ r.w.wdormant();
+}
+
+Mesg.free(m : self ref Mesg)
+{
+ m.text = nil;
+ m.hdr = nil;
+ m.subj = nil;
+ m.realhdr = nil;
+ m.replyto = nil;
+ m = nil;
+}
+
+replyid : ref Ref;
+
+initreply()
+{
+ replyid = Ref.init();
+}
+
+Mesg.open(m : self ref Mesg)
+{
+ buf, s : string;
+
+ if(m.isopen)
+ return;
+ m.w = Win.wnew();
+ if(m.id != 0)
+ m.w.wwritebody("From ");
+ m.w.wwritebody(m.hdr);
+ m.w.wwritebody(m.text);
+ if(m.id){
+ buf = sprint("Mail/box/%d", m.id);
+ m.w.wtagwrite("Reply Delmesg Save");
+ }else{
+ buf = sprint("Mail/%s/Reply%d", s, replyid.inc());
+ m.w.wtagwrite("Post");
+ }
+ m.w.wname(buf);
+ m.w.wclean();
+ m.w.wselect("0");
+ m.isopen = True;
+ m.posted = False;
+ spawn m.slave();
+}
+
+Mesg.putpost(m : self ref Mesg, e : ref Event)
+{
+ if(m.posted || m.id==0)
+ return;
+ if(e.q0 >= len m.hdr+5) # include "From "
+ return;
+ m.w.wtagwrite(" Post");
+ m.posted = True;
+ return;
+}
+
+Mesg.slave(m : self ref Mesg)
+{
+ e, e2, ea, etoss, eq : ref Event;
+ s : string;
+ na : int;
+
+ e = newevent();
+ e2 = newevent();
+ ea = newevent();
+ etoss = newevent();
+ for(;;){
+ m.w.wevent(e);
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' or 'M' => # type away; we don't care
+ case(e.c2){
+ 'x' or 'X' => # mouse only
+ eq = e;
+ if(e.flag & 2){
+ m.w.wevent(e2);
+ eq = e2;
+ }
+ if(e.flag & 8){
+ m.w.wevent(ea);
+ m.w.wevent(etoss);
+ na = ea.nb;
+ }else
+ na = 0;
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = m.w.wread(eq.q0, eq.q1);
+ else
+ s = string eq.b[0:eq.nb];
+ if(na)
+ s = s + " " + string ea.b[0:ea.nb];
+ if(!m.command(s)) # send it back
+ m.w.wwriteevent(e);
+ s = nil;
+ break;
+ 'l' or 'L' => # mouse only
+ if(e.flag & 2)
+ m.w.wevent(e2);
+ # just send it back
+ m.w.wwriteevent(e);
+ break;
+ 'I' or 'D' => # modify away; we don't care
+ m.putpost(e);
+ break;
+ 'd' or 'i' =>
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ }
+}
+
+Mesg.command(m : self ref Mesg, s : string) : int
+{
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(s == "Post"){
+ m.send();
+ return True;
+ }
+ if(len s >= 4 && s[0:4] == "Save"){
+ s = s[4:];
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(s == nil)
+ m.save("stored");
+ else{
+ ss := 0;
+ while(ss < len s && s[ss]!=' ' && s[ss]!='\t' && s[ss]!='\n')
+ ss++;
+ m.save(s[0:ss]);
+ }
+ return True;
+ }
+ if(s == "Reply"){
+ m.mkreply();
+ return True;
+ }
+ if(s == "Del"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ exit;
+ }
+ return True;
+ }
+ if(s == "Delmesg"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ m.box.cdel <-= m;
+ exit;
+ }
+ return True;
+ }
+ return False;
+}
+
+Mesg.save(m : self ref Mesg, base : string)
+{
+ s, buf : string;
+ n : int;
+ fd : ref FD;
+ b : ref Iobuf;
+
+ if(m.id <= 0){
+ fprint(stderr, "can't save reply message; mail it to yourself\n");
+ return;
+ }
+ buf = nil;
+ s = base;
+{
+ if(access(s) < 0)
+ raise("e");
+ fd = tryopen(s, OWRITE);
+ if(fd == nil)
+ raise("e");
+ buf = nil;
+ b = bufio->fopen(fd, OWRITE);
+ # seek to end in case file isn't append-only
+ b.seek(big 0, 2);
+ # use edited headers: first line of real header followed by remainder of selected ones
+ for(n=0; n<len m.realhdr && m.realhdr[n++]!='\n'; )
+ ;
+ b.puts(m.realhdr[0:n]);
+ b.puts(m.hdr[m.lline1:]);
+ b.puts(m.text);
+ b.close();
+ b = nil;
+ fd = nil;
+}
+exception{
+ "*" =>
+ buf = nil;
+ fprint(stderr, "mail: can't open %s: %r\n", base);
+ return;
+}
+}
+
+Mesg.send(m : self ref Mesg)
+{
+ s, buf : string;
+ t, u : int;
+ a, b : list of string;
+ n : int;
+ p : array of ref FD;
+ c : chan of int;
+
+ p = array[2] of ref FD;
+ s = m.w.wreadall();
+ a = "sendmail" :: nil;
+ if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:"))
+ s = s[5:];
+ for(t=0; t < len s && s[t]!='\n' && s[t]!='\t';){
+ while(t < len s && (s[t]==' ' || s[t]==','))
+ t++;
+ u = t;
+ while(t < len s && s[t]!=' ' && s[t]!=',' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ if(t == u)
+ break;
+ a = s[u:t] :: a;
+ }
+ b = nil;
+ for ( ; a != nil; a = tl a)
+ b = hd a :: b;
+ a = b;
+ while(t < len s && s[t]!='\n')
+ t++;
+ if(s[t] == '\n')
+ t++;
+ if(pipe(p) < 0)
+ error("can't pipe: %r");
+ c = chan of int;
+ spawn run(a, c, p[0]);
+ <-c;
+ c = nil;
+ p[0] = nil;
+ n = len s - t;
+ if(swrite(p[1], s[t:]) != n)
+ fprint(stderr, "write to pipe failed: %r\n");
+ p[1] = nil;
+ # run() frees the arg list
+ buf = sprint("Mail/box/%d-R", m.id);
+ m.w.wname(buf);
+ m.w.wclean();
+}
+
+Box.read(readonly : int) : ref Box
+{
+ b : ref Box;
+ m : ref Mesg;
+ buf : string;
+
+ b = ref Box;
+ b.nm = 0;
+ b.leng = 0;
+ b.readonly = readonly;
+ pop3open();
+ pop3init(b);
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ m.id = b.nm;
+ }
+ pop3close();
+ if (b.leng != b.nm)
+ error("bad message count in Box.read()");
+ b.w = Win.wnew();
+ for(m=b.m; m != nil; m=m.next){
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wwritebody(buf);
+ }
+ buf = sprint("Mail/box/");
+ b.w.wname(buf);
+ if(b.readonly)
+ b.w.wtagwrite("Mail");
+ else
+ b.w.wtagwrite("Put Mail");
+ buf = "Mail " + "box";
+ b.w.wsetdump("/acme/mail", buf);
+ b.w.wclean();
+ b.w.wselect("0");
+ b.w.wdormant();
+ b.cdel= chan of ref Mesg;
+ b.cevent = chan of Event;
+ b.cmore = chan of int;
+ spawn b.w.wslave(b.cevent);
+ b.clean = True;
+ return b;
+}
+
+Box.readmore(b : self ref Box)
+{
+ m : ref Mesg;
+ new : int;
+ buf : string;
+
+ new = False;
+ leng := b.leng;
+ n := 0;
+ pop3open();
+ pop3more(b);
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ n++;
+ m.id = b.nm;
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wreplace("0", buf);
+ new = True;
+ }
+ pop3close();
+ if (b.leng != leng+n)
+ error("bad message count in Box.readmore()");
+ if(new){
+ if(b.clean)
+ b.w.wclean();
+ b.w.wselect("0;/.*(\\n[ \t].*)*");
+ b.w.wshow();
+ }
+ b.w.wdormant();
+}
+
+Box.readline(b : self ref Box) : string
+{
+ for (;;) {
+ if(b.peekline != nil){
+ b.line = b.peekline;
+ b.peekline = nil;
+ }else
+ b.line = pop3next(b);
+ # nulls appear in mailboxes!
+ if(b.line != nil && strchr(b.line, 0) >= 0)
+ ;
+ else
+ break;
+ }
+ return b.line;
+}
+
+Box.unreadline(b : self ref Box)
+{
+ b.peekline = b.line;
+}
+
+Box.slave(b : self ref Box)
+{
+ e : ref Event;
+ m : ref Mesg;
+
+ e = newevent();
+ for(;;){
+ alt{
+ *e = <-b.cevent =>
+ b.event(e);
+ break;
+ <-b.cmore =>
+ b.readmore();
+ break;
+ m = <-b.cdel =>
+ b.mdel(m);
+ break;
+ }
+ }
+}
+
+Box.event(b : self ref Box, e : ref Event)
+{
+ e2, ea, eq : ref Event;
+ s : string;
+ t : int;
+ n, na, nopen : int;
+
+ e2 = newevent();
+ ea = newevent();
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' => # type away; we don't care
+ break;
+ 'M' =>
+ case(e.c2){
+ 'x' or 'X' =>
+ if(e.flag & 2)
+ *e2 = <-b.cevent;
+ if(e.flag & 8){
+ *ea = <-b.cevent;
+ na = ea.nb;
+ <- b.cevent;
+ }else
+ na = 0;
+ s = string e.b[0:e.nb];
+ # if it's a known command, do it
+ if((e.flag&2) && e.nb==0)
+ s = string e2.b[0:e2.nb];
+ if(na)
+ s = sprint("%s %s", s, string ea.b[0:ea.nb]);
+ # if it's a long message, it can't be for us anyway
+ if(!b.command(s)) # send it back
+ b.w.wwriteevent(e);
+ if(na)
+ s = nil;
+ break;
+ 'l' or 'L' =>
+ eq = e;
+ if(e.flag & 2){
+ *e2 = <-b.cevent;
+ eq = e2;
+ }
+ s = string eq.b[0:eq.nb];
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = b.w.wread(eq.q0, eq.q1);
+ nopen = 0;
+ do{
+ t = 0;
+ (n, t) = strtoi(s);
+ if(n>0 && (t == len s || s[t]==' ' || s[t]=='\t' || s[t]=='\n')){
+ b.mopen(n);
+ nopen++;
+ s = s[t:];
+ }
+ while(s != nil && s[0]!='\n')
+ s = s[1:];
+ }while(s != nil);
+ if(nopen == 0) # send it back
+ b.w.wwriteevent(e);
+ break;
+ 'I' or 'D' or 'd' or 'i' => # modify away; we don't care
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+}
+
+Box.mopen(b : self ref Box, id : int)
+{
+ m : ref Mesg;
+
+ for(m=b.m; m != nil; m=m.next)
+ if(m.id == id){
+ m.open();
+ break;
+ }
+}
+
+Box.mdel(b : self ref Box, dm : ref Mesg)
+{
+ m : ref Mesg;
+ buf : string;
+
+ if(dm.id){
+ for(m=b.m; m!=nil && m!=dm; m=m.next)
+ ;
+ if(m == nil)
+ error(sprint("message %d not found", dm.id));
+ m.deleted = 1;
+ # remove from screen: use acme to help
+ buf = sprint("/^%d .*\\n(^[ \t].*\\n)*/", m.id);
+ b.w.wreplace(buf, "");
+ }
+ dm.free();
+ b.clean = False;
+}
+
+Box.command(b : self ref Box, s : string) : int
+{
+ t : int;
+ m : ref Mesg;
+
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(len s >= 4 && s[0:4] == "Mail"){
+ s = s[4:];
+ while(s != nil && (s[0]==' ' || s[0]=='\t' || s[0]=='\n'))
+ s = s[1:];
+ t = 0;
+ while(t < len s && s[t] && s[t]!=' ' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ m = b.m; # avoid warning message on b.m.mkmail(...)
+ m.mkmail(b, s[0:t]);
+ return True;
+ }
+ if(s == "Del"){
+
+ if(!b.clean){
+ b.clean = True;
+ fprint(stderr, "mail: mailbox not written\n");
+ return True;
+ }
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ pctl(NEWPGRP, nil);
+ b.w.wdel(True);
+ for(m=b.m; m != nil; m=m.next)
+ m.w.wdel(False);
+ exit;
+ return True;
+ }
+ if(s == "Put"){
+ if(b.readonly)
+ fprint(stderr, "Mail is read-only\n");
+ else
+ b.rewrite();
+ return True;
+ }
+ return False;
+}
+
+Box.rewrite(b : self ref Box)
+{
+ prev, m : ref Mesg;
+
+ if(b.clean){
+ b.w.wclean();
+ return;
+ }
+ prev = nil;
+ pop3open();
+ for(m=b.m; m!=nil; m=m.next) {
+ if (m.deleted && pop3del(m.popno) >= 0) {
+ b.leng--;
+ if (prev == nil)
+ b.m=m.next;
+ else
+ prev.next=m.next;
+ }
+ else
+ prev = m;
+ }
+ pop3close();
+ b.w.wclean();
+ b.clean = True;
+}
--- /dev/null
+++ b/appl/acme/acme/mail/src/Mailpop3.b
@@ -1,0 +1,1720 @@
+implement Mailpop3;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+include "daytime.m";
+include "sh.m";
+include "pop3.m";
+
+Mailpop3 : module {
+ init : fn(ctxt : ref Draw->Context, argl : list of string);
+};
+
+sys : Sys;
+bufio : Bufio;
+daytime : Daytime;
+pop3 : Pop3;
+
+OREAD, OWRITE, ORDWR, FORKENV, NEWFD, FORKFD, NEWPGRP, UTFmax : import Sys;
+FD, Dir : import sys;
+fprint, sprint, sleep, create, open, read, write, remove, stat, fstat, fwstat, fildes, pctl, pipe, dup, byte2char : import sys;
+Context : import Draw;
+EOF : import Bufio;
+Iobuf : import bufio;
+time : import daytime;
+
+DIRLEN : con 116;
+PNPROC, PNGROUP : con iota;
+False : con 0;
+True : con 1;
+EVENTSIZE : con 256;
+Runeself : con 16r80;
+OCEXEC : con 0;
+CHEXCL : con 0; # 16r20000000;
+CHAPPEND : con 0; # 16r40000000;
+
+Win : adt {
+ winid : int;
+ addr : ref FD;
+ body : ref Iobuf;
+ ctl : ref FD;
+ data : ref FD;
+ event : ref FD;
+ buf : array of byte;
+ bufp : int;
+ nbuf : int;
+
+ wnew : fn() : ref Win;
+ wwritebody : fn(w : self ref Win, s : string);
+ wread : fn(w : self ref Win, m : int, n : int) : string;
+ wclean : fn(w : self ref Win);
+ wname : fn(w : self ref Win, s : string);
+ wdormant : fn(w : self ref Win);
+ wevent : fn(w : self ref Win, e : ref Event);
+ wshow : fn(w : self ref Win);
+ wtagwrite : fn(w : self ref Win, s : string);
+ wwriteevent : fn(w : self ref Win, e : ref Event);
+ wslave : fn(w : self ref Win, c : chan of Event);
+ wreplace : fn(w : self ref Win, s : string, t : string);
+ wselect : fn(w : self ref Win, s : string);
+ wsetdump : fn(w : self ref Win, s : string, t : string);
+ wdel : fn(w : self ref Win, n : int) : int;
+ wreadall : fn(w : self ref Win) : string;
+
+ ctlwrite : fn(w : self ref Win, s : string);
+ getec : fn(w : self ref Win) : int;
+ geten : fn(w : self ref Win) : int;
+ geter : fn(w : self ref Win, s : array of byte) : (int, int);
+ openfile : fn(w : self ref Win, s : string) : ref FD;
+ openbody : fn(w : self ref Win, n : int);
+};
+
+Mesg : adt {
+ w : ref Win;
+ id : int;
+ popno : int;
+ hdr : string;
+ realhdr : string;
+ replyto : string;
+ text : string;
+ subj : string;
+ next : cyclic ref Mesg;
+ lline1 : int;
+ box : cyclic ref Box;
+ isopen : int;
+ posted : int;
+ deleted : int;
+
+ read : fn(b : ref Box) : ref Mesg;
+ open : fn(m : self ref Mesg);
+ slave : fn(m : self ref Mesg);
+ free : fn(m : self ref Mesg);
+ save : fn(m : self ref Mesg, s : string);
+ mkreply : fn(m : self ref Mesg);
+ mkmail : fn(b : ref Box, s : string);
+ putpost : fn(m : self ref Mesg, e : ref Event);
+
+ command : fn(m : self ref Mesg, s : string) : int;
+ send : fn(m : self ref Mesg);
+};
+
+Box : adt {
+ w : ref Win;
+ nm : int;
+ readonly : int;
+ m : cyclic ref Mesg;
+# io : ref Iobuf;
+ clean : int;
+ leng : int;
+ cdel : chan of ref Mesg;
+ cevent : chan of Event;
+ cmore : chan of int;
+ lst : list of int;
+ s : string;
+
+ line : string;
+ popno : int;
+ peekline : string;
+
+ read : fn(n : int) : ref Box;
+ readmore : fn(b : self ref Box, lck : int);
+ readline : fn(b : self ref Box) : string;
+ unreadline : fn(b : self ref Box);
+ slave : fn(b : self ref Box);
+ mopen : fn(b : self ref Box, n : int);
+ rewrite : fn(b : self ref Box);
+ mdel : fn(b : self ref Box, m : ref Mesg);
+ event : fn(b : self ref Box, e : ref Event);
+
+ command : fn(b : self ref Box, s : string) : int;
+};
+
+Event : adt {
+ c1 : int;
+ c2 : int;
+ q0 : int;
+ q1 : int;
+ flag : int;
+ nb : int;
+ nr : int;
+ b : array of byte;
+ r : array of int;
+};
+
+Lock : adt {
+ cnt : int;
+ chann : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+};
+
+Ref : adt {
+ l : ref Lock;
+ cnt : int;
+
+ init : fn() : ref Ref;
+ inc : fn(r : self ref Ref) : int;
+};
+
+mbox : ref Box;
+user : string;
+pwd : string;
+date : string;
+mailctxt : ref Context;
+stdout, stderr : ref FD;
+
+killing : int = 0;
+
+init(ctxt : ref Context, nil : list of string)
+{
+ mailctxt = ctxt;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ pop3 = load Pop3 Pop3->PATH;
+ stdout = fildes(1);
+ stderr = fildes(2);
+ main();
+}
+
+dlock : ref Lock;
+dfd : ref Sys->FD;
+
+debug(s : string)
+{
+ if (dfd == nil) {
+ dfd = sys->create("/usr/jrf/acme/debugmail", Sys->OWRITE, 8r600);
+ dlock = Lock.init();
+ }
+ if (dfd == nil)
+ return;
+ dlock.lock();
+ sys->fprint(dfd, "%s", s);
+ dlock.unlock();
+}
+
+postnote(t : int, pid : int, note : string) : int
+{
+ fd := open("#p/" + string pid + "/ctl", OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ fprint(fd, "%s", note);
+ fd = nil;
+ return 0;
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sprint("%r");
+ }
+ if(c == nil){
+ fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(mailctxt, argl);
+}
+
+swrite(fd : ref FD, s : string) : int
+{
+ ab := array of byte s;
+ m := len ab;
+ p := write(fd, ab, m);
+ if (p == m)
+ return len s;
+ if (p <= 0)
+ return p;
+ return 0;
+}
+
+strchr(s : string, c : int) : int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strrchr(s : string, c : int) : int
+{
+ for (i := len s - 1; i >= 0; i--)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strtoi(s : string) : (int, int)
+{
+ m := 0;
+ neg := 0;
+ t := 0;
+ ls := len s;
+ while (t < ls && (s[t] == ' ' || s[t] == '\t'))
+ t++;
+ if (t < ls && s[t] == '+')
+ t++;
+ else if (t < ls && s[t] == '-') {
+ neg = 1;
+ t++;
+ }
+ while (t < ls && (s[t] >= '0' && s[t] <= '9')) {
+ m = 10*m + s[t]-'0';
+ t++;
+ }
+ if (neg)
+ m = -m;
+ return (m, t);
+}
+
+access(s : string) : int
+{
+ fd := open(s, 0);
+ if (fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+newevent() : ref Event
+{
+ e := ref Event;
+ e.b = array[EVENTSIZE*UTFmax+1] of byte;
+ e.r = array[EVENTSIZE+1] of int;
+ return e;
+}
+
+newmesg() : ref Mesg
+{
+ m := ref Mesg;
+ m.id = m.lline1 = m.isopen = m.posted = m.deleted = 0;
+ return m;
+}
+
+lc, uc : chan of ref Lock;
+
+initlock()
+{
+ lc = chan of ref Lock;
+ uc = chan of ref Lock;
+ spawn lockmgr();
+}
+
+lockmgr()
+{
+ l : ref Lock;
+
+ for (;;) {
+ alt {
+ l = <- lc =>
+ if (l.cnt++ == 0)
+ l.chann <-= 1;
+ l = <- uc =>
+ if (--l.cnt > 0)
+ l.chann <-= 1;
+ }
+ }
+}
+
+Lock.init() : ref Lock
+{
+ return ref Lock(0, chan of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ lc <-= l;
+ <- l.chann;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ uc <-= l;
+}
+
+Ref.init() : ref Ref
+{
+ r := ref Ref;
+ r.l = Lock.init();
+ r.cnt = 0;
+ return r;
+}
+
+Ref.inc(r : self ref Ref) : int
+{
+ r.l.lock();
+ i := r.cnt;
+ r.cnt++;
+ r.l.unlock();
+ return i;
+}
+
+error(s : string)
+{
+ if(s != nil)
+ fprint(stderr, "mail: %s\n", s);
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ exit;
+}
+
+tryopen(s : string, mode : int) : ref FD
+{
+ fd : ref FD;
+ try : int;
+
+ for(try=0; try<3; try++){
+ fd = open(s, mode);
+ if(fd != nil)
+ return fd;
+ sleep(1000);
+ }
+ return nil;
+}
+
+run(argv : list of string, c : chan of int, p0 : ref FD)
+{
+ # pctl(FORKFD|NEWPGRP, nil); # had RFMEM
+ pctl(FORKENV|NEWFD|NEWPGRP, 0::1::2::p0.fd::nil);
+ c <-= pctl(0, nil);
+ dup(p0.fd, 0);
+ p0 = nil;
+ exec(hd argv, argv);
+ exit;
+}
+
+getuser() : string
+{
+ fd := open("/dev/user", OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[128] of byte;
+ n := read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0:n];
+}
+
+pop3conn : int = 0;
+pop3bad : int = 0;
+pop3lock : ref Lock;
+
+pop3open(lck : int)
+{
+ if (lck)
+ pop3lock.lock();
+ if (!pop3conn) {
+ (ok, s) := pop3->open(user, pwd, nil);
+ if (ok < 0) {
+ if (!pop3bad) {
+ fprint(stderr, "mail: could not connect to POP3 mail server : %s\n", s);
+ pop3bad = 1;
+ }
+ return;
+ }
+ }
+ pop3conn = 1;
+ pop3bad = 0;
+}
+
+pop3close(unlck : int)
+{
+ if (pop3conn) {
+ (ok, s) := pop3->close();
+ if (ok < 0) {
+ fprint(stderr, "mail: could not close POP3 connection : %s\n", s);
+ pop3lock.unlock();
+ return;
+ }
+ }
+ pop3conn = 0;
+ if (unlck)
+ pop3lock.unlock();
+}
+
+pop3stat(b : ref Box) : int
+{
+ (ok, s, nm, nil) := pop3->stat();
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not stat POP3 server : %s\n", s);
+ return b.leng;
+ }
+ return nm;
+}
+
+pop3list() : list of int
+{
+ (ok, s, l) := pop3->msgnolist();
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not get list from POP3 server : %s\n", s);
+ return nil;
+ }
+ return l;
+}
+
+pop3mesg(mno : int) : string
+{
+ (ok, s, msg) := pop3->get(mno);
+ if (ok < 0 && pop3conn) {
+ fprint(stderr, "mail: could not retrieve a message from server : %s\n", s);
+ return "Acme Mail : FAILED TO RETRIEVE MESSAGE\n";
+ }
+ return msg;
+}
+
+pop3del(mno : int) : int
+{
+ (ok, s) := pop3->delete(mno);
+ if (ok < 0)
+ fprint(stderr, "mail: could not delete message : %s\n", s);
+ return ok;
+}
+
+pop3init(b : ref Box)
+{
+ b.leng = pop3stat(b);
+ b.lst = pop3list();
+ b.s = nil;
+ b.popno = 0;
+ if (len b.lst != b.leng)
+ error("bad lengths in pop3init()");
+}
+
+pop3more(b : ref Box)
+{
+ nl : list of int;
+
+ leng := b.leng;
+ b.leng = pop3stat(b);
+ b.lst = pop3list();
+ b.s = nil;
+ b.popno = 0;
+ if (len b.lst != b.leng || b.leng < leng)
+ error("bad lengths in pop3more()");
+ # is this ok ?
+ nl = nil;
+ for (i := 0; i < leng; i++) {
+ nl = hd b.lst :: nl;
+ b.lst = tl b.lst;
+ }
+ # now update pop nos.
+ for (m := b.m; m != nil; m = m.next) {
+ # opopno := m.popno;
+ if (nl == nil)
+ error("message list too big");
+ m.popno = hd nl;
+ nl = tl nl;
+ # debug(sys->sprint("%d : popno from %d to %d\n", m.id, opopno, m.popno));
+ }
+ if (nl != nil)
+ error("message list too small");
+}
+
+pop3next(b : ref Box) : string
+{
+ mno : int = 0;
+ r : string;
+
+ if (b.s == nil) {
+ if (b.lst == nil)
+ return nil; # end of box
+ first := b.popno == 0;
+ mno = hd b.lst;
+ b.lst = tl b.lst;
+ b.s = pop3mesg(mno);
+ b.popno = mno;
+ if (!first)
+ return nil; # end of message
+ }
+ t := strchr(b.s, '\n');
+ if (t >= 0) {
+ r = b.s[0:t+1];
+ b.s = b.s[t+1:];
+ }
+ else {
+ r = b.s;
+ b.s = nil;
+ }
+ return r;
+}
+
+main()
+{
+ readonly : int;
+
+ initlock();
+ initreply();
+ date = time();
+ if(date==nil)
+ error("can't get current time");
+ user = getuser();
+ if(user == nil)
+ user = "Wile.E.Coyote";
+ readonly = False;
+ pop3lock = Lock.init();
+ mbox = mbox.read(readonly);
+ spawn timeslave(mbox, mbox.cmore);
+ mbox.slave();
+ error(nil);
+}
+
+timeslave(b : ref Box, c : chan of int)
+{
+ for(;;){
+ sleep(30*1000);
+ pop3open(1);
+ leng := pop3stat(b);
+ pop3close(1);
+ if (leng > b.leng)
+ c <-= 0;
+ }
+}
+
+Win.wnew() : ref Win
+{
+ w := ref Win;
+ buf := array[12] of byte;
+ w.ctl = open("/chan/new/ctl", ORDWR);
+ if(w.ctl==nil || read(w.ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ w.ctlwrite("noscroll\n");
+ w.winid = int string buf;
+ w.event = w.openfile("event");
+ w.addr = nil; # will be opened when needed
+ w.body = nil;
+ w.data = nil;
+ w.bufp = w.nbuf = 0;
+ w.buf = array[512] of byte;
+ return w;
+}
+
+Win.openfile(w : self ref Win, f : string) : ref FD
+{
+ buf := sprint("/chan/%d/%s", w.winid, f);
+ fd := open(buf, ORDWR|OCEXEC);
+ if(fd == nil)
+ error(sprint("can't open window %s file: %r", f));
+ return fd;
+}
+
+Win.openbody(w : self ref Win, mode : int)
+{
+ buf := sprint("/chan/%d/body", w.winid);
+ w.body = bufio->open(buf, mode|OCEXEC);
+ if(w.body == nil)
+ error("can't open window body file: %r");
+}
+
+Win.wwritebody(w : self ref Win, s : string)
+{
+ n := len s;
+ if(w.body == nil)
+ w.openbody(OWRITE);
+ if(w.body.puts(s) != n)
+ error("write error to window: %r");
+}
+
+Win.wreplace(w : self ref Win, addr : string, repl : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ if(swrite(w.addr, addr) < 0){
+ fprint(stderr, "mail: warning: bad address %s:%r\n", addr);
+ return;
+ }
+ if(swrite(w.data, repl) != len repl)
+ error("writing data: %r");
+}
+
+nrunes(s : array of byte, nb : int) : int
+{
+ i, n : int;
+
+ n = 0;
+ for(i=0; i<nb; n++) {
+ (nil, b, ok) := byte2char(s, i);
+ if (!ok)
+ error("help needed in nrunes()");
+ i += b;
+ }
+ return n;
+}
+
+Win.wread(w : self ref Win, q0 : int, q1 : int) : string
+{
+ m, n, nr : int;
+ s, buf : string;
+ b : array of byte;
+
+ b = array[256] of byte;
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(w.data == nil)
+ w.data = w.openfile("data");
+ s = nil;
+ m = q0;
+ while(m < q1){
+ buf = sprint("#%d", m);
+ if(swrite(w.addr, buf) != len buf)
+ error("writing addr: %r");
+ n = read(w.data, b, len b);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(b, n);
+ while(m+nr >q1){
+ do; while(n>0 && (int b[--n]&16rC0)==16r80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ s += string b[0:n];
+ m += nr;
+ }
+ return s;
+}
+
+Win.wshow(w : self ref Win)
+{
+ w.ctlwrite("show\n");
+}
+
+Win.wsetdump(w : self ref Win, dir : string, cmd : string)
+{
+ t : string;
+
+ if(dir != nil){
+ t = "dumpdir " + dir + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+ if(cmd != nil){
+ t = "dump " + cmd + "\n";
+ w.ctlwrite(t);
+ t = nil;
+ }
+}
+
+Win.wselect(w : self ref Win, addr : string)
+{
+ if(w.addr == nil)
+ w.addr = w.openfile("addr");
+ if(swrite(w.addr, addr) < 0)
+ error("writing addr");
+ w.ctlwrite("dot=addr\n");
+}
+
+Win.wtagwrite(w : self ref Win, s : string)
+{
+ fd : ref FD;
+
+ fd = w.openfile("tag");
+ if(swrite(fd, s) != len s)
+ error("tag write: %r");
+ fd = nil;
+}
+
+Win.ctlwrite(w : self ref Win, s : string)
+{
+ if(swrite(w.ctl, s) != len s)
+ error("write error to ctl file: %r");
+}
+
+Win.wdel(w : self ref Win, sure : int) : int
+{
+ if (w == nil)
+ return False;
+ if(sure)
+ swrite(w.ctl, "delete\n");
+ else if(swrite(w.ctl, "del\n") != 4)
+ return False;
+ w.wdormant();
+ w.ctl = nil;
+ w.event = nil;
+ return True;
+}
+
+Win.wname(w : self ref Win, s : string)
+{
+ w.ctlwrite("name " + s + "\n");
+}
+
+Win.wclean(w : self ref Win)
+{
+ if(w.body != nil)
+ w.body.flush();
+ w.ctlwrite("clean\n");
+}
+
+Win.wdormant(w : self ref Win)
+{
+ w.addr = nil;
+ if(w.body != nil){
+ w.body.close();
+ w.body = nil;
+ }
+ w.data = nil;
+}
+
+Win.getec(w : self ref Win) : int
+{
+ if(w.nbuf == 0){
+ w.nbuf = read(w.event, w.buf, len w.buf);
+ if(w.nbuf <= 0 && !killing) {
+ error("event read error: %r");
+ }
+ w.bufp = 0;
+ }
+ w.nbuf--;
+ return int w.buf[w.bufp++];
+}
+
+Win.geten(w : self ref Win) : int
+{
+ n, c : int;
+
+ n = 0;
+ while('0'<=(c=w.getec()) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+Win.geter(w : self ref Win, buf : array of byte) : (int, int)
+{
+ r, m, n, ok : int;
+
+ r = w.getec();
+ buf[0] = byte r;
+ n = 1;
+ if(r >= Runeself) {
+ for (;;) {
+ (r, m, ok) = byte2char(buf[0:n], 0);
+ if (m > 0)
+ return (r, n);
+ buf[n++] = byte w.getec();
+ }
+ }
+ return (r, n);
+}
+
+Win.wevent(w : self ref Win, e : ref Event)
+{
+ i, nb : int;
+
+ e.c1 = w.getec();
+ e.c2 = w.getec();
+ e.q0 = w.geten();
+ e.q1 = w.geten();
+ e.flag = w.geten();
+ e.nr = w.geten();
+ if(e.nr > EVENTSIZE)
+ error("event string too long");
+ e.nb = 0;
+ for(i=0; i<e.nr; i++){
+ (e.r[i], nb) = w.geter(e.b[e.nb:]);
+ e.nb += nb;
+ }
+ e.r[e.nr] = 0;
+ e.b[e.nb] = byte 0;
+ c := w.getec();
+ if(c != '\n')
+ error("event syntax 2");
+}
+
+Win.wslave(w : self ref Win, ce : chan of Event)
+{
+ e : ref Event;
+
+ e = newevent();
+ for(;;){
+ w.wevent(e);
+ ce <-= *e;
+ }
+}
+
+Win.wwriteevent(w : self ref Win, e : ref Event)
+{
+ fprint(w.event, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
+}
+
+Win.wreadall(w : self ref Win) : string
+{
+ s, t : string;
+
+ if(w.body != nil)
+ w.body.close();
+ w.openbody(OREAD);
+ s = nil;
+ while ((t = w.body.gets('\n')) != nil)
+ s += t;
+ w.body.close();
+ w.body = nil;
+ return s;
+}
+
+None, Unknown, Ignore, CC, From, ReplyTo, Sender, Subject, Re, To, Date, Received : con iota;
+NHeaders : con 200;
+
+Hdrs : adt {
+ name : string;
+ typex : int;
+};
+
+
+hdrs := array[NHeaders+1] of {
+ Hdrs ( "CC:", CC ),
+ Hdrs ( "From:", From ),
+ Hdrs ( "Reply-To:", ReplyTo ),
+ Hdrs ( "Sender:", Sender ),
+ Hdrs ( "Subject:", Subject ),
+ Hdrs ( "Re:", Re ),
+ Hdrs ( "To:", To ),
+ Hdrs ( "Date:", Date),
+ Hdrs ( "Received:", Received),
+ * => Hdrs ( "", 0 ),
+};
+
+StRnCmP(s : string, t : string, n : int) : int
+{
+ c, d, i, j : int;
+
+ i = j = 0;
+ if (len s < n || len t < n)
+ return -1;
+ while(n > 0){
+ c = s[i++];
+ d = t[j++];
+ --n;
+ if(c != d){
+ if('a'<=c && c<='z')
+ c -= 'a'-'A';
+ if('a'<=d && d<='z')
+ d -= 'a'-'A';
+ if(c != d)
+ return c-d;
+ }
+ }
+ return 0;
+}
+
+readhdr(b : ref Box) : (string, int)
+{
+ i, j, n, m, typex : int;
+ s, t : string;
+
+{
+ s = b.readline();
+ n = len s;
+ if(n <= 0) {
+ b.unreadline();
+ raise("e");
+ }
+ for(i=0; i<n; i++){
+ j = s[i];
+ if(i>0 && j == ':')
+ break;
+ if(j<'!' || '~'<j){
+ b.unreadline();
+ raise("e");
+ }
+ }
+ typex = Unknown;
+ for(i=0; hdrs[i].name != nil; i++){
+ j = len hdrs[i].name;
+ if(StRnCmP(hdrs[i].name, s, j) == 0){
+ typex = hdrs[i].typex;
+ break;
+ }
+ }
+ # scan for multiple sublines
+ for(;;){
+ t = b.readline();
+ m = len t;
+ if(m<=0 || (t[0]!=' ' && t[0]!='\t')){
+ b.unreadline();
+ break;
+ }
+ # absorb
+ s += t;
+ }
+ return(s, typex);
+}
+exception{
+ "*" =>
+ return (nil, None);
+}
+}
+
+Mesg.read(b : ref Box) : ref Mesg
+{
+ m : ref Mesg;
+ s : string;
+ n, typex : int;
+
+ for(;;){
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ return nil;
+ if(n >= 5 && (s[0:5] == "From:" || s[0:5] == "From "))
+ break;
+ }
+{
+ if(n < 5 || (s[0:5] !="From " && s[0:5] != "From:"))
+ raise("e");
+ m = newmesg();
+ m.popno = b.popno;
+ if (m.popno == 0)
+ error("bad pop3 id");
+ m.realhdr = s;
+ # toss 'From '
+ s = s[5:];
+ n -= 5;
+ # toss spaces/tabs
+ while (n > 0 && (s[0] == ' ' || s[0] == '\t')) {
+ s = s[1:];
+ n--;
+ }
+ m.hdr = s;
+ # convert first blank to tab
+ s0 := strchr(m.hdr, ' ');
+ if(s0 >= 0){
+ m.hdr[s0] = '\t';
+ # drop trailing seconds, time zone, and year if match local year
+ t := n-6;
+ if(t <= 0)
+ raise("e");
+ if(m.hdr[t:n-1] == date[23:]){
+ m.hdr = m.hdr[0:t] + "\n"; # drop year for sure
+ t = -1;
+ s1 := strchr(m.hdr[s0:], ':');
+ if(s1 >= 0)
+ t = strchr(m.hdr[s0+s1+1:], ':');
+ if(t >= 0) # drop seconds and time zone
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ else{ # drop time zone
+ t = strchr(m.hdr[s0+s1+1:], ' ');
+ if(t >= 0)
+ m.hdr = m.hdr[0:s0+s1+t+1] + "\n";
+ }
+ n = len m.hdr;
+ }
+ }
+ m.lline1 = n;
+ m.text = nil;
+ # read header
+loop:
+ for(;;){
+ (s, typex) = readhdr(b);
+ case(typex){
+ None =>
+ break loop;
+ ReplyTo =>
+ m.replyto = s[9:];
+ break;
+ From =>
+ if(m.replyto == nil)
+ m.replyto = s[5:];
+ break;
+ Subject =>
+ m.subj = s[8:];
+ break;
+ Re =>
+ m.subj = s[3:];
+ break;
+ Date =>
+ break;
+ }
+ m.realhdr += s;
+ if(typex != Ignore)
+ m.hdr += s;
+ }
+ # read body
+ for(;;){
+ s = b.readline();
+ n = len s;
+ if(n <= 0)
+ break;
+# if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:")){
+# b.unreadline();
+# break;
+# }
+ m.text += s;
+ }
+ # remove trailing "morF\n"
+ l := len m.text;
+ if(l>6 && m.text[l-6:] == "\nmorF\n")
+ m.text = m.text[0:l-5];
+ m.box = b;
+ return m;
+}
+exception{
+ "*" =>
+ error("malformed header " + s);
+ return nil;
+}
+}
+
+Mesg.mkmail(b : ref Box, hdr : string)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ r.hdr = hdr + "\n";
+ r.lline1 = len r.hdr;
+ r.text = nil;
+ r.box = b;
+ r.open();
+ r.w.wdormant();
+}
+
+replyaddr(r : string) : string
+{
+ p, q, rr : int;
+
+ rr = 0;
+ while(r[rr]==' ' || r[rr]=='\t')
+ rr++;
+ r = r[rr:];
+ p = strchr(r, '<');
+ if(p >= 0){
+ q = strchr(r[p+1:], '>');
+ if(q < 0)
+ r = r[p+1:];
+ else
+ r = r[p+1:p+q] + "\n";
+ return r;
+ }
+ p = strchr(r, '(');
+ if(p >= 0){
+ q = strchr(r[p:], ')');
+ if(q < 0)
+ r = r[0:p];
+ else
+ r = r[0:p] + r[p+q+1:];
+ }
+ return r;
+}
+
+Mesg.mkreply(m : self ref Mesg)
+{
+ r : ref Mesg;
+
+ r = newmesg();
+ if(m.replyto != nil){
+ r.hdr = replyaddr(m.replyto);
+ r.lline1 = len r.hdr;
+ }else{
+ r.hdr = m.hdr[0:m.lline1];
+ r.lline1 = m.lline1; # was len m.hdr;
+ }
+ if(m.subj != nil){
+ if(StRnCmP(m.subj, "re:", 3)==0 || StRnCmP(m.subj, " re:", 4)==0)
+ r.text = "Subject:" + m.subj + "\n";
+ else
+ r.text = "Subject: Re:" + m.subj + "\n";
+ }
+ else
+ r.text = nil;
+ r.box = m.box;
+ r.open();
+ r.w.wselect("$");
+ r.w.wdormant();
+}
+
+Mesg.free(m : self ref Mesg)
+{
+ m.text = nil;
+ m.hdr = nil;
+ m.subj = nil;
+ m.realhdr = nil;
+ m.replyto = nil;
+ m = nil;
+}
+
+replyid : ref Ref;
+
+initreply()
+{
+ replyid = Ref.init();
+}
+
+Mesg.open(m : self ref Mesg)
+{
+ buf, s : string;
+
+ if(m.isopen)
+ return;
+ m.w = Win.wnew();
+ if(m.id != 0)
+ m.w.wwritebody("From ");
+ m.w.wwritebody(m.hdr);
+ m.w.wwritebody(m.text);
+ if(m.id){
+ buf = sprint("Mail/box/%d", m.id);
+ m.w.wtagwrite("Reply Delmesg Save");
+ }else{
+ buf = sprint("Mail/%s/Reply%d", s, replyid.inc());
+ m.w.wtagwrite("Post");
+ }
+ m.w.wname(buf);
+ m.w.wclean();
+ m.w.wselect("0");
+ m.isopen = True;
+ m.posted = False;
+ spawn m.slave();
+}
+
+Mesg.putpost(m : self ref Mesg, e : ref Event)
+{
+ if(m.posted || m.id==0)
+ return;
+ if(e.q0 >= len m.hdr+5) # include "From "
+ return;
+ m.w.wtagwrite(" Post");
+ m.posted = True;
+ return;
+}
+
+Mesg.slave(m : self ref Mesg)
+{
+ e, e2, ea, etoss, eq : ref Event;
+ s : string;
+ na : int;
+
+ e = newevent();
+ e2 = newevent();
+ ea = newevent();
+ etoss = newevent();
+ for(;;){
+ m.w.wevent(e);
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' or 'M' => # type away; we don't care
+ case(e.c2){
+ 'x' or 'X' => # mouse only
+ eq = e;
+ if(e.flag & 2){
+ m.w.wevent(e2);
+ eq = e2;
+ }
+ if(e.flag & 8){
+ m.w.wevent(ea);
+ m.w.wevent(etoss);
+ na = ea.nb;
+ }else
+ na = 0;
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = m.w.wread(eq.q0, eq.q1);
+ else
+ s = string eq.b[0:eq.nb];
+ if(na)
+ s = s + " " + string ea.b[0:ea.nb];
+ if(!m.command(s)) # send it back
+ m.w.wwriteevent(e);
+ s = nil;
+ break;
+ 'l' or 'L' => # mouse only
+ if(e.flag & 2)
+ m.w.wevent(e2);
+ # just send it back
+ m.w.wwriteevent(e);
+ break;
+ 'I' or 'D' => # modify away; we don't care
+ m.putpost(e);
+ break;
+ 'd' or 'i' =>
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ }
+}
+
+Mesg.command(m : self ref Mesg, s : string) : int
+{
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(s == "Post"){
+ m.send();
+ return True;
+ }
+ if(len s >= 4 && s[0:4] == "Save"){
+ s = s[4:];
+ while(len s > 0 && (s[0]==' ' || s[0]=='\t' || s[0]=='\n'))
+ s = s[1:];
+ if(s == nil)
+ m.save("stored");
+ else{
+ ss := 0;
+ while(ss < len s && s[ss]!=' ' && s[ss]!='\t' && s[ss]!='\n')
+ ss++;
+ m.save(s[0:ss]);
+ }
+ return True;
+ }
+ if(s == "Reply"){
+ m.mkreply();
+ return True;
+ }
+ if(s == "Del"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ exit;
+ }
+ return True;
+ }
+ if(s == "Delmesg"){
+ if(m.w.wdel(False)){
+ m.isopen = False;
+ m.box.cdel <-= m;
+ exit;
+ }
+ return True;
+ }
+ return False;
+}
+
+Mesg.save(m : self ref Mesg, base : string)
+{
+ s, buf : string;
+ n : int;
+ fd : ref FD;
+ b : ref Iobuf;
+
+ if(m.id <= 0){
+ fprint(stderr, "can't save reply message; mail it to yourself\n");
+ return;
+ }
+ buf = nil;
+ s = base;
+{
+ if(access(s) < 0)
+ raise("e");
+ fd = tryopen(s, OWRITE);
+ if(fd == nil)
+ raise("e");
+ buf = nil;
+ b = bufio->fopen(fd, OWRITE);
+ # seek to end in case file isn't append-only
+ b.seek(big 0, 2);
+ # use edited headers: first line of real header followed by remainder of selected ones
+ for(n=0; n<len m.realhdr && m.realhdr[n++]!='\n'; )
+ ;
+ b.puts(m.realhdr[0:n]);
+ b.puts(m.hdr[m.lline1:]);
+ b.puts(m.text);
+ b.close();
+ b = nil;
+ fd = nil;
+}
+exception{
+ "*" =>
+ buf = nil;
+ fprint(stderr, "mail: can't open %s: %r\n", base);
+ return;
+}
+}
+
+Mesg.send(m : self ref Mesg)
+{
+ s, buf : string;
+ t, u : int;
+ a, b : list of string;
+ n : int;
+ p : array of ref FD;
+ c : chan of int;
+
+ p = array[2] of ref FD;
+ s = m.w.wreadall();
+ a = "sendmail" :: nil;
+ if(len s >= 5 && (s[0:5] == "From " || s[0:5] == "From:"))
+ s = s[5:];
+ for(t=0; t < len s && s[t]!='\n' && s[t]!='\t';){
+ while(t < len s && (s[t]==' ' || s[t]==','))
+ t++;
+ u = t;
+ while(t < len s && s[t]!=' ' && s[t]!=',' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ if(t == u)
+ break;
+ a = s[u:t] :: a;
+ }
+ b = nil;
+ for ( ; a != nil; a = tl a)
+ b = hd a :: b;
+ a = b;
+ while(t < len s && s[t]!='\n')
+ t++;
+ if(s[t] == '\n')
+ t++;
+ if(pipe(p) < 0)
+ error("can't pipe: %r");
+ c = chan of int;
+ spawn run(a, c, p[0]);
+ <-c;
+ c = nil;
+ p[0] = nil;
+ n = len s - t;
+ if(swrite(p[1], s[t:]) != n)
+ fprint(stderr, "write to pipe failed: %r\n");
+ p[1] = nil;
+ # run() frees the arg list
+ buf = sprint("Mail/box/%d-R", m.id);
+ m.w.wname(buf);
+ m.w.wclean();
+}
+
+Box.read(readonly : int) : ref Box
+{
+ b : ref Box;
+ m : ref Mesg;
+ buf : string;
+
+ b = ref Box;
+ b.nm = 0;
+ b.leng = 0;
+ b.readonly = readonly;
+ b.w = Win.wnew();
+ b.w.wwritebody("Password:");
+ b.w.wname("Mail/box/");
+ b.w.wclean();
+ b.w.wselect("$");
+ b.w.ctlwrite("noecho\n");
+ b.cevent = chan of Event;
+ spawn b.w.wslave(b.cevent);
+ e := ref Event;
+ for (;;) {
+ sleep(1000);
+ s := b.w.wreadall();
+ lens := len s;
+ if (lens >= 10 && s[0:9] == "Password:" && s[lens-1] == '\n') {
+ pwd = s[9:lens-1];
+ for (i := 0; i < lens; i++)
+ s[i] = '\b';
+ b.w.wwritebody(s);
+ break;
+ }
+ alt {
+ *e = <-b.cevent =>
+ b.event(e);
+ break;
+ * =>
+ break;
+ }
+ }
+ b.w.ctlwrite("echo\n");
+ pop3open(1);
+ pop3init(b);
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ m.id = b.nm;
+ }
+ pop3close(1);
+ if (b.leng != b.nm)
+ error("bad message count in Box.read()");
+ # b.w = Win.wnew();
+ for(m=b.m; m != nil; m=m.next){
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wwritebody(buf);
+ }
+ # b.w.wname("Mail/box/");
+ if(b.readonly)
+ b.w.wtagwrite("Mail");
+ else
+ b.w.wtagwrite("Put Mail");
+ b.w.wsetdump("/acme/mail", "Mail box");
+ b.w.wclean();
+ b.w.wselect("0");
+ b.w.wdormant();
+ b.cdel= chan of ref Mesg;
+ b.cmore = chan of int;
+ b.clean = True;
+ return b;
+}
+
+Box.readmore(b : self ref Box, lck : int)
+{
+ m : ref Mesg;
+ new : int;
+ buf : string;
+
+ new = False;
+ leng := b.leng;
+ n := 0;
+ pop3open(lck);
+ pop3more(b);
+ while((m = m.read(b)) != nil){
+ m.next = b.m;
+ b.m = m;
+ b.nm++;
+ n++;
+ m.id = b.nm;
+ if(m.subj != nil)
+ buf = sprint("%d\t%s\t %s", m.id, m.hdr[0:m.lline1], m.subj);
+ else
+ buf = sprint("%d\t%s", m.id, m.hdr[0:m.lline1]);
+ b.w.wreplace("0", buf);
+ new = True;
+ }
+ pop3close(1);
+ if (b.leng != leng+n)
+ error("bad message count in Box.readmore()");
+ if(new){
+ if(b.clean)
+ b.w.wclean();
+ b.w.wselect("0;/.*(\\n[ \t].*)*");
+ b.w.wshow();
+ }
+ b.w.wdormant();
+}
+
+Box.readline(b : self ref Box) : string
+{
+ for (;;) {
+ if(b.peekline != nil){
+ b.line = b.peekline;
+ b.peekline = nil;
+ }else
+ b.line = pop3next(b);
+ # nulls appear in mailboxes!
+ if(b.line != nil && strchr(b.line, 0) >= 0)
+ ;
+ else
+ break;
+ }
+ return b.line;
+}
+
+Box.unreadline(b : self ref Box)
+{
+ b.peekline = b.line;
+}
+
+Box.slave(b : self ref Box)
+{
+ e : ref Event;
+ m : ref Mesg;
+
+ e = newevent();
+ for(;;){
+ alt{
+ *e = <-b.cevent =>
+ b.event(e);
+ break;
+ <-b.cmore =>
+ b.readmore(1);
+ break;
+ m = <-b.cdel =>
+ b.mdel(m);
+ break;
+ }
+ }
+}
+
+Box.event(b : self ref Box, e : ref Event)
+{
+ e2, ea, eq : ref Event;
+ s : string;
+ t : int;
+ n, na, nopen : int;
+
+ e2 = newevent();
+ ea = newevent();
+ case(e.c1){
+ 'E' => # write to body; can't affect us
+ break;
+ 'F' => # generated by our actions; ignore
+ break;
+ 'K' => # type away; we don't care
+ break;
+ 'M' =>
+ case(e.c2){
+ 'x' or 'X' =>
+ if(e.flag & 2)
+ *e2 = <-b.cevent;
+ if(e.flag & 8){
+ *ea = <-b.cevent;
+ na = ea.nb;
+ <- b.cevent;
+ }else
+ na = 0;
+ s = string e.b[0:e.nb];
+ # if it's a known command, do it
+ if((e.flag&2) && e.nb==0)
+ s = string e2.b[0:e2.nb];
+ if(na)
+ s = sprint("%s %s", s, string ea.b[0:ea.nb]);
+ # if it's a long message, it can't be for us anyway
+ if(!b.command(s)) # send it back
+ b.w.wwriteevent(e);
+ if(na)
+ s = nil;
+ break;
+ 'l' or 'L' =>
+ eq = e;
+ if(e.flag & 2){
+ *e2 = <-b.cevent;
+ eq = e2;
+ }
+ s = string eq.b[0:eq.nb];
+ if(eq.q1>eq.q0 && eq.nb==0)
+ s = b.w.wread(eq.q0, eq.q1);
+ nopen = 0;
+ do{
+ t = 0;
+ (n, t) = strtoi(s);
+ if(n>0 && (t == len s || s[t]==' ' || s[t]=='\t' || s[t]=='\n')){
+ b.mopen(n);
+ nopen++;
+ s = s[t:];
+ }
+ while(s != nil && s[0]!='\n')
+ s = s[1:];
+ }while(s != nil);
+ if(nopen == 0) # send it back
+ b.w.wwriteevent(e);
+ break;
+ 'I' or 'D' or 'd' or 'i' => # modify away; we don't care
+ break;
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+ * =>
+ fprint(stdout, "unknown message %c%c\n", e.c1, e.c2);
+ break;
+ }
+}
+
+Box.mopen(b : self ref Box, id : int)
+{
+ m : ref Mesg;
+
+ for(m=b.m; m != nil; m=m.next)
+ if(m.id == id){
+ m.open();
+ break;
+ }
+}
+
+Box.mdel(b : self ref Box, dm : ref Mesg)
+{
+ m : ref Mesg;
+ buf : string;
+
+ if(dm.id){
+ for(m=b.m; m!=nil && m!=dm; m=m.next)
+ ;
+ if(m == nil)
+ error(sprint("message %d not found", dm.id));
+ m.deleted = 1;
+ # remove from screen: use acme to help
+ buf = sprint("/^%d .*\\n(^[ \t].*\\n)*/", m.id);
+ b.w.wreplace(buf, "");
+ }
+ dm.free();
+ b.clean = False;
+}
+
+Box.command(b : self ref Box, s : string) : int
+{
+ t : int;
+ m : ref Mesg;
+
+ while(s[0]==' ' || s[0]=='\t' || s[0]=='\n')
+ s = s[1:];
+ if(len s >= 4 && s[0:4] == "Mail"){
+ s = s[4:];
+ while(s != nil && (s[0]==' ' || s[0]=='\t' || s[0]=='\n'))
+ s = s[1:];
+ t = 0;
+ while(t < len s && s[t] && s[t]!=' ' && s[t]!='\t' && s[t]!='\n')
+ t++;
+ m = b.m; # avoid warning message on b.m.mkmail(...)
+ m.mkmail(b, s[0:t]);
+ return True;
+ }
+ if(s == "Del"){
+
+ if(!b.clean){
+ b.clean = True;
+ fprint(stderr, "mail: mailbox not written\n");
+ return True;
+ }
+ postnote(PNGROUP, pctl(0, nil), "kill");
+ killing = 1;
+ pctl(NEWPGRP, nil);
+ b.w.wdel(True);
+ for(m=b.m; m != nil; m=m.next)
+ m.w.wdel(False);
+ exit;
+ return True;
+ }
+ if(s == "Put"){
+ if(b.readonly)
+ fprint(stderr, "Mail is read-only\n");
+ else
+ b.rewrite();
+ return True;
+ }
+ return False;
+}
+
+Box.rewrite(b : self ref Box)
+{
+ prev, m : ref Mesg;
+
+ if(b.clean){
+ b.w.wclean();
+ return;
+ }
+ prev = nil;
+ pop3open(1);
+ for(m=b.m; m!=nil; m=m.next) {
+ if (m.deleted && pop3del(m.popno) >= 0) {
+ b.leng--;
+ if (prev == nil)
+ b.m=m.next;
+ else
+ prev.next=m.next;
+ }
+ else
+ prev = m;
+ }
+ # must update pop nos now so don't unlock pop3
+ pop3close(0);
+ b.w.wclean();
+ b.clean = True;
+ b.readmore(0); # updates pop nos
+}
--- /dev/null
+++ b/appl/acme/acme/mail/src/mashfile
@@ -1,0 +1,18 @@
+make -clear;
+
+MOD=module;
+DISBIN=/acme/mail;
+LFLAGS=-I/$MOD -gw;
+
+fn lcom {
+ limbo $LFLAGS $args;
+};
+
+TARG=mail.dis;
+
+*.dis :~ $1.b { lcom $1.b };
+$DISBIN/*.dis :~ $1.dis { cp $1.dis $DISBIN };
+
+default : $TARG {};
+all : $TARG {};
+install : $DISBIN/*.dis {};
--- /dev/null
+++ b/appl/acme/acme/mail/src/mkfile
@@ -1,0 +1,16 @@
+<../../../../../mkconfig
+
+TARG=\
+ Mail.dis\
+ Mailpop3.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ sh.m\
+ sys.m\
+ draw.m\
+
+DISBIN=$ROOT/acme/mail
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/acme/acme/mkfile
@@ -1,0 +1,9 @@
+<../../../mkconfig
+
+DIRS=\
+ acid\
+ bin\
+ edit\
+ mail\
+
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/appl/acme/buff.b
@@ -1,0 +1,380 @@
+implement Bufferm;
+
+include "common.m";
+
+sys : Sys;
+dat : Dat;
+utils : Utils;
+diskm : Diskm;
+ecmd: Editcmd;
+
+FALSE, TRUE, XXX, Maxblock, Astring : import Dat;
+Block : import Dat;
+disk : import dat;
+Disk : import diskm;
+File: import Filem;
+error, warning, min : import utils;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ diskm = mods.diskm;
+ ecmd = mods.editcmd;
+}
+
+nullbuffer : Buffer;
+
+newbuffer() : ref Buffer
+{
+ b := ref nullbuffer;
+ return b;
+}
+
+Slop : con 100; # room to grow with reallocation
+
+Buffer.sizecache(b : self ref Buffer, n : int)
+{
+ if(n <= b.cmax)
+ return;
+ b.cmax = n+Slop;
+ os := b.c;
+ b.c = utils->stralloc(b.cmax);
+ if (os != nil) {
+ loss := len os.s;
+ c := b.c;
+ oss := os.s;
+ for (i := 0; i < loss && i < b.cmax; i++)
+ c.s[i] = oss[i];
+ utils->strfree(os);
+ }
+}
+
+#
+# Move cache so b.cq <= q0 < b.cq+b.cnc.
+# If at very end, q0 will fall on end of cache block.
+#
+
+Buffer.flush(b : self ref Buffer)
+{
+ if(b.cdirty || b.cnc==0){
+ if(b.cnc == 0)
+ b.delblock(b.cbi);
+ else
+ b.bl[b.cbi] = disk.write(b.bl[b.cbi], b.c.s, b.cnc);
+ b.cdirty = FALSE;
+ }
+}
+
+Buffer.setcache(b : self ref Buffer, q0 : int)
+{
+ blp, bl : ref Block;
+ i, q : int;
+
+ if (q0 > b.nc)
+ error("bad assert in 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;
+ b.flush();
+ # 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 = b.bl[i];
+ if(i >= b.nbl)
+ error("block not found");
+ }
+ bl = blp;
+ # remember position
+ b.cbi = i;
+ b.cq = q;
+ b.sizecache(bl.n);
+ b.cnc = bl.n;
+ #read block
+ disk.read(bl, b.c, b.cnc);
+}
+
+Buffer.addblock(b : self ref Buffer, i : int, n : int)
+{
+ if (i > b.nbl)
+ error("bad assert in addblock");
+
+ obl := b.bl;
+ b.bl = array[b.nbl+1] of ref Block;
+ b.bl[0:] = obl[0:i];
+ if(i < b.nbl)
+ b.bl[i+1:] = obl[i:b.nbl];
+ b.bl[i] = disk.new(n);
+ b.nbl++;
+ obl = nil;
+}
+
+Buffer.delblock(b : self ref Buffer, i : int)
+{
+ if (i >= b.nbl)
+ error("bad assert in delblock");
+
+ disk.release(b.bl[i]);
+ obl := b.bl;
+ b.bl = array[b.nbl-1] of ref Block;
+ b.bl[0:] = obl[0:i];
+ if(i < b.nbl-1)
+ b.bl[i:] = obl[i+1:b.nbl];
+ b.nbl--;
+ obl = nil;
+}
+
+Buffer.insert(b : self ref Buffer, q0 : int, s : string, n : int)
+{
+ i, j, m, t, off, p : int;
+
+ if (q0>b.nc)
+ error("bad assert in insert");
+ p = 0;
+ while(n > 0){
+ b.setcache(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)
+ error("bad assert in insert");
+ b.addblock(0, t);
+ b.cbi = 0;
+ }
+ b.sizecache(t);
+ c := b.c;
+ # cs := c.s;
+ for (j = b.cnc-1; j >= off; j--)
+ c.s[j+m] = c.s[j];
+ for (j = 0; j < m; j++)
+ c.s[off+j] = s[p+j];
+ b.cnc = t;
+ }
+ #
+ # 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.
+ #
+ else if(q0==b.cq || q0==b.cq+b.cnc){
+ if(b.cdirty)
+ b.flush();
+ m = min(n, Maxblock);
+ if(b.bl == nil){ # allocate
+ if (b.cnc != 0)
+ error("bad assert in insert");
+ i = 0;
+ }else{
+ i = b.cbi;
+ if(q0 > b.cq)
+ i++;
+ }
+ b.addblock(i, m);
+ b.sizecache(m);
+ c := b.c;
+ for (j = 0; j < m; j++)
+ c.s[j] = s[p+j];
+ b.cq = q0;
+ b.cbi = i;
+ b.cnc = m;
+ }
+ else {
+ #
+ # Split the block; cut off the right side and
+ # let go of it.
+ #
+
+ m = b.cnc-off;
+ if(m > 0){
+ i = b.cbi+1;
+ b.addblock(i, m);
+ b.bl[i] = disk.write(b.bl[i], b.c.s[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);
+ b.sizecache(b.cnc+m);
+ c := b.c;
+ for (j = 0; j < m; j++)
+ c.s[j+b.cnc] = s[p+j];
+ b.cnc += m;
+ }
+ b.nc += m;
+ q0 += m;
+ p += m;
+ n -= m;
+ b.cdirty = TRUE;
+ }
+}
+
+Buffer.delete(b : self ref Buffer, q0 : int, q1 : int)
+{
+ m, n, off : int;
+
+ if (q0>q1 || q0>b.nc || q1>b.nc)
+ error("bad assert in delete");
+
+ while(q1 > q0){
+ b.setcache(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) {
+ c := b.c;
+ # cs := c.s;
+ p := m+off;
+ for (j := off; j < p; j++)
+ c.s[j] = c.s[j+n];
+ }
+ b.cnc -= n;
+ b.cdirty = TRUE;
+ q1 -= n;
+ b.nc -= n;
+ }
+}
+
+# Buffer.replace(b: self ref Buffer, q0: int, q1: int, s: string, n: int)
+# {
+# if(q0>q1 || q0>b.nc || q1>b.nc || n != q1-q0)
+# error("bad assert in replace");
+# p := 0;
+# while(q1 > q0){
+# b.setcache(q0);
+# off := q0-b.cq;
+# if(q1 > b.cq+b.cnc)
+# n = b.cnc-off;
+# else
+# n = q1-q0;
+# c := b.c;
+# for(i := 0; i < n; i++)
+# c.s[i+off] = s[i+p];
+# b.cdirty = TRUE;
+# q0 += n;
+# p += n;
+# }
+# }
+
+pbuf : array of byte;
+
+bufloader(b: ref Buffer, q0: int, r: string, nr: int): int
+{
+ b.insert(q0, r, nr);
+ return nr;
+}
+
+loadfile(fd: ref Sys->FD, q0: int, fun: int, b: ref Buffer, f: ref File): int
+{
+ p : array of byte;
+ r : string;
+ m, n, nb, nr : int;
+ q1 : int;
+
+ if (pbuf == nil)
+ pbuf = array[Maxblock+Sys->UTFmax] of byte;
+ p = pbuf;
+ 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 = sys->read(fd, p[m:], Maxblock);
+ if(n < 0){
+ warning(nil, "read error in Buffer.load");
+ break;
+ }
+ m += n;
+ nb = sys->utfbytes(p, m);
+ r = string p[0:nb];
+ p[0:] = p[nb:m];
+ m -= nb;
+ nr = len r;
+ if(fun == Dat->BUFL)
+ q1 += bufloader(b, q1, r, nr);
+ else
+ q1 += ecmd->readloader(f, q1, r, nr);
+ }
+ p = nil;
+ r = nil;
+ return q1-q0;
+}
+
+Buffer.loadx(b : self ref Buffer, q0 : int, fd : ref Sys->FD) : int
+{
+ if (q0>b.nc)
+ error("bad assert in load");
+ return loadfile(fd, q0, Dat->BUFL, b, nil);
+}
+
+Buffer.read(b : self ref Buffer, q0 : int, s : ref Astring, p : int, n : int)
+{
+ m : int;
+
+ if (q0>b.nc || q0+n>b.nc)
+ error("bad assert in read");
+ while(n > 0){
+ b.setcache(q0);
+ m = min(n, b.cnc-(q0-b.cq));
+ c := b.c;
+ cs := c.s;
+ for (j := 0; j < m; j++)
+ s.s[p+j] = cs[j+q0-b.cq];
+ q0 += m;
+ p += m;
+ n -= m;
+ }
+}
+
+Buffer.reset(b : self ref Buffer)
+{
+ i : int;
+
+ 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; )
+ b.delblock(i);
+}
+
+Buffer.close(b : self ref Buffer)
+{
+ b.reset();
+ if (b.c != nil) {
+ utils->strfree(b.c);
+ b.c = nil;
+ }
+ b.cnc = 0;
+ b.bl = nil;
+ b.nbl = 0;
+}
--- /dev/null
+++ b/appl/acme/buff.m
@@ -1,0 +1,34 @@
+Bufferm : module {
+ PATH : con "/dis/acme/buff.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ newbuffer : fn() : ref Buffer;
+
+ Buffer : adt {
+ nc : int;
+ c : ref Dat->Astring; # cache
+ cnc : int; # bytes in cache
+ cmax : int; # size of allocated cache
+ cq : int; # position of cache
+ cdirty : int; # cache needs to be written
+ cbi : int; # index of cache Block
+ bl : array of ref Dat->Block; # array of blocks
+ nbl : int; # number of blocks
+
+ insert : fn(b : self ref Buffer, n : int, s : string, m : int);
+ delete : fn(b : self ref Buffer, n : int, m : int);
+ # replace : fn(b : self ref Buffer, q0 : int, q1 : int, s : string, n : int);
+ loadx : fn(b : self ref Buffer, n : int, fd : ref Sys->FD) : int;
+ read : fn(b : self ref Buffer, n : int, s : ref Dat->Astring, p, m : int);
+ close : fn(b : self ref Buffer);
+ reset : fn(b : self ref Buffer);
+ sizecache : fn(b : self ref Buffer, n : int);
+ flush : fn(b : self ref Buffer);
+ setcache : fn(b : self ref Buffer, n : int);
+ addblock : fn(b : self ref Buffer, n : int, m : int);
+ delblock : fn(b : self ref Buffer, n : int);
+ };
+
+ loadfile: fn(fd: ref Sys->FD, q1: int, fun: int, b: ref Bufferm->Buffer, f: ref Filem->File): int;
+};
--- /dev/null
+++ b/appl/acme/col.b
@@ -1,0 +1,610 @@
+implement Columnm;
+
+include "common.m";
+
+sys : Sys;
+utils : Utils;
+drawm : Draw;
+acme : Acme;
+graph : Graph;
+gui : Gui;
+dat : Dat;
+textm : Textm;
+rowm : Rowm;
+filem : Filem;
+windowm : Windowm;
+
+FALSE, TRUE, XXX : import Dat;
+Border : import Dat;
+mouse, colbutton : import dat;
+Point, Rect, Image : import drawm;
+draw : import graph;
+min, max, abs, error, clearmouse : import utils;
+black, white, mainwin : import gui;
+Text : import textm;
+Row : import rowm;
+Window : import windowm;
+File : import filem;
+Columntag : import Textm;
+BACK : import Framem;
+tagcols, textcols : import acme;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ drawm = mods.draw;
+ acme = mods.acme;
+ graph = mods.graph;
+ gui = mods.gui;
+ textm = mods.textm;
+ rowm = mods.rowm;
+ filem = mods.filem;
+ windowm = mods.windowm;
+}
+
+Column.init(c : self ref Column, r : Rect)
+{
+ r1 : Rect;
+ t : ref Text;
+ dummy : ref File = nil;
+
+ draw(mainwin, r, white, nil, (0, 0));
+ c.r = r;
+ c.row = nil;
+ c.w = nil;
+ c.nw = 0;
+ c.tag = textm->newtext();
+ t = c.tag;
+ t.w = nil;
+ t.col = c;
+ r1 = r;
+ r1.max.y = r1.min.y + (graph->font).height;
+ t.init(dummy.addtext(t), r1, dat->reffont, tagcols);
+ t.what = Columntag;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ t.insert(0, "New Cut Paste Snarf Sort Zerox Delcol ", 38, TRUE, 0);
+ t.setselect(t.file.buf.nc, t.file.buf.nc);
+ draw(mainwin, t.scrollr, colbutton, nil, colbutton.r.min);
+ c.safe = TRUE;
+}
+
+Column.add(c : self ref Column, w : ref Window, clone : ref Window, y : int) : ref Window
+{
+ r, r1 : Rect;
+ v : ref Window;
+ i, t : int;
+
+ v = nil;
+ r = c.r;
+ r.min.y = c.tag.frame.r.max.y+Border;
+ if(y<r.min.y && c.nw>0){ # steal half of last window by default
+ v = c.w[c.nw-1];
+ y = v.body.frame.r.min.y+v.body.frame.r.dy()/2;
+ }
+ # look for window we'll land on
+ for(i=0; i<c.nw; i++){
+ v = c.w[i];
+ if(y < v.r.max.y)
+ break;
+ }
+ if(c.nw > 0){
+ if(i < c.nw)
+ i++; # new window will go after v
+ #
+ # if v's too small, grow it first.
+ #
+
+ if(!c.safe || v.body.frame.maxlines<=3){
+ c.grow(v, 1, 1);
+ y = v.body.frame.r.min.y+v.body.frame.r.dy()/2;
+ }
+ r = v.r;
+ if(i == c.nw)
+ t = c.r.max.y;
+ else
+ t = c.w[i].r.min.y-Border;
+ r.max.y = t;
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ r1 = r;
+ y = min(y, t-(v.tag.frame.font.height+v.body.frame.font.height+Border+1));
+ r1.max.y = min(y, v.body.frame.r.min.y+v.body.frame.nlines*v.body.frame.font.height);
+ r1.min.y = v.reshape(r1, FALSE);
+ r1.max.y = r1.min.y+Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ r.min.y = r1.max.y;
+ }
+ if(w == nil){
+ w = ref Window;
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ w.col = c;
+ w.init(clone, r);
+ }else{
+ w.col = c;
+ w.reshape(r, FALSE);
+ }
+ w.tag.col = c;
+ w.tag.row = c.row;
+ w.body.col = c;
+ w.body.row = c.row;
+ ocw := c.w;
+ c.w = array[c.nw+1] of ref Window;
+ c.w[0:] = ocw[0:i];
+ c.w[i+1:] = ocw[i:c.nw];
+ ocw = nil;
+ c.nw++;
+ c.w[i] = w;
+ utils->savemouse(w);
+ # near but not on the button
+ graph->cursorset(w.tag.scrollr.max.add(Point(3, 3)));
+ dat->barttext = w.body;
+ c.safe = TRUE;
+ return w;
+}
+
+Column.close(c : self ref Column, w : ref Window, dofree : int)
+{
+ r : Rect;
+ i : int;
+
+ # w is locked
+ if(!c.safe)
+ c.grow(w, 1, 1);
+ for(i=0; i<c.nw; i++)
+ if(c.w[i] == w)
+ break;
+ if (i == c.nw)
+ error("can't find window");
+ r = w.r;
+ w.tag.col = nil;
+ w.body.col = nil;
+ w.col = nil;
+ utils->restoremouse(w);
+ if(dofree){
+ w.delete();
+ w.close();
+ }
+ ocw := c.w;
+ c.w = array[c.nw-1] of ref Window;
+ c.w[0:] = ocw[0:i];
+ c.w[i:] = ocw[i+1:c.nw];
+ ocw = nil;
+ c.nw--;
+ if(c.nw == 0){
+ draw(mainwin, r, white, nil, (0, 0));
+ return;
+ }
+ if(i == c.nw){ # extend last window down
+ w = c.w[i-1];
+ r.min.y = w.r.min.y;
+ r.max.y = c.r.max.y;
+ }else{ # extend next window up
+ w = c.w[i];
+ r.max.y = w.r.max.y;
+ }
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ if(c.safe)
+ w.reshape(r, FALSE);
+}
+
+Column.closeall(c : self ref Column)
+{
+ i : int;
+ w : ref Window;
+
+ if(c == dat->activecol)
+ dat->activecol = nil;
+ c.tag.close();
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ w.close();
+ }
+ c.nw = 0;
+ c.w = nil;
+ c = nil;
+ clearmouse();
+}
+
+Column.mousebut(c : self ref Column)
+{
+ graph->cursorset(c.tag.scrollr.min.add(c.tag.scrollr.max).div(2));
+}
+
+Column.reshape(c : self ref Column, r : Rect)
+{
+ i : int;
+ r1, r2 : Rect;
+ w : ref Window;
+
+ clearmouse();
+ r1 = r;
+ r1.max.y = r1.min.y + c.tag.frame.font.height;
+ c.tag.reshape(r1);
+ draw(mainwin, c.tag.scrollr, colbutton, nil, colbutton.r.min);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ r1.max.y = r.max.y;
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ w.maxlines = 0;
+ if(i == c.nw-1)
+ r1.max.y = r.max.y;
+ else
+ r1.max.y = r1.min.y+(w.r.dy()+Border)*r.dy()/c.r.dy();
+ r2 = r1;
+ r2.max.y = r2.min.y+Border;
+ draw(mainwin, r2, black, nil, (0, 0));
+ r1.min.y = r2.max.y;
+ r1.min.y = w.reshape(r1, FALSE);
+ }
+ c.r = r;
+}
+
+colcmp(a : ref Window, b : ref Window) : int
+{
+ r1, r2 : string;
+
+ r1 = a.body.file.name;
+ r2 = b.body.file.name;
+ if (r1 < r2)
+ return -1;
+ if (r1 > r2)
+ return 1;
+ return 0;
+}
+
+qsort(a : array of ref Window, n : int)
+{
+ i, j : int;
+ t : ref Window;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && colcmp(a[i], a[0]) < 0);
+ do
+ j--;
+ while(j > 0 && colcmp(a[j], a[0]) > 0);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
+
+Column.sort(c : self ref Column)
+{
+ i, y : int;
+ r, r1 : Rect;
+ rp : array of Rect;
+ w : ref Window;
+ wp : array of ref Window;
+
+ if(c.nw == 0)
+ return;
+ clearmouse();
+ rp = array[c.nw] of Rect;
+ wp = array[c.nw] of ref Window;
+ wp[0:] = c.w[0:c.nw];
+ qsort(wp, c.nw);
+ for(i=0; i<c.nw; i++)
+ rp[i] = wp[i].r;
+ r = c.r;
+ r.min.y = c.tag.frame.r.max.y;
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ y = r.min.y;
+ for(i=0; i<c.nw; i++){
+ w = wp[i];
+ r.min.y = y;
+ if(i == c.nw-1)
+ r.max.y = c.r.max.y;
+ else
+ r.max.y = r.min.y+w.r.dy()+Border;
+ r1 = r;
+ r1.max.y = r1.min.y+Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ r.min.y = r1.max.y;
+ y = w.reshape(r, FALSE);
+ }
+ rp = nil;
+ c.w = wp;
+}
+
+Column.grow(c : self ref Column, w : ref Window, but : int, mv : int)
+{
+ r, cr : Rect;
+ i, j, k, l, y1, y2, tot, nnl, onl, dnl, h : int;
+ nl, ny : array of int;
+ v : ref Window;
+
+ for(i=0; i<c.nw; i++)
+ if(c.w[i] == w)
+ break;
+ if (i == c.nw)
+ error("can't find window");
+
+ cr = c.r;
+ if(but < 0){ # make sure window fills its own space properly
+ r = w.r;
+ if(i == c.nw-1)
+ r.max.y = cr.max.y;
+ else
+ r.max.y = c.w[i+1].r.min.y;
+ w.reshape(r, FALSE);
+ return;
+ }
+ cr.min.y = c.w[0].r.min.y;
+ if(but == 3){ # full size
+ if(i != 0){
+ v = c.w[0];
+ c.w[0] = w;
+ c.w[i] = v;
+ }
+ draw(mainwin, cr, textcols[BACK], nil, (0, 0));
+ w.reshape(cr, FALSE);
+ for(i=1; i<c.nw; i++)
+ c.w[i].body.frame.maxlines = 0;
+ c.safe = FALSE;
+ return;
+ }
+ # store old #lines for each window
+ onl = w.body.frame.maxlines;
+ nl = array[c.nw] of int;
+ ny = array[c.nw] of int;
+ tot = 0;
+ for(j=0; j<c.nw; j++){
+ l = c.w[j].body.frame.maxlines;
+ nl[j] = l;
+ tot += l;
+ }
+ # approximate new #lines for this window
+ if(but == 2){ # as big as can be
+ for (j = 0; j < c.nw; j++)
+ nl[j] = 0;
+ nl[i] = tot;
+ }
+ else {
+ nnl = min(onl + max(min(5, w.maxlines), onl/2), tot);
+ if(nnl < w.maxlines)
+ nnl = (w.maxlines+nnl)/2;
+ if(nnl == 0)
+ nnl = 2;
+ dnl = nnl - onl;
+ # compute new #lines for each window
+ for(k=1; k<c.nw; k++){
+ # prune from later window
+ j = i+k;
+ if(j<c.nw && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ # prune from earlier window
+ j = i-k;
+ if(j>=0 && nl[j]){
+ l = min(dnl, max(1, nl[j]/2));
+ nl[j] -= l;
+ nl[i] += l;
+ dnl -= l;
+ }
+ }
+ }
+ # pack everyone above
+ y1 = cr.min.y;
+ for(j=0; j<i; j++){
+ v = c.w[j];
+ r = v.r;
+ r.min.y = y1;
+ r.max.y = y1+v.tag.all.dy();
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v.body.frame.font.height;
+ if(!c.safe || !v.r.eq(r)){
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ v.reshape(r, c.safe);
+ }
+ r.min.y = v.r.max.y;
+ r.max.y += Border;
+ draw(mainwin, r, black, nil, (0, 0));
+ y1 = r.max.y;
+ }
+ # scan to see new size of everyone below
+ y2 = c.r.max.y;
+ for(j=c.nw-1; j>i; j--){
+ v = c.w[j];
+ r = v.r;
+ r.min.y = y2-v.tag.all.dy();
+ if(nl[j])
+ r.min.y -= 1 + nl[j]*v.body.frame.font.height;
+ r.min.y -= Border;
+ ny[j] = r.min.y;
+ y2 = r.min.y;
+ }
+ # compute new size of window
+ r = w.r;
+ r.min.y = y1;
+ r.max.y = r.min.y+w.tag.all.dy();
+ h = w.body.frame.font.height;
+ if(y2-r.max.y >= 1+h+Border){
+ r.max.y += 1;
+ r.max.y += h*((y2-r.max.y)/h);
+ }
+ # draw window
+ if(!c.safe || !w.r.eq(r)){
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ w.reshape(r, c.safe);
+ }
+ if(i < c.nw-1){
+ r.min.y = r.max.y;
+ r.max.y += Border;
+ draw(mainwin, r, black, nil, (0, 0));
+ for(j=i+1; j<c.nw; j++)
+ ny[j] -= (y2-r.max.y);
+ }
+ # pack everyone below
+ y1 = r.max.y;
+ for(j=i+1; j<c.nw; j++){
+ v = c.w[j];
+ r = v.r;
+ r.min.y = y1;
+ r.max.y = y1+v.tag.all.dy();
+ if(nl[j])
+ r.max.y += 1 + nl[j]*v.body.frame.font.height;
+ if(!c.safe || !v.r.eq(r)){
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ v.reshape(r, c.safe);
+ }
+ if(j < c.nw-1){ # no border on last window
+ r.min.y = v.r.max.y;
+ r.max.y += Border;
+ draw(mainwin, r, black, nil, (0, 0));
+ }
+ y1 = r.max.y;
+ }
+ r = w.r;
+ r.min.y = y1;
+ r.max.y = c.r.max.y;
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ nl = nil;
+ ny = nil;
+ c.safe = TRUE;
+ if (mv)
+ w.mousebut();
+}
+
+Column.dragwin(c : self ref Column, w : ref Window, but : int)
+{
+ r : Rect;
+ i, b : int;
+ p, op : Point;
+ v : ref Window;
+ nc : ref Column;
+
+ clearmouse();
+ graph->cursorswitch(dat->boxcursor);
+ b = mouse.buttons;
+ op = mouse.xy;
+ while(mouse.buttons == b)
+ acme->frgetmouse();
+ graph->cursorswitch(dat->arrowcursor);
+ if(mouse.buttons){
+ while(mouse.buttons)
+ acme->frgetmouse();
+ return;
+ }
+
+ for(i=0; i<c.nw; i++)
+ if(c.w[i] == w)
+ break;
+ if (i == c.nw)
+ error("can't find window");
+
+ p = mouse.xy;
+ if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
+ c.grow(w, but, 1);
+ w.mousebut();
+ return;
+ }
+ # is it a flick to the right?
+ if(abs(p.y-op.y)<10 && p.x>op.x+30 && c.row.whichcol(p) == c)
+ p.x += w.r.dx(); # yes: toss to next column
+ nc = c.row.whichcol(p);
+ if(nc!=nil && nc!=c){
+ c.close(w, FALSE);
+ nc.add(w, nil, p.y);
+ w.mousebut();
+ return;
+ }
+ if(i==0 && c.nw==1)
+ return; # can't do it
+ if((i>0 && p.y<c.w[i-1].r.min.y) || (i<c.nw-1 && p.y>w.r.max.y)
+ || (i==0 && p.y>w.r.max.y)){
+ # shuffle
+ c.close(w, FALSE);
+ c.add(w, nil, p.y);
+ w.mousebut();
+ return;
+ }
+ if(i == 0)
+ return;
+ v = c.w[i-1];
+ if(p.y < v.tag.all.max.y)
+ p.y = v.tag.all.max.y;
+ if(p.y > w.r.max.y-w.tag.all.dy()-Border)
+ p.y = w.r.max.y-w.tag.all.dy()-Border;
+ r = v.r;
+ r.max.y = p.y;
+ if(r.max.y > v.body.frame.r.min.y){
+ r.max.y -= (r.max.y-v.body.frame.r.min.y)%v.body.frame.font.height;
+ if(v.body.frame.r.min.y == v.body.frame.r.max.y)
+ r.max.y++;
+ }
+ if(!r.eq(v.r)){
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ v.reshape(r, c.safe);
+ }
+ r.min.y = v.r.max.y;
+ r.max.y = r.min.y+Border;
+ draw(mainwin, r, black, nil, (0, 0));
+ r.min.y = r.max.y;
+ if(i == c.nw-1)
+ r.max.y = c.r.max.y;
+ else
+ r.max.y = c.w[i+1].r.min.y-Border;
+ # r.max.y = w.r.max.y;
+ if(!r.eq(w.r)){
+ draw(mainwin, r, textcols[BACK], nil, (0, 0));
+ w.reshape(r, c.safe);
+ }
+ c.safe = TRUE;
+ w.mousebut();
+}
+
+Column.which(c : self ref Column, p : Point) : ref Text
+{
+ i : int;
+ w : ref Window;
+
+ if(!p.in(c.r))
+ return nil;
+ if(p.in(c.tag.all))
+ return c.tag;
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ if(p.in(w.r)){
+ if(p.in(w.tag.all))
+ return w.tag;
+ return w.body;
+ }
+ }
+ return nil;
+}
+
+Column.clean(c : self ref Column, exiting : int) : int
+{
+ clean : int;
+ i : int;
+
+ clean = TRUE;
+ for(i=0; i<c.nw; i++)
+ clean &= c.w[i].clean(TRUE, exiting);
+ return clean;
+}
--- /dev/null
+++ b/appl/acme/col.m
@@ -1,0 +1,26 @@
+Columnm : module {
+ PATH : con "/dis/acme/col.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ Column : adt {
+ r : Draw->Rect;
+ tag : cyclic ref Textm->Text;
+ row : cyclic ref Rowm->Row;
+ w : cyclic array of ref Windowm->Window;
+ nw : int;
+ safe : int;
+
+ init : fn (c : self ref Column, r : Draw->Rect);
+ add : fn (c : self ref Column, w : ref Windowm->Window, w0 : ref Windowm->Window, n : int) : ref Windowm->Window;
+ close : fn (c : self ref Column, w : ref Windowm->Window, n : int);
+ closeall : fn (c : self ref Column);
+ reshape : fn (c : self ref Column, r : Draw->Rect);
+ which : fn (c : self ref Column, p : Draw->Point) : ref Textm->Text;
+ dragwin : fn (c : self ref Column, w : ref Windowm->Window, n : int);
+ grow : fn (c : self ref Column, w : ref Windowm->Window, m, n : int);
+ clean : fn (c : self ref Column, exiting : int) : int;
+ sort : fn (c : self ref Column);
+ mousebut : fn (c : self ref Column);
+ };
+};
--- /dev/null
+++ b/appl/acme/common.m
@@ -1,0 +1,30 @@
+include "sys.m";
+include "bufio.m";
+include "plumbmsg.m";
+include "workdir.m";
+include "draw.m";
+include "styx.m";
+include "acme.m";
+include "dat.m";
+include "gui.m";
+include "graph.m";
+include "frame.m";
+include "util.m";
+include "regx.m";
+include "text.m";
+include "file.m";
+include "wind.m";
+include "row.m";
+include "col.m";
+include "buff.m";
+include "disk.m";
+include "xfid.m";
+include "exec.m";
+include "look.m";
+include "time.m";
+include "scrl.m";
+include "fsys.m";
+include "edit.m";
+include "elog.m";
+include "ecmd.m";
+include "styxaux.m";
--- /dev/null
+++ b/appl/acme/dat.b
@@ -1,0 +1,107 @@
+implement Dat;
+
+include "common.m";
+
+sys : Sys;
+acme : Acme;
+utils : Utils;
+
+# lc, uc : chan of ref Lock;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ acme = mods.acme;
+ utils = mods.utils;
+
+ mouse = ref Draw->Pointer;
+ mouse.buttons = mouse.msec = 0;
+ mouse.xy = (0, 0);
+ # lc = chan of ref Lock;
+ # uc = chan of ref Lock;
+ # spawn lockmgr();
+}
+
+# lockmgr()
+# {
+# l : ref Lock;
+#
+# acme->lockpid = sys->pctl(0, nil);
+# for (;;) {
+# alt {
+# l = <- lc =>
+# if (l.cnt++ == 0)
+# l.chann <-= 1;
+# l = <- uc =>
+# if (--l.cnt > 0)
+# l.chann <-= 1;
+# }
+# }
+# }
+
+Lock.init() : ref Lock
+{
+ return ref Lock(0, chan[1] of int);
+ # return ref Lock(0, chan of int);
+}
+
+Lock.lock(l : self ref Lock)
+{
+ l.cnt++;
+ l.chann <-= 0;
+ # lc <-= l;
+ # <- l.chann;
+}
+
+Lock.unlock(l : self ref Lock)
+{
+ <-l.chann;
+ l.cnt--;
+ # uc <-= l;
+}
+
+Lock.locked(l : self ref Lock) : int
+{
+ return l.cnt > 0;
+}
+
+Ref.init() : ref Ref
+{
+ r := ref Ref;
+ r.l = Lock.init();
+ r.cnt = 0;
+ return r;
+}
+
+Ref.inc(r : self ref Ref) : int
+{
+ r.l.lock();
+ i := r.cnt;
+ r.cnt++;
+ r.l.unlock();
+ return i;
+}
+
+Ref.dec(r : self ref Ref) : int
+{
+ r.l.lock();
+ r.cnt--;
+ i := r.cnt;
+ r.l.unlock();
+ return i;
+}
+
+Ref.refx(r : self ref Ref) : int
+{
+ return r.cnt;
+}
+
+Reffont.get(p, q, r : int, b : string) : ref Reffont
+{
+ return acme->get(p, q, r, b);
+}
+
+Reffont.close(r : self ref Reffont)
+{
+ return acme->close(r);
+}
--- /dev/null
+++ b/appl/acme/dat.m
@@ -1,0 +1,283 @@
+Dat : module {
+ PATH : con "/dis/acme/dat.dis";
+
+ init : fn(mods : ref Mods);
+
+ Mods : adt {
+ sys : Sys;
+ bufio : Bufio;
+ draw : Draw;
+ styx : Styx;
+ styxaux : Styxaux;
+ acme : Acme;
+ gui : Gui;
+ graph : Graph;
+ dat : Dat;
+ framem : Framem;
+ utils : Utils;
+ regx : Regx;
+ scroll : Scroll;
+ textm : Textm;
+ filem : Filem;
+ windowm : Windowm;
+ rowm : Rowm;
+ columnm : Columnm;
+ bufferm : Bufferm;
+ diskm : Diskm;
+ exec : Exec;
+ look : Look;
+ timerm : Timerm;
+ fsys : Fsys;
+ xfidm : Xfidm;
+ plumbmsg : Plumbmsg;
+ edit: Edit;
+ editlog: Editlog;
+ editcmd: Editcmd;
+ };
+
+ SZSHORT : con 2;
+ SZINT : con 4;
+
+ FALSE, TRUE, XXX : con iota;
+
+ EM_NORMAL, EM_RAW, EM_MASK : con iota;
+
+ Qdir,Qacme,Qcons,Qconsctl,Qdraw,Qeditout,Qindex,Qlabel,Qnew,QWaddr,QWbody,QWconsctl,QWctl,QWdata,QWeditout,QWevent,QWrdsel,QWwrsel,QWtag,QMAX : con iota;
+
+ Blockincr : con 256;
+ Maxblock : con 8*1024;
+ NRange : con 10;
+ Infinity : con 16r7fffffff; # huge value for regexp address
+
+ # fbufalloc() guarantees room off end of BUFSIZE
+ MAXRPC : con 8192+Styx->IOHDRSZ;
+ BUFSIZE : con MAXRPC;
+ EVENTSIZE : con 256;
+ PLUMBSIZE : con 1024;
+ Scrollwid : con 12; # width of scroll bar
+ Scrollgap : con 4; # gap right of scroll bar
+ Margin : con 4; # margin around text
+ Border : con 2; # line between rows, cols, windows
+ Maxtab : con 4; # size of a tab, in units of the '0' character
+
+ Empty: con 0;
+ Null : con '-';
+ Delete : con 'd';
+ Insert : con 'i';
+ Replace: con 'r';
+ Filename : con 'f';
+
+ # editing
+ Inactive, Inserting, Collecting: con iota;
+
+ # alphabets
+ ALPHA_LATIN: con '\0';
+ ALPHA_GREEK: con '*';
+ ALPHA_CYRILLIC: con '@';
+
+ Kscrollup: con 16re050;
+ Kscrolldown: con 16re051;
+
+ Astring : adt {
+ s : string;
+ };
+
+ Lock : adt {
+ cnt : int;
+ chann : chan of int;
+
+ init : fn() : ref Lock;
+ lock : fn(l : self ref Lock);
+ unlock : fn(l : self ref Lock);
+ locked : fn(l : self ref Lock) : int;
+ };
+
+# Lockx : adt {
+# sem : ref Lock->Semaphore;
+#
+# init : fn() : ref Lockx;
+# lock : fn(l : self ref Lockx);
+# unlock : fn(l : self ref Lockx);
+# };
+
+ Ref : adt {
+ l : ref Lock;
+ cnt : int;
+
+ init : fn() : ref Ref;
+ inc : fn(r : self ref Ref) : int;
+ dec : fn(r : self ref Ref) : int;
+ refx : fn(r : self ref Ref) : int;
+ };
+
+ Runestr : adt {
+ r: string;
+ nr: int;
+ };
+
+ Range : adt {
+ q0 : int;
+ q1 : int;
+ };
+
+ Block : adt {
+ addr : int; # disk address in bytes
+ n : int; # number of used runes in block
+ next : cyclic ref Block; # pointer to next in free list
+ };
+
+ Timer : adt {
+ dt : int;
+ c : chan of int;
+ next : cyclic ref Timer;
+ };
+
+ Command : adt {
+ pid : int;
+ name : string;
+ text : string;
+ av : list of string;
+ iseditcmd: int;
+ md : ref Mntdir;
+ next : cyclic ref Command;
+ };
+
+ Dirtab : adt {
+ name : string;
+ qtype : int;
+ qid : int;
+ perm : int;
+ };
+
+ Mntdir : adt {
+ id : int;
+ refs : int;
+ dir : string;
+ ndir : int;
+ next : cyclic ref Mntdir;
+ nincl : int;
+ incl : array of string;
+ };
+
+ Fid : adt {
+ fid : int;
+ busy : int;
+ open : int;
+ qid : Sys->Qid;
+ w : cyclic ref Windowm->Window;
+ dir : array of Dirtab;
+ next : cyclic ref Fid;
+ mntdir : ref Mntdir;
+ nrpart : int;
+ rpart : array of byte;
+ };
+
+ Rangeset : type array of Range;
+
+ Expand : adt {
+ q0 : int;
+ q1 : int;
+ name : string;
+ bname : string;
+ jump : int;
+ at : ref Textm->Text;
+ ar : string;
+ a0 : int;
+ a1 : int;
+ };
+
+ Dirlist : adt {
+ r : string;
+ wid : int;
+ };
+
+ Reffont : adt {
+ r : ref Ref;
+ f : ref Draw->Font;
+
+ get : fn(p : int, q : int, r : int, b : string) : ref Reffont;
+ close : fn(r : self ref Reffont);
+ };
+
+ Cursor : adt {
+ hot : Draw->Point;
+ size : Draw->Point;
+ bits : array of byte;
+ };
+
+ Smsg0 : adt {
+ msize : int;
+ version : string;
+ iounit: int;
+ qid : Sys->Qid;
+ count : int;
+ data : array of byte;
+ stat : Sys->Dir;
+ qids: array of Sys->Qid;
+ };
+
+ # loadfile function ptr
+
+ BUFL, READL: con iota;
+
+ # allwindows pick type
+
+ Looper: adt{
+ cp: ref Edit->Cmd;
+ XY: int;
+ w: array of ref Windowm->Window;
+ nw: int;
+ }; # only one; X and Y can't nest
+
+ Tofile: adt {
+ f: ref Filem->File;
+ r: ref Edit->String;
+ };
+
+ Filecheck: adt{
+ f: ref Filem->File;
+ r: string;
+ nr: int;
+ };
+
+ Allwin: adt{
+ pick{
+ LP => lp: ref Looper;
+ FF => ff: ref Tofile;
+ FC => fc: ref Filecheck;
+ }
+ };
+
+ seq : int;
+ maxtab : int;
+ mouse : ref Draw->Pointer;
+ reffont : ref Reffont;
+ modbutton : ref Draw->Image;
+ colbutton : ref Draw->Image;
+ button : ref Draw->Image;
+ arrowcursor, boxcursor : ref Cursor;
+ row : ref Rowm->Row;
+ disk : ref Diskm->Disk;
+ seltext : ref Textm->Text;
+ argtext : ref Textm->Text;
+ mousetext : ref Textm->Text; # global because Text.close needs to clear it
+ typetext : ref Textm->Text; # ditto
+ barttext : ref Textm->Text; # shared between mousetask and keyboardtask
+ bartflag : int;
+ activewin : ref Windowm->Window;
+ activecol : ref Columnm->Column;
+ nullrect : Draw->Rect;
+ home : string;
+ plumbed : int;
+
+ ckeyboard : chan of int;
+ cmouse : chan of ref Draw->Pointer;
+ cwait : chan of string;
+ ccommand : chan of ref Command;
+ ckill : chan of string;
+ cxfidalloc : chan of ref Xfidm->Xfid;
+ cxfidfree : chan of ref Xfidm->Xfid;
+ cerr : chan of string;
+ cplumb : chan of ref Plumbmsg->Msg;
+ cedit: chan of int;
+};
--- /dev/null
+++ b/appl/acme/ecmd.b
@@ -1,0 +1,1350 @@
+implement Editcmd;
+
+include "common.m";
+
+sys: Sys;
+utils: Utils;
+edit: Edit;
+editlog: Editlog;
+windowm: Windowm;
+look: Look;
+columnm: Columnm;
+bufferm: Bufferm;
+exec: Exec;
+dat: Dat;
+textm: Textm;
+regx: Regx;
+filem: Filem;
+rowm: Rowm;
+
+Dir: import Sys;
+Allwin, Filecheck, Tofile, Looper, Astring: import Dat;
+aNo, aDot, aAll: import Edit;
+C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: import Edit;
+TRUE, FALSE: import Dat;
+Inactive, Inserting, Collecting: import Dat;
+BUFSIZE, Runestr: import Dat;
+Addr, Address, String, Cmd: import Edit;
+Window: import windowm;
+File: import filem;
+NRange, Range, Rangeset: import Dat;
+Text: import textm;
+Column: import columnm;
+Buffer: import bufferm;
+
+sprint: import sys;
+elogterm, elogclose, eloginsert, elogdelete, elogreplace, elogapply: import editlog;
+cmdtab, allocstring, freestring, Straddc, curtext, editing, newaddr, cmdlookup, editerror: import edit;
+error, stralloc, strfree, warning, skipbl, findbl: import utils;
+lookfile, cleanname, dirname: import look;
+undo, run: import exec;
+Ref, Lock, row, cedit: import dat;
+rxcompile, rxexecute, rxbexecute: import regx;
+allwindows: import rowm;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ utils = mods.utils;
+ edit = mods.edit;
+ editlog = mods.editlog;
+ windowm = mods.windowm;
+ look = mods.look;
+ columnm = mods.columnm;
+ bufferm = mods.bufferm;
+ exec = mods.exec;
+ dat = mods.dat;
+ textm = mods.textm;
+ regx = mods.regx;
+ filem = mods.filem;
+ rowm = mods.rowm;
+
+ none.r.q0 = none.r.q1 = 0;
+ none.f = nil;
+}
+
+cmdtabexec(i: int, t: ref Text, cp: ref Cmd): int
+{
+ case (cmdtab[i].fnc){
+ C_nl => i = nl_cmd(t, cp);
+ C_a => i = a_cmd(t, cp);
+ C_b => i = b_cmd(t, cp);
+ C_c => i = c_cmd(t, cp);
+ C_d => i = d_cmd(t, cp);
+ C_e => i = e_cmd(t, cp);
+ C_f => i = f_cmd(t, cp);
+ C_g => i = g_cmd(t, cp);
+ C_i => i = i_cmd(t, cp);
+ C_m => i = m_cmd(t, cp);
+ C_p => i = p_cmd(t, cp);
+ C_s => i = s_cmd(t, cp);
+ C_u => i = u_cmd(t, cp);
+ C_w => i = w_cmd(t, cp);
+ C_x => i = x_cmd(t, cp);
+ C_eq => i = eq_cmd(t, cp);
+ C_B => i = B_cmd(t, cp);
+ C_D => i = D_cmd(t, cp);
+ C_X => i = X_cmd(t, cp);
+ C_pipe => i = pipe_cmd(t, cp);
+ * => error("bad case in cmdtabexec");
+ }
+ return i;
+}
+
+Glooping: int;
+nest: int;
+Enoname := "no file name given";
+
+addr: Address;
+menu: ref File;
+sel: Rangeset;
+collection: string;
+ncollection: int;
+
+clearcollection()
+{
+ collection = nil;
+ ncollection = 0;
+}
+
+resetxec()
+{
+ Glooping = nest = 0;
+ clearcollection();
+}
+
+mkaddr(f: ref File): Address
+{
+ a: Address;
+
+ a.r.q0 = f.curtext.q0;
+ a.r.q1 = f.curtext.q1;
+ a.f = f;
+ return a;
+}
+
+none: Address;
+
+cmdexec(t: ref Text, cp: ref Cmd): int
+{
+ i: int;
+ ap: ref Addr;
+ f: ref File;
+ w: ref Window;
+ dot: Address;
+
+ if(t == nil)
+ w = nil;
+ else
+ w = t.w;
+ if(w==nil && (cp.addr==nil || cp.addr.typex!='"') &&
+ utils->strchr("bBnqUXY!", cp.cmdc) < 0&&
+ !(cp.cmdc=='D' && cp.text!=nil))
+ editerror("no current window");
+ i = cmdlookup(cp.cmdc); # will be -1 for '{'
+ f = nil;
+ if(t!=nil && t.w!=nil){
+ t = t.w.body;
+ f = t.file;
+ f.curtext = t;
+ }
+ if(i>=0 && cmdtab[i].defaddr != aNo){
+ if((ap=cp.addr)==nil && cp.cmdc!='\n'){
+ cp.addr = ap = newaddr();
+ ap.typex = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap.typex = '*';
+ }else if(ap!=nil && ap.typex=='"' && ap.next==nil && cp.cmdc!='\n'){
+ ap.next = newaddr();
+ ap.next.typex = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap.next.typex = '*';
+ }
+ if(cp.addr!=nil){ # may be false for '\n' (only)
+ if(f!=nil){
+ dot = mkaddr(f);
+ addr = cmdaddress(ap, dot, 0);
+ }else # a "
+ addr = cmdaddress(ap, none, 0);
+ f = addr.f;
+ t = f.curtext;
+ }
+ }
+ case(cp.cmdc){
+ '{' =>
+ dot = mkaddr(f);
+ if(cp.addr != nil)
+ dot = cmdaddress(cp.addr, dot, 0);
+ for(cp = cp.cmd; cp!=nil; cp = cp.next){
+ t.q0 = dot.r.q0;
+ t.q1 = dot.r.q1;
+ cmdexec(t, cp);
+ }
+ break;
+ * =>
+ if(i < 0)
+ editerror(sprint("unknown command %c in cmdexec", cp.cmdc));
+ i = cmdtabexec(i, t, cp);
+ return i;
+ }
+ return 1;
+}
+
+edittext(f: ref File, q: int, r: string, nr: int): string
+{
+ case(editing){
+ Inactive =>
+ return "permission denied";
+ Inserting =>
+ eloginsert(f, q, r, nr);
+ return nil;
+ Collecting =>
+ collection += r[0: nr];
+ ncollection += nr;
+ return nil;
+ * =>
+ return "unknown state in edittext";
+ }
+}
+
+# string is known to be NUL-terminated
+filelist(t: ref Text, r: string, nr: int): string
+{
+ if(nr == 0)
+ return nil;
+ (r, nr) = skipbl(r, nr);
+ if(r[0] != '<')
+ return r;
+ # use < command to collect text
+ clearcollection();
+ runpipe(t, '<', r[1:], nr-1, Collecting);
+ return collection;
+}
+
+a_cmd(t: ref Text, cp: ref Cmd): int
+{
+ return append(t.file, cp, addr.r.q1);
+}
+
+b_cmd(nil: ref Text, cp: ref Cmd): int
+{
+ f: ref File;
+
+ f = tofile(cp.text);
+ if(nest == 0)
+ pfilename(f);
+ curtext = f.curtext;
+ return TRUE;
+}
+
+B_cmd(t: ref Text, cp: ref Cmd): int
+{
+ listx, r, s: string;
+ nr: int;
+
+ listx = filelist(t, cp.text.r, cp.text.n);
+ if(listx == nil)
+ editerror(Enoname);
+ r = listx;
+ nr = len r;
+ (r, nr) = skipbl(r, nr);
+ if(nr == 0)
+ look->new(t, t, nil, 0, 0, r, 0);
+ else while(nr > 0){
+ (s, nr) = findbl(r, nr);
+ look->new(t, t, nil, 0, 0, r, len r);
+ if(nr > 0)
+ (r, nr) = skipbl(s[1:], nr-1);
+ }
+ clearcollection();
+ return TRUE;
+}
+
+c_cmd(t: ref Text, cp: ref Cmd): int
+{
+ elogreplace(t.file, addr.r.q0, addr.r.q1, cp.text.r, cp.text.n);
+ return TRUE;
+}
+
+d_cmd(t: ref Text, nil: ref Cmd): int
+{
+ if(addr.r.q1 > addr.r.q0)
+ elogdelete(t.file, addr.r.q0, addr.r.q1);
+ return TRUE;
+}
+
+D1(t: ref Text)
+{
+ if(t.w.body.file.ntext>1 || t.w.clean(FALSE, FALSE))
+ t.col.close(t.w, TRUE);
+}
+
+D_cmd(t: ref Text, cp: ref Cmd): int
+{
+ listx, r, s, n: string;
+ nr, nn: int;
+ w: ref Window;
+ dir, rs: Runestr;
+ buf: string;
+
+ listx = filelist(t, cp.text.r, cp.text.n);
+ if(listx == nil){
+ D1(t);
+ return TRUE;
+ }
+ dir = dirname(t, nil, 0);
+ r = listx;
+ nr = len r;
+ (r, nr) = skipbl(r, nr);
+ do{
+ (s, nr) = findbl(r, nr);
+ # first time through, could be empty string, meaning delete file empty name
+ nn = len r;
+ if(r[0]=='/' || nn==0 || dir.nr==0){
+ rs.r = r;
+ rs.nr = nn;
+ }else{
+ n = dir.r + "/" + r;
+ rs = cleanname(n, dir.nr+1+nn);
+ }
+ w = lookfile(rs.r, rs.nr);
+ if(w == nil){
+ buf = sprint("no such file %s", rs.r);
+ rs.r = nil;
+ editerror(buf);
+ }
+ rs.r = nil;
+ D1(w.body);
+ if(nr > 0)
+ (r, nr) = skipbl(s[1:], nr-1);
+ }while(nr > 0);
+ clearcollection();
+ dir.r = nil;
+ return TRUE;
+}
+
+readloader(f: ref File, q0: int, r: string, nr: int): int
+{
+ if(nr > 0)
+ eloginsert(f, q0, r, nr);
+ return 0;
+}
+
+e_cmd(t: ref Text , cp: ref Cmd): int
+{
+ name: string;
+ f: ref File;
+ i, q0, q1, nulls, samename, allreplaced, ok: int;
+ fd: ref Sys->FD;
+ s, tmp: string;
+ d: Dir;
+
+ f = t.file;
+ q0 = addr.r.q0;
+ q1 = addr.r.q1;
+ if(cp.cmdc == 'e'){
+ if(t.w.clean(TRUE, FALSE)==FALSE)
+ editerror(""); # winclean generated message already
+ q0 = 0;
+ q1 = f.buf.nc;
+ }
+ allreplaced = (q0==0 && q1==f.buf.nc);
+ name = cmdname(f, cp.text, cp.cmdc=='e');
+ if(name == nil)
+ editerror(Enoname);
+ i = len name;
+ samename = name == t.file.name;
+ s = name;
+ name = nil;
+ fd = sys->open(s, Sys->OREAD);
+ if(fd == nil){
+ tmp = sprint("can't open %s: %r", s);
+ s = nil;
+ editerror(tmp);
+ }
+ (ok, d) = sys->fstat(fd);
+ if(ok >=0 && (d.mode&Sys->DMDIR)){
+ fd = nil;
+ tmp = sprint("%s is a directory", s);
+ s = nil;
+ editerror(tmp);
+ }
+ elogdelete(f, q0, q1);
+ nulls = 0;
+ bufferm->loadfile(fd, q1, Dat->READL, nil, f);
+ s = nil;
+ fd = nil;
+ if(nulls)
+ warning(nil, sprint("%s: NUL bytes elided\n", s));
+ else if(allreplaced && samename)
+ f.editclean = TRUE;
+ return TRUE;
+}
+
+f_cmd(t: ref Text, cp: ref Cmd): int
+{
+ name: string;
+
+ name = cmdname(t.file, cp.text, TRUE);
+ name = nil;
+ pfilename(t.file);
+ return TRUE;
+}
+
+g_cmd(t: ref Text, cp: ref Cmd): int
+{
+ ok: int;
+
+ if(t.file != addr.f){
+ warning(nil, "internal error: g_cmd f!=addr.f\n");
+ return FALSE;
+ }
+ if(rxcompile(cp.re.r) == FALSE)
+ editerror("bad regexp in g command");
+ (ok, sel) = rxexecute(t, nil, addr.r.q0, addr.r.q1);
+ if(ok ^ cp.cmdc=='v'){
+ t.q0 = addr.r.q0;
+ t.q1 = addr.r.q1;
+ return cmdexec(t, cp.cmd);
+ }
+ return TRUE;
+}
+
+i_cmd(t: ref Text, cp: ref Cmd): int
+{
+ return append(t.file, cp, addr.r.q0);
+}
+
+# int
+# k_cmd(File *f, Cmd *cp)
+# {
+# USED(cp);
+# f->mark = addr.r;
+# return TRUE;
+# }
+
+copy(f: ref File, addr2: Address)
+{
+ p: int;
+ ni: int;
+ buf: ref Astring;
+
+ buf = stralloc(BUFSIZE);
+ for(p=addr.r.q0; p<addr.r.q1; p+=ni){
+ ni = addr.r.q1-p;
+ if(ni > BUFSIZE)
+ ni = BUFSIZE;
+ f.buf.read(p, buf, 0, ni);
+ eloginsert(addr2.f, addr2.r.q1, buf.s, ni);
+ }
+ strfree(buf);
+}
+
+move(f: ref File, addr2: Address)
+{
+ if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
+ elogdelete(f, addr.r.q0, addr.r.q1);
+ copy(f, addr2);
+ }else if(addr.r.q0 >= addr2.r.q1){
+ copy(f, addr2);
+ elogdelete(f, addr.r.q0, addr.r.q1);
+ }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
+ ; # move to self; no-op
+ }else
+ editerror("move overlaps itself");
+}
+
+m_cmd(t: ref Text, cp: ref Cmd): int
+{
+ dot, addr2: Address;
+
+ dot = mkaddr(t.file);
+ addr2 = cmdaddress(cp.mtaddr, dot, 0);
+ if(cp.cmdc == 'm')
+ move(t.file, addr2);
+ else
+ copy(t.file, addr2);
+ return TRUE;
+}
+
+# int
+# n_cmd(File *f, Cmd *cp)
+# {
+# int i;
+# USED(f);
+# USED(cp);
+# for(i = 0; i<file.nused; i++){
+# if(file.filepptr[i] == cmd)
+# continue;
+# f = file.filepptr[i];
+# Strduplstr(&genstr, &f->name);
+# filename(f);
+# }
+# return TRUE;
+#}
+
+p_cmd(t: ref Text, nil: ref Cmd): int
+{
+ return pdisplay(t.file);
+}
+
+s_cmd(t: ref Text, cp: ref Cmd): int
+{
+ i, j, k, c, m, n, nrp, didsub, ok: int;
+ p1, op, delta: int;
+ buf: ref String;
+ rp: array of Rangeset;
+ err: string;
+ rbuf: ref Astring;
+
+ n = cp.num;
+ op= -1;
+ if(rxcompile(cp.re.r) == FALSE)
+ editerror("bad regexp in s command");
+ nrp = 0;
+ rp = nil;
+ delta = 0;
+ didsub = FALSE;
+ for(p1 = addr.r.q0; p1<=addr.r.q1; ){
+ (ok, sel) = rxexecute(t, nil, p1, addr.r.q1);
+ if(!ok)
+ break;
+ if(sel[0].q0 == sel[0].q1){ # empty match?
+ if(sel[0].q0 == op){
+ p1++;
+ continue;
+ }
+ p1 = sel[0].q1+1;
+ }else
+ p1 = sel[0].q1;
+ op = sel[0].q1;
+ if(--n>0)
+ continue;
+ nrp++;
+ orp := rp;
+ rp = array[nrp] of Rangeset;
+ rp[0: ] = orp[0:nrp-1];
+ rp[nrp-1] = copysel(sel);
+ orp = nil;
+ }
+ rbuf = stralloc(BUFSIZE);
+ buf = allocstring(0);
+ for(m=0; m<nrp; m++){
+ buf.n = 0;
+ buf.r = nil;
+ sel = rp[m];
+ for(i = 0; i<cp.text.n; i++)
+ if((c = cp.text.r[i])=='\\' && i<cp.text.n-1){
+ c = cp.text.r[++i];
+ if('1'<=c && c<='9') {
+ j = c-'0';
+ if(sel[j].q1-sel[j].q0>BUFSIZE){
+ err = "replacement string too long";
+ rp = nil;
+ freestring(buf);
+ strfree(rbuf);
+ editerror(err);
+ return FALSE;
+ }
+ t.file.buf.read(sel[j].q0, rbuf, 0, sel[j].q1-sel[j].q0);
+ for(k=0; k<sel[j].q1-sel[j].q0; k++)
+ Straddc(buf, rbuf.s[k]);
+ }else
+ Straddc(buf, c);
+ }else if(c!='&')
+ Straddc(buf, c);
+ else{
+ if(sel[0].q1-sel[0].q0>BUFSIZE){
+ err = "right hand side too long in substitution";
+ rp = nil;
+ freestring(buf);
+ strfree(rbuf);
+ editerror(err);
+ return FALSE;
+ }
+ t.file.buf.read(sel[0].q0, rbuf, 0, sel[0].q1-sel[0].q0);
+ for(k=0; k<sel[0].q1-sel[0].q0; k++)
+ Straddc(buf, rbuf.s[k]);
+ }
+ elogreplace(t.file, sel[0].q0, sel[0].q1, buf.r, buf.n);
+ delta -= sel[0].q1-sel[0].q0;
+ delta += buf.n;
+ didsub = 1;
+ if(!cp.flag)
+ break;
+ }
+ rp = nil;
+ freestring(buf);
+ strfree(rbuf);
+ if(!didsub && nest==0)
+ editerror("no substitution");
+ t.q0 = addr.r.q0;
+ t.q1 = addr.r.q1+delta;
+ return TRUE;
+}
+
+u_cmd(t: ref Text, cp: ref Cmd): int
+{
+ n, oseq, flag: int;
+
+ n = cp.num;
+ flag = TRUE;
+ if(n < 0){
+ n = -n;
+ flag = FALSE;
+ }
+ oseq = -1;
+ while(n-->0 && t.file.seq!=0 && t.file.seq!=oseq){
+ oseq = t.file.seq;
+warning(nil, sprint("seq %d\n", t.file.seq));
+ undo(t, flag);
+ }
+ return TRUE;
+}
+
+w_cmd(t: ref Text, cp: ref Cmd): int
+{
+ r: string;
+ f: ref File;
+
+ f = t.file;
+ if(f.seq == dat->seq)
+ editerror("can't write file with pending modifications");
+ r = cmdname(f, cp.text, FALSE);
+ if(r == nil)
+ editerror("no name specified for 'w' command");
+ exec->putfile(f, addr.r.q0, addr.r.q1, r);
+ # r is freed by putfile
+ return TRUE;
+}
+
+x_cmd(t: ref Text, cp: ref Cmd): int
+{
+ if(cp.re!=nil)
+ looper(t.file, cp, cp.cmdc=='x');
+ else
+ linelooper(t.file, cp);
+ return TRUE;
+}
+
+X_cmd(nil: ref Text, cp: ref Cmd): int
+{
+ filelooper(cp, cp.cmdc=='X');
+ return TRUE;
+}
+
+runpipe(t: ref Text, cmd: int, cr: string, ncr: int, state: int)
+{
+ r, s: string;
+ n: int;
+ dir: Runestr;
+ w: ref Window;
+
+ (r, n) = skipbl(cr, ncr);
+ if(n == 0)
+ editerror("no command specified for >");
+ w = nil;
+ if(state == Inserting){
+ w = t.w;
+ t.q0 = addr.r.q0;
+ t.q1 = addr.r.q1;
+ if(cmd == '<' || cmd=='|')
+ elogdelete(t.file, t.q0, t.q1);
+ }
+ tmps := "z";
+ tmps[0] = cmd;
+ s = tmps + r;
+ n++;
+ dir.r = nil;
+ dir.nr = 0;
+ if(t != nil)
+ dir = dirname(t, nil, 0);
+ if(dir.nr==1 && dir.r[0]=='.'){ # sigh
+ dir.r = nil;
+ dir.nr = 0;
+ }
+ editing = state;
+ if(t!=nil && t.w!=nil)
+ t.w.refx.inc(); # run will decref
+ spawn run(w, s, dir.r, dir.nr, TRUE, nil, nil, TRUE);
+ s = nil;
+ if(t!=nil && t.w!=nil)
+ t.w.unlock();
+ row.qlock.unlock();
+ <- cedit;
+ row.qlock.lock();
+ editing = Inactive;
+ if(t!=nil && t.w!=nil)
+ t.w.lock('M');
+}
+
+pipe_cmd(t: ref Text, cp: ref Cmd): int
+{
+ runpipe(t, cp.cmdc, cp.text.r, cp.text.n, Inserting);
+ return TRUE;
+}
+
+nlcount(t: ref Text, q0: int, q1: int): int
+{
+ nl: int;
+ buf: ref Astring;
+ i, nbuf: int;
+
+ buf = stralloc(BUFSIZE);
+ nbuf = 0;
+ i = nl = 0;
+ while(q0 < q1){
+ if(i == nbuf){
+ nbuf = q1-q0;
+ if(nbuf > BUFSIZE)
+ nbuf = BUFSIZE;
+ t.file.buf.read(q0, buf, 0, nbuf);
+ i = 0;
+ }
+ if(buf.s[i++] == '\n')
+ nl++;
+ q0++;
+ }
+ strfree(buf);
+ return nl;
+}
+
+printposn(t: ref Text, charsonly: int)
+{
+ l1, l2: int;
+
+ if(t != nil && t.file != nil && t.file.name != nil)
+ warning(nil, t.file.name + ":");
+ if(!charsonly){
+ l1 = 1+nlcount(t, 0, addr.r.q0);
+ l2 = l1+nlcount(t, addr.r.q0, addr.r.q1);
+ # check if addr ends with '\n'
+ if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && t.readc(addr.r.q1-1)=='\n')
+ --l2;
+ warning(nil, sprint("%ud", l1));
+ if(l2 != l1)
+ warning(nil, sprint(",%ud", l2));
+ warning(nil, "\n");
+ # warning(nil, "; ");
+ return;
+ }
+ warning(nil, sprint("#%d", addr.r.q0));
+ if(addr.r.q1 != addr.r.q0)
+ warning(nil, sprint(",#%d", addr.r.q1));
+ warning(nil, "\n");
+}
+
+eq_cmd(t: ref Text, cp: ref Cmd): int
+{
+ charsonly: int;
+
+ case(cp.text.n){
+ 0 =>
+ charsonly = FALSE;
+ break;
+ 1 =>
+ if(cp.text.r[0] == '#'){
+ charsonly = TRUE;
+ break;
+ }
+ * =>
+ charsonly = TRUE;
+ editerror("newline expected");
+ }
+ printposn(t, charsonly);
+ return TRUE;
+}
+
+nl_cmd(t: ref Text, cp: ref Cmd): int
+{
+ a: Address;
+ f: ref File;
+
+ f = t.file;
+ if(cp.addr == nil){
+ # First put it on newline boundaries
+ a = mkaddr(f);
+ addr = lineaddr(0, a, -1);
+ a = lineaddr(0, a, 1);
+ addr.r.q1 = a.r.q1;
+ if(addr.r.q0==t.q0 && addr.r.q1==t.q1){
+ a = mkaddr(f);
+ addr = lineaddr(1, a, 1);
+ }
+ }
+ t.show(addr.r.q0, addr.r.q1, TRUE);
+ return TRUE;
+}
+
+append(f: ref File, cp: ref Cmd, p: int): int
+{
+ if(cp.text.n > 0)
+ eloginsert(f, p, cp.text.r, cp.text.n);
+ return TRUE;
+}
+
+pdisplay(f: ref File): int
+{
+ p1, p2: int;
+ np: int;
+ buf: ref Astring;
+
+ p1 = addr.r.q0;
+ p2 = addr.r.q1;
+ if(p2 > f.buf.nc)
+ p2 = f.buf.nc;
+ buf = stralloc(BUFSIZE);
+ while(p1 < p2){
+ np = p2-p1;
+ if(np>BUFSIZE-1)
+ np = BUFSIZE-1;
+ f.buf.read(p1, buf, 0, np);
+ warning(nil, sprint("%s", buf.s[0:np]));
+ p1 += np;
+ }
+ strfree(buf);
+ f.curtext.q0 = addr.r.q0;
+ f.curtext.q1 = addr.r.q1;
+ return TRUE;
+}
+
+pfilename(f: ref File)
+{
+ dirty: int;
+ w: ref Window;
+
+ w = f.curtext.w;
+ # same check for dirty as in settag, but we know ncache==0
+ dirty = !w.isdir && !w.isscratch && f.mod;
+ warning(nil, sprint("%c%c%c %s\n", " '"[dirty],
+ '+', " ."[curtext!=nil && curtext.file==f], f.name));
+}
+
+loopcmd(f: ref File, cp: ref Cmd, rp: array of Range, nrp: int)
+{
+ i: int;
+
+ for(i=0; i<nrp; i++){
+ f.curtext.q0 = rp[i].q0;
+ f.curtext.q1 = rp[i].q1;
+ cmdexec(f.curtext, cp);
+ }
+}
+
+looper(f: ref File, cp: ref Cmd, xy: int)
+{
+ p, op, nrp, ok: int;
+ r, tr: Range;
+ rp: array of Range;
+
+ r = addr.r;
+ if(xy)
+ op = -1;
+ else
+ op = r.q0;
+ nest++;
+ if(rxcompile(cp.re.r) == FALSE)
+ editerror(sprint("bad regexp in %c command", cp.cmdc));
+ nrp = 0;
+ rp = nil;
+ for(p = r.q0; p<=r.q1; ){
+ (ok, sel) = rxexecute(f.curtext, nil, p, r.q1);
+ if(!ok){ # no match, but y should still run
+ if(xy || op>r.q1)
+ break;
+ tr.q0 = op;
+ tr.q1 = r.q1;
+ p = r.q1+1; # exit next loop
+ }else{
+ if(sel[0].q0==sel[0].q1){ # empty match?
+ if(sel[0].q0==op){
+ p++;
+ continue;
+ }
+ p = sel[0].q1+1;
+ }else
+ p = sel[0].q1;
+ if(xy)
+ tr = sel[0];
+ else{
+ tr.q0 = op;
+ tr.q1 = sel[0].q0;
+ }
+ }
+ op = sel[0].q1;
+ nrp++;
+ orp := rp;
+ rp = array[nrp] of Range;
+ rp[0: ] = orp[0: nrp-1];
+ rp[nrp-1] = tr;
+ orp = nil;
+ }
+ loopcmd(f, cp.cmd, rp, nrp);
+ rp = nil;
+ --nest;
+}
+
+linelooper(f: ref File, cp: ref Cmd)
+{
+ nrp, p: int;
+ r, linesel: Range;
+ a, a3: Address;
+ rp: array of Range;
+
+ nest++;
+ nrp = 0;
+ rp = nil;
+ r = addr.r;
+ a3.f = f;
+ a3.r.q0 = a3.r.q1 = r.q0;
+ a = lineaddr(0, a3, 1);
+ linesel = a.r;
+ for(p = r.q0; p<r.q1; p = a3.r.q1){
+ a3.r.q0 = a3.r.q1;
+ if(p!=r.q0 || linesel.q1==p){
+ a = lineaddr(1, a3, 1);
+ linesel = a.r;
+ }
+ if(linesel.q0 >= r.q1)
+ break;
+ if(linesel.q1 >= r.q1)
+ linesel.q1 = r.q1;
+ if(linesel.q1 > linesel.q0)
+ if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
+ a3.r = linesel;
+ nrp++;
+ orp := rp;
+ rp = array[nrp] of Range;
+ rp[0: ] = orp[0: nrp-1];
+ rp[nrp-1] = linesel;
+ orp = nil;
+ continue;
+ }
+ break;
+ }
+ loopcmd(f, cp.cmd, rp, nrp);
+ rp = nil;
+ --nest;
+}
+
+loopstruct: ref Looper;
+
+alllooper(w: ref Window, lp: ref Looper)
+{
+ t: ref Text;
+ cp: ref Cmd;
+
+ cp = lp.cp;
+# if(w.isscratch || w.isdir)
+# return;
+ t = w.body;
+ # only use this window if it's the current window for the file
+ if(t.file.curtext != t)
+ return;
+# if(w.nopen[QWevent] > 0)
+# return;
+ # no auto-execute on files without names
+ if(cp.re==nil && t.file.name==nil)
+ return;
+ if(cp.re==nil || filematch(t.file, cp.re)==lp.XY){
+ olpw := lp.w;
+ lp.w = array[lp.nw+1] of ref Window;
+ lp.w[0: ] = olpw[0: lp.nw];
+ lp.w[lp.nw++] = w;
+ olpw = nil;
+ }
+}
+
+filelooper(cp: ref Cmd, XY: int)
+{
+ i: int;
+
+ if(Glooping++)
+ editerror(sprint("can't nest %c command", "YX"[XY]));
+ nest++;
+
+ if(loopstruct == nil)
+ loopstruct = ref Looper;
+ loopstruct.cp = cp;
+ loopstruct.XY = XY;
+ if(loopstruct.w != nil) # error'ed out last time
+ loopstruct.w = nil;
+ loopstruct.w = nil;
+ loopstruct.nw = 0;
+ aw := ref Allwin.LP(loopstruct);
+ allwindows(Edit->ALLLOOPER, aw);
+ aw = nil;
+ for(i=0; i<loopstruct.nw; i++)
+ cmdexec(loopstruct.w[i].body, cp.cmd);
+ loopstruct.w = nil;
+
+ --Glooping;
+ --nest;
+}
+
+nextmatch(f: ref File, r: ref String, p: int, sign: int)
+{
+ ok: int;
+
+ if(rxcompile(r.r) == FALSE)
+ editerror("bad regexp in command address");
+ if(sign >= 0){
+ (ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
+ if(!ok)
+ editerror("no match for regexp");
+ if(sel[0].q0==sel[0].q1 && sel[0].q0==p){
+ if(++p>f.buf.nc)
+ p = 0;
+ (ok, sel) = rxexecute(f.curtext, nil, p, 16r7FFFFFFF);
+ if(!ok)
+ editerror("address");
+ }
+ }else{
+ (ok, sel) = rxbexecute(f.curtext, p);
+ if(!ok)
+ editerror("no match for regexp");
+ if(sel[0].q0==sel[0].q1 && sel[0].q1==p){
+ if(--p<0)
+ p = f.buf.nc;
+ (ok, sel) = rxbexecute(f.curtext, p);
+ if(!ok)
+ editerror("address");
+ }
+ }
+}
+
+cmdaddress(ap: ref Addr, a: Address, sign: int): Address
+{
+ f := a.f;
+ a1, a2: Address;
+
+ do{
+ case(ap.typex){
+ 'l' or
+ '#' =>
+ if(ap.typex == '#')
+ a = charaddr(ap.num, a, sign);
+ else
+ a = lineaddr(ap.num, a, sign);
+ break;
+
+ '.' =>
+ a = mkaddr(f);
+ break;
+
+ '$' =>
+ a.r.q0 = a.r.q1 = f.buf.nc;
+ break;
+
+ '\'' =>
+editerror("can't handle '");
+# a.r = f.mark;
+ break;
+
+ '?' =>
+ sign = -sign;
+ if(sign == 0)
+ sign = -1;
+ if(sign >= 0)
+ v := a.r.q1;
+ else
+ v = a.r.q0;
+ nextmatch(f, ap.re, v, sign);
+ a.r = sel[0];
+ break;
+
+ '/' =>
+ if(sign >= 0)
+ v := a.r.q1;
+ else
+ v = a.r.q0;
+ nextmatch(f, ap.re, v, sign);
+ a.r = sel[0];
+ break;
+
+ '"' =>
+ f = matchfile(ap.re);
+ a = mkaddr(f);
+ break;
+
+ '*' =>
+ a.r.q0 = 0;
+ a.r.q1 = f.buf.nc;
+ return a;
+
+ ',' or
+ ';' =>
+ if(ap.left!=nil)
+ a1 = cmdaddress(ap.left, a, 0);
+ else{
+ a1.f = a.f;
+ a1.r.q0 = a1.r.q1 = 0;
+ }
+ if(ap.typex == ';'){
+ f = a1.f;
+ a = a1;
+ f.curtext.q0 = a1.r.q0;
+ f.curtext.q1 = a1.r.q1;
+ }
+ if(ap.next!=nil)
+ a2 = cmdaddress(ap.next, a, 0);
+ else{
+ a2.f = a.f;
+ a2.r.q0 = a2.r.q1 = f.buf.nc;
+ }
+ if(a1.f != a2.f)
+ editerror("addresses in different files");
+ a.f = a1.f;
+ a.r.q0 = a1.r.q0;
+ a.r.q1 = a2.r.q1;
+ if(a.r.q1 < a.r.q0)
+ editerror("addresses out of order");
+ return a;
+
+ '+' or
+ '-' =>
+ sign = 1;
+ if(ap.typex == '-')
+ sign = -1;
+ if(ap.next==nil || ap.next.typex=='+' || ap.next.typex=='-')
+ a = lineaddr(1, a, sign);
+ break;
+ * =>
+ error("cmdaddress");
+ return a;
+ }
+ }while((ap = ap.next)!=nil); # assign =
+ return a;
+}
+
+alltofile(w: ref Window, tp: ref Tofile)
+{
+ t: ref Text;
+
+ if(tp.f != nil)
+ return;
+ if(w.isscratch || w.isdir)
+ return;
+ t = w.body;
+ # only use this window if it's the current window for the file
+ if(t.file.curtext != t)
+ return;
+# if(w.nopen[QWevent] > 0)
+# return;
+ if(tp.r.r == t.file.name)
+ tp.f = t.file;
+}
+
+tofile(r: ref String): ref File
+{
+ t: ref Tofile;
+ rr: String;
+
+ (rr.r, r.n) = skipbl(r.r, r.n);
+ t = ref Tofile;
+ t.f = nil;
+ t.r = ref String;
+ *t.r = rr;
+ aw := ref Allwin.FF(t);
+ allwindows(Edit->ALLTOFILE, aw);
+ aw = nil;
+ if(t.f == nil)
+ editerror(sprint("no such file\"%s\"", rr.r));
+ return t.f;
+}
+
+allmatchfile(w: ref Window, tp: ref Tofile)
+{
+ t: ref Text;
+
+ if(w.isscratch || w.isdir)
+ return;
+ t = w.body;
+ # only use this window if it's the current window for the file
+ if(t.file.curtext != t)
+ return;
+# if(w.nopen[QWevent] > 0)
+# return;
+ if(filematch(w.body.file, tp.r)){
+ if(tp.f != nil)
+ editerror(sprint("too many files match \"%s\"", tp.r.r));
+ tp.f = w.body.file;
+ }
+}
+
+matchfile(r: ref String): ref File
+{
+ tf: ref Tofile;
+
+ tf = ref Tofile;
+ tf.f = nil;
+ tf.r = r;
+ aw := ref Allwin.FF(tf);
+ allwindows(Edit->ALLMATCHFILE, aw);
+ aw = nil;
+
+ if(tf.f == nil)
+ editerror(sprint("no file matches \"%s\"", r.r));
+ return tf.f;
+}
+
+filematch(f: ref File, r: ref String): int
+{
+ buf: string;
+ w: ref Window;
+ match, i, dirty: int;
+ s: Rangeset;
+
+ # compile expr first so if we get an error, we haven't allocated anything
+ if(rxcompile(r.r) == FALSE)
+ editerror("bad regexp in file match");
+ w = f.curtext.w;
+ # same check for dirty as in settag, but we know ncache==0
+ dirty = !w.isdir && !w.isscratch && f.mod;
+ buf = sprint("%c%c%c %s\n", " '"[dirty],
+ '+', " ."[curtext!=nil && curtext.file==f], f.name);
+ (match, s) = rxexecute(nil, buf, 0, i);
+ buf = nil;
+ return match;
+}
+
+charaddr(l: int, addr: Address, sign: int): Address
+{
+ if(sign == 0)
+ addr.r.q0 = addr.r.q1 = l;
+ else if(sign < 0)
+ addr.r.q1 = addr.r.q0 -= l;
+ else if(sign > 0)
+ addr.r.q0 = addr.r.q1 += l;
+ if(addr.r.q0<0 || addr.r.q1>addr.f.buf.nc)
+ editerror("address out of range");
+ return addr;
+}
+
+lineaddr(l: int, addr: Address, sign: int): Address
+{
+ n: int;
+ c: int;
+ f := addr.f;
+ a: Address;
+ p: int;
+
+ a.f = f;
+ if(sign >= 0){
+ if(l == 0){
+ if(sign==0 || addr.r.q1==0){
+ a.r.q0 = a.r.q1 = 0;
+ return a;
+ }
+ a.r.q0 = addr.r.q1;
+ p = addr.r.q1-1;
+ }else{
+ if(sign==0 || addr.r.q1==0){
+ p = 0;
+ n = 1;
+ }else{
+ p = addr.r.q1-1;
+ n = f.curtext.readc(p++)=='\n';
+ }
+ while(n < l){
+ if(p >= f.buf.nc)
+ editerror("address out of range");
+ if(f.curtext.readc(p++) == '\n')
+ n++;
+ }
+ a.r.q0 = p;
+ }
+ while(p < f.buf.nc && f.curtext.readc(p++)!='\n')
+ ;
+ a.r.q1 = p;
+ }else{
+ p = addr.r.q0;
+ if(l == 0)
+ a.r.q1 = addr.r.q0;
+ else{
+ for(n = 0; n<l; ){ # always runs once
+ if(p == 0){
+ if(++n != l)
+ editerror("address out of range");
+ }else{
+ c = f.curtext.readc(p-1);
+ if(c != '\n' || ++n != l)
+ p--;
+ }
+ }
+ a.r.q1 = p;
+ if(p > 0)
+ p--;
+ }
+ while(p > 0 && f.curtext.readc(p-1)!='\n') # lines start after a newline
+ p--;
+ a.r.q0 = p;
+ }
+ return a;
+}
+
+allfilecheck(w: ref Window, fp: ref Filecheck)
+{
+ f: ref File;
+
+ f = w.body.file;
+ if(w.body.file == fp.f)
+ return;
+ if(fp.r == f.name)
+ warning(nil, sprint("warning: duplicate file name \"%s\"\n", fp.r));
+}
+
+cmdname(f: ref File, str: ref String , set: int): string
+{
+ r, s: string;
+ n: int;
+ fc: ref Filecheck;
+ newname: Runestr;
+
+ r = nil;
+ n = str.n;
+ s = str.r;
+ if(n == 0){
+ # no name; use existing
+ if(f.name == nil)
+ return nil;
+ return f.name;
+ }
+ (s, n) = skipbl(s, n);
+ if(n == 0)
+ ;
+ else{
+ if(s[0] == '/'){
+ r = s;
+ }else{
+ newname = dirname(f.curtext, s, n);
+ r = newname.r;
+ n = newname.nr;
+ }
+ fc = ref Filecheck;
+ fc.f = f;
+ fc.r = r;
+ fc.nr = n;
+ aw := ref Allwin.FC(fc);
+ allwindows(Edit->ALLFILECHECK, aw);
+ aw = nil;
+ if(f.name == nil)
+ set = TRUE;
+ }
+
+ if(set && r[0: n] != f.name){
+ f.mark();
+ f.mod = TRUE;
+ f.curtext.w.dirty = TRUE;
+ f.curtext.w.setname(r, n);
+ }
+ return r;
+}
+
+copysel(rs: Rangeset): Rangeset
+{
+ nrs := array[NRange] of Range;
+ for(i := 0; i < NRange; i++)
+ nrs[i] = rs[i];
+ return nrs;
+}
--- /dev/null
+++ b/appl/acme/ecmd.m
@@ -1,0 +1,18 @@
+Editcmd: module {
+
+ PATH: con "/dis/acme/ecmd.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ cmdexec: fn(a0: ref Textm->Text, a1: ref Edit->Cmd): int;
+ resetxec: fn();
+ cmdaddress: fn(a0: ref Edit->Addr, a1: Edit->Address, a2: int): Edit->Address;
+ edittext: fn(f: ref Filem->File, q: int, r: string, nr: int): string;
+
+ alllooper: fn(w: ref Windowm->Window, lp: ref Dat->Looper);
+ alltofile: fn(w: ref Windowm->Window, tp: ref Dat->Tofile);
+ allmatchfile: fn(w: ref Windowm->Window, tp: ref Dat->Tofile);
+ allfilecheck: fn(w: ref Windowm->Window, fp: ref Dat->Filecheck);
+
+ readloader: fn(f: ref Filem->File, q0: int, r: string, nr: int): int;
+};
--- /dev/null
+++ b/appl/acme/edit.b
@@ -1,0 +1,676 @@
+implement Edit;
+
+include "common.m";
+
+sys: Sys;
+dat: Dat;
+utils: Utils;
+textm: Textm;
+windowm: Windowm;
+rowm: Rowm;
+scroll: Scroll;
+editlog: Editlog;
+editcomd: Editcmd;
+
+sprint, print: import sys;
+FALSE, TRUE, BUFSIZE, Null, Empty, Inactive: import Dat;
+warning, error, strchr: import utils;
+Text: import textm;
+File: import Filem;
+Window: import windowm;
+allwindows: import rowm;
+scrdraw: import scroll;
+elogterm, elogapply: import editlog;
+cmdexec, resetxec: import editcomd;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ textm = mods.textm;
+ windowm = mods.windowm;
+ rowm = mods.rowm;
+ scroll = mods.scroll;
+ editlog = mods.editlog;
+ editcomd = mods.editcmd;
+ editing = Inactive;
+}
+
+linex: con "\n";
+wordx: con "\t\n";
+
+cmdtab = array[28] of {
+# cmdc text regexp addr defcmd defaddr count token fn
+ Cmdt ( '\n', 0, 0, 0, 0, aDot, 0, nil, C_nl ),
+ Cmdt ( 'a', 1, 0, 0, 0, aDot, 0, nil, C_a ),
+ Cmdt ( 'b', 0, 0, 0, 0, aNo, 0, linex, C_b ),
+ Cmdt ( 'c', 1, 0, 0, 0, aDot, 0, nil, C_c ),
+ Cmdt ( 'd', 0, 0, 0, 0, aDot, 0, nil, C_d ),
+ Cmdt ( 'e', 0, 0, 0, 0, aNo, 0, wordx, C_e ),
+ Cmdt ( 'f', 0, 0, 0, 0, aNo, 0, wordx, C_f ),
+ Cmdt ( 'g', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
+ Cmdt ( 'i', 1, 0, 0, 0, aDot, 0, nil, C_i ),
+ Cmdt ( 'm', 0, 0, 1, 0, aDot, 0, nil, C_m ),
+ Cmdt ( 'p', 0, 0, 0, 0, aDot, 0, nil, C_p ),
+ Cmdt ( 'r', 0, 0, 0, 0, aDot, 0, wordx, C_e ),
+ Cmdt ( 's', 0, 1, 0, 0, aDot, 1, nil, C_s ),
+ Cmdt ( 't', 0, 0, 1, 0, aDot, 0, nil, C_m ),
+ Cmdt ( 'u', 0, 0, 0, 0, aNo, 2, nil, C_u ),
+ Cmdt ( 'v', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
+ Cmdt ( 'w', 0, 0, 0, 0, aAll, 0, wordx, C_w ),
+ Cmdt ( 'x', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
+ Cmdt ( 'y', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
+ Cmdt ( '=', 0, 0, 0, 0, aDot, 0, linex, C_eq ),
+ Cmdt ( 'B', 0, 0, 0, 0, aNo, 0, linex, C_B ),
+ Cmdt ( 'D', 0, 0, 0, 0, aNo, 0, linex, C_D ),
+ Cmdt ( 'X', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
+ Cmdt ( 'Y', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
+ Cmdt ( '<', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ Cmdt ( '|', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ Cmdt ( '>', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ # deliberately unimplemented
+ # Cmdt ( 'k', 0, 0, 0, 0, aDot, 0, nil, C_k ),
+ # Cmdt ( 'n', 0, 0, 0, 0, aNo, 0, nil, C_n ),
+ # Cmdt ( 'q', 0, 0, 0, 0, aNo, 0, nil, C_q ),
+ # Cmdt ( '!', 0, 0, 0, 0, aNo, 0, linex, C_plan9 ),
+ Cmdt (0, 0, 0, 0, 0, 0, 0, nil, -1 )
+};
+
+cmdstartp: string;
+cmdendp: int;
+cmdp: int;
+editerrc: chan of string;
+
+lastpat : ref String;
+patset: int;
+
+# cmdlist: ref List;
+# addrlist: ref List;
+# stringlist: ref List;
+
+editwaitproc(pid : int, sync: chan of int)
+{
+ fd : ref Sys->FD;
+ n : int;
+
+ sys->pctl(Sys->FORKFD, nil);
+ w := sprint("#p/%d/wait", pid);
+ fd = sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ error("fd == nil in editwaitproc");
+ sync <-= sys->pctl(0, nil);
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;){
+ if ((n = sys->read(fd, buf, len buf))<0)
+ error("bad read in editwaitproc");
+ status = string buf[0:n];
+ dat->cwait <-= status;
+ }
+}
+
+editthread()
+{
+ cmdp: ref Cmd;
+
+ mypid := sys->pctl(0, nil);
+ sync := chan of int;
+ spawn editwaitproc(mypid, sync);
+ yourpid := <- sync;
+ while((cmdp=parsecmd(0)) != nil){
+# ocurfile = curfile;
+# loaded = curfile && !curfile->unread;
+ if(cmdexec(curtext, cmdp) == 0)
+ break;
+ freecmd();
+ }
+ editerrc <-= nil;
+ utils->postnote(Utils->PNPROC, mypid, yourpid, "kill");
+}
+
+allelogterm(w: ref Window)
+{
+ elogterm(w.body.file);
+}
+
+alleditinit(w: ref Window)
+{
+ w.tag.commit(TRUE);
+ w.body.commit(TRUE);
+ w.body.file.editclean = FALSE;
+}
+
+allupdate(w: ref Window)
+{
+ t: ref Text;
+ i: int;
+ f: ref File;
+
+ t = w.body;
+ f = t.file;
+ if(f.curtext != t) # do curtext only
+ return;
+ if(f.elog.typex == Null)
+ elogterm(f);
+ else if(f.elog.typex != Empty){
+ elogapply(f);
+ if(f.editclean){
+ f.mod = FALSE;
+ for(i=0; i<f.ntext; i++)
+ f.text[i].w.dirty = FALSE;
+ }
+ t.setselect(t.q0, t.q1);
+ scrdraw(t);
+ w.settag();
+ }
+}
+
+editerror(s: string)
+{
+ # print("%s", s);
+ freecmd();
+ allwindows(ALLELOGTERM, nil); # truncate the edit logs
+ editerrc <-= s;
+ exit;
+}
+
+editcmd(ct: ref Text, r: string, n: int)
+{
+ err: string;
+
+ if(n == 0)
+ return;
+ if(2*n > BUFSIZE){
+ warning(nil, "string too long\n");
+ return;
+ }
+
+ allwindows(ALLEDITINIT, nil);
+ cmdstartp = r[0:n];
+ if(r[n-1] != '\n')
+ cmdstartp[n++] = '\n';
+ cmdendp = n;
+ cmdp = 0;
+ if(ct.w == nil)
+ curtext = nil;
+ else
+ curtext = ct.w.body;
+ resetxec();
+ if(editerrc == nil){
+ editerrc = chan of string;
+ lastpat = allocstring(0);
+ }
+ spawn editthread();
+ err = <- editerrc;
+ editing = Inactive;
+ if(err != nil)
+ warning(nil, sprint("Edit: %s\n", err));
+
+ # update everyone whose edit log has data
+ allwindows(ALLUPDATE, nil);
+}
+
+getch(): int
+{
+ if(cmdp == cmdendp)
+ return -1;
+ return cmdstartp[cmdp++];
+}
+
+nextc(): int
+{
+ if(cmdp == cmdendp)
+ return -1;
+ return cmdstartp[cmdp];
+}
+
+ungetch()
+{
+ if(--cmdp < 0)
+ error("ungetch");
+}
+
+getnum(signok: int): int
+{
+ n: int;
+ c, sign: int;
+
+ n = 0;
+ 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;
+}
+
+cmdskipbl(): int
+{
+ c: int;
+ do
+ c = getch();
+ while(c==' ' || c=='\t');
+ if(c >= 0)
+ ungetch();
+ return c;
+}
+
+# Check that list has room for one more element.
+# growlist(l: ref List)
+# {
+# if(l.elems == nil || l.nalloc==0){
+# l.nalloc = INCR;
+# l.elems = array[INCR] of Listelement;
+# l.nused = 0;
+# }else if(l.nused == l.nalloc){
+# old := l.elems;
+# l.elems = array[l.nalloc+INCR] of Listelement;
+# l.elems[0:] = old[0:l.nalloc];
+# l.nalloc += INCR;
+# }
+# }
+
+# Remove the ith element from the list
+# dellist(l: ref List, i: int)
+# {
+# l.elems[i:] = l.elems[i+1:l.nused];
+# l.nused--;
+# }
+
+# Add a new element, whose position is i, to the list
+# inslist(l: ref List, i: int, val: int)
+# {
+# growlist(l);
+# l.elems[i+1:] = l.elems[i:l.nused];
+# l.elems[i] = val;
+# l.nused++;
+# }
+
+# listfree(l: ref List)
+# {
+# l.elems = nil;
+# }
+
+allocstring(n: int): ref String
+{
+ s: ref String;
+
+ s = ref String;
+ s.n = n;
+ s.r = string array[s.n] of { * => byte '\0' };
+ return s;
+}
+
+freestring(s: ref String)
+{
+ s.r = nil;
+}
+
+newcmd(): ref Cmd
+{
+ p: ref Cmd;
+
+ p = ref Cmd;
+ # inslist(cmdlist, cmdlist.nused, p);
+ return p;
+}
+
+newstring(n: int): ref String
+{
+ p: ref String;
+
+ p = allocstring(n);
+ # inslist(stringlist, stringlist.nused, p);
+ return p;
+}
+
+newaddr(): ref Addr
+{
+ p: ref Addr;
+
+ p = ref Addr;
+ # inslist(addrlist, addrlist.nused, p);
+ return p;
+}
+
+freecmd()
+{
+ # i: int;
+
+ # cmdlist.elems = nil;
+ # addrlist.elems = nil;
+ # stringlist.elems = nil;
+ # cmdlist.nused = addrlist.nused = stringlist.nused = 0;
+}
+
+okdelim(c: int)
+{
+ if(c=='\\' || ('a'<=c && c<='z')
+ || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+ editerror(sprint("bad delimiter %c\n", c));
+}
+
+atnl()
+{
+ c: int;
+
+ cmdskipbl();
+ c = getch();
+ if(c != '\n')
+ editerror(sprint("newline expected (saw %c)", c));
+}
+
+Straddc(s: ref String, c: int)
+{
+ s.r[s.n++] = c;
+}
+
+getrhs(s: ref String, delim: int, cmd: int)
+{
+ c: int;
+
+ while((c = getch())>0 && c!=delim && c!='\n'){
+ if(c == '\\'){
+ if((c=getch()) <= 0)
+ error("bad right hand side");
+ 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 delimiter, '\n' or whatever
+}
+
+collecttoken(end: string): ref String
+{
+ c: int;
+
+ s := newstring(0);
+
+ while((c=nextc())==' ' || c=='\t')
+ Straddc(s, getch()); # blanks significant for getname()
+ while((c=getch())>0 && strchr(end, c)<0)
+ Straddc(s, c);
+ if(c != '\n')
+ atnl();
+ return s;
+}
+
+collecttext(): ref String
+{
+ s: ref String;
+ begline, i, c, delim: int;
+
+ s = newstring(0);
+ if(cmdskipbl()=='\n'){
+ getch();
+ i = 0;
+ do{
+ begline = i;
+ while((c = getch())>0 && c!='\n'){
+ i++;
+ Straddc(s, c);
+ }
+ i++;
+ Straddc(s, '\n');
+ if(c < 0)
+ return s;
+ }while(s.r[begline]!='.' || s.r[begline+1]!='\n');
+ s.r[s.n-2] = '\0';
+ }else{
+ okdelim(delim = getch());
+ getrhs(s, delim, 'a');
+ if(nextc()==delim)
+ getch();
+ atnl();
+ }
+ return s;
+}
+
+cmdlookup(c: int): int
+{
+ i: int;
+
+ for(i=0; cmdtab[i].cmdc; i++)
+ if(cmdtab[i].cmdc == c)
+ return i;
+ return -1;
+}
+
+parsecmd(nest: int): ref Cmd
+{
+ i, c: int;
+ cp, ncp: ref Cmd;
+ cmd: ref Cmd;
+
+ cmd = ref Cmd;
+ cmd.next = cmd.cmd = nil;
+ cmd.re = nil;
+ cmd.flag = cmd.num = 0;
+ cmd.addr = compoundaddr();
+ if(cmdskipbl() == -1)
+ return nil;
+ if((c=getch())==-1)
+ return nil;
+ cmd.cmdc = c;
+ if(cmd.cmdc=='c' && nextc()=='d'){ # sleazy two-character case
+ getch(); # the 'd'
+ cmd.cmdc='c'|16r100;
+ }
+ i = cmdlookup(cmd.cmdc);
+ if(i >= 0){
+ if(cmd.cmdc == '\n'){
+ cp = newcmd();
+ *cp = *cmd;
+ return cp;
+ # let nl_cmd work it all out
+ }
+ ct := cmdtab[i];
+ if(ct.defaddr==aNo && cmd.addr != nil)
+ editerror("command takes no address");
+ 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')){
+ cmdskipbl();
+ if((c = getch())=='\n' || c<0)
+ editerror("no address");
+ okdelim(c);
+ cmd.re = getregexp(c);
+ if(ct.cmdc == 's'){
+ cmd.text = newstring(0);
+ getrhs(cmd.text, c, 's');
+ if(nextc() == c){
+ getch();
+ if(nextc() == 'g')
+ cmd.flag = getch();
+ }
+
+ }
+ }
+ }
+ if(ct.addr && (cmd.mtaddr=simpleaddr())==nil)
+ editerror("bad address");
+ if(ct.defcmd){
+ if(cmdskipbl() == '\n'){
+ getch();
+ cmd.cmd = newcmd();
+ cmd.cmd.cmdc = ct.defcmd;
+ }else if((cmd.cmd = parsecmd(nest))==nil)
+ error("defcmd");
+ }else if(ct.text)
+ cmd.text = collecttext();
+ else if(ct.token != nil)
+ cmd.text = collecttoken(ct.token);
+ else
+ atnl();
+ }else
+ case(cmd.cmdc){
+ '{' =>
+ cp = nil;
+ do{
+ if(cmdskipbl()=='\n')
+ getch();
+ ncp = parsecmd(nest+1);
+ if(cp != nil)
+ cp.next = ncp;
+ else
+ cmd.cmd = ncp;
+ }while((cp = ncp) != nil);
+ break;
+ '}' =>
+ atnl();
+ if(nest==0)
+ editerror("right brace with no left brace");
+ return nil;
+ 'c'|16r100 =>
+ editerror("unimplemented command cd");
+ * =>
+ editerror(sprint("unknown command %c", cmd.cmdc));
+ }
+ cp = newcmd();
+ *cp = *cmd;
+ return cp;
+}
+
+getregexp(delim: int): ref String
+{
+ buf, r: ref String;
+ i, c: int;
+
+ buf = allocstring(0);
+ for(i=0; ; i++){
+ if((c = getch())=='\\'){
+ if(nextc()==delim)
+ c = getch();
+ else if(nextc()=='\\'){
+ Straddc(buf, c);
+ c = getch();
+ }
+ }else if(c==delim || c=='\n')
+ break;
+ if(i >= BUFSIZE)
+ editerror("regular expression too long");
+ Straddc(buf, c);
+ }
+ if(c!=delim && c)
+ ungetch();
+ if(buf.n > 0){
+ patset = TRUE;
+ freestring(lastpat);
+ lastpat = buf;
+ }else
+ freestring(buf);
+ if(lastpat.n == 0)
+ editerror("no regular expression defined");
+ r = newstring(lastpat.n);
+ k := lastpat.n;
+ for(j := 0; j < k; j++)
+ r.r[j] = lastpat.r[j]; # newstring put \0 at end
+ return r;
+}
+
+simpleaddr(): ref Addr
+{
+ addr: Addr;
+ ap, nap: ref Addr;
+
+ addr.next = nil;
+ addr.left = nil;
+ case(cmdskipbl()){
+ '#' =>
+ addr.typex = getch();
+ addr.num = getnum(1);
+ break;
+ '0' to '9' =>
+ addr.num = getnum(1);
+ addr.typex='l';
+ break;
+ '/' or '?' or '"' =>
+ addr.re = getregexp(addr.typex = getch());
+ break;
+ '.' or
+ '$' or
+ '+' or
+ '-' or
+ '\'' =>
+ addr.typex = getch();
+ break;
+ * =>
+ return nil;
+ }
+ if((addr.next = simpleaddr()) != nil)
+ case(addr.next.typex){
+ '.' or
+ '$' or
+ '\'' =>
+ if(addr.typex!='"')
+ editerror("bad address syntax");
+ break;
+ '"' =>
+ editerror("bad address syntax");
+ break;
+ 'l' or
+ '#' =>
+ if(addr.typex=='"')
+ break;
+ if(addr.typex!='+' && addr.typex!='-'){
+ # insert the missing '+'
+ nap = newaddr();
+ nap.typex='+';
+ nap.next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ '/' or
+ '?' =>
+ if(addr.typex!='+' && addr.typex!='-'){
+ # insert the missing '+'
+ nap = newaddr();
+ nap.typex='+';
+ nap.next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ '+' or
+ '-' =>
+ break;
+ * =>
+ error("simpleaddr");
+ }
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+
+compoundaddr(): ref Addr
+{
+ addr: Addr;
+ ap, next: ref Addr;
+
+ addr.left = simpleaddr();
+ if((addr.typex = cmdskipbl())!=',' && addr.typex!=';')
+ return addr.left;
+ getch();
+ next = addr.next = compoundaddr();
+ if(next != nil && (next.typex==',' || next.typex==';') && next.left==nil)
+ editerror("bad address syntax");
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+
--- /dev/null
+++ b/appl/acme/edit.m
@@ -1,0 +1,85 @@
+Edit: module {
+ #pragma varargck argpos editerror 1
+
+ PATH: con "/dis/acme/edit.dis";
+
+ String: adt{
+ n: int;
+ r: string;
+ };
+
+ Addr: adt{
+ typex: int; # # (char addr), l (line addr), / ? . $ + - , ;
+ num: int;
+ next: cyclic ref Addr; # or right side of , and ;
+ re: ref String;
+ left: cyclic ref Addr; # left side of , and ;
+ };
+
+ Address: adt{
+ r: Dat->Range;
+ f: ref Filem->File;
+ };
+
+ Cmd: adt{
+ addr: ref Addr; # address (range of text)
+ re: ref String; # regular expression for e.g. 'x'
+ next: cyclic ref Cmd; # pointer to next element in {}
+ num: int;
+ flag: int; # whatever
+ cmdc: int; # command character; 'x' etc.
+ cmd: cyclic ref Cmd; # target of x, g, {, etc.
+ text: ref String; # text of a, c, i; rhs of s
+ mtaddr: ref Addr; # address for m, t
+ };
+
+ Cmdt: adt{
+ cmdc: int; # command character
+ text: int; # takes a textual argument?
+ regexp: int; # takes a regular expression?
+ addr: int; # takes an address (m or t)?
+ defcmd: int; # default command; 0==>none
+ defaddr: int; # default address
+ count: int; # takes a count e.g. s2///
+ token: string; # takes text terminated by one of these
+ fnc: int; # function to call with parse tree
+ };
+
+ cmdtab: array of Cmdt;
+
+ INCR: con 25; # delta when growing list
+
+ List: adt{
+ nalloc: int;
+ nused: int;
+ pick{
+ C => cmdptr: array of ref Cmd;
+ S => stringptr: array of ref String;
+ A => addrptr: array of ref Addr;
+ }
+ };
+
+ aNo, aDot, aAll: con iota; # default addresses
+
+ ALLLOOPER, ALLTOFILE, ALLMATCHFILE, ALLFILECHECK, ALLELOGTERM, ALLEDITINIT, ALLUPDATE: con iota;
+
+ C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: con iota;
+
+ editing: int;
+ curtext: ref Textm->Text;
+
+ init : fn(mods : ref Dat->Mods);
+
+ allocstring: fn(a0: int): ref String;
+ freestring: fn(a0: ref String);
+ getregexp: fn(a0: int): ref String;
+ newaddr: fn(): ref Addr;
+ editcmd: fn(t: ref Textm->Text, r: string, n: int);
+ editerror: fn(a0: string);
+ cmdlookup: fn(a0: int): int;
+ Straddc: fn(a0: ref String, a1: int);
+
+ allelogterm: fn(w: ref Windowm->Window);
+ alleditinit: fn(w: ref Windowm->Window);
+ allupdate: fn(w: ref Windowm->Window);
+};
--- /dev/null
+++ b/appl/acme/elog.b
@@ -1,0 +1,353 @@
+implement Editlog;
+
+include "common.m";
+
+sys: Sys;
+utils: Utils;
+buffm: Bufferm;
+filem: Filem;
+textm: Textm;
+edit: Edit;
+
+sprint, fprint: import sys;
+FALSE, TRUE, BUFSIZE, Empty, Null, Delete, Insert, Replace, Filename, Astring: import Dat;
+File: import filem;
+Buffer: import buffm;
+Text: import textm;
+error, warning, stralloc, strfree: import utils;
+editerror: import edit;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ utils = mods.utils;
+ buffm = mods.bufferm;
+ filem = mods.filem;
+ textm = mods.textm;
+ edit = mods.edit;
+}
+
+Wsequence := "warning: changes out of sequence\n";
+warned := FALSE;
+
+#
+# Log of changes made by editing commands. Three reasons for this:
+# 1) We want addresses in commands to apply to old file, not file-in-change.
+# 2) It's difficult to track changes correctly as things move, e.g. ,x m$
+# 3) This gives an opportunity to optimize by merging adjacent changes.
+# It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
+# separate implementation. To do this well, we use Replace as well as
+# Insert and Delete
+#
+
+Buflog: adt{
+ typex: int; # Replace, Filename
+ q0: int; # location of change (unused in f)
+ nd: int; # runes to delete
+ nr: int; # runes in string or file name
+};
+
+Buflogsize: con 7;
+SHM : con 16rffff;
+
+pack(b: Buflog) : string
+{
+ a := "0123456";
+ a[0] = b.typex;
+ a[1] = b.q0&SHM;
+ a[2] = (b.q0>>16)&SHM;
+ a[3] = b.nd&SHM;
+ a[4] = (b.nd>>16)&SHM;
+ a[5] = b.nr&SHM;
+ a[6] = (b.nr>>16)&SHM;
+ return a;
+}
+
+scopy(s1: ref Astring, m: int, s2: string, n: int, o: int)
+{
+ p := o-n;
+ for(i := 0; i < p; i++)
+ s1.s[m++] = s2[n++];
+}
+
+#
+# Minstring shouldn't be very big or we will do lots of I/O for small changes.
+# Maxstring is BUFSIZE so we can fbufalloc() once and not realloc elog.r.
+#
+Minstring: con 16; # distance beneath which we merge changes
+Maxstring: con BUFSIZE; # maximum length of change we will merge into one
+
+eloginit(f: ref File)
+{
+ if(f.elog.typex != Empty)
+ return;
+ f.elog.typex = Null;
+ if(f.elogbuf == nil)
+ f.elogbuf = buffm->newbuffer();
+ # f.elogbuf = ref Buffer;
+ if(f.elog.r == nil)
+ f.elog.r = stralloc(BUFSIZE);
+ f.elogbuf.reset();
+}
+
+elogclose(f: ref File)
+{
+ if(f.elogbuf != nil){
+ f.elogbuf.close();
+ f.elogbuf = nil;
+ }
+}
+
+elogreset(f: ref File)
+{
+ f.elog.typex = Null;
+ f.elog.nd = 0;
+ f.elog.nr = 0;
+}
+
+elogterm(f: ref File)
+{
+ elogreset(f);
+ if(f.elogbuf != nil)
+ f.elogbuf.reset();
+ f.elog.typex = Empty;
+ if(f.elog.r != nil){
+ strfree(f.elog.r);
+ f.elog.r = nil;
+ }
+ warned = FALSE;
+}
+
+elogflush(f: ref File)
+{
+ b: Buflog;
+
+ b.typex = f.elog.typex;
+ b.q0 = f.elog.q0;
+ b.nd = f.elog.nd;
+ b.nr = f.elog.nr;
+ case(f.elog.typex){
+ * =>
+ warning(nil, sprint("unknown elog type 0x%ux\n", f.elog.typex));
+ break;
+ Null =>
+ break;
+ Insert or
+ Replace =>
+ if(f.elog.nr > 0)
+ f.elogbuf.insert(f.elogbuf.nc, f.elog.r.s, f.elog.nr);
+ f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize);
+ break;
+ Delete =>
+ f.elogbuf.insert(f.elogbuf.nc, pack(b), Buflogsize);
+ break;
+ }
+ elogreset(f);
+}
+
+elogreplace(f: ref File, q0: int, q1: int, r: string, nr: int)
+{
+ gap: int;
+
+ if(q0==q1 && nr==0)
+ return;
+ eloginit(f);
+ if(f.elog.typex!=Null && q0<f.elog.q0){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ # try to merge with previous
+ gap = q0 - (f.elog.q0+f.elog.nd); # gap between previous and this
+ if(f.elog.typex==Replace && f.elog.nr+gap+nr<Maxstring){
+ if(gap < Minstring){
+ if(gap > 0){
+ f.buf.read(f.elog.q0+f.elog.nd, f.elog.r, f.elog.nr, gap);
+ f.elog.nr += gap;
+ }
+ f.elog.nd += gap + q1-q0;
+ scopy(f.elog.r, f.elog.nr, r, 0, nr);
+ f.elog.nr += nr;
+ return;
+ }
+ }
+ elogflush(f);
+ f.elog.typex = Replace;
+ f.elog.q0 = q0;
+ f.elog.nd = q1-q0;
+ f.elog.nr = nr;
+ if(nr > BUFSIZE)
+ editerror(sprint("internal error: replacement string too large(%d)", nr));
+ scopy(f.elog.r, 0, r, 0, nr);
+}
+
+eloginsert(f: ref File, q0: int, r: string, nr: int)
+{
+ n: int;
+
+ if(nr == 0)
+ return;
+ eloginit(f);
+ if(f.elog.typex!=Null && q0<f.elog.q0){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ # try to merge with previous
+ if(f.elog.typex==Insert && q0==f.elog.q0 && f.elog.nr+nr<Maxstring){
+ ofer := f.elog.r;
+ f.elog.r = stralloc(f.elog.nr+nr);
+ scopy(f.elog.r, 0, ofer.s, 0, f.elog.nr);
+ scopy(f.elog.r, f.elog.nr, r, 0, nr);
+ f.elog.nr += nr;
+ strfree(ofer);
+ return;
+ }
+ while(nr > 0){
+ elogflush(f);
+ f.elog.typex = Insert;
+ f.elog.q0 = q0;
+ n = nr;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ f.elog.nr = n;
+ scopy(f.elog.r, 0, r, 0, n);
+ r = r[n:];
+ nr -= n;
+ }
+}
+
+elogdelete(f: ref File, q0: int, q1: int)
+{
+ if(q0 == q1)
+ return;
+ eloginit(f);
+ if(f.elog.typex!=Null && q0<f.elog.q0+f.elog.nd){
+ if(warned++ == 0)
+ warning(nil, Wsequence);
+ elogflush(f);
+ }
+ # try to merge with previous
+ if(f.elog.typex==Delete && f.elog.q0+f.elog.nd==q0){
+ f.elog.nd += q1-q0;
+ return;
+ }
+ elogflush(f);
+ f.elog.typex = Delete;
+ f.elog.q0 = q0;
+ f.elog.nd = q1-q0;
+}
+
+elogapply(f: ref File)
+{
+ b: Buflog;
+ buf: ref Astring;
+ i, n, up, mod : int;
+ log: ref Buffer;
+
+ elogflush(f);
+ log = f.elogbuf;
+ t := f.curtext;
+
+ a := stralloc(Buflogsize);
+ buf = stralloc(BUFSIZE);
+ mod = FALSE;
+
+ #
+ # The edit commands have already updated the selection in t.q0, t.q1.
+ # The text.insert and text.delete calls below will update it again, so save the
+ # current setting and restore it at the end.
+ #
+ q0 := t.q0;
+ q1 := t.q1;
+
+ while(log.nc > 0){
+ up = log.nc-Buflogsize;
+ log.read(up, a, 0, Buflogsize);
+ b.typex = a.s[0];
+ b.q0 = a.s[1]|(a.s[2]<<16);
+ b.nd = a.s[3]|(a.s[4]<<16);
+ b.nr = a.s[5]|(a.s[6]<<16);
+ case(b.typex){
+ * =>
+ error(sprint("elogapply: 0x%ux\n", b.typex));
+ break;
+
+ Replace =>
+ if(!mod){
+ mod = TRUE;
+ f.mark();
+ }
+ # if(b.nd == b.nr && b.nr <= BUFSIZE){
+ # up -= b.nr;
+ # log.read(up, buf, 0, b.nr);
+ # t.replace(b.q0, b.q0+b.nd, buf.s, b.nr, TRUE, 0);
+ # break;
+ # }
+ t.delete(b.q0, b.q0+b.nd, TRUE);
+ up -= b.nr;
+ for(i=0; i<b.nr; i+=n){
+ n = b.nr - i;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ log.read(up+i, buf, 0, n);
+ t.insert(b.q0+i, buf.s, n, TRUE, 0);
+ }
+ # t.q0 = b.q0;
+ # t.q1 = b.q0+b.nr;
+ break;
+
+ Delete =>
+ if(!mod){
+ mod = TRUE;
+ f.mark();
+ }
+ t.delete(b.q0, b.q0+b.nd, TRUE);
+ # t.q0 = b.q0;
+ # t.q1 = b.q0;
+ break;
+
+ Insert =>
+ if(!mod){
+ mod = TRUE;
+ f.mark();
+ }
+ up -= b.nr;
+ for(i=0; i<b.nr; i+=n){
+ n = b.nr - i;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ log.read(up+i, buf, 0, n);
+ t.insert(b.q0+i, buf.s, n, TRUE, 0);
+ }
+ # t.q0 = b.q0;
+ # t.q1 = b.q0+b.nr;
+ break;
+
+# Filename =>
+# f.seq = u.seq;
+# f.unsetname(epsilon);
+# f.mod = u.mod;
+# up -= u.n;
+# if(u.n == 0)
+# f.name = nil;
+# else{
+# fn0 := stralloc(u.n);
+# delta.read(up, fn0, 0, u.n);
+# f.name = fn0.s;
+# strfree(fn0);
+# }
+# break;
+#
+ }
+ log.delete(up, log.nc);
+ }
+ strfree(buf);
+ strfree(a);
+ elogterm(f);
+
+ t.q0 = q0;
+ t.q1 = q1;
+ if(t.q1 > f.buf.nc) # can't happen
+ t.q1 = f.buf.nc;
+}
--- /dev/null
+++ b/appl/acme/elog.m
@@ -1,0 +1,24 @@
+Editlog: module {
+
+ PATH: con "/dis/acme/elog.dis";
+
+ Elog: adt{
+ typex: int; # Delete, Insert, Filename
+ q0: int; # location of change (unused in f)
+ nd: int; # number of deleted characters
+ nr: int; # runes in string or file name
+ r: ref Dat->Astring;
+ };
+
+ init : fn(mods : ref Dat->Mods);
+
+ elogterm: fn(a0: ref Filem->File);
+ elogclose: fn(a0: ref Filem->File);
+ eloginsert: fn(a0: ref Filem->File, a1: int, a2: string, a3: int);
+ elogdelete: fn(a0: ref Filem->File, a1: int, a2: int);
+ elogreplace: fn(a0: ref Filem->File, a1: int, a2: int, a3: string, a4: int);
+ elogapply: fn(a0: ref Filem->File);
+
+};
+
+
--- /dev/null
+++ b/appl/acme/exec.b
@@ -1,0 +1,1350 @@
+implement Exec;
+
+include "common.m";
+
+sys : Sys;
+dat : Dat;
+acme : Acme;
+utils : Utils;
+graph : Graph;
+gui : Gui;
+lookx : Look;
+bufferm : Bufferm;
+textm : Textm;
+scrl : Scroll;
+filem : Filem;
+windowm : Windowm;
+rowm : Rowm;
+columnm : Columnm;
+fsys : Fsys;
+editm: Edit;
+
+Dir, OREAD, OWRITE : import Sys;
+EVENTSIZE, QWaddr, QWdata, QWevent, Astring : import dat;
+Lock, Reffont, Ref, seltext, seq, row : import dat;
+warning, error, skipbl, findbl, stralloc, strfree, exec : import utils;
+dirname : import lookx;
+Body, Text : import textm;
+File : import filem;
+sprint : import sys;
+TRUE, FALSE, XXX, BUFSIZE : import Dat;
+Buffer : import bufferm;
+Row : import rowm;
+Column : import columnm;
+Window : import windowm;
+setalphabet: import textm;
+
+# snarfbuf : ref Buffer;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ acme = mods.acme;
+ utils = mods.utils;
+ graph = mods.graph;
+ gui = mods.gui;
+ lookx = mods.look;
+ bufferm = mods.bufferm;
+ textm = mods.textm;
+ scrl = mods.scroll;
+ filem = mods.filem;
+ rowm = mods.rowm;
+ windowm = mods.windowm;
+ columnm = mods.columnm;
+ fsys = mods.fsys;
+ editm = mods.edit;
+
+ snarfbuf = bufferm->newbuffer();
+}
+
+Exectab : adt {
+ name : string;
+ fun : int;
+ mark : int;
+ flag1 : int;
+ flag2 : int;
+};
+
+F_ALPHABET, F_CUT, F_DEL, F_DELCOL, F_DUMP, F_EDIT, F_EXITX, F_FONTX, F_GET, F_ID, F_INCL, F_KILL, F_LIMBO, F_LINENO, F_LOCAL, F_LOOK, F_NEW, F_NEWCOL, F_PASTE, F_PUT, F_PUTALL, F_UNDO, F_SEND, F_SORT, F_TAB, F_ZEROX : con iota;
+
+exectab := array[] of {
+ Exectab ( "Alphabet", F_ALPHABET, FALSE, XXX, XXX ),
+ Exectab ( "Cut", F_CUT, TRUE, TRUE, TRUE ),
+ Exectab ( "Del", F_DEL, FALSE, FALSE, XXX ),
+ Exectab ( "Delcol", F_DELCOL, FALSE, XXX, XXX ),
+ Exectab ( "Delete", F_DEL, FALSE, TRUE, XXX ),
+ Exectab ( "Dump", F_DUMP, FALSE, TRUE, XXX ),
+ Exectab ( "Edit", F_EDIT, FALSE, XXX, XXX ),
+ Exectab ( "Exit", F_EXITX, FALSE, XXX, XXX ),
+ Exectab ( "Font", F_FONTX, FALSE, XXX, XXX ),
+ Exectab ( "Get", F_GET, FALSE, TRUE, XXX ),
+ Exectab ( "ID", F_ID, FALSE, XXX, XXX ),
+ Exectab ( "Incl", F_INCL, FALSE, XXX, XXX ),
+ Exectab ( "Kill", F_KILL, FALSE, XXX, XXX ),
+ Exectab ( "Limbo", F_LIMBO, FALSE, XXX, XXX ),
+ Exectab ( "Lineno", F_LINENO, FALSE, XXX, XXX ),
+ Exectab ( "Load", F_DUMP, FALSE, FALSE, XXX ),
+ Exectab ( "Local", F_LOCAL, FALSE, XXX, XXX ),
+ Exectab ( "Look", F_LOOK, FALSE, XXX, XXX ),
+ Exectab ( "New", F_NEW, FALSE, XXX, XXX ),
+ Exectab ( "Newcol", F_NEWCOL, FALSE, XXX, XXX ),
+ Exectab ( "Paste", F_PASTE, TRUE, TRUE, XXX ),
+ Exectab ( "Put", F_PUT, FALSE, XXX, XXX ),
+ Exectab ( "Putall", F_PUTALL, FALSE, XXX, XXX ),
+ Exectab ( "Redo", F_UNDO, FALSE, FALSE, XXX ),
+ Exectab ( "Send", F_SEND, TRUE, XXX, XXX ),
+ Exectab ( "Snarf", F_CUT, FALSE, TRUE, FALSE ),
+ Exectab ( "Sort", F_SORT, FALSE, XXX, XXX ),
+ Exectab ( "Tab", F_TAB, FALSE, XXX, XXX ),
+ Exectab ( "Undo", F_UNDO, FALSE, TRUE, XXX ),
+ Exectab ( "Zerox", F_ZEROX, FALSE, XXX, XXX ),
+ Exectab ( nil, 0, 0, 0, 0 ),
+};
+
+runfun(fun : int, et, t, argt : ref Text, flag1, flag2 : int, arg : string, narg : int)
+{
+ case (fun) {
+ F_ALPHABET => alphabet(et, argt, arg, narg);
+ F_CUT => cut(et, t, flag1, flag2);
+ F_DEL => del(et, flag1);
+ F_DELCOL => delcol(et);
+ F_DUMP => dump(argt, flag1, arg, narg);
+ F_EDIT => edit(et, argt, arg, narg);
+ F_EXITX => exitx();
+ F_FONTX => fontx(et, t, argt, arg, narg);
+ F_GET => get(et, t, argt, flag1, arg, narg);
+ F_ID => id(et);
+ F_INCL => incl(et, argt, arg, narg);
+ F_KILL => kill(argt, arg, narg);
+ F_LIMBO => limbo(et);
+ F_LINENO => lineno(et);
+ F_LOCAL => local(et, argt, arg);
+ F_LOOK => look(et, t, argt);
+ F_NEW => lookx->new(et, t, argt, flag1, flag2, arg, narg);
+ F_NEWCOL => newcol(et);
+ F_PASTE => paste(et, t, flag1, flag2);
+ F_PUT => put(et, argt, arg, narg);
+ F_PUTALL => putall();
+ F_UNDO => undo(et, flag1);
+ F_SEND => send(et, t);
+ F_SORT => sort(et);
+ F_TAB => tab(et, argt, arg, narg);
+ F_ZEROX => zerox(et, t);
+ * => error("bad case in runfun()");
+ }
+}
+
+lookup(r : string, n : int) : int
+{
+ nr : int;
+
+ (r, n) = skipbl(r, n);
+ if(n == 0)
+ return -1;
+ (nil, nr) = findbl(r, n);
+ nr = n-nr;
+ for(i := 0; exectab[i].name != nil; i++)
+ if (r[0:nr] == exectab[i].name)
+ return i;
+ return -1;
+}
+
+isexecc(c : int) : int
+{
+ if(lookx->isfilec(c))
+ return 1;
+ return c=='<' || c=='|' || c=='>';
+}
+
+execute(t : ref Text, aq0 : int, aq1 : int, external : int, argt : ref Text)
+{
+ q0, q1 : int;
+ r : ref Astring;
+ s, dir, aa, a : string;
+ e : int;
+ c, n, f : int;
+
+ q0 = aq0;
+ q1 = aq1;
+ if(q1 == q0){ # expand to find word (actually file name)
+ # if in selection, choose selection
+ if(t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){
+ q0 = t.q0;
+ q1 = t.q1;
+ }else{
+ while(q1<t.file.buf.nc && isexecc(c=t.readc(q1)) && c!=':')
+ q1++;
+ while(q0>0 && isexecc(c=t.readc(q0-1)) && c!=':')
+ q0--;
+ if(q1 == q0)
+ return;
+ }
+ }
+ r = stralloc(q1-q0);
+ t.file.buf.read(q0, r, 0, q1-q0);
+ e = lookup(r.s, q1-q0);
+ if(!external && t.w!=nil && t.w.nopen[QWevent]>byte 0){
+ f = 0;
+ if(e >= 0)
+ f |= 1;
+ if(q0!=aq0 || q1!=aq1){
+ t.file.buf.read(aq0, r, 0, aq1-aq0);
+ f |= 2;
+ }
+ (aa, a) = getbytearg(argt, TRUE, TRUE);
+ if(a != nil){
+ if(len a > EVENTSIZE){ # too big; too bad
+ aa = a = nil;
+ warning(nil, "`argument string too long\n");
+ return;
+ }
+ f |= 8;
+ }
+ c = 'x';
+ if(t.what == Body)
+ c = 'X';
+ n = aq1-aq0;
+ if(n <= EVENTSIZE)
+ t.w.event(sprint("%c%d %d %d %d %s\n", c, aq0, aq1, f, n, r.s[0:n]));
+ else
+ t.w.event(sprint("%c%d %d %d 0 \n", c, aq0, aq1, f));
+ if(q0!=aq0 || q1!=aq1){
+ n = q1-q0;
+ t.file.buf.read(q0, r, 0, n);
+ if(n <= EVENTSIZE)
+ t.w.event(sprint("%c%d %d 0 %d %s\n", c, q0, q1, n, r.s[0:n]));
+ else
+ t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q1));
+ }
+ if(a != nil){
+ t.w.event(sprint("%c0 0 0 %d %s\n", c, len a, a));
+ if(aa != nil)
+ t.w.event(sprint("%c0 0 0 %d %s\n", c, len aa, aa));
+ else
+ t.w.event(sprint("%c0 0 0 0 \n", c));
+ }
+ strfree(r);
+ r = nil;
+ a = aa = nil;
+ return;
+ }
+ if(e >= 0){
+ if(exectab[e].mark && seltext!=nil)
+ if(seltext.what == Body){
+ seq++;
+ seltext.w.body.file.mark();
+ }
+ (s, n) = skipbl(r.s, q1-q0);
+ (s, n) = findbl(s, n);
+ (s, n) = skipbl(s, n);
+ runfun(exectab[e].fun, t, seltext, argt, exectab[e].flag1, exectab[e].flag2, s, n);
+ strfree(r);
+ r = nil;
+ return;
+ }
+
+ (dir, n) = dirname(t, nil, 0);
+ if(n==1 && dir[0]=='.'){ # sigh
+ dir = nil;
+ n = 0;
+ }
+ (aa, a) = getbytearg(argt, TRUE, TRUE);
+ if(t.w != nil)
+ t.w.refx.inc();
+ spawn run(t.w, r.s, dir, n, TRUE, aa, a, FALSE);
+}
+
+printarg(argt : ref Text, q0 : int, q1 : int) : string
+{
+ buf : string;
+
+ if(argt.what!=Body || argt.file.name==nil)
+ return nil;
+ if(q0 == q1)
+ buf = sprint("%s:#%d", argt.file.name, q0);
+ else
+ buf = sprint("%s:#%d,#%d", argt.file.name, q0, q1);
+ return buf;
+}
+
+getarg(argt : ref Text, doaddr : int, dofile : int) : (string, string, int)
+{
+ r : ref Astring;
+ n : int;
+ e : Dat->Expand;
+ a : string;
+ ok : int;
+
+ if(argt == nil)
+ return (nil, nil, 0);
+ a = nil;
+ argt.commit(TRUE);
+ (ok, e) = lookx->expand(argt, argt.q0, argt.q1);
+ if (ok) {
+ e.bname = nil;
+ if(len e.name && dofile){
+ if(doaddr)
+ a = printarg(argt, e.q0, e.q1);
+ return (a, e.name, len e.name);
+ }
+ e.name = nil;
+ }else{
+ e.q0 = argt.q0;
+ e.q1 = argt.q1;
+ }
+ n = e.q1 - e.q0;
+ r = stralloc(n);
+ argt.file.buf.read(e.q0, r, 0, n);
+ if(doaddr)
+ a = printarg(argt, e.q0, e.q1);
+ return(a, r.s, n);
+}
+
+getbytearg(argt : ref Text, doaddr : int, dofile : int) : (string, string)
+{
+ r : string;
+ n : int;
+ aa : string;
+
+ (aa, r, n) = getarg(argt, doaddr, dofile);
+ if(r == nil)
+ return (nil, nil);
+ return (aa, r);
+}
+
+newcol(et : ref Text)
+{
+ c : ref Column;
+
+ c = et.row.add(nil, -1);
+ if(c != nil)
+ c.add(nil, nil, -1).settag();
+}
+
+delcol(et : ref Text)
+{
+ c := et.col;
+ if(c==nil || !c.clean(FALSE))
+ return;
+ for(i:=0; i<c.nw; i++){
+ w := c.w[i];
+ if(int w.nopen[QWevent]+int w.nopen[QWaddr]+int w.nopen[QWdata] > 0){
+ warning(nil, sys->sprint("can't delete column; %s is running an external command\n", w.body.file.name));
+ return;
+ }
+ }
+ c.row.close(c, TRUE);
+}
+
+del(et : ref Text, flag1 : int)
+{
+ if(et.col==nil || et.w == nil)
+ return;
+ if(flag1 || et.w.body.file.ntext>1 || et.w.clean(FALSE, FALSE))
+ et.col.close(et.w, TRUE);
+}
+
+sort(et : ref Text)
+{
+ if(et.col != nil)
+ et.col.sort();
+}
+
+seqof(w: ref Window, isundo: int): int
+{
+ # if it's undo, see who changed with us
+ if(isundo)
+ return w.body.file.seq;
+ # if it's redo, see who we'll be sync'ed up with
+ return w.body.file.redoseq();
+}
+
+undo(et : ref Text, flag1 : int)
+{
+ i, j: int;
+ c: ref Column;
+ w: ref Window;
+ seq: int;
+
+ if(et==nil || et.w== nil)
+ return;
+ seq = seqof(et.w, flag1);
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c.nw; j++){
+ w = c.w[j];
+ if(seqof(w, flag1) == seq)
+ w.undo(flag1);
+ }
+ }
+ # et.w.undo(flag1);
+}
+
+getname(t : ref Text, argt : ref Text, arg : string, narg : int, isput : int) : string
+{
+ r, dir : string;
+ i, n, ndir, promote : int;
+
+ (nil, r, n) = getarg(argt, FALSE, TRUE);
+ promote = FALSE;
+ if(r == nil)
+ promote = TRUE;
+ else if(isput){
+ # if are doing a Put, want to synthesize name even for non-existent file
+ # best guess is that file name doesn't contain a slash
+ promote = TRUE;
+ for(i=0; i<n; i++)
+ if(r[i] == '/'){
+ promote = FALSE;
+ break;
+ }
+ if(promote){
+ t = argt;
+ arg = r;
+ narg = n;
+ }
+ }
+ if(promote){
+ n = narg;
+ if(n <= 0)
+ return t.file.name;
+ # prefix with directory name if necessary
+ dir = nil;
+ ndir = 0;
+ if(n>0 && arg[0]!='/'){
+ (dir, ndir) = dirname(t, nil, 0);
+ if(n==1 && dir[0]=='.'){ # sigh
+ dir = nil;
+ ndir = 0;
+ }
+ }
+ if(dir != nil){
+ r = dir[0:ndir] + arg[0:n];
+ dir = nil;
+ n += ndir;
+ }else
+ r = arg[0:n];
+ }
+ return r;
+}
+
+zerox(et : ref Text, t : ref Text)
+{
+ nw : ref Window;
+ c, locked : int;
+
+ locked = FALSE;
+ if(t!=nil && t.w!=nil && t.w!=et.w){
+ locked = TRUE;
+ c = 'M';
+ if(et.w != nil)
+ c = et.w.owner;
+ t.w.lock(c);
+ }
+ if(t == nil)
+ t = et;
+ if(t==nil || t.w==nil)
+ return;
+ t = t.w.body;
+ if(t.w.isdir)
+ warning(nil, sprint("%s is a directory; Zerox illegal\n", t.file.name));
+ else{
+ nw = t.w.col.add(nil, t.w, -1);
+ # ugly: fix locks so w.unlock works
+ nw.lock1(t.w.owner);
+ }
+ if(locked)
+ t.w.unlock();
+}
+
+get(et : ref Text, t : ref Text, argt : ref Text, flag1 : int, arg : string, narg : int)
+{
+ name : string;
+ r : string;
+ i, n, dirty : int;
+ w : ref Window;
+ u : ref Text;
+ d : Dir;
+ ok : int;
+
+ if(flag1)
+ if(et==nil || et.w==nil)
+ return;
+ if(!et.w.isdir && (et.w.body.file.buf.nc>0 && !et.w.clean(TRUE, FALSE)))
+ return;
+ w = et.w;
+ t = w.body;
+ name = getname(t, argt, arg, narg, FALSE);
+ if(name == nil){
+ warning(nil, "no file name\n");
+ return;
+ }
+ if(t.file.ntext>1){
+ (ok, d) = sys->stat(name);
+ if (ok == 0 && d.qid.qtype & Sys->QTDIR) {
+ warning(nil, sprint("%s is a directory; can't read with multiple windows on it\n", name));
+ return;
+ }
+ }
+ r = name;
+ n = len name;
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ # second and subsequent calls with zero an already empty buffer, but OK
+ u.reset();
+ u.w.dirfree();
+ }
+ samename := r[0:n] == t.file.name;
+ t.loadx(0, name, samename);
+ if(samename){
+ t.file.mod = FALSE;
+ dirty = FALSE;
+ }else{
+ t.file.mod = TRUE;
+ dirty = TRUE;
+ }
+ for(i=0; i<t.file.ntext; i++)
+ t.file.text[i].w.dirty = dirty;
+ name = nil;
+ r = nil;
+ w.settag();
+ t.file.unread = FALSE;
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ u.w.tag.setselect(u.w.tag.file.buf.nc, u.w.tag.file.buf.nc);
+ scrl->scrdraw(u);
+ }
+}
+
+putfile(f: ref File, q0: int, q1: int, name: string)
+{
+ n : int;
+ r, s : ref Astring;
+ w : ref Window;
+ i, q : int;
+ fd : ref Sys->FD;
+ d : Dir;
+ ok : int;
+
+ w = f.curtext.w;
+
+ {
+ if(name == f.name){
+ (ok, d) = sys->stat(name);
+ if(ok >= 0 && (f.dev!=d.dev || f.qidpath!=d.qid.path || f.mtime<d.mtime)){
+ f.dev = d.dev;
+ f.qidpath = d.qid.path;
+ f.mtime = d.mtime;
+ if(f.unread)
+ warning(nil, sys->sprint("%s not written; file already exists\n", name));
+ else
+ warning(nil, sys->sprint("%s modified since last read\n", name));
+ raise "e";
+ }
+ }
+ fd = sys->create(name, OWRITE, 8r664); # was 666
+ if(fd == nil){
+ warning(nil, sprint("can't create file %s: %r\n", name));
+ raise "e";
+ }
+ r = stralloc(BUFSIZE);
+ s = stralloc(BUFSIZE);
+
+ {
+ (ok, d) = sys->fstat(fd);
+ if(ok>=0 && (d.mode&Sys->DMAPPEND) && d.length>big 0){
+ warning(nil, sprint("%s not written; file is append only\n", name));
+ raise "e";
+ }
+ for(q = q0; q < q1; q += n){
+ n = q1 - q;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ f.buf.read(q, r, 0, n);
+ ab := array of byte r.s[0:n];
+ if(sys->write(fd, ab, len ab) != len ab){
+ ab = nil;
+ warning(nil, sprint("can't write file %s: %r\n", name));
+ raise "e";
+ }
+ ab = nil;
+ }
+ if(name == f.name){
+ d0 : Dir;
+
+ if(q0 != 0 || q1 != f.buf.nc){
+ f.mod = TRUE;
+ w.dirty = TRUE;
+ f.unread = TRUE;
+ }
+ else{
+ (ok, d0) = sys->fstat(fd); # use old values if we failed
+ if (ok >= 0)
+ d = d0;
+ f.qidpath = d.qid.path;
+ f.dev = d.dev;
+ f.mtime = d.mtime;
+ f.mod = FALSE;
+ w.dirty = FALSE;
+ f.unread = FALSE;
+ }
+ for(i=0; i<f.ntext; i++){
+ f.text[i].w.putseq = f.seq;
+ f.text[i].w.dirty = w.dirty;
+ }
+ }
+ strfree(s);
+ strfree(r);
+ s = r = nil;
+ name = nil;
+ fd = nil;
+ w.settag();
+ }
+ exception{
+ * =>
+ strfree(s);
+ strfree(r);
+ s = r = nil;
+ fd = nil;
+ raise "e";
+ }
+ }
+ exception{
+ * =>
+ name = nil;
+ return;
+ }
+}
+
+put(et : ref Text, argt : ref Text, arg : string, narg : int)
+{
+ namer : string;
+ name : string;
+ w : ref Window;
+
+ if(et==nil || et.w==nil || et.w.isdir)
+ return;
+ w = et.w;
+ f := w.body.file;
+
+ name = getname(w.body, argt, arg, narg, TRUE);
+ if(name == nil){
+ warning(nil, "no file name\n");
+ return;
+ }
+ namer = name;
+ putfile(f, 0, f.buf.nc, namer);
+ name = nil;
+}
+
+dump(argt : ref Text, isdump : int, arg : string, narg : int)
+{
+ name : string;
+
+ if(narg)
+ name = arg;
+ else
+ (nil, name) = getbytearg(argt, FALSE, TRUE);
+ if(isdump)
+ row.dump(name);
+ else {
+ if (!row.qlock.locked())
+ error("row not locked in dump()");
+ row.loadx(name, FALSE);
+ }
+ name = nil;
+}
+
+cut(et : ref Text, t : ref Text, dosnarf : int, docut : int)
+{
+ q0, q1, n, locked, c : int;
+ r : ref Astring;
+
+ # use current window if snarfing and its selection is non-null
+ if(et!=t && dosnarf && et.w!=nil){
+ if(et.w.body.q1>et.w.body.q0){
+ t = et.w.body;
+ t.file.mark(); # seq has been incremented by execute
+ }
+ else if(et.w.tag.q1>et.w.tag.q0)
+ t = et.w.tag;
+ }
+ if(t == nil)
+ return;
+ locked = FALSE;
+ if(t.w!=nil && et.w!=t.w){
+ locked = TRUE;
+ c = 'M';
+ if(et.w != nil)
+ c = et.w.owner;
+ t.w.lock(c);
+ }
+ if(t.q0 == t.q1){
+ if(locked)
+ t.w.unlock();
+ return;
+ }
+ if(dosnarf){
+ q0 = t.q0;
+ q1 = t.q1;
+ snarfbuf.delete(0, snarfbuf.nc);
+ r = stralloc(BUFSIZE);
+ while(q0 < q1){
+ n = q1 - q0;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ t.file.buf.read(q0, r, 0, n);
+ snarfbuf.insert(snarfbuf.nc, r.s, n);
+ q0 += n;
+ }
+ strfree(r);
+ r = nil;
+ acme->putsnarf();
+ }
+ if(docut){
+ t.delete(t.q0, t.q1, TRUE);
+ t.setselect(t.q0, t.q0);
+ if(t.w != nil){
+ scrl->scrdraw(t);
+ t.w.settag();
+ }
+ }else if(dosnarf) # Snarf command
+ dat->argtext = t;
+ if(locked)
+ t.w.unlock();
+}
+
+paste(et : ref Text, t : ref Text, selectall : int, tobody: int)
+{
+ c : int;
+ q, q0, q1, n : int;
+ r : ref Astring;
+
+ # if(tobody), use body of executing window (Paste or Send command)
+ if(tobody && et!=nil && et.w!=nil){
+ t = et.w.body;
+ t.file.mark(); # seq has been incremented by execute
+ }
+ if(t == nil)
+ return;
+
+ acme->getsnarf();
+ if(t==nil || snarfbuf.nc==0)
+ return;
+ if(t.w!=nil && et.w!=t.w){
+ c = 'M';
+ if(et.w != nil)
+ c = et.w.owner;
+ t.w.lock(c);
+ }
+ cut(t, t, FALSE, TRUE);
+ q = 0;
+ q0 = t.q0;
+ q1 = t.q0+snarfbuf.nc;
+ r = stralloc(BUFSIZE);
+ while(q0 < q1){
+ n = q1 - q0;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ if(r == nil)
+ r = stralloc(n);
+ snarfbuf.read(q, r, 0, n);
+ t.insert(q0, r.s, n, TRUE, 0);
+ q += n;
+ q0 += n;
+ }
+ strfree(r);
+ r = nil;
+ if(selectall)
+ t.setselect(t.q0, q1);
+ else
+ t.setselect(q1, q1);
+ if(t.w != nil){
+ scrl->scrdraw(t);
+ t.w.settag();
+ }
+ if(t.w!=nil && et.w!=t.w)
+ t.w.unlock();
+}
+
+look(et : ref Text, t : ref Text, argt : ref Text)
+{
+ r : string;
+ s : ref Astring;
+ n : int;
+
+ if(et != nil && et.w != nil){
+ t = et.w.body;
+ (nil, r, n) = getarg(argt, FALSE, FALSE);
+ if(r == nil){
+ n = t.q1-t.q0;
+ s = stralloc(n);
+ t.file.buf.read(t.q0, s, 0, n);
+ r = s.s;
+ }
+ lookx->search(t, r, n);
+ r = nil;
+ }
+}
+
+send(et : ref Text, t : ref Text)
+{
+ if(et.w==nil)
+ return;
+ t = et.w.body;
+ if(t.q0 != t.q1)
+ cut(t, t, TRUE, FALSE);
+ t.setselect(t.file.buf.nc, t.file.buf.nc);
+ paste(t, t, TRUE, TRUE);
+ if(t.readc(t.file.buf.nc-1) != '\n'){
+ t.insert(t.file.buf.nc, "\n", 1, TRUE, 0);
+ t.setselect(t.file.buf.nc, t.file.buf.nc);
+ }
+}
+
+edit(et: ref Text, argt: ref Text, arg: string, narg: int)
+{
+ r: string;
+ leng: int;
+
+ if(et == nil)
+ return;
+ (nil, r, leng) = getarg(argt, FALSE, TRUE);
+ seq++;
+ if(r != nil){
+ editm->editcmd(et, r, leng);
+ r = nil;
+ }else
+ editm->editcmd(et, arg, narg);
+}
+
+exitx()
+{
+ if(row.clean(TRUE))
+ acme->acmeexit(nil);
+}
+
+putall()
+{
+ i, j, e : int;
+ w : ref Window;
+ c : ref Column;
+ a : string;
+
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c.nw; j++){
+ w = c.w[j];
+ if(w.isscratch || w.isdir || len w.body.file.name==0)
+ continue;
+ if(w.nopen[QWevent] > byte 0)
+ continue;
+ a = w.body.file.name;
+ e = utils->access(a);
+ if(w.body.file.mod || w.body.ncache)
+ if(e < 0)
+ warning(nil, sprint("no auto-Put of %s: %r\n", a));
+ else{
+ w.commit(w.body);
+ put(w.body, nil, nil, 0);
+ }
+ a = nil;
+ }
+ }
+}
+
+id(et : ref Text)
+{
+ if(et != nil && et.w != nil)
+ warning(nil, sprint("/mnt/acme/%d/\n", et.w.id));
+}
+
+limbo(et: ref Text)
+{
+ s := getname(et.w.body, nil, nil, 0, 0);
+ if(s == nil)
+ return;
+ for(l := len s; l > 0 && s[--l] != '/'; )
+ ;
+ if(s[l] == '/')
+ s = s[l+1: ];
+ s = "limbo -gw " + s;
+ (dir, n) := dirname(et, nil, 0);
+ if(n==1 && dir[0]=='.'){ # sigh
+ dir = nil;
+ n = 0;
+ }
+ spawn run(nil, s, dir, n, TRUE, nil, nil, FALSE);
+}
+
+local(et : ref Text, argt : ref Text, arg : string)
+{
+ a, aa : string;
+ dir : string;
+ n : int;
+
+ (aa, a) = getbytearg(argt, TRUE, TRUE);
+
+ (dir, n) = dirname(et, nil, 0);
+ if(n==1 && dir[0]=='.'){ # sigh
+ dir = nil;
+ n = 0;
+ }
+ spawn run(nil, arg, dir, n, FALSE, aa, a, FALSE);
+}
+
+kill(argt : ref Text, arg : string, narg : int)
+{
+ a, cmd, r : string;
+ na : int;
+
+ (nil, r, na) = getarg(argt, FALSE, FALSE);
+ if(r != nil)
+ kill(nil, r, na);
+ # loop condition: *arg is not a blank
+ for(;;){
+ (a, na) = findbl(arg, narg);
+ if(a == arg)
+ break;
+ cmd = arg[0:narg-na];
+ dat->ckill <-= cmd;
+ (arg, narg) = skipbl(a, na);
+ }
+}
+
+lineno(et : ref Text)
+{
+ n : int;
+
+ if (et == nil || et.w == nil || (et = et.w.body) == nil)
+ return;
+ q0 := et.q0;
+ q1 := et.q1;
+ if (q0 < 0 || q1 < 0 || q0 > q1)
+ return;
+ ln0 := 1;
+ ln1 := 1;
+ rp := stralloc(BUFSIZE);
+ nc := et.file.buf.nc;
+ if (q0 >= nc)
+ q0 = nc-1;
+ if (q1 >= nc)
+ q1 = nc-1;
+ for (q := 0; q < q1; ) {
+ if (q+BUFSIZE > nc)
+ n = nc-q;
+ else
+ n = BUFSIZE;
+ et.file.buf.read(q, rp, 0, n);
+ for (i := 0; i < n && q < q1; i++) {
+ if (rp.s[i] == '\n') {
+ if (q < q0)
+ ln0++;
+ if (q < q1-1)
+ ln1++;
+ }
+ q++;
+ }
+ }
+ rp = nil;
+ if (et.file.name != nil)
+ file := et.file.name + ":";
+ else
+ file = nil;
+ if (ln0 == ln1)
+ warning(nil, sprint("%s%d\n", file, ln0));
+ else
+ warning(nil, sprint("%s%d,%d\n", file, ln0, ln1));
+}
+
+fontx(et : ref Text, t : ref Text, argt : ref Text, arg : string, narg : int)
+{
+ a, r, flag, file : string;
+ na, nf : int;
+ aa : string;
+ newfont : ref Reffont;
+ dp : ref Dat->Dirlist;
+ i, fix : int;
+
+ if(et==nil || et.w==nil)
+ return;
+ t = et.w.body;
+ flag = nil;
+ file = nil;
+ # loop condition: *arg is not a blank
+ nf = 0;
+ for(;;){
+ (a, na) = findbl(arg, narg);
+ if(a == arg)
+ break;
+ r = arg[0:narg-na];
+ if(r == "fix" || r == "var"){
+ flag = nil;
+ flag = r;
+ }else{
+ file = r;
+ nf = narg-na;
+ }
+ (arg, narg) = skipbl(a, na);
+ }
+ (nil, r, na) = getarg(argt, FALSE, TRUE);
+ if(r != nil)
+ if(r == "fix" || r == "var"){
+ flag = nil;
+ flag = r;
+ }else{
+ file = r;
+ nf = na;
+ }
+ fix = 1;
+ if(flag != nil)
+ fix = flag == "fix";
+ else if(file == nil){
+ newfont = Reffont.get(FALSE, FALSE, FALSE, nil);
+ if(newfont != nil)
+ fix = newfont.f.name == t.frame.font.name;
+ }
+ if(file != nil){
+ aa = file[0:nf];
+ newfont = Reffont.get(fix, flag!=nil, FALSE, aa);
+ aa = nil;
+ }else
+ newfont = Reffont.get(fix, FALSE, FALSE, nil);
+ if(newfont != nil){
+ graph->draw(gui->mainwin, t.w.r, acme->textcols[Framem->BACK], nil, (0, 0));
+ t.reffont.close();
+ t.reffont = newfont;
+ t.frame.font = newfont.f;
+ if(t.w.isdir){
+ t.all.min.x++; # force recolumnation; disgusting!
+ for(i=0; i<t.w.ndl; i++){
+ dp = t.w.dlp[i];
+ aa = dp.r;
+ dp.wid = graph->strwidth(newfont.f, aa);
+ aa = nil;
+ }
+ }
+ # avoid shrinking of window due to quantization
+ t.w.col.grow(t.w, -1, 1);
+ }
+ file = nil;
+ flag = nil;
+}
+
+incl(et : ref Text, argt : ref Text, arg : string, narg : int)
+{
+ a, r : string;
+ w : ref Window;
+ na, n, leng : int;
+
+ if(et==nil || et.w==nil)
+ return;
+ w = et.w;
+ n = 0;
+ (nil, r, leng) = getarg(argt, FALSE, TRUE);
+ if(r != nil){
+ n++;
+ w.addincl(r, leng);
+ }
+ # loop condition: *arg is not a blank
+ for(;;){
+ (a, na) = findbl(arg, narg);
+ if(a == arg)
+ break;
+ r = arg[0:narg-na];
+ n++;
+ w.addincl(r, narg-na);
+ (arg, narg) = skipbl(a, na);
+ }
+ if(n==0 && w.nincl){
+ for(n=w.nincl; --n>=0; )
+ warning(nil, sprint("%s ", w.incl[n]));
+ warning(nil, "\n");
+ }
+}
+
+tab(et : ref Text, argt : ref Text, arg : string, narg : int)
+{
+ a, r, p : string;
+ w : ref Window;
+ na, leng, tab : int;
+
+ if(et==nil || et.w==nil)
+ return;
+ w = et.w;
+ (nil, r, leng) = getarg(argt, FALSE, TRUE);
+ tab = 0;
+ if(r!=nil && leng>0){
+ p = r[0:leng];
+ if('0'<=p[0] && p[0]<='9')
+ tab = int p;
+ p = nil;
+ }else{
+ (a, na) = findbl(arg, narg);
+ if(a != arg){
+ p = arg[0:narg-na];
+ if('0'<=p[0] && p[0]<='9')
+ tab = int p;
+ p = nil;
+ }
+ }
+ if(tab > 0){
+ if(w.body.tabstop != tab){
+ w.body.tabstop = tab;
+ w.reshape(w.r, 1);
+ }
+ }else
+ warning(nil, sys->sprint("%s: Tab %d\n", w.body.file.name, w.body.tabstop));
+}
+
+alphabet(et: ref Text, argt: ref Text, arg: string, narg: int)
+{
+ r: string;
+ leng: int;
+
+ if(et == nil)
+ return;
+ (nil, r, leng) = getarg(argt, FALSE, FALSE);
+ if(r != nil)
+ setalphabet(r[0:leng]);
+ else
+ setalphabet(arg[0:narg]);
+}
+
+runfeed(p : array of ref Sys->FD, c : chan of int)
+{
+ n : int;
+ buf : array of byte;
+ s : string;
+
+ sys->pctl(Sys->FORKFD, nil);
+ c <-= 1;
+ # p[1] = nil;
+ buf = array[256] of byte;
+ for(;;){
+ if((n = sys->read(p[0], buf, 256)) <= 0)
+ break;
+ s = string buf[0:n];
+ dat->cerr <-= s;
+ s = nil;
+ }
+ buf = nil;
+ exit;
+}
+
+run(win : ref Window, s : string, rdir : string, ndir : int, newns : int, argaddr : string, arg : string, iseditcmd: int)
+{
+ c : ref Dat->Command;
+ name, dir : string;
+ e, t : int;
+ av : list of string;
+ r : int;
+ incl : array of string;
+ inarg, i, nincl : int;
+ tfd : ref Sys->FD;
+ p : array of ref Sys->FD;
+ pc : chan of int;
+ winid : int;
+
+ c = ref Dat->Command;
+ t = 0;
+ while(t < len s && (s[t]==' ' || s[t]=='\n' || s[t]=='\t'))
+ t++;
+ for(e=t; e < len s; e++)
+ if(s[e]==' ' || s[e]=='\n' || s[e]=='\t' )
+ break;
+ name = s[t:e];
+ e = utils->strrchr(name, '/');
+ if(e >= 0)
+ name = name[e+1:];
+ name += " "; # add blank here for ease in waittask
+ c.name = name;
+ name = nil;
+ pipechar := 0;
+ if (t < len s && (s[t] == '<' || s[t] == '|' || s[t] == '>')){
+ pipechar = s[t++];
+ s = s[t:];
+ }
+ c.pid = sys->pctl(0, nil);
+ c.iseditcmd = iseditcmd;
+ c.text = s;
+ dat->ccommand <-= c;
+ #
+ # must pctl() after communication because rendezvous name
+ # space is part of RFNAMEG.
+ #
+
+ if(newns){
+ wids : string = "";
+ filename: string;
+
+ if(win != nil){
+ filename = win.body.file.name;
+ wids = string win.id;
+ nincl = win.nincl;
+ incl = array[nincl] of string;
+ for(i=0; i<nincl; i++)
+ incl[i] = win.incl[i];
+ winid = win.id;
+ win.close();
+ }else{
+ winid = 0;
+ nincl = 0;
+ incl = nil;
+ if(dat->activewin != nil)
+ winid = (dat->activewin).id;
+ }
+ # sys->pctl(Sys->FORKNS|Sys->FORKFD|Sys->NEWPGRP, nil);
+ sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->FORKENV|Sys->NEWPGRP, 0::1::2::fsys->fsyscfd()::nil);
+ if(rdir != nil){
+ dir = rdir[0:ndir];
+ sys->chdir(dir); # ignore error: probably app. window
+ dir = nil;
+ }
+ if(filename != nil)
+ utils->setenv("%", filename);
+ c.md = fsys->fsysmount(rdir, ndir, incl, nincl);
+ if(c.md == nil){
+ # error("child: can't mount /mnt/acme");
+ warning(nil, "can't mount /mnt/acme");
+ exit;
+ }
+ if(winid > 0 && (pipechar=='|' || pipechar=='>')){
+ buf := sys->sprint("/mnt/acme/%d/rdsel", winid);
+ tfd = sys->open(buf, OREAD);
+ }
+ else
+ tfd = sys->open("/dev/null", OREAD);
+ sys->dup(tfd.fd, 0);
+ tfd = nil;
+ if((winid > 0 || iseditcmd) && (pipechar=='|' || pipechar=='<')){
+ buf: string;
+
+ if(iseditcmd){
+ if(winid > 0)
+ buf = sprint("/mnt/acme/%d/editout", winid);
+ else
+ buf = sprint("/mnt/acme/editout");
+ }
+ else
+ buf = sys->sprint("/mnt/acme/%d/wrsel", winid);
+ tfd = sys->open(buf, OWRITE);
+ }
+ else
+ tfd = sys->open("/dev/cons", OWRITE);
+ sys->dup(tfd.fd, 1);
+ tfd = nil;
+ if(winid > 0 && (pipechar=='|' || pipechar=='<')){
+ tfd = sys->open("/dev/cons", OWRITE);
+ sys->dup(tfd.fd, 2);
+ }
+ else
+ sys->dup(1, 2);
+ tfd = nil;
+ utils->setenv("acmewin", wids);
+ }else{
+ if(win != nil)
+ win.close();
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ if(rdir != nil){
+ dir = rdir[0:ndir];
+ sys->chdir(dir); # ignore error: probably app. window
+ dir = nil;
+ }
+ p = array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0){
+ error("child: can't pipe");
+ exit;
+ }
+ pc = chan of int;
+ spawn runfeed(p, pc);
+ <-pc;
+ pc = nil;
+ fsys->fsysclose();
+ tfd = sys->open("/dev/null", OREAD);
+ sys->dup(tfd.fd, 0);
+ tfd = nil;
+ sys->dup(p[1].fd, 1);
+ sys->dup(1, 2);
+ p[0] = p[1] = nil;
+ }
+
+ if(argaddr != nil)
+ utils->setenv("acmeaddr", argaddr);
+ hard := 0;
+ if(len s > 512-10) # may need to print into stack
+ hard = 1;
+ else {
+ inarg = FALSE;
+ for(e=0; e < len s; e++){
+ r = s[e];
+ if(r==' ' || r=='\t')
+ continue;
+ if(r < ' ') {
+ hard = 1;
+ break;
+ }
+ if(utils->strchr("#;&|^$=`'{}()<>[]*?^~`", r) >= 0) {
+ hard = 1;
+ break;
+ }
+ inarg = TRUE;
+ }
+ if (!hard) {
+ if(!inarg)
+ exit;
+ av = nil;
+ sa := -1;
+ for(e=0; e < len s; e++){
+ r = s[e];
+ if(r==' ' || r=='\t'){
+ if (sa >= 0) {
+ av = s[sa:e] :: av;
+ sa = -1;
+ }
+ continue;
+ }
+ if (sa < 0)
+ sa = e;
+ }
+ if (sa >= 0)
+ av = s[sa:e] :: av;
+ if (arg != nil)
+ av = arg :: av;
+ av = utils->reverse(av);
+ c.av = av;
+ exec(hd av, av);
+ dat->cwait <-= string c.pid + " \"Exec\":";
+ exit;
+ }
+ }
+
+ if(arg != nil){
+ s = sprint("%s '%s'", s, arg); # BUG: what if quote in arg?
+ c.text = s;
+ }
+ av = nil;
+ av = s :: av;
+ av = "-c" :: av;
+ av = "/dis/sh" :: av;
+ exec(hd av, av);
+ dat->cwait <-= string c.pid + " \"Exec\":";
+ exit;
+}
+
+# Nasty bug causes
+# Edit ,|nonexistentcommand
+# (or ,> or ,<) to lock up acme. Easy fix. Add these two lines
+# to the failure case of runwaittask():
+#
+# /sys/src/cmd/acme/exec.c:1287 a exec.c:1288,1289
+# else{
+# if(c->iseditcmd)
+# sendul(cedit, 0);
+# free(c->name);
+# free(c->text);
+# free(c);
+# }
+
+
--- /dev/null
+++ b/appl/acme/exec.m
@@ -1,0 +1,19 @@
+Exec : module {
+ PATH : con "/dis/acme/exec.dis";
+
+ snarfbuf : ref Bufferm->Buffer;
+
+ init : fn(mods : ref Dat->Mods);
+
+ fontx : fn(et : ref Textm->Text, t : ref Textm->Text, argt : ref Textm->Text, arg : string, narg : int);
+ get : fn(et, t, argt : ref Textm->Text, flag1 : int, arg : string, narg : int);
+ put : fn(et, argt : ref Textm->Text, arg : string, narg : int);
+ cut : fn(et, t : ref Textm->Text, flag1, flag2 : int);
+ paste : fn(et, t : ref Textm->Text, flag1 : int, flag2: int);
+
+ getarg : fn(t : ref Textm->Text, m : int, n : int) : (string, string, int);
+ execute : fn(t : ref Textm->Text, aq0, aq1, external : int, argt : ref Textm->Text);
+ run : fn(w : ref Windowm->Window, s : string, rdir : string, ndir : int, newns : int, argaddr : string, arg : string, ise: int);
+ undo: fn(t: ref Textm->Text, flag: int);
+ putfile: fn(f: ref Filem->File, q0: int, q1: int, r: string);
+};
--- /dev/null
+++ b/appl/acme/file.b
@@ -1,0 +1,331 @@
+implement Filem;
+
+include "common.m";
+
+sys : Sys;
+dat : Dat;
+utils : Utils;
+buffm : Bufferm;
+textm : Textm;
+editlog: Editlog;
+
+FALSE, TRUE, XXX, Delete, Insert, Filename, BUFSIZE, Astring : import Dat;
+Buffer, newbuffer : import buffm;
+Text : import textm;
+error : import utils;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ buffm = mods.bufferm;
+ textm = mods.textm;
+ editlog = mods.editlog;
+}
+
+#
+# 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.
+#
+
+Undo : adt
+{
+ typex : int; # Delete, Insert, Filename
+ mod : int; # modify bit
+ seq : int; # sequence number
+ p0 : int; # location of change (unused in f)
+ n : int; # # runes in string or file name
+};
+
+Undosize : con 8;
+SHM : con 16rffff;
+
+undotostr(t, m, s, p, n : int) : string
+{
+ a := "01234567";
+ a[0] = t;
+ a[1] = m;
+ a[2] = s&SHM;
+ a[3] = (s>>16)&SHM;
+ a[4] = p&SHM;
+ a[5] = (p>>16)&SHM;
+ a[6] = n&SHM;
+ a[7] = (n>>16)&SHM;
+ return a;
+}
+
+strtoundo(s: string): Undo
+{
+ u: Undo;
+
+ u.typex = s[0];
+ u.mod = s[1];
+ u.seq = s[2]|(s[3]<<16);
+ u.p0 = s[4]|(s[5]<<16);
+ u.n = s[6]|(s[7]<<16);
+ return u;
+}
+
+nullfile : File;
+
+File.addtext(f : self ref File, t : ref Text) : ref File
+{
+ if(f == nil) {
+ f = ref nullfile;
+ f.buf = newbuffer();
+ f.delta = newbuffer();
+ f.epsilon = newbuffer();
+ f.ntext = 0;
+ f.unread = TRUE;
+ }
+ oft := f.text;
+ f.text = array[f.ntext+1] of ref Text;
+ f.text[0:] = oft[0:f.ntext];
+ oft = nil;
+ f.text[f.ntext++] = t;
+ f.curtext = t;
+ return f;
+}
+
+File.deltext(f : self ref File, t : ref Text)
+{
+ i : int;
+
+ for(i=0; i<f.ntext; i++)
+ if(f.text[i] == t)
+ break;
+ if (i == f.ntext)
+ error("can't find text in File.deltext");
+
+ f.ntext--;
+ if(f.ntext == 0){
+ f.close();
+ return;
+ }
+ f.text[i:] = f.text[i+1:f.ntext+1];
+ if(f.curtext == t)
+ f.curtext = f.text[0];
+}
+
+File.insert(f : self ref File, p0 : int, s : string, ns : int)
+{
+ if (p0 > f.buf.nc)
+ error("bad assert in File.insert");
+ if(f.seq > 0)
+ f.uninsert(f.delta, p0, ns);
+ f.buf.insert(p0, s, ns);
+ if(ns)
+ f.mod = TRUE;
+}
+
+File.uninsert(f : self ref File, delta : ref Buffer, p0 : int, ns : int)
+{
+ # undo an insertion by deleting
+ a := undotostr(Delete, f.mod, f.seq, p0, ns);
+ delta.insert(delta.nc, a, Undosize);
+}
+
+File.delete(f : self ref File, p0 : int, p1 : int)
+{
+ if (p0>p1 || p0>f.buf.nc || p1>f.buf.nc)
+ error("bad assert in File.delete");
+ if(f.seq > 0)
+ f.undelete(f.delta, p0, p1);
+ f.buf.delete(p0, p1);
+ if(p1 > p0)
+ f.mod = TRUE;
+}
+
+File.undelete(f : self ref File, delta : ref Buffer, p0 : int, p1 : int)
+{
+ buf : ref Astring;
+ i, n : int;
+
+ # undo a deletion by inserting
+ a := undotostr(Insert, f.mod, f.seq, p0, p1-p0);
+ m := p1-p0;
+ if(m > BUFSIZE)
+ m = BUFSIZE;
+ buf = utils->stralloc(m);
+ for(i=p0; i<p1; i+=n){
+ n = p1 - i;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ f.buf.read(i, buf, 0, n);
+ delta.insert(delta.nc, buf.s, n);
+ }
+ utils->strfree(buf);
+ buf = nil;
+ delta.insert(delta.nc, a, Undosize);
+}
+
+File.setname(f : self ref File, name : string, n : int)
+{
+ if(f.seq > 0)
+ f.unsetname(f.delta);
+ f.name = name[0:n];
+ f.unread = TRUE;
+}
+
+File.unsetname(f : self ref File, delta : ref Buffer)
+{
+ # undo a file name change by restoring old name
+ a := undotostr(Filename, f.mod, f.seq, 0, len f.name);
+ if(f.name != nil)
+ delta.insert(delta.nc, f.name, len f.name);
+ delta.insert(delta.nc, a, Undosize);
+}
+
+File.loadx(f : self ref File, p0 : int, fd : ref Sys->FD) : int
+{
+ if(f.seq > 0)
+ error("undo in file.load unimplemented");
+ return f.buf.loadx(p0, fd);
+}
+
+File.undo(f : self ref File, isundo : int, q0 : int, q1 : int) : (int, int)
+{
+ buf : ref Astring;
+ i, j, n, up : int;
+ stop : int;
+ delta, epsilon : ref Buffer;
+ u : Undo;
+
+ a := utils->stralloc(Undosize);
+ 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
+ }
+
+ buf = utils->stralloc(BUFSIZE);
+ while(delta.nc > 0){
+ up = delta.nc-Undosize;
+ delta.read(up, a, 0, Undosize);
+ u = strtoundo(a.s);
+ if(isundo){
+ if(u.seq < stop){
+ f.seq = u.seq;
+ utils->strfree(buf);
+ utils->strfree(a);
+ return (q0, q1);
+ }
+ }else{
+ if(stop == 0)
+ stop = u.seq;
+ if(u.seq > stop){
+ utils->strfree(buf);
+ utils->strfree(a);
+ return (q0, q1);
+ }
+ }
+ case(u.typex){
+ Delete =>
+ f.seq = u.seq;
+ f.undelete(epsilon, u.p0, u.p0+u.n);
+ f.mod = u.mod;
+ f.buf.delete(u.p0, u.p0+u.n);
+ for(j=0; j<f.ntext; j++)
+ f.text[j].delete(u.p0, u.p0+u.n, FALSE);
+ q0 = u.p0;
+ q1 = u.p0;
+ Insert =>
+ f.seq = u.seq;
+ f.uninsert(epsilon, u.p0, u.n);
+ f.mod = u.mod;
+ up -= u.n;
+ # buf = utils->stralloc(BUFSIZE);
+ for(i=0; i<u.n; i+=n){
+ n = u.n - i;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ delta.read(up+i, buf, 0, n);
+ f.buf.insert(u.p0+i, buf.s, n);
+ for(j=0; j<f.ntext; j++)
+ f.text[j].insert(u.p0+i, buf.s, n, FALSE, 0);
+ }
+ # utils->strfree(buf);
+ # buf = nil;
+ q0 = u.p0;
+ q1 = u.p0+u.n;
+ Filename =>
+ f.seq = u.seq;
+ f.unsetname(epsilon);
+ f.mod = u.mod;
+ up -= u.n;
+ f.name = nil;
+ if(u.n == 0)
+ f.name = nil;
+ else {
+ fn0 := utils->stralloc(u.n);
+ delta.read(up, fn0, 0, u.n);
+ f.name = fn0.s;
+ utils->strfree(fn0);
+ fn0 = nil;
+ }
+ * =>
+ error(sys->sprint("undo: 0x%ux", u.typex));
+ error("");
+ }
+ delta.delete(up, delta.nc);
+ }
+ utils->strfree(buf);
+ utils->strfree(a);
+ buf = nil;
+ if(isundo)
+ f.seq = 0;
+ return (q0, q1);
+}
+
+File.reset(f : self ref File)
+{
+ f.delta.reset();
+ f.epsilon.reset();
+ f.seq = 0;
+}
+
+File.close(f : self ref File)
+{
+ f.name = nil;
+ f.ntext = 0;
+ f.text = nil;
+ f.buf.close();
+ f.delta.close();
+ f.epsilon.close();
+ editlog->elogclose(f);
+ f = nil;
+}
+
+File.mark(f : self ref File)
+{
+ if(f.epsilon.nc)
+ f.epsilon.delete(0, f.epsilon.nc);
+ f.seq = dat->seq;
+}
+
+File.redoseq(f : self ref File): int
+{
+ u: Undo;
+ delta: ref Buffer;
+
+ delta = f.epsilon;
+ if(delta.nc == 0)
+ return ~0;
+ buf := utils->stralloc(Undosize);
+ delta.read(delta.nc-Undosize, buf, 0, Undosize);
+ u = strtoundo(buf.s);
+ utils->strfree(buf);
+ return u.seq;
+}
--- /dev/null
+++ b/appl/acme/file.m
@@ -1,0 +1,40 @@
+Filem : module {
+ PATH : con "/dis/acme/file.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ File : adt {
+ buf : ref Bufferm->Buffer; # the data
+ delta : ref Bufferm->Buffer; # transcript of changes
+ epsilon : ref Bufferm->Buffer; # inversion of delta for redo
+ elogbuf: ref Bufferm->Buffer; # log of pending editor changes
+ elog: Editlog->Elog; # current pending change
+ name : string; # name of associated file
+ qidpath : big; # of file when read
+ mtime : int; # of file when read
+ dev : int; # of file when read
+ unread : int; # file has not been read from disk
+ editclean: int; # mark clean after edit command
+ seq : int; # if seq==0, File acts like Buffer
+ mod : int;
+ curtext : cyclic ref Textm->Text; # most recently used associated text
+ text : cyclic array of ref Textm->Text; # list of associated texts
+ ntext : int;
+ dumpid : int; # used in dumping zeroxed windows
+
+ addtext : fn(f : self ref File, t : ref Textm->Text) : ref File;
+ deltext : fn(f : self ref File, t : ref Textm->Text);
+ insert : fn(f : self ref File, n : int, s : string, m : int);
+ delete : fn(f : self ref File, m : int, n : int);
+ loadx : fn(f : self ref File, p : int, fd : ref Sys->FD) : int;
+ setname : fn(f : self ref File, s : string, n : int);
+ undo : fn(f : self ref File, p : int, q : int, r : int) : (int, int);
+ mark : fn(f : self ref File);
+ reset : fn(f : self ref File);
+ close : fn(f : self ref File);
+ undelete : fn(f : self ref File, b : ref Bufferm->Buffer, m : int, n : int);
+ uninsert : fn(f : self ref File, b : ref Bufferm->Buffer, m : int, n : int);
+ unsetname : fn(f : self ref File, b : ref Bufferm->Buffer);
+ redoseq : fn(f: self ref File): int;
+ };
+};
--- /dev/null
+++ b/appl/acme/frame.b
@@ -1,0 +1,1189 @@
+implement Framem;
+
+include "common.m";
+
+sys : Sys;
+drawm : Draw;
+acme : Acme;
+gui : Gui;
+graph : Graph;
+utils : Utils;
+textm : Textm;
+
+sprint : import sys;
+Point, Rect, Font, Image, Pointer : import drawm;
+draw, berror, charwidth, strwidth : import graph;
+black, white : import gui;
+
+SLOP : con 25;
+
+noglyphs := array[4] of { 16rFFFD, 16r80, '?', ' ' };
+
+frame : ref Frame;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ drawm = mods.draw;
+ acme = mods.acme;
+ gui = mods.gui;
+ graph = mods.graph;
+ utils = mods.utils;
+ textm = mods.textm;
+
+ frame = newframe();
+}
+
+nullframe : Frame;
+
+newframe() : ref Frame
+{
+ f := ref nullframe;
+ f.cols = array[NCOL] of ref Draw->Image;
+ return f;
+}
+
+frdump(f : ref Frame)
+{
+ utils->debug(sprint("nchars=%d\n", f.nchars));
+ for (i := 0; i < f.nbox; i++) {
+ utils->debug(sprint("box %d : ", i));
+ fb := f.box[i];
+ if (fb.nrune >= 0)
+ utils->debug(sprint("%d %d %s\n", fb.nrune, len fb.ptr, fb.ptr));
+ else
+ utils->debug(sprint("%d\n", fb.nrune));
+ }
+}
+
+# debugcheck(f : ref Frame, n : int)
+# {
+# if (f.nchars != xfrstrlen(f, 0)) {
+# utils->debug(sprint("%d : bad frame nchars\n", n));
+# frdump(f);
+# berror("");
+# }
+# }
+
+xfraddbox(f : ref Frame, bn : int, n : int) # add n boxes after bn, shift the rest up,
+ # * box[bn+n]==box[bn]
+{
+ i : int;
+
+ if(bn > f.nbox)
+ berror("xfraddbox");
+ # bn = f.nbox has same effect as bn = f.nbox-1
+ if(f.nbox+n > f.nalloc)
+ xfrgrowbox(f, n+SLOP);
+ for (i=f.nbox; --i > bn; ) {
+ t := f.box[i+n];
+ f.box[i+n] = f.box[i];
+ f.box[i] = t;
+ }
+ if (bn < f.nbox)
+ *f.box[bn+n] = *f.box[bn];
+ f.nbox+=n;
+}
+
+xfrclosebox(f : ref Frame, n0 : int, n1 : int) # inclusive
+{
+ i: int;
+
+ if(n0>=f.nbox || n1>=f.nbox || n1<n0)
+ berror("xfrclosebox");
+ n1++;
+ for(i=n1; i<f.nbox; i++) {
+ t := f.box[i-(n1-n0)];
+ f.box[i-(n1-n0)] = f.box[i];
+ f.box[i] = t;
+ }
+ f.nbox -= n1-n0;
+}
+
+xfrdelbox(f : ref Frame, n0 : int, n1 : int) # inclusive
+{
+ if(n0>=f.nbox || n1>=f.nbox || n1<n0)
+ berror("xfrdelbox");
+ xfrfreebox(f, n0, n1);
+ xfrclosebox(f, n0, n1);
+}
+
+xfrfreebox(f : ref Frame, n0 : int, n1 : int) # inclusive
+{
+ i : int;
+
+ if(n1<n0)
+ return;
+ if(n0>=f.nbox || n1>=f.nbox)
+ berror("xfrfreebox");
+ n1++;
+ for(i=n0; i<n1; i++)
+ if(f.box[i].nrune >= 0) {
+ f.box[i].nrune = 0;
+ f.box[i].ptr = nil;
+ }
+}
+
+nilfrbox : Frbox;
+
+xfrgrowbox(f : ref Frame, delta : int)
+{
+ ofb := f.box;
+ f.box = array[f.nalloc+delta] of ref Frbox;
+ if(f.box == nil)
+ berror("xfrgrowbox");
+ f.box[0:] = ofb[0:f.nalloc];
+ for (i := 0; i < delta; i++)
+ f.box[i+f.nalloc] = ref nilfrbox;
+ f.nalloc += delta;
+ ofb = nil;
+}
+
+dupbox(f : ref Frame, bn : int)
+{
+ if(f.box[bn].nrune < 0)
+ berror("dupbox");
+ xfraddbox(f, bn, 1);
+ if(f.box[bn].nrune >= 0) {
+ f.box[bn+1].nrune = f.box[bn].nrune;
+ f.box[bn+1].ptr = f.box[bn].ptr;
+ }
+}
+
+truncatebox(f : ref Frame, b : ref Frbox, n : int) # drop last n chars; no allocation done
+{
+ if(b.nrune<0 || b.nrune<n)
+ berror("truncatebox");
+ b.nrune -= n;
+ b.ptr = b.ptr[0:b.nrune];
+ b.wid = strwidth(f.font, b.ptr);
+}
+
+chopbox(f : ref Frame, b : ref Frbox, n : int) # drop first n chars; no allocation done
+{
+ if(b.nrune<0 || b.nrune<n)
+ berror("chopbox");
+ b.nrune -= n;
+ b.ptr = b.ptr[n:];
+ b.wid = strwidth(f.font, b.ptr);
+}
+
+xfrsplitbox(f : ref Frame, bn : int, n : int)
+{
+ dupbox(f, bn);
+ truncatebox(f, f.box[bn], f.box[bn].nrune-n);
+ chopbox(f, f.box[bn+1], n);
+}
+
+xfrmergebox(f : ref Frame, bn : int) # merge bn and bn+1
+{
+ b0 := f.box[bn];
+ b1 := f.box[bn+1];
+ b0.ptr += b1.ptr;
+ b0.wid += b1.wid;
+ b0.nrune += b1.nrune;
+ xfrdelbox(f, bn+1, bn+1);
+}
+
+xfrfindbox(f : ref Frame, bn : int, p : int, q : int) : int # find box containing q and put q on a box boundary
+{
+ nrune : int;
+
+ for( ; bn < f.nbox; bn++) {
+ nrune = 1;
+ b := f.box[bn];
+# if (b.nrune >= 0 && len b.ptr != b.nrune) {
+# frdump(f);
+# berror(sprint("findbox %d %d %d\n", bn, p, q));
+# }
+ if(b.nrune >= 0)
+ nrune = b.nrune;
+ if(p+nrune > q)
+ break;
+ p += nrune;
+ }
+ if(p != q)
+ xfrsplitbox(f, bn++, q-p);
+ return bn;
+}
+
+frdelete(f : ref Frame, p0 : int, p1 : int) : int
+{
+ pt0, pt1, ppt0 : Point;
+ n0, n1, n, s : int;
+ r : Rect;
+ nn0 : int;
+ col : ref Image;
+
+ if(p0 >= f.nchars || p0 == p1 || f.b == nil)
+ return 0;
+ if(p1 > f.nchars)
+ p1 = f.nchars;
+ n0 = xfrfindbox(f, 0, 0, p0);
+ if(n0 == f.nbox)
+ berror("off end in frdelete");
+ n1 = xfrfindbox(f, n0, p0, p1);
+ pt0 = xfrptofcharnb(f, p0, n0);
+ pt1 = frptofchar(f, p1);
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 0);
+ nn0 = n0;
+ ppt0 = pt0;
+ xfrfreebox(f, n0, n1-1);
+ f.modified = 1;
+
+ #
+ # Invariants:
+ # pt0 points to beginning, pt1 points to end
+ # n0 is box containing beginning of stuff being deleted
+ # n1, b are box containing beginning of stuff to be kept after deletion
+ # cn1 is char position of n1
+ # f.p0 and f.p1 are not adjusted until after all deletion is done
+ # region between pt0 and pt1 is clear
+ #
+ cn1 := p1;
+ while(pt1.x!=pt0.x && n1<f.nbox){
+ b := f.box[n1];
+ pt0 = xfrcklinewrap0(f, pt0, b);
+ pt1 = xfrcklinewrap(f, pt1, b);
+ n = xfrcanfit(f, pt0, b);
+ if(n==0)
+ berror("xfrcanfit==0");
+ r.min = pt0;
+ r.max = pt0;
+ r.max.y += f.font.height;
+ if(b.nrune > 0){
+ if(n != b.nrune){
+ xfrsplitbox(f, n1, n);
+ b = f.box[n1];
+ }
+ r.max.x += b.wid;
+ draw(f.b, r, f.b, nil, pt1);
+ cn1 += b.nrune;
+ }
+ else{
+ r.max.x += xfrnewwid0(f, pt0, b);
+ if(r.max.x > f.r.max.x)
+ r.max.x = f.r.max.x;
+ col = f.cols[BACK];
+ if(f.p0<=cn1 && cn1<f.p1)
+ col = f.cols[HIGH];
+ draw(f.b, r, col, nil, pt0);
+ cn1++;
+ }
+ pt1 = xfradvance(f, pt1, b);
+ pt0.x += xfrnewwid(f, pt0, b);
+ *f.box[n0++] = *f.box[n1++];
+ }
+ if(n1==f.nbox && pt0.x!=pt1.x) # deleting last thing in window; must clean up
+ frselectpaint(f, pt0, pt1, f.cols[BACK]);
+ if(pt1.y != pt0.y){
+ pt2 : Point;
+
+ pt2 = xfrptofcharptb(f, 32767, pt1, n1);
+ if(pt2.y > f.r.max.y)
+ berror("frptofchar in frdelete");
+ if(n1 < f.nbox){
+ q0, q1, q2 : int;
+
+ q0 = pt0.y+f.font.height;
+ q1 = pt1.y+f.font.height;
+ q2 = pt2.y+f.font.height;
+ # rob: before was just q2 = pt1.y+f.font.height;
+ # q2 = pt2.y;
+ if(q2 > f.r.max.y)
+ q2 = f.r.max.y;
+ draw(f.b, (pt0, (pt0.x+(f.r.max.x-pt1.x), q0)),
+ f.b, nil, pt1);
+ draw(f.b, ((f.r.min.x, q0), (f.r.max.x, q0+(q2-q1))),
+ f.b, nil, (f.r.min.x, q1));
+ frselectpaint(f, (pt2.x, pt2.y-(pt1.y-pt0.y)), pt2, f.cols[BACK]);
+ }else
+ frselectpaint(f, pt0, pt2, f.cols[BACK]);
+ }
+ xfrclosebox(f, n0, n1-1);
+ if(nn0>0 && f.box[nn0-1].nrune>=0 && ppt0.x-f.box[nn0-1].wid>=f.r.min.x){
+ --nn0;
+ ppt0.x -= f.box[nn0].wid;
+ }
+ s = n0;
+ if(n0 < f.nbox-1)
+ s++;
+ xfrclean(f, ppt0, nn0, s);
+ if(f.p1 > p1)
+ f.p1 -= p1-p0;
+ else if(f.p1 > p0)
+ f.p1 = p0;
+ if(f.p0 > p1)
+ f.p0 -= p1-p0;
+ else if(f.p0 > p0)
+ f.p0 = p0;
+ f.nchars -= p1-p0;
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 1);
+ pt0 = frptofchar(f, f.nchars);
+ n = f.nlines;
+ f.nlines = (pt0.y-f.r.min.y)/f.font.height+(pt0.x>f.r.min.x);
+ return n - f.nlines;
+}
+
+xfrredraw(f : ref Frame, pt : Point)
+{
+ nb : int;
+
+ for(nb = 0; nb < f.nbox; nb++) {
+ b := f.box[nb];
+ pt = xfrcklinewrap(f, pt, b);
+ if(b.nrune >= 0)
+ graph->stringx(f.b, pt, f.font, b.ptr, f.cols[TEXT]);
+ pt.x += b.wid;
+ }
+}
+
+frdrawsel(f : ref Frame, pt : Point, p0 : int, p1 : int, issel : int)
+{
+ back, text : ref Image;
+
+ if(f.ticked)
+ frtick(f, frptofchar(f, f.p0), 0);
+ if(p0 == p1){
+ frtick(f, pt, issel);
+ return;
+ }
+ if(issel){
+ back = f.cols[HIGH];
+ text = f.cols[HTEXT];
+ }else{
+ back = f.cols[BACK];
+ text = f.cols[TEXT];
+ }
+ frdrawsel0(f, pt, p0, p1, back, text);
+}
+
+frdrawsel0(f : ref Frame, pt : Point, p0 : int, p1 : int, back : ref Image, text : ref Image)
+{
+ b : ref Frbox;
+ nb, nr, w, x, trim : int;
+ qt : Point;
+ p : int;
+ ptr : string;
+
+ p = 0;
+ trim = 0;
+ for(nb=0; nb<f.nbox && p<p1; nb++){
+ b = f.box[nb];
+ nr = b.nrune;
+ if(nr < 0)
+ nr = 1;
+ if(p+nr <= p0){
+ p += nr;
+ continue;
+ }
+ if(p >= p0){
+ qt = pt;
+ pt = xfrcklinewrap(f, pt, b);
+ if(pt.y > qt.y)
+ draw(f.b, (qt, (f.r.max.x, pt.y)), back, nil, qt);
+ }
+ ptr = b.ptr;
+ if(p < p0){ # beginning of region: advance into box
+ ptr = ptr[p0-p:];
+ nr -= (p0-p);
+ p = p0;
+ }
+ trim = 0;
+ if(p+nr > p1){ # end of region: trim box
+ nr -= (p+nr)-p1;
+ trim = 1;
+ }
+ if(b.nrune<0 || nr==b.nrune)
+ w = b.wid;
+ else
+ w = strwidth(f.font, ptr[0:nr]);
+ x = pt.x+w;
+ if(x > f.r.max.x)
+ x = f.r.max.x;
+ draw(f.b, (pt, (x, pt.y+f.font.height)), back, nil, pt);
+ if(b.nrune >= 0)
+ graph->stringx(f.b, pt, f.font, ptr[0:nr], text);
+ pt.x += w;
+ p += nr;
+ }
+ # if this is end of last plain text box on wrapped line, fill to end of line
+ if(p1>p0 && nb>0 && nb<f.nbox && f.box[nb-1].nrune>0 && !trim){
+ qt = pt;
+ pt = xfrcklinewrap(f, pt, f.box[nb]);
+ if(pt.y > qt.y)
+ draw(f.b, (qt, (f.r.max.x, pt.y)), back, nil, qt);
+ }
+}
+
+frtick(f : ref Frame, pt : Point, ticked : int)
+{
+ r : Rect;
+
+ if(f.ticked==ticked || f.tick==nil || !pt.in(f.r))
+ return;
+ pt.x--; # looks best just left of where requested
+ r = (pt, (pt.x+FRTICKW, pt.y+f.font.height));
+ if(ticked){
+ draw(f.tickback, f.tickback.r, f.b, nil, pt);
+ draw(f.b, r, f.tick, nil, (0, 0));
+ }else
+ draw(f.b, r, f.tickback, nil, (0, 0));
+ f.ticked = ticked;
+}
+
+xfrdraw(f : ref Frame, pt : Point) : Point
+{
+ nb, n : int;
+
+ for(nb=0; nb < f.nbox; nb++){
+ b := f.box[nb];
+ pt = xfrcklinewrap0(f, pt, b);
+ if(pt.y == f.r.max.y){
+ f.nchars -= xfrstrlen(f, nb);
+ xfrdelbox(f, nb, f.nbox-1);
+ break;
+ }
+ if(b.nrune > 0){
+ n = xfrcanfit(f, pt, b);
+ if(n == 0)
+ berror("draw: xfrcanfit==0");
+ if(n != b.nrune){
+ xfrsplitbox(f, nb, n);
+ b = f.box[nb];
+ }
+ pt.x += b.wid;
+ }else{
+ if(b.bc == '\n') {
+ pt.x = f.r.min.x;
+ pt.y += f.font.height;
+ }
+ else
+ pt.x += xfrnewwid(f, pt, b);
+ }
+ }
+ return pt;
+}
+
+xfrstrlen(f : ref Frame, nb : int) : int
+{
+ n, nrune : int;
+
+ for(n=0; nb<f.nbox; nb++) {
+ nrune = f.box[nb].nrune;
+ if(nrune < 0)
+ nrune = 1;
+ n += nrune;
+ }
+ return n;
+}
+
+frinit(f : ref Frame, r : Rect, ft : ref Font, b : ref Image, cols : array of ref Draw->Image)
+{
+ f.font = ft;
+ f.scroll = 0;
+ f.maxtab = 8*charwidth(ft, '0');
+ f.nbox = 0;
+ f.nalloc = 0;
+ f.nchars = 0;
+ f.nlines = 0;
+ f.p0 = 0;
+ f.p1 = 0;
+ f.box = nil;
+ f.lastlinefull = 0;
+ if(cols != nil)
+ for(i := 0; i < NCOL; i++)
+ f.cols[i] = cols[i];
+ for (i = 0; i < len noglyphs; i++) {
+ if (charwidth(ft, noglyphs[i]) != 0) {
+ f.noglyph = noglyphs[i];
+ break;
+ }
+ }
+ frsetrects(f, r, b);
+ if (f.tick==nil && f.cols[BACK] != nil)
+ frinittick(f);
+}
+
+frinittick(f : ref Frame)
+{
+ ft : ref Font;
+
+ ft = f.font;
+ f.tick = nil;
+ f.tick = graph->balloc(((0, 0), (FRTICKW, ft.height)), (gui->mainwin).chans, Draw->White);
+ if(f.tick == nil)
+ return;
+ f.tickback = graph->balloc(f.tick.r, (gui->mainwin).chans, Draw->White);
+ if(f.tickback == nil){
+ f.tick = nil;
+ return;
+ }
+ # background color
+ draw(f.tick, f.tick.r, f.cols[BACK], nil, (0, 0));
+ # vertical line
+ draw(f.tick, ((FRTICKW/2, 0), (FRTICKW/2+1, ft.height)), black, nil, (0, 0));
+ # box on each end
+ # draw(f->tick, Rect(0, 0, FRTICKW, FRTICKW), f->cols[TEXT], nil, ZP);
+ # draw(f->tick, Rect(0, ft->height-FRTICKW, FRTICKW, ft->height), f->cols[TEXT], nil, ZP);
+}
+
+frsetrects(f : ref Frame, r : Rect, b : ref Image)
+{
+ f.b = b;
+ f.entire = r;
+ f.r = r;
+ f.r.max.y -= (r.max.y-r.min.y)%f.font.height;
+ f.maxlines = (r.max.y-r.min.y)/f.font.height;
+}
+
+frclear(f : ref Frame, freeall : int)
+{
+ if(f.nbox)
+ xfrdelbox(f, 0, f.nbox-1);
+ for (i := 0; i < f.nalloc; i++)
+ f.box[i] = nil;
+ if(freeall)
+ f.tick = f.tickback = nil;
+ f.box = nil;
+ f.ticked = 0;
+}
+
+DELTA : con 25;
+TMPSIZE : con 256;
+
+Plist : adt {
+ pt0 : Point;
+ pt1 : Point;
+};
+
+nalloc : int = 0;
+pts : array of Plist;
+
+bxscan(f : ref Frame, rp : string, l : int, ppt : Point) : (Point, Point)
+{
+ w, c, nb, delta, nl, nr : int;
+ sp : int = 0;
+
+ frame.r = f.r;
+ frame.b = f.b;
+ frame.font = f.font;
+ frame.maxtab = f.maxtab;
+ frame.nbox = 0;
+ frame.nchars = 0;
+ for(i := 0; i < NCOL; i++)
+ frame.cols[i] = f.cols[i];
+ frame.noglyph = f.noglyph;
+ delta = DELTA;
+ nl = 0;
+ for(nb=0; sp<l && nl <= f.maxlines; nb++){
+ if(nb == frame.nalloc){
+ xfrgrowbox(frame, delta);
+ if(delta < 10000)
+ delta *= 2;
+ }
+ b := frame.box[nb];
+ c = rp[sp];
+ if(c=='\t' || c=='\n'){
+ b.bc = c;
+ b.wid = 5000;
+ if(c == '\n')
+ b.minwid = 0;
+ else
+ b.minwid = charwidth(frame.font, ' ');
+ b.nrune = -1;
+ if(c=='\n')
+ nl++;
+ frame.nchars++;
+ sp++;
+ }else{
+ nr = 0;
+ w = 0;
+ ssp := sp;
+ nul := 0;
+ while(sp < l){
+ c = rp[sp];
+ if(c=='\t' || c=='\n')
+ break;
+ if(nr+1 >= TMPSIZE)
+ break;
+ if ((cw := charwidth(frame.font, c)) == 0) { # used to be only for c == 0
+ c = frame.noglyph;
+ cw = charwidth(frame.font, c);
+ nul = 1;
+ }
+ w += cw;
+ sp++;
+ nr++;
+ }
+ b = frame.box[nb];
+ b.ptr = rp[ssp:sp];
+ b.wid = w;
+ b.nrune = nr;
+ frame.nchars += nr;
+ if (nul) {
+ for (i = 0; i < nr; i++)
+ if (charwidth(frame.font, b.ptr[i]) == 0)
+ b.ptr[i] = frame.noglyph;
+ }
+ }
+ frame.nbox++;
+ }
+ ppt = xfrcklinewrap0(f, ppt, frame.box[0]);
+ return (xfrdraw(frame, ppt), ppt);
+}
+
+chopframe(f : ref Frame, pt : Point, p : int, bn : int)
+{
+ nb, nrune : int;
+
+ for(nb = bn; ; nb++){
+ if(nb >= f.nbox)
+ berror("endofframe");
+ b := f.box[nb];
+ pt = xfrcklinewrap(f, pt, b);
+ if(pt.y >= f.r.max.y)
+ break;
+ nrune = b.nrune;
+ if(nrune < 0)
+ nrune = 1;
+ p += nrune;
+ pt = xfradvance(f, pt, b);
+ }
+ f.nchars = p;
+ f.nlines = f.maxlines;
+ if (nb < f.nbox) # BUG
+ xfrdelbox(f, nb, f.nbox-1);
+}
+
+frinsert(f : ref Frame, rp : string, l : int, p0 : int)
+{
+ pt0, pt1, ppt0, ppt1, pt : Point;
+ s, n, n0, nn0, y : int;
+ r : Rect;
+ npts : int;
+ col : ref Image;
+
+ if(p0 > f.nchars || l == 0 || f.b == nil)
+ return;
+ n0 = xfrfindbox(f, 0, 0, p0);
+ cn0 := p0;
+ nn0 = n0;
+ pt0 = xfrptofcharnb(f, p0, n0);
+ ppt0 = pt0;
+ (pt1, ppt0) = bxscan(f, rp, l, ppt0);
+ ppt1 = pt1;
+ if(n0 < f.nbox){
+ b := f.box[n0];
+ pt0 = xfrcklinewrap(f, pt0, b); # for frdrawsel()
+ ppt1 = xfrcklinewrap0(f, ppt1, b);
+ }
+ f.modified = 1;
+ #
+ # ppt0 and ppt1 are start and end of insertion as they will appear when
+ # insertion is complete. pt0 is current location of insertion position
+ # (p0); pt1 is terminal point (without line wrap) of insertion.
+ #
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 0);
+
+ #
+ # Find point where old and new x's line up
+ # Invariants:
+ # pt0 is where the next box (b, n0) is now
+ # pt1 is where it will be after then insertion
+ # If pt1 goes off the rectangle, we can toss everything from there on
+ #
+
+ for(npts=0; pt1.x!= pt0.x && pt1.y!=f.r.max.y && n0<f.nbox; npts++){
+ b := f.box[n0];
+ pt0 = xfrcklinewrap(f, pt0, b);
+ pt1 = xfrcklinewrap0(f, pt1, b);
+ if(b.nrune > 0){
+ n = xfrcanfit(f, pt1, b);
+ if(n == 0)
+ berror("xfrcanfit==0");
+ if(n != b.nrune){
+ xfrsplitbox(f, n0, n);
+ b = f.box[n0];
+ }
+ }
+ if(npts == nalloc){
+ opts := pts;
+ pts = array[npts+DELTA] of Plist;
+ pts[0:] = opts[0:npts];
+ for (k := 0; k < DELTA; k++)
+ pts[k+npts].pt0 = pts[k+npts].pt1 = (0, 0);
+ opts = nil;
+ nalloc += DELTA;
+ b = f.box[n0];
+ }
+ pts[npts].pt0 = pt0;
+ pts[npts].pt1 = pt1;
+ # has a text box overflowed off the frame?
+ if(pt1.y == f.r.max.y)
+ break;
+ pt0 = xfradvance(f, pt0, b);
+ pt1.x += xfrnewwid(f, pt1, b);
+ n0++;
+ nrune := b.nrune;
+ if(nrune < 0)
+ nrune = 1;
+ cn0 += nrune;
+ }
+ if(pt1.y > f.r.max.y)
+ berror("frinsert pt1 too far");
+ if(pt1.y==f.r.max.y && n0<f.nbox){
+ f.nchars -= xfrstrlen(f, n0);
+ xfrdelbox(f, n0, f.nbox-1);
+ }
+ if(n0 == f.nbox)
+ f.nlines = (pt1.y-f.r.min.y)/f.font.height+(pt1.x>f.r.min.x);
+ else if(pt1.y!=pt0.y){
+ q0, q1 : int;
+
+ y = f.r.max.y;
+ q0 = pt0.y+f.font.height;
+ q1 = pt1.y+f.font.height;
+ f.nlines += (q1-q0)/f.font.height;
+ if(f.nlines > f.maxlines)
+ chopframe(f, ppt1, p0, nn0);
+ if(pt1.y < y){
+ r = f.r;
+ r.min.y = q1;
+ r.max.y = y;
+ if(q1 < y)
+ draw(f.b, r, f.b, nil, (f.r.min.x, q0));
+ r.min = pt1;
+ r.max.x = pt1.x+(f.r.max.x-pt0.x);
+ r.max.y = q1;
+ draw(f.b, r, f.b, nil, pt0);
+ }
+ }
+ #
+ # Move the old stuff down to make room. The loop will move the stuff
+ # between the insertion and the point where the x's lined up.
+ # The draws above moved everything down after the point they lined up.
+ #
+ y = 0;
+ if(pt1.y == f.r.max.y)
+ y = pt1.y;
+ for(j := n0-1; --npts >= 0; --j){
+ pt = pts[npts].pt1;
+ b := f.box[j];
+ if(b.nrune > 0){
+ r.min = pt;
+ r.max = r.min;
+ r.max.x += b.wid;
+ r.max.y += f.font.height;
+ draw(f.b, r, f.b, nil, pts[npts].pt0);
+ if(pt.y < y){ # clear bit hanging off right
+ r.min = pt;
+ r.max = pt;
+ r.min.x += b.wid;
+ r.max.x = f.r.max.x;
+ r.max.y += f.font.height;
+ if(f.p0<=cn0 && cn0<f.p1) # b+1 is inside selection
+ col = f.cols[HIGH];
+ else
+ col = f.cols[BACK];
+ draw(f.b, r, col, nil, r.min);
+ }
+ y = pt.y;
+ cn0 -= b.nrune;
+ }else{
+ r.min = pt;
+ r.max = pt;
+ r.max.x += b.wid;
+ r.max.y += f.font.height;
+ if(r.max.x >= f.r.max.x)
+ r.max.x = f.r.max.x;
+ cn0--;
+ if(f.p0<=cn0 && cn0<f.p1) # b is inside selection
+ col = f.cols[HIGH];
+ else
+ col = f.cols[BACK];
+ draw(f.b, r, col, nil, r.min);
+ y = 0;
+ if(pt.x == f.r.min.x)
+ y = pt.y;
+ }
+ }
+ # insertion can extend the selection, so the condition here is different
+ if(f.p0<p0 && p0<=f.p1)
+ col = f.cols[HIGH];
+ else
+ col = f.cols[BACK];
+ frselectpaint(f, ppt0, ppt1, col);
+ xfrredraw(frame, ppt0);
+ xfraddbox(f, nn0, frame.nbox);
+ for(n=0; n<frame.nbox; n++)
+ *f.box[nn0+n] = *frame.box[n];
+ if(nn0>0 && f.box[nn0-1].nrune>=0 && ppt0.x-f.box[nn0-1].wid>=f.r.min.x){
+ --nn0;
+ ppt0.x -= f.box[nn0].wid;
+ }
+ n0 += frame.nbox;
+ s = n0;
+ if(n0 < f.nbox-1)
+ s++;
+ xfrclean(f, ppt0, nn0, s);
+ f.nchars += frame.nchars;
+ if(f.p0 >= p0)
+ f.p0 += frame.nchars;
+ if(f.p0 > f.nchars)
+ f.p0 = f.nchars;
+ if(f.p1 >= p0)
+ f.p1 += frame.nchars;
+ if(f.p1 > f.nchars)
+ f.p1 = f.nchars;
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 1);
+}
+
+xfrptofcharptb(f : ref Frame, p : int, pt : Point, bn : int) : Point
+{
+ s : int;
+ l : int;
+ r : int;
+
+ for( ; bn < f.nbox; bn++){
+ b := f.box[bn];
+ pt = xfrcklinewrap(f, pt, b);
+ l = b.nrune;
+ if(l < 0)
+ l = 1;
+ if(p < l){
+ if(b.nrune > 0)
+ for(s = 0; p > 0; s++){
+ r = b.ptr[s];
+ pt.x += charwidth(f.font, r);
+ if(r==0 || pt.x>f.r.max.x)
+ berror("frptofchar");
+ p--;
+ }
+ break;
+ }
+ p -= l;
+ pt = xfradvance(f, pt, b);
+ }
+ return pt;
+}
+
+frptofchar(f : ref Frame, p : int) : Point
+{
+ return xfrptofcharptb(f, p, f.r.min, 0);
+}
+
+xfrptofcharnb(f : ref Frame, p : int, nb : int) : Point # doesn't do final xfradvance to next line
+{
+ pt : Point;
+ nbox : int;
+
+ nbox = f.nbox;
+ f.nbox = nb;
+ pt = xfrptofcharptb(f, p, f.r.min, 0);
+ f.nbox = nbox;
+ return pt;
+}
+
+xfrgrid(f : ref Frame, p: Point) : Point
+{
+ p.y -= f.r.min.y;
+ p.y -= p.y%f.font.height;
+ p.y += f.r.min.y;
+ if(p.x > f.r.max.x)
+ p.x = f.r.max.x;
+ return p;
+}
+
+frcharofpt(f : ref Frame, pt : Point) : int
+{
+ qt : Point;
+ bn, nrune : int;
+ s : int;
+ p : int;
+ r : int;
+
+ pt = xfrgrid(f, pt);
+ qt = f.r.min;
+
+ bn=0;
+ for(p=0; bn<f.nbox && qt.y<pt.y; bn++){
+ b := f.box[bn];
+ qt = xfrcklinewrap(f, qt, b);
+ if(qt.y >= pt.y)
+ break;
+ qt = xfradvance(f, qt, b);
+ nrune = b.nrune;
+ if(nrune < 0)
+ nrune = 1;
+ p += nrune;
+ }
+
+ for(; bn<f.nbox && qt.x<=pt.x; bn++){
+ b := f.box[bn];
+ qt = xfrcklinewrap(f, qt, b);
+ if(qt.y > pt.y)
+ break;
+ if(qt.x+b.wid > pt.x){
+ if(b.nrune < 0)
+ qt = xfradvance(f, qt, b);
+ else{
+ s = 0;
+ for(;;){
+ r = b.ptr[s++];
+ qt.x += charwidth(f.font, r);
+ if(qt.x > pt.x)
+ break;
+ p++;
+ }
+ }
+ }else{
+ nrune = b.nrune;
+ if(nrune < 0)
+ nrune = 1;
+ p += nrune;
+ qt = xfradvance(f, qt, b);
+ }
+ }
+ return p;
+}
+
+region(a, b : int) : int
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+frselect(f : ref Frame, m : ref Pointer) # when called, button 1 is down
+{
+ p0, p1, q : int;
+ mp, pt0, pt1, qt : Point;
+ b, scrled, reg : int;
+
+ mp = m.xy;
+ b = m.buttons;
+
+ f.modified = 0;
+ frdrawsel(f, frptofchar(f, f.p0), f.p0, f.p1, 0);
+ p0 = p1 = frcharofpt(f, mp);
+ f.p0 = p0;
+ f.p1 = p1;
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ frdrawsel(f, pt0, p0, p1, 1);
+ do{
+ scrled = 0;
+ if(f.scroll){
+ if(m.xy.y < f.r.min.y){
+ textm->framescroll(f, -(f.r.min.y-m.xy.y)/f.font.height-1);
+ p0 = f.p1;
+ p1 = f.p0;
+ scrled = 1;
+ }else if(m.xy.y > f.r.max.y){
+ textm->framescroll(f, (m.xy.y-f.r.max.y)/f.font.height+1);
+ p0 = f.p0;
+ p1 = f.p1;
+ scrled = 1;
+ }
+ if(scrled){
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = region(p1, p0);
+ }
+ }
+ q = frcharofpt(f, m.xy);
+ if(p1 != q){
+ if(reg != region(q, p0)){ # crossed starting point; reset
+ if(reg > 0)
+ frdrawsel(f, pt0, p0, p1, 0);
+ else if(reg < 0)
+ frdrawsel(f, pt1, p1, p0, 0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel(f, pt0, p0, p1, 1);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel(f, pt1, p1, q, 1);
+ else if(q < p1)
+ frdrawsel(f, qt, q, p1, 0);
+ }else if(reg < 0){
+ if(q > p1)
+ frdrawsel(f, pt1, p1, q, 0);
+ else
+ frdrawsel(f, qt, q, p1, 1);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ f.modified = 0;
+ if(p0 < p1) {
+ f.p0 = p0;
+ f.p1 = p1;
+ }
+ else {
+ f.p0 = p1;
+ f.p1 = p0;
+ }
+ if(scrled)
+ textm->framescroll(f, 0);
+ graph->bflush();
+ if(!scrled)
+ acme->frgetmouse();
+ }while(m.buttons == b);
+}
+
+frselectpaint(f : ref Frame, p0 : Point, p1 : Point, col : ref Image)
+{
+ n : int;
+ q0, q1 : Point;
+
+ q0 = p0;
+ q1 = p1;
+ q0.y += f.font.height;
+ q1.y += f.font.height;
+ n = (p1.y-p0.y)/f.font.height;
+ if(f.b == nil)
+ berror("frselectpaint b==0");
+ if(p0.y == f.r.max.y)
+ return;
+ if(n == 0)
+ draw(f.b, (p0, q1), col, nil, (0, 0));
+ else{
+ if(p0.x >= f.r.max.x)
+ p0.x = f.r.max.x-1;
+ draw(f.b, ((p0.x, p0.y), (f.r.max.x, q0.y)), col, nil, (0, 0));
+ if(n > 1)
+ draw(f.b, ((f.r.min.x, q0.y), (f.r.max.x, p1.y)),
+ col, nil, (0, 0));
+ draw(f.b, ((f.r.min.x, p1.y), (q1.x, q1.y)),
+ col, nil, (0, 0));
+ }
+}
+
+xfrcanfit(f : ref Frame, pt : Point, b : ref Frbox) : int
+{
+ left, nr : int;
+ p : int;
+ r : int;
+
+ left = f.r.max.x-pt.x;
+ if(b.nrune < 0)
+ return b.minwid <= left;
+ if(left >= b.wid)
+ return b.nrune;
+ nr = 0;
+ for(p = 0; p < len b.ptr; p++){
+ r = b.ptr[p];
+ left -= charwidth(f.font, r);
+ if(left < 0)
+ return nr;
+ nr++;
+ }
+ berror("xfrcanfit can't");
+ return 0;
+}
+
+xfrcklinewrap(f : ref Frame, p : Point, b : ref Frbox) : Point
+{
+ wid : int;
+
+ if(b.nrune < 0)
+ wid = b.minwid;
+ else
+ wid = b.wid;
+
+ if(wid > f.r.max.x-p.x){
+ p.x = f.r.min.x;
+ p.y += f.font.height;
+ }
+ return p;
+}
+
+xfrcklinewrap0(f : ref Frame, p : Point, b : ref Frbox) : Point
+{
+ if(xfrcanfit(f, p, b) == 0){
+ p.x = f.r.min.x;
+ p.y += f.font.height;
+ }
+ return p;
+}
+
+xfrcklinewrap1(f : ref Frame, p : Point, wid : int) : Point
+{
+ if(wid > f.r.max.x-p.x){
+ p.x = f.r.min.x;
+ p.y += f.font.height;
+ }
+ return p;
+}
+
+xfradvance(f : ref Frame, p : Point, b : ref Frbox) : Point
+{
+ if(b.nrune<0 && b.bc=='\n'){
+ p.x = f.r.min.x;
+ p.y += f.font.height;
+ }else
+ p.x += b.wid;
+ return p;
+}
+
+xfrnewwid(f : ref Frame, pt : Point, b : ref Frbox) : int
+{
+ b.wid = xfrnewwid0(f, pt, b);
+ return b.wid;
+}
+
+xfrnewwid0(f : ref Frame, pt : Point, b : ref Frbox) : int
+{
+ c, x : int;
+
+ c = f.r.max.x;
+ x = pt.x;
+ if(b.nrune >= 0 || b.bc != '\t')
+ return b.wid;
+ if(x+b.minwid > c)
+ x = pt.x = f.r.min.x;
+ x += f.maxtab;
+ x -= (x-f.r.min.x)%f.maxtab;
+ if(x-pt.x<b.minwid || x>c)
+ x = pt.x+b.minwid;
+ return x-pt.x;
+}
+
+xfrclean(f : ref Frame, pt : Point, n0 : int, n1 : int) # look for mergeable boxes
+{
+ nb, c : int;
+
+ c = f.r.max.x;
+ for(nb=n0; nb<n1-1; nb++){
+ b0 := f.box[nb];
+ b1 := f.box[nb+1];
+ pt = xfrcklinewrap(f, pt, b0);
+ while(b0.nrune>=0 && nb<n1-1 && b1.nrune>=0 && pt.x+b0.wid+b1.wid<c){
+ xfrmergebox(f, nb);
+ n1--;
+ b0 = f.box[nb];
+ b1 = f.box[nb+1];
+ }
+ pt = xfradvance(f, pt, f.box[nb]);
+ }
+ for(; nb<f.nbox; nb++){
+ b := f.box[nb];
+ pt = xfrcklinewrap(f, pt, b);
+ pt = xfradvance(f, pt, f.box[nb]);
+ }
+ f.lastlinefull = 0;
+ if(pt.y >= f.r.max.y)
+ f.lastlinefull = 1;
+}
--- /dev/null
+++ b/appl/acme/frame.m
@@ -1,0 +1,54 @@
+Framem : module {
+ PATH : con "/dis/acme/frame.dis";
+
+ BACK, HIGH, BORD, TEXT, HTEXT, NCOL : con iota;
+
+ FRTICKW : con 3;
+
+ init : fn(mods : ref Dat->Mods);
+
+ newframe : fn() : ref Frame;
+
+ Frbox : adt {
+ wid : int; # in pixels
+ nrune : int; # <0 ==> negate and treat as break char
+ ptr : string;
+ bc : int; # break char
+ minwid : int;
+ };
+
+ Frame : adt {
+ font : ref Draw->Font; # of chars in the frame
+ b : ref Draw->Image; # on which frame appears
+ cols : array of ref Draw->Image; # colours
+ r : Draw->Rect; # in which text appears
+ entire : Draw->Rect; # of full frame
+ box : array of ref Frbox;
+ scroll : int; # call framescroll function
+ p0 : int;
+ p1 : int; # selection
+ nbox, nalloc : int;
+ maxtab : int; # max size of tab, in pixels
+ nchars : int; # runes in frame
+ nlines : int; # lines with text
+ maxlines : int; # total # lines in frame
+ lastlinefull : int; # last line fills frame
+ modified : int; # changed since frselect()
+ noglyph : int; # char to use when a char has 0 width glyph
+ tick : ref Draw->Image; # typing tick
+ tickback : ref Draw->Image; # saved image under tick
+ ticked : int; # is tick on screen ?
+ };
+
+ frcharofpt : fn(f : ref Frame, p : Draw->Point) : int;
+ frptofchar : fn(f : ref Frame, c : int) : Draw->Point;
+ frdelete : fn(f : ref Frame, c1 : int, c2 : int) : int;
+ frinsert : fn(f : ref Frame, s : string, l : int, i : int);
+ frselect : fn(f : ref Frame, m : ref Draw->Pointer);
+ frinit : fn(f : ref Frame, r : Draw->Rect, f : ref Draw->Font, b : ref Draw->Image, cols : array of ref Draw->Image);
+ frsetrects : fn(f : ref Frame, r : Draw->Rect, b : ref Draw->Image);
+ frclear : fn(f : ref Frame, x : int);
+ frdrawsel : fn(f : ref Frame, p : Draw->Point, p0 : int, p1 : int, n : int);
+ frdrawsel0 : fn(f : ref Frame, p : Draw->Point, p0 : int, p1 : int, i1 : ref Draw->Image, i2 : ref Draw->Image);
+ frtick : fn(f : ref Frame, p : Draw->Point, n : int);
+};
--- /dev/null
+++ b/appl/acme/fsys.b
@@ -1,0 +1,866 @@
+implement Fsys;
+
+include "common.m";
+
+sys : Sys;
+styx : Styx;
+styxaux : Styxaux;
+acme : Acme;
+dat : Dat;
+utils : Utils;
+look : Look;
+windowm : Windowm;
+xfidm : Xfidm;
+
+QTDIR, QTFILE, QTAPPEND : import Sys;
+DMDIR, DMAPPEND, Qid, ORCLOSE, OTRUNC, OREAD, OWRITE, ORDWR, Dir : import Sys;
+sprint : import sys;
+MAXWELEM, Rerror : import Styx;
+Qdir,Qacme,Qcons,Qconsctl,Qdraw,Qeditout,Qindex,Qlabel,Qnew,QWaddr,QWbody,QWconsctl,QWctl,QWdata,QWeditout,QWevent,QWrdsel,QWwrsel,QWtag,QMAX : import Dat;
+TRUE, FALSE : import Dat;
+cxfidalloc, cerr : import dat;
+Mntdir, Fid, Dirtab, Lock, Ref, Smsg0 : import dat;
+Tmsg, Rmsg : import styx;
+msize, version, fid, uname, aname, newfid, name, mode, offset, count, setmode : import styxaux;
+Xfid : import xfidm;
+row : import dat;
+Column : import Columnm;
+Window : import windowm;
+lookid : import look;
+warning, error : import utils;
+
+init(mods : ref Dat->Mods)
+{
+ messagesize = Styx->MAXRPC;
+
+ sys = mods.sys;
+ styx = mods.styx;
+ styxaux = mods.styxaux;
+ acme = mods.acme;
+ dat = mods.dat;
+ utils = mods.utils;
+ look = mods.look;
+ windowm = mods.windowm;
+ xfidm = mods.xfidm;
+}
+
+sfd, cfd : ref Sys->FD;
+
+Nhash : con 16;
+DEBUG : con 0;
+
+fids := array[Nhash] of ref Fid;
+
+Eperm := "permission denied";
+Eexist := "file does not exist";
+Enotdir := "not a directory";
+
+dirtab := array[10] of {
+ Dirtab ( ".", QTDIR, Qdir, 8r500|DMDIR ),
+ Dirtab ( "acme", QTDIR, Qacme, 8r500|DMDIR ),
+ Dirtab ( "cons", QTFILE, Qcons, 8r600 ),
+ Dirtab ( "consctl", QTFILE, Qconsctl, 8r000 ),
+ Dirtab ( "draw", QTDIR, Qdraw, 8r000|DMDIR ),
+ Dirtab ( "editout", QTFILE, Qeditout, 8r200 ),
+ Dirtab ( "index", QTFILE, Qindex, 8r400 ),
+ Dirtab ( "label", QTFILE, Qlabel, 8r600 ),
+ Dirtab ( "new", QTDIR, Qnew, 8r500|DMDIR ),
+ Dirtab ( nil, 0, 0, 0 ),
+};
+
+dirtabw := array[12] of {
+ Dirtab ( ".", QTDIR, Qdir, 8r500|DMDIR ),
+ Dirtab ( "addr", QTFILE, QWaddr, 8r600 ),
+ Dirtab ( "body", QTAPPEND, QWbody, 8r600|DMAPPEND ),
+ Dirtab ( "ctl", QTFILE, QWctl, 8r600 ),
+ Dirtab ( "consctl", QTFILE, QWconsctl, 8r200 ),
+ Dirtab ( "data", QTFILE, QWdata, 8r600 ),
+ Dirtab ( "editout", QTFILE, QWeditout, 8r200 ),
+ Dirtab ( "event", QTFILE, QWevent, 8r600 ),
+ Dirtab ( "rdsel", QTFILE, QWrdsel, 8r400 ),
+ Dirtab ( "wrsel", QTFILE, QWwrsel, 8r200 ),
+ Dirtab ( "tag", QTAPPEND, QWtag, 8r600|DMAPPEND ),
+ Dirtab ( nil, 0, 0, 0 ),
+};
+
+Mnt : adt {
+ qlock : ref Lock;
+ id : int;
+ md : ref Mntdir;
+};
+
+mnt : Mnt;
+user : string;
+clockfd : ref Sys->FD;
+closing := 0;
+
+fsysinit()
+{
+ p : array of ref Sys->FD;
+
+ p = array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ error("can't create pipe");
+ cfd = p[0];
+ sfd = p[1];
+ clockfd = sys->open("/dev/time", Sys->OREAD);
+ user = utils->getuser();
+ if (user == nil)
+ user = "Wile. E. Coyote";
+ mnt.qlock = Lock.init();
+ mnt.id = 0;
+ spawn fsysproc();
+}
+
+fsyscfd() : int
+{
+ return cfd.fd;
+}
+
+QID(w, q : int) : int
+{
+ return (w<<8)|q;
+}
+
+FILE(q : Qid) : int
+{
+ return int q.path & 16rFF;
+}
+
+WIN(q : Qid) : int
+{
+ return (int q.path>>8) & 16rFFFFFF;
+}
+
+# nullsmsg : Smsg;
+nullsmsg0 : Smsg0;
+
+fsysproc()
+{
+ n, ok : int;
+ x : ref Xfid;
+ f : ref Fid;
+ t : Smsg0;
+
+ acme->fsyspid = sys->pctl(0, nil);
+ x = nil;
+ for(;;){
+ if(x == nil){
+ cxfidalloc <-= nil;
+ x = <-cxfidalloc;
+ }
+ n = sys->read(sfd, x.buf, messagesize);
+ if(n <= 0) {
+ if (closing)
+ break;
+ error("i/o error on server channel");
+ }
+ (ok, x.fcall) = Tmsg.unpack(x.buf[0:n]);
+ if(ok < 0)
+ error("convert error in convM2S");
+ if(DEBUG)
+ utils->debug(sprint("%d:%s\n", x.tid, x.fcall.text()));
+ pick fc := x.fcall {
+ Version =>
+ f = nil;
+ Auth =>
+ f = nil;
+ * =>
+ f = allocfid(fid(x.fcall));
+ }
+ x.f = f;
+ pick fc := x.fcall {
+ Readerror => x = fsyserror();
+ Flush => x = fsysflush(x);
+ Version => x = fsysversion(x);
+ Auth => x = fsysauth(x);
+ Attach => x = fsysattach(x, f);
+ Walk => x = fsyswalk(x, f);
+ Open => x = fsysopen(x, f);
+ Create => x = fsyscreate(x);
+ Read => x = fsysread(x, f);
+ Write => x = fsyswrite(x);
+ Clunk => x = fsysclunk(x, f);
+ Remove => x = fsysremove(x);
+ Stat => x = fsysstat(x, f);
+ Wstat => x = fsyswstat(x);
+ # Clone => x = fsysclone(x, f);
+ * =>
+ x = respond(x, t, "bad fcall type");
+ }
+ }
+}
+
+fsysaddid(dir : string, ndir : int, incl : array of string, nincl : int) : ref Mntdir
+{
+ m : ref Mntdir;
+ id : int;
+
+ mnt.qlock.lock();
+ id = ++mnt.id;
+ m = ref Mntdir;
+ m.id = id;
+ m.dir = dir;
+ m.refs = 1; # one for Command, one will be incremented in attach
+ m.ndir = ndir;
+ m.next = mnt.md;
+ m.incl = incl;
+ m.nincl = nincl;
+ mnt.md = m;
+ mnt.qlock.unlock();
+ return m;
+}
+
+fsysdelid(idm : ref Mntdir)
+{
+ m, prev : ref Mntdir;
+ i : int;
+
+ if(idm == nil)
+ return;
+ mnt.qlock.lock();
+ if(--idm.refs > 0){
+ mnt.qlock.unlock();
+ return;
+ }
+ prev = nil;
+ for(m=mnt.md; m != nil; m=m.next){
+ if(m == idm){
+ if(prev != nil)
+ prev.next = m.next;
+ else
+ mnt.md = m.next;
+ for(i=0; i<m.nincl; i++)
+ m.incl[i] = nil;
+ m.incl = nil;
+ m.dir = nil;
+ m = nil;
+ mnt.qlock.unlock();
+ return;
+ }
+ prev = m;
+ }
+ mnt.qlock.unlock();
+ buf := sys->sprint("fsysdelid: can't find id %d\n", idm.id);
+ cerr <-= buf;
+}
+
+#
+# Called only in exec.l:run(), from a different FD group
+#
+fsysmount(dir : string, ndir : int, incl : array of string, nincl : int) : ref Mntdir
+{
+ m : ref Mntdir;
+
+ # close server side so don't hang if acme is half-exited
+ # sfd = nil;
+ m = fsysaddid(dir, ndir, incl, nincl);
+ buf := sys->sprint("%d", m.id);
+ if(sys->mount(cfd, nil, "/mnt/acme", Sys->MREPL, buf) < 0){
+ fsysdelid(m);
+ return nil;
+ }
+ # cfd = nil;
+ sys->bind("/mnt/acme", "/chan", Sys->MBEFORE); # was MREPL
+ if(sys->bind("/mnt/acme", "/dev", Sys->MBEFORE) < 0){
+ fsysdelid(m);
+ return nil;
+ }
+ return m;
+}
+
+fsysclose()
+{
+ closing = 1;
+ # sfd = cfd = nil;
+}
+
+respond(x : ref Xfid, t0 : Smsg0, err : string) : ref Xfid
+{
+ t : ref Rmsg;
+
+ # t = nullsmsg;
+ tag := x.fcall.tag;
+ # fid := fid(x.fcall);
+ qid := t0.qid;
+ if(err != nil)
+ t = ref Rmsg.Error(tag, err);
+ else
+ pick fc := x.fcall {
+ Readerror => t = ref Rmsg.Error(tag, err);
+ Flush => t = ref Rmsg.Flush(tag);
+ Version => t = ref Rmsg.Version(tag, t0.msize, t0.version);
+ Auth => t = ref Rmsg.Auth(tag, qid);
+ # Clone => t = ref Rmsg.Clone(tag, fid);
+ Attach => t = ref Rmsg.Attach(tag, qid);
+ Walk => t = ref Rmsg.Walk(tag, t0.qids);
+ Open => t = ref Rmsg.Open(tag, qid, t0.iounit);
+ Create => t = ref Rmsg.Create(tag, qid, 0);
+ Read => if(t0.count == len t0.data)
+ t = ref Rmsg.Read(tag, t0.data);
+ else
+ t = ref Rmsg.Read(tag, t0.data[0: t0.count]);
+ Write => t = ref Rmsg.Write(tag, t0.count);
+ Clunk => t = ref Rmsg.Clunk(tag);
+ Remove => t = ref Rmsg.Remove(tag);
+ Stat => t = ref Rmsg.Stat(tag, t0.stat);
+ Wstat => t = ref Rmsg.Wstat(tag);
+
+ }
+ # t.qid = t0.qid;
+ # t.count = t0.count;
+ # t.data = t0.data;
+ # t.stat = t0.stat;
+ # t.fid = x.fcall.fid;
+ # t.tag = x.fcall.tag;
+ buf := t.pack();
+ if(buf == nil)
+ error("convert error in convS2M");
+ if(sys->write(sfd, buf, len buf) != len buf)
+ error("write error in respond");
+ buf = nil;
+ if(DEBUG)
+ utils->debug(sprint("%d:r: %s\n", x.tid, t.text()));
+ return x;
+}
+
+# fsysnop(x : ref Xfid) : ref Xfid
+# {
+# t : Smsg0;
+#
+# return respond(x, t, nil);
+# }
+
+fsyserror() : ref Xfid
+{
+ error("sys error : Terror");
+ return nil;
+}
+
+fsyssession(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ # BUG: should shut everybody down ??
+ t = nullsmsg0;
+ return respond(x, t, nil);
+}
+
+fsysversion(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ pick m := x.fcall {
+ Version =>
+ (t.msize, t.version) = styx->compatible(m, messagesize, nil);
+ messagesize = t.msize;
+ return respond(x, t, nil);
+ }
+ return respond(x, t, "acme: bad version");
+
+ # ms := msize(x.fcall);
+ # if(ms < 256)
+ # return respond(x, t, "version: message size too small");
+ # t.msize = messagesize = ms;
+ # v := version(x.fcall);
+ # if(len v < 6 || v[0: 6] != "9P2000")
+ # return respond(x, t, "unrecognized 9P version");
+ # t.version = "9P2000";
+ # return respond(x, t, nil);
+}
+
+fsysauth(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ return respond(x, t, "acme: authentication not required");
+}
+
+fsysflush(x : ref Xfid) : ref Xfid
+{
+ x.c <-= Xfidm->Xflush;
+ return nil;
+}
+
+fsysattach(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+ id : int;
+ m : ref Mntdir;
+
+ if (uname(x.fcall) != user)
+ return respond(x, t, Eperm);
+ f.busy = TRUE;
+ f.open = FALSE;
+ f.qid = (Qid)(big Qdir, 0, QTDIR);
+ f.dir = dirtab;
+ f.nrpart = 0;
+ f.w = nil;
+ t.qid = f.qid;
+ f.mntdir = nil;
+ id = int aname(x.fcall);
+ mnt.qlock.lock();
+ for(m=mnt.md; m != nil; m=m.next)
+ if(m.id == id){
+ f.mntdir = m;
+ m.refs++;
+ break;
+ }
+ if(m == nil)
+ cerr <-= "unknown id in attach";
+ mnt.qlock.unlock();
+ return respond(x, t, nil);
+}
+
+fsyswalk(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+ c, i, j, id : int;
+ path, qtype : int;
+ d, dir : array of Dirtab;
+ w : ref Window;
+ nf : ref Fid;
+
+ if(f.open)
+ return respond(x, t, "walk of open file");
+ if(fid(x.fcall) != newfid(x.fcall)){
+ nf = allocfid(newfid(x.fcall));
+ if(nf.busy)
+ return respond(x, t, "newfid already in use");
+ nf.busy = TRUE;
+ nf.open = FALSE;
+ nf.mntdir = f.mntdir;
+ if(f.mntdir != nil)
+ f.mntdir.refs++;
+ nf.dir = f.dir;
+ nf.qid = f.qid;
+ nf.w = f.w;
+ nf.nrpart = 0; # not open, so must be zero
+ if(nf.w != nil)
+ nf.w.refx.inc();
+ f = nf; # walk f
+ }
+
+ qtype = QTFILE;
+ wqids: list of Qid;
+ err := string nil;
+ id = WIN(f.qid);
+ q := f.qid;
+ names := styxaux->names(x.fcall);
+ nwname := len names;
+
+ if(nwname > 0){
+ for(i = 0; i < nwname; i++){
+ if((q.qtype & QTDIR) == 0){
+ err = Enotdir;
+ break;
+ }
+
+ name := names[i];
+ if(name == ".."){
+ path = Qdir;
+ qtype = QTDIR;
+ id = 0;
+ if(w != nil){
+ w.close();
+ w = nil;
+ }
+ if(i == MAXWELEM){
+ err = "name too long";
+ break;
+ }
+ q.qtype = qtype;
+ q.vers = 0;
+ q.path = big QID(id, path);
+ wqids = q :: wqids;
+ continue;
+ }
+
+ # is it a numeric name?
+ regular := 0;
+ for(j=0; j < len name; j++) {
+ c = name[j];
+ if(c<'0' || '9'<c) {
+ regular = 1;
+ break;
+ }
+ }
+
+ if (!regular) {
+ # yes: it's a directory
+ if(w != nil) # name has form 27/23; get out before losing w
+ break;
+ id = int name;
+ row.qlock.lock();
+ w = lookid(id, FALSE);
+ if(w == nil){
+ row.qlock.unlock();
+ break;
+ }
+ w.refx.inc();
+ path = Qdir;
+ qtype = QTDIR;
+ row.qlock.unlock();
+ dir = dirtabw;
+ if(i == MAXWELEM){
+ err = "name too long";
+ break;
+ }
+ q.qtype = qtype;
+ q.vers = 0;
+ q.path = big QID(id, path);
+ wqids = q :: wqids;
+ continue;
+ }
+ else {
+ # if(FILE(f.qid) == Qacme) # empty directory
+ # break;
+ if(name == "new"){
+ if(w != nil)
+ error("w set in walk to new");
+ cw := chan of ref Window;
+ spawn x.walk(cw);
+ w = <- cw;
+ w.refx.inc();
+ path = QID(w.id, Qdir);
+ qtype = QTDIR;
+ id = w.id;
+ dir = dirtabw;
+ # x.c <-= Xfidm->Xwalk;
+ if(i == MAXWELEM){
+ err = "name too long";
+ break;
+ }
+ q.qtype = qtype;
+ q.vers = 0;
+ q.path = big QID(id, path);
+ wqids = q :: wqids;
+ continue;
+ }
+
+ if(id == 0)
+ d = dirtab;
+ else
+ d = dirtabw;
+ k := 1; # skip '.'
+ found := 0;
+ for( ; d[k].name != nil; k++){
+ if(name == d[k].name){
+ path = d[k].qid;
+ qtype = d[k].qtype;
+ dir = d[k:];
+ if(i == MAXWELEM){
+ err = "name too long";
+ break;
+ }
+ q.qtype = qtype;
+ q.vers = 0;
+ q.path = big QID(id, path);
+ wqids = q :: wqids;
+ found = 1;
+ break;
+ }
+ }
+ if(found)
+ continue;
+ break; # file not found
+ }
+ }
+
+ if(i == 0 && err == nil)
+ err = Eexist;
+ }
+
+ nwqid := len wqids;
+ if(nwqid > 0){
+ t.qids = array[nwqid] of Qid;
+ for(i = nwqid-1; i >= 0; i--){
+ t.qids[i] = hd wqids;
+ wqids = tl wqids;
+ }
+ }
+ if(err != nil || nwqid < nwname){
+ if(nf != nil){
+ nf.busy = FALSE;
+ fsysdelid(nf.mntdir);
+ }
+ }
+ else if(nwqid == nwname){
+ if(w != nil){
+ f.w = w;
+ w = nil;
+ }
+ if(dir != nil)
+ f.dir = dir;
+ f.qid = q;
+ }
+
+ if(w != nil)
+ w.close();
+
+ return respond(x, t, err);
+}
+
+fsysopen(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+ m : int;
+
+ # can't truncate anything, so just disregard
+ setmode(x.fcall, mode(x.fcall)&~OTRUNC);
+ # can't execute or remove anything
+ if(mode(x.fcall)&ORCLOSE)
+ return respond(x, t, Eperm);
+ case(mode(x.fcall)){
+ OREAD =>
+ m = 8r400;
+ OWRITE =>
+ m = 8r200;
+ ORDWR =>
+ m = 8r600;
+ * =>
+ return respond(x, t, Eperm);
+ }
+ if(((f.dir[0].perm&~(DMDIR|DMAPPEND))&m) != m)
+ return respond(x, t, Eperm);
+ x.c <-= Xfidm->Xopen;
+ return nil;
+}
+
+fsyscreate(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ return respond(x, t, Eperm);
+}
+
+idcmp(a, b : int) : int
+{
+ return a-b;
+}
+
+qsort(a : array of int, n : int)
+{
+ i, j : int;
+ t : int;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && idcmp(a[i], a[0]) < 0);
+ do
+ j--;
+ while(j > 0 && idcmp(a[j], a[0]) > 0);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
+
+fsysread(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+ b : array of byte;
+ i, id, n, o, e, j, k, nids : int;
+ ids : array of int;
+ d : array of Dirtab;
+ dt : Dirtab;
+ c : ref Column;
+ clock : int;
+
+ b = nil;
+ if(f.qid.qtype & QTDIR){
+ # if(int offset(x.fcall) % DIRLEN)
+ # return respond(x, t, "illegal offset in directory");
+ if(FILE(f.qid) == Qacme){ # empty dir
+ t.data = nil;
+ t.count = 0;
+ respond(x, t, nil);
+ return x;
+ }
+ o = int offset(x.fcall);
+ e = int offset(x.fcall)+count(x.fcall);
+ clock = getclock();
+ b = array[messagesize] of byte;
+ id = WIN(f.qid);
+ n = 0;
+ if(id > 0)
+ d = dirtabw;
+ else
+ d = dirtab;
+ k = 1; # first entry is '.'
+ leng := 0;
+ for(i=0; d[k].name!=nil && i<e; i+=leng){
+ bb := styx->packdir(dostat(WIN(x.f.qid), d[k], clock));
+ leng = len bb;
+ for (kk := 0; kk < leng; kk++)
+ b[kk+n] = bb[kk];
+ bb = nil;
+ if(leng <= Styx->BIT16SZ)
+ break;
+ if(i >= o)
+ n += leng;
+ k++;
+ }
+ if(id == 0){
+ row.qlock.lock();
+ nids = 0;
+ ids = nil;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(k=0; k<c.nw; k++){
+ oids := ids;
+ ids = array[nids+1] of int;
+ ids[0:] = oids[0:nids];
+ oids = nil;
+ ids[nids++] = c.w[k].id;
+ }
+ }
+ row.qlock.unlock();
+ qsort(ids, nids);
+ j = 0;
+ for(; j<nids && i<e; i+=leng){
+ k = ids[j];
+ dt.name = sys->sprint("%d", k);
+ dt.qid = QID(k, 0);
+ dt.qtype = QTDIR;
+ dt.perm = DMDIR|8r700;
+ bb := styx->packdir(dostat(k, dt, clock));
+ leng = len bb;
+ for (kk := 0; kk < leng; kk++)
+ b[kk+n] = bb[kk];
+ bb = nil;
+ if(leng == 0)
+ break;
+ if(i >= o)
+ n += leng;
+ j++;
+ }
+ ids = nil;
+ }
+ t.data = b;
+ t.count = n;
+ respond(x, t, nil);
+ b = nil;
+ return x;
+ }
+ x.c <-= Xfidm->Xread;
+ return nil;
+}
+
+fsyswrite(x : ref Xfid) : ref Xfid
+{
+ x.c <-= Xfidm->Xwrite;
+ return nil;
+}
+
+fsysclunk(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+
+ fsysdelid(f.mntdir);
+ if(f.open){
+ f.busy = FALSE;
+ f.open = FALSE;
+ x.c <-= Xfidm->Xclose;
+ return nil;
+ }
+ if(f.w != nil)
+ f.w.close();
+ f.busy = FALSE;
+ f.open = FALSE;
+ return respond(x, t, nil);
+}
+
+fsysremove(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ return respond(x, t, Eperm);
+}
+
+fsysstat(x : ref Xfid, f : ref Fid) : ref Xfid
+{
+ t : Smsg0;
+
+ t.stat = dostat(WIN(x.f.qid), f.dir[0], getclock());
+ return respond(x, t, nil);
+}
+
+fsyswstat(x : ref Xfid) : ref Xfid
+{
+ t : Smsg0;
+
+ return respond(x, t, Eperm);
+}
+
+allocfid(fid : int) : ref Fid
+{
+ f, ff : ref Fid;
+ fh : int;
+
+ ff = nil;
+ fh = fid&(Nhash-1);
+ for(f=fids[fh]; f != nil; f=f.next)
+ if(f.fid == fid)
+ return f;
+ else if(ff==nil && f.busy==FALSE)
+ ff = f;
+ if(ff != nil){
+ ff.fid = fid;
+ return ff;
+ }
+ f = ref Fid;
+ f.busy = FALSE;
+ f.rpart = array[Sys->UTFmax] of byte;
+ f.nrpart = 0;
+ f.fid = fid;
+ f.next = fids[fh];
+ fids[fh] = f;
+ return f;
+}
+
+cbuf := array[32] of byte;
+
+getclock() : int
+{
+ sys->seek(clockfd, big 0, 0);
+ n := sys->read(clockfd, cbuf, len cbuf);
+ return int string cbuf[0:n];
+}
+
+dostat(id : int, dir : Dirtab, clock : int) : Sys->Dir
+{
+ d : Dir;
+
+ d.qid.path = big QID(id, dir.qid);
+ d.qid.vers = 0;
+ d.qid.qtype = dir.qtype;
+ d.mode = dir.perm;
+ d.length = big 0; # would be nice to do better
+ d.name = dir.name;
+ d.uid = user;
+ d.gid = user;
+ d.atime = clock;
+ d.mtime = clock;
+ d.dtype = d.dev = 0;
+ return d;
+ # buf := styx->convD2M(d);
+ # d = nil;
+ # return buf;
+}
--- /dev/null
+++ b/appl/acme/fsys.m
@@ -1,0 +1,18 @@
+Fsys : module {
+ PATH : con "/dis/acme/fsys.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ messagesize: int;
+
+ QID : fn(w, f : int) : int;
+ FILE : fn(q : Sys->Qid) : int;
+ WIN : fn(q : Sys->Qid) : int;
+
+ fsysinit : fn();
+ fsyscfd : fn() : int;
+ fsysmount: fn(dir : string, ndir : int, incl : array of string, nincl : int) : ref Dat->Mntdir;
+ fsysdelid : fn(idm : ref Dat->Mntdir);
+ fsysclose: fn();
+ respond : fn(x : ref Xfidm->Xfid, t : Dat->Smsg0, err : string) : ref Xfidm->Xfid;
+};
--- /dev/null
+++ b/appl/acme/graph.b
@@ -1,0 +1,82 @@
+implement Graph;
+
+include "common.m";
+
+sys : Sys;
+drawm : Draw;
+dat : Dat;
+gui : Gui;
+utils : Utils;
+
+Image, Point, Rect, Font, Display : import drawm;
+black, white, display : import gui;
+error : import utils;
+
+refp : ref Point;
+pixarr : array of byte;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ drawm = mods.draw;
+ dat = mods.dat;
+ gui = mods.gui;
+ utils = mods.utils;
+
+ refp = ref Point;
+ refp.x = refp.y = 0;
+}
+
+charwidth(f : ref Font, c : int) : int
+{
+ s : string = "z";
+
+ s[0] = c;
+ return f.width(s);
+}
+
+strwidth(f : ref Font, s : string) : int
+{
+ return f.width(s);
+}
+
+balloc(r : Rect, c : Draw->Chans, col : int) : ref Image
+{
+ im := display.newimage(r, c, 0, col);
+ if (im == nil)
+ error("failed to get new image");
+ return im;
+}
+
+draw(d : ref Image, r : Rect, s : ref Image, m : ref Image, p : Point)
+{
+ d.draw(r, s, m, p);
+}
+
+stringx(d : ref Image, p : Point, f : ref Font, s : string, c : ref Image)
+{
+ d.text(p, c, (0, 0), f, s);
+}
+
+cursorset(p : Point)
+{
+ gui->cursorset(p);
+}
+
+cursorswitch(c : ref Dat->Cursor)
+{
+ gui->cursorswitch(c);
+}
+
+binit()
+{
+}
+
+bflush()
+{
+}
+
+berror(s : string)
+{
+ error(s);
+}
--- /dev/null
+++ b/appl/acme/graph.m
@@ -1,0 +1,18 @@
+Graph : module {
+ PATH : con "/dis/acme/graph.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ balloc : fn(r : Draw->Rect, c : Draw->Chans, col : int) : ref Draw->Image;
+ draw : fn(d : ref Draw->Image, r : Draw->Rect, s : ref Draw->Image, m : ref Draw->Image, p : Draw->Point);
+ stringx : fn(d : ref Draw->Image, p : Draw->Point, f : ref Draw->Font, s : string, c : ref Draw->Image);
+ cursorset: fn(p : Draw->Point);
+ cursorswitch : fn(c : ref Dat->Cursor);
+ charwidth : fn(f : ref Draw->Font, c : int) : int;
+ strwidth : fn(f : ref Draw->Font, p : string) : int;
+ binit : fn();
+ bflush : fn();
+ berror : fn(s : string);
+
+ font : ref Draw->Font;
+};
--- /dev/null
+++ b/appl/acme/gui.b
@@ -1,0 +1,126 @@
+implement Gui;
+
+include "common.m";
+include "tk.m";
+include "wmclient.m";
+ wmclient: Wmclient;
+
+sys : Sys;
+draw : Draw;
+acme : Acme;
+dat : Dat;
+utils : Utils;
+
+Font, Point, Rect, Image, Context, Screen, Display, Pointer : import draw;
+keyboardpid, mousepid : import acme;
+ckeyboard, cmouse : import dat;
+mousefd: ref Sys->FD;
+error : import utils;
+
+win: ref Wmclient->Window;
+
+r2s(r: Rect): string
+{
+ return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ draw = mods.draw;
+ acme = mods.acme;
+ dat = mods.dat;
+ utils = mods.utils;
+ wmclient = load Wmclient Wmclient->PATH;
+ if(wmclient == nil)
+ error(sys->sprint("cannot load %s: %r", Wmclient->PATH));
+ wmclient->init();
+
+ if(acme->acmectxt == nil)
+ acme->acmectxt = wmclient->makedrawcontext();
+ display = (acme->acmectxt).display;
+ win = wmclient->window(acme->acmectxt, "Acme", Wmclient->Appl);
+ wmclient->win.reshape(((0, 0), (win.displayr.size().div(2))));
+ cmouse = chan of ref Draw->Pointer;
+ ckeyboard = win.ctxt.kbd;
+ wmclient->win.onscreen("place");
+ wmclient->win.startinput("kbd"::"ptr"::nil);
+ mainwin = win.image;
+
+ yellow = display.color(Draw->Yellow);
+ green = display.color(Draw->Green);
+ red = display.color(Draw->Red);
+ blue = display.color(Draw->Blue);
+ black = display.color(Draw->Black);
+ white = display.color(Draw->White);
+}
+
+spawnprocs()
+{
+ spawn mouseproc();
+ spawn eventproc();
+}
+
+zpointer: Draw->Pointer;
+
+eventproc()
+{
+ for(;;) alt{
+ e := <-win.ctl or
+ e = <-win.ctxt.ctl =>
+ p := ref zpointer;
+ if(e == "exit"){
+ p.buttons = Acme->M_QUIT;
+ cmouse <-= p;
+ }else{
+ wmclient->win.wmctl(e);
+ if(win.image != mainwin){
+ mainwin = win.image;
+ p.buttons = Acme->M_RESIZE;
+ cmouse <-= p;
+ }
+ }
+ }
+}
+
+mouseproc()
+{
+ for(;;){
+ p := <-win.ctxt.ptr;
+ if(wmclient->win.pointer(*p) == 0){
+ p.buttons &= ~Acme->M_DOUBLE;
+ cmouse <-= p;
+ }
+ }
+}
+
+
+# consctlfd : ref Sys->FD;
+
+cursorset(p: Point)
+{
+ wmclient->win.wmctl("ptr " + string p.x + " " + string p.y);
+}
+
+cursorswitch(cur: ref Dat->Cursor)
+{
+ s: string;
+ if(cur == nil)
+ s = "cursor";
+ else{
+ Hex: con "0123456789abcdef";
+ s = sys->sprint("cursor %d %d %d %d ", cur.hot.x, cur.hot.y, cur.size.x, cur.size.y);
+ buf := cur.bits;
+ for(i := 0; i < len buf; i++){
+ c := int buf[i];
+ s[len s] = Hex[c >> 4];
+ s[len s] = Hex[c & 16rf];
+ }
+ }
+ wmclient->win.wmctl(s);
+}
+
+killwins()
+{
+ wmclient->win.wmctl("exit");
+}
--- /dev/null
+++ b/appl/acme/gui.m
@@ -1,0 +1,15 @@
+Gui: module {
+ PATH: con "/dis/acme/gui.dis";
+ WMPATH: con "/dis/acme/guiwm.dis";
+
+ display : ref Draw->Display;
+ mainwin : ref Draw->Image;
+ yellow, green, red, blue, black, white : ref Draw->Image;
+
+ init : fn(mods : ref Dat->Mods);
+ spawnprocs : fn();
+ cursorset : fn(p : Draw->Point);
+ cursorswitch: fn(c : ref Dat->Cursor);
+
+ killwins : fn();
+};
--- /dev/null
+++ b/appl/acme/look.b
@@ -1,0 +1,743 @@
+implement Look;
+
+include "common.m";
+
+sys : Sys;
+draw : Draw;
+utils : Utils;
+dat : Dat;
+graph : Graph;
+acme : Acme;
+framem : Framem;
+regx : Regx;
+bufferm : Bufferm;
+textm : Textm;
+windowm : Windowm;
+columnm : Columnm;
+exec : Exec;
+scrl : Scroll;
+plumbmsg : Plumbmsg;
+
+sprint : import sys;
+Point : import draw;
+warning, isalnum, stralloc, strfree, strchr, tgetc : import utils;
+Range, TRUE, FALSE, XXX, BUFSIZE, Astring : import Dat;
+Expand, seltext, row : import dat;
+cursorset : import graph;
+frptofchar : import framem;
+isaddrc, isregexc, address : import regx;
+Buffer : import bufferm;
+Text : import textm;
+Window : import windowm;
+Column : import columnm;
+Msg : import plumbmsg;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ draw = mods.draw;
+ utils = mods.utils;
+ graph = mods.graph;
+ acme = mods.acme;
+ framem = mods.framem;
+ regx = mods.regx;
+ dat = mods.dat;
+ bufferm = mods.bufferm;
+ textm = mods.textm;
+ windowm = mods.windowm;
+ columnm = mods.columnm;
+ exec = mods.exec;
+ scrl = mods.scroll;
+ plumbmsg = mods.plumbmsg;
+}
+
+nuntitled : int;
+
+look3(t : ref Text, q0 : int, q1 : int, external : int)
+{
+ n, c, f : int;
+ ct : ref Text;
+ e : Expand;
+ r : ref Astring;
+ expanded : int;
+
+ ct = seltext;
+ if(ct == nil)
+ seltext = t;
+ (expanded, e) = expand(t, q0, q1);
+ if(!external && t.w!=nil && t.w.nopen[Dat->QWevent]>byte 0){
+ if(!expanded)
+ return;
+ f = 0;
+ if((e.at!=nil && t.w!=nil) || (e.name!=nil && lookfile(e.name, len e.name)!=nil))
+ f = 1; # acme can do it without loading a file
+ if(q0!=e.q0 || q1!=e.q1)
+ f |= 2; # second (post-expand) message follows
+ if(e.name != nil)
+ f |= 4; # it's a file name
+ c = 'l';
+ if(t.what == Textm->Body)
+ c = 'L';
+ n = q1-q0;
+ if(n <= Dat->EVENTSIZE){
+ r = stralloc(n);
+ t.file.buf.read(q0, r, 0, n);
+ t.w.event(sprint("%c%d %d %d %d %s\n", c, q0, q1, f, n, r.s[0:n]));
+ strfree(r);
+ r = nil;
+ }else
+ t.w.event(sprint("%c%d %d %d 0 \n", c, q0, q1, f));
+ if(q0==e.q0 && q1==e.q1)
+ return;
+ if(e.name != nil){
+ n = len e.name;
+ if(e.a1 > e.a0)
+ n += 1+(e.a1-e.a0);
+ r = stralloc(n);
+ for (i := 0; i < len e.name; i++)
+ r.s[i] = e.name[i];
+ if(e.a1 > e.a0){
+ r.s[len e.name] = ':';
+ e.at.file.buf.read(e.a0, r, len e.name+1, e.a1-e.a0);
+ }
+ }else{
+ n = e.q1 - e.q0;
+ r = stralloc(n);
+ t.file.buf.read(e.q0, r, 0, n);
+ }
+ f &= ~2;
+ if(n <= Dat->EVENTSIZE)
+ t.w.event(sprint("%c%d %d %d %d %s\n", c, e.q0, e.q1, f, n, r.s[0:n]));
+ else
+ t.w.event(sprint("%c%d %d %d 0 \n", c, e.q0, e.q1, f));
+ strfree(r);
+ r = nil;
+ return;
+ }
+ if(0 && dat->plumbed){ # don't do yet : 2 acmes running => only 1 receives msg
+ m := ref Msg;
+ m.src = "acme";
+ m.dst = nil;
+ (dir, nil) := dirname(t, nil, 0);
+ if(dir == ".") # sigh
+ dir = nil;
+ if(dir == nil)
+ dir = acme->wdir;
+ m.dir = dir;
+ m.kind = "text";
+ m.attr = nil;
+ if(q1 == q0){
+ if(t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){
+ q0 = t.q0;
+ q1 = t.q1;
+ }else{
+ p := q0;
+ while(q0 > 0 && (c = tgetc(t, q0-1)) != ' ' && c != '\t' && c != '\n')
+ q0--;
+ while(q1 < t.file.buf.nc && (c = tgetc(t, q1)) != ' ' && c != '\t' && c != '\n')
+ q1++;
+ if(q1 == q0)
+ return;
+ m.attr = "click=" + string (p-q0);
+ }
+ }
+ r = stralloc(q1-q0);
+ t.file.buf.read(q0, r, 0, q1-q0);
+ m.data = array of byte r.s;
+ strfree(r);
+ if(m.send() >= 0)
+ return;
+ # plumber failed to match : fall through
+ }
+ if(!expanded)
+ return;
+ if(e.name != nil || e.at != nil)
+ (nil, e) = openfile(t, e);
+ else{
+ if(t.w == nil)
+ return;
+ ct = t.w.body;
+ if(t.w != ct.w)
+ ct.w.lock('M');
+ if(t == ct)
+ ct.setselect(e.q1, e.q1);
+ n = e.q1 - e.q0;
+ r = stralloc(n);
+ t.file.buf.read(e.q0, r, 0, n);
+ if(search(ct, r.s, n) && e.jump)
+ cursorset(frptofchar(ct.frame, ct.frame.p0).add((4, ct.frame.font.height-4)));
+ if(t.w != ct.w)
+ ct.w.unlock();
+ strfree(r);
+ r = nil;
+ }
+ e.name = nil;
+ e.bname = nil;
+}
+
+plumblook(m : ref Msg)
+{
+ e : Expand;
+
+ if (len m.data > Dat->PLUMBSIZE) {
+ warning(nil, sys->sprint("plumb message too long : %s\n", string m.data));
+ return;
+ }
+ e.q0 = e.q1 = 0;
+ if (len m.data == 0)
+ return;
+ e.ar = nil;
+ e.name = string m.data;
+ if(e.name[0] != '/' && m.dir != nil)
+ e.name = m.dir + "/" + e.name;
+ (e.name, nil) = cleanname(e.name, len e.name);
+ e.bname = e.name;
+ e.jump = TRUE;
+ e.a0 = e.a1 = 0;
+ (found, addr) := plumbmsg->lookup(plumbmsg->string2attrs(m.attr), "addr");
+ if (found && addr != nil) {
+ e.ar = addr;
+ e.a1 = len addr;
+ }
+ openfile(nil, e);
+ e.at = nil;
+}
+
+plumbshow(m : ref Msg)
+{
+ w := utils->newwindow(nil);
+ (found, name) := plumbmsg->lookup(plumbmsg->string2attrs(m.attr), "filename");
+ if (!found || name == nil) {
+ nuntitled++;
+ name = "Untitled-" + string nuntitled;
+ }
+ if (name[0] != '/' && m.dir != nil)
+ name = m.dir + "/" + name;
+ (name, nil) = cleanname(name, len name);
+ w.setname(name, len name);
+ d := string m.data;
+ w.body.insert(0, d, len d, TRUE, FALSE);
+ w.body.file.mod = FALSE;
+ w.dirty = FALSE;
+ w.settag();
+ scrl->scrdraw(w.body);
+ w.tag.setselect(w.tag.file.buf.nc, w.tag.file.buf.nc);
+}
+
+search(ct : ref Text, r : string, n : int) : int
+{
+ q, nb, maxn : int;
+ around : int;
+ s : ref Astring;
+ b, c : int;
+
+ if(n==0 || n>ct.file.buf.nc)
+ return FALSE;
+ if(2*n > BUFSIZE){
+ warning(nil, "string too long\n");
+ return FALSE;
+ }
+ maxn = utils->max(2*n, BUFSIZE);
+ s = utils->stralloc(BUFSIZE);
+ b = nb = 0;
+ around = 0;
+ q = ct.q1;
+ for(;;){
+ if(q >= ct.file.buf.nc){
+ q = 0;
+ around = 1;
+ nb = 0;
+ }
+ if(nb > 0){
+ for (c = 0; c < nb; c++)
+ if (s.s[b+c] == r[0])
+ break;
+ if(c >= nb){
+ q += nb;
+ nb = 0;
+ if(around && q>=ct.q1)
+ break;
+ continue;
+ }
+ q += c;
+ nb -= c;
+ b += c;
+ }
+ # reload if buffer covers neither string nor rest of file
+ if(nb<n && nb!=ct.file.buf.nc-q){
+ nb = ct.file.buf.nc-q;
+ if(nb >= maxn)
+ nb = maxn-1;
+ ct.file.buf.read(q, s, 0, nb);
+ b = 0;
+ }
+ if(n <= nb && s.s[b:b+n] == r[0:n]){
+ if(ct.w != nil){
+ ct.show(q, q+n, TRUE);
+ ct.w.settag();
+ }else{
+ ct.q0 = q;
+ ct.q1 = q+n;
+ }
+ seltext = ct;
+ utils->strfree(s);
+ s = nil;
+ return TRUE;
+ }
+ if(around && q>=ct.q1)
+ break;
+ --nb;
+ b++;
+ q++;
+ }
+ utils->strfree(s);
+ s = nil;
+ return FALSE;
+}
+
+isfilec(r : int) : int
+{
+ if(isalnum(r))
+ return TRUE;
+ if(strchr(".-+/:", r) >= 0)
+ return TRUE;
+ return FALSE;
+}
+
+cleanname(b : string, n : int) : (string, int)
+{
+ i, j, found : int;
+
+ b = b[0:n];
+ # compress multiple slashes
+ for(i=0; i<n-1; i++)
+ if(b[i]=='/' && b[i+1]=='/'){
+ b = b[0:i] + b[i+1:];
+ --n;
+ --i;
+ }
+ # eliminate ./
+ for(i=0; i<n-1; i++)
+ if(b[i]=='.' && b[i+1]=='/' && (i==0 || b[i-1]=='/')){
+ b = b[0:i] + b[i+2:];
+ n -= 2;
+ --i;
+ }
+ # eliminate trailing .
+ if(n>=2 && b[n-2]=='/' && b[n-1]=='.') {
+ --n;
+ b = b[0:n];
+ }
+ do{
+ # compress xx/..
+ found = FALSE;
+ for(i=1; i<=n-3; i++)
+ if(b[i:i+3] == "/.."){
+ if(i==n-3 || b[i+3]=='/'){
+ found = TRUE;
+ break;
+ }
+ }
+ if(found)
+ for(j=i-1; j>=0; --j)
+ if(j==0 || b[j-1]=='/'){
+ i += 3; # character beyond ..
+ if(i<n && b[i]=='/')
+ ++i;
+ b = b[0:j] + b[i:];
+ n -= (i-j);
+ break;
+ }
+ }while(found);
+ if(n == 0){
+ b = ".";
+ n = 1;
+ }
+ return (b, n);
+}
+
+includefile(dir : string, file : string, nfile : int) : (string, int)
+{
+ m, n : int;
+ a : string;
+
+ if (dir == ".") {
+ m = 0;
+ a = file;
+ }
+ else {
+ m = 1 + len dir;
+ a = dir + "/" + file;
+ }
+ n = utils->access(a);
+ if(n < 0) {
+ a = nil;
+ return (nil, 0);
+ }
+ file = nil;
+ return cleanname(a, m+nfile);
+}
+
+objdir : string;
+
+includename(t : ref Text , r : string, n : int) : (string, int)
+{
+ file : string;
+ i, nfile : int;
+ w : ref Window;
+
+ {
+ w = t.w;
+ if(n==0 || r[0]=='/' || w==nil)
+ raise "e";
+ if(n>2 && r[0]=='.' && r[1]=='/')
+ raise "e";
+ file = nil;
+ nfile = 0;
+ (file, nfile) = includefile(".", r, n);
+ if (file == nil) {
+ (dr, dn) := dirname(t, r, n);
+ (file, nfile) = includefile(".", dr, dn);
+ }
+ if (file == nil) {
+ for(i=0; i<w.nincl && file==nil; i++)
+ (file, nfile) = includefile(w.incl[i], r, n);
+ }
+ if(file == nil)
+ (file, nfile) = includefile("/module", r, n);
+ if(file == nil)
+ (file, nfile) = includefile("/include", r, n);
+ if(file==nil && objdir!=nil)
+ (file, nfile) = includefile(objdir, r, n);
+ if(file == nil)
+ raise "e";
+ return (file, nfile);
+ }
+ exception{
+ * =>
+ return (r, n);
+ }
+ return (nil, 0);
+}
+
+dirname(t : ref Text, r : string, n : int) : (string, int)
+{
+ b : ref Astring;
+ c : int;
+ m, nt : int;
+ slash : int;
+
+ {
+ b = nil;
+ if(t == nil || t.w == nil)
+ raise "e";
+ nt = t.w.tag.file.buf.nc;
+ if(nt == 0)
+ raise "e";
+ if(n>=1 && r[0]=='/')
+ raise "e";
+ b = stralloc(nt+n+1);
+ t.w.tag.file.buf.read(0, b, 0, nt);
+ slash = -1;
+ for(m=0; m<nt; m++){
+ c = b.s[m];
+ if(c == '/')
+ slash = m;
+ if(c==' ' || c=='\t')
+ break;
+ }
+ if(slash < 0)
+ raise "e";
+ for (i := 0; i < n; i++)
+ b.s[slash+1+i] = r[i];
+ r = nil;
+ return cleanname(b.s, slash+1+n);
+ }
+ exception{
+ * =>
+ b = nil;
+ if(r != nil)
+ return cleanname(r, n);
+ return (r, n);
+ }
+ return (nil, 0);
+}
+
+expandfile(t : ref Text, q0 : int, q1 : int, e : Expand) : (int, Expand)
+{
+ i, n, nname, colon : int;
+ amin, amax : int;
+ r : ref Astring;
+ c : int;
+ w : ref Window;
+
+ amax = q1;
+ if(q1 == q0){
+ colon = -1;
+ while(q1<t.file.buf.nc && isfilec(c=t.readc(q1))){
+ if(c == ':'){
+ colon = q1;
+ break;
+ }
+ q1++;
+ }
+ while(q0>0 && (isfilec(c=t.readc(q0-1)) || isaddrc(c) || isregexc(c))){
+ q0--;
+ if(colon==-1 && c==':')
+ colon = q0;
+ }
+ #
+ # if it looks like it might begin file: , consume address chars after :
+ # otherwise terminate expansion at :
+ #
+
+ if(colon>=0 && colon<t.file.buf.nc-1 && isaddrc(t.readc(colon+1))){
+ q1 = colon+1;
+ while(q1<t.file.buf.nc-1 && isaddrc(t.readc(q1)))
+ q1++;
+ }else if(colon >= 0)
+ q1 = colon;
+ if(q1 > q0)
+ if(colon >= 0){ # stop at white space
+ for(amax=colon+1; amax<t.file.buf.nc; amax++)
+ if((c=t.readc(amax))==' ' || c=='\t' || c=='\n')
+ break;
+ }else
+ amax = t.file.buf.nc;
+ }
+ amin = amax;
+ e.q0 = q0;
+ e.q1 = q1;
+ n = q1-q0;
+ if(n == 0)
+ return (FALSE, e);
+ # see if it's a file name
+ r = stralloc(n);
+ t.file.buf.read(q0, r, 0, n);
+ # first, does it have bad chars?
+ nname = -1;
+ for(i=0; i<n; i++){
+ c = r.s[i];
+ if(c==':' && nname<0){
+ if(q0+i+1<t.file.buf.nc && (i==n-1 || isaddrc(t.readc(q0+i+1))))
+ amin = q0+i;
+ else {
+ strfree(r);
+ r = nil;
+ return (FALSE, e);
+ }
+ nname = i;
+ }
+ }
+ if(nname == -1)
+ nname = n;
+ for(i=0; i<nname; i++)
+ if(!isfilec(r.s[i])) {
+ strfree(r);
+ r = nil;
+ return (FALSE, e);
+ }
+ #
+ # See if it's a file name in <>, and turn that into an include
+ # file name if so. Should probably do it for "" too, but that's not
+ # restrictive enough syntax and checking for a #include earlier on the
+ # line would be silly.
+ #
+
+ isfile := 0;
+ if(q0>0 && t.readc(q0-1)=='<' && q1<t.file.buf.nc && t.readc(q1)=='>')
+ (r.s, nname) = includename(t, r.s, nname);
+ else if(q0>0 && t.readc(q0-1)=='"' && q1<t.file.buf.nc && t.readc(q1)=='"')
+ (r.s, nname) = includename(t, r.s, nname);
+ else if(amin == q0)
+ isfile = 1;
+ else
+ (r.s, nname) = dirname(t, r.s, nname);
+ if (!isfile) {
+ e.bname = r.s;
+ # if it's already a window name, it's a file
+ w = lookfile(r.s, nname);
+ # if it's the name of a file, it's a file
+ if(w == nil && utils->access(e.bname) < 0){
+ e.bname = nil;
+ strfree(r);
+ r = nil;
+ return (FALSE, e);
+ }
+ }
+
+ e.name = r.s[0:nname];
+ e.at = t;
+ e.a0 = amin+1;
+ (nil, e.a1, nil) = address(nil, nil, (Range)(-1,-1), (Range)(0, 0), t, nil, e.a0, amax, FALSE);
+ strfree(r);
+ r = nil;
+ return (TRUE, e);
+}
+
+expand(t : ref Text, q0 : int, q1 : int) : (int, Expand)
+{
+ e : Expand;
+ ok : int;
+
+ e.q0 = e.q1 = e.a0 = e.a1 = 0;
+ e.name = e.bname = nil;
+ e.at = nil;
+ # if in selection, choose selection
+ e.jump = TRUE;
+ if(q1==q0 && t.q1>t.q0 && t.q0<=q0 && q0<=t.q1){
+ q0 = t.q0;
+ q1 = t.q1;
+ if(t.what == Textm->Tag)
+ e.jump = FALSE;
+ }
+
+ (ok, e) = expandfile(t, q0, q1, e);
+ if (ok)
+ return (TRUE, e);
+
+ if(q0 == q1){
+ while(q1<t.file.buf.nc && isalnum(t.readc(q1)))
+ q1++;
+ while(q0>0 && isalnum(t.readc(q0-1)))
+ q0--;
+ }
+ e.q0 = q0;
+ e.q1 = q1;
+ return (q1 > q0, e);
+}
+
+lookfile(s : string, n : int) : ref Window
+{
+ i, j, k : int;
+ w : ref Window;
+ c : ref Column;
+ t : ref Text;
+
+ # avoid terminal slash on directories
+ if(n > 1 && s[n-1] == '/')
+ --n;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ t = w.body;
+ k = len t.file.name;
+ if(k>0 && t.file.name[k-1] == '/')
+ k--;
+ if(t.file.name[0:k] == s[0:n]){
+ w = w.body.file.curtext.w;
+ if(w.col != nil) # protect against race deleting w
+ return w;
+ }
+ }
+ }
+ return nil;
+}
+
+lookid(id : int, dump : int) : ref Window
+{
+ i, j : int;
+ w : ref Window;
+ c : ref Column;
+
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ if(dump && w.dumpid == id)
+ return w;
+ if(!dump && w.id == id)
+ return w;
+ }
+ }
+ return nil;
+}
+
+openfile(t : ref Text, e : Expand) : (ref Window, Expand)
+{
+ r : Range;
+ w, ow : ref Window;
+ eval, i, n : int;
+
+ if(e.name == nil){
+ w = t.w;
+ if(w == nil)
+ return (nil, e);
+ }else
+ w = lookfile(e.name, len e.name);
+ if(w != nil){
+ t = w.body;
+ if(!t.col.safe && t.frame.maxlines==0) # window is obscured by full-column window
+ t.col.grow(t.col.w[0], 1, 1);
+ }
+ else{
+ ow = nil;
+ if(t != nil)
+ ow = t.w;
+ w = utils->newwindow(t);
+ t = w.body;
+ w.setname(e.name, len e.name);
+ t.loadx(0, e.bname, 1);
+ t.file.mod = FALSE;
+ t.w.dirty = FALSE;
+ t.w.settag();
+ t.w.tag.setselect(t.w.tag.file.buf.nc, t.w.tag.file.buf.nc);
+ if(ow != nil)
+ for(i=ow.nincl; --i>=0; ){
+ n = len ow.incl[i];
+ w.addincl(ow.incl[i], n); # really do want to copy here
+ }
+ }
+ if(e.a1 == e.a0)
+ eval = FALSE;
+ else
+ (eval, nil, r) = address(nil, t, (Range)(-1, -1), (Range)(t.q0, t.q1), e.at, e.ar, e.a0, e.a1, TRUE);
+ # was (eval, nil, r) = address(nil, t, (Range)(-1, -1), (Range)(t.q0, t.q1), e.at, nil, e.a0, e.a1, TRUE);
+ if(eval == FALSE){
+ r.q0 = t.q0;
+ r.q1 = t.q1;
+ }
+ t.show(r.q0, r.q1, TRUE);
+ t.w.settag();
+ seltext = t;
+ if(e.jump)
+ cursorset(frptofchar(t.frame, t.frame.p0).add((4, t.frame.font.height-4)));
+ return (w, e);
+}
+
+new(et : ref Text, t : ref Text, argt : ref Text, flag1 : int, flag2 : int, arg : string, narg : int)
+{
+ ndone : int;
+ a, f : string;
+ na, nf : int;
+ e : Expand;
+
+ (nil, a, na) = exec->getarg(argt, FALSE, TRUE);
+ if(a != nil){
+ new(et, t, nil, flag1, flag2, a, na);
+ if(narg == 0)
+ return;
+ }
+ # loop condition: *arg is not a blank
+ for(ndone=0; ; ndone++){
+ (a, na) = utils->findbl(arg, narg);
+ if(a == arg){
+ if(ndone==0 && et.col!=nil)
+ et.col.add(nil, nil, -1).settag();
+ break;
+ }
+ nf = narg-na;
+ f = arg[0:nf]; # want a copy
+ (f, nf) = dirname(et, f, nf);
+ e.q0 = e.q1 = e.a0 = e.a1 = 0;
+ e.at = nil;
+ e.name = f;
+ e.bname = f;
+ e.jump = TRUE;
+ (nil, e) = openfile(et, e);
+ f = nil;
+ e.bname = nil;
+ (arg, narg) = utils->skipbl(a, na);
+ }
+}
--- /dev/null
+++ b/appl/acme/look.m
@@ -1,0 +1,17 @@
+Look : module {
+ PATH : con "/dis/acme/look.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ isfilec: fn(r : int) : int;
+ lookid : fn(n : int, b : int) : ref Windowm->Window;
+ lookfile : fn(s : string, n : int) : ref Windowm->Window;
+ dirname : fn(t : ref Textm->Text, r : string, n : int) : (string, int);
+ cleanname : fn(s : string, n : int) : (string, int);
+ new : fn(et, t, argt : ref Textm->Text, flag1, flag2 : int, arg : string, narg : int);
+ expand : fn(t : ref Textm->Text, q0, q1 : int) : (int, Dat->Expand);
+ search : fn(t : ref Textm->Text, r : string, n : int) : int;
+ look3 : fn(t : ref Textm->Text, q0, q1, external : int);
+ plumblook : fn(m : ref Plumbmsg->Msg);
+ plumbshow : fn(m : ref Plumbmsg->Msg);
+};
--- /dev/null
+++ b/appl/acme/mkfile
@@ -1,0 +1,90 @@
+<../../mkconfig
+
+DIRS=\
+ acme\
+
+TARG=\
+ acme.dis\
+ dat.dis\
+ buff.dis\
+ col.dis\
+ disk.dis\
+ exec.dis\
+ file.dis\
+ fsys.dis\
+ look.dis\
+ regx.dis\
+ row.dis\
+ scrl.dis\
+ text.dis\
+ time.dis\
+ util.dis\
+ wind.dis\
+ graph.dis\
+ xfid.dis\
+ gui.dis\
+ frame.dis\
+ edit.dis\
+ ecmd.dis\
+ elog.dis\
+ styxaux.dis\
+
+ICONS=\
+ abcde.bit\
+
+MODULES=\
+ acme.m\
+ buff.m\
+ col.m\
+ disk.m\
+ exec.m\
+ file.m\
+ fsys.m\
+ look.m\
+ regx.m\
+ row.m\
+ scrl.m\
+ text.m\
+ time.m\
+ util.m\
+ wind.m\
+ xfid.m\
+ common.m\
+ graph.m\
+ gui.m\
+ frame.m\
+ dat.m\
+ edit.m\
+ elog.m\
+ ecmd.m\
+ styxaux.m\
+
+SYSMODULES=\
+ bufio.m\
+ daytime.m\
+ debug.m\
+ draw.m\
+ sh.m\
+ string.m\
+ styx.m\
+ sys.m\
+ tk.m\
+ workdir.m\
+ wmclient.m\
+
+DISBIN=$ROOT/dis/acme
+
+all:V: acme.dis
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
+
+install:V: $ROOT/dis/acme.dis
+
+$ROOT/dis/acme.dis: acme.dis
+ rm -f $target && cp acme.dis $target
+
+acme.dis: $MODULES $SYS_MODULES
+
+nuke:V:
+ rm -f $ROOT/dis/acme.dis
--- /dev/null
+++ b/appl/acme/regx.b
@@ -1,0 +1,1050 @@
+implement Regx;
+
+include "common.m";
+
+sys : Sys;
+utils : Utils;
+textm : Textm;
+
+FALSE, TRUE, XXX : import Dat;
+NRange : import Dat;
+Range, Rangeset : import Dat;
+error, warning, tgetc, rgetc : import utils;
+Text : import textm;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ utils = mods.utils;
+ textm = mods.textm;
+}
+
+None : con 0;
+Fore : con '+';
+Back : con '-';
+
+Char : con 0;
+Line : con 1;
+
+isaddrc(r : int) : int
+{
+ if (utils->strchr("0123456789+-/$.#", r) >= 0)
+ return TRUE;
+ return FALSE;
+}
+
+#
+# quite hard: could be almost anything but white space, but we are a little conservative,
+# aiming for regular expressions of alphanumerics and no white space
+#
+isregexc(r : int) : int
+{
+ if(r == 0)
+ return FALSE;
+ if(utils->isalnum(r))
+ return TRUE;
+ if(utils->strchr("^+-.*?#,;[]()$", r)>=0)
+ return TRUE;
+ return FALSE;
+}
+
+number(md: ref Dat->Mntdir, t : ref Text, r : Range, line : int, dir : int, size : int) : (int, Range)
+{
+ q0, q1 : int;
+
+ {
+ if(size == Char){
+ if(dir == Fore)
+ line = r.q1+line; # was t.file.buf.nc+line;
+ else if(dir == Back){
+ if(r.q0==0 && line > 0)
+ r.q0 = t.file.buf.nc;
+ line = r.q0-line; # was t.file.buf.nc - line;
+ }
+ if(line<0 || line>t.file.buf.nc)
+ raise "e";
+ return (TRUE, (line, line));
+ }
+ (q0, q1) = r;
+ case(dir){
+ None =>
+ q0 = 0;
+ q1 = 0;
+ while(line>0 && q1<t.file.buf.nc)
+ if(t.readc(q1++) == '\n')
+ if(--line > 0)
+ q0 = q1;
+ if(line==1 && t.readc(q1-1)!='\n') # no newline at end - count it
+ ;
+ else if(line > 0)
+ raise "e";
+ Fore =>
+ if(q1 > 0)
+ while(t.readc(q1-1) != '\n')
+ q1++;
+ q0 = q1;
+ while(line>0 && q1<t.file.buf.nc)
+ if(t.readc(q1++) == '\n')
+ if(--line > 0)
+ q0 = q1;
+ if(line > 0)
+ raise "e";
+ Back =>
+ if(q0 < t.file.buf.nc)
+ while(q0>0 && t.readc(q0-1)!='\n')
+ q0--;
+ q1 = q0;
+ while(line>0 && q0>0){
+ if(t.readc(q0-1) == '\n'){
+ if(--line >= 0)
+ q1 = q0;
+ }
+ --q0;
+ }
+ if(line > 0)
+ raise "e";
+ while(q0>0 && t.readc(q0-1)!='\n')
+ --q0;
+ }
+ return (TRUE, (q0, q1));
+ }
+ exception{
+ * =>
+ if(md != nil)
+ warning(nil, "address out of range\n");
+ return (FALSE, r);
+ }
+ return (FALSE, r);
+}
+
+regexp(md: ref Dat->Mntdir, t : ref Text, lim : Range, r : Range, pat : string, dir : int) : (int, Range)
+{
+ found : int;
+ sel : Rangeset;
+ q : int;
+
+ if(pat == nil && rxnull()){
+ warning(md, "no previous regular expression");
+ return (FALSE, r);
+ }
+ if(pat == nil || !rxcompile(pat))
+ return (FALSE, r);
+ if(dir == Back)
+ (found, sel) = rxbexecute(t, r.q0);
+ else{
+ if(lim.q0 < 0)
+ q = Dat->Infinity;
+ else
+ q = lim.q1;
+ (found, sel) = rxexecute(t, nil, r.q1, q);
+ }
+ if(!found && md == nil)
+ warning(nil, "no match for regexp\n");
+ return (found, sel[0]);
+}
+
+xgetc(a0 : ref Text, a1 : string, n : int) : int
+{
+ if (a0 == nil)
+ return rgetc(a1, n);
+ return tgetc(a0, n);
+}
+
+address(md: ref Dat->Mntdir, t : ref Text, lim : Range, ar : Range, a0 : ref Text, a1 : string, q0 : int, q1 : int, eval : int) : (int, int, Range)
+{
+ dir, size : int;
+ prevc, c, n : int;
+ q : int;
+ pat : string;
+ r, nr : Range;
+
+ r = ar;
+ q = q0;
+ dir = None;
+ size = Line;
+ c = 0;
+ while(q < q1){
+ prevc = c;
+ c = xgetc(a0, a1, q++);
+ case(c){
+ ';' =>
+ ar = r;
+ if(prevc == 0) # lhs defaults to 0
+ r.q0 = 0;
+ if(q>=q1 && t!=nil && t.file!=nil) # rhs defaults to $
+ r.q1 = t.file.buf.nc;
+ else{
+ (eval, q, nr) = address(md, t, lim, ar, a0, a1, q, q1, eval);
+ r.q1 = nr.q1;
+ }
+ return (eval, q, r);
+ ',' =>
+ if(prevc == 0) # lhs defaults to 0
+ r.q0 = 0;
+ if(q>=q1 && t!=nil && t.file!=nil) # rhs defaults to $
+ r.q1 = t.file.buf.nc;
+ else{
+ (eval, q, nr) = address(md, t, lim, ar, a0, a1, q, q1, eval);
+ r.q1 = nr.q1;
+ }
+ return (eval, q, r);
+ '+' or '-' =>
+ if(eval && (prevc=='+' || prevc=='-')){
+ if((nc := xgetc(a0, a1, q)) != '#' && nc != '/' && nc != '?')
+ (eval, r) = number(md, t, r, 1, prevc, Line); # do previous one
+ }
+ dir = c;
+ '.' or '$' =>
+ if(q != q0+1)
+ return (eval, q-1, r);
+ if(eval)
+ if(c == '.')
+ r = ar;
+ else
+ r = (t.file.buf.nc, t.file.buf.nc);
+ if(q < q1)
+ dir = Fore;
+ else
+ dir = None;
+ '#' =>
+ if(q==q1 || (c=xgetc(a0, a1, q++))<'0' || '9'<c)
+ return (eval, q-1, r);
+ size = Char;
+ n = c -'0';
+ while(q<q1){
+ c = xgetc(a0, a1, q++);
+ if(c<'0' || '9'<c){
+ q--;
+ break;
+ }
+ n = n*10+(c-'0');
+ }
+ if(eval)
+ (eval, r) = number(md, t, r, n, dir, size);
+ dir = None;
+ size = Line;
+ '0' to '9' =>
+ n = c -'0';
+ while(q<q1){
+ c = xgetc(a0, a1, q++);
+ if(c<'0' || '9'<c){
+ q--;
+ break;
+ }
+ n = n*10+(c-'0');
+ }
+ if(eval)
+ (eval, r) = number(md, t, r, n, dir, size);
+ dir = None;
+ size = Line;
+ '/' =>
+ pat = nil;
+ break2 := 0; # Ow !
+ while(q<q1){
+ c = xgetc(a0, a1, q++);
+ case(c){
+ '\n' =>
+ --q;
+ break2 = 1;
+ '\\' =>
+ pat[len pat] = c;
+ if(q == q1)
+ break2 = 1;
+ else
+ c = xgetc(a0, a1, q++);
+ '/' =>
+ break2 = 1;
+ }
+ if (break2)
+ break;
+ pat[len pat] = c;
+ }
+ if(eval)
+ (eval, r) = regexp(md, t, lim, r, pat, dir);
+ pat = nil;
+ dir = None;
+ size = Line;
+ * =>
+ return (eval, q-1, r);
+ }
+ }
+ if(eval && dir != None)
+ (eval, r) = number(md, t, r, 1, dir, Line); # do previous one
+ return (eval, q, r);
+}
+
+sel : Rangeset = array[NRange] of Range;
+lastregexp : string;
+
+# Machine Information
+
+Inst : adt {
+ typex : int; # < 16r10000 ==> literal, otherwise action
+ # sid : int;
+ subid : int;
+ class : int;
+ # other : cyclic ref Inst;
+ right : cyclic ref Inst;
+ # left : cyclic ref Inst;
+ next : cyclic ref Inst;
+};
+
+NPROG : con 1024;
+program := array[NPROG] of ref Inst;
+progp : int;
+startinst : ref Inst; # First inst. of program; might not be program[0]
+bstartinst : ref Inst; # same for backwards machine
+
+Ilist : adt {
+ inst : ref Inst; # Instruction of the thread
+ se : Rangeset;
+ startp : int; # first char of match
+};
+
+NLIST : con 128;
+
+thl, nl : array of Ilist; # This list, next list
+listx := array[2] of array of Ilist;
+sempty : Rangeset = array[NRange] of Range;
+
+#
+# Actions and Tokens
+#
+# 0x100xx are operators, value == precedence
+# 0x200xx are tokens, i.e. operands for operators
+#
+
+OPERATOR : con 16r10000; # Bitmask of all operators
+START : con 16r10000; # Start, used for marker on stack
+RBRA : con 16r10001; # Right bracket, )
+LBRA : con 16r10002; # Left bracket, (
+OR : con 16r10003; # Alternation, |
+CAT : con 16r10004; # Concatentation, implicit operator
+STAR : con 16r10005; # Closure, *
+PLUS : con 16r10006; # a+ == aa*
+QUEST : con 16r10007; # a? == a|nothing, i.e. 0 or 1 a's
+ANY : con 16r20000; # Any character but newline, .
+NOP : con 16r20001; # No operation, internal use only
+BOL : con 16r20002; # Beginning of line, ^
+EOL : con 16r20003; # End of line, $
+CCLASS : con 16r20004; # Character class, []
+NCCLASS : con 16r20005; # Negated character class, [^]
+END : con 16r20077; # Terminate: match found
+
+ISATOR : con 16r10000;
+ISAND : con 16r20000;
+
+# Parser Information
+
+Node : adt {
+ first : ref Inst;
+ last : ref Inst;
+};
+
+NSTACK : con 20;
+andstack := array[NSTACK] of ref Node;
+andp : int;
+atorstack := array[NSTACK] of int;
+atorp : int;
+lastwasand : int; # Last token was operand
+cursubid : int;
+subidstack := array[NSTACK] of int;
+subidp : int;
+backwards : int;
+nbra : int;
+exprs : string;
+exprp : int; # pointer to next character in source expression
+DCLASS : con 10; # allocation increment
+nclass : int; # number active
+Nclass : int = 0; # high water mark
+class : array of string;
+negateclass : int;
+
+nilnode : Node;
+nilinst : Inst;
+
+rxinit()
+{
+ lastregexp = nil;
+ for (k := 0; k < NPROG; k++)
+ program[k] = ref nilinst;
+ for (k = 0; k < NSTACK; k++)
+ andstack[k] = ref nilnode;
+ for (k = 0; k < 2; k++) {
+ listx[k] = array[NLIST] of Ilist;
+ for (i := 0; i < NLIST; i++) {
+ listx[k][i].inst = nil;
+ listx[k][i].startp = 0;
+ listx[k][i].se = array[NRange] of Range;
+ for (j := 0; j < NRange; j++)
+ listx[k][i].se[j].q0 = listx[k][i].se[j].q1 = 0;
+ }
+ }
+}
+
+regerror(e : string)
+{
+ lastregexp = nil;
+ buf := sys->sprint("regexp: %s\n", e);
+ warning(nil, buf);
+ raise "regerror";
+}
+
+newinst(t : int) : ref Inst
+{
+ if(progp >= NPROG)
+ regerror("expression too long");
+ program[progp].typex = t;
+ program[progp].next = nil; # next was left
+ program[progp].right = nil;
+ return program[progp++];
+}
+
+realcompile(s : string) : ref Inst
+{
+ token : int;
+
+ {
+ startlex(s);
+ atorp = 0;
+ andp = 0;
+ subidp = 0;
+ 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("unmatched `('");
+ --andp; # points to first and only operand
+ return andstack[andp].first;
+ }
+ exception{
+ "regerror" =>
+ return nil;
+ }
+ return nil;
+}
+
+rxcompile(r : string) : int
+{
+ oprogp : int;
+
+ if(lastregexp == r)
+ return TRUE;
+ lastregexp = nil;
+ for (i := 0; i < nclass; i++)
+ class[i] = nil;
+ nclass = 0;
+ progp = 0;
+ backwards = FALSE;
+ bstartinst = nil;
+ startinst = realcompile(r);
+ if(startinst == nil)
+ return FALSE;
+ optimize(0);
+ oprogp = progp;
+ backwards = TRUE;
+ bstartinst = realcompile(r);
+ if(bstartinst == nil)
+ return FALSE;
+ optimize(oprogp);
+ lastregexp = r;
+ return TRUE;
+}
+
+operand(t : int)
+{
+ i : ref Inst;
+
+ if(lastwasand)
+ operator(CAT); # catenate is implicit
+ i = newinst(t);
+ if(t == CCLASS){
+ if(negateclass)
+ i.typex = NCCLASS; # UGH
+ i.class = nclass-1; # UGH
+ }
+ pushand(i, i);
+ lastwasand = TRUE;
+}
+
+operator(t : int)
+{
+ if(t==RBRA && --nbra<0)
+ regerror("unmatched `)'");
+ if(t==LBRA){
+ 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
+}
+
+pushand(f : ref Inst, l : ref Inst)
+{
+ if(andp >= NSTACK)
+ error("operand stack overflow");
+ andstack[andp].first = f;
+ andstack[andp].last = l;
+ andp++;
+}
+
+pushator(t : int)
+{
+ if(atorp >= NSTACK)
+ error("operator stack overflow");
+ atorstack[atorp++]=t;
+ if(cursubid >= NRange)
+ subidstack[subidp++]= -1;
+ else
+ subidstack[subidp++]=cursubid;
+}
+
+popand(op : int) : ref Node
+{
+ if(andp <= 0)
+ if(op){
+ buf := sys->sprint("missing operand for %c", op);
+ regerror(buf);
+ }else
+ regerror("malformed regexp");
+ return andstack[--andp];
+}
+
+popator() : int
+{
+ if(atorp <= 0)
+ error("operator stack underflow");
+ --subidp;
+ return atorstack[--atorp];
+}
+
+evaluntil(pri : int)
+{
+ op1, op2 : ref Node;
+ inst1, inst2 : ref Inst;
+
+ while(pri==RBRA || atorstack[atorp-1]>=pri){
+ case(popator()){
+ LBRA =>
+ op1 = popand('(');
+ inst2 = newinst(RBRA);
+ inst2.subid = subidstack[subidp];
+ op1.last.next = inst2;
+ inst1 = newinst(LBRA);
+ inst1.subid = subidstack[subidp];
+ inst1.next = op1.first;
+ pushand(inst1, inst2);
+ return; # must have been RBRA
+ OR =>
+ op2 = popand('|');
+ op1 = popand('|');
+ inst2 = newinst(NOP);
+ op2.last.next = inst2;
+ op1.last.next = inst2;
+ inst1 = newinst(OR);
+ inst1.right = op1.first;
+ inst1.next = op2.first; # next was left
+ pushand(inst1, inst2);
+ CAT =>
+ op2 = popand(0);
+ op1 = popand(0);
+ if(backwards && op2.first.typex!=END)
+ (op1, op2) = (op2, op1);
+ op1.last.next = op2.first;
+ pushand(op1.first, op2.last);
+ STAR =>
+ op2 = popand('*');
+ inst1 = newinst(OR);
+ op2.last.next = inst1;
+ inst1.right = op2.first;
+ pushand(inst1, inst1);
+ PLUS =>
+ op2 = popand('+');
+ inst1 = newinst(OR);
+ op2.last.next = inst1;
+ inst1.right = op2.first;
+ pushand(op2.first, inst1);
+ QUEST =>
+ op2 = popand('?');
+ inst1 = newinst(OR);
+ inst2 = newinst(NOP);
+ inst1.next = inst2; # next was left
+ inst1.right = op2.first;
+ op2.last.next = inst2;
+ pushand(inst1, inst2);
+ * =>
+ error("unknown regexp operator");
+ }
+ }
+}
+
+optimize(start : int)
+{
+ inst : int;
+ target : ref Inst;
+
+ for(inst=start; program[inst].typex!=END; inst++){
+ target = program[inst].next;
+ while(target.typex == NOP)
+ target = target.next;
+ program[inst].next = target;
+ }
+}
+
+startlex(s : string)
+{
+ exprs = s;
+ exprp = 0;
+ nbra = 0;
+}
+
+lex() : int
+{
+ c : int;
+
+ if (exprp == len exprs)
+ return END;
+ c = exprs[exprp++];
+ case(c){
+ '\\' =>
+ if(exprp < len exprs)
+ if((c= exprs[exprp++])=='n')
+ c='\n';
+ '*' =>
+ c = STAR;
+ '?' =>
+ c = QUEST;
+ '+' =>
+ c = PLUS;
+ '|' =>
+ c = OR;
+ '.' =>
+ c = ANY;
+ '(' =>
+ c = LBRA;
+ ')' =>
+ c = RBRA;
+ '^' =>
+ c = BOL;
+ '$' =>
+ c = EOL;
+ '[' =>
+ c = CCLASS;
+ bldcclass();
+ }
+ return c;
+}
+
+nextrec() : int
+{
+ if(exprp == len exprs || (exprp == len exprs-1 && exprs[exprp]=='\\'))
+ regerror("malformed `[]'");
+ if(exprs[exprp] == '\\'){
+ exprp++;
+ if(exprs[exprp]=='n'){
+ exprp++;
+ return '\n';
+ }
+ return exprs[exprp++] | 16r10000;
+ }
+ return exprs[exprp++];
+}
+
+bldcclass()
+{
+ c1, c2 : int;
+ classp : string;
+
+ # we have already seen the '['
+ if(exprp < len exprs && exprs[exprp] == '^'){
+ classp[len classp] = '\n'; # don't match newline in negate case
+ negateclass = TRUE;
+ exprp++;
+ }else
+ negateclass = FALSE;
+ while((c1 = nextrec()) != ']'){
+ if(c1 == '-'){
+ classp = nil;
+ regerror("malformed `[]'");
+ }
+ if(exprp < len exprs && exprs[exprp] == '-'){
+ exprp++; # eat '-'
+ if((c2 = nextrec()) == ']') {
+ classp = nil;
+ regerror("malformed '[]'");
+ }
+ classp[len classp] = 16rFFFF;
+ classp[len classp] = c1;
+ classp[len classp] = c2;
+ }else
+ classp[len classp] = c1;
+ }
+ if(nclass == Nclass){
+ Nclass += DCLASS;
+ oc := class;
+ class = array[Nclass] of string;
+ if (oc != nil) {
+ class[0:] = oc[0:Nclass-DCLASS];
+ oc = nil;
+ }
+ }
+ class[nclass++] = classp;
+}
+
+classmatch(classno : int, c : int, negate : int) : int
+{
+ p : string;
+
+ p = class[classno];
+ for (i := 0; i < len p; ) {
+ if(p[i] == 16rFFFF){
+ if(p[i+1]<=c && c<=p[i+2])
+ return !negate;
+ i += 3;
+ }else if(p[i++] == 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.
+#
+addinst(l : array of Ilist, inst : ref Inst, sep : Rangeset)
+{
+ p : int;
+
+ for(p = 0; l[p].inst != nil; p++){
+ if(l[p].inst==inst){
+ if(sep[0].q0 < l[p].se[0].q0)
+ l[p].se[0:] = sep[0:NRange]; # this would be bug
+ return; # It's already there
+ }
+ }
+ l[p].inst = inst;
+ l[p].se[0:]= sep[0:NRange];
+ l[p+1].inst = nil;
+}
+
+rxnull() : int
+{
+ return startinst==nil || bstartinst==nil;
+}
+
+OVERFLOW : con "overflow";
+
+# either t!=nil or r!=nil, and we match the string in the appropriate place
+rxexecute(t : ref Text, r: string, startp : int, eof : int) : (int, Rangeset)
+{
+ flag : int;
+ inst : ref Inst;
+ tlp : int;
+ p : int;
+ nnl, ntl : int;
+ nc, c : int;
+ wrapped : int;
+ startchar : int;
+
+ flag = 0;
+ p = startp;
+ startchar = 0;
+ wrapped = 0;
+ nnl = 0;
+ if(startinst.typex<OPERATOR)
+ startchar = startinst.typex;
+ listx[0][0].inst = listx[1][0].inst = nil;
+ sel[0].q0 = -1;
+
+ {
+ if(t != nil)
+ nc = t.file.buf.nc;
+ else
+ nc = len r;
+ # Execute machine once for each character
+ for(;;p++){
+ if(p>=eof || p>=nc){
+ case(wrapped++){
+ 0 or 2 => # let loop run one more click
+ ;
+ 1 => # expired; wrap to beginning
+ if(sel[0].q0>=0 || eof!=Dat->Infinity)
+ return (sel[0].q0>=0, sel);
+ listx[0][0].inst = listx[1][0].inst = nil;
+ p = -1;
+ continue;
+ * =>
+ return (sel[0].q0>=0, sel);
+ }
+ c = 0;
+ }else{
+ if(((wrapped && p>=startp) || sel[0].q0>0) && nnl==0)
+ break;
+ if(t != nil)
+ c = t.readc(p);
+ else
+ c = r[p];
+ }
+ # fast check for first char
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ thl = listx[flag];
+ nl = listx[flag^=1];
+ nl[0].inst = nil;
+ ntl = nnl;
+ nnl = 0;
+ if(sel[0].q0<0 && (!wrapped || p<startp || startp==eof)){
+ # Add first instruction to this list
+ if(++ntl >= NLIST)
+ raise OVERFLOW;
+ sempty[0].q0 = p;
+ addinst(thl, startinst, sempty);
+ }
+ # Execute machine until this list is empty
+ tlp = 0;
+ inst = thl[0].inst;
+ while(inst != nil){ # assignment =
+ case(inst.typex){
+ LBRA =>
+ if(inst.subid>=0)
+ thl[tlp].se[inst.subid].q0 = p;
+ inst = inst.next;
+ continue;
+ RBRA =>
+ if(inst.subid>=0)
+ thl[tlp].se[inst.subid].q1 = p;
+ inst = inst.next;
+ continue;
+ ANY =>
+ if(c!='\n') {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ BOL =>
+ if(p==0 || (t != nil && t.readc(p-1)=='\n') || (r != nil && r[p-1] == '\n')){
+ inst = inst.next;
+ continue;
+ }
+ EOL =>
+ if(c == '\n') {
+ inst = inst.next;
+ continue;
+ }
+ CCLASS =>
+ if(c>=0 && classmatch(inst.class, c, 0)) {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ NCCLASS =>
+ if(c>=0 && classmatch(inst.class, c, 1)) {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ OR =>
+ # evaluate right choice later
+ if(++ntl >= NLIST)
+ raise OVERFLOW;
+ addinst(thl[tlp:], inst.right, thl[tlp].se);
+ # efficiency: advance and re-evaluate
+ inst = inst.next; # next was left
+ continue;
+ END => # Match!
+ thl[tlp].se[0].q1 = p;
+ newmatch(thl[tlp].se);
+ * => # regular character
+ if(inst.typex==c){
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ }
+ tlp++;
+ inst = thl[tlp].inst;
+ }
+ }
+ return (sel[0].q0>=0, sel);
+ }
+ exception{
+ OVERFLOW =>
+ error("regexp list overflow");
+ sel[0].q0 = -1;
+ return (0, sel);
+ }
+ return (0, sel);
+}
+
+newmatch(sp : Rangeset)
+{
+ if(sel[0].q0<0 || sp[0].q0<sel[0].q0 ||
+ (sp[0].q0==sel[0].q0 && sp[0].q1>sel[0].q1))
+ sel[0:] = sp[0:NRange];
+}
+
+rxbexecute(t : ref Text, startp : int) : (int, Rangeset)
+{
+ flag : int;
+ inst : ref Inst;
+ tlp : int;
+ p : int;
+ nnl, ntl : int;
+ c : int;
+ wrapped : int;
+ startchar : int;
+
+ flag = 0;
+ nnl = 0;
+ wrapped = 0;
+ p = startp;
+ startchar = 0;
+ if(bstartinst.typex<OPERATOR)
+ startchar = bstartinst.typex;
+ listx[0][0].inst = listx[1][0].inst = nil;
+ sel[0].q0= -1;
+
+ {
+ # Execute machine once for each character, including terminal NUL
+ for(;;--p){
+ if(p <= 0){
+ case(wrapped++){
+ 0 or 2 => # let loop run one more click
+ ;
+ 1 => # expired; wrap to end
+ if(sel[0].q0>=0)
+ return (sel[0].q0>=0, sel);
+ listx[0][0].inst = listx[1][0].inst = nil;
+ p = t.file.buf.nc+1;
+ continue;
+ 3 or * =>
+ return (sel[0].q0>=0, sel);
+ }
+ c = 0;
+ }else{
+ if(((wrapped && p<=startp) || sel[0].q0>0) && nnl==0)
+ break;
+ c = t.readc(p-1);
+ }
+ # fast check for first char
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ thl = listx[flag];
+ nl = listx[flag^=1];
+ nl[0].inst = nil;
+ ntl = nnl;
+ nnl = 0;
+ if(sel[0].q0<0 && (!wrapped || p>startp)){
+ # Add first instruction to this list
+ if(++ntl >= NLIST)
+ raise OVERFLOW;
+ # the minus is so the optimizations in addinst work
+ sempty[0].q0 = -p;
+ addinst(thl, bstartinst, sempty);
+ }
+ # Execute machine until this list is empty
+ tlp = 0;
+ inst = thl[0].inst;
+ while(inst != nil){ # assignment =
+ case(inst.typex){
+ LBRA =>
+ if(inst.subid>=0)
+ thl[tlp].se[inst.subid].q0 = p;
+ inst = inst.next;
+ continue;
+ RBRA =>
+ if(inst.subid >= 0)
+ thl[tlp].se[inst.subid].q1 = p;
+ inst = inst.next;
+ continue;
+ ANY =>
+ if(c != '\n') {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ BOL =>
+ if(c=='\n' || p==0){
+ inst = inst.next;
+ continue;
+ }
+ EOL =>
+ if(p<t.file.buf.nc && t.readc(p)=='\n') {
+ inst = inst.next;
+ continue;
+ }
+ CCLASS =>
+ if(c>0 && classmatch(inst.class, c, 0)) {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ NCCLASS =>
+ if(c>0 && classmatch(inst.class, c, 1)) {
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ OR =>
+ # evaluate right choice later
+ if(++ntl >= NLIST)
+ raise OVERFLOW;
+ addinst(thl[tlp:], inst.right, thl[tlp].se);
+ # efficiency: advance and re-evaluate
+ inst = inst.next; # next was left
+ continue;
+ END => # Match!
+ thl[tlp].se[0].q0 = -thl[tlp].se[0].q0; # minus sign
+ thl[tlp].se[0].q1 = p;
+ bnewmatch(thl[tlp].se);
+ * => # regular character
+ if(inst.typex == c){
+ if(++nnl >= NLIST)
+ raise OVERFLOW;
+ addinst(nl, inst.next, thl[tlp].se);
+ }
+ }
+ tlp++;
+ inst = thl[tlp].inst;
+ }
+ }
+ return (sel[0].q0>=0, sel);
+ }
+ exception{
+ OVERFLOW =>
+ error("regexp list overflow");
+ sel[0].q0 = -1;
+ return (0, sel);
+ }
+ return (0, sel);
+}
+
+bnewmatch(sp : Rangeset)
+{
+ i : int;
+
+ if(sel[0].q0<0 || sp[0].q0>sel[0].q1 || (sp[0].q0==sel[0].q1 && sp[0].q1<sel[0].q0))
+ for(i = 0; i<NRange; i++){ # note the reversal; q0<=q1
+ sel[i].q0 = sp[i].q1;
+ sel[i].q1 = sp[i].q0;
+ }
+}
--- /dev/null
+++ b/appl/acme/regx.m
@@ -1,0 +1,13 @@
+Regx : module {
+ PATH : con "/dis/acme/regx.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ rxinit : fn();
+ rxcompile: fn(r : string) : int;
+ rxexecute: fn(t : ref Textm->Text, r: string, startp : int, eof : int) : (int, Dat->Rangeset);
+ rxbexecute: fn(t : ref Textm->Text, startp : int) : (int, Dat->Rangeset);
+ isaddrc : fn(r : int) : int;
+ isregexc : fn(r : int) : int;
+ address : fn(md: ref Dat->Mntdir, t : ref Textm->Text, lim : Dat->Range, ar : Dat->Range, a0 : ref Textm->Text, a1 : string, q0 : int, q1 : int, eval : int) : (int, int, Dat->Range);
+};
--- /dev/null
+++ b/appl/acme/row.b
@@ -1,0 +1,767 @@
+implement Rowm;
+
+include "common.m";
+
+sys : Sys;
+bufio : Bufio;
+utils : Utils;
+drawm : Draw;
+acme : Acme;
+graph : Graph;
+gui : Gui;
+dat : Dat;
+bufferm : Bufferm;
+textm : Textm;
+filem : Filem;
+windowm : Windowm;
+columnm : Columnm;
+exec : Exec;
+look : Look;
+edit : Edit;
+ecmd : Editcmd;
+
+ALLLOOPER, ALLTOFILE, ALLMATCHFILE, ALLFILECHECK, ALLELOGTERM, ALLEDITINIT, ALLUPDATE: import Edit;
+sprint : import sys;
+FALSE, TRUE, XXX : import Dat;
+Border, BUFSIZE, Astring : import Dat;
+Reffont, reffont, Lock, Ref : import dat;
+row, home, mouse : import dat;
+fontnames : import acme;
+font, draw : import graph;
+Point, Rect, Image : import drawm;
+min, max, abs, error, warning, clearmouse, stralloc, strfree : import utils;
+black, white, mainwin : import gui;
+Buffer : import bufferm;
+Tag, Rowtag, Text : import textm;
+Window : import windowm;
+File : import filem;
+Column : import columnm;
+Iobuf : import bufio;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ bufio = mods.bufio;
+ dat = mods.dat;
+ utils = mods.utils;
+ drawm = mods.draw;
+ acme = mods.acme;
+ graph = mods.graph;
+ gui = mods.gui;
+ bufferm = mods.bufferm;
+ textm = mods.textm;
+ filem = mods.filem;
+ windowm = mods.windowm;
+ columnm = mods.columnm;
+ exec = mods.exec;
+ look = mods.look;
+ edit = mods.edit;
+ ecmd = mods.editcmd;
+}
+
+newrow() : ref Row
+{
+ r := ref Row;
+ r.qlock = Lock.init();
+ r.r = ((0, 0), (0, 0));
+ r.tag = nil;
+ r.col = nil;
+ r.ncol = 0;
+ return r;
+}
+
+Row.init(row : self ref Row, r : Rect)
+{
+ r1 : Rect;
+ t : ref Text;
+ dummy : ref File = nil;
+
+ draw(mainwin, r, white, nil, (0, 0));
+ row.r = r;
+ row.col = nil;
+ row.ncol = 0;
+ r1 = r;
+ r1.max.y = r1.min.y + font.height;
+ row.tag = textm->newtext();
+ t = row.tag;
+ t.init(dummy.addtext(t), r1, Reffont.get(FALSE, FALSE, FALSE, nil), acme->tagcols);
+ t.what = Rowtag;
+ t.row = row;
+ t.w = nil;
+ t.col = nil;
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ t.insert(0, "Newcol Kill Putall Dump Exit ", 29, TRUE, 0);
+ t.setselect(t.file.buf.nc, t.file.buf.nc);
+}
+
+Row.add(row : self ref Row, c : ref Column, x : int) : ref Column
+{
+ r, r1 : Rect;
+ d : ref Column;
+ i : int;
+
+ d = nil;
+ r = row.r;
+ r.min.y = row.tag.frame.r.max.y+Border;
+ if(x<r.min.x && row.ncol>0){ #steal 40% of last column by default
+ d = row.col[row.ncol-1];
+ x = d.r.min.x + 3*d.r.dx()/5;
+ }
+ # look for column we'll land on
+ for(i=0; i<row.ncol; i++){
+ d = row.col[i];
+ if(x < d.r.max.x)
+ break;
+ }
+ if(row.ncol > 0){
+ if(i < row.ncol)
+ i++; # new column will go after d
+ r = d.r;
+ if(r.dx() < 100)
+ return nil;
+ draw(mainwin, r, white, nil, (0, 0));
+ r1 = r;
+ r1.max.x = min(x, r.max.x-50);
+ if(r1.dx() < 50)
+ r1.max.x = r1.min.x+50;
+ d.reshape(r1);
+ r1.min.x = r1.max.x;
+ r1.max.x = r1.min.x+Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ r.min.x = r1.max.x;
+ }
+ if(c == nil){
+ c = ref Column;
+ c.init(r);
+ reffont.r.inc();
+ }else
+ c.reshape(r);
+ c.row = row;
+ c.tag.row = row;
+ orc := row.col;
+ row.col = array[row.ncol+1] of ref Column;
+ row.col[0:] = orc[0:i];
+ row.col[i+1:] = orc[i:row.ncol];
+ orc = nil;
+ row.col[i] = c;
+ row.ncol++;
+ clearmouse();
+ return c;
+}
+
+Row.reshape(row : self ref Row, r : Rect)
+{
+ i, dx, odx : int;
+ r1, r2 : Rect;
+ c : ref Column;
+
+ dx = r.dx();
+ odx = row.r.dx();
+ row.r = r;
+ r1 = r;
+ r1.max.y = r1.min.y + font.height;
+ row.tag.reshape(r1);
+ r1.min.y = r1.max.y;
+ r1.max.y += Border;
+ draw(mainwin, r1, black, nil, (0, 0));
+ r.min.y = r1.max.y;
+ r1 = r;
+ r1.max.x = r1.min.x;
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ r1.min.x = r1.max.x;
+ if(i == row.ncol-1)
+ r1.max.x = r.max.x;
+ else
+ r1.max.x = r1.min.x+c.r.dx()*dx/odx;
+ r2 = r1;
+ r2.max.x = r2.min.x+Border;
+ draw(mainwin, r2, black, nil, (0, 0));
+ r1.min.x = r2.max.x;
+ c.reshape(r1);
+ }
+}
+
+Row.dragcol(row : self ref Row, c : ref Column)
+{
+ r : Rect;
+ i, b, x : int;
+ p, op : Point;
+ d : ref Column;
+
+ clearmouse();
+ graph->cursorswitch(dat->boxcursor);
+ b = mouse.buttons;
+ op = mouse.xy;
+ while(mouse.buttons == b)
+ acme->frgetmouse();
+ graph->cursorswitch(dat->arrowcursor);
+ if(mouse.buttons){
+ while(mouse.buttons)
+ acme->frgetmouse();
+ return;
+ }
+
+ for(i=0; i<row.ncol; i++)
+ if(row.col[i] == c)
+ break;
+ if (i == row.ncol)
+ error("can't find column");
+
+ if(i == 0)
+ return;
+ p = mouse.xy;
+ if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
+ return;
+ if((i>0 && p.x<row.col[i-1].r.min.x) || (i<row.ncol-1 && p.x>c.r.max.x)){
+ # shuffle
+ x = c.r.min.x;
+ row.close(c, FALSE);
+ if(row.add(c, p.x) == nil) # whoops!
+ if(row.add(c, x) == nil) # WHOOPS!
+ if(row.add(c, -1)==nil){ # shit!
+ row.close(c, TRUE);
+ return;
+ }
+ c.mousebut();
+ return;
+ }
+ d = row.col[i-1];
+ if(p.x < d.r.min.x+80+Dat->Scrollwid)
+ p.x = d.r.min.x+80+Dat->Scrollwid;
+ if(p.x > c.r.max.x-80-Dat->Scrollwid)
+ p.x = c.r.max.x-80-Dat->Scrollwid;
+ r = d.r;
+ r.max.x = c.r.max.x;
+ draw(mainwin, r, white, nil, (0, 0));
+ r.max.x = p.x;
+ d.reshape(r);
+ r = c.r;
+ r.min.x = p.x;
+ r.max.x = r.min.x;
+ r.max.x += Border;
+ draw(mainwin, r, black, nil, (0, 0));
+ r.min.x = r.max.x;
+ r.max.x = c.r.max.x;
+ c.reshape(r);
+ c.mousebut();
+}
+
+Row.close(row : self ref Row, c : ref Column, dofree : int)
+{
+ r : Rect;
+ i : int;
+
+ for(i=0; i<row.ncol; i++)
+ if(row.col[i] == c)
+ break;
+ if (i == row.ncol)
+ error("can't find column");
+
+ r = c.r;
+ if(dofree)
+ c.closeall();
+ orc := row.col;
+ row.col = array[row.ncol-1] of ref Column;
+ row.col[0:] = orc[0:i];
+ row.col[i:] = orc[i+1:row.ncol];
+ orc = nil;
+ row.ncol--;
+ if(row.ncol == 0){
+ draw(mainwin, r, white, nil, (0, 0));
+ return;
+ }
+ if(i == row.ncol){ # extend last column right
+ c = row.col[i-1];
+ r.min.x = c.r.min.x;
+ r.max.x = row.r.max.x;
+ }else{ # extend next window left
+ c = row.col[i];
+ r.max.x = c.r.max.x;
+ }
+ draw(mainwin, r, white, nil, (0, 0));
+ c.reshape(r);
+}
+
+Row.whichcol(row : self ref Row, p : Point) : ref Column
+{
+ i : int;
+ c : ref Column;
+
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ if(p.in(c.r))
+ return c;
+ }
+ return nil;
+}
+
+Row.which(row : self ref Row, p : Point) : ref Text
+{
+ c : ref Column;
+
+ if(p.in(row.tag.all))
+ return row.tag;
+ c = row.whichcol(p);
+ if(c != nil)
+ return c.which(p);
+ return nil;
+}
+
+Row.typex(row : self ref Row, r : int, p : Point) : ref Text
+{
+ w : ref Window;
+ t : ref Text;
+
+ clearmouse();
+ row.qlock.lock();
+ if(dat->bartflag)
+ t = dat->barttext;
+ else
+ t = row.which(p);
+ if(t!=nil && !(t.what==Tag && p.in(t.scrollr))){
+ w = t.w;
+ if(w == nil)
+ t.typex(r, 0);
+ else{
+ w.lock('K');
+ w.typex(t, r);
+ w.unlock();
+ }
+ }
+ row.qlock.unlock();
+ return t;
+}
+
+Row.clean(row : self ref Row, exiting : int) : int
+{
+ clean : int;
+ i : int;
+
+ clean = TRUE;
+ for(i=0; i<row.ncol; i++)
+ clean &= row.col[i].clean(exiting);
+ return clean;
+}
+
+Row.dump(row : self ref Row, file : string)
+{
+ i, j, m, n, dumped : int;
+ q0, q1 : int;
+ b : ref Iobuf;
+ buf, fontname, a : string;
+ r : ref Astring;
+ c : ref Column;
+ w, w1 : ref Window;
+ t : ref Text;
+
+ if(row.ncol == 0)
+ return;
+
+ {
+ if(file == nil){
+ if(home == nil){
+ warning(nil, "can't find file for dump: $home not defined\n");
+ raise "e";
+ }
+ buf = sprint("%s/acme.dump", home);
+ file = buf;
+ }
+ b = bufio->create(file, Bufio->OWRITE, 8r600);
+ if(b == nil){
+ warning(nil, sprint("can't open %s: %r\n", file));
+ raise "e";
+ }
+ r = stralloc(BUFSIZE);
+ b.puts(acme->wdir); b.putc('\n');
+ b.puts(fontnames[0]); b.putc('\n');
+ b.puts(fontnames[1]); b.putc('\n');
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ b.puts(sprint("%11d", 100*(c.r.min.x-row.r.min.x)/row.r.dx()));
+ if(i == row.ncol-1)
+ b.putc('\n');
+ else
+ b.putc(' ');
+ }
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c.nw; j++)
+ c.w[j].body.file.dumpid = 0;
+ }
+ for(i=0; i<row.ncol; i++){
+ c = row.col[i];
+ for(j=0; j<c.nw; j++){
+ w = c.w[j];
+ w.commit(w.tag);
+ t = w.body;
+ # windows owned by others get special treatment
+ if(w.nopen[Dat->QWevent] > byte 0)
+ if(w.dumpstr == nil)
+ continue;
+ # zeroxes of external windows are tossed
+ if(t.file.ntext > 1)
+ for(n=0; n<t.file.ntext; n++){
+ w1 = t.file.text[n].w;
+ if(w == w1)
+ continue;
+ if(w1.nopen[Dat->QWevent] != byte 0) {
+ j = c.nw;
+ continue;
+ }
+ }
+ fontname = "";
+ if(t.reffont.f != font)
+ fontname = t.reffont.f.name;
+ a = t.file.name;
+ if(t.file.dumpid){
+ dumped = FALSE;
+ b.puts(sprint("x%11d %11d %11d %11d %11d %s\n", i, t.file.dumpid,
+ w.body.q0, w.body.q1,
+ 100*(w.r.min.y-c.r.min.y)/c.r.dy(),
+ fontname));
+ }else if(w.dumpstr != nil){
+ dumped = FALSE;
+ b.puts(sprint("e%11d %11d %11d %11d %11d %s\n", i, t.file.dumpid,
+ 0, 0,
+ 100*(w.r.min.y-c.r.min.y)/c.r.dy(),
+ fontname));
+ }else if(len a == 0){ # don't save unnamed windows
+ continue;
+ }else if((!w.dirty && utils->access(a)==0) || w.isdir){
+ dumped = FALSE;
+ t.file.dumpid = w.id;
+ b.puts(sprint("f%11d %11d %11d %11d %11d %s\n", i, w.id,
+ w.body.q0, w.body.q1,
+ 100*(w.r.min.y-c.r.min.y)/c.r.dy(),
+ fontname));
+ }else{
+ dumped = TRUE;
+ t.file.dumpid = w.id;
+ b.puts(sprint("F%11d %11d %11d %11d %11d %11d %s\n", i, j,
+ w.body.q0, w.body.q1,
+ 100*(w.r.min.y-c.r.min.y)/c.r.dy(),
+ w.body.file.buf.nc, fontname));
+ }
+ a = nil;
+ buf = w.ctlprint(0);
+ b.puts(buf);
+ m = min(BUFSIZE, w.tag.file.buf.nc);
+ w.tag.file.buf.read(0, r, 0, m);
+ n = 0;
+ while(n<m && r.s[n]!='\n')
+ n++;
+ r.s[n++] = '\n';
+ b.puts(r.s[0:n]);
+ if(dumped){
+ q0 = 0;
+ q1 = t.file.buf.nc;
+ while(q0 < q1){
+ n = q1 - q0;
+ if(n > Dat->BUFSIZE)
+ n = Dat->BUFSIZE;
+ t.file.buf.read(q0, r, 0, n);
+ b.puts(r.s[0:n]);
+ q0 += n;
+ }
+ }
+ if(w.dumpstr != nil){
+ if(w.dumpdir != nil)
+ b.puts(sprint("%s\n%s\n", w.dumpdir, w.dumpstr));
+ else
+ b.puts(sprint("\n%s\n", w.dumpstr));
+ }
+ }
+ }
+ b.close();
+ b = nil;
+ strfree(r);
+ r = nil;
+ }
+ exception{
+ * =>
+ return;
+ }
+}
+
+rdline(b : ref Iobuf, line : int) : (int, string)
+{
+ l : string;
+
+ l = b.gets('\n');
+ if(l != nil)
+ line++;
+ return (line, l);
+}
+
+Row.loadx(row : self ref Row, file : string, initing : int)
+{
+ i, j, line, percent, y, nr, nfontr, n, ns, ndumped, dumpid, x : int;
+ b, bout : ref Iobuf;
+ fontname : string;
+ l, buf, t : string;
+ rune : int;
+ r, fontr : string;
+ c, c1, c2 : ref Column;
+ q0, q1 : int;
+ r1, r2 : Rect;
+ w : ref Window;
+
+ {
+ if(file == nil){
+ if(home == nil){
+ warning(nil, "can't find file for load: $home not defined\n");
+ raise "e";
+ }
+ buf = sprint("%s/acme.dump", home);
+ file = buf;
+ }
+ b = bufio->open(file, Bufio->OREAD);
+ if(b == nil){
+ warning(nil, sprint("can't open load file %s: %r\n", file));
+ raise "e";
+ }
+
+ {
+ # current directory
+ (line, l) = rdline(b, 0);
+ if(l == nil)
+ raise "e";
+ l = l[0:len l - 1];
+ if(sys->chdir(l) < 0){
+ warning(nil, sprint("can't chdir %s\n", l));
+ b.close();
+ return;
+ }
+ # global fonts
+ for(i=0; i<2; i++){
+ (line, l) = rdline(b, line);
+ if(l == nil)
+ raise "e";
+ l = l[0:len l -1];
+ if(l != nil && l != fontnames[i])
+ Reffont.get(i, TRUE, i==0 && initing, l);
+ }
+ if(initing && row.ncol==0)
+ row.init(mainwin.clipr);
+ (line, l) = rdline(b, line);
+ if(l == nil)
+ raise "e";
+ j = len l/12;
+ if(j<=0 || j>10)
+ raise "e";
+ for(i=0; i<j; i++){
+ percent = int l[12*i:12*i+11];
+ if(percent<0 || percent>=100)
+ raise "e";
+ x = row.r.min.x+percent*row.r.dx()/100;
+ if(i < row.ncol){
+ if(i == 0)
+ continue;
+ c1 = row.col[i-1];
+ c2 = row.col[i];
+ r1 = c1.r;
+ r2 = c2.r;
+ r1.max.x = x;
+ r2.min.x = x+Border;
+ if(r1.dx() < 50 || r2.dx() < 50)
+ continue;
+ draw(mainwin, (r1.min, r2.max), white, nil, (0, 0));
+ c1.reshape(r1);
+ c2.reshape(r2);
+ r2.min.x = x;
+ r2.max.x = x+Border;
+ draw(mainwin, r2, black, nil, (0, 0));
+ }
+ if(i >= row.ncol)
+ row.add(nil, x);
+ }
+ for(;;){
+ (line, l) = rdline(b, line);
+ if(l == nil)
+ break;
+ dumpid = 0;
+ case(l[0]){
+ 'e' =>
+ if(len l < 1+5*12+1)
+ raise "e";
+ (line, l) = rdline(b, line); # ctl line; ignored
+ if(l == nil)
+ raise "e";
+ (line, l) = rdline(b, line); # directory
+ if(l == nil)
+ raise "e";
+ l = l[0:len l -1];
+ if(len l != 0)
+ r = l;
+ else{
+ if(home == nil)
+ r = "./";
+ else
+ r = home+"/";
+ }
+ nr = len r;
+ (line, l) = rdline(b, line); # command
+ if(l == nil)
+ raise "e";
+ t = l[0:len l -1];
+ spawn exec->run(nil, t, r, nr, TRUE, nil, nil, FALSE);
+ # r is freed in run()
+ continue;
+ 'f' =>
+ if(len l < 1+5*12+1)
+ raise "e";
+ fontname = l[1+5*12:len l - 1];
+ ndumped = -1;
+ 'F' =>
+ if(len l < 1+6*12+1)
+ raise "e";
+ fontname = l[1+6*12:len l - 1];
+ ndumped = int l[1+5*12:1+5*12+11];
+ 'x' =>
+ if(len l < 1+5*12+1)
+ raise "e";
+ fontname = l[1+5*12: len l - 1];
+ ndumped = -1;
+ dumpid = int l[1+1*12:1+1*12+11];
+ * =>
+ raise "e";
+ }
+ l = l[0:len l -1];
+ if(len fontname != 0) {
+ fontr = fontname;
+ nfontr = len fontname;
+ }
+ else
+ (fontr, nfontr) = (nil, 0);
+ i = int l[1+0*12:1+0*12+11];
+ j = int l[1+1*12:1+1*12+11];
+ q0 = int l[1+2*12:1+2*12+11];
+ q1 = int l[1+3*12:1+3*12+11];
+ percent = int l[1+4*12:1+4*12+11];
+ if(i<0 || i>10)
+ raise "e";
+ if(i > row.ncol)
+ i = row.ncol;
+ c = row.col[i];
+ y = c.r.min.y+(percent*c.r.dy())/100;
+ if(y<c.r.min.y || y>=c.r.max.y)
+ y = -1;
+ if(dumpid == 0)
+ w = c.add(nil, nil, y);
+ else
+ w = c.add(nil, look->lookid(dumpid, TRUE), y);
+ if(w == nil)
+ continue;
+ w.dumpid = j;
+ (line, l) = rdline(b, line);
+ if(l == nil)
+ raise "e";
+ l = l[0:len l - 1];
+ r = l[5*12:len l];
+ nr = len r;
+ ns = -1;
+ for(n=0; n<nr; n++){
+ if(r[n] == '/')
+ ns = n;
+ if(r[n] == ' ')
+ break;
+ }
+ if(dumpid == 0)
+ w.setname(r, n);
+ for(; n<nr; n++)
+ if(r[n] == '|')
+ break;
+ w.cleartag();
+ w.tag.insert(w.tag.file.buf.nc, r[n+1:len r], nr-(n+1), TRUE, 0);
+ if(ndumped >= 0){
+ # simplest thing is to put it in a file and load that
+ buf = sprint("/tmp/d%d.%.4sacme", sys->pctl(0, nil), utils->getuser());
+ bout = bufio->create(buf, Bufio->OWRITE, 8r600);
+ if(bout == nil){
+ warning(nil, "can't create temp file: %r\n");
+ b.close();
+ return;
+ }
+ for(n=0; n<ndumped; n++){
+ rune = b.getc();
+ if(rune == '\n')
+ line++;
+ if(rune == Bufio->EOF){
+ bout.close();
+ bout = nil;
+ raise "e";
+ }
+ bout.putc(rune);
+ }
+ bout.close();
+ bout = nil;
+ w.body.loadx(0, buf, 1);
+ w.body.file.mod = TRUE;
+ for(n=0; n<w.body.file.ntext; n++)
+ w.body.file.text[n].w.dirty = TRUE;
+ w.settag();
+ sys->remove(buf);
+ buf = nil;
+ }else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
+ exec->get(w.body, nil, nil, FALSE, nil, 0);
+ l = r = nil;
+ if(fontr != nil){
+ exec->fontx(w.body, nil, nil, fontr, nfontr);
+ fontr = nil;
+ }
+ if(q0>w.body.file.buf.nc || q1>w.body.file.buf.nc || q0>q1)
+ q0 = q1 = 0;
+ w.body.show(q0, q1, TRUE);
+ w.maxlines = min(w.body.frame.nlines, max(w.maxlines, w.body.frame.maxlines));
+ }
+ b.close();
+ }
+ exception{
+ * =>
+ warning(nil, sprint("bad load file %s:%d\n", file, line));
+ b.close();
+ raise "e";
+ }
+ }
+ exception{
+ * =>
+ return;
+ }
+}
+
+allwindows(o: int, aw: ref Dat->Allwin)
+{
+ for(i:=0; i<row.ncol; i++){
+ c := row.col[i];
+ for(j:=0; j<c.nw; j++){
+ w := c.w[j];
+ case (o){
+ ALLLOOPER =>
+ pick k := aw{
+ LP => ecmd->alllooper(w, k.lp);
+ }
+ ALLTOFILE =>
+ pick k := aw{
+ FF => ecmd->alltofile(w, k.ff);
+ }
+ ALLMATCHFILE =>
+ pick k := aw{
+ FF => ecmd->allmatchfile(w, k.ff);
+ }
+ ALLFILECHECK =>
+ pick k := aw{
+ FC => ecmd->allfilecheck(w, k.fc);
+ }
+ ALLELOGTERM =>
+ edit->allelogterm(w);
+ ALLEDITINIT =>
+ edit->alleditinit(w);
+ ALLUPDATE =>
+ edit->allupdate(w);
+ }
+ }
+ }
+}
--- /dev/null
+++ b/appl/acme/row.m
@@ -1,0 +1,29 @@
+Rowm : module {
+ PATH : con "/dis/acme/row.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ newrow : fn() : ref Row;
+
+ Row : adt {
+ qlock : ref Dat->Lock;
+ r : Draw->Rect;
+ tag : cyclic ref Textm->Text;
+ col : cyclic array of ref Columnm->Column;
+ ncol : int;
+
+ init : fn(r : self ref Row, re : Draw->Rect);
+ add : fn(r : self ref Row, c : ref Columnm->Column, n : int) : ref Columnm->Column;
+ close : fn(r : self ref Row, c : ref Columnm->Column, n : int);
+ which : fn(r : self ref Row, p : Draw->Point) : ref Textm->Text;
+ whichcol : fn(r : self ref Row, p : Draw->Point) : ref Columnm->Column;
+ reshape : fn(r : self ref Row, re : Draw->Rect);
+ typex : fn(r : self ref Row, ru : int, p : Draw->Point) : ref Textm->Text;
+ dragcol : fn(r : self ref Row, c : ref Columnm->Column);
+ clean : fn(r : self ref Row, exiting : int) : int;
+ dump : fn(r : self ref Row, b : string);
+ loadx : fn(r : self ref Row, b : string, n : int);
+ };
+
+ allwindows: fn(a0: int, aw: ref Dat->Allwin);
+};
--- /dev/null
+++ b/appl/acme/scrl.b
@@ -1,0 +1,187 @@
+implement Scroll;
+
+include "common.m";
+
+sys : Sys;
+drawm : Draw;
+acme : Acme;
+graph : Graph;
+utils : Utils;
+gui : Gui;
+dat : Dat;
+framem : Framem;
+textm : Textm;
+timerm : Timerm;
+
+BORD, BACK : import Framem;
+FALSE, TRUE, XXX, Maxblock : import Dat;
+error, warning : import utils;
+Point, Rect, Image, Display : import drawm;
+draw : import graph;
+black, white, display : import gui;
+mouse, cmouse : import dat;
+Frame : import framem;
+Timer : import Dat;
+Text : import textm;
+frgetmouse : import acme;
+mainwin : import gui;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ drawm = mods.draw;
+ acme = mods.acme;
+ graph = mods.graph;
+ utils = mods.utils;
+ gui = mods.gui;
+ dat = mods.dat;
+ framem = mods.framem;
+ textm = mods.textm;
+ timerm = mods.timerm;
+}
+
+scrpos(r : Rect, p0 : int, p1 : int, tot : int) : Rect
+{
+ h : int;
+ q : Rect;
+
+ q = r;
+ # q = r.inset(1);
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ 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;
+}
+
+scrx : ref Image;
+
+scrresize()
+{
+ scrx = nil;
+ h := 1024;
+ if (display != nil)
+ h = display.image.r.dy();
+ rr := Rect((0, 0), (32, h));
+ scrx = graph->balloc(rr, mainwin.chans, Draw->White);
+ if(scrx == nil)
+ error("scroll balloc");
+}
+
+scrdraw(t : ref Text)
+{
+ r, r1, r2 : Rect;
+
+ if(t.w==nil || t.what!=Textm->Body || t != t.w.body)
+ return;
+ if(scrx == nil)
+ scrresize();
+ r = t.scrollr;
+ b := scrx;
+ r1 = r;
+ # r.min.x += 1; # border between margin and bar
+ r1.min.x = 0;
+ r1.max.x = r.dx();
+ r2 = scrpos(r1, t.org, t.org+t.frame.nchars, t.file.buf.nc);
+ if(!r2.eq(t.lastsr)){
+ t.lastsr = r2;
+ draw(b, r1, t.frame.cols[BORD], nil, (0, 0));
+ draw(b, r2, t.frame.cols[BACK], nil, (0, 0));
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, t.frame.cols[BORD], nil, (0, 0));
+ draw(t.frame.b, r, b, nil, (0, r1.min.y));
+ # bflush();
+ }
+}
+
+scrsleep(dt : int)
+{
+ timer : ref Timer;
+
+ timer = timerm->timerstart(dt);
+ graph->bflush();
+ # only run from mouse task, so safe to use cmouse
+ alt{
+ <-(timer.c) =>
+ timerm->timerstop(timer);
+ *mouse = *<-cmouse =>
+ spawn timerm->timerwaittask(timer);
+ }
+}
+
+scroll(t : ref Text, but : int)
+{
+ p0, oldp0 : int;
+ s : Rect;
+ x, y, my, h, first : int;
+
+ s = t.scrollr.inset(1);
+ h = s.max.y-s.min.y;
+ x = (s.min.x+s.max.x)/2;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ graph->bflush();
+ my = mouse.xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 2){
+ y = my;
+ if(y > s.max.y-2)
+ y = s.max.y-2;
+ if(t.file.buf.nc > 1024*1024)
+ p0 = ((t.file.buf.nc>>10)*(y-s.min.y)/h)<<10;
+ else
+ p0 = t.file.buf.nc*(y-s.min.y)/h;
+ if(oldp0 != p0)
+ t.setorigin(p0, FALSE);
+ oldp0 = p0;
+ frgetmouse();
+ continue;
+ }
+ if(but == 1) {
+ p0 = t.backnl(t.org, (my-s.min.y)/t.frame.font.height);
+ if(p0 == t.org)
+ p0 = t.backnl(t.org, 1);
+ }
+ else {
+ p0 = t.org+framem->frcharofpt(t.frame, (s.max.x, my));
+ if(p0 == t.org)
+ p0 = t.forwnl(t.org, 1);
+ }
+ if(oldp0 != p0)
+ t.setorigin(p0, TRUE);
+ oldp0 = p0;
+ # debounce
+ if(first){
+ graph->bflush();
+ sys->sleep(200);
+ alt {
+ *mouse = *<-cmouse =>
+ ;
+ * =>
+ ;
+ }
+ first = FALSE;
+ }
+ scrsleep(80);
+ }while(mouse.buttons & (1<<(but-1)));
+ while(mouse.buttons)
+ frgetmouse();
+}
--- /dev/null
+++ b/appl/acme/scrl.m
@@ -1,0 +1,9 @@
+Scroll : module {
+ PATH : con "/dis/acme/scrl.dis";
+
+ init : fn(mods : ref Dat->Mods);
+ scrsleep : fn(n : int);
+ scrdraw : fn(t : ref Textm->Text);
+ scrresize : fn();
+ scroll : fn(t : ref Textm->Text, but : int);
+};
--- /dev/null
+++ b/appl/acme/styxaux.b
@@ -1,0 +1,174 @@
+implement Styxaux;
+
+include "sys.m";
+ sys: Sys;
+include "styx.m";
+include "styxaux.m";
+
+Tmsg : import Styx;
+
+init()
+{
+}
+
+msize(m: ref Tmsg): int
+{
+ pick fc := m {
+ Version => return fc.msize;
+ }
+ error("bad styx msize", m);
+ return 0;
+}
+
+version(m: ref Tmsg): string
+{
+ pick fc := m {
+ Version => return fc.version;
+ }
+ error("bad styx version", m);
+ return nil;
+}
+
+fid(m: ref Tmsg): int
+{
+ pick fc := m {
+ Readerror => return 0;
+ Version => return 0;
+ Flush => return 0;
+ Walk => return fc.fid;
+ Open => return fc.fid;
+ Create => return fc.fid;
+ Read => return fc.fid;
+ Write => return fc.fid;
+ Clunk => return fc.fid;
+ Remove => return fc.fid;
+ Stat => return fc.fid;
+ Wstat => return fc.fid;
+ Attach => return fc.fid;
+ }
+ error("bad styx fid", m);
+ return 0;
+}
+
+uname(m: ref Tmsg): string
+{
+ pick fc := m {
+ Attach => return fc.uname;
+ }
+ error("bad styx uname", m);
+ return nil;
+}
+
+aname(m: ref Tmsg): string
+{
+ pick fc := m {
+ Attach => return fc.aname;
+ }
+ error("bad styx aname", m);
+ return nil;
+}
+
+newfid(m: ref Tmsg): int
+{
+ pick fc := m {
+ Walk => return fc.newfid;
+ }
+ error("bad styx newfd", m);
+ return 0;
+}
+
+name(m: ref Tmsg): string
+{
+ pick fc := m {
+ Create => return fc.name;
+ }
+ error("bad styx name", m);
+ return nil;
+}
+
+names(m: ref Tmsg): array of string
+{
+ pick fc := m {
+ Walk => return fc.names;
+ }
+ error("bad styx names", m);
+ return nil;
+}
+
+mode(m: ref Tmsg): int
+{
+ pick fc := m {
+ Open => return fc.mode;
+ }
+ error("bad styx mode", m);
+ return 0;
+}
+
+setmode(m: ref Tmsg, mode: int)
+{
+ pick fc := m {
+ Open => fc.mode = mode;
+ * => error("bad styx setmode", m);
+ }
+}
+
+offset(m: ref Tmsg): big
+{
+ pick fc := m {
+ Read => return fc.offset;
+ Write => return fc.offset;
+ }
+ error("bad styx offset", m);
+ return big 0;
+}
+
+count(m: ref Tmsg): int
+{
+ pick fc := m {
+ Read => return fc.count;
+ Write => return len fc.data;
+ }
+ error("bad styx count", m);
+ return 0;
+}
+
+setcount(m: ref Tmsg, count: int)
+{
+ pick fc := m {
+ Read => fc.count = count;
+ * => error("bad styx setcount", m);
+ }
+}
+
+oldtag(m: ref Tmsg): int
+{
+ pick fc := m {
+ Flush => return fc.oldtag;
+ }
+ error("bad styx oldtag", m);
+ return 0;
+}
+
+data(m: ref Tmsg): array of byte
+{
+ pick fc := m {
+ Write => return fc.data;
+ }
+ error("bad styx data", m);
+ return nil;
+}
+
+setdata(m: ref Tmsg, data: array of byte)
+{
+ pick fc := m {
+ Write => fc.data = data;
+ * => error("bad styx setdata", m);
+ }
+}
+
+error(s: string, m: ref Tmsg)
+{
+ if(sys == nil)
+ sys = load Sys Sys->PATH;
+ sys->fprint(sys->fildes(2), "%s %d\n", s, tagof m);
+}
--- /dev/null
+++ b/appl/acme/styxaux.m
@@ -1,0 +1,24 @@
+Styxaux : module {
+ PATH : con "/dis/acme/styxaux.dis";
+
+ init : fn();
+
+ msize: fn(m: ref Styx->Tmsg): int;
+ version: fn(m: ref Styx->Tmsg): string;
+ fid: fn(m: ref Styx->Tmsg): int;
+ uname: fn(m: ref Styx->Tmsg): string;
+ aname: fn(m: ref Styx->Tmsg): string;
+ newfid: fn(m: ref Styx->Tmsg): int;
+ name: fn(m: ref Styx->Tmsg): string;
+ names: fn(m: ref Styx->Tmsg): array of string;
+ mode: fn(m: ref Styx->Tmsg): int;
+ offset: fn(m: ref Styx->Tmsg): big;
+ count: fn(m: ref Styx->Tmsg): int;
+ oldtag: fn(m: ref Styx->Tmsg): int;
+ data: fn(m: ref Styx->Tmsg): array of byte;
+
+ setmode: fn(m: ref Styx->Tmsg, mode: int);
+ setcount: fn(m: ref Styx->Tmsg, count: int);
+ setdata: fn(m: ref Styx->Tmsg, data: array of byte);
+
+};
--- /dev/null
+++ b/appl/acme/text.b
@@ -1,0 +1,1455 @@
+implement Textm;
+
+include "common.m";
+include "keyboard.m";
+
+sys : Sys;
+utils : Utils;
+framem : Framem;
+drawm : Draw;
+acme : Acme;
+graph : Graph;
+gui : Gui;
+dat : Dat;
+scrl : Scroll;
+bufferm : Bufferm;
+filem : Filem;
+columnm : Columnm;
+windowm : Windowm;
+exec : Exec;
+
+Dir, sprint : import sys;
+frgetmouse : import acme;
+min, warning, error, stralloc, strfree, isalnum : import utils;
+Frame, frinsert, frdelete, frptofchar, frcharofpt, frselect, frdrawsel, frdrawsel0, frtick : import framem;
+BUFSIZE, Astring, SZINT, TRUE, FALSE, XXX, Reffont, Dirlist,Scrollwid, Scrollgap, seq, mouse : import dat;
+EM_NORMAL, EM_RAW, EM_MASK : import dat;
+ALPHA_LATIN, ALPHA_GREEK, ALPHA_CYRILLIC: import Dat;
+BACK, TEXT, HIGH, HTEXT : import Framem;
+Flushon, Flushoff : import Draw;
+Point, Display, Rect, Image : import drawm;
+charwidth, bflush, draw : import graph;
+black, white, mainwin, display : import gui;
+Buffer : import bufferm;
+File : import filem;
+Column : import columnm;
+Window : import windowm;
+scrdraw : import scrl;
+
+cvlist: adt {
+ ld: int;
+ nm: string;
+ si: string;
+ so: string;
+};
+
+# "@@", "'EKSTYZekstyz ", "ьЕКСТЫЗекстызъЁё",
+
+latintab := array[] of {
+ cvlist(
+ ALPHA_LATIN,
+ "latin",
+ nil,
+ nil
+ ),
+ cvlist(
+ ALPHA_GREEK,
+ "greek",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+ "ΑΒΞΔΕΦΓΘΙΪΚΛΜΝΟΠΨΡΣΤΥΫΩΧΗΖαβξδεφγθιϊκλμνοπψρστυϋωχηζ"
+ ),
+ cvlist(
+ ALPHA_CYRILLIC,
+ "cyrillic",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+ "АБЧДЭФГШИЙХЛМНОПЕРЩЦУВЮХЯЖабчдэфгшийхлмноперщцувюхяж"
+ ),
+ cvlist(-1, nil, nil, nil)
+};
+
+alphabet := ALPHA_LATIN; # per window perhaps
+
+setalphabet(s: string)
+{
+ for(a := 0; latintab[a].ld != -1; a++){
+ k := latintab[a].ld;
+ for(i := 0; latintab[i].ld != -1; i++){
+ if(s == transs(latintab[i].nm, k)){
+ alphabet = latintab[i].ld;
+ return;
+ }
+ }
+ }
+}
+
+transc(c: int, k: int): int
+{
+ for(i := 0; latintab[i].ld != -1; i++){
+ if(k == latintab[i].ld){
+ si := latintab[i].si;
+ so := latintab[i].so;
+ ln := len si;
+ for(j := 0; j < ln; j++)
+ if(c == si[j])
+ return so[j];
+ }
+ }
+ return c;
+}
+
+transs(s: string, k: int): string
+{
+ ln := len s;
+ for(i := 0; i < ln; i++)
+ s[i] = transc(s[i], k);
+ return s;
+}
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ framem = mods.framem;
+ dat = mods.dat;
+ utils = mods.utils;
+ drawm = mods.draw;
+ acme = mods.acme;
+ graph = mods.graph;
+ gui = mods.gui;
+ scrl = mods.scroll;
+ bufferm = mods.bufferm;
+ filem = mods.filem;
+ columnm = mods.columnm;
+ windowm = mods.windowm;
+ exec = mods.exec;
+}
+
+TABDIR : con 3; # width of tabs in directory windows
+
+# remove eventually
+KF : con 16rF000;
+Kup : con KF | 16r0E;
+Kleft : con KF | 16r11;
+Kright : con KF | 16r12;
+Kend : con KF | 16r18;
+Kdown : con 16r80;
+
+
+nulltext : Text;
+
+newtext() : ref Text
+{
+ t := ref nulltext;
+ t.frame = framem->newframe();
+ return t;
+}
+
+Text.init(t : self ref Text, f : ref File, r : Rect, rf : ref Dat->Reffont, cols : array of ref Image)
+{
+ t.file = f;
+ t.all = r;
+ t.scrollr = r;
+ t.scrollr.max.x = r.min.x+Scrollwid;
+ t.lastsr = dat->nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ t.eq0 = ~0;
+ t.ncache = 0;
+ t.reffont = rf;
+ t.tabstop = dat->maxtab;
+ for(i:=0; i<Framem->NCOL; i++)
+ t.frame.cols[i] = cols[i];
+ t.redraw(r, rf.f, mainwin, -1);
+}
+
+Text.redraw(t : self ref Text, r : Rect, f : ref Draw->Font, b : ref Image, odx : int)
+{
+ framem->frinit(t.frame, r, f, b, t.frame.cols);
+ rr := t.frame.r;
+ rr.min.x -= Scrollwid; # back fill to scroll bar
+ draw(t.frame.b, rr, t.frame.cols[Framem->BACK], nil, (0, 0));
+ # use no wider than 3-space tabs in a directory
+ maxt := dat->maxtab;
+ if(t.what == Body){
+ if(t.w != nil && t.w.isdir)
+ maxt = min(TABDIR, dat->maxtab);
+ else
+ maxt = t.tabstop;
+ }
+ t.frame.maxtab = maxt*charwidth(f, '0');
+ # c = '0';
+ # if(t.what==Body && t.w!=nil && t.w.isdir)
+ # c = ' ';
+ # t.frame.maxtab = Dat->Maxtab*charwidth(f, c);
+ if(t.what==Body && t.w.isdir && odx!=t.all.dx()){
+ if(t.frame.maxlines > 0){
+ t.reset();
+ t.columnate(t.w.dlp, t.w.ndl);
+ t.show(0, 0, TRUE);
+ }
+ }else{
+ t.fill();
+ t.setselect(t.q0, t.q1);
+ }
+}
+
+Text.reshape(t : self ref Text, r : Rect) : int
+{
+ odx : int;
+
+ if(r.dy() > 0)
+ r.max.y -= r.dy()%t.frame.font.height;
+ else
+ r.max.y = r.min.y;
+ odx = t.all.dx();
+ t.all = r;
+ t.scrollr = r;
+ t.scrollr.max.x = r.min.x+Scrollwid;
+ t.lastsr = dat->nullrect;
+ r.min.x += Scrollwid+Scrollgap;
+ framem->frclear(t.frame, 0);
+ # t.redraw(r, t.frame.font, t.frame.b, odx);
+ t.redraw(r, t.frame.font, mainwin, odx);
+ return r.max.y;
+}
+
+Text.close(t : self ref Text)
+{
+ t.cache = nil;
+ framem->frclear(t.frame, 1);
+ t.file.deltext(t);
+ t.file = nil;
+ t.reffont.close();
+ if(dat->argtext == t)
+ dat->argtext = nil;
+ if(dat->typetext == t)
+ dat->typetext = nil;
+ if(dat->seltext == t)
+ dat->seltext = nil;
+ if(dat->mousetext == t)
+ dat->mousetext = nil;
+ if(dat->barttext == t)
+ dat->barttext = nil;
+}
+
+dircmp(da : ref Dirlist, db : ref Dirlist) : int
+{
+ if (da.r < db.r)
+ return -1;
+ if (da.r > db.r)
+ return 1;
+ return 0;
+}
+
+qsort(a : array of ref Dirlist, n : int)
+{
+ i, j : int;
+ t : ref Dirlist;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && dircmp(a[i], a[0]) < 0);
+ do
+ j--;
+ while(j > 0 && dircmp(a[j], a[0]) > 0);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
+
+Text.columnate(t : self ref Text, dlp : array of ref Dirlist, ndl : int)
+{
+ i, j, w, colw, mint, maxt, ncol, nrow : int;
+ dl : ref Dirlist;
+ q1 : int;
+
+ if(t.file.ntext > 1)
+ return;
+ mint = charwidth(t.frame.font, '0');
+ # go for narrower tabs if set more than 3 wide
+ t.frame.maxtab = min(dat->maxtab, TABDIR)*mint;
+ maxt = t.frame.maxtab;
+ colw = 0;
+ for(i=0; i<ndl; i++){
+ dl = dlp[i];
+ w = dl.wid;
+ if(maxt-w%maxt < mint)
+ w += mint;
+ if(w % maxt)
+ w += maxt-(w%maxt);
+ if(w > colw)
+ colw = w;
+ }
+ if(colw == 0)
+ ncol = 1;
+ else
+ ncol = utils->max(1, t.frame.r.dx()/colw);
+ nrow = (ndl+ncol-1)/ncol;
+
+ q1 = 0;
+ for(i=0; i<nrow; i++){
+ for(j=i; j<ndl; j+=nrow){
+ dl = dlp[j];
+ t.file.insert(q1, dl.r, len dl.r);
+ q1 += len dl.r;
+ if(j+nrow >= ndl)
+ break;
+ w = dl.wid;
+ if(maxt-w%maxt < mint){
+ t.file.insert(q1, "\t", 1);
+ q1++;
+ w += mint;
+ }
+ do{
+ t.file.insert(q1, "\t", 1);
+ q1++;
+ w += maxt-(w%maxt);
+ }while(w < colw);
+ }
+ t.file.insert(q1, "\n", 1);
+ q1++;
+ }
+}
+
+Text.loadx(t : self ref Text, q0 : int, file : string, setqid : int) : int
+{
+ rp : ref Astring;
+ dl : ref Dirlist;
+ dlp : array of ref Dirlist;
+ i, n, ndl : int;
+ fd : ref Sys->FD;
+ q, q1 : int;
+ d : Dir;
+ u : ref Text;
+ ok : int;
+
+ if(t.ncache!=0 || t.file.buf.nc || t.w==nil || t!=t.w.body || (t.w.isdir && t.file.name==nil))
+ error("text.load");
+
+ {
+ fd = sys->open(file, Sys->OREAD);
+ if(fd == nil){
+ warning(nil, sprint("can't open %s: %r\n", file));
+ raise "e";
+ }
+ (ok, d) = sys->fstat(fd);
+ if(ok){
+ warning(nil, sprint("can't fstat %s: %r\n", file));
+ raise "e";
+ }
+ if(d.qid.qtype & Sys->QTDIR){
+ # this is checked in get() but it's possible the file changed underfoot
+ if(t.file.ntext > 1){
+ warning(nil, sprint("%s is a directory; can't read with multiple windows on it\n", file));
+ raise "e";
+ }
+ t.w.isdir = TRUE;
+ t.w.filemenu = FALSE;
+ if(t.file.name[len t.file.name-1] != '/')
+ t.w.setname(t.file.name + "/", len t.file.name+1);
+ dlp = nil;
+ ndl = 0;
+ for(;;){
+ (nd, dbuf) := sys->dirread(fd);
+ if(nd <= 0)
+ break;
+ for(i=0; i<nd; i++){
+ dl = ref Dirlist;
+ dl.r = dbuf[i].name;
+ if(dbuf[i].mode & Sys->DMDIR)
+ dl.r = dl.r + "/";
+ dl.wid = graph->strwidth(t.frame.font, dl.r);
+ ndl++;
+ odlp := dlp;
+ dlp = array[ndl] of ref Dirlist;
+ dlp[0:] = odlp[0:ndl-1];
+ odlp = nil;
+ dlp[ndl-1] = dl;
+ }
+ }
+ qsort(dlp, ndl);
+ t.w.dlp = dlp;
+ t.w.ndl = ndl;
+ t.columnate(dlp, ndl);
+ q1 = t.file.buf.nc;
+ }else{
+ tmp : int;
+
+ t.w.isdir = FALSE;
+ t.w.filemenu = TRUE;
+ tmp = t.file.loadx(q0, fd);
+ q1 = q0 + tmp;
+ }
+ fd = nil;
+ if(setqid){
+ t.file.dev = d.dev;
+ t.file.mtime = d.mtime;
+ t.file.qidpath = d.qid.path;
+ }
+ rp = stralloc(BUFSIZE);
+ for(q=q0; q<q1; q+=n){
+ n = q1-q;
+ if(n > Dat->BUFSIZE)
+ n = Dat->BUFSIZE;
+ t.file.buf.read(q, rp, 0, n);
+ if(q < t.org)
+ t.org += n;
+ else if(q <= t.org+t.frame.nchars)
+ frinsert(t.frame, rp.s, n, q-t.org);
+ if(t.frame.lastlinefull)
+ break;
+ }
+ strfree(rp);
+ rp = nil;
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u != t){
+ if(u.org > u.file.buf.nc) # will be 0 because of reset(), but safety first
+ u.org = 0;
+ u.reshape(u.all);
+ u.backnl(u.org, 0); # go to beginning of line
+ }
+ u.setselect(q0, q0);
+ }
+ return q1-q0;
+ }
+ exception{
+ * =>
+ fd = nil;
+ return 0;
+ }
+ return 0;
+}
+
+Text.bsinsert(t : self ref Text, q0 : int, r : string, n : int, tofile : int) : (int, int)
+{
+ tp : ref Astring;
+ bp, up : int;
+ i, initial : int;
+
+ {
+ if(t.what == Tag) # can't happen but safety first: mustn't backspace over file name
+ raise "e";
+ bp = 0;
+ for(i=0; i<n; i++)
+ if(r[bp++] == '\b'){
+ --bp;
+ initial = 0;
+ tp = utils->stralloc(n);
+ for (k := 0; k < i; k++)
+ tp.s[k] = r[k];
+ up = i;
+ for(; i<n; i++){
+ tp.s[up] = r[bp++];
+ if(tp.s[up] == '\b')
+ if(up == 0)
+ initial++;
+ else
+ --up;
+ else
+ up++;
+ }
+ if(initial){
+ if(initial > q0)
+ initial = q0;
+ q0 -= initial;
+ t.delete(q0, q0+initial, tofile);
+ }
+ n = up;
+ t.insert(q0, tp.s, n, tofile, 0);
+ strfree(tp);
+ tp = nil;
+ return (q0, n);
+ }
+ raise "e";
+ return(0, 0);
+ }
+ exception{
+ * =>
+ t.insert(q0, r, n, tofile, 0);
+ return (q0, n);
+ }
+ return (0, 0);
+}
+
+Text.insert(t : self ref Text, q0 : int, r : string, n : int, tofile : int, echomode : int)
+{
+ c, i : int;
+ u : ref Text;
+
+ if(tofile && t.ncache != 0)
+ error("text.insert");
+ if(n == 0)
+ return;
+ if(tofile){
+ t.file.insert(q0, r, n);
+ if(t.what == Body){
+ t.w.dirty = TRUE;
+ t.w.utflastqid = -1;
+ }
+ if(t.file.ntext > 1)
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u != t){
+ u.w.dirty = TRUE; # always a body
+ u.insert(q0, r, n, FALSE, echomode);
+ u.setselect(u.q0, u.q1);
+ scrdraw(u);
+ }
+ }
+ }
+ if(q0 < t.q1)
+ t.q1 += n;
+ if(q0 < t.q0)
+ t.q0 += n;
+ if(q0 < t.org)
+ t.org += n;
+ else if(q0 <= t.org+t.frame.nchars) {
+ if (echomode == EM_MASK && len r == 1 && r[0] != '\n')
+ frinsert(t.frame, "*", n, q0-t.org);
+ else
+ frinsert(t.frame, r, n, q0-t.org);
+ }
+ if(t.w != nil){
+ c = 'i';
+ if(t.what == Body)
+ c = 'I';
+ if(n <= Dat->EVENTSIZE)
+ t.w.event(sprint("%c%d %d 0 %d %s\n", c, q0, q0+n, n, r[0:n]));
+ else
+ t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q0+n));
+ }
+}
+
+Text.fill(t : self ref Text)
+{
+ rp : ref Astring;
+ i, n, m, nl : int;
+
+ if(t.frame.lastlinefull || t.nofill)
+ return;
+ if(t.ncache > 0){
+ if(t.w != nil)
+ t.w.commit(t);
+ else
+ t.commit(TRUE);
+ }
+ rp = stralloc(BUFSIZE);
+ do{
+ n = t.file.buf.nc-(t.org+t.frame.nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) # educated guess at reasonable amount
+ n = 2000;
+ t.file.buf.read(t.org+t.frame.nchars, rp, 0, n);
+ #
+ # it's expensive to frinsert more than we need, so
+ # count newlines.
+ #
+
+ nl = t.frame.maxlines-t.frame.nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp.s[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(t.frame, rp.s, i, t.frame.nchars);
+ }while(t.frame.lastlinefull == FALSE);
+ strfree(rp);
+ rp = nil;
+}
+
+Text.delete(t : self ref Text, q0 : int, q1 : int, tofile : int)
+{
+ n, p0, p1 : int;
+ i, c : int;
+ u : ref Text;
+
+ if(tofile && t.ncache != 0)
+ error("text.delete");
+ n = q1-q0;
+ if(n == 0)
+ return;
+ if(tofile){
+ t.file.delete(q0, q1);
+ if(t.what == Body){
+ t.w.dirty = TRUE;
+ t.w.utflastqid = -1;
+ }
+ if(t.file.ntext > 1)
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u != t){
+ u.w.dirty = TRUE; # always a body
+ u.delete(q0, q1, FALSE);
+ u.setselect(u.q0, u.q1);
+ scrdraw(u);
+ }
+ }
+ }
+ if(q0 < t.q0)
+ t.q0 -= min(n, t.q0-q0);
+ if(q0 < t.q1)
+ t.q1 -= min(n, t.q1-q0);
+ if(q1 <= t.org)
+ t.org -= n;
+ else if(q0 < t.org+t.frame.nchars){
+ p1 = q1 - t.org;
+ if(p1 > t.frame.nchars)
+ p1 = t.frame.nchars;
+ if(q0 < t.org){
+ t.org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - t.org;
+ frdelete(t.frame, p0, p1);
+ t.fill();
+ }
+ if(t.w != nil){
+ c = 'd';
+ if(t.what == Body)
+ c = 'D';
+ t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q1));
+ }
+}
+
+onechar : ref Astring;
+
+Text.readc(t : self ref Text, q : int) : int
+{
+ if(t.cq0<=q && q<t.cq0+t.ncache)
+ return t.cache[q-t.cq0];
+ if (onechar == nil)
+ onechar = stralloc(1);
+ t.file.buf.read(q, onechar, 0, 1);
+ return onechar.s[0];
+}
+
+Text.bswidth(t : self ref Text, c : int) : int
+{
+ q, eq : int;
+ r : int;
+ skipping : int;
+
+ # there is known to be at least one character to erase
+ if(c == 16r08) # ^H: erase character
+ return 1;
+ q = t.q0;
+ skipping = TRUE;
+ while(q > 0){
+ r = t.readc(q-1);
+ if(r == '\n'){ # eat at most one more character
+ if(q == t.q0) # eat the newline
+ --q;
+ break;
+ }
+ if(c == 16r17){
+ eq = isalnum(r);
+ if(eq && skipping) # found one; stop skipping
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return t.q0-q;
+}
+
+Text.typex(t : self ref Text, r : int, echomode : int)
+{
+ q0, q1 : int;
+ nnb, nb, n, i : int;
+ u : ref Text;
+
+ if(alphabet != ALPHA_LATIN)
+ r = transc(r, alphabet);
+ if (echomode == EM_RAW && t.what == Body) {
+ if (t.w != nil) {
+ s := "a";
+ s[0] = r;
+ t.w.event(sprint("R0 0 0 1 %s\n", s));
+ }
+ return;
+ }
+ if(t.what!=Body && r=='\n')
+ return;
+ case r {
+ Dat->Kscrolldown=>
+ if(t.what == Body){
+ q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+2*t.frame.font.height));
+ t.setorigin(q0, FALSE);
+ }
+ return;
+ Dat->Kscrollup=>
+ if(t.what == Body){
+ q0 = t.backnl(t.org, 4);
+ t.setorigin(q0, FALSE);
+ }
+ return;
+ Kdown or Keyboard->Down =>
+ n = t.frame.maxlines/3;
+ q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height));
+ t.setorigin(q0, FALSE);
+ return;
+ Keyboard->Pgdown =>
+ n = 2*t.frame.maxlines/3;
+ q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height));
+ t.setorigin(q0, FALSE);
+ return;
+ Kup or Keyboard->Up =>
+ n = t.frame.maxlines/3;
+ q0 = t.backnl(t.org, n);
+ t.setorigin(q0, FALSE);
+ return;
+ Keyboard->Pgup =>
+ n = 2*t.frame.maxlines/3;
+ q0 = t.backnl(t.org, n);
+ t.setorigin(q0, FALSE);
+ return;
+ Keyboard->Home =>
+ t.commit(TRUE);
+ t.show(0, 0, FALSE);
+ return;
+ Kend or Keyboard->End =>
+ t.commit(TRUE);
+ t.show(t.file.buf.nc, t.file.buf.nc, FALSE);
+ return;
+ Kleft or Keyboard->Left =>
+ t.commit(TRUE);
+ if(t.q0 != t.q1)
+ t.show(t.q0, t.q0, TRUE);
+ else if(t.q0 != 0)
+ t.show(t.q0-1, t.q0-1, TRUE);
+ return;
+ Kright or Keyboard->Right =>
+ t.commit(TRUE);
+ if(t.q0 != t.q1)
+ t.show(t.q1, t.q1, TRUE);
+ else if(t.q1 != t.file.buf.nc)
+ t.show(t.q1+1, t.q1+1, TRUE);
+ return;
+ 1 => # ^A: beginning of line
+ t.commit(TRUE);
+ # go to where ^U would erase, if not already at BOL
+ nnb = 0;
+ if(t.q0>0 && t.readc(t.q0-1)!='\n')
+ nnb = t.bswidth(16r15);
+ t.show(t.q0-nnb, t.q0-nnb, TRUE);
+ return;
+ 5 => # ^E: end of line
+ t.commit(TRUE);
+ q0 = t.q0;
+ while(q0<t.file.buf.nc && t.readc(q0)!='\n')
+ q0++;
+ t.show(q0, q0, TRUE);
+ return;
+ }
+ if(t.what == Body){
+ seq++;
+ t.file.mark();
+ }
+ if(t.q1 > t.q0){
+ if(t.ncache != 0)
+ error("text.type");
+ exec->cut(t, t, TRUE, TRUE);
+ t.eq0 = ~0;
+ if (r == 16r08 || r == 16r7f){ # erase character : odd if a char then erased
+ t.show(t.q0, t.q0,TRUE);
+ return;
+ }
+ }
+ t.show(t.q0, t.q0, TRUE);
+ case(r){
+ 16r1B =>
+ if(t.eq0 != ~0)
+ t.setselect(t.eq0, t.q0);
+ if(t.ncache > 0){
+ if(t.w != nil)
+ t.w.commit(t);
+ else
+ t.commit(TRUE);
+ }
+ return;
+ 16r08 or 16r15 or 16r17 =>
+ # ^H: erase character or ^U: erase line or ^W: erase word
+ if(t.q0 == 0)
+ return;
+if(0) # DEBUGGING
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0))
+ error("text.type inconsistent caches");
+ }
+ nnb = t.bswidth(r);
+ q1 = t.q0;
+ q0 = q1-nnb;
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ u.nofill = TRUE;
+ nb = nnb;
+ n = u.ncache;
+ if(n > 0){
+ if(q1 != u.cq0+n)
+ error("text.type backspace");
+ if(n > nb)
+ n = nb;
+ u.ncache -= n;
+ u.delete(q1-n, q1, FALSE);
+ nb -= n;
+ }
+ if(u.eq0==q1 || u.eq0==~0)
+ u.eq0 = q0;
+ if(nb && u==t)
+ u.delete(q0, q0+nb, TRUE);
+ if(u != t)
+ u.setselect(u.q0, u.q1);
+ else
+ t.setselect(q0, q0);
+ u.nofill = FALSE;
+ }
+ for(i=0; i<t.file.ntext; i++)
+ t.file.text[i].fill();
+ return;
+ 16r7f or Keyboard->Del =>
+ # Delete character - forward delete
+ t.commit(TRUE);
+ if(t.q0 >= t.file.buf.nc)
+ return;
+ nnb = 1;
+ q0 = t.q0;
+ q1 = q0+nnb;
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if (u!=t)
+ u.commit(FALSE);
+ u.nofill = TRUE;
+ if(u.eq0==q1 || u.eq0==~0)
+ u.eq0 = q0;
+ if(u==t)
+ u.delete(q0, q1, TRUE);
+ if(u != t)
+ u.setselect(u.q0, u.q1);
+ else
+ t.setselect(q0, q0);
+ u.nofill = FALSE;
+ }
+ for(i=0; i<t.file.ntext; i++)
+ t.file.text[i].fill();
+ return;
+ }
+ # otherwise ordinary character; just insert, typically in caches of all texts
+if(0) # DEBUGGING
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0))
+ error("text.type inconsistent caches");
+ }
+ for(i=0; i<t.file.ntext; i++){
+ u = t.file.text[i];
+ if(u.eq0 == ~0)
+ u.eq0 = t.q0;
+ if(u.ncache == 0)
+ u.cq0 = t.q0;
+ else if(t.q0 != u.cq0+u.ncache)
+ error("text.type cq1");
+ str := "Z";
+ str[0] = r;
+ u.insert(t.q0, str, 1, FALSE, echomode);
+ str = nil;
+ if(u != t)
+ u.setselect(u.q0, u.q1);
+ if(u.ncache == u.ncachealloc){
+ u.ncachealloc += 10;
+ u.cache += "1234567890";
+ }
+ u.cache[u.ncache++] = r;
+ }
+ t.setselect(t.q0+1, t.q0+1);
+ if(r=='\n' && t.w!=nil)
+ t.w.commit(t);
+}
+
+Text.commit(t : self ref Text, tofile : int)
+{
+ if(t.ncache == 0)
+ return;
+ if(tofile)
+ t.file.insert(t.cq0, t.cache, t.ncache);
+ if(t.what == Body){
+ t.w.dirty = TRUE;
+ t.w.utflastqid = -1;
+ }
+ t.ncache = 0;
+}
+
+clicktext : ref Text;
+clickmsec : int = 0;
+selecttext : ref Text;
+selectq : int = 0;
+
+#
+# called from frame library
+#
+
+framescroll(f : ref Frame, dl : int)
+{
+ if(f != selecttext.frame)
+ error("frameselect not right frame");
+ selecttext.framescroll(dl);
+}
+
+Text.framescroll(t : self ref Text, dl : int)
+{
+ q0 : int;
+
+ if(dl == 0){
+ scrl->scrsleep(100);
+ return;
+ }
+ if(dl < 0){
+ q0 = t.backnl(t.org, -dl);
+ if(selectq > t.org+t.frame.p0)
+ t.setselect0(t.org+t.frame.p0, selectq);
+ else
+ t.setselect0(selectq, t.org+t.frame.p0);
+ }else{
+ if(t.org+t.frame.nchars == t.file.buf.nc)
+ return;
+ q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+dl*t.frame.font.height));
+ if(selectq > t.org+t.frame.p1)
+ t.setselect0(t.org+t.frame.p1, selectq);
+ else
+ t.setselect0(selectq, t.org+t.frame.p1);
+ }
+ t.setorigin(q0, TRUE);
+}
+
+
+Text.select(t : self ref Text, double : int)
+{
+ q0, q1 : int;
+ b, x, y : int;
+ state : int;
+
+ selecttext = t;
+ #
+ # To have double-clicking and chording, we double-click
+ # immediately if it might make sense.
+ #
+
+ b = mouse.buttons;
+ q0 = t.q0;
+ q1 = t.q1;
+ selectq = t.org+frcharofpt(t.frame, mouse.xy);
+ if(double || (clicktext==t && mouse.msec-clickmsec<500))
+ if(q0==q1 && selectq==q0){
+ (q0, q1) = t.doubleclick(q0, q1);
+ t.setselect(q0, q1);
+ bflush();
+ x = mouse.xy.x;
+ y = mouse.xy.y;
+ # stay here until something interesting happens
+ do
+ frgetmouse();
+ while(mouse.buttons==b && utils->abs(mouse.xy.x-x)<3 && utils->abs(mouse.xy.y-y)<3);
+ mouse.xy.x = x; # in case we're calling frselect
+ mouse.xy.y = y;
+ q0 = t.q0; # may have changed
+ q1 = t.q1;
+ selectq = q0;
+ }
+ if(mouse.buttons == b){
+ t.frame.scroll = 1;
+ frselect(t.frame, mouse);
+ # horrible botch: while asleep, may have lost selection altogether
+ if(selectq > t.file.buf.nc)
+ selectq = t.org + t.frame.p0;
+ t.frame.scroll = 0;
+ if(selectq < t.org)
+ q0 = selectq;
+ else
+ q0 = t.org + t.frame.p0;
+ if(selectq > t.org+t.frame.nchars)
+ q1 = selectq;
+ else
+ q1 = t.org+t.frame.p1;
+ }
+ if(q0 == q1){
+ if(q0==t.q0 && (double || clicktext==t && mouse.msec-clickmsec<500)){
+ (q0, q1) = t.doubleclick(q0, q1);
+ clicktext = nil;
+ }else{
+ clicktext = t;
+ clickmsec = mouse.msec;
+ }
+ }else
+ clicktext = nil;
+ t.setselect(q0, q1);
+ bflush();
+ state = 0; # undo when possible; +1 for cut, -1 for paste
+ while(mouse.buttons){
+ mouse.msec = 0;
+ b = mouse.buttons;
+ if(b & 6){
+ if(state==0 && t.what==Body){
+ seq++;
+ t.w.body.file.mark();
+ }
+ if(b & 2){
+ if(state==-1 && t.what==Body){
+ t.w.undo(TRUE);
+ t.setselect(q0, t.q0);
+ state = 0;
+ }else if(state != 1){
+ exec->cut(t, t, TRUE, TRUE);
+ state = 1;
+ }
+ }else{
+ if(state==1 && t.what==Body){
+ t.w.undo(TRUE);
+ t.setselect(q0, t.q1);
+ state = 0;
+ }else if(state != -1){
+ exec->paste(t, t, TRUE, FALSE);
+ state = -1;
+ }
+ }
+ scrdraw(t);
+ utils->clearmouse();
+ }
+ bflush();
+ while(mouse.buttons == b)
+ frgetmouse();
+ clicktext = nil;
+ }
+}
+
+Text.show(t : self ref Text, q0 : int, q1 : int, doselect : int)
+{
+ qe : int;
+ nl : int;
+ q : int;
+
+ if(t.what != Body){
+ if(doselect)
+ t.setselect(q0, q1);
+ return;
+ }
+ if(t.w!=nil && t.frame.maxlines==0)
+ t.col.grow(t.w, 1, 0);
+ if(doselect)
+ t.setselect(q0, q1);
+ qe = t.org+t.frame.nchars;
+ if(t.org<=q0 && (q0<qe || (q0==qe && qe==t.file.buf.nc+t.ncache)))
+ scrdraw(t);
+ else{
+ if(t.w.nopen[Dat->QWevent]>byte 0)
+ nl = 3*t.frame.maxlines/4;
+ else
+ nl = t.frame.maxlines/4;
+ q = t.backnl(q0, nl);
+ # avoid going backwards if trying to go forwards - long lines!
+ if(!(q0>t.org && q<t.org))
+ t.setorigin(q, TRUE);
+ while(q0 > t.org+t.frame.nchars)
+ t.setorigin(t.org+1, FALSE);
+ }
+}
+
+region(a, b : int) : int
+{
+ if(a < b)
+ return -1;
+ if(a == b)
+ return 0;
+ return 1;
+}
+
+selrestore(f : ref Frame, pt0 : Point, p0 : int, p1 : int)
+{
+ if(p1<=f.p0 || p0>=f.p1){
+ # no overlap
+ frdrawsel0(f, pt0, p0, p1, f.cols[BACK], f.cols[TEXT]);
+ return;
+ }
+ if(p0>=f.p0 && p1<=f.p1){
+ # entirely inside
+ frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]);
+ return;
+ }
+ # they now are known to overlap
+ # before selection
+ if(p0 < f.p0){
+ frdrawsel0(f, pt0, p0, f.p0, f.cols[BACK], f.cols[TEXT]);
+ p0 = f.p0;
+ pt0 = frptofchar(f, p0);
+ }
+ # after selection
+ if(p1 > f.p1){
+ frdrawsel0(f, frptofchar(f, f.p1), f.p1, p1, f.cols[BACK], f.cols[TEXT]);
+ p1 = f.p1;
+ }
+ # inside selection
+ frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]);
+}
+
+Text.setselect(t : self ref Text, q0 : int, q1 : int)
+{
+ p0, p1 : int;
+
+ # t.p0 and t.p1 are always right; t.q0 and t.q1 may be off
+ t.q0 = q0;
+ t.q1 = q1;
+ # compute desired p0,p1 from q0,q1
+ p0 = q0-t.org;
+ p1 = q1-t.org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > t.frame.nchars)
+ p0 = t.frame.nchars;
+ if(p1 > t.frame.nchars)
+ p1 = t.frame.nchars;
+ if(p0==t.frame.p0 && p1==t.frame.p1)
+ return;
+ # screen disagrees with desired selection
+ if(t.frame.p1<=p0 || p1<=t.frame.p0 || p0==p1 || t.frame.p1==t.frame.p0){
+ # no overlap or too easy to bother trying
+ frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, t.frame.p1, 0);
+ frdrawsel(t.frame, frptofchar(t.frame, p0), p0, p1, 1);
+ t.frame.p0 = p0;
+ t.frame.p1 = p1;
+ return;
+ }
+ # overlap; avoid unnecessary painting
+ if(p0 < t.frame.p0){
+ # extend selection backwards
+ frdrawsel(t.frame, frptofchar(t.frame, p0), p0, t.frame.p0, 1);
+ }else if(p0 > t.frame.p0){
+ # trim first part of selection
+ frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, p0, 0);
+ }
+ if(p1 > t.frame.p1){
+ # extend selection forwards
+ frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1), t.frame.p1, p1, 1);
+ }else if(p1 < t.frame.p1){
+ # trim last part of selection
+ frdrawsel(t.frame, frptofchar(t.frame, p1), p1, t.frame.p1, 0);
+ }
+ t.frame.p0 = p0;
+ t.frame.p1 = p1;
+}
+
+Text.setselect0(t : self ref Text, q0 : int, q1 : int)
+{
+ t.q0 = q0;
+ t.q1 = q1;
+}
+
+xselect(f : ref Frame, mc : ref Draw->Pointer, col, colt : ref Image) : (int, int)
+{
+ p0, p1, q, tmp : int;
+ mp, pt0, pt1, qt : Point;
+ reg, b : int;
+
+ # when called button 1 is down
+ mp = mc.xy;
+ b = mc.buttons;
+
+ # remove tick
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 0);
+ p0 = p1 = frcharofpt(f, mp);
+ pt0 = frptofchar(f, p0);
+ pt1 = frptofchar(f, p1);
+ reg = 0;
+ frtick(f, pt0, 1);
+ do{
+ q = frcharofpt(f, mc.xy);
+ if(p1 != q){
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ if(reg != region(q, p0)){ # crossed starting point; reset
+ if(reg > 0)
+ selrestore(f, pt0, p0, p1);
+ else if(reg < 0)
+ selrestore(f, pt1, p1, p0);
+ p1 = p0;
+ pt1 = pt0;
+ reg = region(q, p0);
+ if(reg == 0)
+ frdrawsel0(f, pt0, p0, p1, col, colt);
+ }
+ qt = frptofchar(f, q);
+ if(reg > 0){
+ if(q > p1)
+ frdrawsel0(f, pt1, p1, q, col, colt);
+ else if(q < p1)
+ selrestore(f, qt, q, p1);
+ }else if(reg < 0){
+ if(q > p1)
+ selrestore(f, pt1, p1, q);
+ else
+ frdrawsel0(f, qt, q, p1, col, colt);
+ }
+ p1 = q;
+ pt1 = qt;
+ }
+ if(p0 == p1)
+ frtick(f, pt0, 1);
+ bflush();
+ frgetmouse();
+ }while(mc.buttons == b);
+ if(p1 < p0){
+ tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+ pt0 = frptofchar(f, p0);
+ if(p0 == p1)
+ frtick(f, pt0, 0);
+ selrestore(f, pt0, p0, p1);
+ # restore tick
+ if(f.p0 == f.p1)
+ frtick(f, frptofchar(f, f.p0), 1);
+ bflush();
+ return (p0, p1);
+}
+
+Text.select23(t : self ref Text, q0 : int, q1 : int, high, low : ref Image, mask : int) : (int, int, int)
+{
+ p0, p1 : int;
+ buts : int;
+
+ (p0, p1) = xselect(t.frame, mouse, high, low);
+ buts = mouse.buttons;
+ if((buts & mask) == 0){
+ q0 = p0+t.org;
+ q1 = p1+t.org;
+ }
+ while(mouse.buttons)
+ frgetmouse();
+ return (buts, q0, q1);
+}
+
+Text.select2(t : self ref Text, q0 : int, q1 : int) : (int, ref Text, int, int)
+{
+ buts : int;
+
+ (buts, q0, q1) = t.select23(q0, q1, acme->but2col, acme->but2colt, 4);
+ if(buts & 4)
+ return (0, nil, q0, q1);
+ if(buts & 1) # pick up argument
+ return (1, dat->argtext, q0, q1);
+ return (1, nil, q0, q1);
+}
+
+Text.select3(t : self ref Text, q0 : int, q1 : int) : (int, int, int)
+{
+ buts : int;
+
+ (buts, q0, q1) = t.select23(q0, q1, acme->but3col, acme->but3colt, 1|2);
+ return (buts == 0, q0, q1);
+}
+
+left := array[4] of {
+ "{[(<«",
+ "\n",
+ "'\"`",
+ nil
+};
+right := array[4] of {
+ "}])>»",
+ "\n",
+ "'\"`",
+ nil
+};
+
+Text.doubleclick(t : self ref Text, q0 : int, q1 : int) : (int, int)
+{
+ c, i : int;
+ r, l : string;
+ p : int;
+ q : int;
+ res : int;
+
+ for(i=0; left[i]!=nil; i++){
+ q = q0;
+ l = left[i];
+ r = right[i];
+ # try matching character to left, looking right
+ if(q == 0)
+ c = '\n';
+ else
+ c = t.readc(q-1);
+ p = utils->strchr(l, c);
+ if(p >= 0){
+ (res, q) = t.clickmatch(c, r[p], 1, q);
+ if (res)
+ q1 = q-(c!='\n');
+ return (q0, q1);
+ }
+ # try matching character to right, looking left
+ if(q == t.file.buf.nc)
+ c = '\n';
+ else
+ c = t.readc(q);
+ p = utils->strchr(r, c);
+ if(p >= 0){
+ (res, q) = t.clickmatch(c, l[p], -1, q);
+ if (res){
+ q1 = q0+(q0<t.file.buf.nc && c=='\n');
+ q0 = q;
+ if(c!='\n' || q!=0 || t.readc(0)=='\n')
+ q0++;
+ }
+ return (q0, q1);
+ }
+ }
+ # try filling out word to right
+ while(q1<t.file.buf.nc && isalnum(t.readc(q1)))
+ q1++;
+ # try filling out word to left
+ while(q0>0 && isalnum(t.readc(q0-1)))
+ q0--;
+ return (q0, q1);
+}
+
+Text.clickmatch(t : self ref Text, cl : int, cr : int, dir : int, q : int) : (int, int)
+{
+ c : int;
+ nest : int;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(q == t.file.buf.nc)
+ break;
+ c = t.readc(q);
+ q++;
+ }else{
+ if(q == 0)
+ break;
+ q--;
+ c = t.readc(q);
+ }
+ if(c == cr){
+ if(--nest==0)
+ return (1, q);
+ }else if(c == cl)
+ nest++;
+ }
+ return (cl=='\n' && nest==1, q);
+}
+
+Text.forwnl(t : self ref Text, p : int, n : int) : int
+{
+ i, j : int;
+
+ e := t.file.buf.nc-1;
+ i = n;
+ while(i-- > 0 && p<e){
+ ++p;
+ if(p == e)
+ break;
+ for(j=128; --j>0 && p<e; p++)
+ if(t.readc(p)=='\n')
+ break;
+ }
+ return p;
+}
+
+Text.backnl(t : self ref Text, p : int, n : int) : int
+{
+ i, j : int;
+
+ # look for start of this line if n==0
+ if(n==0 && p>0 && t.readc(p-1)!='\n')
+ n = 1;
+ i = n;
+ while(i-- > 0 && p>0){
+ --p; # it's at a newline now; back over it
+ if(p == 0)
+ break;
+ # at 128 chars, call it a line anyway
+ for(j=128; --j>0 && p>0; p--)
+ if(t.readc(p-1)=='\n')
+ break;
+ }
+ return p;
+}
+
+Text.setorigin(t : self ref Text, org : int, exact : int)
+{
+ i, a : int;
+ r : ref Astring;
+ n : int;
+
+ t.frame.b.flush(Flushoff);
+ if(org>0 && !exact){
+ # org is an estimate of the char posn; find a newline
+ # don't try harder than 256 chars
+ for(i=0; i<256 && org<t.file.buf.nc; i++){
+ if(t.readc(org) == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-t.org;
+ fixup := 0;
+ if(a>=0 && a<t.frame.nchars){
+ frdelete(t.frame, 0, a);
+ fixup = 1; # frdelete can leave end of last line in wrong selection mode; it doesn't know what follows
+ }
+ else if(a<0 && -a<t.frame.nchars){
+ n = t.org - org;
+ r = utils->stralloc(n);
+ t.file.buf.read(org, r, 0, n);
+ frinsert(t.frame, r.s, n, 0);
+ utils->strfree(r);
+ r = nil;
+ }else
+ frdelete(t.frame, 0, t.frame.nchars);
+ t.org = org;
+ t.fill();
+ scrdraw(t);
+ t.setselect(t.q0, t.q1);
+ if(fixup && t.frame.p1 > t.frame.p0)
+ frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1-1), t.frame.p1-1, t.frame.p1, 1);
+ t.frame.b.flush(Flushon);
+}
+
+Text.reset(t : self ref Text)
+{
+ t.file.seq = 0;
+ t.eq0 = ~0;
+ # do t.delete(0, t.nc, TRUE) without building backup stuff
+ t.setselect(t.org, t.org);
+ frdelete(t.frame, 0, t.frame.nchars);
+ t.org = 0;
+ t.q0 = 0;
+ t.q1 = 0;
+ t.file.reset();
+ t.file.buf.reset();
+}
--- /dev/null
+++ b/appl/acme/text.m
@@ -1,0 +1,65 @@
+Textm : module {
+ PATH : con "/dis/acme/text.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ # Text.what
+ Columntag, Rowtag, Tag, Body : con iota;
+
+ newtext : fn() : ref Text;
+
+ Text : adt {
+ file : cyclic ref Filem->File;
+ frame : ref Framem->Frame;
+ reffont : ref Dat->Reffont;
+ org : int;
+ q0 : int;
+ q1 : int;
+ what : int;
+ tabstop : int;
+ w : cyclic ref Windowm->Window;
+ scrollr : Draw->Rect;
+ lastsr : Draw->Rect;
+ all : Draw->Rect;
+ row : cyclic ref Rowm->Row;
+ col : cyclic ref Columnm->Column;
+ eq0 : int; # start of typing for ESC
+ cq0 : int; # cache position
+ ncache : int; # storage for insert
+ ncachealloc : int;
+ cache : string;
+ nofill : int;
+
+ init : fn(t : self ref Text, f : ref Filem->File, r : Draw->Rect, rf : ref Dat->Reffont, cols : array of ref Draw->Image);
+ redraw : fn(t : self ref Text, r : Draw->Rect, f : ref Draw->Font, b : ref Draw->Image, n : int);
+ insert : fn(t : self ref Text, n : int, s : string, p : int, q : int, r : int);
+ bsinsert : fn(t : self ref Text, n : int, s : string, p : int, q : int) : (int, int);
+ delete : fn(t : self ref Text, n : int, p : int, q : int);
+ loadx : fn(t : self ref Text, n : int, b : string, q : int) : int;
+ typex : fn(t : self ref Text, r : int, echomode : int);
+ select : fn(t : self ref Text, d : int);
+ select2 : fn(t : self ref Text, p : int, q : int) : (int, ref Text, int, int);
+ select3 : fn(t : self ref Text, p: int, q : int) : (int, int, int);
+ setselect : fn(t : self ref Text, p : int, q : int);
+ setselect0 : fn(t : self ref Text, p : int, q : int);
+ show : fn(t : self ref Text, p : int, q : int, dosel : int);
+ fill : fn(t : self ref Text);
+ commit : fn(t : self ref Text, n : int);
+ setorigin : fn(t : self ref Text, p : int, q : int);
+ readc : fn(t : self ref Text, n : int) : int;
+ reset : fn(t : self ref Text);
+ reshape : fn(t : self ref Text, r : Draw->Rect) : int;
+ close : fn(t : self ref Text);
+ framescroll : fn(t : self ref Text, n : int);
+ select23 : fn(t : self ref Text, p : int, q : int, i, it : ref Draw->Image, n : int) : (int, int, int);
+ forwnl : fn(t : self ref Text, p : int, q : int) : int;
+ backnl : fn(t : self ref Text, p : int, q : int) : int;
+ bswidth : fn(t : self ref Text, r : int) : int;
+ doubleclick : fn(t : self ref Text, p : int, q : int) : (int, int);
+ clickmatch : fn(t : self ref Text, p : int, q : int, r : int, n : int) : (int, int);
+ columnate : fn(t : self ref Text, d : array of ref Dat->Dirlist, n : int);
+ };
+
+ framescroll : fn(f : ref Framem->Frame, dl : int);
+ setalphabet: fn(s: string);
+};
--- /dev/null
+++ b/appl/acme/time.b
@@ -1,0 +1,129 @@
+implement Timerm;
+
+include "common.m";
+
+sys : Sys;
+acme : Acme;
+utils : Utils;
+dat : Dat;
+
+millisec : import sys;
+Timer : import dat;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ acme = mods.acme;
+ utils = mods.utils;
+ dat = mods.dat;
+}
+
+ctimer : chan of ref Timer;
+
+timeproc()
+{
+ i, nt, na, dt : int;
+ x : ref Timer;
+ t : array of ref Timer;
+ old, new : int;
+
+ acme->timerpid = sys->pctl(0, nil);
+ sys->pctl(Sys->FORKFD, nil);
+ t = array[10] of ref Timer;
+ na = 10;
+ nt = 0;
+ old = millisec();
+ for(;;){
+ if (nt == 0) { # don't waste cpu time
+ x = <-ctimer;
+ t[nt++] = x;
+ old = millisec();
+ }
+ sys->sleep(1); # will sleep minimum incr
+ new = millisec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) # timer wrapped; go around, losing a tick
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x.dt -= dt;
+ if(x.dt <= 0){
+ #
+ # avoid possible deadlock if client is
+ # now sending on ctimer
+ #
+
+ alt {
+ x.c <-= 0 =>
+ t[i:] = t[i+1:nt];
+ t[nt-1] = nil;
+ nt--;
+ i--;
+ * =>
+ ;
+ }
+ }
+ }
+ gotone := 1;
+ while (gotone) {
+ alt {
+ x = <-ctimer =>
+ if (nt == na) {
+ ot := t;
+ t = array[na+10] of ref Timer;
+ t[0:] = ot[0:na];
+ ot = nil;
+ na += 10;
+ }
+ t[nt++] = x;
+ old = millisec();
+ * =>
+ gotone = 0;
+ }
+ }
+ }
+}
+
+timerinit()
+{
+ ctimer = chan of ref Timer;
+ spawn timeproc();
+}
+
+#
+# timeralloc() and timerfree() don't lock, so can only be
+# called from the main proc.
+#
+
+
+timer : ref Timer;
+
+timerstart(dt : int) : ref Timer
+{
+ t : ref Timer;
+
+ t = timer;
+ if(t != nil)
+ timer = timer.next;
+ else{
+ t = ref Timer;
+ t.c = chan of int;
+ }
+ t.next = nil;
+ t.dt = dt;
+ ctimer <-= t;
+ return t;
+}
+
+timerstop(t : ref Timer)
+{
+ t.next = timer;
+ timer = t;
+}
+
+timerwaittask(timer : ref Timer)
+{
+ <-(timer.c);
+ timerstop(timer);
+}
--- /dev/null
+++ b/appl/acme/time.m
@@ -1,0 +1,10 @@
+Timerm : module {
+ PATH : con "/dis/acme/time.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ timerinit: fn();
+ timerstart : fn(dt : int) : ref Dat->Timer;
+ timerstop : fn(t : ref Dat->Timer);
+ timerwaittask : fn(t : ref Dat->Timer);
+};
--- /dev/null
+++ b/appl/acme/util.b
@@ -1,0 +1,574 @@
+implement Utils;
+
+include "common.m";
+include "sh.m";
+include "env.m";
+
+sys : Sys;
+draw : Draw;
+gui : Gui;
+acme : Acme;
+dat : Dat;
+graph : Graph;
+textm : Textm;
+windowm : Windowm;
+columnm : Columnm;
+rowm : Rowm;
+scrl : Scroll;
+look : Look;
+
+RELEASECOPY : import acme;
+Point, Rect : import draw;
+Astring, TRUE, FALSE, Mntdir, Lock : import dat;
+mouse, activecol, seltext, row : import dat;
+cursorset : import graph;
+mainwin : import gui;
+Text : import textm;
+Window : import windowm;
+Column : import columnm;
+Row : import rowm;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ draw = mods.draw;
+ gui = mods.gui;
+ acme = mods.acme;
+ dat = mods.dat;
+ graph = mods.graph;
+ textm = mods.textm;
+ windowm = mods.windowm;
+ columnm = mods.columnm;
+ rowm = mods.rowm;
+ scrl = mods.scroll;
+ look = mods.look;
+
+ stderr = sys->fildes(2);
+}
+
+min(x : int, y : int) : int
+{
+ if (x < y)
+ return x;
+ return y;
+}
+
+max(x : int, y : int) : int
+{
+ if (x > y)
+ return x;
+ return y;
+}
+
+abs(x : int) : int
+{
+ if (x < 0)
+ return -x;
+ return x;
+}
+
+isalnum(c : int) : int
+{
+ #
+ # 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 FALSE;
+ if(16r7F<=c && c<=16rA0)
+ return FALSE;
+ if(strchr("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c) >= 0)
+ return FALSE;
+ return TRUE;
+ # return ('a' <= c && c <= 'z') ||
+ # ('A' <= c && c <= 'Z') ||
+ # ('0' <= c && c <= '9');
+}
+
+strchr(s : string, c : int) : int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strrchr(s : string, c : int) : int
+{
+ for (i := len s - 1; i >= 0; i--)
+ if (s[i] == c)
+ return i;
+ return -1;
+}
+
+strncmp(s, t : string, n : int) : int
+{
+ if (len s > n)
+ s = s[0:n];
+ if (len t > n)
+ t = t[0:n];
+ if (s < t)
+ return -1;
+ if (s > t)
+ return 1;
+ return 0;
+}
+
+env : Env;
+
+getenv(s : string) : string
+{
+ if (env == nil)
+ env = load Env Env->PATH;
+ e := env->getenv(s);
+ if(e != nil && e[len e - 1] == '\n') # shell bug
+ return e[0: len e -1];
+ return e;
+}
+
+setenv(s, t : string)
+{
+ if (env == nil)
+ env = load Env Env->PATH;
+ env->setenv(s, t);
+}
+
+stob(s : string, n : int) : array of byte
+{
+ b := array[2*n] of byte;
+ for (i := 0; i < n; i++) {
+ b[2*i] = byte (s[i]&16rff);
+ b[2*i+1] = byte ((s[i]>>8)&16rff);
+ }
+ return b;
+}
+
+btos(b : array of byte, s : ref Astring)
+{
+ n := (len b)/2;
+ for (i := 0; i < n; i++)
+ s.s[i] = int b[2*i] | ((int b[2*i+1])<<8);
+}
+
+reverse(ol : list of string) : list of string
+{
+ nl : list of string;
+
+ nl = nil;
+ while (ol != nil) {
+ nl = hd ol :: nl;
+ ol = tl ol;
+ }
+ return nl;
+}
+
+nextarg(p : ref Arg) : int
+{
+ bp : string;
+
+ if(p.av != nil){
+ bp = hd p.av;
+ if(bp != nil && bp[0] == '-'){
+ p.p = bp[1:];
+ p.av = tl p.av;
+ return 1;
+ }
+ }
+ p.p = nil;
+ return 0;
+}
+
+arginit(av : list of string) : ref Arg
+{
+ p : ref Arg;
+
+ p = ref Arg;
+ p.arg0 = hd av;
+ p.av = tl av;
+ nextarg(p);
+ return p;
+}
+
+argopt(p : ref Arg) : int
+{
+ r : int;
+
+ if(p.p == nil && nextarg(p) == 0)
+ return 0;
+ r = p.p[0];
+ p.p = p.p[1:];
+ return r;
+}
+
+argf(p : ref Arg) : string
+{
+ bp : string;
+
+ if(p.p != nil){
+ bp = p.p;
+ p.p = nil;
+ } else if(p.av != nil){
+ bp = hd p.av;
+ p.av = tl p.av;
+ } else
+ bp = nil;
+ return bp;
+}
+
+exec(cmd : string, argl : list of string)
+{
+ file := cmd;
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0]!='/' && file[0:2]!="./"){
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ # debug(sys->sprint("file %s not found\n", file));
+ sys->fprint(stderr, "%s: %s\n", cmd, err);
+ return;
+ }
+ }
+ c->init(acme->acmectxt, argl);
+}
+
+getuser() : string
+{
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+gethome(usr : string) : string
+{
+ if (usr == nil)
+ usr = "tmp";
+ return "/usr/" + usr;
+}
+
+postnote(t : int, this : int, pid : int, note : string) : int
+{
+ if (pid == this || pid == 0)
+ return 0;
+ # fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE);
+ fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if (fd == nil)
+ return -1;
+ if (t == PNGROUP)
+ note += "grp";
+ sys->fprint(fd, "%s", note);
+ fd = nil;
+ return 0;
+}
+
+error(s : string)
+{
+ sys->fprint(stderr, "acme: %s: %r\n", s);
+ debug(sys->sprint("error %s : %r\n", s));
+ # s[-1] = 0; # create broken process for debugging
+ acme->acmeexit("error");
+}
+
+dlock : ref Lock;
+dfd : ref Sys->FD;
+
+debuginit()
+{
+ if (RELEASECOPY)
+ return;
+ dfd = sys->create("./debug", Sys->OWRITE, 8r600);
+ # fd = nil;
+ dlock = Lock.init();
+}
+
+debugpr(s : string)
+{
+ if (RELEASECOPY)
+ return;
+ # fd := sys->open("./debug", Sys->OWRITE);
+ # sys->seek(fd, big 0, Sys->SEEKEND);
+ sys->fprint(dfd, "%s", s);
+ # fd = nil;
+}
+
+debug(s : string)
+{
+ if (RELEASECOPY)
+ return;
+ if (dfd == nil)
+ return;
+ dlock.lock();
+ debugpr(s);
+ dlock.unlock();
+}
+
+memfd : ref Sys->FD;
+memb : array of byte;
+
+memdebug(s : string)
+{
+ if (RELEASECOPY)
+ return;
+ dlock.lock();
+ if (memfd == nil) {
+ sys->bind("#c", "/usr/jrf/mnt", Sys->MBEFORE);
+ memfd = sys->open("/usr/jrf/mnt/memory", Sys->OREAD);
+ memb = array[1024] of byte;
+ }
+ sys->seek(memfd, big 0, 0);
+ n := sys->read(memfd, memb, len memb);
+ if (n <= 0) {
+ dlock.unlock();
+ debug(sys->sprint("bad read %r\n"));
+ return;
+ }
+ s = s + " : " + string memb[0:n] + "\n";
+ dlock.unlock();
+ debug(s);
+ s = nil;
+}
+
+rgetc(s : string, n : int) : int
+{
+ if (n < 0 || n >= len s)
+ return 0;
+ return s[n];
+}
+
+tgetc(t : ref Text, n : int) : int
+{
+ if(n >= t.file.buf.nc)
+ return 0;
+ return t.readc(n);
+}
+
+skipbl(r : string, n : int) : (string, int)
+{
+ i : int = 0;
+
+ while(n>0 && (r[i]==' ' || r[i]=='\t' || r[i]=='\n')){
+ --n;
+ i++;
+ }
+ return (r[i:], n);
+}
+
+findbl(r : string, n : int) : (string, int)
+{
+ i : int = 0;
+
+ while(n>0 && r[i]!=' ' && r[i]!='\t' && r[i]!='\n'){
+ --n;
+ i++;
+ }
+ return (r[i:], n);
+}
+
+prevmouse : Point;
+mousew : ref Window;
+
+savemouse(w : ref Window)
+{
+ prevmouse = mouse.xy;
+ mousew = w;
+}
+
+restoremouse(w : ref Window)
+{
+ if(mousew!=nil && mousew==w)
+ cursorset(prevmouse);
+ mousew = nil;
+}
+
+clearmouse()
+{
+ mousew = nil;
+}
+
+#
+# Heuristic city.
+#
+newwindow(t : ref Text) : ref Window
+{
+ c : ref Column;
+ w, bigw, emptyw : ref Window;
+ emptyb : ref Text;
+ i, y, el : int;
+
+ if(activecol != nil)
+ c = activecol;
+ else if(seltext != nil && seltext.col != nil)
+ c = seltext.col;
+ else if(t != nil && t.col != nil)
+ c = t.col;
+ else{
+ if(row.ncol==0 && row.add(nil, -1)==nil)
+ error("can't make column");
+ c = row.col[row.ncol-1];
+ }
+ activecol = c;
+ if(t==nil || t.w==nil || c.nw==0)
+ return c.add(nil, nil, -1);
+
+ # find biggest window and biggest blank spot
+ emptyw = c.w[0];
+ bigw = emptyw;
+ for(i=1; i<c.nw; i++){
+ w = c.w[i];
+ # use >= to choose one near bottom of screen
+ if(w.body.frame.maxlines >= bigw.body.frame.maxlines)
+ bigw = w;
+ if(w.body.frame.maxlines-w.body.frame.nlines >= emptyw.body.frame.maxlines-emptyw.body.frame.nlines)
+ emptyw = w;
+ }
+ emptyb = emptyw.body;
+ el = emptyb.frame.maxlines-emptyb.frame.nlines;
+ # if empty space is big, use it
+ if(el>15 || (el>3 && el>(bigw.body.frame.maxlines-1)/2))
+ y = emptyb.frame.r.min.y+emptyb.frame.nlines*(graph->font).height;
+ else{
+ # if this window is in column and isn't much smaller, split it
+ if(t.col==c && t.w.r.dy()>2*bigw.r.dy()/3)
+ bigw = t.w;
+ y = (bigw.r.min.y + bigw.r.max.y)/2;
+ }
+ w = c.add(nil, nil, y);
+ if(w.body.frame.maxlines < 2)
+ w.col.grow(w, 1, 1);
+ return w;
+}
+
+stralloc(n : int) : ref Astring
+{
+ r := ref Astring;
+ ab := array[n] of { * => byte 'z' };
+ r.s = string ab;
+ if (len r.s != n)
+ error("bad stralloc");
+ ab = nil;
+ return r;
+}
+
+strfree(s : ref Astring)
+{
+ s.s = nil;
+ s = nil;
+}
+
+access(s : string) : int
+{
+ fd := sys->open(s, 0);
+ if (fd == nil)
+ return -1;
+ fd = nil;
+ return 0;
+}
+
+errorwin(dir : string, ndir : int, incl : array of string, nincl : int) : ref Window
+{
+ w : ref Window;
+ r : string;
+ i, n : int;
+
+ n = ndir;
+ r = dir + "+Errors";
+ n += 7;
+ w = look->lookfile(r, n);
+ if(w == nil){
+ w = row.col[row.ncol-1].add(nil, nil, -1);
+ w.filemenu = FALSE;
+ w.setname(r, n);
+ }
+ r = nil;
+ for(i=nincl; --i>=0; )
+ w.addincl(incl[i], n);
+ return w;
+}
+
+warning(md : ref Mntdir, s : string)
+{
+ n, q0, owner : int;
+ w : ref Window;
+ t : ref Text;
+
+ debug(sys->sprint("warning %s\n", s));
+ if (row == nil) {
+ sys->fprint(sys->fildes(2), "warning: %s\n", s);
+ debug(s);
+ debug("\n");
+ return;
+ }
+ if(row.ncol == 0){ # really early error
+ row.init(mainwin.clipr);
+ row.add(nil, -1);
+ row.add(nil, -1);
+ if(row.ncol == 0)
+ error("initializing columns in warning()");
+ }
+ if(md != nil){
+ for(;;){
+ w = errorwin(md.dir, md.ndir, md.incl, md.nincl);
+ w.lock('E');
+ if (w.col != nil)
+ break;
+ # window was deleted too fast
+ w.unlock();
+ }
+ }else
+ w = errorwin(nil, 0, nil, 0);
+ t = w.body;
+ owner = w.owner;
+ if(owner == 0)
+ w.owner = 'E';
+ w.commit(t);
+ (q0, n) = t.bsinsert(t.file.buf.nc, s, len s, TRUE);
+ t.show(q0, q0+n, TRUE);
+ t.w.settag();
+ scrl->scrdraw(t);
+ w.owner = owner;
+ w.dirty = FALSE;
+ if(md != nil)
+ w.unlock();
+}
+
+getexc(): string
+{
+ f := "/prog/"+string sys->pctl(0, nil)+"/exception";
+ if((fd := sys->open(f, Sys->OREAD)) == nil)
+ return nil;
+ b := array[8192] of byte;
+ if((n := sys->read(fd, b, len b)) < 0)
+ return nil;
+ return string b[0: n];
+}
+
+# returns pc, module, exception
+readexc(): (int, string, string)
+{
+ s := getexc();
+ if(s == nil)
+ return (0, nil, nil);
+ (m, l) := sys->tokenize(s, " ");
+ if(m < 3)
+ return (0, nil, nil);
+ pc := int hd l; l = tl l;
+ mod := hd l; l = tl l;
+ exc := hd l; l = tl l;
+ for( ; l != nil; l = tl l)
+ exc += " " + hd l;
+ return (pc, mod, exc);
+}
--- /dev/null
+++ b/appl/acme/util.m
@@ -1,0 +1,52 @@
+Utils : module {
+ PATH : con "/dis/acme/util.dis";
+
+ stderr : ref Sys->FD;
+
+ Arg : adt {
+ arg0 : string;
+ av : list of string;
+ p : string;
+ };
+
+ PNPROC, PNGROUP : con iota;
+
+ init : fn(mods : ref Dat->Mods);
+ arginit : fn(av : list of string) : ref Arg;
+ argopt : fn(p : ref Arg) : int;
+ argf : fn(p : ref Arg) : string;
+ min : fn(a : int, b : int) : int;
+ max : fn(a : int, b : int) : int;
+ abs : fn(x : int) : int;
+ error : fn(s : string);
+ warning : fn(md : ref Dat->Mntdir, t : string);
+ debuginit : fn();
+ debug : fn(s : string);
+ memdebug : fn(s : string);
+ postnote : fn(t : int, this : int, pid : int, note : string) : int;
+ exec: fn(c: string, args : list of string);
+ getuser : fn() : string;
+ gethome : fn(user : string) : string;
+ access : fn(s : string) : int;
+ isalnum : fn(c : int) : int;
+ savemouse : fn(w : ref Windowm->Window);
+ restoremouse : fn(w : ref Windowm->Window);
+ clearmouse : fn();
+ rgetc : fn(r : string, n : int) : int;
+ tgetc : fn(t : ref Textm->Text, n : int) : int;
+ reverse : fn(l : list of string) : list of string;
+ stralloc : fn(n : int) : ref Dat->Astring;
+ strfree :fn(s : ref Dat->Astring);
+ strchr : fn(s : string, c : int) : int;
+ strrchr: fn(s : string, c : int) : int;
+ strncmp : fn(s, t : string, n : int) : int;
+ getenv : fn(s : string) : string;
+ setenv : fn(s, t : string);
+ stob : fn(s : string, n : int) : array of byte;
+ btos : fn(b : array of byte, s : ref Dat->Astring);
+ findbl : fn(s : string, n : int) : (string, int);
+ skipbl : fn(s : string, n : int) : (string, int);
+ newwindow : fn(t : ref Textm->Text) : ref Windowm->Window;
+ getexc: fn(): string;
+ readexc : fn() : (int, string, string);
+};
--- /dev/null
+++ b/appl/acme/wind.b
@@ -1,0 +1,558 @@
+implement Windowm;
+
+include "common.m";
+
+sys : Sys;
+utils : Utils;
+drawm : Draw;
+graph : Graph;
+gui : Gui;
+dat : Dat;
+bufferm : Bufferm;
+textm : Textm;
+filem : Filem;
+look : Look;
+scrl : Scroll;
+acme : Acme;
+
+sprint : import sys;
+FALSE, TRUE, XXX, Astring : import Dat;
+Reffont, reffont, Lock, Ref, button, modbutton : import dat;
+Point, Rect, Image : import drawm;
+min, max, error, warning, stralloc, strfree : import utils;
+font, draw : import graph;
+black, white, mainwin : import gui;
+Buffer : import bufferm;
+Body, Text, Tag : import textm;
+File : import filem;
+Xfid : import Xfidm;
+scrdraw : import scrl;
+tagcols, textcols : import acme;
+BORD : import Framem;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ drawm = mods.draw;
+ graph = mods.graph;
+ gui = mods.gui;
+ textm = mods.textm;
+ filem = mods.filem;
+ bufferm = mods.bufferm;
+ look = mods.look;
+ scrl = mods.scroll;
+ acme = mods.acme;
+}
+
+winid : int;
+nullwin : Window;
+
+Window.init(w : self ref Window, clone : ref Window, r : Rect)
+{
+ r1, br : Rect;
+ f : ref File;
+ rf : ref Reffont;
+ rp : ref Astring;
+ nc : int;
+ dummy : ref File = nil;
+
+ c := w.col;
+ *w = nullwin;
+ w.col = c;
+ w.nopen = array[Dat->QMAX] of byte;
+ for (i := 0; i < Dat->QMAX; i++)
+ w.nopen[i] = byte 0;
+ w.qlock = Lock.init();
+ w.ctllock = Lock.init();
+ w.refx = Ref.init();
+ w.tag = textm->newtext();
+ w.tag.w = w;
+ w.body = textm->newtext();
+ w.body.w = w;
+ w.id = ++winid;
+ w.refx.inc();
+ w.ctlfid = ~0;
+ w.utflastqid = -1;
+ r1 = r;
+ r1.max.y = r1.min.y + font.height;
+ reffont.r.inc();
+ f = dummy.addtext(w.tag);
+ w.tag.init(f, r1, reffont, tagcols);
+ w.tag.what = Tag;
+ # tag is a copy of the contents, not a tracked image
+ if(clone != nil){
+ w.tag.delete(0, w.tag.file.buf.nc, TRUE);
+ nc = clone.tag.file.buf.nc;
+ rp = utils->stralloc(nc);
+ clone.tag.file.buf.read(0, rp, 0, nc);
+ w.tag.insert(0, rp.s, nc, TRUE, 0);
+ utils->strfree(rp);
+ rp = nil;
+ w.tag.file.reset();
+ w.tag.setselect(nc, nc);
+ }
+ r1 = r;
+ r1.min.y += font.height + 1;
+ if(r1.max.y < r1.min.y)
+ r1.max.y = r1.min.y;
+ f = nil;
+ if(clone != nil){
+ f = clone.body.file;
+ w.body.org = clone.body.org;
+ w.isscratch = clone.isscratch;
+ rf = Reffont.get(FALSE, FALSE, FALSE, clone.body.reffont.f.name);
+ }else
+ rf = Reffont.get(FALSE, FALSE, FALSE, nil);
+ f = f.addtext(w.body);
+ w.body.what = Body;
+ w.body.init(f, r1, rf, textcols);
+ r1.min.y -= 1;
+ r1.max.y = r1.min.y+1;
+ draw(mainwin, r1, tagcols[BORD], nil, (0, 0));
+ scrdraw(w.body);
+ w.r = r;
+ w.r.max.y = w.body.frame.r.max.y;
+ br.min = w.tag.scrollr.min;
+ br.max.x = br.min.x + button.r.dx();
+ br.max.y = br.min.y + button.r.dy();
+ draw(mainwin, br, button, nil, button.r.min);
+ w.filemenu = TRUE;
+ w.maxlines = w.body.frame.maxlines;
+ if(clone != nil){
+ w.dirty = clone.dirty;
+ w.body.setselect(clone.body.q0, clone.body.q1);
+ w.settag();
+ }
+}
+
+Window.reshape(w : self ref Window, r : Rect, safe : int) : int
+{
+ r1, br : Rect;
+ y : int;
+ b : ref Image;
+
+ r1 = r;
+ r1.max.y = r1.min.y + font.height;
+ y = r1.max.y;
+ if(!safe || !w.tag.frame.r.eq(r1)){
+ y = w.tag.reshape(r1);
+ b = button;
+ if(w.body.file.mod && !w.isdir && !w.isscratch)
+ b = modbutton;
+ br.min = w.tag.scrollr.min;
+ br.max.x = br.min.x + b.r.dx();
+ br.max.y = br.min.y + b.r.dy();
+ draw(mainwin, br, b, nil, b.r.min);
+ }
+ if(!safe || !w.body.frame.r.eq(r1)){
+ if(y+1+font.height > r.max.y){ # no body
+ r1.min.y = y;
+ r1.max.y = y;
+ w.body.reshape(r1);
+ w.r = r;
+ w.r.max.y = y;
+ return y;
+ }
+ r1 = r;
+ r1.min.y = y;
+ r1.max.y = y + 1;
+ draw(mainwin, r1, tagcols[BORD], nil, (0, 0));
+ r1.min.y = y + 1;
+ r1.max.y = r.max.y;
+ y = w.body.reshape(r1);
+ w.r = r;
+ w.r.max.y = y;
+ scrdraw(w.body);
+ }
+ w.maxlines = min(w.body.frame.nlines, max(w.maxlines, w.body.frame.maxlines));
+ return w.r.max.y;
+}
+
+Window.lock1(w : self ref Window, owner : int)
+{
+ w.refx.inc();
+ w.qlock.lock();
+ w.owner = owner;
+}
+
+Window.lock(w : self ref Window, owner : int)
+{
+ i : int;
+ f : ref File;
+
+ f = w.body.file;
+ for(i=0; i<f.ntext; i++)
+ f.text[i].w.lock1(owner);
+}
+
+Window.unlock(w : self ref Window)
+{
+ f := w.body.file;
+ #
+ # subtle: loop runs backwards to avoid tripping over
+ # winclose indirectly editing f.text and freeing f
+ # on the last iteration of the loop
+ #
+ for(i:=f.ntext-1; i>=0; i--){
+ w = f.text[i].w;
+ w.owner = 0;
+ w.qlock.unlock();
+ w.close();
+ }
+}
+
+Window.mousebut(w : self ref Window)
+{
+ graph->cursorset(w.tag.scrollr.min.add(w.tag.scrollr.max).div(2));
+}
+
+Window.dirfree(w : self ref Window)
+{
+ i : int;
+ dl : ref Dat->Dirlist;
+
+ if(w.isdir){
+ for(i=0; i<w.ndl; i++){
+ dl = w.dlp[i];
+ dl.r = nil;
+ dl = nil;
+ }
+ }
+ w.dlp = nil;
+ w.ndl = 0;
+}
+
+Window.close(w : self ref Window)
+{
+ i : int;
+
+ if(w.refx.dec() == 0){
+ w.dirfree();
+ w.tag.close();
+ w.body.close();
+ if(dat->activewin == w)
+ dat->activewin = nil;
+ for(i=0; i<w.nincl; i++)
+ w.incl[i] = nil;
+ w.incl = nil;
+ w.events = nil;
+ w = nil;
+ }
+}
+
+Window.delete(w : self ref Window)
+{
+ x : ref Xfid;
+
+ x = w.eventx;
+ if(x != nil){
+ w.nevents = 0;
+ w.events = nil;
+ w.eventx = nil;
+ x.c <-= Xfidm->Xnil;
+ }
+}
+
+Window.undo(w : self ref Window, isundo : int)
+{
+ body : ref Text;
+ i : int;
+ f : ref File;
+ v : ref Window;
+
+ if(w==nil)
+ return;
+ w.utflastqid = -1;
+ body = w.body;
+ (body.q0, body.q1) = body.file.undo(isundo, body.q0, body.q1);
+ body.show(body.q0, body.q1, TRUE);
+ f = body.file;
+ for(i=0; i<f.ntext; i++){
+ v = f.text[i].w;
+ v.dirty = (f.seq != v.putseq);
+ if(v != w){
+ v.body.q0 = v.body.frame.p0+v.body.org;
+ v.body.q1 = v.body.frame.p1+v.body.org;
+ }
+ }
+ w.settag();
+}
+
+Window.setname(w : self ref Window, name : string, n : int)
+{
+ t : ref Text;
+ v : ref Window;
+ i : int;
+
+ t = w.body;
+ if(t.file.name == name)
+ return;
+ w.isscratch = FALSE;
+ if(n>=6 && name[n-6:n] == "/guide")
+ w.isscratch = TRUE;
+ else if(n>=7 && name[n-7:n] == "+Errors")
+ w.isscratch = TRUE;
+ t.file.setname(name, n);
+ for(i=0; i<t.file.ntext; i++){
+ v = t.file.text[i].w;
+ v.settag();
+ v.isscratch = w.isscratch;
+ }
+}
+
+Window.typex(w : self ref Window, t : ref Text, r : int)
+{
+ i : int;
+
+ t.typex(r, w.echomode);
+ if(t.what == Body)
+ for(i=0; i<t.file.ntext; i++)
+ scrdraw(t.file.text[i]);
+ w.settag();
+}
+
+Window.cleartag(w : self ref Window)
+{
+ i, n : int;
+ r : ref Astring;
+
+ # w must be committed
+ n = w.tag.file.buf.nc;
+ r = utils->stralloc(n);
+ w.tag.file.buf.read(0, r, 0, n);
+ for(i=0; i<n; i++)
+ if(r.s[i]==' ' || r.s[i]=='\t')
+ break;
+ for(; i<n; i++)
+ if(r.s[i] == '|')
+ break;
+ if(i == n)
+ return;
+ i++;
+ w.tag.delete(i, n, TRUE);
+ utils->strfree(r);
+ r = nil;
+ w.tag.file.mod = FALSE;
+ if(w.tag.q0 > i)
+ w.tag.q0 = i;
+ if(w.tag.q1 > i)
+ w.tag.q1 = i;
+ w.tag.setselect(w.tag.q0, w.tag.q1);
+}
+
+Window.settag(w : self ref Window)
+{
+ i : int;
+ f : ref File;
+
+ f = w.body.file;
+ for(i=0; i<f.ntext; i++){
+ v := f.text[i].w;
+ if(v.col.safe || v.body.frame.maxlines>0)
+ v.settag1();
+ }
+}
+
+Window.settag1(w : self ref Window)
+{
+ ii, j, k, n, bar, dirty : int;
+ old : ref Astring;
+ new : string;
+ r : int;
+ b : ref Image;
+ q0, q1 : int;
+ br : Rect;
+
+ if(w.tag.ncache!=0 || w.tag.file.mod)
+ w.commit(w.tag); # check file name; also can now modify tag
+ old = utils->stralloc(w.tag.file.buf.nc);
+ w.tag.file.buf.read(0, old, 0, w.tag.file.buf.nc);
+ for(ii=0; ii<w.tag.file.buf.nc; ii++)
+ if(old.s[ii]==' ' || old.s[ii]=='\t')
+ break;
+ if(old.s[0:ii] != w.body.file.name){
+ w.tag.delete(0, ii, TRUE);
+ w.tag.insert(0, w.body.file.name, len w.body.file.name, TRUE, 0);
+ strfree(old);
+ old = nil;
+ old = utils->stralloc(w.tag.file.buf.nc);
+ w.tag.file.buf.read(0, old, 0, w.tag.file.buf.nc);
+ }
+ new = w.body.file.name + " Del Snarf";
+ if(w.filemenu){
+ if(w.body.file.delta.nc>0 || w.body.ncache)
+ new += " Undo";
+ if(w.body.file.epsilon.nc > 0)
+ new += " Redo";
+ dirty = w.body.file.name != nil && (w.body.ncache || w.body.file.seq!=w.putseq);
+ if(!w.isdir && dirty)
+ new += " Put";
+ }
+ if(w.isdir)
+ new += " Get";
+ l := len w.body.file.name;
+ if(l >= 2 && w.body.file.name[l-2: ] == ".b")
+ new += " Limbo";
+ new += " |";
+ r = utils->strchr(old.s, '|');
+ if(r >= 0)
+ k = r+1;
+ else{
+ k = w.tag.file.buf.nc;
+ if(w.body.file.seq == 0)
+ new += " Look ";
+ }
+ if(new != old.s[0:k]){
+ n = k;
+ if(n > len new)
+ n = len new;
+ for(j=0; j<n; j++)
+ if(old.s[j] != new[j])
+ break;
+ q0 = w.tag.q0;
+ q1 = w.tag.q1;
+ w.tag.delete(j, k, TRUE);
+ w.tag.insert(j, new[j:], len new - j, TRUE, 0);
+ # try to preserve user selection
+ r = utils->strchr(old.s, '|');
+ if(r >= 0){
+ bar = r;
+ if(q0 > bar){
+ bar = utils->strchr(new, '|')-bar;
+ w.tag.q0 = q0+bar;
+ w.tag.q1 = q1+bar;
+ }
+ }
+ }
+ strfree(old);
+ old = nil;
+ new = nil;
+ w.tag.file.mod = FALSE;
+ n = w.tag.file.buf.nc+w.tag.ncache;
+ if(w.tag.q0 > n)
+ w.tag.q0 = n;
+ if(w.tag.q1 > n)
+ w.tag.q1 = n;
+ w.tag.setselect(w.tag.q0, w.tag.q1);
+ b = button;
+ if(!w.isdir && !w.isscratch && (w.body.file.mod || w.body.ncache))
+ b = modbutton;
+ br.min = w.tag.scrollr.min;
+ br.max.x = br.min.x + b.r.dx();
+ br.max.y = br.min.y + b.r.dy();
+ draw(mainwin, br, b, nil, b.r.min);
+}
+
+Window.commit(w : self ref Window, t : ref Text)
+{
+ r : ref Astring;
+ i : int;
+ f : ref File;
+
+ t.commit(TRUE);
+ f = t.file;
+ if(f.ntext > 1)
+ for(i=0; i<f.ntext; i++)
+ f.text[i].commit(FALSE); # no-op for t
+ if(t.what == Body)
+ return;
+ r = utils->stralloc(w.tag.file.buf.nc);
+ w.tag.file.buf.read(0, r, 0, w.tag.file.buf.nc);
+ for(i=0; i<w.tag.file.buf.nc; i++)
+ if(r.s[i]==' ' || r.s[i]=='\t')
+ break;
+ if(r.s[0:i] != w.body.file.name){
+ dat->seq++;
+ w.body.file.mark();
+ w.body.file.mod = TRUE;
+ w.dirty = TRUE;
+ w.setname(r.s, i);
+ w.settag();
+ }
+ utils->strfree(r);
+ r = nil;
+}
+
+Window.addincl(w : self ref Window, r : string, n : int)
+{
+ {
+ (ok, d) := sys->stat(r);
+ if(ok < 0){
+ if(r[0] == '/')
+ raise "e";
+ (r, n) = look->dirname(w.body, r, n);
+ (ok, d) = sys->stat(r);
+ if(ok < 0)
+ raise "e";
+ }
+ if((d.mode&Sys->DMDIR) == 0){
+ warning(nil, sprint("%s: not a directory\n", r));
+ r = nil;
+ return;
+ }
+ w.nincl++;
+ owi := w.incl;
+ w.incl = array[w.nincl] of string;
+ w.incl[1:] = owi[0:w.nincl-1];
+ owi = nil;
+ w.incl[0] = r;
+ r = nil;
+ }
+ exception{
+ * =>
+ warning(nil, sprint("%s: %r\n", r));
+ r = nil;
+ }
+}
+
+Window.clean(w : self ref Window, conservative : int, exiting : int) : int # as it stands, conservative is always TRUE
+{
+ if(w.isscratch || w.isdir) # don't whine if it's a guide file, error window, etc.
+ return TRUE;
+ if((!conservative||exiting) && w.nopen[Dat->QWevent]>byte 0)
+ return TRUE;
+ if(w.dirty){
+ if(w.body.file.name != nil)
+ warning(nil, sprint("%s modified\n", w.body.file.name));
+ else{
+ if(w.body.file.buf.nc < 100) # don't whine if it's too small
+ return TRUE;
+ warning(nil, "unnamed file modified\n");
+ }
+ w.dirty = FALSE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+Window.ctlprint(w : self ref Window, fonts : int) : string
+{
+ s := sprint("%11d %11d %11d %11d %11d ", w.id, w.tag.file.buf.nc,
+ w.body.file.buf.nc, w.isdir, w.dirty);
+ if(fonts)
+ return sprint("%s%11d %q %11d ", s, w.body.frame.r.dx(),
+ w.body.reffont.f.name, w.body.frame.maxtab);
+ return s;
+}
+
+Window.event(w : self ref Window, fmt : string)
+{
+ n : int;
+ x : ref Xfid;
+
+ if(w.nopen[Dat->QWevent] == byte 0)
+ return;
+ if(w.owner == 0)
+ error("no window owner");
+ n = len fmt;
+ w.events[len w.events] = w.owner;
+ w.events += fmt;
+ w.nevents += n+1;
+ x = w.eventx;
+ if(x != nil){
+ w.eventx = nil;
+ x.c <-= Xfidm->Xnil;
+ }
+}
--- /dev/null
+++ b/appl/acme/wind.m
@@ -1,0 +1,67 @@
+Windowm : module {
+ PATH : con "/dis/acme/wind.dis";
+
+ init : fn(mods : ref Dat->Mods);
+
+ Window : adt {
+ qlock : ref Dat->Lock;
+ refx : ref Dat->Ref;
+ tag : cyclic ref Textm->Text;
+ body : cyclic ref Textm->Text;
+ r : Draw->Rect;
+ isdir : int;
+ isscratch : int;
+ filemenu : int;
+ dirty : int;
+ id : int;
+ addr : Dat->Range;
+ limit : Dat->Range;
+ nopen : array of byte;
+ nomark : int;
+ noscroll : int;
+ echomode : int;
+ wrselrange : Dat->Range;
+ rdselfd : ref Sys->FD;
+ col : cyclic ref Columnm->Column;
+ eventx : cyclic ref Xfidm->Xfid;
+ events : string;
+ nevents : int;
+ owner : int;
+ maxlines : int;
+ dlp : array of ref Dat->Dirlist;
+ ndl : int;
+ putseq : int;
+ nincl : int;
+ incl : array of string;
+ reffont : ref Dat->Reffont;
+ ctllock : ref Dat->Lock;
+ ctlfid : int;
+ dumpstr : string;
+ dumpdir : string;
+ dumpid : int;
+ utflastqid : int;
+ utflastboff : int;
+ utflastq : int;
+
+ init : fn(w : self ref Window, w0 : ref Window, r : Draw->Rect);
+ lock : fn(w : self ref Window, n : int);
+ lock1 : fn(w : self ref Window, n : int);
+ unlock : fn(w : self ref Window);
+ typex : fn(w : self ref Window, t : ref Textm->Text, r : int);
+ undo : fn(w : self ref Window, n : int);
+ setname : fn(w : self ref Window, r : string, n : int);
+ settag : fn(w : self ref Window);
+ settag1 : fn(w : self ref Window);
+ commit : fn(w : self ref Window, t : ref Textm->Text);
+ reshape : fn(w : self ref Window, r : Draw->Rect, n : int) : int;
+ close : fn(w : self ref Window);
+ delete : fn(w : self ref Window);
+ clean : fn(w : self ref Window, n : int, exiting : int) : int;
+ dirfree : fn(w : self ref Window);
+ event : fn(w : self ref Window, b : string);
+ mousebut : fn(w : self ref Window);
+ addincl : fn(w : self ref Window, r : string, n : int);
+ cleartag : fn(w : self ref Window);
+ ctlprint : fn(w : self ref Window, fonts : int) : string;
+ };
+};
--- /dev/null
+++ b/appl/acme/xfid.b
@@ -1,0 +1,1087 @@
+implement Xfidm;
+
+include "common.m";
+
+sys : Sys;
+dat : Dat;
+graph : Graph;
+utils : Utils;
+regx : Regx;
+bufferm : Bufferm;
+diskm : Diskm;
+filem : Filem;
+textm : Textm;
+columnm : Columnm;
+scrl : Scroll;
+look : Look;
+exec : Exec;
+windowm : Windowm;
+fsys : Fsys;
+editm: Edit;
+ecmd: Editcmd;
+styxaux: Styxaux;
+
+UTFmax : import Sys;
+sprint : import sys;
+Smsg0 : import Dat;
+TRUE, FALSE, XXX, BUFSIZE, MAXRPC : import Dat;
+EM_NORMAL, EM_RAW, EM_MASK : import Dat;
+Qdir, Qcons, Qlabel, Qindex, Qeditout : import Dat;
+QWaddr, QWdata, QWevent, QWconsctl, QWctl, QWbody, QWeditout, QWtag, QWrdsel, QWwrsel : import Dat;
+seq, cxfidfree, Lock, Ref, Range, Mntdir, Astring : import dat;
+error, warning, max, min, stralloc, strfree, strncmp : import utils;
+address : import regx;
+Buffer : import bufferm;
+File : import filem;
+Text : import textm;
+scrdraw : import scrl;
+Window : import windowm;
+bflush : import graph;
+Column : import columnm;
+row : import dat;
+FILE, QID, respond : import fsys;
+oldtag, name, offset, count, data, setcount, setdata : import styxaux;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ graph = mods.graph;
+ utils = mods.utils;
+ regx = mods.regx;
+ filem = mods.filem;
+ bufferm = mods.bufferm;
+ diskm = mods.diskm;
+ textm = mods.textm;
+ columnm = mods.columnm;
+ scrl = mods.scroll;
+ look = mods.look;
+ exec = mods.exec;
+ windowm = mods.windowm;
+ fsys = mods.fsys;
+ editm = mods.edit;
+ ecmd = mods.editcmd;
+ styxaux = mods.styxaux;
+}
+
+nullxfid : Xfid;
+
+newxfid() : ref Xfid
+{
+ x := ref Xfid;
+ *x = nullxfid;
+ x.buf = array[fsys->messagesize+UTFmax] of byte;
+ return x;
+}
+
+Ctlsize : con 5*12;
+
+Edel := "deleted window";
+Ebadctl := "ill-formed control message";
+Ebadaddr := "bad address syntax";
+Eaddr := "address out of range";
+Einuse := "already in use";
+Ebadevent:= "bad event syntax";
+
+clampaddr(w : ref Window)
+{
+ if(w.addr.q0 < 0)
+ w.addr.q0 = 0;
+ if(w.addr.q1 < 0)
+ w.addr.q1 = 0;
+ if(w.addr.q0 > w.body.file.buf.nc)
+ w.addr.q0 = w.body.file.buf.nc;
+ if(w.addr.q1 > w.body.file.buf.nc)
+ w.addr.q1 = w.body.file.buf.nc;
+}
+
+xfidtid : array of int;
+nxfidtid := 0;
+
+xfidkill()
+{
+ if (sys == nil)
+ return;
+ thispid := sys->pctl(0, nil);
+ for (i := 0; i < nxfidtid; i++)
+ utils->postnote(Utils->PNPROC, thispid, xfidtid[i], "kill");
+}
+
+Xfid.ctl(x : self ref Xfid)
+{
+ x.tid = sys->pctl(0, nil);
+ ox := xfidtid;
+ xfidtid = array[nxfidtid+1] of int;
+ xfidtid[0:] = ox[0:nxfidtid];
+ xfidtid[nxfidtid++] = x.tid;
+ ox = nil;
+ for (;;) {
+ f := <- x.c;
+ case (f) {
+ Xnil => ;
+ Xflush => x.flush();
+ Xwalk => x.walk(nil);
+ Xopen => x.open();
+ Xclose => x.close();
+ Xread => x.read();
+ Xwrite => x.write();
+ * => error("bad case in Xfid.ctl()");
+ }
+ bflush();
+ cxfidfree <-= x;
+ }
+}
+
+Xfid.flush(x : self ref Xfid)
+{
+ fc : Smsg0;
+ i, j : int;
+ w : ref Window;
+ c : ref Column;
+ wx : ref Xfid;
+
+ # search windows for matching tag
+ row.qlock.lock();
+loop:
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ w.lock('E');
+ wx = w.eventx;
+ if(wx!=nil && wx.fcall.tag==oldtag(x.fcall)){
+ w.eventx = nil;
+ wx.flushed = TRUE;
+ wx.c <-= Xnil;
+ w.unlock();
+ break loop;
+ }
+ w.unlock();
+ }
+ }
+ row.qlock.unlock();
+ respond(x, fc, nil);
+}
+
+Xfid.walk(nil : self ref Xfid, cw: chan of ref Window)
+{
+ # fc : Smsg0;
+ w : ref Window;
+
+ # if(name(x.fcall) != "new")
+ # error("unknown path in walk\n");
+ row.qlock.lock(); # tasks->procs now
+ w = utils->newwindow(nil);
+ w.settag();
+ # w.refx.inc();
+ # x.f.w = w;
+ # x.f.qid.path = big QID(w.id, Qdir);
+ # x.f.qid.qtype = Sys->QTDIR;
+ # fc.qid = x.f.qid;
+ row.qlock.unlock();
+ # respond(x, fc, nil);
+ cw <-= w;
+}
+
+Xfid.open(x : self ref Xfid)
+{
+ fc : Smsg0;
+ w : ref Window;
+ q : int;
+
+ fc.iounit = 0;
+ w = x.f.w;
+ if(w != nil){
+ t := w.body;
+ row.qlock.lock(); # tasks->procs now
+ w.lock('E');
+ q = FILE(x.f.qid);
+ case(q){
+ QWaddr or QWdata or QWevent =>
+ if(w.nopen[q]++ == byte 0){
+ if(q == QWaddr){
+ w.addr = (Range)(0,0);
+ w.limit = (Range)(-1,-1);
+ }
+ if(q==QWevent && !w.isdir && w.col!=nil){
+ w.filemenu = FALSE;
+ w.settag();
+ }
+ }
+ QWrdsel =>
+ #
+ # Use a temporary file.
+ # A pipe would be the obvious, but we can't afford the
+ # broken pipe notification. Using the code to read QWbody
+ # is n², which should probably also be fixed. Even then,
+ # though, we'd need to squirrel away the data in case it's
+ # modified during the operation, e.g. by |sort
+ #
+ if(w.rdselfd != nil){
+ w.unlock();
+ respond(x, fc, Einuse);
+ return;
+ }
+ w.rdselfd = diskm->tempfile();
+ if(w.rdselfd == nil){
+ w.unlock();
+ respond(x, fc, "can't create temp file");
+ return;
+ }
+ w.nopen[q]++;
+ q0 := t.q0;
+ q1 := t.q1;
+ r := utils->stralloc(BUFSIZE);
+ while(q0 < q1){
+ n := q1 - q0;
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ t.file.buf.read(q0, r, 0, n);
+ s := array of byte r.s[0:n];
+ m := len s;
+ if(sys->write(w.rdselfd, s, m) != m){
+ warning(nil, "can't write temp file for pipe command %r\n");
+ break;
+ }
+ s = nil;
+ q0 += n;
+ }
+ utils->strfree(r);
+ QWwrsel =>
+ w.nopen[q]++;
+ seq++;
+ t.file.mark();
+ exec->cut(t, t, FALSE, TRUE);
+ w.wrselrange = (Range)(t.q1, t.q1);
+ w.nomark = TRUE;
+ QWeditout =>
+ if(editm->editing == FALSE){
+ w.unlock();
+ respond(x, fc, "permission denied");
+ return;
+ }
+ w.wrselrange = (Range)(t.q1, t.q1);
+ break;
+ }
+ w.unlock();
+ row.qlock.unlock();
+ }
+ fc.qid = x.f.qid;
+ fc.iounit = fsys->messagesize-Styx->IOHDRSZ;
+ x.f.open = TRUE;
+ respond(x, fc, nil);
+}
+
+Xfid.close(x : self ref Xfid)
+{
+ fc : Smsg0;
+ w : ref Window;
+ q : int;
+
+ w = x.f.w;
+ # BUG in C version ? fsysclunk() has just set busy, open to FALSE
+ # x.f.busy = FALSE;
+ # if(!x.f.open){
+ # if(w != nil)
+ # w.close();
+ # respond(x, fc, nil);
+ # return;
+ # }
+ # x.f.open = FALSE;
+ if(w != nil){
+ row.qlock.lock(); # tasks->procs now
+ w.lock('E');
+ q = FILE(x.f.qid);
+ case(q){
+ QWctl =>
+ if(w.ctlfid!=~0 && w.ctlfid==x.f.fid){
+ w.ctlfid = ~0;
+ w.ctllock.unlock();
+ }
+ QWdata or QWaddr or QWevent =>
+ # BUG: do we need to shut down Xfid?
+ if (q == QWdata)
+ w.nomark = FALSE;
+ if(--w.nopen[q] == byte 0){
+ if(q == QWdata)
+ w.nomark = FALSE;
+ if(q==QWevent && !w.isdir && w.col!=nil){
+ w.filemenu = TRUE;
+ w.settag();
+ }
+ if(q == QWevent){
+ w.dumpstr = nil;
+ w.dumpdir = nil;
+ }
+ }
+ QWrdsel =>
+ w.rdselfd = nil;
+ QWwrsel =>
+ w.nomark = FALSE;
+ t :=w.body;
+ # before: only did this if !w->noscroll, but that didn't seem right in practice
+ t.show(min(w.wrselrange.q0, t.file.buf.nc),
+ min(w.wrselrange.q1, t.file.buf.nc), TRUE);
+ scrdraw(t);
+ QWconsctl=>
+ w.echomode = EM_NORMAL;
+ }
+ w.close();
+ w.unlock();
+ row.qlock.unlock();
+ }
+ respond(x, fc, nil);
+}
+
+Xfid.read(x : self ref Xfid)
+{
+ fc : Smsg0;
+ n, q : int;
+ off : int;
+ sbuf : string;
+ buf : array of byte;
+ w : ref Window;
+
+ sbuf = nil;
+ q = FILE(x.f.qid);
+ w = x.f.w;
+ if(w == nil){
+ fc.count = 0;
+ case(q){
+ Qcons or Qlabel =>
+ ;
+ Qindex =>
+ x.indexread();
+ return;
+ * =>
+ warning(nil, sprint("unknown qid %d\n", q));
+ }
+ respond(x, fc, nil);
+ return;
+ }
+ w.lock('F');
+ if(w.col == nil){
+ w.unlock();
+ respond(x, fc, Edel);
+ return;
+ }
+ off = int offset(x.fcall);
+ case(q){
+ QWaddr =>
+ w.body.commit(TRUE);
+ clampaddr(w);
+ sbuf = sprint("%11d %11d ", w.addr.q0, w.addr.q1);
+ QWbody =>
+ x.utfread(w.body, 0, w.body.file.buf.nc, QWbody);
+ QWctl =>
+ sbuf = w.ctlprint(1);
+ QWevent =>
+ x.eventread(w);
+ QWdata =>
+ # BUG: what should happen if q1 > q0?
+ if(w.addr.q0 > w.body.file.buf.nc){
+ respond(x, fc, Eaddr);
+ break;
+ }
+ w.addr.q0 += x.runeread(w.body, w.addr.q0, w.body.file.buf.nc);
+ w.addr.q1 = w.addr.q0;
+ QWtag =>
+ x.utfread(w.tag, 0, w.tag.file.buf.nc, QWtag);
+ QWrdsel =>
+ sys->seek(w.rdselfd, big off, 0);
+ n = count(x.fcall);
+ if(n > BUFSIZE)
+ n = BUFSIZE;
+ b := array[n] of byte;
+ n = sys->read(w.rdselfd, b, n);
+ if(n < 0){
+ respond(x, fc, "I/O error in temp file");
+ break;
+ }
+ fc.count = n;
+ fc.data = b;
+ respond(x, fc, nil);
+ b = nil;
+ * =>
+ sbuf = sprint("unknown qid %d in read", q);
+ respond(x, fc, sbuf);
+ sbuf = nil;
+ }
+ if (sbuf != nil) {
+ buf = array of byte sbuf;
+ sbuf = nil;
+ n = len buf;
+ if(off > n)
+ off = n;
+ if(off+count(x.fcall) > n)
+ setcount(x.fcall, n-off);
+ fc.count = count(x.fcall);
+ fc.data = buf[off:];
+ respond(x, fc, nil);
+ buf = nil;
+ }
+ w.unlock();
+}
+
+Xfid.write(x : self ref Xfid)
+{
+ fc : Smsg0;
+ c, cnt, qid, q, nb, nr, eval : int;
+ w : ref Window;
+ r : string;
+ a : Range;
+ t : ref Text;
+ q0, tq0, tq1 : int;
+ md : ref Mntdir;
+
+ qid = FILE(x.f.qid);
+ w = x.f.w;
+ row.qlock.lock(); # tasks->procs now
+ if(w != nil){
+ c = 'F';
+ if(qid==QWtag || qid==QWbody)
+ c = 'E';
+ w.lock(c);
+ if(w.col == nil){
+ w.unlock();
+ row.qlock.unlock();
+ respond(x, fc, Edel);
+ return;
+ }
+ }
+ bodytag := 0;
+ case(qid){
+ Qcons =>
+ md = x.f.mntdir;
+ warning(md, string data(x.fcall));
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ QWconsctl =>
+ if (w != nil) {
+ r = string data(x.fcall);
+ if (strncmp(r, "rawon", 5) == 0)
+ w.echomode = EM_RAW;
+ else if (strncmp(r, "rawoff", 6) == 0)
+ w.echomode = EM_NORMAL;
+ }
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ Qlabel =>
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ QWaddr =>
+ r = string data(x.fcall);
+ nr = len r;
+ t = w.body;
+ w.commit(t);
+ (eval, nb, a) = address(x.f.mntdir, t, w.limit, w.addr, nil, r, 0, nr, TRUE);
+ r = nil;
+ if(nb < nr){
+ respond(x, fc, Ebadaddr);
+ break;
+ }
+ if(!eval){
+ respond(x, fc, Eaddr);
+ break;
+ }
+ w.addr = a;
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ Qeditout or
+ QWeditout =>
+ r = string data(x.fcall);
+ nr = len r;
+ if(w!=nil)
+ err := ecmd->edittext(w.body.file, w.wrselrange.q1, r, nr);
+ else
+ err = ecmd->edittext(nil, 0, r, nr);
+ r = nil;
+ if(err != nil){
+ respond(x, fc, err);
+ break;
+ }
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ break;
+ QWbody or QWwrsel =>
+ t = w.body;
+ bodytag = 1;
+ QWctl =>
+ x.ctlwrite(w);
+ QWdata =>
+ t = w.body;
+ w.commit(t);
+ if(w.addr.q0>t.file.buf.nc || w.addr.q1>t.file.buf.nc){
+ respond(x, fc, Eaddr);
+ break;
+ }
+ nb = sys->utfbytes(data(x.fcall), count(x.fcall));
+ r = string data(x.fcall)[0:nb];
+ nr = len r;
+ if(w.nomark == FALSE){
+ seq++;
+ t.file.mark();
+ }
+ q0 = w.addr.q0;
+ if(w.addr.q1 > q0){
+ t.delete(q0, w.addr.q1, TRUE);
+ w.addr.q1 = q0;
+ }
+ tq0 = t.q0;
+ tq1 = t.q1;
+ t.insert(q0, r, nr, TRUE, 0);
+ if(tq0 >= q0)
+ tq0 += nr;
+ if(tq1 >= q0)
+ tq1 += nr;
+ if(!t.w.noscroll)
+ t.show(tq0, tq1, TRUE);
+ scrdraw(t);
+ w.settag();
+ r = nil;
+ w.addr.q0 += nr;
+ w.addr.q1 = w.addr.q0;
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ QWevent =>
+ x.eventwrite(w);
+ QWtag =>
+ t = w.tag;
+ bodytag = 1;
+ * =>
+ r = sprint("unknown qid %d in write", qid);
+ respond(x, fc, r);
+ r = nil;
+ }
+ if (bodytag) {
+ q = x.f.nrpart;
+ cnt = count(x.fcall);
+ if(q > 0){
+ nd := array[cnt+q] of byte;
+ nd[q:] = data(x.fcall)[0:cnt];
+ nd[0:] = x.f.rpart[0:q];
+ setdata(x.fcall, nd);
+ cnt += q;
+ x.f.nrpart = 0;
+ }
+ nb = sys->utfbytes(data(x.fcall), cnt);
+ r = string data(x.fcall)[0:nb];
+ nr = len r;
+ if(nb < cnt){
+ x.f.rpart = data(x.fcall)[nb:cnt];
+ x.f.nrpart = cnt-nb;
+ }
+ if(nr > 0){
+ t.w.commit(t);
+ if(qid == QWwrsel){
+ q0 = w.wrselrange.q1;
+ if(q0 > t.file.buf.nc)
+ q0 = t.file.buf.nc;
+ }else
+ q0 = t.file.buf.nc;
+ if(qid == QWbody || qid == QWwrsel){
+ if(!w.nomark){
+ seq++;
+ t.file.mark();
+ }
+ (q0, nr) = t.bsinsert(q0, r, nr, TRUE);
+ if(qid!=QWwrsel && !t.w.noscroll)
+ t.show(q0+nr, q0+nr, TRUE);
+ scrdraw(t);
+ }else
+ t.insert(q0, r, nr, TRUE, 0);
+ w.settag();
+ if(qid == QWwrsel)
+ w.wrselrange.q1 += nr;
+ r = nil;
+ }
+ fc.count = count(x.fcall);
+ respond(x, fc, nil);
+ }
+ if(w != nil)
+ w.unlock();
+ row.qlock.unlock();
+}
+
+Xfid.ctlwrite(x : self ref Xfid, w : ref Window)
+{
+ fc : Smsg0;
+ i, m, n, nb : int;
+ r, err, p, pp : string;
+ q : int;
+ scrdrw, settag : int;
+ t : ref Text;
+
+ err = nil;
+ scrdrw = FALSE;
+ settag = FALSE;
+ w.tag.commit(TRUE);
+ nb = sys->utfbytes(data(x.fcall), count(x.fcall));
+ r = string data(x.fcall)[0:nb];
+loop :
+ for(n=0; n<len r; n+=m){
+ p = r[n:];
+ if(strncmp(p, "lock", 4) == 0){ # make window exclusive use
+ w.ctllock.lock();
+ w.ctlfid = x.f.fid;
+ m = 4;
+ }else
+ if(strncmp(p, "unlock", 6) == 0){ # release exclusive use
+ w.ctlfid = ~0;
+ w.ctllock.unlock();
+ m = 6;
+ }else
+ if(strncmp(p, "clean", 5) == 0){ # mark window 'clean', seq=0
+ t = w.body;
+ t.eq0 = ~0;
+ t.file.reset();
+ t.file.mod = FALSE;
+ w.dirty = FALSE;
+ settag = TRUE;
+ m = 5;
+ }else
+ if(strncmp(p, "show", 4) == 0){ # show dot
+ t = w.body;
+ t.show(t.q0, t.q1, TRUE);
+ m = 4;
+ }else
+ if(strncmp(p, "name ", 5) == 0){ # set file name
+ pp = p[5:];
+ m = 5;
+ q = utils->strchr(pp, '\n');
+ if(q<=0){
+ err = Ebadctl;
+ break;
+ }
+ nm := pp[0:q];
+ for(i=0; i<len nm; i++)
+ if(nm[i] <= ' '){
+ err = "bad character in file name";
+ break loop;
+ }
+ seq++;
+ w.body.file.mark();
+ w.setname(nm, len nm);
+ m += (q+1);
+ }else
+ if(strncmp(p, "dump ", 5) == 0){ # set dump string
+ pp = p[5:];
+ m = 5;
+ q = utils->strchr(pp, '\n');
+ if(q<=0){
+ err = Ebadctl;
+ break;
+ }
+ nm := pp[0:q];
+ w.dumpstr = nm;
+ m += (q+1);
+ }else
+ if(strncmp(p, "dumpdir ", 8) == 0){ # set dump directory
+ pp = p[8:];
+ m = 8;
+ q = utils->strchr(pp, '\n');
+ if(q<=0){
+ err = Ebadctl;
+ break;
+ }
+ nm := pp[0:q];
+ w.dumpdir = nm;
+ m += (q+1);
+ }else
+ if(strncmp(p, "delete", 6) == 0){ # delete for sure
+ w.col.close(w, TRUE);
+ m = 6;
+ }else
+ if(strncmp(p, "del", 3) == 0){ # delete, but check dirty
+ if(!w.clean(TRUE, FALSE)){
+ err = "file dirty";
+ break;
+ }
+ w.col.close(w, TRUE);
+ m = 3;
+ }else
+ if(strncmp(p, "get", 3) == 0){ # get file
+ exec->get(w.body, nil, nil, FALSE, nil, 0);
+ m = 3;
+ }else
+ if(strncmp(p, "put", 3) == 0){ # put file
+ exec->put(w.body, nil, nil, 0);
+ m = 3;
+ }else
+ if(strncmp(p, "dot=addr", 8) == 0){ # set dot
+ w.body.commit(TRUE);
+ clampaddr(w);
+ w.body.q0 = w.addr.q0;
+ w.body.q1 = w.addr.q1;
+ w.body.setselect(w.body.q0, w.body.q1);
+ settag = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "addr=dot", 8) == 0){ # set addr
+ w.addr.q0 = w.body.q0;
+ w.addr.q1 = w.body.q1;
+ m = 8;
+ }else
+ if(strncmp(p, "limit=addr", 10) == 0){ # set limit
+ w.body.commit(TRUE);
+ clampaddr(w);
+ w.limit.q0 = w.addr.q0;
+ w.limit.q1 = w.addr.q1;
+ m = 10;
+ }else
+ if(strncmp(p, "nomark", 6) == 0){ # turn off automatic marking
+ w.nomark = TRUE;
+ m = 6;
+ }else
+ if(strncmp(p, "mark", 4) == 0){ # mark file
+ seq++;
+ w.body.file.mark();
+ settag = TRUE;
+ m = 4;
+ }else
+ if(strncmp(p, "noscroll", 8) == 0){ # turn off automatic scrolling
+ w.noscroll = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "cleartag", 8) == 0){ # wipe tag right of bar
+ w.cleartag();
+ settag = TRUE;
+ m = 8;
+ }else
+ if(strncmp(p, "scroll", 6) == 0){ # turn on automatic scrolling (writes to body only)
+ w.noscroll = FALSE;
+ m = 6;
+ }else
+ if(strncmp(p, "noecho", 6) == 0){ # don't echo chars - mask them
+ w.echomode = EM_MASK;
+ m = 6;
+ }else
+ if (strncmp(p, "echo", 4) == 0){ # echo chars (normal state)
+ w.echomode = EM_NORMAL;
+ m = 4;
+ }else{
+ err = Ebadctl;
+ break;
+ }
+ while(m < len p && p[m] == '\n')
+ m++;
+ }
+
+ ab := array of byte r[0:n];
+ n = len ab;
+ ab = nil;
+ r = nil;
+ if(err != nil)
+ n = 0;
+ fc.count = n;
+ respond(x, fc, err);
+ if(settag)
+ w.settag();
+ if(scrdrw)
+ scrdraw(w.body);
+}
+
+Xfid.eventwrite(x : self ref Xfid, w : ref Window)
+{
+ fc : Smsg0;
+ m, n, nb : int;
+ r, err : string;
+ p, q : int;
+ t : ref Text;
+ c : int;
+ q0, q1 : int;
+
+ err = nil;
+ nb = sys->utfbytes(data(x.fcall), count(x.fcall));
+ r = string data(x.fcall)[0:nb];
+loop :
+ for(n=0; n<len r; n+=m){
+ p = n;
+ w.owner = r[p++]; # disgusting
+ c = r[p++];
+ while(r[p] == ' ')
+ p++;
+ q0 = int r[p:];
+ q = p;
+ if (r[q] == '+' || r[q] == '-')
+ q++;
+ while (r[q] >= '0' && r[q] <= '9')
+ q++;
+ if(q == p) {
+ err = Ebadevent;
+ break;
+ }
+ p = q;
+ while(r[p] == ' ')
+ p++;
+ q1 = int r[p:];
+ q = p;
+ if (r[q] == '+' || r[q] == '-')
+ q++;
+ while (r[q] >= '0' && r[q] <= '9')
+ q++;
+ if(q == p) {
+ err = Ebadevent;
+ break;
+ }
+ p = q;
+ while(r[p] == ' ')
+ p++;
+ if(r[p++] != '\n') {
+ err = Ebadevent;
+ break;
+ }
+ m = p-n;
+ if('a'<=c && c<='z')
+ t = w.tag;
+ else if('A'<=c && c<='Z')
+ t = w.body;
+ else {
+ err = Ebadevent;
+ break;
+ }
+ if(q0>t.file.buf.nc || q1>t.file.buf.nc || q0>q1) {
+ err = Ebadevent;
+ break;
+ }
+ # row.qlock.lock();
+ case(c){
+ 'x' or 'X' =>
+ exec->execute(t, q0, q1, TRUE, nil);
+ 'l' or 'L' =>
+ look->look3(t, q0, q1, TRUE);
+ * =>
+ err = Ebadevent;
+ break loop;
+ }
+ # row.qlock.unlock();
+ }
+
+ ab := array of byte r[0:n];
+ n = len ab;
+ ab = nil;
+ r = nil;
+ if(err != nil)
+ n = 0;
+ fc.count = n;
+ respond(x, fc, err);
+}
+
+Xfid.utfread(x : self ref Xfid, t : ref Text, q0, q1 : int, qid : int)
+{
+ fc : Smsg0;
+ w : ref Window;
+ r : ref Astring;
+ b, b1 : array of byte;
+ q, off, boff : int;
+ m, n, nr, nb : int;
+
+ w = t.w;
+ w.commit(t);
+ off = int offset(x.fcall);
+ r = stralloc(BUFSIZE);
+ b1 = array[MAXRPC] of byte;
+ n = 0;
+ if(qid==w.utflastqid && off>=w.utflastboff && w.utflastq<=q1){
+ boff = w.utflastboff;
+ q = w.utflastq;
+ }else{
+ # BUG: stupid code: scan from beginning
+ boff = 0;
+ q = q0;
+ }
+ w.utflastqid = qid;
+ while(q<q1 && n<count(x.fcall)){
+ w.utflastboff = boff;
+ w.utflastq = q;
+ nr = q1-q;
+ if(nr > BUFSIZE)
+ nr = BUFSIZE;
+ t.file.buf.read(q, r, 0, nr);
+ b = array of byte r.s[0:nr];
+ nb = len b;
+ if(boff >= off){
+ m = nb;
+ if(boff+m > off+count(x.fcall))
+ m = off+count(x.fcall) - boff;
+ b1[n:] = b[0:m];
+ n += m;
+ }else if(boff+nb > off){
+ if(n != 0)
+ error("bad count in utfrune");
+ m = nb - (off-boff);
+ if(m > count(x.fcall))
+ m = count(x.fcall);
+ b1[0:] = b[off-boff:off-boff+m];
+ n += m;
+ }
+ b = nil;
+ boff += nb;
+ q += nr;
+ }
+ strfree(r);
+ r = nil;
+ fc.count = n;
+ fc.data = b1;
+ respond(x, fc, nil);
+ b1 = nil;
+}
+
+Xfid.runeread(x : self ref Xfid, t : ref Text, q0, q1 : int) : int
+{
+ fc : Smsg0;
+ w : ref Window;
+ r : ref Astring;
+ junk, ok : int;
+ b, b1 : array of byte;
+ q, boff : int;
+ i, rw, m, n, nr, nb : int;
+
+ w = t.w;
+ w.commit(t);
+ r = stralloc(BUFSIZE);
+ b1 = array[MAXRPC] of byte;
+ n = 0;
+ q = q0;
+ boff = 0;
+ while(q<q1 && n<count(x.fcall)){
+ nr = q1-q;
+ if(nr > BUFSIZE)
+ nr = BUFSIZE;
+ t.file.buf.read(q, r, 0, nr);
+ b = array of byte r.s[0:nr];
+ nb = len b;
+ m = nb;
+ if(boff+m > count(x.fcall)){
+ i = count(x.fcall) - boff;
+ # copy whole runes only
+ m = 0;
+ nr = 0;
+ while(m < i){
+ (junk, rw, ok) = sys->byte2char(b, m);
+ if(m+rw > i)
+ break;
+ m += rw;
+ nr++;
+ }
+ if(m == 0)
+ break;
+ }
+ b1[n:] = b[0:m];
+ b = nil;
+ n += m;
+ boff += nb;
+ q += nr;
+ }
+ strfree(r);
+ r = nil;
+ fc.count = n;
+ fc.data = b1;
+ respond(x, fc, nil);
+ b1 = nil;
+ return q-q0;
+}
+
+Xfid.eventread(x : self ref Xfid, w : ref Window)
+{
+ fc : Smsg0;
+ b : string;
+ i, n : int;
+
+ i = 0;
+ x.flushed = FALSE;
+ while(w.nevents == 0){
+ if(i){
+ if(!x.flushed)
+ respond(x, fc, "window shut down");
+ return;
+ }
+ w.eventx = x;
+ w.unlock();
+ <- x.c;
+ w.lock('F');
+ i++;
+ }
+ eveb := array of byte w.events;
+ ne := len eveb;
+ n = w.nevents;
+ if(ne > count(x.fcall)) {
+ ne = count(x.fcall);
+ while (sys->utfbytes(eveb, ne) != ne)
+ --ne;
+ s := string eveb[0:ne];
+ n = len s;
+ s = nil;
+ }
+ fc.count = ne;
+ fc.data = eveb;
+ respond(x, fc, nil);
+ b = w.events;
+ w.events = w.events[n:];
+ b = nil;
+ w.nevents -= n;
+ eveb = nil;
+}
+
+Xfid.indexread(x : self ref Xfid)
+{
+ fc : Smsg0;
+ i, j, m, n, nmax, cnt, off : int;
+ w : ref Window;
+ b : array of byte;
+ r : ref Astring;
+ c : ref Column;
+
+ row.qlock.lock();
+ nmax = 0;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ nmax += Ctlsize + w.tag.file.buf.nc*UTFmax + 1;
+ }
+ }
+ nmax++;
+ b = array[nmax] of byte;
+ r = stralloc(BUFSIZE);
+ n = 0;
+ for(j=0; j<row.ncol; j++){
+ c = row.col[j];
+ for(i=0; i<c.nw; i++){
+ w = c.w[i];
+ # only show the currently active window of a set
+ if(w.body.file.curtext != w.body)
+ continue;
+ ctls := w.ctlprint(0);
+ ctlb := array of byte ctls;
+ if (len ctls != Ctlsize || len ctlb != Ctlsize)
+ error("bad length in indexread");
+ b[n:] = ctlb[0:];
+ n += Ctlsize;
+ ctls = nil;
+ ctlb = nil;
+ m = min(BUFSIZE, w.tag.file.buf.nc);
+ w.tag.file.buf.read(0, r, 0, m);
+ rb := array of byte r.s[0:m];
+ b[n:] = rb[0:len rb];
+ m = n+len rb;
+ rb = nil;
+ while(n<m && b[n]!=byte '\n')
+ n++;
+ b[n++] = byte '\n';
+ }
+ }
+ row.qlock.unlock();
+ off = int offset(x.fcall);
+ cnt = count(x.fcall);
+ if(off > n)
+ off = n;
+ if(off+cnt > n)
+ cnt = n-off;
+ fc.count = cnt;
+ fc.data = b[off:off+cnt];
+ respond(x, fc, nil);
+ b = nil;
+ strfree(r);
+ r = nil;
+}
--- /dev/null
+++ b/appl/acme/xfid.m
@@ -1,0 +1,34 @@
+Xfidm : module {
+ PATH : con "/dis/acme/xfid.dis";
+
+ Xnil, Xflush, Xwalk, Xopen, Xclose, Xread, Xwrite : con iota;
+
+ init : fn(mods : ref Dat->Mods);
+
+ newxfid : fn() : ref Xfid;
+ xfidkill : fn();
+
+ Xfid : adt {
+ tid : int;
+ fcall : ref Styx->Tmsg;
+ next : cyclic ref Xfid;
+ c : chan of int;
+ f : cyclic ref Dat->Fid;
+ buf : array of byte;
+ flushed : int;
+
+ ctl : fn(x : self ref Xfid);
+ flush: fn(x : self ref Xfid);
+ walk: fn(x : self ref Xfid, c: chan of ref Windowm->Window);
+ open: fn(x : self ref Xfid);
+ close: fn(x : self ref Xfid);
+ read: fn(x : self ref Xfid);
+ write: fn(x : self ref Xfid);
+ ctlwrite: fn(x : self ref Xfid, w : ref Windowm->Window);
+ eventread: fn(x : self ref Xfid, w : ref Windowm->Window);
+ eventwrite: fn(x : self ref Xfid, w : ref Windowm->Window);
+ indexread: fn(x : self ref Xfid);
+ utfread: fn(x : self ref Xfid, t : ref Textm->Text, m : int, n : int, qid : int);
+ runeread: fn(x : self ref Xfid, t : ref Textm->Text, m : int, n : int) : int;
+ };
+};
--- /dev/null
+++ b/appl/alphabet/abc/abc.b
@@ -1,0 +1,53 @@
+implement Mkabc, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "alphabet/reports.m";
+ reports: Reports;
+ report: import reports;
+include "alphabet.m";
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+
+Mkabc: module {};
+types(): string
+{
+ return "A";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ reports = checkload(load Reports Reports->PATH, Reports->PATH);
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+}
+
+quit()
+{
+}
+
+run(errorc: chan of string, nil: ref Reports->Report,
+ nil: list of (int, list of ref Value),
+ nil: list of ref Value
+ ): ref Value
+{
+ alphabet := load Alphabet Alphabet->PATH;
+ if(alphabet == nil){
+ report(errorc, sys->sprint("abc: cannot load %q: %r", Alphabet->PATH));
+ return nil;
+ }
+ alphabet->init();
+ c := chan[1] of int;
+ c <-= 1;
+ return ref Value.VA((c, alphabet));
+}
+
+checkload[T](m: T, path: string): T
+{
+ if(m != nil)
+ return m;
+ raise sys->sprint("fail:cannot load %s: %r", path);
+}
--- /dev/null
+++ b/appl/alphabet/abc/autoconvert.b
@@ -1,0 +1,80 @@
+implement Autoconvert, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ Cmd,
+ n_BLOCK, n_WORD, n_SEQ, n_LIST, n_ADJ, n_VAR: import Sh;
+include "alphabet/reports.m";
+ reports: Reports;
+ report: import reports;
+include "alphabet.m";
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+
+Autoconvert: module {};
+types(): string
+{
+ return "AAssc";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ reports = checkload(load Reports Reports->PATH, Reports->PATH);
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+}
+
+quit()
+{
+}
+
+run(errorc: chan of string, nil: ref Reports->Report,
+ nil: list of (int, list of ref Value),
+ args: list of ref Value
+ ): ref Value
+{
+ a := (hd args).A().i.alphabet;
+ src := (hd tl args).s().i;
+ dst := (hd tl tl args).s().i;
+ c := (hd tl tl tl args).c().i;
+
+ # {word} -> {(src); word $1}
+ if(c.ntype == n_BLOCK && c.left.ntype == n_WORD){
+ c = mk(n_BLOCK,
+ mk(n_SEQ,
+ mk(n_LIST, mkw(src), nil),
+ mk(n_ADJ,
+ c.left,
+ mk(n_VAR, mkw("1"), nil)
+ )
+ ),
+ nil
+ );
+ }
+
+ err := a->autoconvert(src, dst, c, errorc);
+ if(err != nil){
+ report(errorc, "abcautoconvert: "+err);
+ return nil;
+ }
+ return (hd args).dup();
+}
+
+checkload[T](m: T, path: string): T
+{
+ if(m != nil)
+ return m;
+ raise sys->sprint("fail:cannot load %s: %r", path);
+}
+
+mk(ntype: int, left, right: ref Cmd): ref Cmd
+{
+ return ref Cmd(ntype, left, right, nil, nil);
+}
+mkw(w: string): ref Cmd
+{
+ return ref Cmd(n_WORD, nil, nil, w, nil);
+}
--- /dev/null
+++ b/appl/alphabet/abc/autodeclare.b
@@ -1,0 +1,42 @@
+implement Autoconvert, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "alphabet/reports.m";
+include "alphabet.m";
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+
+Autoconvert: module {};
+types(): string
+{
+ return "AAs";
+}
+
+init()
+{
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+}
+
+quit()
+{
+}
+
+run(nil: chan of string, nil: ref Reports->Report,
+ nil: list of (int, list of ref Value),
+ args: list of ref Value
+ ): ref Value
+{
+ (hd args).A().i.alphabet->setautodeclare(int (hd tl args).s().i);
+ return (hd args).dup();
+}
+
+checkload[T](m: T, path: string): T
+{
+ if(m != nil)
+ return m;
+ raise sys->sprint("fail:cannot load %s: %r", path);
+}
--- /dev/null
+++ b/appl/alphabet/abc/declare.b
@@ -1,0 +1,70 @@
+implement Declare, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "alphabet/reports.m";
+ reports: Reports;
+ report: import reports;
+include "alphabet.m";
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+
+Declare: module {};
+types(): string
+{
+ return "AAss*-q-c";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ reports = checkload(load Reports Reports->PATH, Reports->PATH);
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+}
+
+quit()
+{
+}
+
+run(errorc: chan of string, nil: ref Reports->Report,
+ opts: list of (int, list of ref Value),
+ args: list of ref Value
+ ): ref Value
+{
+ flags := 0;
+ for(; opts != nil; opts = tl opts){
+ case (hd opts).t0 {
+ 'q' =>
+ flags |= Alphabet->ONDEMAND;
+ 'c' =>
+ flags |= Alphabet->CHECK;
+ }
+ }
+
+ n := len args;
+ if(n > 3){
+ report(errorc, "declare: maximum of two arguments allowed");
+ return nil;
+ }
+ a := (hd args).A().i.alphabet;
+ m := (hd tl args).s().i;
+ sig := "";
+ if(n > 2)
+ sig = (hd tl tl args).s().i;
+ e := a->declare(m, sig, flags);
+ if(e != nil){
+ report(errorc, "declare: "+e);
+ return nil;
+ }
+ return (hd args).dup();
+}
+
+checkload[T](m: T, path: string): T
+{
+ if(m != nil)
+ return m;
+ raise sys->sprint("fail:cannot load %s: %r", path);
+}
--- /dev/null
+++ b/appl/alphabet/abc/declares.b
@@ -1,0 +1,124 @@
+implement Declares, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ n_BLOCK, n_ADJ, n_VAR, n_WORD: import Sh;
+include "alphabet/reports.m";
+ reports: Reports;
+ report, Report: import reports;
+include "alphabet.m";
+ alphabet: Alphabet;
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+include "alphabet/abctypes.m";
+ abctypes: Abctypes;
+ Abccvt: import abctypes;
+
+cvt: ref Abccvt;
+
+types(): string
+{
+ return "AAc";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ reports = checkload(load Reports Reports->PATH, Reports->PATH);
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+ alphabet = checkload(load Alphabet Alphabet->PATH, Alphabet->PATH);
+ alphabet->init();
+ abctypes = checkload(load Abctypes Abctypes->PATH, Abctypes->PATH);
+ (c, nil, abccvt) := abctypes->proxy0();
+ cvt = abccvt;
+ alphabet->loadtypeset("/abc", c, nil);
+ alphabet->importtype("/abc/abc");
+ alphabet->importtype("/string");
+ alphabet->importtype("/cmd");
+ c = nil;
+ # note: it's faster if we provide the signatures, as we don't
+ # have to load the module to find out its signature just to throw
+ # it away again. pity about the maintenance.
+
+ # Edit x s:(/abc/[a-z]+) (.*):declimport("\1", "\2");
+ declimport("/abc/autoconvert", "abc string string cmd -> abc");
+ declimport("/abc/autodeclare", "abc string -> abc");
+ declimport("/abc/declare", "[-qc] abc string [string...] -> abc");
+ declimport("/abc/define", "abc string cmd -> abc");
+ declimport("/abc/import", "abc string [string...] -> abc");
+ declimport("/abc/type", "abc string [string...] -> abc");
+ declimport("/abc/typeset", "abc string -> abc");
+ declimport("/abc/undeclare", "abc string [string...] -> abc");
+}
+
+quit()
+{
+ alphabet->quit();
+}
+
+run(errorc: chan of string, r: ref Reports->Report,
+ nil: list of (int, list of ref Value),
+ args: list of ref Value
+ ): ref Value
+{
+ (av, err) := alphabet->importvalue(cvt.int2ext((hd args).dup()), "/abc/abc");
+ if(av == nil){
+ report(errorc, sys->sprint("declares: cannot import abc value: %s", err));
+ return nil;
+ }
+ vc := chan of ref Alphabet->Value;
+ spawn alphabet->eval0((hd tl args).c().i, "/abc/abc", nil, r, r.start("evaldecl"), av :: nil, vc);
+ av = <-vc;
+ if(av == nil)
+ return nil;
+ v := cvt.ext2int(av).dup();
+ alphabet->av.free(1);
+ return v;
+}
+
+declimport(m: string, sig: string)
+{
+ if((e := alphabet->declare(m, sig, Alphabet->ONDEMAND)) != nil)
+ raise sys->sprint("fail:cannot declare %s: %s", m, e);
+ alphabet->importmodule(m);
+}
+
+checkload[T](m: T, path: string): T
+{
+ if(m != nil)
+ return m;
+ raise sys->sprint("fail:cannot load %s: %r", path);
+}
+
+declares(a: Alphabet, decls: ref Sh->Cmd, errorc: chan of string, stopc: chan of int): string
+{
+ spawn reports->reportproc(reportc := chan of string, stopc, reply := chan of ref Report);
+ r := <-reply;
+ reply = nil;
+ spawn declaresproc(a, decls, r.start("declares"), r, vc := chan of ref Value);
+ r.enable();
+
+ v: ref Value;
+wait:
+ for(;;)alt{
+ v = <-vc =>
+ ;
+ msg := <-reportc =>
+ if(msg == nil)
+ break wait;
+ errorc <-= sys->sprint("declares: %s", msg);
+ }
+ if(v == nil)
+ return "declarations failed";
+ return nil;
+}
+
+declaresproc(a: Alphabet, decls: ref Sh->Cmd, errorc: chan of string, r: ref Report, vc: chan of ref Value)
+{
+ novals: list of ref Value;
+ vc <-= run(errorc, r, nil, abc->mkabc(a).dup() :: ref Value.Vc(decls) :: novals);
+ errorc <-= nil;
+}
--- /dev/null
+++ b/appl/alphabet/abc/define.b
@@ -1,0 +1,52 @@
+implement Define, Abcmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "alphabet/reports.m";
+ reports: Reports;
+ report: import reports;
+include "alphabet.m";
+include "alphabet/abc.m";
+ abc: Abc;
+ Value: import abc;
+
+Define: module {};
+types(): string
+{
+ return "AAsc";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ reports = checkload(load Reports Reports->PATH, Reports->PATH);
+ abc = checkload(load Abc Abc->PATH, Abc->PATH);
+ abc->init();
+}
+
+quit()
+{
+}
+
+run(errorc: chan of string, nil: ref Reports->Report,
+ nil: list of (int, list of ref Value),
+ args: list of ref V