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; /* 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 fd = open(file, OREAD);
197 if(fd < 0){
198 warning(nil, "can't open %s: %r\n", file);
199 return 0;
201 d = dirfstat(fd);
202 if(d == nil){
203 warning(nil, "can't fstat %s: %r\n", file);
204 goto Rescue;
206 nulls = FALSE;
207 if(d->qid.type & QTDIR){
208 /* this is checked in get() but it's possible the file changed underfoot */
209 if(t->file->ntext > 1){
210 warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
211 goto Rescue;
213 t->w->isdir = TRUE;
214 t->w->filemenu = FALSE;
215 if(t->file->name[t->file->nname-1] != '/'){
216 rp = runemalloc(t->file->nname+1);
217 runemove(rp, t->file->name, t->file->nname);
218 rp[t->file->nname] = '/';
219 winsetname(t->w, rp, t->file->nname+1);
220 free(rp);
222 dlp = nil;
223 ndl = 0;
224 dbuf = nil;
225 while((n=dirread(fd, &dbuf)) > 0){
226 for(i=0; i<n; i++){
227 dl = emalloc(sizeof(Dirlist));
228 j = strlen(dbuf[i].name);
229 tmp = emalloc(j+1+1);
230 memmove(tmp, dbuf[i].name, j);
231 if(dbuf[i].qid.type & QTDIR)
232 tmp[j++] = '/';
233 tmp[j] = '\0';
234 dl->r = bytetorune(tmp, &dl->nr);
235 dl->wid = stringwidth(t->fr.font, tmp);
236 free(tmp);
237 ndl++;
238 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
239 dlp[ndl-1] = dl;
241 free(dbuf);
243 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
244 t->w->dlp = dlp;
245 t->w->ndl = ndl;
246 textcolumnate(t, dlp, ndl);
247 q1 = t->file->b.nc;
248 }else{
249 t->w->isdir = FALSE;
250 t->w->filemenu = TRUE;
251 q1 = q0 + fileload(t->file, q0, fd, &nulls);
253 if(setqid){
254 t->file->dev = d->dev;
255 t->file->mtime = d->mtime;
256 t->file->qidpath = d->qid.path;
258 close(fd);
259 rp = fbufalloc();
260 for(q=q0; q<q1; q+=n){
261 n = q1-q;
262 if(n > RBUFSIZE)
263 n = RBUFSIZE;
264 bufread(&t->file->b, q, rp, n);
265 if(q < t->org)
266 t->org += n;
267 else if(q <= t->org+t->fr.nchars)
268 frinsert(&t->fr, rp, rp+n, q-t->org);
269 if(t->fr.lastlinefull)
270 break;
272 fbuffree(rp);
273 for(i=0; i<t->file->ntext; i++){
274 u = t->file->text[i];
275 if(u != t){
276 if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */
277 u->org = 0;
278 textresize(u, u->all);
279 textbacknl(u, u->org, 0); /* go to beginning of line */
281 textsetselect(u, q0, q0);
283 if(nulls)
284 warning(nil, "%s: NUL bytes elided\n", file);
285 free(d);
286 return q1-q0;
288 Rescue:
289 close(fd);
290 return 0;
293 uint
294 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
296 Rune *bp, *tp, *up;
297 int i, initial;
299 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
300 Err:
301 textinsert(t, q0, r, n, tofile);
302 *nrp = n;
303 return q0;
305 bp = r;
306 for(i=0; i<n; i++)
307 if(*bp++ == '\b'){
308 --bp;
309 initial = 0;
310 tp = runemalloc(n);
311 runemove(tp, r, i);
312 up = tp+i;
313 for(; i<n; i++){
314 *up = *bp++;
315 if(*up == '\b')
316 if(up == tp)
317 initial++;
318 else
319 --up;
320 else
321 up++;
323 if(initial){
324 if(initial > q0)
325 initial = q0;
326 q0 -= initial;
327 textdelete(t, q0, q0+initial, tofile);
329 n = up-tp;
330 textinsert(t, q0, tp, n, tofile);
331 free(tp);
332 *nrp = n;
333 return q0;
335 goto Err;
338 void
339 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
341 int c, i;
342 Text *u;
344 if(tofile && t->ncache != 0)
345 error("text.insert");
346 if(n == 0)
347 return;
348 if(tofile){
349 fileinsert(t->file, q0, r, n);
350 if(t->what == Body){
351 t->w->dirty = TRUE;
352 t->w->utflastqid = -1;
354 if(t->file->ntext > 1)
355 for(i=0; i<t->file->ntext; i++){
356 u = t->file->text[i];
357 if(u != t){
358 u->w->dirty = TRUE; /* always a body */
359 textinsert(u, q0, r, n, FALSE);
360 textsetselect(u, u->q0, u->q1);
361 textscrdraw(u);
366 if(q0 < t->q1)
367 t->q1 += n;
368 if(q0 < t->q0)
369 t->q0 += n;
370 if(q0 < t->org)
371 t->org += n;
372 else if(q0 <= t->org+t->fr.nchars)
373 frinsert(&t->fr, r, r+n, q0-t->org);
374 if(t->w){
375 c = 'i';
376 if(t->what == Body)
377 c = 'I';
378 if(n <= EVENTSIZE)
379 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
380 else
381 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
386 void
387 textfill(Text *t)
389 Rune *rp;
390 int i, n, m, nl;
392 if(t->fr.lastlinefull || t->nofill)
393 return;
394 if(t->ncache > 0){
395 if(t->w != nil)
396 wincommit(t->w, t);
397 else
398 textcommit(t, TRUE);
400 rp = fbufalloc();
401 do{
402 n = t->file->b.nc-(t->org+t->fr.nchars);
403 if(n == 0)
404 break;
405 if(n > 2000) /* educated guess at reasonable amount */
406 n = 2000;
407 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
408 /*
409 * it's expensive to frinsert more than we need, so
410 * count newlines.
411 */
412 nl = t->fr.maxlines-t->fr.nlines;
413 m = 0;
414 for(i=0; i<n; ){
415 if(rp[i++] == '\n'){
416 m++;
417 if(m >= nl)
418 break;
421 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
422 }while(t->fr.lastlinefull == FALSE);
423 fbuffree(rp);
426 void
427 textdelete(Text *t, uint q0, uint q1, int tofile)
429 uint n, p0, p1;
430 int i, c;
431 Text *u;
433 if(tofile && t->ncache != 0)
434 error("text.delete");
435 n = q1-q0;
436 if(n == 0)
437 return;
438 if(tofile){
439 filedelete(t->file, q0, q1);
440 if(t->what == Body){
441 t->w->dirty = TRUE;
442 t->w->utflastqid = -1;
444 if(t->file->ntext > 1)
445 for(i=0; i<t->file->ntext; i++){
446 u = t->file->text[i];
447 if(u != t){
448 u->w->dirty = TRUE; /* always a body */
449 textdelete(u, q0, q1, FALSE);
450 textsetselect(u, u->q0, u->q1);
451 textscrdraw(u);
455 if(q0 < t->q0)
456 t->q0 -= min(n, t->q0-q0);
457 if(q0 < t->q1)
458 t->q1 -= min(n, t->q1-q0);
459 if(q1 <= t->org)
460 t->org -= n;
461 else if(q0 < t->org+t->fr.nchars){
462 p1 = q1 - t->org;
463 if(p1 > t->fr.nchars)
464 p1 = t->fr.nchars;
465 if(q0 < t->org){
466 t->org = q0;
467 p0 = 0;
468 }else
469 p0 = q0 - t->org;
470 frdelete(&t->fr, p0, p1);
471 textfill(t);
473 if(t->w){
474 c = 'd';
475 if(t->what == Body)
476 c = 'D';
477 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
481 void
482 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
484 *p0 = min(q0, t->file->b.nc);
485 *p1 = min(q1, t->file->b.nc);
488 Rune
489 textreadc(Text *t, uint q)
491 Rune r;
493 if(t->cq0<=q && q<t->cq0+t->ncache)
494 r = t->cache[q-t->cq0];
495 else
496 bufread(&t->file->b, q, &r, 1);
497 return r;
500 int
501 textbswidth(Text *t, Rune c)
503 uint q, eq;
504 Rune r;
505 int skipping;
507 /* there is known to be at least one character to erase */
508 if(c == 0x08) /* ^H: erase character */
509 return 1;
510 q = t->q0;
511 skipping = TRUE;
512 while(q > 0){
513 r = textreadc(t, q-1);
514 if(r == '\n'){ /* eat at most one more character */
515 if(q == t->q0) /* eat the newline */
516 --q;
517 break;
519 if(c == 0x17){
520 eq = isalnum(r);
521 if(eq && skipping) /* found one; stop skipping */
522 skipping = FALSE;
523 else if(!eq && !skipping)
524 break;
526 --q;
528 return t->q0-q;
531 int
532 textfilewidth(Text *t, uint q0, int oneelement)
534 uint q;
535 Rune r;
537 q = q0;
538 while(q > 0){
539 r = textreadc(t, q-1);
540 if(r <= ' ')
541 break;
542 if(oneelement && r=='/')
543 break;
544 --q;
546 return q0-q;
549 Rune*
550 textcomplete(Text *t)
552 int i, nstr, npath;
553 uint q;
554 Rune tmp[200];
555 Rune *str, *path;
556 Rune *rp;
557 Completion *c;
558 char *s, *dirs;
559 Runestr dir;
561 /* control-f: filename completion; works back to white space or / */
562 if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
563 return nil;
564 nstr = textfilewidth(t, t->q0, TRUE);
565 str = runemalloc(nstr);
566 npath = textfilewidth(t, t->q0-nstr, FALSE);
567 path = runemalloc(npath);
569 c = nil;
570 rp = nil;
571 dirs = nil;
573 q = t->q0-nstr;
574 for(i=0; i<nstr; i++)
575 str[i] = textreadc(t, q++);
576 q = t->q0-nstr-npath;
577 for(i=0; i<npath; i++)
578 path[i] = textreadc(t, q++);
579 /* is path rooted? if not, we need to make it relative to window path */
580 if(npath>0 && path[0]=='/')
581 dir = runestr(path, npath);
582 else{
583 dir = dirname(t, nil, 0);
584 if(dir.nr + 1 + npath > nelem(tmp)){
585 free(dir.r);
586 goto Return;
588 if(dir.nr == 0){
589 dir.nr = 1;
590 dir.r = runestrdup(Ldot);
592 runemove(tmp, dir.r, dir.nr);
593 tmp[dir.nr] = '/';
594 runemove(tmp+dir.nr+1, path, npath);
595 free(dir.r);
596 dir.r = tmp;
597 dir.nr += 1+npath;
598 dir = cleanrname(dir);
601 s = smprint("%.*S", nstr, str);
602 dirs = smprint("%.*S", dir.nr, dir.r);
603 c = complete(dirs, s);
604 free(s);
605 if(c == nil){
606 warning(nil, "error attempting completion: %r\n");
607 goto Return;
610 if(!c->advance){
611 warning(nil, "%.*S%s%.*S*%s\n",
612 dir.nr, dir.r,
613 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
614 nstr, str,
615 c->nmatch ? "" : ": no matches in:");
616 for(i=0; i<c->nfile; i++)
617 warning(nil, " %s\n", c->filename[i]);
620 if(c->advance)
621 rp = runesmprint("%s", c->string);
622 else
623 rp = nil;
624 Return:
625 freecompletion(c);
626 free(dirs);
627 free(str);
628 free(path);
629 return rp;
632 void
633 texttype(Text *t, Rune r)
635 uint q0, q1;
636 int nnb, nb, n, i;
637 int nr;
638 Rune *rp;
639 Text *u;
641 if(t->what!=Body && r=='\n')
642 return;
643 nr = 1;
644 rp = &r;
645 switch(r){
646 case Kleft:
647 if(t->q0 > 0){
648 wincommit(t->w, t);
649 textshow(t, t->q0-1, t->q0-1, TRUE);
651 return;
652 case Kright:
653 if(t->q1 < t->file->b.nc){
654 wincommit(t->w, t);
655 textshow(t, t->q1+1, t->q1+1, TRUE);
657 return;
658 case Kdown:
659 n = t->fr.maxlines/3;
660 goto case_Down;
661 case Kscrollonedown:
662 n = mousescrollsize(t->fr.maxlines);
663 if(n <= 0)
664 n = 1;
665 goto case_Down;
666 case Kpgdown:
667 n = 2*t->fr.maxlines/3;
668 case_Down:
669 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
670 textsetorigin(t, q0, TRUE);
671 return;
672 case Kup:
673 n = t->fr.maxlines/3;
674 goto case_Up;
675 case Kscrolloneup:
676 n = mousescrollsize(t->fr.maxlines);
677 goto case_Up;
678 case Kpgup:
679 n = 2*t->fr.maxlines/3;
680 case_Up:
681 q0 = textbacknl(t, t->org, n);
682 textsetorigin(t, q0, TRUE);
683 return;
684 case Khome:
685 textshow(t, 0, 0, FALSE);
686 return;
687 case Kend:
688 if(t->w)
689 wincommit(t->w, t);
690 else
691 textcommit(t, TRUE);
692 textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
693 return;
695 if(t->what == Body){
696 seq++;
697 filemark(t->file);
699 if(t->q1 > t->q0){
700 if(t->ncache != 0)
701 error("text.type");
702 cut(t, t, nil, TRUE, TRUE, nil, 0);
703 t->eq0 = ~0;
705 textshow(t, t->q0, t->q0, 1);
706 switch(r){
707 case 0x06: /* ^F: complete */
708 case Kins:
709 rp = textcomplete(t);
710 if(rp == nil)
711 return;
712 nr = runestrlen(rp);
713 break; /* fall through to normal insertion case */
714 case 0x1B:
715 if(t->eq0 != ~0)
716 textsetselect(t, t->eq0, t->q0);
717 if(t->ncache > 0){
718 if(t->w != nil)
719 wincommit(t->w, t);
720 else
721 textcommit(t, TRUE);
723 return;
724 case 0x08: /* ^H: erase character */
725 case 0x15: /* ^U: erase line */
726 case 0x17: /* ^W: erase word */
727 if(t->q0 == 0) /* nothing to erase */
728 return;
729 nnb = textbswidth(t, r);
730 q1 = t->q0;
731 q0 = q1-nnb;
732 /* if selection is at beginning of window, avoid deleting invisible text */
733 if(q0 < t->org){
734 q0 = t->org;
735 nnb = q1-q0;
737 if(nnb <= 0)
738 return;
739 for(i=0; i<t->file->ntext; i++){
740 u = t->file->text[i];
741 u->nofill = TRUE;
742 nb = nnb;
743 n = u->ncache;
744 if(n > 0){
745 if(q1 != u->cq0+n)
746 error("text.type backspace");
747 if(n > nb)
748 n = nb;
749 u->ncache -= n;
750 textdelete(u, q1-n, q1, FALSE);
751 nb -= n;
753 if(u->eq0==q1 || u->eq0==~0)
754 u->eq0 = q0;
755 if(nb && u==t)
756 textdelete(u, q0, q0+nb, TRUE);
757 if(u != t)
758 textsetselect(u, u->q0, u->q1);
759 else
760 textsetselect(t, q0, q0);
761 u->nofill = FALSE;
763 for(i=0; i<t->file->ntext; i++)
764 textfill(t->file->text[i]);
765 return;
766 case '\n':
767 if(t->w->autoindent){
768 /* find beginning of previous line using backspace code */
769 nnb = textbswidth(t, 0x15); /* ^U case */
770 rp = runemalloc(nnb + 1);
771 nr = 0;
772 rp[nr++] = r;
773 for(i=0; i<nnb; i++){
774 r = textreadc(t, t->q0-nnb+i);
775 if(r != ' ' && r != '\t')
776 break;
777 rp[nr++] = r;
780 break; /* fall through to normal code */
782 /* otherwise ordinary character; just insert, typically in caches of all texts */
783 for(i=0; i<t->file->ntext; i++){
784 u = t->file->text[i];
785 if(u->eq0 == ~0)
786 u->eq0 = t->q0;
787 if(u->ncache == 0)
788 u->cq0 = t->q0;
789 else if(t->q0 != u->cq0+u->ncache)
790 error("text.type cq1");
791 textinsert(u, t->q0, rp, nr, FALSE);
792 if(u != t)
793 textsetselect(u, u->q0, u->q1);
794 if(u->ncache+nr > u->ncachealloc){
795 u->ncachealloc += 10 + nr;
796 u->cache = runerealloc(u->cache, u->ncachealloc);
798 runemove(u->cache+u->ncache, rp, nr);
799 u->ncache += nr;
801 if(rp != &r)
802 free(rp);
803 textsetselect(t, t->q0+nr, t->q0+nr);
804 if(r=='\n' && t->w!=nil)
805 wincommit(t->w, t);
808 void
809 textcommit(Text *t, int tofile)
811 if(t->ncache == 0)
812 return;
813 if(tofile)
814 fileinsert(t->file, t->cq0, t->cache, t->ncache);
815 if(t->what == Body){
816 t->w->dirty = TRUE;
817 t->w->utflastqid = -1;
819 t->ncache = 0;
822 static Text *clicktext;
823 static uint clickmsec;
824 static Text *selecttext;
825 static uint selectq;
827 /*
828 * called from frame library
829 */
830 void
831 framescroll(Frame *f, int dl)
833 if(f != &selecttext->fr)
834 error("frameselect not right frame");
835 textframescroll(selecttext, dl);
838 void
839 textframescroll(Text *t, int dl)
841 uint q0;
843 if(dl == 0){
844 scrsleep(100);
845 return;
847 if(dl < 0){
848 q0 = textbacknl(t, t->org, -dl);
849 if(selectq > t->org+t->fr.p0)
850 textsetselect(t, t->org+t->fr.p0, selectq);
851 else
852 textsetselect(t, selectq, t->org+t->fr.p0);
853 }else{
854 if(t->org+t->fr.nchars == t->file->b.nc)
855 return;
856 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
857 if(selectq > t->org+t->fr.p1)
858 textsetselect(t, t->org+t->fr.p1, selectq);
859 else
860 textsetselect(t, selectq, t->org+t->fr.p1);
862 textsetorigin(t, q0, TRUE);
866 void
867 textselect(Text *t)
869 uint q0, q1;
870 int b, x, y;
871 int state, op;
873 selecttext = t;
874 /*
875 * To have double-clicking and chording, we double-click
876 * immediately if it might make sense.
877 */
878 b = mouse->buttons;
879 q0 = t->q0;
880 q1 = t->q1;
881 selectq = t->org+frcharofpt(&t->fr, mouse->xy);
882 if(clicktext==t && mouse->msec-clickmsec<500)
883 if(q0==q1 && selectq==q0){
884 textdoubleclick(t, &q0, &q1);
885 textsetselect(t, q0, q1);
886 flushimage(display, 1);
887 x = mouse->xy.x;
888 y = mouse->xy.y;
889 /* stay here until something interesting happens */
890 do
891 readmouse(mousectl);
892 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
893 mouse->xy.x = x; /* in case we're calling frselect */
894 mouse->xy.y = y;
895 q0 = t->q0; /* may have changed */
896 q1 = t->q1;
897 selectq = q0;
899 if(mouse->buttons == b){
900 t->fr.scroll = framescroll;
901 frselect(&t->fr, mousectl);
902 /* horrible botch: while asleep, may have lost selection altogether */
903 if(selectq > t->file->b.nc)
904 selectq = t->org + t->fr.p0;
905 t->fr.scroll = 0;
906 if(selectq < t->org)
907 q0 = selectq;
908 else
909 q0 = t->org + t->fr.p0;
910 if(selectq > t->org+t->fr.nchars)
911 q1 = selectq;
912 else
913 q1 = t->org+t->fr.p1;
915 if(q0 == q1){
916 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
917 textdoubleclick(t, &q0, &q1);
918 clicktext = nil;
919 }else{
920 clicktext = t;
921 clickmsec = mouse->msec;
923 }else
924 clicktext = nil;
925 textsetselect(t, q0, q1);
926 flushimage(display, 1);
927 state = op = 0; /* undo when possible; +1 for cut, -1 for paste */
928 while(mouse->buttons){
929 mouse->msec = 0;
930 b = mouse->buttons;
931 if(b & 6){
932 if(state==0 && op==0 && t->what==Body){
933 seq++;
934 filemark(t->w->body.file);
936 if(b & 2){
937 if(state==-1 && t->what==Body){
938 winundo(t->w, TRUE);
939 textsetselect(t, q0, t->q0);
940 state = 0;
941 }else if(state != 1 && op != -1){
942 cut(t, t, nil, TRUE, TRUE, nil, 0);
943 op = state = 1;
945 }else{
946 if(state==1 && t->what==Body){
947 winundo(t->w, TRUE);
948 textsetselect(t, q0, t->q1);
949 state = 0;
950 }else if(state != -1 && op != 1){
951 paste(t, t, nil, TRUE, FALSE, nil, 0);
952 op = state = -1;
955 textscrdraw(t);
956 clearmouse();
958 flushimage(display, 1);
959 while(mouse->buttons == b)
960 readmouse(mousectl);
961 clicktext = nil;
965 void
966 textshow(Text *t, uint q0, uint q1, int doselect)
968 int qe;
969 int nl;
970 uint q;
972 if(t->what != Body){
973 if(doselect)
974 textsetselect(t, q0, q1);
975 return;
977 if(t->w!=nil && t->fr.maxlines==0)
978 colgrow(t->col, t->w, 1);
979 if(doselect)
980 textsetselect(t, q0, q1);
981 qe = t->org+t->fr.nchars;
982 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache)))
983 textscrdraw(t);
984 else{
985 if(t->w->nopen[QWevent] > 0)
986 nl = 3*t->fr.maxlines/4;
987 else
988 nl = t->fr.maxlines/4;
989 q = textbacknl(t, q0, nl);
990 /* avoid going backwards if trying to go forwards - long lines! */
991 if(!(q0>t->org && q<t->org))
992 textsetorigin(t, q, TRUE);
993 while(q0 > t->org+t->fr.nchars)
994 textsetorigin(t, t->org+1, FALSE);
998 static
999 int
1000 region(int a, int b)
1002 if(a < b)
1003 return -1;
1004 if(a == b)
1005 return 0;
1006 return 1;
1009 void
1010 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1012 if(p1<=f->p0 || p0>=f->p1){
1013 /* no overlap */
1014 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1015 return;
1017 if(p0>=f->p0 && p1<=f->p1){
1018 /* entirely inside */
1019 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1020 return;
1023 /* they now are known to overlap */
1025 /* before selection */
1026 if(p0 < f->p0){
1027 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1028 p0 = f->p0;
1029 pt0 = frptofchar(f, p0);
1031 /* after selection */
1032 if(p1 > f->p1){
1033 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1034 p1 = f->p1;
1036 /* inside selection */
1037 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1040 void
1041 textsetselect(Text *t, uint q0, uint q1)
1043 int p0, p1;
1045 /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1046 t->q0 = q0;
1047 t->q1 = q1;
1048 /* compute desired p0,p1 from q0,q1 */
1049 p0 = q0-t->org;
1050 p1 = q1-t->org;
1051 if(p0 < 0)
1052 p0 = 0;
1053 if(p1 < 0)
1054 p1 = 0;
1055 if(p0 > t->fr.nchars)
1056 p0 = t->fr.nchars;
1057 if(p1 > t->fr.nchars)
1058 p1 = t->fr.nchars;
1059 if(p0==t->fr.p0 && p1==t->fr.p1)
1060 return;
1061 /* screen disagrees with desired selection */
1062 if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1063 /* no overlap or too easy to bother trying */
1064 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1065 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1066 goto Return;
1068 /* overlap; avoid unnecessary painting */
1069 if(p0 < t->fr.p0){
1070 /* extend selection backwards */
1071 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1072 }else if(p0 > t->fr.p0){
1073 /* trim first part of selection */
1074 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1076 if(p1 > t->fr.p1){
1077 /* extend selection forwards */
1078 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1079 }else if(p1 < t->fr.p1){
1080 /* trim last part of selection */
1081 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1084 Return:
1085 t->fr.p0 = p0;
1086 t->fr.p1 = p1;
1090 * Release the button in less than DELAY ms and it's considered a null selection
1091 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1093 enum {
1094 DELAY = 2,
1095 MINMOVE = 4,
1098 uint
1099 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1101 uint p0, p1, q, tmp;
1102 ulong msec;
1103 Point mp, pt0, pt1, qt;
1104 int reg, b;
1106 mp = mc->m.xy;
1107 b = mc->m.buttons;
1108 msec = mc->m.msec;
1110 /* remove tick */
1111 if(f->p0 == f->p1)
1112 frtick(f, frptofchar(f, f->p0), 0);
1113 p0 = p1 = frcharofpt(f, mp);
1114 pt0 = frptofchar(f, p0);
1115 pt1 = frptofchar(f, p1);
1116 reg = 0;
1117 frtick(f, pt0, 1);
1118 do{
1119 q = frcharofpt(f, mc->m.xy);
1120 if(p1 != q){
1121 if(p0 == p1)
1122 frtick(f, pt0, 0);
1123 if(reg != region(q, p0)){ /* crossed starting point; reset */
1124 if(reg > 0)
1125 selrestore(f, pt0, p0, p1);
1126 else if(reg < 0)
1127 selrestore(f, pt1, p1, p0);
1128 p1 = p0;
1129 pt1 = pt0;
1130 reg = region(q, p0);
1131 if(reg == 0)
1132 frdrawsel0(f, pt0, p0, p1, col, display->white);
1134 qt = frptofchar(f, q);
1135 if(reg > 0){
1136 if(q > p1)
1137 frdrawsel0(f, pt1, p1, q, col, display->white);
1139 else if(q < p1)
1140 selrestore(f, qt, q, p1);
1141 }else if(reg < 0){
1142 if(q > p1)
1143 selrestore(f, pt1, p1, q);
1144 else
1145 frdrawsel0(f, qt, q, p1, col, display->white);
1147 p1 = q;
1148 pt1 = qt;
1150 if(p0 == p1)
1151 frtick(f, pt0, 1);
1152 flushimage(f->display, 1);
1153 readmouse(mc);
1154 }while(mc->m.buttons == b);
1155 if(mc->m.msec-msec < DELAY && p0!=p1
1156 && abs(mp.x-mc->m.xy.x)<MINMOVE
1157 && abs(mp.y-mc->m.xy.y)<MINMOVE) {
1158 if(reg > 0)
1159 selrestore(f, pt0, p0, p1);
1160 else if(reg < 0)
1161 selrestore(f, pt1, p1, p0);
1162 p1 = p0;
1164 if(p1 < p0){
1165 tmp = p0;
1166 p0 = p1;
1167 p1 = tmp;
1169 pt0 = frptofchar(f, p0);
1170 if(p0 == p1)
1171 frtick(f, pt0, 0);
1172 selrestore(f, pt0, p0, p1);
1173 /* restore tick */
1174 if(f->p0 == f->p1)
1175 frtick(f, frptofchar(f, f->p0), 1);
1176 flushimage(f->display, 1);
1177 *p1p = p1;
1178 return p0;
1181 int
1182 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1184 uint p0, p1;
1185 int buts;
1187 p0 = xselect(&t->fr, mousectl, high, &p1);
1188 buts = mousectl->m.buttons;
1189 if((buts & mask) == 0){
1190 *q0 = p0+t->org;
1191 *q1 = p1+t->org;
1194 while(mousectl->m.buttons)
1195 readmouse(mousectl);
1196 return buts;
1199 int
1200 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1202 int buts;
1204 *tp = nil;
1205 buts = textselect23(t, q0, q1, but2col, 4);
1206 if(buts & 4)
1207 return 0;
1208 if(buts & 1){ /* pick up argument */
1209 *tp = argtext;
1210 return 1;
1212 return 1;
1215 int
1216 textselect3(Text *t, uint *q0, uint *q1)
1218 int h;
1220 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1221 return h;
1224 static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1225 static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1226 static Rune left2[] = { '\n', 0 };
1227 static Rune left3[] = { '\'', '"', '`', 0 };
1229 static
1230 Rune *left[] = {
1231 left1,
1232 left2,
1233 left3,
1234 nil
1236 static
1237 Rune *right[] = {
1238 right1,
1239 left2,
1240 left3,
1241 nil
1244 void
1245 textdoubleclick(Text *t, uint *q0, uint *q1)
1247 int c, i;
1248 Rune *r, *l, *p;
1249 uint q;
1251 for(i=0; left[i]!=nil; i++){
1252 q = *q0;
1253 l = left[i];
1254 r = right[i];
1255 /* try matching character to left, looking right */
1256 if(q == 0)
1257 c = '\n';
1258 else
1259 c = textreadc(t, q-1);
1260 p = runestrchr(l, c);
1261 if(p != nil){
1262 if(textclickmatch(t, c, r[p-l], 1, &q))
1263 *q1 = q-(c!='\n');
1264 return;
1266 /* try matching character to right, looking left */
1267 if(q == t->file->b.nc)
1268 c = '\n';
1269 else
1270 c = textreadc(t, q);
1271 p = runestrchr(r, c);
1272 if(p != nil){
1273 if(textclickmatch(t, c, l[p-r], -1, &q)){
1274 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1275 *q0 = q;
1276 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1277 (*q0)++;
1279 return;
1282 /* try filling out word to right */
1283 while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1284 (*q1)++;
1285 /* try filling out word to left */
1286 while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1287 (*q0)--;
1290 int
1291 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1293 Rune c;
1294 int nest;
1296 nest = 1;
1297 for(;;){
1298 if(dir > 0){
1299 if(*q == t->file->b.nc)
1300 break;
1301 c = textreadc(t, *q);
1302 (*q)++;
1303 }else{
1304 if(*q == 0)
1305 break;
1306 (*q)--;
1307 c = textreadc(t, *q);
1309 if(c == cr){
1310 if(--nest==0)
1311 return 1;
1312 }else if(c == cl)
1313 nest++;
1315 return cl=='\n' && nest==1;
1318 uint
1319 textbacknl(Text *t, uint p, uint n)
1321 int i, j;
1323 /* look for start of this line if n==0 */
1324 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1325 n = 1;
1326 i = n;
1327 while(i-->0 && p>0){
1328 --p; /* it's at a newline now; back over it */
1329 if(p == 0)
1330 break;
1331 /* at 128 chars, call it a line anyway */
1332 for(j=128; --j>0 && p>0; p--)
1333 if(textreadc(t, p-1)=='\n')
1334 break;
1336 return p;
1339 void
1340 textsetorigin(Text *t, uint org, int exact)
1342 int i, a, fixup;
1343 Rune *r;
1344 uint n;
1346 if(org>0 && !exact){
1347 /* org is an estimate of the char posn; find a newline */
1348 /* don't try harder than 256 chars */
1349 for(i=0; i<256 && org<t->file->b.nc; i++){
1350 if(textreadc(t, org) == '\n'){
1351 org++;
1352 break;
1354 org++;
1357 a = org-t->org;
1358 fixup = 0;
1359 if(a>=0 && a<t->fr.nchars){
1360 frdelete(&t->fr, 0, a);
1361 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1363 else if(a<0 && -a<t->fr.nchars){
1364 n = t->org - org;
1365 r = runemalloc(n);
1366 bufread(&t->file->b, org, r, n);
1367 frinsert(&t->fr, r, r+n, 0);
1368 free(r);
1369 }else
1370 frdelete(&t->fr, 0, t->fr.nchars);
1371 t->org = org;
1372 textfill(t);
1373 textscrdraw(t);
1374 textsetselect(t, t->q0, t->q1);
1375 if(fixup && t->fr.p1 > t->fr.p0)
1376 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1379 void
1380 textreset(Text *t)
1382 t->file->seq = 0;
1383 t->eq0 = ~0;
1384 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1385 textsetselect(t, t->org, t->org);
1386 frdelete(&t->fr, 0, t->fr.nchars);
1387 t->org = 0;
1388 t->q0 = 0;
1389 t->q1 = 0;
1390 filereset(t->file);
1391 bufreset(&t->file->b);