Blob


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