implement Samstub; include "sys.m"; sys: Sys; fprint, FD, fildes: import sys; stderr: ref FD; include "draw.m"; draw: Draw; include "samterm.m"; samterm: Samterm; Text, Menu, Context, Flayer, Section: import samterm; include "samtk.m"; samtk: Samtk; panic, whichtext, whichmenu: import samtk; include "samstub.m"; sendsam: chan of ref Sammsg; recvsam: chan of ref Sammsg; snarflen: int; ctxt: ref Context; requested: list of (int, int); tname := array [] of { "Tversion", "Tstartcmdfile", "Tcheck", "Trequest", "Torigin", "Tstartfile", "Tworkfile", "Ttype", "Tcut", "Tpaste", "Tsnarf", "Tstartnewfile", "Twrite", "Tclose", "Tlook", "Tsearch", "Tsend", "Tdclick", "Tstartsnarf", "Tsetsnarf", "Tack", "Texit", }; hname := array [] of { "Hversion", "Hbindname", "Hcurrent", "Hnewname", "Hmovname", "Hgrow", "Hcheck0", "Hcheck", "Hunlock", "Hdata", "Horigin", "Hunlockfile", "Hsetdot", "Hgrowdata", "Hmoveto", "Hclean", "Hdirty", "Hcut", "Hsetpat", "Hdelname", "Hclose", "Hsetsnarf", "Hsnarflen", "Hack", "Hexit", }; init(c: ref Context) { ctxt = c; sys = load Sys Sys->PATH; draw = load Draw Draw->PATH; stderr = fildes(2); samterm = load Samterm Samterm->PATH; samtk = load Samtk Samtk->PATH; samtk->init(ctxt); requested = nil; } start(): (ref Samio, chan of ref Sammsg) { sys = load Sys Sys->PATH; sys->bind("#C", "/", sys->MAFTER); # Allocate a cmd device ctl := sys->open("/cmd/clone", sys->ORDWR); if(ctl == nil) { fprint(stderr, "can't open /cmd/clone\n"); return (nil, nil); } # Find out which one buf := array[32] of byte; n := sys->read(ctl, buf, len buf); if(n <= 0) { fprint(stderr, "can't read cmd device\n"); return (nil, nil); } dir := "/cmd/"+string buf[0:n]; # Start the Command n = sys->fprint(ctl, "exec "+ SAM); if(n <= 0) { fprint(stderr, "can't exec %s\n", SAM); return (nil, nil); } data := sys->open(dir+"/data", sys->ORDWR); if(data == nil) { fprint(stderr, "can't open cmd data file\n"); return (nil, nil); } sendsam = chan of ref Sammsg; recvsam = chan of ref Sammsg; samio := ref Samio(ctl, data, array[1] of byte, 0, 0); spawn sender(samio, sendsam); spawn receiver(samio, recvsam); return (samio, recvsam); } sender(samio: ref Samio, c: chan of ref Sammsg) { fprint(ctxt.logfd, "sender started\n"); for (;;) { h := <- c; if (h == nil) return; buf := array[3 + len h.mdata] of byte; buf[0] = byte h.mtype; buf[1] = byte h.mcount; buf[2] = byte (h.mcount >> 8); buf[3:] = h.mdata; sys->write(samio.data, buf, len buf); } } receiver(samio: ref Samio, msgchan: chan of ref Sammsg) { c: int; fprint(ctxt.logfd, "receiver started\n"); state := 0; i := 0; errs := 0; h: ref Sammsg; for (;;) { if (samio.count == 0) { n := sys->read(samio.data, samio.buffer, len samio.buffer); if (n <= 0) { fprint(stderr, "Read error on sam's pipe\n"); return; } samio.index = 0; samio.count = n; } samio.count--; c = int samio.buffer[samio.index++]; case state { 0 => h = ref Sammsg(c, 0, nil); state++; continue; 1 => h.mcount = c; state++; continue; 2 => h.mcount = h.mcount|(c<<8); if (h.mcount > DATASIZE || h.mcount < 0) panic("receiver: count>DATASIZE"); if(h.mcount != 0) { h.mdata = array[h.mcount] of byte; i = 0; state++; continue; } 3 => h.mdata[i++] = byte c; if(i < h.mcount){ continue; } } msgchan <- = h; h = nil; state = 0; } } inmesg(h: ref Sammsg): int { case h.mtype { Hversion => m := h.inshort(0); fprint(ctxt.logfd, "Hversion: %d\n", m); Hbindname => m := h.inshort(0); vl := h.invlong(2); fprint(ctxt.logfd, "Hbindname: %ux, %bux\n", m, vl); bindname(m, int vl); Hcurrent => m := h.inshort(0); fprint(ctxt.logfd, "Hcurrent: %d\n", m); hcurrent(m); Hmovname => m := h.inshort(0); fprint(ctxt.logfd, "Hmovname: %d, %s\n", m, string h.mdata[2:]); movename(m, string h.mdata[2:]); Hgrow => m := h.inshort(0); l1 := h.inlong(2); l2 := h.inlong(6); fprint(ctxt.logfd, "Hgrow: %d, %d, %d\n", m, l1, l2); hgrow(m, l1, l2); Hnewname => m := h.inshort(0); fprint(ctxt.logfd, "Hnewname: %d\n", m); newname(m); Hcheck0 => m := h.inshort(0); fprint(ctxt.logfd, "Hcheck0: %d\n", m); i := whichmenu(m); if (i >= 0) { t := ctxt.menus[i].text; if (t != nil) t.lock++; outTs(Tcheck, m); } Hcheck => m := h.inshort(0); fprint(ctxt.logfd, "Hcheck: %d\n", m); i := whichmenu(m); if (i >= 0) { t := ctxt.menus[i].text; if (t != nil && t.lock) t.lock--; hcheck(t); } Hunlock => fprint(ctxt.logfd, "Hunlock\n"); clrlock(); Hdata => m := h.inshort(0); l := h.inlong(2); fprint(ctxt.logfd, "Hdata: %d, %d, %s\n", m, l, contract(string h.mdata[6:])); hdata(m, l, string h.mdata[6:]); Horigin => m := h.inshort(0); l := h.inlong(2); fprint(ctxt.logfd, "Horigin: %d, %d\n", m, l); horigin(m, l); Hunlockfile => m := h.inshort(0); fprint(ctxt.logfd, "Hunlockfile: %d\n", m); clrlock(); Hsetdot => m := h.inshort(0); l1 := h.inlong(2); l2 := h.inlong(6); fprint(ctxt.logfd, "Hsetdot: %d, %d, %d\n", m, l1, l2); hsetdot(m, l1, l2); Hgrowdata => m := h.inshort(0); l1 := h.inlong(2); l2 := h.inlong(6); fprint(ctxt.logfd, "Hgrowdata: %d, %d, %d, %s\n", m, l1, l2, contract(string h.mdata[10:])); hgrowdata(m, l1, l2, string h.mdata[10:]); Hmoveto => m := h.inshort(0); l := h.inlong(2); fprint(ctxt.logfd, "Hmoveto: %d, %d\n", m, l); hmoveto(m, l); Hclean => m := h.inshort(0); fprint(ctxt.logfd, "Hclean: %d\n", m); hclean(m); Hdirty => m := h.inshort(0); fprint(ctxt.logfd, "Hdirty: %d\n", m); hdirty(m); Hdelname => m := h.inshort(0); fprint(ctxt.logfd, "Hdelname: %d\n", m); hdelname(m); Hcut => m := h.inshort(0); l1 := h.inlong(2); l2 := h.inlong(6); fprint(ctxt.logfd, "Hcut: %d, %d, %d\n", m, l1, l2); hcut(m, l1, l2); Hclose => m := h.inshort(0); fprint(ctxt.logfd, "Hclose: %d\n", m); hclose(m); Hsetpat => fprint(ctxt.logfd, "Hsetpat: %s\n", string h.mdata); samtk->hsetpat(string h.mdata); Hsetsnarf => m := h.inshort(0); fprint(ctxt.logfd, "Hsetsnarf: %d\n", m); Hsnarflen => snarflen = h.inlong(0); fprint(ctxt.logfd, "Hsnarflen: %d\n", snarflen); Hack => fprint(ctxt.logfd, "Hack\n"); outT0(Tack); Hexit => fprint(ctxt.logfd, "Hexit\n"); return 1; -1 => panic("rcv error"); * => fprint(ctxt.logfd, "type %d\n", h.mtype); panic("rcv unknown"); } return 0; } Sammsg.inshort(h: self ref Sammsg, n: int): int { return ((int h.mdata[n+1])<<8) | ((int h.mdata[n])); } Sammsg.inlong(h: self ref Sammsg, n: int): int { return ((int h.mdata[n+3])<<24) | ((int h.mdata[n+2])<<16) | ((int h.mdata[n+1])<< 8) | ((int h.mdata[n])); } Sammsg.invlong(h: self ref Sammsg, n: int): big { return ((big h.mdata[n+7])<<56) | ((big h.mdata[n+6])<<48) | ((big h.mdata[n+5])<<40) | ((big h.mdata[n+4])<<32) | ((big h.mdata[n+3])<<24) | ((big h.mdata[n+2])<<16) | ((big h.mdata[n+1])<< 8) | ((big h.mdata[n])); } Sammsg.outcopy(h: self ref Sammsg, pos: int, data: array of byte) { h.mdata[pos:] = data; } Sammsg.outshort(h: self ref Sammsg, pos: int, s: int) { h.mdata[pos++] = byte s; h.mdata[pos] = byte (s >> 8); } Sammsg.outlong(h: self ref Sammsg, pos: int, s: int) { h.mdata[pos++] = byte s; h.mdata[pos++] = byte (s >> 8); h.mdata[pos++] = byte (s >> 16); h.mdata[pos] = byte (s >> 24); } Sammsg.outvlong(h: self ref Sammsg, pos: int, s: big) { h.mdata[pos++] = byte s; h.mdata[pos++] = byte (s >> 8); h.mdata[pos++] = byte (s >> 16); h.mdata[pos++] = byte (s >> 24); h.mdata[pos++] = byte (s >> 32); h.mdata[pos++] = byte (s >> 40); h.mdata[pos++] = byte (s >> 48); h.mdata[pos] = byte (s >> 56); } outT0(t: int) { fprint(ctxt.logfd, "\t\t\t\t\t%s\n", tname[t]); h := ref Sammsg(t, 0, nil); sendsam <- = h; } outTs(t, s: int) { fprint(ctxt.logfd, "\t\t\t\t\t%s %ux\n", tname[t], s); a := array[2] of byte; h := ref Sammsg(t, 2, a); h.outshort(0, s); sendsam <- = h; } outTv(t: int, i: big) { fprint(ctxt.logfd, "\t\t\t\t\t%s %bux\n", tname[t], i); a := array[8] of byte; h := ref Sammsg(t, 8, a); h.outvlong(0, i); sendsam <- = h; } outTsll(t, m, l1, l2: int) { fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2); a := array[10] of byte; h := ref Sammsg(t, 10, a); h.outshort(0, m); h.outlong(2, l1); h.outlong(6, l2); sendsam <- = h; } outTsl(t, m, l: int) { fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d\n", tname[t], m, l); a := array[6] of byte; h := ref Sammsg(t, 6, a); h.outshort(0, m); h.outlong(2, l); sendsam <- = h; } outTsls(t, m, l1, l2: int) { fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2); a := array[8] of byte; h := ref Sammsg(t, 8, a); h.outshort(0, m); h.outlong(2, l1); h.outshort(6, l2); sendsam <- = h; } outTslS(t, s1, l1: int, s: string) { fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %s\n", tname[t], s1, l1, s); a := array[6 + len array of byte s] of byte; h := ref Sammsg(t, len a, a); h.outshort(0, s1); h.outlong(2, l1); h.outcopy(6, array of byte s); sendsam <- = h; } newname(tag: int) { menuins(0, "dummy", nil, tag); } bindname(tag, l: int) { if ((m := whichmenu(tag)) < 0) panic("bindname: whichmenu"); if ((l = whichtext(l)) < 0) panic("bindname: whichtext"); if (ctxt.menus[m].text != nil) return; # Already bound t := ctxt.texts[l]; t.tag = tag; for (fls := t.flayers; fls != nil; fls = tl fls) (hd fls).tag = tag; ctxt.menus[m].text = t; } menuins(m: int, s: string, t: ref Text, tag: int) { newmenus := array [len ctxt.menus+1] of ref Menu; menu := ref Menu( tag, # tag s, # name t # text ); if (m > 0) newmenus[0:] = ctxt.menus[0:m]; newmenus[m] = menu; if (m < len ctxt.menus) newmenus[m+1:] = ctxt.menus[m:]; ctxt.menus = newmenus; samtk->menuins(m, s); } menudel(m: int) { if (len ctxt.menus == 0 || m >= len ctxt.menus || ctxt.menus[m].text != nil) panic("menudel"); newmenus := array [len ctxt.menus - 1] of ref Menu; newmenus[0:] = ctxt.menus[0:m]; newmenus[m:] = ctxt.menus[m+1:]; ctxt.menus = newmenus; samtk->menudel(m); } outcmd() { if(ctxt.work != nil) { fl := ctxt.work; outTsll(Tworkfile, fl.tag, fl.dot.first, fl.dot.last); } } hclose(m: int) { i: int; # close LAST window of a file if((m = whichmenu(m)) < 0) panic("hclose: whichmenu"); t := ctxt.menus[m].text; if (tl t.flayers != nil) panic("hclose: flayers"); fl := hd t.flayers; fl.t = nil; for (i = 0; i< len ctxt.flayers; i++) if (ctxt.flayers[i] == fl) break; if (i == len ctxt.flayers) panic("hclose: ctxt.flayers"); samtk->chandel(i); t.flayers = nil; for (i = 0; i< len ctxt.texts; i++) if (ctxt.texts[i] == ctxt.menus[m].text) break; if (i == len ctxt.texts) panic("hclose: ctxt.texts"); ctxt.texts[i:] = ctxt.texts[i+1:]; ctxt.texts = ctxt.texts[:len ctxt.texts - 1]; ctxt.menus[m].text = nil; ctxt.which = nil; samtk->focus(hd ctxt.cmd.flayers); } close(win, tag: int) { nfls: list of ref Flayer; if ((m := whichtext(tag)) < 0) panic("close: text"); t := ctxt.texts[m]; if ((m = whichmenu(tag)) < 0) panic("close: menu"); if (len t.flayers == 1) { outTs(Tclose, tag); setlock(); return; } fl := ctxt.flayers[win]; nfls = nil; for (fls := t.flayers; fls != nil; fls = tl fls) if (hd fls != fl) nfls = hd fls :: nfls; t.flayers = nfls; samtk->chandel(win); fl.t = nil; samtk->settitle(t, ctxt.menus[m].name); ctxt.which = nil; } hdelname(m: int) { # close LAST window of a file if((m = whichmenu(m)) < 0) panic("hdelname: whichmenu"); if (ctxt.menus[m].text != nil) panic("hdelname: text"); ctxt.menus[m:] = ctxt.menus[m+1:]; ctxt.menus = ctxt.menus[:len ctxt.menus - 1]; samtk->menudel(m); ctxt.which = nil; } hdirty(m: int) { if((m = whichmenu(m)) < 0) panic("hdirty: whichmenu"); if (ctxt.menus[m].text == nil) panic("hdirty: text"); ctxt.menus[m].text.state |= Samterm->Dirty; samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name); } hclean(m: int) { if((m = whichmenu(m)) < 0) panic("hclean: whichmenu"); if (ctxt.menus[m].text == nil) panic("hclean: text"); ctxt.menus[m].text.state &= ~Samterm->Dirty; samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name); } movename(tag: int, s: string) { i := whichmenu(tag); if (i < 0) panic("movename: whichmenu"); t := ctxt.menus[i].text; ctxt.menus[i].text = nil; # suppress panic in menudel menudel(i); if(t == ctxt.cmd) i = 0; else { if (len ctxt.menus > 0 && ctxt.menus[0].text == ctxt.cmd) i = 1; else i = 0; for(; i < len ctxt.menus; i++) { if (s < ctxt.menus[i].name) break; } } if (t != nil) samtk->settitle(t, s); menuins(i, s, t, tag); } hcheck(t: ref Text) { if (t == nil) { fprint(ctxt.logfd, "hcheck: no text in menu entry\n"); return; } for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; scrollto(fl, fl.scope.first); } } setlock() { ctxt.lock++; samtk->allflayers("cursor -bitmap cursor.wait"); } clrlock() { if (ctxt.lock > 0) ctxt.lock--; else fprint(ctxt.logfd, "lock: wasn't locked\n"); if (ctxt.lock == 0) samtk->allflayers("cursor -default; update"); } hcut(m, where, howmuch: int) { if((m = whichmenu(m)) < 0) panic("hcut: whichmenu"); t := ctxt.menus[m].text; if (t == nil) panic("hcut -- no text"); # sctdump(t.sects, "Hcut, before"); t.nrunes -= howmuch; t.sects = sctdelete(t.sects, where, howmuch); # sctdump(t.sects, "Hcut, after"); for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; if (where < fl.scope.first) { if (where + howmuch <= fl.scope.first) fl.scope.first -= howmuch; else fl.scope.first = where; } if (where < fl.scope.last) { if (where + howmuch <= fl.scope.last) fl.scope.last -= howmuch; else fl.scope.last = where; } } } hgrow(tag, l1, l2: int) { if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu"); t := ctxt.menus[m].text; grow(t, l1, l2); } hdata(m, l: int, s: string) { nr: list of (int, int); if((m = whichmenu(m)) < 0) panic("hdata: whichmenu"); t := ctxt.menus[m].text; if (t == nil) panic("hdata -- no text"); if (s != "") { t.sects = sctput(t.sects, l, s); updatefls(t, l, s); } for (nr = nil; requested != nil; requested = tl requested) { (r1, r2) := hd requested; if (r1 != m || r2 != l) nr = (r1, r2) :: nr; } requested = nr; clrlock(); } hgrowdata(tag, l1, l2: int, s: string) { if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu"); t := ctxt.menus[m].text; if (t == nil) panic("hdata -- no text"); grow(t, l1, l2); t.sects = sctput(t.sects, l1, s); updatefls(t, l1, s); } hsetdot(m, l1, l2: int) { if((m = whichmenu(m)) < 0) panic("hsetdot: whichmenu"); t := ctxt.menus[m].text; if (t == nil || t.flayers == nil) panic("hsetdot -- no text"); samtk->setdot(hd t.flayers, l1, l2); } hcurrent(tag: int) { if ((i := whichmenu(tag)) < 0) panic("hcurrent: whichmenu"); if (ctxt.menus[i].text == nil) { n := startfile(tag); ctxt.menus[i].text = ctxt.texts[n]; if (ctxt.menus[i].name != nil) samtk->settitle(ctxt.texts[n], ctxt.menus[i].name); } ctxt.work = hd ctxt.menus[i].text.flayers; } hmoveto(m, l: int) { if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu"); t := ctxt.menus[m].text; fl := hd t.flayers; if (fl.scope.first <= l && (l < fl.scope.last || fl.scope.last == fl.scope.first)) return; (n, p) := sctrevcnt(t.sects, l, fl.lines/2); # fprint(ctxt.logfd, "hmoveto: (n, p) = (%d, %d)\n", n, p); if (n < 0) { outTsll(Torigin, t.tag, l, fl.lines/2); setlock(); return; } scrollto(fl, p); } startcmdfile() { t := ctxt.tag++; n := newtext(t, 1); ctxt.cmd = ctxt.texts[n]; outTv(Tstartcmdfile, big t); } startnewfile() { t := ctxt.tag++; n := newtext(t, 0); outTv(Tstartnewfile, big t); } startfile(tag: int): int { n := newtext(tag, 0); outTv(Tstartfile, big tag); setlock(); return n; } horigin(m, l: int) { if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu"); t := ctxt.menus[m].text; fl := hd t.flayers; scrollto(fl, l); clrlock(); } scrollto(fl: ref Flayer, where: int) { s: string; n: int; tag := fl.tag; if ((i := whichtext(tag)) < 0) panic("scrollto: whichtext"); t := ctxt.texts[i]; samtk->flclear(fl); (n, s) = sctgetlines(t.sects, where, fl.lines); fl.scope.first = where; fl.scope.last = where + len s; if (s != "") samtk->flinsert(fl, where, s); if (n == 0) { samtk->setscrollbar(t, fl); } else { (h, l) := scthole(t, fl.scope.last); fl.scope.last = h; if (l > 0) outrequest(tag, h, l); else if (fl.scope.first > t.nrunes) { fl.scope.first = t.nrunes; fl.scope.last = t.nrunes; samtk->setscrollbar(t, fl); } } } scthole(t: ref Text, f: int): (int, int) { p := 0; h := -1; l := 0; for (scts := t.sects; scts != nil; scts = tl scts) { sct := hd scts; nr := sct.nrunes; nt := len sct.text; if (h >= 0) { if (sct.text == "") { l += nr; if (l >= 512) return (h,512); } else return (h,l); } if (h < 0 && f < nr) { if (nt < nr) { if (f < nt) { h = p + nt; l = nr - nt; } else { h = p + f; l = nr - f; } if (l >= 512) return (h,512); } } p += sct.nrunes; f -= sct.nrunes; } if (h == -1) return (p, 0); return (h, l); } # return (x, p): x = -1: p -> hole; x = 0: p -> line n; x > 0: p -> eof sctlinecount(t: ref Text, pos, n: int): (int, int) { i: int; p := 0; for (scts := t.sects; scts != nil; scts = tl scts) { sct := hd scts; nr := sct.nrunes; nt := len sct.text; if (pos < nr) { if (pos > 0) i = pos; else i = 0; while (i < nt) { if (sct.text[i++] == '\n') n--; if (n == 0) return (0, p + i); } if (nt < nr) return (-1, p + nt); } p += sct.nrunes; pos -= sct.nrunes; } return (n, p); } sctrevcnt(scts: list of ref Section, pos, n: int): (int, int) { if (scts == nil) return (n, 0); sct := hd scts; scts = tl scts; nt := len sct.text; nr := sct.nrunes; if (pos >= nr) { (n, pos) = sctrevcnt(scts, pos - nr, n); pos += nr; } if (n > 0) { if (nt < nr && pos > nt) return(-1, pos); for (i := pos-1; i >= 0; i--) { if (sct.text[i] == '\n') n--; if (n == 0) break; } return (n, i + 1); } return (n, pos); } insertfls(t: ref Text, l: int, s: string) { for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; if (l < fl.scope.first || l > fl.scope.last) continue; samtk->flinsert(fl, l, s); samtk->setscrollbar(t, fl); fl.scope.last += len s; } } updatefls(t: ref Text, l: int, s: string) { for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; if (l < fl.scope.first || l > fl.scope.last) continue; samtk->flinsert(fl, l, s); (x, p) := sctlinecount(t, fl.scope.first, fl.lines); fl.scope.last = p; if (x >= 0) { if (p > l + len s) { samtk->flinsert(fl, l + len s, sctget(t.sects, l + len s, p)); } if (x == 0) samtk->fldelexcess(fl); } else { (h1, h2) := scthole(t, l); fl.scope.last = h1; if (h2 > 0) { outrequest(t.tag, h1, h2); continue; } else { panic("Can't happen ??"); } } samtk->setscrollbar(t, fl); } } outrequest(tag, h1, h2: int) { for (l := requested; l != nil; l = tl l) { (r1, r2) := hd l; if (r1 == tag && r2 == h1) return; } outTsls(Trequest, tag, h1, h2); requested = (tag, h1) :: requested; setlock(); } deletefls(t: ref Text, pos, nbytes: int) { for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; if (pos >= fl.scope.last) continue; if (pos + nbytes <= fl.scope.first || pos >= fl.scope.last) { fl.scope.first -= nbytes; fl.scope.last -= nbytes; continue; } samtk->fldelete(fl, pos, pos + nbytes); (x, p) := sctlinecount(t, fl.scope.first, fl.lines); if (x >= 0 && p > fl.scope.last) { samtk->flinsert(fl, fl.scope.last, sctget(t.sects, fl.scope.last, p)); fl.scope.last = p; } else { fl.scope.last = p; (h1, h2) := scthole(t, fl.scope.last); if (h2 > 0) outrequest(t.tag, h1, h2); } samtk->setscrollbar(t, fl); } } contract(s: string): string { if (len s < 32) cs := s; else cs = s[0:16] + " ... " + s[len s - 16:]; for (i := 0; i < len cs; i++) if (cs[i] == '\n') cs[i] = '\u008a'; return cs; } cleanout() { if ((fl := ctxt.which) == nil) return; if ((i := whichtext(fl.tag)) < 0) panic("cleanout: whichtext"); t := ctxt.texts[i]; if (fl.typepoint >= 0 && fl.dot.first > fl.typepoint) { s := sctget(t.sects, fl.typepoint, fl.dot.first); outTslS(Samstub->Ttype, fl.tag, fl.typepoint, s); t.state &= ~Samterm->LDirty; } fl.typepoint = -1; } newtext(tag, tp: int): int { n := len ctxt.texts; t := ref Text( tag, # tag 0, # lock samtk->newflayer(tag, tp) :: nil, # flayers 0, # nrunes nil, # sects 0 # state ); texts := array [n + 1] of ref Text; texts[0:] = ctxt.texts; texts[n] = t; ctxt.texts = texts; samtk->newcur(t, hd t.flayers); return n; } keypress(key: string) { # Find text and flayer fl := ctxt.which; tag := fl.tag; if ((i := whichtext(tag)) < 0) panic("keypress: whichtext"); t := ctxt.texts[i]; if (fl.dot.last != fl.dot.first) { cut(t, fl); } case (key) { "\b" => if (t.nrunes == 0 || fl.dot.first == 0) return; fl.dot.first--; if (fl.typepoint >= 0 && fl.dot.first >= fl.typepoint) { t.nrunes -= fl.dot.last - fl.dot.first; t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first); deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first); if (fl.dot.first == fl.typepoint) { fl.typepoint = -1; t.state &= ~Samterm->LDirty; if ((i = whichmenu(tag)) < 0) panic("keypress: whichmenu"); samtk->settitle(t, ctxt.menus[i].name); } } else { cut(t, fl); } * => if (fl.typepoint < 0) { fl.typepoint = fl.dot.first; t.state |= Samterm->LDirty; if ((i = whichmenu(tag)) < 0) panic("keypress: whichmenu"); samtk->settitle(t, ctxt.menus[i].name); } if (fl.dot.first > t.nrunes) panic("keypress -- cursor > file len"); t.sects = sctmakeroom(t.sects, fl.dot.first, len key); t.nrunes += len key; t.sects = sctput(t.sects, fl.dot.first, key); insertfls(t, fl.dot.first, key); f := fl.dot.first + len key; samtk->setdot(fl, f, f); if (key == "\n") { if (f >= fl.scope.last) { (n, p) := sctrevcnt(t.sects, f-1, 2*fl.lines/3); if (n < 0) { outTsll(Torigin, t.tag, f-1, 2*fl.lines/3); setlock(); } else { scrollto(fl, p); } } if (t == ctxt.cmd && fl.dot.last == t.nrunes) { outcmd(); setlock(); } cleanout(); } } return; } cut(t: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("cut: typepoint"); outTsll(Tcut, fl.tag, fl.dot.first, fl.dot.last); t.nrunes -= fl.dot.last - fl.dot.first; t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first); deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first); } paste(t: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("paste: typepoint"); if (snarflen == 0) return; if (fl.dot.first < fl.dot.last) cut(t, fl); outTsl(Tpaste, fl.tag, fl.dot.first); } snarf(nil: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("snarf: typepoint"); if (fl.dot.first == fl.dot.last) return; snarflen = fl.dot.last - fl.dot.first; outTsll(Tsnarf, fl.tag, fl.dot.first, fl.dot.last); } look(nil: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("look: typepoint"); outTsll(Tlook, fl.tag, fl.dot.first, fl.dot.last); setlock(); } send(nil: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("send: typepoint"); outcmd(); outTsll(Tsend, fl.tag, fl.dot.first, fl.dot.last); setlock(); } search(nil: ref Text, fl: ref Flayer) { if (fl.typepoint >= 0) panic("search: typepoint"); outcmd(); outT0(Tsearch); setlock(); } zerox(t: ref Text) { fl := samtk->newflayer(t.tag, ctxt.cmd == t); t.flayers = fl :: t.flayers; m := whichmenu(t.tag); samtk->settitle(t, ctxt.menus[m].name); samtk->newcur(t, fl); scrollto(fl, 0); } sctget(scts: list of ref Section, p1, p2: int): string { while (scts != nil) { sct := hd scts; scts = tl scts; ln := len sct.text; if (p1 < sct.nrunes) { if (ln < sct.nrunes && p2 > ln) { sctdump(scts, "panic"); panic("sctget - asking for a hole"); } if (p2 > sct.nrunes) { s := sct.text[p1:]; return s + sctget(scts, 0, p2 - ln); } return sct.text[p1:p2]; } p1 -= sct.nrunes; p2 -= sct.nrunes; } return ""; } sctgetlines(scts: list of ref Section, p, n: int): (int, string) { s := ""; while (scts != nil) { sct := hd scts; scts = tl scts; ln := len sct.text; if (p < sct.nrunes) { if (p > ln) return (n, s); if (p > 0) b := p; else b = 0; for (i := b; i < ln && n > 0; ) { if (sct.text[i++] == '\n') n--; } if ( i > b) s = s + sct.text[b:i]; if (n == 0 || ln < sct.nrunes) return (n, s); } p -= sct.nrunes; } return (n, s); } sctput(scts: list of ref Section, pos: int, s: string): list of ref Section { # There should be a hole to receive text if (scts == nil && s != "") panic("sctput: scts is nil\n"); sct := hd scts; l := len sct.text; if (sct.nrunes <= pos) { return sct :: sctput(tl scts, pos-sct.nrunes, s); } if (pos < l) { sctdump(scts, "panic"); panic("sctput: overwriting"); } if (pos == l) { if (sct.nrunes < l + len s) { sct.text += s[:sct.nrunes-l]; return sct :: sctput(tl scts, 0, s[sct.nrunes-l:]); } sct.text += s; return sct :: tl scts; } nrunes := sct.nrunes; sct.nrunes = pos; if (nrunes < pos + len s) return sct :: ref Section(nrunes-pos, s[:nrunes-pos]) :: sctput(tl scts, 0, s[nrunes-pos:]); return sct :: ref Section(nrunes-pos, s) :: tl scts; } sctmakeroom(scts: list of ref Section, pos: int, l: int): list of ref Section { if (scts == nil) { if (pos) panic("sctmakeroom: beyond end of sections"); return ref Section(l, nil) :: nil; } sct := hd scts; if (sct.nrunes < pos) return sct :: sctmakeroom(tl scts, pos-sct.nrunes, l); if (len sct.text <= pos) { # just add to the hole at end of section sct.nrunes += l; return sct :: tl scts; } if (pos == 0) { # text is non-nil! bsct := ref Section(l, nil); return bsct :: scts; } bsct := ref Section(pos + l, sct.text[0:pos]); esct := ref Section(sct.nrunes-pos, sct.text[pos:]); return bsct :: esct :: tl scts; } sctdelete(scts: list of ref Section, start, nbytes: int): list of ref Section { if (nbytes == 0) return scts; if (scts == nil) panic("sctdelete: at eof"); sct := hd scts; scts = tl scts; nrunes := sct.nrunes; if (start + nbytes < len sct.text) { sct.text = sct.text[0:start] + sct.text[start+nbytes:]; sct.nrunes -= nbytes; return sct :: scts; } if (start < nrunes) { if (start > 0) { if (start < len sct.text) sct.text = sct.text[0:start]; if (start + nbytes <= nrunes) { sct.nrunes -= nbytes; return sct :: scts; } sct.nrunes = start; return sct :: sctdelete(scts, 0, nbytes-nrunes+start); } if (nbytes < nrunes) { sct.text = ""; sct.nrunes -= nbytes; return sct :: scts; } return sctdelete(scts, 0, nbytes - nrunes); } return sct :: sctdelete(scts, start - nrunes, nbytes); } grow(t: ref Text, at, l: int) { # sctdump(t.sects, "grow, before"); t.sects = sctmakeroom(t.sects, at, l); t.nrunes += l; # sctdump(t.sects, "grow, after"); for (fls := t.flayers; fls != nil; fls = tl fls) { fl := hd fls; if (at < fl.scope.first) fl.scope.first += l; if (at < fl.scope.last) fl.scope.last += l; } } findhole(t: ref Text): (int, int) { for (fls := t.flayers; fls != nil; fls = tl fls) { (h, l) := scthole(t, (hd fls).scope.first); if (l > 0) return (h, l); } return (0, 0); } sctdump(scts: list of ref Section, s: string) { fprint(ctxt.logfd, "Sctdump: %s\n", s); p := 0; while (scts != nil) { sct := hd scts; scts = tl scts; fprint(ctxt.logfd, "\tsct@%4d len=%4d len txt=%4d: %s\n", p, sct.nrunes, len sct.text, contract(sct.text)); p += sct.nrunes; } fprint(ctxt.logfd, "\tend@%4d\n", p); }