diff options
Diffstat (limited to 'appl/acme')
94 files changed, 25764 insertions, 0 deletions
diff --git a/appl/acme/acme.b b/appl/acme/acme.b new file mode 100644 index 00000000..5d880f8b --- /dev/null +++ b/appl/acme/acme.b @@ -0,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(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; + + tagcols = array[NCOL] of ref Draw->Image; + tagcols[BACK] = imagemix(display.rgb(16raa, 16rff, 16rff), white, 1, 3); # mix DPalebluegreenwith DWhite + tagcols[HIGH] = display.rgb(16r9e, 16ree, 16ree); # was DPalegreygreen + tagcols[BORD] = display.rgb(16r88, 16r88, 16rcc); # was DPurpleblue + tagcols[TEXT] = black; + tagcols[HTEXT] = black; + + textcols = array[NCOL] of ref Draw->Image; + textcols[BACK] = imagemix(display.rgb(16rff, 16rff, 16raa), white, 1, 3); # mix DPaleyellow with DWhite + textcols[HIGH] = display.rgb(16ree, 16ree, 16r9e); # was DDarkyellow + textcols[BORD] = display.rgb(16r99, 16r99, 16r4c); # was Dyellowgreen + 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)); +} + +imagemix(i1 : ref Image, i2 : ref Image, n1 : int, n2 : int) : ref Image +{ + # 2,2 1,3 only : generalize later + i3 := balloc(((0, 0), (2, 2)), mainwin.chans, Draw->White); + draw(i3, i3.r, i2, nil, (0, 0)); + draw(i3, ((0, 0), (1, 1)), i1, nil, (0, 0)); + if (n1 == n2) + draw(i3, ((1, 1), (2, 2)), i1, nil, (0, 0)); + i3.repl = 1; + i3.clipr = ((-1729, -1729), (1729, 1729)); + return i3; +} + +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) { + (r, g, b) := rgb(c, 1); + i1 := display.rgb(r, g, b); + if (len c >= 15 && c[7] == '/' && c[8] == '#') { + (r, g, b) = rgb(c, 9); + i2 := display.rgb(r, g, b); + return imagemix(i1, i2, 2, 2); + } + return i1; + } + 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; + } +} diff --git a/appl/acme/acme.m b/appl/acme/acme.m new file mode 100644 index 00000000..bbd586f0 --- /dev/null +++ b/appl/acme/acme.m @@ -0,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(); +};
\ No newline at end of file diff --git a/appl/acme/acme/acid/guide b/appl/acme/acme/acid/guide new file mode 100644 index 00000000..0fc99511 --- /dev/null +++ b/appl/acme/acme/acid/guide @@ -0,0 +1,2 @@ +Acid pid +Acid -l alef -l symsfile pid diff --git a/appl/acme/acme/acid/mkfile b/appl/acme/acme/acid/mkfile new file mode 100644 index 00000000..f867a177 --- /dev/null +++ b/appl/acme/acme/acid/mkfile @@ -0,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
\ No newline at end of file diff --git a/appl/acme/acme/acid/readme b/appl/acme/acme/acid/readme new file mode 100644 index 00000000..fcf343be --- /dev/null +++ b/appl/acme/acme/acid/readme @@ -0,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. diff --git a/appl/acme/acme/acid/src/Acid.b b/appl/acme/acme/acid/src/Acid.b new file mode 100644 index 00000000..d923160b --- /dev/null +++ b/appl/acme/acme/acid/src/Acid.b @@ -0,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); +}
\ No newline at end of file diff --git a/appl/acme/acme/acid/src/Acid0.b b/appl/acme/acme/acid/src/Acid0.b new file mode 100644 index 00000000..887110e9 --- /dev/null +++ b/appl/acme/acme/acid/src/Acid0.b @@ -0,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; diff --git a/appl/acme/acme/acid/src/mkfile b/appl/acme/acme/acid/src/mkfile new file mode 100644 index 00000000..c53a358e --- /dev/null +++ b/appl/acme/acme/acid/src/mkfile @@ -0,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
\ No newline at end of file diff --git a/appl/acme/acme/bin/guide b/appl/acme/acme/bin/guide new file mode 100644 index 00000000..31cea12e --- /dev/null +++ b/appl/acme/acme/bin/guide @@ -0,0 +1,5 @@ +win +new command ... +aspell file +adiff file1 file2 +adict -d oed diff --git a/appl/acme/acme/bin/mkfile b/appl/acme/acme/bin/mkfile new file mode 100644 index 00000000..6ab4d64d --- /dev/null +++ b/appl/acme/acme/bin/mkfile @@ -0,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
\ No newline at end of file diff --git a/appl/acme/acme/bin/readme b/appl/acme/acme/bin/readme new file mode 100644 index 00000000..941099e8 --- /dev/null +++ b/appl/acme/acme/bin/readme @@ -0,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. diff --git a/appl/acme/acme/bin/src/adiff.b b/appl/acme/acme/bin/src/adiff.b new file mode 100644 index 00000000..28021f1d --- /dev/null +++ b/appl/acme/acme/bin/src/adiff.b @@ -0,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; +} diff --git a/appl/acme/acme/bin/src/agrep.b b/appl/acme/acme/bin/src/agrep.b new file mode 100644 index 00000000..79ea6ff9 --- /dev/null +++ b/appl/acme/acme/bin/src/agrep.b @@ -0,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; +} diff --git a/appl/acme/acme/bin/src/awd.b b/appl/acme/acme/bin/src/awd.b new file mode 100644 index 00000000..41c1e32f --- /dev/null +++ b/appl/acme/acme/bin/src/awd.b @@ -0,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; +} diff --git a/appl/acme/acme/bin/src/cd.b b/appl/acme/acme/bin/src/cd.b new file mode 100644 index 00000000..31843921 --- /dev/null +++ b/appl/acme/acme/bin/src/cd.b @@ -0,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]; +} diff --git a/appl/acme/acme/bin/src/mkfile b/appl/acme/acme/bin/src/mkfile new file mode 100644 index 00000000..3dfb52b1 --- /dev/null +++ b/appl/acme/acme/bin/src/mkfile @@ -0,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 diff --git a/appl/acme/acme/bin/src/new.b b/appl/acme/acme/bin/src/new.b new file mode 100644 index 00000000..1a88e203 --- /dev/null +++ b/appl/acme/acme/bin/src/new.b @@ -0,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; +}
\ No newline at end of file diff --git a/appl/acme/acme/bin/src/spout.b b/appl/acme/acme/bin/src/spout.b new file mode 100644 index 00000000..168ddc52 --- /dev/null +++ b/appl/acme/acme/bin/src/spout.b @@ -0,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(); +} diff --git a/appl/acme/acme/bin/src/win.b b/appl/acme/acme/bin/src/win.b new file mode 100644 index 00000000..f8299ee1 --- /dev/null +++ b/appl/acme/acme/bin/src/win.b @@ -0,0 +1,764 @@ +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; +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; + 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; + 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) +{ + 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; +} + diff --git a/appl/acme/acme/bin/src/winm.b b/appl/acme/acme/bin/src/winm.b new file mode 100644 index 00000000..f7b9ae19 --- /dev/null +++ b/appl/acme/acme/bin/src/winm.b @@ -0,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; +} + diff --git a/appl/acme/acme/edit/guide b/appl/acme/acme/edit/guide new file mode 100644 index 00000000..5beced2e --- /dev/null +++ b/appl/acme/acme/edit/guide @@ -0,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 diff --git a/appl/acme/acme/edit/mkfile b/appl/acme/acme/edit/mkfile new file mode 100644 index 00000000..4f51f170 --- /dev/null +++ b/appl/acme/acme/edit/mkfile @@ -0,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
\ No newline at end of file diff --git a/appl/acme/acme/edit/readme b/appl/acme/acme/edit/readme new file mode 100644 index 00000000..e4d29579 --- /dev/null +++ b/appl/acme/acme/edit/readme @@ -0,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. diff --git a/appl/acme/acme/edit/src/a.b b/appl/acme/acme/edit/src/a.b new file mode 100644 index 00000000..ab950d71 --- /dev/null +++ b/appl/acme/acme/edit/src/a.b @@ -0,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; +} diff --git a/appl/acme/acme/edit/src/c.b b/appl/acme/acme/edit/src/c.b new file mode 100644 index 00000000..f3bdd060 --- /dev/null +++ b/appl/acme/acme/edit/src/c.b @@ -0,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; +} diff --git a/appl/acme/acme/edit/src/d.b b/appl/acme/acme/edit/src/d.b new file mode 100644 index 00000000..0b663942 --- /dev/null +++ b/appl/acme/acme/edit/src/d.b @@ -0,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); +}
\ No newline at end of file diff --git a/appl/acme/acme/edit/src/e.b b/appl/acme/acme/edit/src/e.b new file mode 100644 index 00000000..3b71db1d --- /dev/null +++ b/appl/acme/acme/edit/src/e.b @@ -0,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); + + # print + 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); +} diff --git a/appl/acme/acme/edit/src/findfile.b b/appl/acme/acme/edit/src/findfile.b new file mode 100644 index 00000000..b8bf4c9c --- /dev/null +++ b/appl/acme/acme/edit/src/findfile.b @@ -0,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; + } + } +}
\ No newline at end of file diff --git a/appl/acme/acme/edit/src/g.b b/appl/acme/acme/edit/src/g.b new file mode 100644 index 00000000..87c57ff0 --- /dev/null +++ b/appl/acme/acme/edit/src/g.b @@ -0,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; +} diff --git a/appl/acme/acme/edit/src/i.b b/appl/acme/acme/edit/src/i.b new file mode 100644 index 00000000..ecb3e01f --- /dev/null +++ b/appl/acme/acme/edit/src/i.b @@ -0,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; +} diff --git a/appl/acme/acme/edit/src/input.b b/appl/acme/acme/edit/src/input.b new file mode 100644 index 00000000..64ab9297 --- /dev/null +++ b/appl/acme/acme/edit/src/input.b @@ -0,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:]; + } diff --git a/appl/acme/acme/edit/src/mkfile b/appl/acme/acme/edit/src/mkfile new file mode 100644 index 00000000..394b6017 --- /dev/null +++ b/appl/acme/acme/edit/src/mkfile @@ -0,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 diff --git a/appl/acme/acme/edit/src/p.b b/appl/acme/acme/edit/src/p.b new file mode 100644 index 00000000..feeb7f88 --- /dev/null +++ b/appl/acme/acme/edit/src/p.b @@ -0,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); + + # print + 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; +} diff --git a/appl/acme/acme/edit/src/pipe.b b/appl/acme/acme/edit/src/pipe.b new file mode 100644 index 00000000..f96289d9 --- /dev/null +++ b/appl/acme/acme/edit/src/pipe.b @@ -0,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); +}
\ No newline at end of file diff --git a/appl/acme/acme/edit/src/x.b b/appl/acme/acme/edit/src/x.b new file mode 100644 index 00000000..e665ffd2 --- /dev/null +++ b/appl/acme/acme/edit/src/x.b @@ -0,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; +} diff --git a/appl/acme/acme/edit/src/xxx.b b/appl/acme/acme/edit/src/xxx.b new file mode 100644 index 00000000..793f6d10 --- /dev/null +++ b/appl/acme/acme/edit/src/xxx.b @@ -0,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); + + # print + 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; +}
\ No newline at end of file diff --git a/appl/acme/acme/mail/guide b/appl/acme/acme/mail/guide new file mode 100644 index 00000000..2e23b9e1 --- /dev/null +++ b/appl/acme/acme/mail/guide @@ -0,0 +1,5 @@ +Mail /mail/box/$user/stored +Mail +Mailpop3 +mkbox /mail/box/$user/new_box +mail -'x' someaddress diff --git a/appl/acme/acme/mail/mkbox.b b/appl/acme/acme/mail/mkbox.b new file mode 100644 index 00000000..c0da4a78 --- /dev/null +++ b/appl/acme/acme/mail/mkbox.b @@ -0,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; + } + } +} diff --git a/appl/acme/acme/mail/mkfile b/appl/acme/acme/mail/mkfile new file mode 100644 index 00000000..7ede0684 --- /dev/null +++ b/appl/acme/acme/mail/mkfile @@ -0,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
\ No newline at end of file diff --git a/appl/acme/acme/mail/readme b/appl/acme/acme/mail/readme new file mode 100644 index 00000000..9442f57e --- /dev/null +++ b/appl/acme/acme/mail/readme @@ -0,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. diff --git a/appl/acme/acme/mail/src/Mail.b b/appl/acme/acme/mail/src/Mail.b new file mode 100644 index 00000000..1b2cf7c3 --- /dev/null +++ b/appl/acme/acme/mail/src/Mail.b @@ -0,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); + } +} diff --git a/appl/acme/acme/mail/src/Mailp.b b/appl/acme/acme/mail/src/Mailp.b new file mode 100644 index 00000000..f9bb757f --- /dev/null +++ b/appl/acme/acme/mail/src/Mailp.b @@ -0,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; +} diff --git a/appl/acme/acme/mail/src/Mailpop3.b b/appl/acme/acme/mail/src/Mailpop3.b new file mode 100644 index 00000000..f267d737 --- /dev/null +++ b/appl/acme/acme/mail/src/Mailpop3.b @@ -0,0 +1,1716 @@ +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++) { + (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; + 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 +} diff --git a/appl/acme/acme/mail/src/mashfile b/appl/acme/acme/mail/src/mashfile new file mode 100644 index 00000000..6d9c433e --- /dev/null +++ b/appl/acme/acme/mail/src/mashfile @@ -0,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 {};
\ No newline at end of file diff --git a/appl/acme/acme/mail/src/mkfile b/appl/acme/acme/mail/src/mkfile new file mode 100644 index 00000000..3584e020 --- /dev/null +++ b/appl/acme/acme/mail/src/mkfile @@ -0,0 +1,16 @@ +<../../../../../mkconfig + +TARG=\ + Mail.dis\ + Mailpop3.dis\ + +MODULES=\ + +SYSMODULES=\ + sh.m\ + sys.m\ + draw.m\ + +DISBIN=$ROOT/acme/mail + +<$ROOT/mkfiles/mkdis diff --git a/appl/acme/acme/mkfile b/appl/acme/acme/mkfile new file mode 100644 index 00000000..fc75514b --- /dev/null +++ b/appl/acme/acme/mkfile @@ -0,0 +1,9 @@ +<../../../mkconfig + +DIRS=\ + acid\ + bin\ + edit\ + mail\ + +<$ROOT/mkfiles/mksubdirs
\ No newline at end of file diff --git a/appl/acme/buff.b b/appl/acme/buff.b new file mode 100644 index 00000000..f5d18146 --- /dev/null +++ b/appl/acme/buff.b @@ -0,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; +} diff --git a/appl/acme/buff.m b/appl/acme/buff.m new file mode 100644 index 00000000..606d9900 --- /dev/null +++ b/appl/acme/buff.m @@ -0,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; +}; diff --git a/appl/acme/col.b b/appl/acme/col.b new file mode 100644 index 00000000..c696fc5d --- /dev/null +++ b/appl/acme/col.b @@ -0,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; +} diff --git a/appl/acme/col.m b/appl/acme/col.m new file mode 100644 index 00000000..b38cad4f --- /dev/null +++ b/appl/acme/col.m @@ -0,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); + }; +}; diff --git a/appl/acme/common.m b/appl/acme/common.m new file mode 100644 index 00000000..6a64396b --- /dev/null +++ b/appl/acme/common.m @@ -0,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"; diff --git a/appl/acme/dat.b b/appl/acme/dat.b new file mode 100644 index 00000000..924d16e6 --- /dev/null +++ b/appl/acme/dat.b @@ -0,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); +}
\ No newline at end of file diff --git a/appl/acme/dat.m b/appl/acme/dat.m new file mode 100644 index 00000000..66a0f592 --- /dev/null +++ b/appl/acme/dat.m @@ -0,0 +1,280 @@ +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 '@'; + + 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; +}; diff --git a/appl/acme/disk.b b/appl/acme/disk.b new file mode 100644 index 00000000..932254c1 --- /dev/null +++ b/appl/acme/disk.b @@ -0,0 +1,136 @@ +implement Diskm; + +include "common.m"; + +sys : Sys; +acme : Acme; +utils : Utils; + +SZSHORT, Block, Blockincr, Astring : import Dat; +error : import utils; + +init(mods : ref Dat->Mods) +{ + sys = mods.sys; + acme = mods.acme; + utils = mods.utils; +} + +blist : ref Block; + +tempfile() : ref Sys->FD +{ + buf := sys->sprint("/tmp/X%d.%.4sacme", sys->pctl(0, nil), utils->getuser()); + for(i:='A'; i<='Z'; i++){ + buf[5] = i; + (ok, nil) := sys->stat(buf); + if(ok == 0) + continue; + fd := sys->create(buf, Sys->ORDWR|Sys->ORCLOSE, 8r600); + if(fd != nil) + return fd; + } + return nil; +} + +Disk.init() : ref Disk +{ + d : ref Disk; + + d = ref Disk; + d.free = array[Dat->Maxblock/Dat->Blockincr+1] of ref Block; + d.addr = 0; + d.fd = tempfile(); + if(d.fd == nil){ + error(sys->sprint("can't create temp file %r")); + acme->acmeexit("temp create"); + } + return d; +} + +ntosize(n : int) : (int, int) +{ + size : int; + + if (n > Dat->Maxblock) + error("bad assert in ntosize"); + size = n; + if(size & (Blockincr-1)) + size += Blockincr - (size & (Blockincr-1)); + # last bucket holds blocks of exactly Maxblock + return (size * SZSHORT, size/Blockincr); +} + +Disk.new(d : self ref Disk, n : int) : ref Block +{ + i, j, size : int; + b, bl : ref Block; + + (size, i) = ntosize(n); + b = d.free[i]; + if(b != nil) + d.free[i] = b.next; + else{ + # allocate in chunks to reduce malloc overhead + if(blist == nil){ + blist = ref Block; + bl = blist; + for(j=0; j<100-1; j++) { + bl.next = ref Block; + bl = bl.next; + } + } + b = blist; + blist = b.next; + b.addr = d.addr; + d.addr += size; + } + b.n = n; + return b; +} + +Disk.release(d : self ref Disk, b : ref Block) +{ + (nil, i) := ntosize(b.n); + b.next = d.free[i]; + d.free[i] = b; +} + +Disk.write(d : self ref Disk, bp : ref Block, r : string, n : int) : ref Block +{ + size, nsize, i : int; + b : ref Block; + ab : array of byte; + + b = bp; + (size, i) = ntosize(b.n); + (nsize, i) = ntosize(n); + if(size != nsize){ + d.release(b); + b = d.new(n); + } + if(sys->seek(d.fd, big b.addr, 0) < big 0) + error("seek error in temp file"); + ab = utils->stob(r, n); + if(sys->write(d.fd, ab, len ab) != len ab) + error("write error to temp file"); + ab = nil; + b.n = n; + return b; +} + +Disk.read(d : self ref Disk, b : ref Block, r : ref Astring, n : int) +{ + ab : array of byte; + + if (n > b.n) + error("bad assert in Disk.read"); + (nil, nil) := ntosize(b.n); + if(sys->seek(d.fd, big b.addr, 0) < big 0) + error("seek error in temp file"); + ab = array[n*SZSHORT] of byte; + if(sys->read(d.fd, ab, len ab) != len ab) + error("read error from temp file"); + utils->btos(ab, r); + ab = nil; +} diff --git a/appl/acme/disk.m b/appl/acme/disk.m new file mode 100644 index 00000000..66b2a2ba --- /dev/null +++ b/appl/acme/disk.m @@ -0,0 +1,19 @@ +Diskm : module { + PATH : con "/dis/acme/disk.dis"; + + init : fn(mods : ref Dat->Mods); + + Disk : adt { + fd : ref Sys->FD; + addr : int; # length of temp file + free : array of ref Dat->Block; + + init : fn() : ref Disk; + new : fn(d : self ref Disk, n : int) : ref Dat->Block; + release : fn(d : self ref Disk, b : ref Dat->Block); + read : fn(d : self ref Disk, b : ref Dat->Block, s : ref Dat->Astring, n : int); + write : fn(d : self ref Disk, b : ref Dat->Block, s : string, n : int) : ref Dat->Block; + }; + + tempfile: fn() : ref Sys->FD; +}; diff --git a/appl/acme/ecmd.b b/appl/acme/ecmd.b new file mode 100644 index 00000000..c7cc2408 --- /dev/null +++ b/appl/acme/ecmd.b @@ -0,0 +1,1349 @@ +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 + error("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); + 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; +} +
\ No newline at end of file diff --git a/appl/acme/ecmd.m b/appl/acme/ecmd.m new file mode 100644 index 00000000..e9367c2a --- /dev/null +++ b/appl/acme/ecmd.m @@ -0,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; +};
\ No newline at end of file diff --git a/appl/acme/edit.b b/appl/acme/edit.b new file mode 100644 index 00000000..92faad1a --- /dev/null +++ b/appl/acme/edit.b @@ -0,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; +} + diff --git a/appl/acme/edit.m b/appl/acme/edit.m new file mode 100644 index 00000000..0ea3503d --- /dev/null +++ b/appl/acme/edit.m @@ -0,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); +}; diff --git a/appl/acme/elog.b b/appl/acme/elog.b new file mode 100644 index 00000000..8dea6b4c --- /dev/null +++ b/appl/acme/elog.b @@ -0,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; +} diff --git a/appl/acme/elog.m b/appl/acme/elog.m new file mode 100644 index 00000000..b935ad6f --- /dev/null +++ b/appl/acme/elog.m @@ -0,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); + +}; + +
\ No newline at end of file diff --git a/appl/acme/exec.b b/appl/acme/exec.b new file mode 100644 index 00000000..204b6c37 --- /dev/null +++ b/appl/acme/exec.b @@ -0,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[1: ]; + } + 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); +# } + + diff --git a/appl/acme/exec.m b/appl/acme/exec.m new file mode 100644 index 00000000..9a93e24c --- /dev/null +++ b/appl/acme/exec.m @@ -0,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); +};
\ No newline at end of file diff --git a/appl/acme/file.b b/appl/acme/file.b new file mode 100644 index 00000000..1f53cc66 --- /dev/null +++ b/appl/acme/file.b @@ -0,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; +}
\ No newline at end of file diff --git a/appl/acme/file.m b/appl/acme/file.m new file mode 100644 index 00000000..7e68e40b --- /dev/null +++ b/appl/acme/file.m @@ -0,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; + }; +}; diff --git a/appl/acme/frame.b b/appl/acme/frame.b new file mode 100644 index 00000000..5ab920a1 --- /dev/null +++ b/appl/acme/frame.b @@ -0,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; +} diff --git a/appl/acme/frame.m b/appl/acme/frame.m new file mode 100644 index 00000000..c581e350 --- /dev/null +++ b/appl/acme/frame.m @@ -0,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); +}; diff --git a/appl/acme/fsys.b b/appl/acme/fsys.b new file mode 100644 index 00000000..65ce4982 --- /dev/null +++ b/appl/acme/fsys.b @@ -0,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; +} diff --git a/appl/acme/fsys.m b/appl/acme/fsys.m new file mode 100644 index 00000000..d964d7c4 --- /dev/null +++ b/appl/acme/fsys.m @@ -0,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; +};
\ No newline at end of file diff --git a/appl/acme/graph.b b/appl/acme/graph.b new file mode 100644 index 00000000..9b8eb738 --- /dev/null +++ b/appl/acme/graph.b @@ -0,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); +} diff --git a/appl/acme/graph.m b/appl/acme/graph.m new file mode 100644 index 00000000..147771c2 --- /dev/null +++ b/appl/acme/graph.m @@ -0,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; +};
\ No newline at end of file diff --git a/appl/acme/gui.b b/appl/acme/gui.b new file mode 100644 index 00000000..92e61f08 --- /dev/null +++ b/appl/acme/gui.b @@ -0,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"); +} diff --git a/appl/acme/gui.m b/appl/acme/gui.m new file mode 100644 index 00000000..b97e3b7d --- /dev/null +++ b/appl/acme/gui.m @@ -0,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(); +};
\ No newline at end of file diff --git a/appl/acme/look.b b/appl/acme/look.b new file mode 100644 index 00000000..1004b803 --- /dev/null +++ b/appl/acme/look.b @@ -0,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); + 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); + 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); + } +} diff --git a/appl/acme/look.m b/appl/acme/look.m new file mode 100644 index 00000000..5618c4cc --- /dev/null +++ b/appl/acme/look.m @@ -0,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); +}; diff --git a/appl/acme/mkfile b/appl/acme/mkfile new file mode 100644 index 00000000..e8184a51 --- /dev/null +++ b/appl/acme/mkfile @@ -0,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 diff --git a/appl/acme/regx.b b/appl/acme/regx.b new file mode 100644 index 00000000..2bb571c0 --- /dev/null +++ b/appl/acme/regx.b @@ -0,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; + } +} diff --git a/appl/acme/regx.m b/appl/acme/regx.m new file mode 100644 index 00000000..9cdccf6f --- /dev/null +++ b/appl/acme/regx.m @@ -0,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); +};
\ No newline at end of file diff --git a/appl/acme/row.b b/appl/acme/row.b new file mode 100644 index 00000000..9c857c93 --- /dev/null +++ b/appl/acme/row.b @@ -0,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(); + 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); + 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); + } + } + } +}
\ No newline at end of file diff --git a/appl/acme/row.m b/appl/acme/row.m new file mode 100644 index 00000000..2a90f5e4 --- /dev/null +++ b/appl/acme/row.m @@ -0,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); +}; diff --git a/appl/acme/scrl.b b/appl/acme/scrl.b new file mode 100644 index 00000000..e25fc709 --- /dev/null +++ b/appl/acme/scrl.b @@ -0,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(); +} diff --git a/appl/acme/scrl.m b/appl/acme/scrl.m new file mode 100644 index 00000000..d405eabd --- /dev/null +++ b/appl/acme/scrl.m @@ -0,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); +};
\ No newline at end of file diff --git a/appl/acme/styxaux.b b/appl/acme/styxaux.b new file mode 100644 index 00000000..efb7329b --- /dev/null +++ b/appl/acme/styxaux.b @@ -0,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); +} diff --git a/appl/acme/styxaux.m b/appl/acme/styxaux.m new file mode 100644 index 00000000..5cd8f7db --- /dev/null +++ b/appl/acme/styxaux.m @@ -0,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); + +}; diff --git a/appl/acme/text.b b/appl/acme/text.b new file mode 100644 index 00000000..c374bf8d --- /dev/null +++ b/appl/acme/text.b @@ -0,0 +1,1404 @@ +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; +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); + } + }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){ + Kdown or Keyboard->Down => + n = t.frame.maxlines/2; + 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/2; + q0 = t.backnl(t.org, n); + t.setorigin(q0, FALSE); + return; + Kleft or Keyboard->Left => + t.commit(TRUE); + if(t.q0 != t.q1) + t.show(t.q0, t.q0); + else if(t.q0 != 0) + t.show(t.q0-1, t.q0-1); + return; + Kright or Keyboard->Right => + t.commit(TRUE); + if(t.q0 != t.q1) + t.show(t.q1, t.q1); + else if(t.q1 != t.file.buf.nc) + t.show(t.q1+1, t.q1+1); + 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); + return; + } + } + t.show(t.q0, t.q0); + 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) +{ + qe : int; + nl : int; + q : int; + + if(t.what != Body) + return; + if(t.w!=nil && t.frame.maxlines==0) + t.col.grow(t.w, 1, 0); + 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(); +} diff --git a/appl/acme/text.m b/appl/acme/text.m new file mode 100644 index 00000000..1c258be3 --- /dev/null +++ b/appl/acme/text.m @@ -0,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); + 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); +};
\ No newline at end of file diff --git a/appl/acme/time.b b/appl/acme/time.b new file mode 100644 index 00000000..b5edda74 --- /dev/null +++ b/appl/acme/time.b @@ -0,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); +} diff --git a/appl/acme/time.m b/appl/acme/time.m new file mode 100644 index 00000000..a9a01b13 --- /dev/null +++ b/appl/acme/time.m @@ -0,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); +};
\ No newline at end of file diff --git a/appl/acme/util.b b/appl/acme/util.b new file mode 100644 index 00000000..b2930c35 --- /dev/null +++ b/appl/acme/util.b @@ -0,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); + 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); +} diff --git a/appl/acme/util.m b/appl/acme/util.m new file mode 100644 index 00000000..6da7afed --- /dev/null +++ b/appl/acme/util.m @@ -0,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); +};
\ No newline at end of file diff --git a/appl/acme/wind.b b/appl/acme/wind.b new file mode 100644 index 00000000..2b97fafa --- /dev/null +++ b/appl/acme/wind.b @@ -0,0 +1,554 @@ +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); + 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) : string +{ + return sprint("%11d %11d %11d %11d %11d ", w.id, w.tag.file.buf.nc, + w.body.file.buf.nc, w.isdir, w.dirty); +} + +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; + } +} diff --git a/appl/acme/wind.m b/appl/acme/wind.m new file mode 100644 index 00000000..0a58b40a --- /dev/null +++ b/appl/acme/wind.m @@ -0,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) : string; + }; +}; diff --git a/appl/acme/xfid.b b/appl/acme/xfid.b new file mode 100644 index 00000000..0533a70e --- /dev/null +++ b/appl/acme/xfid.b @@ -0,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)); + 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(); + 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); + 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); + 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); + 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(); + 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; +} diff --git a/appl/acme/xfid.m b/appl/acme/xfid.m new file mode 100644 index 00000000..c0d10fa7 --- /dev/null +++ b/appl/acme/xfid.m @@ -0,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; + }; +}; |
