summaryrefslogtreecommitdiff
path: root/appl/acme/text.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/acme/text.b')
-rw-r--r--appl/acme/text.b1404
1 files changed, 1404 insertions, 0 deletions
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();
+}