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