]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/acme/text.c
stats: show amount of reclaimable pages (add -r flag)
[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 int
506 textbswidth(Text *t, Rune c)
507 {
508         uint q, eq;
509         Rune r;
510         int skipping;
511
512         /* there is known to be at least one character to erase */
513         if(c == 0x08)   /* ^H: erase character */
514                 return 1;
515         q = t->q0;
516         skipping = TRUE;
517         while(q > 0){
518                 r = textreadc(t, q-1);
519                 if(r == '\n'){          /* eat at most one more character */
520                         if(q == t->q0)  /* eat the newline */
521                                 --q;
522                         break; 
523                 }
524                 if(c == 0x17){
525                         eq = isalnum(r);
526                         if(eq && skipping)      /* found one; stop skipping */
527                                 skipping = FALSE;
528                         else if(!eq && !skipping)
529                                 break;
530                 }
531                 --q;
532         }
533         return t->q0-q;
534 }
535
536 int
537 textfilewidth(Text *t, uint q0, int oneelement)
538 {
539         uint q;
540         Rune r;
541
542         q = q0;
543         while(q > 0){
544                 r = textreadc(t, q-1);
545                 if(r <= ' ')
546                         break;
547                 if(oneelement && r=='/')
548                         break;
549                 --q;
550         }
551         return q0-q;
552 }
553
554 Rune*
555 textcomplete(Text *t)
556 {
557         int i, nstr, npath;
558         uint q;
559         Rune tmp[200];
560         Rune *str, *path;
561         Rune *rp;
562         Completion *c;
563         char *s, *dirs;
564         Runestr dir;
565
566         /* control-f: filename completion; works back to white space or / */
567         if(t->q0<t->file->nc && textreadc(t, t->q0)>' ')        /* must be at end of word */
568                 return nil;
569         nstr = textfilewidth(t, t->q0, TRUE);
570         str = runemalloc(nstr);
571         npath = textfilewidth(t, t->q0-nstr, FALSE);
572         path = runemalloc(npath);
573
574         c = nil;
575         rp = nil;
576         dirs = nil;
577
578         q = t->q0-nstr;
579         for(i=0; i<nstr; i++)
580                 str[i] = textreadc(t, q++);
581         q = t->q0-nstr-npath;
582         for(i=0; i<npath; i++)
583                 path[i] = textreadc(t, q++);
584         /* is path rooted? if not, we need to make it relative to window path */
585         if(npath>0 && path[0]=='/')
586                 dir = (Runestr){path, npath};
587         else{
588                 dir = dirname(t, nil, 0);
589                 if(dir.nr + 1 + npath > nelem(tmp)){
590                         free(dir.r);
591                         goto Return;
592                 }
593                 if(dir.nr == 0){
594                         dir.nr = 1;
595                         dir.r = runestrdup(L".");
596                 }
597                 runemove(tmp, dir.r, dir.nr);
598                 tmp[dir.nr] = '/';
599                 runemove(tmp+dir.nr+1, path, npath);
600                 free(dir.r);
601                 dir.r = tmp;
602                 dir.nr += 1+npath;
603                 dir = cleanrname(dir);
604         }
605
606         s = smprint("%.*S", nstr, str);
607         dirs = smprint("%.*S", dir.nr, dir.r);
608         c = complete(dirs, s);
609         free(s);
610         if(c == nil){
611                 warning(nil, "error attempting completion: %r\n");
612                 goto Return;
613         }
614
615         if(!c->advance){
616                 warning(nil, "%.*S%s%.*S*%s\n",
617                         dir.nr, dir.r,
618                         dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
619                         nstr, str,
620                         c->nmatch? "" : ": no matches in:");
621                 for(i=0; i<c->nfile; i++)
622                         warning(nil, " %s\n", c->filename[i]);
623         }
624
625         if(c->advance)
626                 rp = runesmprint("%s", c->string);
627         else
628                 rp = nil;
629   Return:
630         freecompletion(c);
631         free(dirs);
632         free(str);
633         free(path);
634         return rp;
635 }
636
637 void
638 texttype(Text *t, Rune r)
639 {
640         uint q0, q1;
641         int nnb, nb, n, i;
642         int nr;
643         Rune *rp;
644         Text *u;
645
646         nr = 1;
647         rp = &r;
648         switch(r){
649         case Kleft:
650                 typecommit(t);
651                 if(t->q0 > 0)
652                         textshow(t, t->q0-1, t->q0-1, TRUE);
653                 return;
654         case Kright:
655                 typecommit(t);
656                 if(t->q1 < t->file->nc)
657                         textshow(t, t->q1+1, t->q1+1, TRUE);
658                 return;
659         case Kdown:
660                 n = t->maxlines/3;
661                 goto case_Down;
662         case Kscrollonedown:
663                 n = mousescrollsize(t->maxlines);
664                 if(n <= 0)
665                         n = 1;
666                 goto case_Down;
667         case Kpgdown:
668                 n = 2*t->maxlines/3;
669         case_Down:
670                 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
671                 if(t->what == Body)
672                         textsetorigin(t, q0, TRUE);
673                 return;
674         case Kup:
675                 n = t->maxlines/3;
676                 goto case_Up;
677         case Kscrolloneup:
678                 n = mousescrollsize(t->maxlines);
679                 goto case_Up;
680         case Kpgup:
681                 n = 2*t->maxlines/3;
682         case_Up:
683                 q0 = textbacknl(t, t->org, n);
684                 if(t->what == Body)
685                         textsetorigin(t, q0, TRUE);
686                 return;
687         case Khome:
688                 typecommit(t);
689                 textshow(t, 0, 0, FALSE);
690                 return;
691         case Kend:
692                 typecommit(t);
693                 textshow(t, t->file->nc, t->file->nc, FALSE);
694                 return;
695         case 0x01:      /* ^A: beginning of line */
696                 typecommit(t);
697                 /* go to where ^U would erase, if not already at BOL */
698                 nnb = 0;
699                 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
700                         nnb = textbswidth(t, 0x15);
701                 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
702                 return;
703         case 0x05:      /* ^E: end of line */
704                 typecommit(t);
705                 q0 = t->q0;
706                 while(q0<t->file->nc && textreadc(t, q0)!='\n')
707                         q0++;
708                 textshow(t, q0, q0, TRUE);
709                 return;
710         }
711         if(t->what == Body){
712                 seq++;
713                 filemark(t->file);
714         }
715         if(t->q1 > t->q0){
716                 if(t->ncache != 0)
717                         error("text.type");
718                 cut(t, t, nil, TRUE, TRUE, nil, 0);
719                 t->eq0 = ~0;
720         }
721         textshow(t, t->q0, t->q0, 1);
722         switch(r){
723         case 0x06:
724         case Kins:
725                 rp = textcomplete(t);
726                 if(rp == nil)
727                         return;
728                 nr = runestrlen(rp);
729                 break;  /* fall through to normal insertion case */
730         case 0x1B:
731                 if(t->eq0 != ~0)
732                         textsetselect(t, t->eq0, t->q0);
733                 if(t->ncache > 0)
734                         typecommit(t);
735                 return;
736         case 0x08:      /* ^H: erase character */
737         case 0x15:      /* ^U: erase line */
738         case 0x17:      /* ^W: erase word */
739                 if(t->q0 == 0)  /* nothing to erase */
740                         return;
741                 nnb = textbswidth(t, r);
742                 q1 = t->q0;
743                 q0 = q1-nnb;
744                 /* if selection is at beginning of window, avoid deleting invisible text */
745                 if(q0 < t->org){
746                         q0 = t->org;
747                         nnb = q1-q0;
748                 }
749                 if(nnb <= 0)
750                         return;
751                 for(i=0; i<t->file->ntext; i++){
752                         u = t->file->text[i];
753                         u->nofill = TRUE;
754                         nb = nnb;
755                         n = u->ncache;
756                         if(n > 0){
757                                 if(q1 != u->cq0+n)
758                                         error("text.type backspace");
759                                 if(n > nb)
760                                         n = nb;
761                                 u->ncache -= n;
762                                 textdelete(u, q1-n, q1, FALSE);
763                                 nb -= n;
764                         }
765                         if(u->eq0==q1 || u->eq0==~0)
766                                 u->eq0 = q0;
767                         if(nb && u==t)
768                                 textdelete(u, q0, q0+nb, TRUE);
769                         if(u != t)
770                                 textsetselect(u, u->q0, u->q1);
771                         else
772                                 textsetselect(t, q0, q0);
773                         u->nofill = FALSE;
774                 }
775                 for(i=0; i<t->file->ntext; i++)
776                         textfill(t->file->text[i]);
777                 return;
778         case '\n':
779                 if(t->what == Body && t->w->autoindent){
780                         /* find beginning of previous line using backspace code */
781                         nnb = textbswidth(t, 0x15); /* ^U case */
782                         rp = runemalloc(nnb + 1);
783                         nr = 0;
784                         rp[nr++] = r;
785                         for(i=0; i<nnb; i++){
786                                 r = textreadc(t, t->q0-nnb+i);
787                                 if(r != ' ' && r != '\t')
788                                         break;
789                                 rp[nr++] = r;
790                         }
791                 }
792                 break; /* fall through to normal code */
793         }
794         /* otherwise ordinary character; just insert, typically in caches of all texts */
795         for(i=0; i<t->file->ntext; i++){
796                 u = t->file->text[i];
797                 if(u->eq0 == ~0)
798                         u->eq0 = t->q0;
799                 if(u->ncache == 0)
800                         u->cq0 = t->q0;
801                 else if(t->q0 != u->cq0+u->ncache)
802                         error("text.type cq1");
803                 textinsert(u, t->q0, rp, nr, FALSE);
804                 if(u != t)
805                         textsetselect(u, u->q0, u->q1);
806                 if(u->ncache+nr > u->ncachealloc){
807                         u->ncachealloc += 10 + nr;
808                         u->cache = runerealloc(u->cache, u->ncachealloc);
809                 }
810                 runemove(u->cache+u->ncache, rp, nr);
811                 u->ncache += nr;
812         }
813         if(rp != &r)
814                 free(rp);
815         textsetselect(t, t->q0+nr, t->q0+nr);
816         if(r=='\n' && t->w!=nil)
817                 wincommit(t->w, t);
818 }
819
820 void
821 textcommit(Text *t, int tofile)
822 {
823         if(t->ncache == 0)
824                 return;
825         if(tofile)
826                 fileinsert(t->file, t->cq0, t->cache, t->ncache);
827         if(t->what == Body){
828                 t->w->dirty = TRUE;
829                 t->w->utflastqid = -1;
830         }
831         t->ncache = 0;
832 }
833
834 static  Text    *clicktext;
835 static  uint    clickmsec;
836 static  Text    *selecttext;
837 static  uint    selectq;
838
839 /*
840  * called from frame library
841  */
842 void
843 framescroll(Frame *f, int dl)
844 {
845         if(f != &selecttext->Frame)
846                 error("frameselect not right frame");
847         textframescroll(selecttext, dl);
848 }
849
850 void
851 textframescroll(Text *t, int dl)
852 {
853         uint q0;
854
855         if(dl == 0){
856                 scrsleep(100);
857                 return;
858         }
859         if(dl < 0){
860                 q0 = textbacknl(t, t->org, -dl);
861                 if(selectq > t->org+t->p0)
862                         textsetselect(t, t->org+t->p0, selectq);
863                 else
864                         textsetselect(t, selectq, t->org+t->p0);
865         }else{
866                 if(t->org+t->nchars == t->file->nc)
867                         return;
868                 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
869                 if(selectq > t->org+t->p1)
870                         textsetselect(t, t->org+t->p1, selectq);
871                 else
872                         textsetselect(t, selectq, t->org+t->p1);
873         }
874         textsetorigin(t, q0, TRUE);
875 }
876
877
878 void
879 textselect(Text *t)
880 {
881         uint q0, q1;
882         int b, x, y;
883         int state;
884
885         selecttext = t;
886         /*
887          * To have double-clicking and chording, we double-click
888          * immediately if it might make sense.
889          */
890         b = mouse->buttons;
891         q0 = t->q0;
892         q1 = t->q1;
893         selectq = t->org+frcharofpt(t, mouse->xy);
894         if(clicktext==t && mouse->msec-clickmsec<500)
895         if(q0==q1 && selectq==q0){
896                 textdoubleclick(t, &q0, &q1);
897                 textsetselect(t, q0, q1);
898                 flushimage(display, 1);
899                 x = mouse->xy.x;
900                 y = mouse->xy.y;
901                 /* stay here until something interesting happens */
902                 do
903                         readmouse(mousectl);
904                 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
905                 mouse->xy.x = x;        /* in case we're calling frselect */
906                 mouse->xy.y = y;
907                 q0 = t->q0;     /* may have changed */
908                 q1 = t->q1;
909                 selectq = q0;
910         }
911         if(mouse->buttons == b){
912                 t->Frame.scroll = framescroll;
913                 frselect(t, mousectl);
914                 /* horrible botch: while asleep, may have lost selection altogether */
915                 if(selectq > t->file->nc)
916                         selectq = t->org + t->p0;
917                 t->Frame.scroll = nil;
918                 if(selectq < t->org)
919                         q0 = selectq;
920                 else
921                         q0 = t->org + t->p0;
922                 if(selectq > t->org+t->nchars)
923                         q1 = selectq;
924                 else
925                         q1 = t->org+t->p1;
926         }
927         if(q0 == q1){
928                 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
929                         textdoubleclick(t, &q0, &q1);
930                         clicktext = nil;
931                 }else{
932                         clicktext = t;
933                         clickmsec = mouse->msec;
934                 }
935         }else
936                 clicktext = nil;
937         textsetselect(t, q0, q1);
938         flushimage(display, 1);
939         state = 0;      /* undo when possible; +1 for cut, -1 for paste */
940         while(mouse->buttons){
941                 mouse->msec = 0;
942                 b = mouse->buttons;
943                 if((b&1) && (b&6)){
944                         if(state==0 && t->what==Body){
945                                 seq++;
946                                 filemark(t->w->body.file);
947                         }
948                         if(b & 2){
949                                 if(state==-1 && t->what==Body){
950                                         winundo(t->w, TRUE);
951                                         textsetselect(t, q0, t->q0);
952                                         state = 0;
953                                 }else if(state != 1){
954                                         cut(t, t, nil, TRUE, TRUE, nil, 0);
955                                         state = 1;
956                                 }
957                         }else{
958                                 if(state==1 && t->what==Body){
959                                         winundo(t->w, TRUE);
960                                         textsetselect(t, q0, t->q1);
961                                         state = 0;
962                                 }else if(state != -1){
963                                         paste(t, t, nil, TRUE, FALSE, nil, 0);
964                                         state = -1;
965                                 }
966                         }
967                         textscrdraw(t);
968                         clearmouse();
969                 }
970                 flushimage(display, 1);
971                 while(mouse->buttons == b)
972                         readmouse(mousectl);
973                 clicktext = nil;
974         }
975 }
976
977 void
978 textshow(Text *t, uint q0, uint q1, int doselect)
979 {
980         int qe;
981         int nl;
982         uint q;
983
984         if(t->what != Body){
985                 if(doselect)
986                         textsetselect(t, q0, q1);
987                 return;
988         }
989         if(t->w!=nil && t->maxlines==0)
990                 colgrow(t->col, t->w, 1);
991         if(doselect)
992                 textsetselect(t, q0, q1);
993         qe = t->org+t->nchars;
994         if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
995                 textscrdraw(t);
996         else{
997                 if(t->w->nopen[QWevent] > 0)
998                         nl = 3*t->maxlines/4;
999                 else
1000                         nl = t->maxlines/4;
1001                 q = textbacknl(t, q0, nl);
1002                 /* avoid going backwards if trying to go forwards - long lines! */
1003                 if(!(q0>t->org && q<t->org))
1004                         textsetorigin(t, q, TRUE);
1005                 while(q0 > t->org+t->nchars)
1006                         textsetorigin(t, t->org+1, FALSE);
1007         }
1008 }
1009
1010 static
1011 int
1012 region(int a, int b)
1013 {
1014         if(a < b)
1015                 return -1;
1016         if(a == b)
1017                 return 0;
1018         return 1;
1019 }
1020
1021 void
1022 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1023 {
1024         if(p1<=f->p0 || p0>=f->p1){
1025                 /* no overlap */
1026                 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1027                 return;
1028         }
1029         if(p0>=f->p0 && p1<=f->p1){
1030                 /* entirely inside */
1031                 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1032                 return;
1033         }
1034
1035         /* they now are known to overlap */
1036
1037         /* before selection */
1038         if(p0 < f->p0){
1039                 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1040                 p0 = f->p0;
1041                 pt0 = frptofchar(f, p0);
1042         }
1043         /* after selection */
1044         if(p1 > f->p1){
1045                 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1046                 p1 = f->p1;
1047         }
1048         /* inside selection */
1049         frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1050 }
1051
1052 void
1053 textsetselect(Text *t, uint q0, uint q1)
1054 {
1055         int p0, p1;
1056
1057         /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
1058         t->q0 = q0;
1059         t->q1 = q1;
1060         /* compute desired p0,p1 from q0,q1 */
1061         p0 = q0-t->org;
1062         p1 = q1-t->org;
1063         if(p0 < 0)
1064                 p0 = 0;
1065         if(p1 < 0)
1066                 p1 = 0;
1067         if(p0 > t->nchars)
1068                 p0 = t->nchars;
1069         if(p1 > t->nchars)
1070                 p1 = t->nchars;
1071         if(p0==t->p0 && p1==t->p1)
1072                 return;
1073         /* screen disagrees with desired selection */
1074         if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
1075                 /* no overlap or too easy to bother trying */
1076                 frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
1077                 frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
1078                 goto Return;
1079         }
1080         /* overlap; avoid unnecessary painting */
1081         if(p0 < t->p0){
1082                 /* extend selection backwards */
1083                 frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
1084         }else if(p0 > t->p0){
1085                 /* trim first part of selection */
1086                 frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
1087         }
1088         if(p1 > t->p1){
1089                 /* extend selection forwards */
1090                 frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
1091         }else if(p1 < t->p1){
1092                 /* trim last part of selection */
1093                 frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
1094         }
1095
1096     Return:
1097         t->p0 = p0;
1098         t->p1 = p1;
1099 }
1100
1101 /*
1102  * Release the button in less than DELAY ms and it's considered a null selection
1103  * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1104  */
1105 enum {
1106         DELAY = 2,
1107         MINMOVE = 4,
1108 };
1109
1110 uint
1111 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)  /* when called, button is down */
1112 {
1113         uint p0, p1, q, tmp;
1114         ulong msec;
1115         Point mp, pt0, pt1, qt;
1116         int reg, b;
1117
1118         mp = mc->xy;
1119         b = mc->buttons;
1120         msec = mc->msec;
1121
1122         /* remove tick */
1123         if(f->p0 == f->p1)
1124                 frtick(f, frptofchar(f, f->p0), 0);
1125         p0 = p1 = frcharofpt(f, mp);
1126         pt0 = frptofchar(f, p0);
1127         pt1 = frptofchar(f, p1);
1128         reg = 0;
1129         frtick(f, pt0, 1);
1130         do{
1131                 q = frcharofpt(f, mc->xy);
1132                 if(p1 != q){
1133                         if(p0 == p1)
1134                                 frtick(f, pt0, 0);
1135                         if(reg != region(q, p0)){       /* crossed starting point; reset */
1136                                 if(reg > 0)
1137                                         selrestore(f, pt0, p0, p1);
1138                                 else if(reg < 0)
1139                                         selrestore(f, pt1, p1, p0);
1140                                 p1 = p0;
1141                                 pt1 = pt0;
1142                                 reg = region(q, p0);
1143                                 if(reg == 0)
1144                                         frdrawsel0(f, pt0, p0, p1, col, display->white);
1145                         }
1146                         qt = frptofchar(f, q);
1147                         if(reg > 0){
1148                                 if(q > p1)
1149                                         frdrawsel0(f, pt1, p1, q, col, display->white);
1150
1151                                 else if(q < p1)
1152                                         selrestore(f, qt, q, p1);
1153                         }else if(reg < 0){
1154                                 if(q > p1)
1155                                         selrestore(f, pt1, p1, q);
1156                                 else
1157                                         frdrawsel0(f, qt, q, p1, col, display->white);
1158                         }
1159                         p1 = q;
1160                         pt1 = qt;
1161                 }
1162                 if(p0 == p1)
1163                         frtick(f, pt0, 1);
1164                 flushimage(f->display, 1);
1165                 readmouse(mc);
1166         }while(mc->buttons == b);
1167         if(mc->msec-msec < DELAY && p0!=p1
1168         && abs(mp.x-mc->xy.x)<MINMOVE
1169         && abs(mp.y-mc->xy.y)<MINMOVE) {
1170                 if(reg > 0)
1171                         selrestore(f, pt0, p0, p1);
1172                 else if(reg < 0)
1173                         selrestore(f, pt1, p1, p0);
1174                 p1 = p0;
1175         }
1176         if(p1 < p0){
1177                 tmp = p0;
1178                 p0 = p1;
1179                 p1 = tmp;
1180         }
1181         pt0 = frptofchar(f, p0);
1182         if(p0 == p1)
1183                 frtick(f, pt0, 0);
1184         selrestore(f, pt0, p0, p1);
1185         /* restore tick */
1186         if(f->p0 == f->p1)
1187                 frtick(f, frptofchar(f, f->p0), 1);
1188         flushimage(f->display, 1);
1189         *p1p = p1;
1190         return p0;
1191 }
1192
1193 int
1194 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1195 {
1196         uint p0, p1;
1197         int buts;
1198         
1199         p0 = xselect(t, mousectl, high, &p1);
1200         buts = mousectl->buttons;
1201         if((buts & mask) == 0){
1202                 *q0 = p0+t->org;
1203                 *q1 = p1+t->org;
1204         }
1205
1206         while(mousectl->buttons)
1207                 readmouse(mousectl);
1208         return buts;
1209 }
1210
1211 int
1212 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1213 {
1214         int buts;
1215
1216         *tp = nil;
1217         buts = textselect23(t, q0, q1, but2col, 4);
1218         if(buts & 4)
1219                 return 0;
1220         if(buts & 1){   /* pick up argument */
1221                 *tp = argtext;
1222                 return 1;
1223         }
1224         return 1;
1225 }
1226
1227 int
1228 textselect3(Text *t, uint *q0, uint *q1)
1229 {
1230         int h;
1231
1232         h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1233         return h;
1234 }
1235
1236 static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
1237 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
1238 static Rune left2[] =  { L'\n', 0 };
1239 static Rune left3[] =  { L'\'', L'"', L'`', 0 };
1240
1241 static
1242 Rune *left[] = {
1243         left1,
1244         left2,
1245         left3,
1246         nil
1247 };
1248 static
1249 Rune *right[] = {
1250         right1,
1251         left2,
1252         left3,
1253         nil
1254 };
1255
1256 void
1257 textdoubleclick(Text *t, uint *q0, uint *q1)
1258 {
1259         int c, i;
1260         Rune *r, *l, *p;
1261         uint q;
1262
1263         for(i=0; left[i]!=nil; i++){
1264                 q = *q0;
1265                 l = left[i];
1266                 r = right[i];
1267                 /* try matching character to left, looking right */
1268                 if(q == 0)
1269                         c = '\n';
1270                 else
1271                         c = textreadc(t, q-1);
1272                 p = runestrchr(l, c);
1273                 if(p != nil){
1274                         if(textclickmatch(t, c, r[p-l], 1, &q))
1275                                 *q1 = q-(c!='\n');
1276                         return;
1277                 }
1278                 /* try matching character to right, looking left */
1279                 if(q == t->file->nc)
1280                         c = '\n';
1281                 else
1282                         c = textreadc(t, q);
1283                 p = runestrchr(r, c);
1284                 if(p != nil){
1285                         if(textclickmatch(t, c, l[p-r], -1, &q)){
1286                                 *q1 = *q0+(*q0<t->file->nc && c=='\n');
1287                                 *q0 = q;
1288                                 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1289                                         (*q0)++;
1290                         }
1291                         return;
1292                 }
1293         }
1294         /* try filling out word to right */
1295         while(*q1<t->file->nc && isalnum(textreadc(t, *q1)))
1296                 (*q1)++;
1297         /* try filling out word to left */
1298         while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1299                 (*q0)--;
1300 }
1301
1302 int
1303 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1304 {
1305         Rune c;
1306         int nest;
1307
1308         nest = 1;
1309         for(;;){
1310                 if(dir > 0){
1311                         if(*q == t->file->nc)
1312                                 break;
1313                         c = textreadc(t, *q);
1314                         (*q)++;
1315                 }else{
1316                         if(*q == 0)
1317                                 break;
1318                         (*q)--;
1319                         c = textreadc(t, *q);
1320                 }
1321                 if(c == cr){
1322                         if(--nest==0)
1323                                 return 1;
1324                 }else if(c == cl)
1325                         nest++;
1326         }
1327         return cl=='\n' && nest==1;
1328 }
1329
1330 uint
1331 textbacknl(Text *t, uint p, uint n)
1332 {
1333         int i, j;
1334
1335         /* look for start of this line if n==0 */
1336         if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1337                 n = 1;
1338         i = n;
1339         while(i-->0 && p>0){
1340                 --p;    /* it's at a newline now; back over it */
1341                 if(p == 0)
1342                         break;
1343                 /* at 128 chars, call it a line anyway */
1344                 for(j=128; --j>0 && p>0; p--)
1345                         if(textreadc(t, p-1)=='\n')
1346                                 break;
1347         }
1348         return p;
1349 }
1350
1351 void
1352 textsetorigin(Text *t, uint org, int exact)
1353 {
1354         int i, a, fixup;
1355         Rune *r;
1356         uint n;
1357
1358         if(org>0 && !exact){
1359                 /* org is an estimate of the char posn; find a newline */
1360                 /* don't try harder than 256 chars */
1361                 for(i=0; i<256 && org<t->file->nc; i++){
1362                         if(textreadc(t, org) == '\n'){
1363                                 org++;
1364                                 break;
1365                         }
1366                         org++;
1367                 }
1368         }
1369         a = org-t->org;
1370         fixup = 0;
1371         if(a>=0 && a<t->nchars){
1372                 frdelete(t, 0, a);
1373                 fixup = 1;      /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1374         }
1375         else if(a<0 && -a<t->nchars){
1376                 n = t->org - org;
1377                 r = runemalloc(n);
1378                 bufread(t->file, org, r, n);
1379                 frinsert(t, r, r+n, 0);
1380                 free(r);
1381         }else
1382                 frdelete(t, 0, t->nchars);
1383         t->org = org;
1384         textfill(t);
1385         textscrdraw(t);
1386         textsetselect(t, t->q0, t->q1);
1387         if(fixup && t->p1 > t->p0)
1388                 frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
1389 }
1390
1391 void
1392 textreset(Text *t)
1393 {
1394         t->file->seq = 0;
1395         t->eq0 = ~0;
1396         /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1397         textsetselect(t, t->org, t->org);
1398         frdelete(t, 0, t->nchars);
1399         t->org = 0;
1400         t->q0 = 0;
1401         t->q1 = 0;
1402         filereset(t->file);
1403         bufreset(t->file);
1404 }