URI:
       ttext.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       ttext.c (33763B)
       ---
            1 #include <u.h>
            2 #include <libc.h>
            3 #include <draw.h>
            4 #include <thread.h>
            5 #include <cursor.h>
            6 #include <mouse.h>
            7 #include <keyboard.h>
            8 #include <frame.h>
            9 #include <fcall.h>
           10 #include <plumb.h>
           11 #include <libsec.h>
           12 #include <complete.h>
           13 #include "dat.h"
           14 #include "fns.h"
           15 
           16 Image        *tagcols[NCOL];
           17 Image        *textcols[NCOL];
           18 static Rune Ldot[] = { '.', 0 };
           19 
           20 enum{
           21         TABDIR = 3        /* width of tabs in directory windows */
           22 };
           23 
           24 void
           25 textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
           26 {
           27         t->file = f;
           28         t->all = r;
           29         t->scrollr = r;
           30         t->scrollr.max.x = r.min.x+Scrollwid;
           31         t->lastsr = nullrect;
           32         r.min.x += Scrollwid+Scrollgap;
           33         t->eq0 = ~0;
           34         t->ncache = 0;
           35         t->reffont = rf;
           36         t->tabstop = maxtab;
           37         memmove(t->fr.cols, cols, sizeof t->fr.cols);
           38         textredraw(t, r, rf->f, screen, -1);
           39 }
           40 
           41 void
           42 textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
           43 {
           44         int maxt;
           45         Rectangle rr;
           46 
           47         frinit(&t->fr, r, f, b, t->fr.cols);
           48         rr = t->fr.r;
           49         rr.min.x -= Scrollwid+Scrollgap;        /* back fill to scroll bar */
           50         if(!t->fr.noredraw)
           51                 draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
           52         /* use no wider than 3-space tabs in a directory */
           53         maxt = maxtab;
           54         if(t->what == Body){
           55                 if(t->w->isdir)
           56                         maxt = min(TABDIR, maxtab);
           57                 else
           58                         maxt = t->tabstop;
           59         }
           60         t->fr.maxtab = maxt*stringwidth(f, "0");
           61         if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
           62                 if(t->fr.maxlines > 0){
           63                         textreset(t);
           64                         textcolumnate(t, t->w->dlp,  t->w->ndl);
           65                         textshow(t, 0, 0, 1);
           66                 }
           67         }else{
           68                 textfill(t);
           69                 textsetselect(t, t->q0, t->q1);
           70         }
           71 }
           72 
           73 int
           74 textresize(Text *t, Rectangle r, int keepextra)
           75 {
           76         int odx;
           77 
           78         if(Dy(r) <= 0)
           79                 r.max.y = r.min.y;
           80         else if(!keepextra)
           81                 r.max.y -= Dy(r)%t->fr.font->height;
           82         odx = Dx(t->all);
           83         t->all = r;
           84         t->scrollr = r;
           85         t->scrollr.max.x = r.min.x+Scrollwid;
           86         t->lastsr = nullrect;
           87         r.min.x += Scrollwid+Scrollgap;
           88         frclear(&t->fr, 0);
           89         textredraw(t, r, t->fr.font, t->fr.b, odx);
           90         if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){
           91                 /* draw background in bottom fringe of window */
           92                 r.min.x -= Scrollgap;
           93                 r.min.y = t->fr.r.max.y;
           94                 r.max.y = t->all.max.y;
           95                 draw(screen, r, t->fr.cols[BACK], nil, ZP);
           96         }
           97         return t->all.max.y;
           98 }
           99 
          100 void
          101 textclose(Text *t)
          102 {
          103         free(t->cache);
          104         frclear(&t->fr, 1);
          105         filedeltext(t->file, t);
          106         t->file = nil;
          107         rfclose(t->reffont);
          108         if(argtext == t)
          109                 argtext = nil;
          110         if(typetext == t)
          111                 typetext = nil;
          112         if(seltext == t)
          113                 seltext = nil;
          114         if(mousetext == t)
          115                 mousetext = nil;
          116         if(barttext == t)
          117                 barttext = nil;
          118 }
          119 
          120 int
          121 dircmp(const void *a, const void *b)
          122 {
          123         Dirlist *da, *db;
          124         int i, n;
          125 
          126         da = *(Dirlist**)a;
          127         db = *(Dirlist**)b;
          128         n = min(da->nr, db->nr);
          129         i = memcmp(da->r, db->r, n*sizeof(Rune));
          130         if(i)
          131                 return i;
          132         return da->nr - db->nr;
          133 }
          134 
          135 void
          136 textcolumnate(Text *t, Dirlist **dlp, int ndl)
          137 {
          138         int i, j, w, colw, mint, maxt, ncol, nrow;
          139         Dirlist *dl;
          140         uint q1;
          141         static Rune Lnl[] = { '\n', 0 };
          142         static Rune Ltab[] = { '\t', 0 };
          143 
          144         if(t->file->ntext > 1)
          145                 return;
          146         mint = stringwidth(t->fr.font, "0");
          147         /* go for narrower tabs if set more than 3 wide */
          148         t->fr.maxtab = min(maxtab, TABDIR)*mint;
          149         maxt = t->fr.maxtab;
          150         colw = 0;
          151         for(i=0; i<ndl; i++){
          152                 dl = dlp[i];
          153                 w = dl->wid;
          154                 if(maxt-w%maxt < mint || w%maxt==0)
          155                         w += mint;
          156                 if(w % maxt)
          157                         w += maxt-(w%maxt);
          158                 if(w > colw)
          159                         colw = w;
          160         }
          161         if(colw == 0)
          162                 ncol = 1;
          163         else
          164                 ncol = max(1, Dx(t->fr.r)/colw);
          165         nrow = (ndl+ncol-1)/ncol;
          166 
          167         q1 = 0;
          168         for(i=0; i<nrow; i++){
          169                 for(j=i; j<ndl; j+=nrow){
          170                         dl = dlp[j];
          171                         fileinsert(t->file, q1, dl->r, dl->nr);
          172                         q1 += dl->nr;
          173                         if(j+nrow >= ndl)
          174                                 break;
          175                         w = dl->wid;
          176                         if(maxt-w%maxt < mint){
          177                                 fileinsert(t->file, q1, Ltab, 1);
          178                                 q1++;
          179                                 w += mint;
          180                         }
          181                         do{
          182                                 fileinsert(t->file, q1, Ltab, 1);
          183                                 q1++;
          184                                 w += maxt-(w%maxt);
          185                         }while(w < colw);
          186                 }
          187                 fileinsert(t->file, q1, Lnl, 1);
          188                 q1++;
          189         }
          190 }
          191 
          192 int
          193 textload(Text *t, uint q0, char *file, int setqid)
          194 {
          195         Rune *rp;
          196         Dirlist *dl, **dlp;
          197         int fd, i, j, n, ndl, nulls;
          198         uint q, q1;
          199         Dir *d, *dbuf;
          200         char *tmp;
          201         Text *u;
          202         DigestState *h;
          203 
          204         if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body)
          205                 error("text.load");
          206         if(t->w->isdir && t->file->nname==0){
          207                 warning(nil, "empty directory name");
          208                 return -1;
          209         }
          210         if(ismtpt(file)){
          211                 warning(nil, "will not open self mount point %s\n", file);
          212                 return -1;
          213         }
          214         fd = open(file, OREAD);
          215         if(fd < 0){
          216                 warning(nil, "can't open %s: %r\n", file);
          217                 return -1;
          218         }
          219         d = dirfstat(fd);
          220         if(d == nil){
          221                 warning(nil, "can't fstat %s: %r\n", file);
          222                 goto Rescue;
          223         }
          224         nulls = FALSE;
          225         h = nil;
          226         if(d->qid.type & QTDIR){
          227                 /* this is checked in get() but it's possible the file changed underfoot */
          228                 if(t->file->ntext > 1){
          229                         warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
          230                         goto Rescue;
          231                 }
          232                 t->w->isdir = TRUE;
          233                 t->w->filemenu = FALSE;
          234                 if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
          235                         rp = runemalloc(t->file->nname+1);
          236                         runemove(rp, t->file->name, t->file->nname);
          237                         rp[t->file->nname] = '/';
          238                         winsetname(t->w, rp, t->file->nname+1);
          239                         free(rp);
          240                 }
          241                 dlp = nil;
          242                 ndl = 0;
          243                 dbuf = nil;
          244                 while((n=dirread(fd, &dbuf)) > 0){
          245                         for(i=0; i<n; i++){
          246                                 dl = emalloc(sizeof(Dirlist));
          247                                 j = strlen(dbuf[i].name);
          248                                 tmp = emalloc(j+1+1);
          249                                 memmove(tmp, dbuf[i].name, j);
          250                                 if(dbuf[i].qid.type & QTDIR)
          251                                         tmp[j++] = '/';
          252                                 tmp[j] = '\0';
          253                                 dl->r = bytetorune(tmp, &dl->nr);
          254                                 dl->wid = stringwidth(t->fr.font, tmp);
          255                                 free(tmp);
          256                                 ndl++;
          257                                 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
          258                                 dlp[ndl-1] = dl;
          259                         }
          260                         free(dbuf);
          261                 }
          262                 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
          263                 t->w->dlp = dlp;
          264                 t->w->ndl = ndl;
          265                 textcolumnate(t, dlp, ndl);
          266                 q1 = t->file->b.nc;
          267         }else{
          268                 t->w->isdir = FALSE;
          269                 t->w->filemenu = TRUE;
          270                 if(q0 == 0)
          271                         h = sha1(nil, 0, nil, nil);
          272                 q1 = q0 + fileload(t->file, q0, fd, &nulls, h);
          273         }
          274         if(setqid){
          275                 if(h != nil) {
          276                         sha1(nil, 0, t->file->sha1, h);
          277                         h = nil;
          278                 } else {
          279                         memset(t->file->sha1, 0, sizeof t->file->sha1);
          280                 }
          281                 t->file->dev = d->dev;
          282                 t->file->mtime = d->mtime;
          283                 t->file->qidpath = d->qid.path;
          284         }
          285         close(fd);
          286         rp = fbufalloc();
          287         for(q=q0; q<q1; q+=n){
          288                 n = q1-q;
          289                 if(n > RBUFSIZE)
          290                         n = RBUFSIZE;
          291                 bufread(&t->file->b, q, rp, n);
          292                 if(q < t->org)
          293                         t->org += n;
          294                 else if(q <= t->org+t->fr.nchars)
          295                         frinsert(&t->fr, rp, rp+n, q-t->org);
          296                 if(t->fr.lastlinefull)
          297                         break;
          298         }
          299         fbuffree(rp);
          300         for(i=0; i<t->file->ntext; i++){
          301                 u = t->file->text[i];
          302                 if(u != t){
          303                         if(u->org > u->file->b.nc)        /* will be 0 because of reset(), but safety first */
          304                                 u->org = 0;
          305                         textresize(u, u->all, TRUE);
          306                         textbacknl(u, u->org, 0);        /* go to beginning of line */
          307                 }
          308                 textsetselect(u, q0, q0);
          309         }
          310         if(nulls)
          311                 warning(nil, "%s: NUL bytes elided\n", file);
          312         free(d);
          313         return q1-q0;
          314 
          315     Rescue:
          316         close(fd);
          317         return -1;
          318 }
          319 
          320 uint
          321 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
          322 {
          323         Rune *bp, *tp, *up;
          324         int i, initial;
          325 
          326         if(t->what == Tag){        /* can't happen but safety first: mustn't backspace over file name */
          327     Err:
          328                 textinsert(t, q0, r, n, tofile);
          329                 *nrp = n;
          330                 return q0;
          331         }
          332         bp = r;
          333         for(i=0; i<n; i++)
          334                 if(*bp++ == '\b'){
          335                         --bp;
          336                         initial = 0;
          337                         tp = runemalloc(n);
          338                         runemove(tp, r, i);
          339                         up = tp+i;
          340                         for(; i<n; i++){
          341                                 *up = *bp++;
          342                                 if(*up == '\b')
          343                                         if(up == tp)
          344                                                 initial++;
          345                                         else
          346                                                 --up;
          347                                 else
          348                                         up++;
          349                         }
          350                         if(initial){
          351                                 if(initial > q0)
          352                                         initial = q0;
          353                                 q0 -= initial;
          354                                 textdelete(t, q0, q0+initial, tofile);
          355                         }
          356                         n = up-tp;
          357                         textinsert(t, q0, tp, n, tofile);
          358                         free(tp);
          359                         *nrp = n;
          360                         return q0;
          361                 }
          362         goto Err;
          363 }
          364 
          365 void
          366 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
          367 {
          368         int c, i;
          369         Text *u;
          370 
          371         if(tofile && t->ncache != 0)
          372                 error("text.insert");
          373         if(n == 0)
          374                 return;
          375         if(tofile){
          376                 fileinsert(t->file, q0, r, n);
          377                 if(t->what == Body){
          378                         t->w->dirty = TRUE;
          379                         t->w->utflastqid = -1;
          380                 }
          381                 if(t->file->ntext > 1)
          382                         for(i=0; i<t->file->ntext; i++){
          383                                 u = t->file->text[i];
          384                                 if(u != t){
          385                                         u->w->dirty = TRUE;        /* always a body */
          386                                         textinsert(u, q0, r, n, FALSE);
          387                                         textsetselect(u, u->q0, u->q1);
          388                                         textscrdraw(u);
          389                                 }
          390                         }
          391 
          392         }
          393         if(q0 < t->iq1)
          394                 t->iq1 += n;
          395         if(q0 < t->q1)
          396                 t->q1 += n;
          397         if(q0 < t->q0)
          398                 t->q0 += n;
          399         if(q0 < t->org)
          400                 t->org += n;
          401         else if(q0 <= t->org+t->fr.nchars)
          402                 frinsert(&t->fr, r, r+n, q0-t->org);
          403         if(t->w){
          404                 c = 'i';
          405                 if(t->what == Body)
          406                         c = 'I';
          407                 if(n <= EVENTSIZE)
          408                         winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
          409                 else
          410                         winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
          411         }
          412 }
          413 
          414 void
          415 typecommit(Text *t)
          416 {
          417         if(t->w != nil)
          418                 wincommit(t->w, t);
          419         else
          420                 textcommit(t, TRUE);
          421 }
          422 
          423 void
          424 textfill(Text *t)
          425 {
          426         Rune *rp;
          427         int i, n, m, nl;
          428 
          429         if(t->fr.lastlinefull || t->nofill)
          430                 return;
          431         if(t->ncache > 0)
          432                 typecommit(t);
          433         rp = fbufalloc();
          434         do{
          435                 n = t->file->b.nc-(t->org+t->fr.nchars);
          436                 if(n == 0)
          437                         break;
          438                 if(n > 2000)        /* educated guess at reasonable amount */
          439                         n = 2000;
          440                 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
          441                 /*
          442                  * it's expensive to frinsert more than we need, so
          443                  * count newlines.
          444                  */
          445                 nl = t->fr.maxlines-t->fr.nlines;
          446                 m = 0;
          447                 for(i=0; i<n; ){
          448                         if(rp[i++] == '\n'){
          449                                 m++;
          450                                 if(m >= nl)
          451                                         break;
          452                         }
          453                 }
          454                 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
          455         }while(t->fr.lastlinefull == FALSE);
          456         fbuffree(rp);
          457 }
          458 
          459 void
          460 textdelete(Text *t, uint q0, uint q1, int tofile)
          461 {
          462         uint n, p0, p1;
          463         int i, c;
          464         Text *u;
          465 
          466         if(tofile && t->ncache != 0)
          467                 error("text.delete");
          468         n = q1-q0;
          469         if(n == 0)
          470                 return;
          471         if(tofile){
          472                 filedelete(t->file, q0, q1);
          473                 if(t->what == Body){
          474                         t->w->dirty = TRUE;
          475                         t->w->utflastqid = -1;
          476                 }
          477                 if(t->file->ntext > 1)
          478                         for(i=0; i<t->file->ntext; i++){
          479                                 u = t->file->text[i];
          480                                 if(u != t){
          481                                         u->w->dirty = TRUE;        /* always a body */
          482                                         textdelete(u, q0, q1, FALSE);
          483                                         textsetselect(u, u->q0, u->q1);
          484                                         textscrdraw(u);
          485                                 }
          486                         }
          487         }
          488         if(q0 < t->iq1)
          489                 t->iq1 -= min(n, t->iq1-q0);
          490         if(q0 < t->q0)
          491                 t->q0 -= min(n, t->q0-q0);
          492         if(q0 < t->q1)
          493                 t->q1 -= min(n, t->q1-q0);
          494         if(q1 <= t->org)
          495                 t->org -= n;
          496         else if(q0 < t->org+t->fr.nchars){
          497                 p1 = q1 - t->org;
          498                 if(p1 > t->fr.nchars)
          499                         p1 = t->fr.nchars;
          500                 if(q0 < t->org){
          501                         t->org = q0;
          502                         p0 = 0;
          503                 }else
          504                         p0 = q0 - t->org;
          505                 frdelete(&t->fr, p0, p1);
          506                 textfill(t);
          507         }
          508         if(t->w){
          509                 c = 'd';
          510                 if(t->what == Body)
          511                         c = 'D';
          512                 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
          513         }
          514 }
          515 
          516 void
          517 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
          518 {
          519         *p0 = min(q0, t->file->b.nc);
          520         *p1 = min(q1, t->file->b.nc);
          521 }
          522 
          523 Rune
          524 textreadc(Text *t, uint q)
          525 {
          526         Rune r;
          527 
          528         if(t->cq0<=q && q<t->cq0+t->ncache)
          529                 r = t->cache[q-t->cq0];
          530         else
          531                 bufread(&t->file->b, q, &r, 1);
          532         return r;
          533 }
          534 
          535 int
          536 textbswidth(Text *t, Rune c)
          537 {
          538         uint q, eq;
          539         Rune r;
          540         int skipping;
          541 
          542         /* there is known to be at least one character to erase */
          543         if(c == 0x08)        /* ^H: erase character */
          544                 return 1;
          545         q = t->q0;
          546         skipping = TRUE;
          547         while(q > 0){
          548                 r = textreadc(t, q-1);
          549                 if(r == '\n'){                /* eat at most one more character */
          550                         if(q == t->q0)        /* eat the newline */
          551                                 --q;
          552                         break;
          553                 }
          554                 if(c == 0x17){
          555                         eq = isalnum(r);
          556                         if(eq && skipping)        /* found one; stop skipping */
          557                                 skipping = FALSE;
          558                         else if(!eq && !skipping)
          559                                 break;
          560                 }
          561                 --q;
          562         }
          563         return t->q0-q;
          564 }
          565 
          566 int
          567 textfilewidth(Text *t, uint q0, int oneelement)
          568 {
          569         uint q;
          570         Rune r;
          571 
          572         q = q0;
          573         while(q > 0){
          574                 r = textreadc(t, q-1);
          575                 if(r <= ' ')
          576                         break;
          577                 if(oneelement && r=='/')
          578                         break;
          579                 --q;
          580         }
          581         return q0-q;
          582 }
          583 
          584 Rune*
          585 textcomplete(Text *t)
          586 {
          587         int i, nstr, npath;
          588         uint q;
          589         Rune tmp[200];
          590         Rune *str, *path;
          591         Rune *rp;
          592         Completion *c;
          593         char *s, *dirs;
          594         Runestr dir;
          595 
          596         /* control-f: filename completion; works back to white space or / */
          597         if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ')        /* must be at end of word */
          598                 return nil;
          599         nstr = textfilewidth(t, t->q0, TRUE);
          600         str = runemalloc(nstr);
          601         npath = textfilewidth(t, t->q0-nstr, FALSE);
          602         path = runemalloc(npath);
          603 
          604         c = nil;
          605         rp = nil;
          606         dirs = nil;
          607 
          608         q = t->q0-nstr;
          609         for(i=0; i<nstr; i++)
          610                 str[i] = textreadc(t, q++);
          611         q = t->q0-nstr-npath;
          612         for(i=0; i<npath; i++)
          613                 path[i] = textreadc(t, q++);
          614         /* is path rooted? if not, we need to make it relative to window path */
          615         if(npath>0 && path[0]=='/')
          616                 dir = runestr(path, npath);
          617         else{
          618                 dir = dirname(t, nil, 0);
          619                 if(dir.nr + 1 + npath > nelem(tmp)){
          620                         free(dir.r);
          621                         goto Return;
          622                 }
          623                 if(dir.nr == 0){
          624                         dir.nr = 1;
          625                         dir.r = runestrdup(Ldot);
          626                 }
          627                 runemove(tmp, dir.r, dir.nr);
          628                 tmp[dir.nr] = '/';
          629                 runemove(tmp+dir.nr+1, path, npath);
          630                 free(dir.r);
          631                 dir.r = tmp;
          632                 dir.nr += 1+npath;
          633                 dir = cleanrname(dir);
          634         }
          635 
          636         s = smprint("%.*S", nstr, str);
          637         dirs = smprint("%.*S", dir.nr, dir.r);
          638         c = complete(dirs, s);
          639         free(s);
          640         if(c == nil){
          641                 warning(nil, "error attempting completion: %r\n");
          642                 goto Return;
          643         }
          644 
          645         if(!c->advance){
          646                 warning(nil, "%.*S%s%.*S*%s\n",
          647                         dir.nr, dir.r,
          648                         dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
          649                         nstr, str,
          650                         c->nmatch ? "" : ": no matches in:");
          651                 for(i=0; i<c->nfile; i++)
          652                         warning(nil, " %s\n", c->filename[i]);
          653         }
          654 
          655         if(c->advance)
          656                 rp = runesmprint("%s", c->string);
          657         else
          658                 rp = nil;
          659   Return:
          660         freecompletion(c);
          661         free(dirs);
          662         free(str);
          663         free(path);
          664         return rp;
          665 }
          666 
          667 void
          668 texttype(Text *t, Rune r)
          669 {
          670         uint q0, q1;
          671         int nnb, nb, n, i;
          672         int nr;
          673         Rune *rp;
          674         Text *u;
          675 
          676         if(t->what!=Body && t->what!=Tag && r=='\n')
          677                 return;
          678         if(t->what == Tag)
          679                 t->w->tagsafe = FALSE;
          680 
          681         nr = 1;
          682         rp = &r;
          683         switch(r){
          684         case Kleft:
          685                 typecommit(t);
          686                 if(t->q0 > 0)
          687                         textshow(t, t->q0-1, t->q0-1, TRUE);
          688                 return;
          689         case Kright:
          690                 typecommit(t);
          691                 if(t->q1 < t->file->b.nc)
          692                         textshow(t, t->q1+1, t->q1+1, TRUE);
          693                 return;
          694         case Kdown:
          695                 if(t->what == Tag)
          696                         goto Tagdown;
          697                 n = t->fr.maxlines/3;
          698                 goto case_Down;
          699         case Kscrollonedown:
          700                 if(t->what == Tag)
          701                         goto Tagdown;
          702                 n = mousescrollsize(t->fr.maxlines);
          703                 if(n <= 0)
          704                         n = 1;
          705                 goto case_Down;
          706         case Kpgdown:
          707                 n = 2*t->fr.maxlines/3;
          708         case_Down:
          709                 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
          710                 textsetorigin(t, q0, TRUE);
          711                 return;
          712         case Kup:
          713                 if(t->what == Tag)
          714                         goto Tagup;
          715                 n = t->fr.maxlines/3;
          716                 goto case_Up;
          717         case Kscrolloneup:
          718                 if(t->what == Tag)
          719                         goto Tagup;
          720                 n = mousescrollsize(t->fr.maxlines);
          721                 goto case_Up;
          722         case Kpgup:
          723                 n = 2*t->fr.maxlines/3;
          724         case_Up:
          725                 q0 = textbacknl(t, t->org, n);
          726                 textsetorigin(t, q0, TRUE);
          727                 return;
          728         case Khome:
          729                 typecommit(t);
          730                 if(t->org > t->iq1) {
          731                         q0 = textbacknl(t, t->iq1, 1);
          732                         textsetorigin(t, q0, TRUE);
          733                 } else
          734                         textshow(t, 0, 0, FALSE);
          735                 return;
          736         case Kend:
          737                 typecommit(t);
          738                 if(t->iq1 > t->org+t->fr.nchars) {
          739                         if(t->iq1 > t->file->b.nc) {
          740                                 // should not happen, but does. and it will crash textbacknl.
          741                                 t->iq1 = t->file->b.nc;
          742                         }
          743                         q0 = textbacknl(t, t->iq1, 1);
          744                         textsetorigin(t, q0, TRUE);
          745                 } else
          746                         textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
          747                 return;
          748         case 0x01:        /* ^A: beginning of line */
          749                 typecommit(t);
          750                 /* go to where ^U would erase, if not already at BOL */
          751                 nnb = 0;
          752                 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
          753                         nnb = textbswidth(t, 0x15);
          754                 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
          755                 return;
          756         case 0x05:        /* ^E: end of line */
          757                 typecommit(t);
          758                 q0 = t->q0;
          759                 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
          760                         q0++;
          761                 textshow(t, q0, q0, TRUE);
          762                 return;
          763         case Kcmd+'c':        /* %C: copy */
          764                 typecommit(t);
          765                 cut(t, t, nil, TRUE, FALSE, nil, 0);
          766                 return;
          767         case Kcmd+'z':        /* %Z: undo */
          768                  typecommit(t);
          769                 undo(t, nil, nil, TRUE, 0, nil, 0);
          770                 return;
          771         case Kcmd+'Z':        /* %-shift-Z: redo */
          772                  typecommit(t);
          773                 undo(t, nil, nil, FALSE, 0, nil, 0);
          774                 return;
          775 
          776         Tagdown:
          777                 /* expand tag to show all text */
          778                 if(!t->w->tagexpand){
          779                         t->w->tagexpand = TRUE;
          780                         winresize(t->w, t->w->r, FALSE, TRUE);
          781                 }
          782                 return;
          783 
          784         Tagup:
          785                 /* shrink tag to single line */
          786                 if(t->w->tagexpand){
          787                         t->w->tagexpand = FALSE;
          788                         t->w->taglines = 1;
          789                         winresize(t->w, t->w->r, FALSE, TRUE);
          790                 }
          791                 return;
          792         }
          793         if(t->what == Body){
          794                 seq++;
          795                 filemark(t->file);
          796         }
          797         /* cut/paste must be done after the seq++/filemark */
          798         switch(r){
          799         case Kcmd+'x':        /* %X: cut */
          800                 typecommit(t);
          801                 if(t->what == Body){
          802                         seq++;
          803                         filemark(t->file);
          804                 }
          805                 cut(t, t, nil, TRUE, TRUE, nil, 0);
          806                 textshow(t, t->q0, t->q0, 1);
          807                 t->iq1 = t->q0;
          808                 return;
          809         case Kcmd+'v':        /* %V: paste */
          810                 typecommit(t);
          811                 if(t->what == Body){
          812                         seq++;
          813                         filemark(t->file);
          814                 }
          815                 paste(t, t, nil, TRUE, FALSE, nil, 0);
          816                 textshow(t, t->q0, t->q1, 1);
          817                 t->iq1 = t->q1;
          818                 return;
          819         }
          820         if(t->q1 > t->q0){
          821                 if(t->ncache != 0)
          822                         error("text.type");
          823                 cut(t, t, nil, TRUE, TRUE, nil, 0);
          824                 t->eq0 = ~0;
          825         }
          826         textshow(t, t->q0, t->q0, 1);
          827         switch(r){
          828         case 0x06:        /* ^F: complete */
          829         case Kins:
          830                 typecommit(t);
          831                 rp = textcomplete(t);
          832                 if(rp == nil)
          833                         return;
          834                 nr = runestrlen(rp);
          835                 break;        /* fall through to normal insertion case */
          836         case 0x1B:
          837                 if(t->eq0 != ~0) {
          838                         if(t->eq0 <= t->q0)
          839                                 textsetselect(t, t->eq0, t->q0);
          840                         else
          841                                 textsetselect(t, t->q0, t->eq0);
          842                 }
          843                 if(t->ncache > 0)
          844                         typecommit(t);
          845                 t->iq1 = t->q0;
          846                 return;
          847         case 0x08:        /* ^H: erase character */
          848         case 0x15:        /* ^U: erase line */
          849         case 0x17:        /* ^W: erase word */
          850                 if(t->q0 == 0)        /* nothing to erase */
          851                         return;
          852                 nnb = textbswidth(t, r);
          853                 q1 = t->q0;
          854                 q0 = q1-nnb;
          855                 /* if selection is at beginning of window, avoid deleting invisible text */
          856                 if(q0 < t->org){
          857                         q0 = t->org;
          858                         nnb = q1-q0;
          859                 }
          860                 if(nnb <= 0)
          861                         return;
          862                 for(i=0; i<t->file->ntext; i++){
          863                         u = t->file->text[i];
          864                         u->nofill = TRUE;
          865                         nb = nnb;
          866                         n = u->ncache;
          867                         if(n > 0){
          868                                 if(q1 != u->cq0+n)
          869                                         error("text.type backspace");
          870                                 if(n > nb)
          871                                         n = nb;
          872                                 u->ncache -= n;
          873                                 textdelete(u, q1-n, q1, FALSE);
          874                                 nb -= n;
          875                         }
          876                         if(u->eq0==q1 || u->eq0==~0)
          877                                 u->eq0 = q0;
          878                         if(nb && u==t)
          879                                 textdelete(u, q0, q0+nb, TRUE);
          880                         if(u != t)
          881                                 textsetselect(u, u->q0, u->q1);
          882                         else
          883                                 textsetselect(t, q0, q0);
          884                         u->nofill = FALSE;
          885                 }
          886                 for(i=0; i<t->file->ntext; i++)
          887                         textfill(t->file->text[i]);
          888                 t->iq1 = t->q0;
          889                 return;
          890         case '\n':
          891                 if(t->w->autoindent){
          892                         /* find beginning of previous line using backspace code */
          893                         nnb = textbswidth(t, 0x15); /* ^U case */
          894                         rp = runemalloc(nnb + 1);
          895                         nr = 0;
          896                         rp[nr++] = r;
          897                         for(i=0; i<nnb; i++){
          898                                 r = textreadc(t, t->q0-nnb+i);
          899                                 if(r != ' ' && r != '\t')
          900                                         break;
          901                                 rp[nr++] = r;
          902                         }
          903                 }
          904                 break; /* fall through to normal code */
          905         }
          906         /* otherwise ordinary character; just insert, typically in caches of all texts */
          907         for(i=0; i<t->file->ntext; i++){
          908                 u = t->file->text[i];
          909                 if(u->eq0 == ~0)
          910                         u->eq0 = t->q0;
          911                 if(u->ncache == 0)
          912                         u->cq0 = t->q0;
          913                 else if(t->q0 != u->cq0+u->ncache)
          914                         error("text.type cq1");
          915                 /*
          916                  * Change the tag before we add to ncache,
          917                  * so that if the window body is resized the
          918                  * commit will not find anything in ncache.
          919                  */
          920                 if(u->what==Body && u->ncache == 0){
          921                         u->needundo = TRUE;
          922                         winsettag(t->w);
          923                         u->needundo = FALSE;
          924                 }
          925                 textinsert(u, t->q0, rp, nr, FALSE);
          926                 if(u != t)
          927                         textsetselect(u, u->q0, u->q1);
          928                 if(u->ncache+nr > u->ncachealloc){
          929                         u->ncachealloc += 10 + nr;
          930                         u->cache = runerealloc(u->cache, u->ncachealloc);
          931                 }
          932                 runemove(u->cache+u->ncache, rp, nr);
          933                 u->ncache += nr;
          934         }
          935         if(rp != &r)
          936                 free(rp);
          937         textsetselect(t, t->q0+nr, t->q0+nr);
          938         if(r=='\n' && t->w!=nil)
          939                 wincommit(t->w, t);
          940         t->iq1 = t->q0;
          941 }
          942 
          943 void
          944 textcommit(Text *t, int tofile)
          945 {
          946         if(t->ncache == 0)
          947                 return;
          948         if(tofile)
          949                 fileinsert(t->file, t->cq0, t->cache, t->ncache);
          950         if(t->what == Body){
          951                 t->w->dirty = TRUE;
          952                 t->w->utflastqid = -1;
          953         }
          954         t->ncache = 0;
          955 }
          956 
          957 static        Text        *clicktext;
          958 static        uint        clickmsec;
          959 static        Text        *selecttext;
          960 static        uint        selectq;
          961 
          962 /*
          963  * called from frame library
          964  */
          965 void
          966 framescroll(Frame *f, int dl)
          967 {
          968         if(f != &selecttext->fr)
          969                 error("frameselect not right frame");
          970         textframescroll(selecttext, dl);
          971 }
          972 
          973 void
          974 textframescroll(Text *t, int dl)
          975 {
          976         uint q0;
          977 
          978         if(dl == 0){
          979                 scrsleep(100);
          980                 return;
          981         }
          982         if(dl < 0){
          983                 q0 = textbacknl(t, t->org, -dl);
          984                 if(selectq > t->org+t->fr.p0)
          985                         textsetselect(t, t->org+t->fr.p0, selectq);
          986                 else
          987                         textsetselect(t, selectq, t->org+t->fr.p0);
          988         }else{
          989                 if(t->org+t->fr.nchars == t->file->b.nc)
          990                         return;
          991                 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
          992                 if(selectq > t->org+t->fr.p1)
          993                         textsetselect(t, t->org+t->fr.p1, selectq);
          994                 else
          995                         textsetselect(t, selectq, t->org+t->fr.p1);
          996         }
          997         textsetorigin(t, q0, TRUE);
          998 }
          999 
         1000 
         1001 void
         1002 textselect(Text *t)
         1003 {
         1004         uint q0, q1;
         1005         int b, x, y;
         1006         int state;
         1007         enum { None, Cut, Paste };
         1008 
         1009         selecttext = t;
         1010         /*
         1011          * To have double-clicking and chording, we double-click
         1012          * immediately if it might make sense.
         1013          */
         1014         b = mouse->buttons;
         1015         q0 = t->q0;
         1016         q1 = t->q1;
         1017         selectq = t->org+frcharofpt(&t->fr, mouse->xy);
         1018         if(clicktext==t && mouse->msec-clickmsec<500)
         1019         if(q0==q1 && selectq==q0){
         1020                 textdoubleclick(t, &q0, &q1);
         1021                 textsetselect(t, q0, q1);
         1022                 flushimage(display, 1);
         1023                 x = mouse->xy.x;
         1024                 y = mouse->xy.y;
         1025                 /* stay here until something interesting happens */
         1026                 do
         1027                         readmouse(mousectl);
         1028                 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
         1029                 mouse->xy.x = x;        /* in case we're calling frselect */
         1030                 mouse->xy.y = y;
         1031                 q0 = t->q0;        /* may have changed */
         1032                 q1 = t->q1;
         1033                 selectq = q0;
         1034         }
         1035         if(mouse->buttons == b){
         1036                 t->fr.scroll = framescroll;
         1037                 frselect(&t->fr, mousectl);
         1038                 /* horrible botch: while asleep, may have lost selection altogether */
         1039                 if(selectq > t->file->b.nc)
         1040                         selectq = t->org + t->fr.p0;
         1041                 t->fr.scroll = nil;
         1042                 if(selectq < t->org)
         1043                         q0 = selectq;
         1044                 else
         1045                         q0 = t->org + t->fr.p0;
         1046                 if(selectq > t->org+t->fr.nchars)
         1047                         q1 = selectq;
         1048                 else
         1049                         q1 = t->org+t->fr.p1;
         1050         }
         1051         if(q0 == q1){
         1052                 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
         1053                         textdoubleclick(t, &q0, &q1);
         1054                         clicktext = nil;
         1055                 }else{
         1056                         clicktext = t;
         1057                         clickmsec = mouse->msec;
         1058                 }
         1059         }else
         1060                 clicktext = nil;
         1061         textsetselect(t, q0, q1);
         1062         flushimage(display, 1);
         1063         state = None;        /* what we've done; undo when possible */
         1064         while(mouse->buttons){
         1065                 mouse->msec = 0;
         1066                 b = mouse->buttons;
         1067                 if((b&1) && (b&6)){
         1068                         if(state==None && t->what==Body){
         1069                                 seq++;
         1070                                 filemark(t->w->body.file);
         1071                         }
         1072                         if(b & 2){
         1073                                 if(state==Paste && t->what==Body){
         1074                                         winundo(t->w, TRUE);
         1075                                         textsetselect(t, q0, t->q1);
         1076                                         state = None;
         1077                                 }else if(state != Cut){
         1078                                         cut(t, t, nil, TRUE, TRUE, nil, 0);
         1079                                         state = Cut;
         1080                                 }
         1081                         }else{
         1082                                 if(state==Cut && t->what==Body){
         1083                                         winundo(t->w, TRUE);
         1084                                         textsetselect(t, q0, t->q1);
         1085                                         state = None;
         1086                                 }else if(state != Paste){
         1087                                         paste(t, t, nil, TRUE, FALSE, nil, 0);
         1088                                         state = Paste;
         1089                                 }
         1090                         }
         1091                         textscrdraw(t);
         1092                         clearmouse();
         1093                 }
         1094                 flushimage(display, 1);
         1095                 while(mouse->buttons == b)
         1096                         readmouse(mousectl);
         1097                 clicktext = nil;
         1098         }
         1099 }
         1100 
         1101 void
         1102 textshow(Text *t, uint q0, uint q1, int doselect)
         1103 {
         1104         int qe;
         1105         int nl;
         1106         int tsd;
         1107         int nc;
         1108         uint q;
         1109 
         1110         if(t->what != Body){
         1111                 if(doselect)
         1112                         textsetselect(t, q0, q1);
         1113                 return;
         1114         }
         1115         if(t->w!=nil && t->fr.maxlines==0)
         1116                 colgrow(t->col, t->w, 1);
         1117         if(doselect)
         1118                 textsetselect(t, q0, q1);
         1119         qe = t->org+t->fr.nchars;
         1120         tsd = FALSE;        /* do we call textscrdraw? */
         1121         nc = t->file->b.nc+t->ncache;
         1122         if(t->org <= q0){
         1123                 if(nc==0 || q0<qe)
         1124                         tsd = TRUE;
         1125                 else if(q0==qe && qe==nc){
         1126                         if(textreadc(t, nc-1) == '\n'){
         1127                                 if(t->fr.nlines<t->fr.maxlines)
         1128                                         tsd = TRUE;
         1129                         }else
         1130                                 tsd = TRUE;
         1131                 }
         1132         }
         1133         if(tsd)
         1134                 textscrdraw(t);
         1135         else{
         1136                 if(t->w->nopen[QWevent] > 0)
         1137                         nl = 3*t->fr.maxlines/4;
         1138                 else
         1139                         nl = t->fr.maxlines/4;
         1140                 q = textbacknl(t, q0, nl);
         1141                 /* avoid going backwards if trying to go forwards - long lines! */
         1142                 if(!(q0>t->org && q<t->org))
         1143                         textsetorigin(t, q, TRUE);
         1144                 while(q0 > t->org+t->fr.nchars)
         1145                         textsetorigin(t, t->org+1, FALSE);
         1146         }
         1147 }
         1148 
         1149 static
         1150 int
         1151 region(int a, int b)
         1152 {
         1153         if(a < b)
         1154                 return -1;
         1155         if(a == b)
         1156                 return 0;
         1157         return 1;
         1158 }
         1159 
         1160 void
         1161 selrestore(Frame *f, Point pt0, uint p0, uint p1)
         1162 {
         1163         if(p1<=f->p0 || p0>=f->p1){
         1164                 /* no overlap */
         1165                 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
         1166                 return;
         1167         }
         1168         if(p0>=f->p0 && p1<=f->p1){
         1169                 /* entirely inside */
         1170                 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
         1171                 return;
         1172         }
         1173 
         1174         /* they now are known to overlap */
         1175 
         1176         /* before selection */
         1177         if(p0 < f->p0){
         1178                 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
         1179                 p0 = f->p0;
         1180                 pt0 = frptofchar(f, p0);
         1181         }
         1182         /* after selection */
         1183         if(p1 > f->p1){
         1184                 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
         1185                 p1 = f->p1;
         1186         }
         1187         /* inside selection */
         1188         frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
         1189 }
         1190 
         1191 void
         1192 textsetselect(Text *t, uint q0, uint q1)
         1193 {
         1194         int p0, p1, ticked;
         1195 
         1196         /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
         1197         t->q0 = q0;
         1198         t->q1 = q1;
         1199         /* compute desired p0,p1 from q0,q1 */
         1200         p0 = q0-t->org;
         1201         p1 = q1-t->org;
         1202         ticked = 1;
         1203         if(p0 < 0){
         1204                 ticked = 0;
         1205                 p0 = 0;
         1206         }
         1207         if(p1 < 0)
         1208                 p1 = 0;
         1209         if(p0 > t->fr.nchars)
         1210                 p0 = t->fr.nchars;
         1211         if(p1 > t->fr.nchars){
         1212                 ticked = 0;
         1213                 p1 = t->fr.nchars;
         1214         }
         1215         if(p0==t->fr.p0 && p1==t->fr.p1){
         1216                 if(p0 == p1 && ticked != t->fr.ticked)
         1217                         frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
         1218                 return;
         1219         }
         1220         if(p0 > p1)
         1221                 sysfatal("acme: textsetselect p0=%d p1=%d q0=%ud q1=%ud t->org=%d nchars=%d", p0, p1, q0, q1, (int)t->org, (int)t->fr.nchars);
         1222         /* screen disagrees with desired selection */
         1223         if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
         1224                 /* no overlap or too easy to bother trying */
         1225                 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
         1226                 if(p0 != p1 || ticked)
         1227                         frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
         1228                 goto Return;
         1229         }
         1230         /* overlap; avoid unnecessary painting */
         1231         if(p0 < t->fr.p0){
         1232                 /* extend selection backwards */
         1233                 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
         1234         }else if(p0 > t->fr.p0){
         1235                 /* trim first part of selection */
         1236                 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
         1237         }
         1238         if(p1 > t->fr.p1){
         1239                 /* extend selection forwards */
         1240                 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
         1241         }else if(p1 < t->fr.p1){
         1242                 /* trim last part of selection */
         1243                 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
         1244         }
         1245 
         1246     Return:
         1247         t->fr.p0 = p0;
         1248         t->fr.p1 = p1;
         1249 }
         1250 
         1251 /*
         1252  * Release the button in less than DELAY ms and it's considered a null selection
         1253  * if the mouse hardly moved, regardless of whether it crossed a char boundary.
         1254  */
         1255 enum {
         1256         DELAY = 2,
         1257         MINMOVE = 4
         1258 };
         1259 
         1260 uint
         1261 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)        /* when called, button is down */
         1262 {
         1263         uint p0, p1, q, tmp;
         1264         ulong msec;
         1265         Point mp, pt0, pt1, qt;
         1266         int reg, b;
         1267 
         1268         mp = mc->m.xy;
         1269         b = mc->m.buttons;
         1270         msec = mc->m.msec;
         1271 
         1272         /* remove tick */
         1273         if(f->p0 == f->p1)
         1274                 frtick(f, frptofchar(f, f->p0), 0);
         1275         p0 = p1 = frcharofpt(f, mp);
         1276         pt0 = frptofchar(f, p0);
         1277         pt1 = frptofchar(f, p1);
         1278         reg = 0;
         1279         frtick(f, pt0, 1);
         1280         do{
         1281                 q = frcharofpt(f, mc->m.xy);
         1282                 if(p1 != q){
         1283                         if(p0 == p1)
         1284                                 frtick(f, pt0, 0);
         1285                         if(reg != region(q, p0)){        /* crossed starting point; reset */
         1286                                 if(reg > 0)
         1287                                         selrestore(f, pt0, p0, p1);
         1288                                 else if(reg < 0)
         1289                                         selrestore(f, pt1, p1, p0);
         1290                                 p1 = p0;
         1291                                 pt1 = pt0;
         1292                                 reg = region(q, p0);
         1293                                 if(reg == 0)
         1294                                         frdrawsel0(f, pt0, p0, p1, col, display->white);
         1295                         }
         1296                         qt = frptofchar(f, q);
         1297                         if(reg > 0){
         1298                                 if(q > p1)
         1299                                         frdrawsel0(f, pt1, p1, q, col, display->white);
         1300 
         1301                                 else if(q < p1)
         1302                                         selrestore(f, qt, q, p1);
         1303                         }else if(reg < 0){
         1304                                 if(q > p1)
         1305                                         selrestore(f, pt1, p1, q);
         1306                                 else
         1307                                         frdrawsel0(f, qt, q, p1, col, display->white);
         1308                         }
         1309                         p1 = q;
         1310                         pt1 = qt;
         1311                 }
         1312                 if(p0 == p1)
         1313                         frtick(f, pt0, 1);
         1314                 flushimage(f->display, 1);
         1315                 readmouse(mc);
         1316         }while(mc->m.buttons == b);
         1317         if(mc->m.msec-msec < DELAY && p0!=p1
         1318         && abs(mp.x-mc->m.xy.x)<MINMOVE
         1319         && abs(mp.y-mc->m.xy.y)<MINMOVE) {
         1320                 if(reg > 0)
         1321                         selrestore(f, pt0, p0, p1);
         1322                 else if(reg < 0)
         1323                         selrestore(f, pt1, p1, p0);
         1324                 p1 = p0;
         1325         }
         1326         if(p1 < p0){
         1327                 tmp = p0;
         1328                 p0 = p1;
         1329                 p1 = tmp;
         1330         }
         1331         pt0 = frptofchar(f, p0);
         1332         if(p0 == p1)
         1333                 frtick(f, pt0, 0);
         1334         selrestore(f, pt0, p0, p1);
         1335         /* restore tick */
         1336         if(f->p0 == f->p1)
         1337                 frtick(f, frptofchar(f, f->p0), 1);
         1338         flushimage(f->display, 1);
         1339         *p1p = p1;
         1340         return p0;
         1341 }
         1342 
         1343 int
         1344 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
         1345 {
         1346         uint p0, p1;
         1347         int buts;
         1348 
         1349         p0 = xselect(&t->fr, mousectl, high, &p1);
         1350         buts = mousectl->m.buttons;
         1351         if((buts & mask) == 0){
         1352                 *q0 = p0+t->org;
         1353                 *q1 = p1+t->org;
         1354         }
         1355 
         1356         while(mousectl->m.buttons)
         1357                 readmouse(mousectl);
         1358         return buts;
         1359 }
         1360 
         1361 int
         1362 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
         1363 {
         1364         int buts;
         1365 
         1366         *tp = nil;
         1367         buts = textselect23(t, q0, q1, but2col, 4);
         1368         if(buts & 4)
         1369                 return 0;
         1370         if(buts & 1){        /* pick up argument */
         1371                 *tp = argtext;
         1372                 return 1;
         1373         }
         1374         return 1;
         1375 }
         1376 
         1377 int
         1378 textselect3(Text *t, uint *q0, uint *q1)
         1379 {
         1380         int h;
         1381 
         1382         h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
         1383         return h;
         1384 }
         1385 
         1386 static Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
         1387 static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
         1388 static Rune left2[] =  { '\n', 0 };
         1389 static Rune left3[] =  { '\'', '"', '`', 0 };
         1390 
         1391 static
         1392 Rune *left[] = {
         1393         left1,
         1394         left2,
         1395         left3,
         1396         nil
         1397 };
         1398 static
         1399 Rune *right[] = {
         1400         right1,
         1401         left2,
         1402         left3,
         1403         nil
         1404 };
         1405 
         1406 void
         1407 textdoubleclick(Text *t, uint *q0, uint *q1)
         1408 {
         1409         int c, i;
         1410         Rune *r, *l, *p;
         1411         uint q;
         1412 
         1413         if(textclickhtmlmatch(t, q0, q1))
         1414                 return;
         1415 
         1416         for(i=0; left[i]!=nil; i++){
         1417                 q = *q0;
         1418                 l = left[i];
         1419                 r = right[i];
         1420                 /* try matching character to left, looking right */
         1421                 if(q == 0)
         1422                         c = '\n';
         1423                 else
         1424                         c = textreadc(t, q-1);
         1425                 p = runestrchr(l, c);
         1426                 if(p != nil){
         1427                         if(textclickmatch(t, c, r[p-l], 1, &q))
         1428                                 *q1 = q-(c!='\n');
         1429                         return;
         1430                 }
         1431                 /* try matching character to right, looking left */
         1432                 if(q == t->file->b.nc)
         1433                         c = '\n';
         1434                 else
         1435                         c = textreadc(t, q);
         1436                 p = runestrchr(r, c);
         1437                 if(p != nil){
         1438                         if(textclickmatch(t, c, l[p-r], -1, &q)){
         1439                                 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
         1440                                 *q0 = q;
         1441                                 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
         1442                                         (*q0)++;
         1443                         }
         1444                         return;
         1445                 }
         1446         }
         1447 
         1448         /* try filling out word to right */
         1449         while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
         1450                 (*q1)++;
         1451         /* try filling out word to left */
         1452         while(*q0>0 && isalnum(textreadc(t, *q0-1)))
         1453                 (*q0)--;
         1454 }
         1455 
         1456 int
         1457 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
         1458 {
         1459         Rune c;
         1460         int nest;
         1461 
         1462         nest = 1;
         1463         for(;;){
         1464                 if(dir > 0){
         1465                         if(*q == t->file->b.nc)
         1466                                 break;
         1467                         c = textreadc(t, *q);
         1468                         (*q)++;
         1469                 }else{
         1470                         if(*q == 0)
         1471                                 break;
         1472                         (*q)--;
         1473                         c = textreadc(t, *q);
         1474                 }
         1475                 if(c == cr){
         1476                         if(--nest==0)
         1477                                 return 1;
         1478                 }else if(c == cl)
         1479                         nest++;
         1480         }
         1481         return cl=='\n' && nest==1;
         1482 }
         1483 
         1484 // Is the text starting at location q an html tag?
         1485 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
         1486 // Set *q1, if non-nil, to the location after the tag.
         1487 static int
         1488 ishtmlstart(Text *t, uint q, uint *q1)
         1489 {
         1490         int c, c1, c2;
         1491 
         1492         if(q+2 > t->file->b.nc)
         1493                 return 0;
         1494         if(textreadc(t, q++) != '<')
         1495                 return 0;
         1496         c = textreadc(t, q++);
         1497         c1 = c;
         1498         c2 = c;
         1499         while(c != '>') {
         1500                 if(q >= t->file->b.nc)
         1501                         return 0;
         1502                 c2 = c;
         1503                 c = textreadc(t, q++);
         1504         }
         1505         if(q1)
         1506                 *q1 = q;
         1507         if(c1 == '/')        // closing tag
         1508                 return -1;
         1509         if(c2 == '/' || c2 == '!')        // open + close tag or comment
         1510                 return 0;
         1511         return 1;
         1512 }
         1513 
         1514 // Is the text ending at location q an html tag?
         1515 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
         1516 // Set *q0, if non-nil, to the start of the tag.
         1517 static int
         1518 ishtmlend(Text *t, uint q, uint *q0)
         1519 {
         1520         int c, c1, c2;
         1521 
         1522         if(q < 2)
         1523                 return 0;
         1524         if(textreadc(t, --q) != '>')
         1525                 return 0;
         1526         c = textreadc(t, --q);
         1527         c1 = c;
         1528         c2 = c;
         1529         while(c != '<') {
         1530                 if(q == 0)
         1531                         return 0;
         1532                 c1 = c;
         1533                 c = textreadc(t, --q);
         1534         }
         1535         if(q0)
         1536                 *q0 = q;
         1537         if(c1 == '/')        // closing tag
         1538                 return -1;
         1539         if(c2 == '/' || c2 == '!')        // open + close tag or comment
         1540                 return 0;
         1541         return 1;
         1542 }
         1543 
         1544 int
         1545 textclickhtmlmatch(Text *t, uint *q0, uint *q1)
         1546 {
         1547         int depth, n;
         1548         uint q, nq;
         1549 
         1550         q = *q0;
         1551         // after opening tag?  scan forward for closing tag
         1552         if(ishtmlend(t, q, nil) == 1) {
         1553                 depth = 1;
         1554                 while(q < t->file->b.nc) {
         1555                         n = ishtmlstart(t, q, &nq);
         1556                         if(n != 0) {
         1557                                 depth += n;
         1558                                 if(depth == 0) {
         1559                                         *q1 = q;
         1560                                         return 1;
         1561                                 }
         1562                                 q = nq;
         1563                                 continue;
         1564                         }
         1565                         q++;
         1566                 }
         1567         }
         1568 
         1569         // before closing tag?  scan backward for opening tag
         1570         if(ishtmlstart(t, q, nil) == -1) {
         1571                 depth = -1;
         1572                 while(q > 0) {
         1573                         n = ishtmlend(t, q, &nq);
         1574                         if(n != 0) {
         1575                                 depth += n;
         1576                                 if(depth == 0) {
         1577                                         *q0 = q;
         1578                                         return 1;
         1579                                 }
         1580                                 q = nq;
         1581                                 continue;
         1582                         }
         1583                         q--;
         1584                 }
         1585         }
         1586 
         1587         return 0;
         1588 }
         1589 
         1590 uint
         1591 textbacknl(Text *t, uint p, uint n)
         1592 {
         1593         int i, j;
         1594 
         1595         /* look for start of this line if n==0 */
         1596         if(n==0 && p>0 && textreadc(t, p-1)!='\n')
         1597                 n = 1;
         1598         i = n;
         1599         while(i-->0 && p>0){
         1600                 --p;        /* it's at a newline now; back over it */
         1601                 if(p == 0)
         1602                         break;
         1603                 /* at 128 chars, call it a line anyway */
         1604                 for(j=128; --j>0 && p>0; p--)
         1605                         if(textreadc(t, p-1)=='\n')
         1606                                 break;
         1607         }
         1608         return p;
         1609 }
         1610 
         1611 void
         1612 textsetorigin(Text *t, uint org, int exact)
         1613 {
         1614         int i, a, fixup;
         1615         Rune *r;
         1616         uint n;
         1617 
         1618         if(org>0 && !exact && textreadc(t, org-1) != '\n'){
         1619                 /* org is an estimate of the char posn; find a newline */
         1620                 /* don't try harder than 256 chars */
         1621                 for(i=0; i<256 && org<t->file->b.nc; i++){
         1622                         if(textreadc(t, org) == '\n'){
         1623                                 org++;
         1624                                 break;
         1625                         }
         1626                         org++;
         1627                 }
         1628         }
         1629         a = org-t->org;
         1630         fixup = 0;
         1631         if(a>=0 && a<t->fr.nchars){
         1632                 frdelete(&t->fr, 0, a);
         1633                 fixup = 1;        /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
         1634         }
         1635         else if(a<0 && -a<t->fr.nchars){
         1636                 n = t->org - org;
         1637                 r = runemalloc(n);
         1638                 bufread(&t->file->b, org, r, n);
         1639                 frinsert(&t->fr, r, r+n, 0);
         1640                 free(r);
         1641         }else
         1642                 frdelete(&t->fr, 0, t->fr.nchars);
         1643         t->org = org;
         1644         textfill(t);
         1645         textscrdraw(t);
         1646         textsetselect(t, t->q0, t->q1);
         1647         if(fixup && t->fr.p1 > t->fr.p0)
         1648                 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
         1649 }
         1650 
         1651 void
         1652 textreset(Text *t)
         1653 {
         1654         t->file->seq = 0;
         1655         t->eq0 = ~0;
         1656         /* do t->delete(0, t->nc, TRUE) without building backup stuff */
         1657         textsetselect(t, t->org, t->org);
         1658         frdelete(&t->fr, 0, t->fr.nchars);
         1659         t->org = 0;
         1660         t->q0 = 0;
         1661         t->q1 = 0;
         1662         filereset(t->file);
         1663         bufreset(&t->file->b);
         1664 }