]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/acme/text.c
Reset click count on mouse motion.
[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  int     clickcount;
872 static  Point   clickpt;
873 static  Text    *selecttext;
874 static  uint    selectq;
875
876 /*
877  * called from frame library
878  */
879 void
880 framescroll(Frame *f, int dl)
881 {
882         if(f != &selecttext->Frame)
883                 error("frameselect not right frame");
884         textframescroll(selecttext, dl);
885 }
886
887 void
888 textframescroll(Text *t, int dl)
889 {
890         uint q0;
891
892         if(dl == 0){
893                 scrsleep(100);
894                 return;
895         }
896         if(dl < 0){
897                 q0 = textbacknl(t, t->org, -dl);
898                 if(selectq > t->org+t->p0)
899                         textsetselect(t, t->org+t->p0, selectq);
900                 else
901                         textsetselect(t, selectq, t->org+t->p0);
902         }else{
903                 if(t->org+t->nchars == t->file->nc)
904                         return;
905                 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
906                 if(selectq > t->org+t->p1)
907                         textsetselect(t, t->org+t->p1, selectq);
908                 else
909                         textsetselect(t, selectq, t->org+t->p1);
910         }
911         textsetorigin(t, q0, TRUE);
912         flushimage(display, 1);
913 }
914
915
916 void
917 textselect(Text *t)
918 {
919         uint q0, q1;
920         int b, x, y, dx, dy;
921         int state;
922
923         selecttext = t;
924         /*
925          * To have double-clicking and chording, we double-click
926          * immediately if it might make sense.
927          */
928         b = mouse->buttons;
929         q0 = t->q0;
930         q1 = t->q1;
931         dx = abs(clickpt.x - w->mc.xy.x);
932         dy = abs(clickpt.y - w->mc.xy.y);
933         clickpt = w->mc.xy;
934         selectq = t->org+frcharofpt(t, mouse->xy);
935         clickcount++;
936         if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3)
937                 clickcount = 0;
938         if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){
939                 textstretchsel(t, selectq, &q0, &q1, clickcount);
940                 textsetselect(t, q0, q1);
941                 flushimage(display, 1);
942                 x = mouse->xy.x;
943                 y = mouse->xy.y;
944                 /* stay here until something interesting happens */
945                 while(1){
946                         readmouse(mousectl);
947                         dx = abs(mouse->xy.x - x);
948                         dy = abs(mouse->xy.y - y);
949                         if(mouse->buttons != b || dx >= 3 || dy >= 3)
950                                 break;
951                         clickcount++;
952                         clickmsec = mouse->msec;
953                 }
954                 mouse->xy.x = x;        /* in case we're calling frselect */
955                 mouse->xy.y = y;
956                 q0 = t->q0;     /* may have changed */
957                 q1 = t->q1;
958                 selectq = t->org+frcharofpt(t, mouse->xy);;
959         }
960         if(mouse->buttons == b && clickcount == 0){
961                 t->Frame.scroll = framescroll;
962                 frselect(t, mousectl);
963                 /* horrible botch: while asleep, may have lost selection altogether */
964                 if(selectq > t->file->nc)
965                         selectq = t->org + t->p0;
966                 t->Frame.scroll = nil;
967                 if(selectq < t->org)
968                         q0 = selectq;
969                 else
970                         q0 = t->org + t->p0;
971                 if(selectq > t->org+t->nchars)
972                         q1 = selectq;
973                 else
974                         q1 = t->org+t->p1;
975         }
976         if(q0 == q1){
977                 if(q0==t->q0 && mouse->msec-clickmsec<500)
978                         textstretchsel(t, selectq, &q0, &q1, clickcount);
979                 else
980                         clicktext = t;
981                 clickmsec = mouse->msec;
982         }else
983                 clicktext = nil;
984         textsetselect(t, q0, q1);
985         flushimage(display, 1);
986         state = 0;      /* undo when possible; +1 for cut, -1 for paste */
987         while(mouse->buttons){
988                 mouse->msec = 0;
989                 b = mouse->buttons;
990                 if((b&1) && (b&6)){
991                         if(state==0 && t->what==Body){
992                                 seq++;
993                                 filemark(t->w->body.file);
994                         }
995                         if(b & 2){
996                                 if(state==-1 && t->what==Body){
997                                         winundo(t->w, TRUE);
998                                         textsetselect(t, q0, t->q0);
999                                         state = 0;
1000                                 }else if(state != 1){
1001                                         cut(t, t, nil, TRUE, TRUE, nil, 0);
1002                                         state = 1;
1003                                 }
1004                         }else{
1005                                 if(state==1 && t->what==Body){
1006                                         winundo(t->w, TRUE);
1007                                         textsetselect(t, q0, t->q1);
1008                                         state = 0;
1009                                 }else if(state != -1){
1010                                         paste(t, t, nil, TRUE, FALSE, nil, 0);
1011                                         state = -1;
1012                                 }
1013                         }
1014                         textscrdraw(t);
1015                         clearmouse();
1016                 }
1017                 flushimage(display, 1);
1018                 while(mouse->buttons == b)
1019                         readmouse(mousectl);
1020                 if(mouse->msec-clickmsec >= 500)
1021                         clicktext = nil;
1022         }
1023 }
1024
1025 void
1026 textshow(Text *t, uint q0, uint q1, int doselect)
1027 {
1028         int qe;
1029         int nl;
1030         uint q;
1031
1032         if(t->what != Body){
1033                 if(doselect)
1034                         textsetselect(t, q0, q1);
1035                 return;
1036         }
1037         if(t->w!=nil && t->maxlines==0)
1038                 colgrow(t->col, t->w, 1);
1039         if(doselect)
1040                 textsetselect(t, q0, q1);
1041         qe = t->org+t->nchars;
1042         if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
1043                 textscrdraw(t);
1044         else{
1045                 if(t->w->nopen[QWevent] > 0)
1046                         nl = 3*t->maxlines/4;
1047                 else
1048                         nl = t->maxlines/4;
1049                 q = textbacknl(t, q0, nl);
1050                 /* avoid going backwards if trying to go forwards - long lines! */
1051                 if(!(q0>t->org && q<t->org))
1052                         textsetorigin(t, q, TRUE);
1053                 while(q0 > t->org+t->nchars)
1054                         textsetorigin(t, t->org+1, FALSE);
1055         }
1056 }
1057
1058 static
1059 int
1060 region(int a, int b)
1061 {
1062         if(a < b)
1063                 return -1;
1064         if(a == b)
1065                 return 0;
1066         return 1;
1067 }
1068
1069 void
1070 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1071 {
1072         if(p1<=f->p0 || p0>=f->p1){
1073                 /* no overlap */
1074                 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1075                 return;
1076         }
1077         if(p0>=f->p0 && p1<=f->p1){
1078                 /* entirely inside */
1079                 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1080                 return;
1081         }
1082
1083         /* they now are known to overlap */
1084
1085         /* before selection */
1086         if(p0 < f->p0){
1087                 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1088                 p0 = f->p0;
1089                 pt0 = frptofchar(f, p0);
1090         }
1091         /* after selection */
1092         if(p1 > f->p1){
1093                 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1094                 p1 = f->p1;
1095         }
1096         /* inside selection */
1097         frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1098 }
1099
1100 void
1101 textsetselect(Text *t, uint q0, uint q1)
1102 {
1103         int p0, p1;
1104
1105         /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
1106         t->q0 = q0;
1107         t->q1 = q1;
1108         /* compute desired p0,p1 from q0,q1 */
1109         p0 = q0-t->org;
1110         p1 = q1-t->org;
1111         if(p0 < 0)
1112                 p0 = 0;
1113         if(p1 < 0)
1114                 p1 = 0;
1115         if(p0 > t->nchars)
1116                 p0 = t->nchars;
1117         if(p1 > t->nchars)
1118                 p1 = t->nchars;
1119         if(p0==t->p0 && p1==t->p1)
1120                 return;
1121         /* screen disagrees with desired selection */
1122         if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
1123                 /* no overlap or too easy to bother trying */
1124                 frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
1125                 frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
1126                 goto Return;
1127         }
1128         /* overlap; avoid unnecessary painting */
1129         if(p0 < t->p0){
1130                 /* extend selection backwards */
1131                 frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
1132         }else if(p0 > t->p0){
1133                 /* trim first part of selection */
1134                 frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
1135         }
1136         if(p1 > t->p1){
1137                 /* extend selection forwards */
1138                 frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
1139         }else if(p1 < t->p1){
1140                 /* trim last part of selection */
1141                 frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
1142         }
1143
1144     Return:
1145         t->p0 = p0;
1146         t->p1 = p1;
1147 }
1148
1149 /*
1150  * Release the button in less than DELAY ms and it's considered a null selection
1151  * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1152  */
1153 enum {
1154         DELAY = 2,
1155         MINMOVE = 4,
1156 };
1157
1158 uint
1159 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)  /* when called, button is down */
1160 {
1161         uint p0, p1, q, tmp;
1162         ulong msec;
1163         Point mp, pt0, pt1, qt;
1164         int reg, b;
1165
1166         mp = mc->xy;
1167         b = mc->buttons;
1168         msec = mc->msec;
1169
1170         /* remove tick */
1171         if(f->p0 == f->p1)
1172                 frtick(f, frptofchar(f, f->p0), 0);
1173         p0 = p1 = frcharofpt(f, mp);
1174         pt0 = frptofchar(f, p0);
1175         pt1 = frptofchar(f, p1);
1176         reg = 0;
1177         frtick(f, pt0, 1);
1178         do{
1179                 q = frcharofpt(f, mc->xy);
1180                 if(p1 != q){
1181                         if(p0 == p1)
1182                                 frtick(f, pt0, 0);
1183                         if(reg != region(q, p0)){       /* crossed starting point; reset */
1184                                 if(reg > 0)
1185                                         selrestore(f, pt0, p0, p1);
1186                                 else if(reg < 0)
1187                                         selrestore(f, pt1, p1, p0);
1188                                 p1 = p0;
1189                                 pt1 = pt0;
1190                                 reg = region(q, p0);
1191                                 if(reg == 0)
1192                                         frdrawsel0(f, pt0, p0, p1, col, display->white);
1193                         }
1194                         qt = frptofchar(f, q);
1195                         if(reg > 0){
1196                                 if(q > p1)
1197                                         frdrawsel0(f, pt1, p1, q, col, display->white);
1198
1199                                 else if(q < p1)
1200                                         selrestore(f, qt, q, p1);
1201                         }else if(reg < 0){
1202                                 if(q > p1)
1203                                         selrestore(f, pt1, p1, q);
1204                                 else
1205                                         frdrawsel0(f, qt, q, p1, col, display->white);
1206                         }
1207                         p1 = q;
1208                         pt1 = qt;
1209                 }
1210                 if(p0 == p1)
1211                         frtick(f, pt0, 1);
1212                 flushimage(f->display, 1);
1213                 readmouse(mc);
1214         }while(mc->buttons == b);
1215         if(mc->msec-msec < DELAY && p0!=p1
1216         && abs(mp.x-mc->xy.x)<MINMOVE
1217         && abs(mp.y-mc->xy.y)<MINMOVE) {
1218                 if(reg > 0)
1219                         selrestore(f, pt0, p0, p1);
1220                 else if(reg < 0)
1221                         selrestore(f, pt1, p1, p0);
1222                 p1 = p0;
1223         }
1224         if(p1 < p0){
1225                 tmp = p0;
1226                 p0 = p1;
1227                 p1 = tmp;
1228         }
1229         pt0 = frptofchar(f, p0);
1230         if(p0 == p1)
1231                 frtick(f, pt0, 0);
1232         selrestore(f, pt0, p0, p1);
1233         /* restore tick */
1234         if(f->p0 == f->p1)
1235                 frtick(f, frptofchar(f, f->p0), 1);
1236         flushimage(f->display, 1);
1237         *p1p = p1;
1238         return p0;
1239 }
1240
1241 int
1242 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1243 {
1244         uint p0, p1;
1245         int buts;
1246         
1247         p0 = xselect(t, mousectl, high, &p1);
1248         buts = mousectl->buttons;
1249         if((buts & mask) == 0){
1250                 *q0 = p0+t->org;
1251                 *q1 = p1+t->org;
1252         }
1253
1254         while(mousectl->buttons)
1255                 readmouse(mousectl);
1256         return buts;
1257 }
1258
1259 int
1260 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1261 {
1262         int buts;
1263
1264         *tp = nil;
1265         buts = textselect23(t, q0, q1, but2col, 4);
1266         if(buts & 4)
1267                 return 0;
1268         if(buts & 1){   /* pick up argument */
1269                 *tp = argtext;
1270                 return 1;
1271         }
1272         return 1;
1273 }
1274
1275 int
1276 textselect3(Text *t, uint *q0, uint *q1)
1277 {
1278         int h;
1279
1280         h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1281         return h;
1282 }
1283
1284 static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
1285 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
1286 static Rune left2[] =  { L'\n', 0 };
1287 static Rune left3[] =  { L'\'', L'"', L'`', 0 };
1288
1289 static
1290 Rune *left[] = {
1291         left1,
1292         left2,
1293         left3,
1294         nil
1295 };
1296 static
1297 Rune *right[] = {
1298         right1,
1299         left2,
1300         left3,
1301         nil
1302 };
1303
1304 int
1305 inmode(Rune r, int mode)
1306 {
1307         return (mode == 1) ? isalnum(r) : r && !isspace(r);
1308 }
1309
1310 void
1311 textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode)
1312 {
1313         int c, i;
1314         Rune *r, *l, *p;
1315         uint q;
1316
1317         *q0 = mp;
1318         *q1 = mp;
1319         for(i=0; left[i]!=nil; i++){
1320                 q = *q0;
1321                 l = left[i];
1322                 r = right[i];
1323                 /* try matching character to left, looking right */
1324                 if(q == 0)
1325                         c = '\n';
1326                 else
1327                         c = textreadc(t, q-1);
1328                 p = runestrchr(l, c);
1329                 if(p != nil){
1330                         if(textclickmatch(t, c, r[p-l], 1, &q))
1331                                 *q1 = q-(c!='\n');
1332                         return;
1333                 }
1334                 /* try matching character to right, looking left */
1335                 if(q == t->file->nc)
1336                         c = '\n';
1337                 else
1338                         c = textreadc(t, q);
1339                 p = runestrchr(r, c);
1340                 if(p != nil){
1341                         if(textclickmatch(t, c, l[p-r], -1, &q)){
1342                                 *q1 = *q0+(*q0<t->file->nc && c=='\n');
1343                                 *q0 = q;
1344                                 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1345                                         (*q0)++;
1346                         }
1347                         return;
1348                 }
1349         }
1350         /* try filling out word to right */
1351         while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode))
1352                 (*q1)++;
1353         /* try filling out word to left */
1354         while(*q0>0 && inmode(textreadc(t, *q0-1), mode))
1355                 (*q0)--;
1356 }
1357
1358 int
1359 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1360 {
1361         Rune c;
1362         int nest;
1363
1364         nest = 1;
1365         for(;;){
1366                 if(dir > 0){
1367                         if(*q == t->file->nc)
1368                                 break;
1369                         c = textreadc(t, *q);
1370                         (*q)++;
1371                 }else{
1372                         if(*q == 0)
1373                                 break;
1374                         (*q)--;
1375                         c = textreadc(t, *q);
1376                 }
1377                 if(c == cr){
1378                         if(--nest==0)
1379                                 return 1;
1380                 }else if(c == cl)
1381                         nest++;
1382         }
1383         return cl=='\n' && nest==1;
1384 }
1385
1386 uint
1387 textbacknl(Text *t, uint p, uint n)
1388 {
1389         int i, j;
1390
1391         /* look for start of this line if n==0 */
1392         if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1393                 n = 1;
1394         i = n;
1395         while(i-->0 && p>0){
1396                 --p;    /* it's at a newline now; back over it */
1397                 if(p == 0)
1398                         break;
1399                 /* at 128 chars, call it a line anyway */
1400                 for(j=128; --j>0 && p>0; p--)
1401                         if(textreadc(t, p-1)=='\n')
1402                                 break;
1403         }
1404         return p;
1405 }
1406
1407 void
1408 textsetorigin(Text *t, uint org, int exact)
1409 {
1410         int i, a, fixup;
1411         Rune *r;
1412         uint n;
1413
1414         if(org>0 && !exact){
1415                 /* org is an estimate of the char posn; find a newline */
1416                 /* don't try harder than 256 chars */
1417                 for(i=0; i<256 && org<t->file->nc; i++){
1418                         if(textreadc(t, org) == '\n'){
1419                                 org++;
1420                                 break;
1421                         }
1422                         org++;
1423                 }
1424         }
1425         a = org-t->org;
1426         fixup = 0;
1427         if(a>=0 && a<t->nchars){
1428                 frdelete(t, 0, a);
1429                 fixup = 1;      /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1430         }
1431         else if(a<0 && -a<t->nchars){
1432                 n = t->org - org;
1433                 r = runemalloc(n);
1434                 bufread(t->file, org, r, n);
1435                 frinsert(t, r, r+n, 0);
1436                 free(r);
1437         }else
1438                 frdelete(t, 0, t->nchars);
1439         t->org = org;
1440         textfill(t);
1441         textscrdraw(t);
1442         textsetselect(t, t->q0, t->q1);
1443         if(fixup && t->p1 > t->p0)
1444                 frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
1445 }
1446
1447 void
1448 textreset(Text *t)
1449 {
1450         t->file->seq = 0;
1451         t->eq0 = ~0;
1452         /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1453         textsetselect(t, t->org, t->org);
1454         frdelete(t, 0, t->nchars);
1455         t->org = 0;
1456         t->q0 = 0;
1457         t->q1 = 0;
1458         filereset(t->file);
1459         bufreset(t->file);
1460 }