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