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 if(!t->fr.noredraw)
50 draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
51 /* use no wider than 3-space tabs in a directory */
52 maxt = maxtab;
53 if(t->what == Body){
54 if(t->w->isdir)
55 maxt = min(TABDIR, maxtab);
56 else
57 maxt = t->tabstop;
58 }
59 t->fr.maxtab = maxt*stringwidth(f, "0");
60 if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
61 if(t->fr.maxlines > 0){
62 textreset(t);
63 textcolumnate(t, t->w->dlp, t->w->ndl);
64 textshow(t, 0, 0, 1);
65 }
66 }else{
67 textfill(t);
68 textsetselect(t, t->q0, t->q1);
69 }
70 }
72 int
73 textresize(Text *t, Rectangle r, int keepextra)
74 {
75 int odx;
77 if(Dy(r) <= 0)
78 r.max.y = r.min.y;
79 else if(!keepextra)
80 r.max.y -= Dy(r)%t->fr.font->height;
81 odx = Dx(t->all);
82 t->all = r;
83 t->scrollr = r;
84 t->scrollr.max.x = r.min.x+Scrollwid;
85 t->lastsr = nullrect;
86 r.min.x += Scrollwid+Scrollgap;
87 frclear(&t->fr, 0);
88 textredraw(t, r, t->fr.font, t->fr.b, odx);
89 if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){
90 /* draw background in bottom fringe of window */
91 r.min.x -= Scrollgap;
92 r.min.y = t->fr.r.max.y;
93 r.max.y = t->all.max.y;
94 draw(screen, r, t->fr.cols[BACK], nil, ZP);
95 }
96 return t->all.max.y;
97 }
99 void
100 textclose(Text *t)
102 free(t->cache);
103 frclear(&t->fr, 1);
104 filedeltext(t->file, t);
105 t->file = nil;
106 rfclose(t->reffont);
107 if(argtext == t)
108 argtext = nil;
109 if(typetext == t)
110 typetext = nil;
111 if(seltext == t)
112 seltext = nil;
113 if(mousetext == t)
114 mousetext = nil;
115 if(barttext == t)
116 barttext = nil;
119 int
120 dircmp(const void *a, const void *b)
122 Dirlist *da, *db;
123 int i, n;
125 da = *(Dirlist**)a;
126 db = *(Dirlist**)b;
127 n = min(da->nr, db->nr);
128 i = memcmp(da->r, db->r, n*sizeof(Rune));
129 if(i)
130 return i;
131 return da->nr - db->nr;
134 void
135 textcolumnate(Text *t, Dirlist **dlp, int ndl)
137 int i, j, w, colw, mint, maxt, ncol, nrow;
138 Dirlist *dl;
139 uint q1;
140 static Rune Lnl[] = { '\n', 0 };
141 static Rune Ltab[] = { '\t', 0 };
143 if(t->file->ntext > 1)
144 return;
145 mint = stringwidth(t->fr.font, "0");
146 /* go for narrower tabs if set more than 3 wide */
147 t->fr.maxtab = min(maxtab, TABDIR)*mint;
148 maxt = t->fr.maxtab;
149 colw = 0;
150 for(i=0; i<ndl; i++){
151 dl = dlp[i];
152 w = dl->wid;
153 if(maxt-w%maxt < mint || w%maxt==0)
154 w += mint;
155 if(w % maxt)
156 w += maxt-(w%maxt);
157 if(w > colw)
158 colw = w;
160 if(colw == 0)
161 ncol = 1;
162 else
163 ncol = max(1, Dx(t->fr.r)/colw);
164 nrow = (ndl+ncol-1)/ncol;
166 q1 = 0;
167 for(i=0; i<nrow; i++){
168 for(j=i; j<ndl; j+=nrow){
169 dl = dlp[j];
170 fileinsert(t->file, q1, dl->r, dl->nr);
171 q1 += dl->nr;
172 if(j+nrow >= ndl)
173 break;
174 w = dl->wid;
175 if(maxt-w%maxt < mint){
176 fileinsert(t->file, q1, Ltab, 1);
177 q1++;
178 w += mint;
180 do{
181 fileinsert(t->file, q1, Ltab, 1);
182 q1++;
183 w += maxt-(w%maxt);
184 }while(w < colw);
186 fileinsert(t->file, q1, Lnl, 1);
187 q1++;
191 int
192 textload(Text *t, uint q0, char *file, int setqid)
194 Rune *rp;
195 Dirlist *dl, **dlp;
196 int fd, i, j, n, ndl, nulls;
197 uint q, q1;
198 Dir *d, *dbuf;
199 char *tmp;
200 Text *u;
202 if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body)
203 error("text.load");
204 if(t->w->isdir && t->file->nname==0){
205 warning(nil, "empty directory name");
206 return -1;
208 if(ismtpt(file)){
209 warning(nil, "will not open self mount point %s\n", file);
210 return -1;
212 fd = open(file, OREAD);
213 if(fd < 0){
214 warning(nil, "can't open %s: %r\n", file);
215 return -1;
217 d = dirfstat(fd);
218 if(d == nil){
219 warning(nil, "can't fstat %s: %r\n", file);
220 goto Rescue;
222 nulls = FALSE;
223 if(d->qid.type & QTDIR){
224 /* this is checked in get() but it's possible the file changed underfoot */
225 if(t->file->ntext > 1){
226 warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
227 goto Rescue;
229 t->w->isdir = TRUE;
230 t->w->filemenu = FALSE;
231 if(t->file->name[t->file->nname-1] != '/'){
232 rp = runemalloc(t->file->nname+1);
233 runemove(rp, t->file->name, t->file->nname);
234 rp[t->file->nname] = '/';
235 winsetname(t->w, rp, t->file->nname+1);
236 free(rp);
238 dlp = nil;
239 ndl = 0;
240 dbuf = nil;
241 while((n=dirread(fd, &dbuf)) > 0){
242 for(i=0; i<n; i++){
243 dl = emalloc(sizeof(Dirlist));
244 j = strlen(dbuf[i].name);
245 tmp = emalloc(j+1+1);
246 memmove(tmp, dbuf[i].name, j);
247 if(dbuf[i].qid.type & QTDIR)
248 tmp[j++] = '/';
249 tmp[j] = '\0';
250 dl->r = bytetorune(tmp, &dl->nr);
251 dl->wid = stringwidth(t->fr.font, tmp);
252 free(tmp);
253 ndl++;
254 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
255 dlp[ndl-1] = dl;
257 free(dbuf);
259 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
260 t->w->dlp = dlp;
261 t->w->ndl = ndl;
262 textcolumnate(t, dlp, ndl);
263 q1 = t->file->b.nc;
264 }else{
265 t->w->isdir = FALSE;
266 t->w->filemenu = TRUE;
267 q1 = q0 + fileload(t->file, q0, fd, &nulls);
269 if(setqid){
270 t->file->dev = d->dev;
271 t->file->mtime = d->mtime;
272 t->file->qidpath = d->qid.path;
274 close(fd);
275 rp = fbufalloc();
276 for(q=q0; q<q1; q+=n){
277 n = q1-q;
278 if(n > RBUFSIZE)
279 n = RBUFSIZE;
280 bufread(&t->file->b, q, rp, n);
281 if(q < t->org)
282 t->org += n;
283 else if(q <= t->org+t->fr.nchars)
284 frinsert(&t->fr, rp, rp+n, q-t->org);
285 if(t->fr.lastlinefull)
286 break;
288 fbuffree(rp);
289 for(i=0; i<t->file->ntext; i++){
290 u = t->file->text[i];
291 if(u != t){
292 if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */
293 u->org = 0;
294 textresize(u, u->all, TRUE);
295 textbacknl(u, u->org, 0); /* go to beginning of line */
297 textsetselect(u, q0, q0);
299 if(nulls)
300 warning(nil, "%s: NUL bytes elided\n", file);
301 free(d);
302 return q1-q0;
304 Rescue:
305 close(fd);
306 return -1;
309 uint
310 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
312 Rune *bp, *tp, *up;
313 int i, initial;
315 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
316 Err:
317 textinsert(t, q0, r, n, tofile);
318 *nrp = n;
319 return q0;
321 bp = r;
322 for(i=0; i<n; i++)
323 if(*bp++ == '\b'){
324 --bp;
325 initial = 0;
326 tp = runemalloc(n);
327 runemove(tp, r, i);
328 up = tp+i;
329 for(; i<n; i++){
330 *up = *bp++;
331 if(*up == '\b')
332 if(up == tp)
333 initial++;
334 else
335 --up;
336 else
337 up++;
339 if(initial){
340 if(initial > q0)
341 initial = q0;
342 q0 -= initial;
343 textdelete(t, q0, q0+initial, tofile);
345 n = up-tp;
346 textinsert(t, q0, tp, n, tofile);
347 free(tp);
348 *nrp = n;
349 return q0;
351 goto Err;
354 void
355 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
357 int c, i;
358 Text *u;
360 if(tofile && t->ncache != 0)
361 error("text.insert");
362 if(n == 0)
363 return;
364 if(tofile){
365 fileinsert(t->file, q0, r, n);
366 if(t->what == Body){
367 t->w->dirty = TRUE;
368 t->w->utflastqid = -1;
370 if(t->file->ntext > 1)
371 for(i=0; i<t->file->ntext; i++){
372 u = t->file->text[i];
373 if(u != t){
374 u->w->dirty = TRUE; /* always a body */
375 textinsert(u, q0, r, n, FALSE);
376 textsetselect(u, u->q0, u->q1);
377 textscrdraw(u);
382 if(q0 < t->iq1)
383 t->iq1 += n;
384 if(q0 < t->q1)
385 t->q1 += n;
386 if(q0 < t->q0)
387 t->q0 += n;
388 if(q0 < t->org)
389 t->org += n;
390 else if(q0 <= t->org+t->fr.nchars)
391 frinsert(&t->fr, r, r+n, q0-t->org);
392 if(t->w){
393 c = 'i';
394 if(t->what == Body)
395 c = 'I';
396 if(n <= EVENTSIZE)
397 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
398 else
399 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
403 void
404 typecommit(Text *t)
406 if(t->w != nil)
407 wincommit(t->w, t);
408 else
409 textcommit(t, TRUE);
412 void
413 textfill(Text *t)
415 Rune *rp;
416 int i, n, m, nl;
418 if(t->fr.lastlinefull || t->nofill)
419 return;
420 if(t->ncache > 0)
421 typecommit(t);
422 rp = fbufalloc();
423 do{
424 n = t->file->b.nc-(t->org+t->fr.nchars);
425 if(n == 0)
426 break;
427 if(n > 2000) /* educated guess at reasonable amount */
428 n = 2000;
429 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
430 /*
431 * it's expensive to frinsert more than we need, so
432 * count newlines.
433 */
434 nl = t->fr.maxlines-t->fr.nlines;
435 m = 0;
436 for(i=0; i<n; ){
437 if(rp[i++] == '\n'){
438 m++;
439 if(m >= nl)
440 break;
443 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
444 }while(t->fr.lastlinefull == FALSE);
445 fbuffree(rp);
448 void
449 textdelete(Text *t, uint q0, uint q1, int tofile)
451 uint n, p0, p1;
452 int i, c;
453 Text *u;
455 if(tofile && t->ncache != 0)
456 error("text.delete");
457 n = q1-q0;
458 if(n == 0)
459 return;
460 if(tofile){
461 filedelete(t->file, q0, q1);
462 if(t->what == Body){
463 t->w->dirty = TRUE;
464 t->w->utflastqid = -1;
466 if(t->file->ntext > 1)
467 for(i=0; i<t->file->ntext; i++){
468 u = t->file->text[i];
469 if(u != t){
470 u->w->dirty = TRUE; /* always a body */
471 textdelete(u, q0, q1, FALSE);
472 textsetselect(u, u->q0, u->q1);
473 textscrdraw(u);
477 if(q0 < t->iq1)
478 t->iq1 -= min(n, t->iq1-q0);
479 if(q0 < t->q0)
480 t->q0 -= min(n, t->q0-q0);
481 if(q0 < t->q1)
482 t->q1 -= min(n, t->q1-q0);
483 if(q1 <= t->org)
484 t->org -= n;
485 else if(q0 < t->org+t->fr.nchars){
486 p1 = q1 - t->org;
487 if(p1 > t->fr.nchars)
488 p1 = t->fr.nchars;
489 if(q0 < t->org){
490 t->org = q0;
491 p0 = 0;
492 }else
493 p0 = q0 - t->org;
494 frdelete(&t->fr, p0, p1);
495 textfill(t);
497 if(t->w){
498 c = 'd';
499 if(t->what == Body)
500 c = 'D';
501 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
505 void
506 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
508 *p0 = min(q0, t->file->b.nc);
509 *p1 = min(q1, t->file->b.nc);
512 Rune
513 textreadc(Text *t, uint q)
515 Rune r;
517 if(t->cq0<=q && q<t->cq0+t->ncache)
518 r = t->cache[q-t->cq0];
519 else
520 bufread(&t->file->b, q, &r, 1);
521 return r;
524 int
525 textbswidth(Text *t, Rune c)
527 uint q, eq;
528 Rune r;
529 int skipping;
531 /* there is known to be at least one character to erase */
532 if(c == 0x08) /* ^H: erase character */
533 return 1;
534 q = t->q0;
535 skipping = TRUE;
536 while(q > 0){
537 r = textreadc(t, q-1);
538 if(r == '\n'){ /* eat at most one more character */
539 if(q == t->q0) /* eat the newline */
540 --q;
541 break;
543 if(c == 0x17){
544 eq = isalnum(r);
545 if(eq && skipping) /* found one; stop skipping */
546 skipping = FALSE;
547 else if(!eq && !skipping)
548 break;
550 --q;
552 return t->q0-q;
555 int
556 textfilewidth(Text *t, uint q0, int oneelement)
558 uint q;
559 Rune r;
561 q = q0;
562 while(q > 0){
563 r = textreadc(t, q-1);
564 if(r <= ' ')
565 break;
566 if(oneelement && r=='/')
567 break;
568 --q;
570 return q0-q;
573 Rune*
574 textcomplete(Text *t)
576 int i, nstr, npath;
577 uint q;
578 Rune tmp[200];
579 Rune *str, *path;
580 Rune *rp;
581 Completion *c;
582 char *s, *dirs;
583 Runestr dir;
585 /* control-f: filename completion; works back to white space or / */
586 if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
587 return nil;
588 nstr = textfilewidth(t, t->q0, TRUE);
589 str = runemalloc(nstr);
590 npath = textfilewidth(t, t->q0-nstr, FALSE);
591 path = runemalloc(npath);
593 c = nil;
594 rp = nil;
595 dirs = nil;
597 q = t->q0-nstr;
598 for(i=0; i<nstr; i++)
599 str[i] = textreadc(t, q++);
600 q = t->q0-nstr-npath;
601 for(i=0; i<npath; i++)
602 path[i] = textreadc(t, q++);
603 /* is path rooted? if not, we need to make it relative to window path */
604 if(npath>0 && path[0]=='/')
605 dir = runestr(path, npath);
606 else{
607 dir = dirname(t, nil, 0);
608 if(dir.nr + 1 + npath > nelem(tmp)){
609 free(dir.r);
610 goto Return;
612 if(dir.nr == 0){
613 dir.nr = 1;
614 dir.r = runestrdup(Ldot);
616 runemove(tmp, dir.r, dir.nr);
617 tmp[dir.nr] = '/';
618 runemove(tmp+dir.nr+1, path, npath);
619 free(dir.r);
620 dir.r = tmp;
621 dir.nr += 1+npath;
622 dir = cleanrname(dir);
625 s = smprint("%.*S", nstr, str);
626 dirs = smprint("%.*S", dir.nr, dir.r);
627 c = complete(dirs, s);
628 free(s);
629 if(c == nil){
630 warning(nil, "error attempting completion: %r\n");
631 goto Return;
634 if(!c->advance){
635 warning(nil, "%.*S%s%.*S*%s\n",
636 dir.nr, dir.r,
637 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
638 nstr, str,
639 c->nmatch ? "" : ": no matches in:");
640 for(i=0; i<c->nfile; i++)
641 warning(nil, " %s\n", c->filename[i]);
644 if(c->advance)
645 rp = runesmprint("%s", c->string);
646 else
647 rp = nil;
648 Return:
649 freecompletion(c);
650 free(dirs);
651 free(str);
652 free(path);
653 return rp;
656 void
657 texttype(Text *t, Rune r)
659 uint q0, q1;
660 int nnb, nb, n, i;
661 int nr;
662 Rune *rp;
663 Text *u;
665 if(t->what!=Body && t->what!=Tag && r=='\n')
666 return;
667 if(t->what == Tag)
668 t->w->tagsafe = FALSE;
670 nr = 1;
671 rp = &r;
672 switch(r){
673 case Kleft:
674 typecommit(t);
675 if(t->q0 > 0)
676 textshow(t, t->q0-1, t->q0-1, TRUE);
677 return;
678 case Kright:
679 typecommit(t);
680 if(t->q1 < t->file->b.nc)
681 textshow(t, t->q1+1, t->q1+1, TRUE);
682 return;
683 case Kdown:
684 if(t->what == Tag)
685 goto Tagdown;
686 n = t->fr.maxlines/3;
687 goto case_Down;
688 case Kscrollonedown:
689 if(t->what == Tag)
690 goto Tagdown;
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 if(t->what == Tag)
703 goto Tagup;
704 n = t->fr.maxlines/3;
705 goto case_Up;
706 case Kscrolloneup:
707 if(t->what == Tag)
708 goto Tagup;
709 n = mousescrollsize(t->fr.maxlines);
710 goto case_Up;
711 case Kpgup:
712 n = 2*t->fr.maxlines/3;
713 case_Up:
714 q0 = textbacknl(t, t->org, n);
715 textsetorigin(t, q0, TRUE);
716 return;
717 case Khome:
718 typecommit(t);
719 if(t->org > t->iq1) {
720 q0 = textbacknl(t, t->iq1, 1);
721 textsetorigin(t, q0, TRUE);
722 } else
723 textshow(t, 0, 0, FALSE);
724 return;
725 case Kend:
726 typecommit(t);
727 if(t->iq1 > t->org+t->fr.nchars) {
728 q0 = textbacknl(t, t->iq1, 1);
729 textsetorigin(t, q0, TRUE);
730 } else
731 textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
732 return;
733 case 0x01: /* ^A: beginning of line */
734 typecommit(t);
735 /* go to where ^U would erase, if not already at BOL */
736 nnb = 0;
737 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
738 nnb = textbswidth(t, 0x15);
739 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
740 return;
741 case 0x05: /* ^E: end of line */
742 typecommit(t);
743 q0 = t->q0;
744 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
745 q0++;
746 textshow(t, q0, q0, TRUE);
747 return;
748 case Kcmd+'c': /* %C: copy */
749 typecommit(t);
750 cut(t, t, nil, TRUE, FALSE, nil, 0);
751 return;
752 case Kcmd+'z': /* %Z: undo */
753 typecommit(t);
754 undo(t, nil, nil, TRUE, 0, nil, 0);
755 return;
757 Tagdown:
758 /* expand tag to show all text */
759 if(!t->w->tagexpand){
760 t->w->tagexpand = TRUE;
761 winresize(t->w, t->w->r, FALSE, TRUE);
763 return;
765 Tagup:
766 /* shrink tag to single line */
767 if(t->w->tagexpand){
768 t->w->tagexpand = FALSE;
769 t->w->taglines = 1;
770 winresize(t->w, t->w->r, FALSE, TRUE);
772 return;
774 if(t->what == Body){
775 seq++;
776 filemark(t->file);
778 /* cut/paste must be done after the seq++/filemark */
779 switch(r){
780 case Kcmd+'x': /* %X: cut */
781 typecommit(t);
782 if(t->what == Body){
783 seq++;
784 filemark(t->file);
786 cut(t, t, nil, TRUE, TRUE, nil, 0);
787 textshow(t, t->q0, t->q0, 1);
788 t->iq1 = t->q0;
789 return;
790 case Kcmd+'v': /* %V: paste */
791 typecommit(t);
792 if(t->what == Body){
793 seq++;
794 filemark(t->file);
796 paste(t, t, nil, TRUE, FALSE, nil, 0);
797 textshow(t, t->q0, t->q1, 1);
798 t->iq1 = t->q1;
799 return;
801 if(t->q1 > t->q0){
802 if(t->ncache != 0)
803 error("text.type");
804 cut(t, t, nil, TRUE, TRUE, nil, 0);
805 t->eq0 = ~0;
807 textshow(t, t->q0, t->q0, 1);
808 switch(r){
809 case 0x06: /* ^F: complete */
810 case Kins:
811 rp = textcomplete(t);
812 if(rp == nil)
813 return;
814 nr = runestrlen(rp);
815 break; /* fall through to normal insertion case */
816 case 0x1B:
817 if(t->eq0 != ~0)
818 textsetselect(t, t->eq0, t->q0);
819 if(t->ncache > 0)
820 typecommit(t);
821 t->iq1 = t->q0;
822 return;
823 case 0x08: /* ^H: erase character */
824 case 0x15: /* ^U: erase line */
825 case 0x17: /* ^W: erase word */
826 if(t->q0 == 0) /* nothing to erase */
827 return;
828 nnb = textbswidth(t, r);
829 q1 = t->q0;
830 q0 = q1-nnb;
831 /* if selection is at beginning of window, avoid deleting invisible text */
832 if(q0 < t->org){
833 q0 = t->org;
834 nnb = q1-q0;
836 if(nnb <= 0)
837 return;
838 for(i=0; i<t->file->ntext; i++){
839 u = t->file->text[i];
840 u->nofill = TRUE;
841 nb = nnb;
842 n = u->ncache;
843 if(n > 0){
844 if(q1 != u->cq0+n)
845 error("text.type backspace");
846 if(n > nb)
847 n = nb;
848 u->ncache -= n;
849 textdelete(u, q1-n, q1, FALSE);
850 nb -= n;
852 if(u->eq0==q1 || u->eq0==~0)
853 u->eq0 = q0;
854 if(nb && u==t)
855 textdelete(u, q0, q0+nb, TRUE);
856 if(u != t)
857 textsetselect(u, u->q0, u->q1);
858 else
859 textsetselect(t, q0, q0);
860 u->nofill = FALSE;
862 for(i=0; i<t->file->ntext; i++)
863 textfill(t->file->text[i]);
864 t->iq1 = t->q0;
865 return;
866 case '\n':
867 if(t->w->autoindent){
868 /* find beginning of previous line using backspace code */
869 nnb = textbswidth(t, 0x15); /* ^U case */
870 rp = runemalloc(nnb + 1);
871 nr = 0;
872 rp[nr++] = r;
873 for(i=0; i<nnb; i++){
874 r = textreadc(t, t->q0-nnb+i);
875 if(r != ' ' && r != '\t')
876 break;
877 rp[nr++] = r;
880 break; /* fall through to normal code */
882 /* otherwise ordinary character; just insert, typically in caches of all texts */
883 for(i=0; i<t->file->ntext; i++){
884 u = t->file->text[i];
885 if(u->eq0 == ~0)
886 u->eq0 = t->q0;
887 if(u->ncache == 0)
888 u->cq0 = t->q0;
889 else if(t->q0 != u->cq0+u->ncache)
890 error("text.type cq1");
891 /*
892 * Change the tag before we add to ncache,
893 * so that if the window body is resized the
894 * commit will not find anything in ncache.
895 */
896 if(u->what==Body && u->ncache == 0){
897 u->needundo = TRUE;
898 winsettag(t->w);
899 u->needundo = FALSE;
901 textinsert(u, t->q0, rp, nr, FALSE);
902 if(u != t)
903 textsetselect(u, u->q0, u->q1);
904 if(u->ncache+nr > u->ncachealloc){
905 u->ncachealloc += 10 + nr;
906 u->cache = runerealloc(u->cache, u->ncachealloc);
908 runemove(u->cache+u->ncache, rp, nr);
909 u->ncache += nr;
911 if(rp != &r)
912 free(rp);
913 textsetselect(t, t->q0+nr, t->q0+nr);
914 if(r=='\n' && t->w!=nil)
915 wincommit(t->w, t);
916 t->iq1 = t->q0;
919 void
920 textcommit(Text *t, int tofile)
922 if(t->ncache == 0)
923 return;
924 if(tofile)
925 fileinsert(t->file, t->cq0, t->cache, t->ncache);
926 if(t->what == Body){
927 t->w->dirty = TRUE;
928 t->w->utflastqid = -1;
930 t->ncache = 0;
933 static Text *clicktext;
934 static uint clickmsec;
935 static Text *selecttext;
936 static uint selectq;
938 /*
939 * called from frame library
940 */
941 void
942 framescroll(Frame *f, int dl)
944 if(f != &selecttext->fr)
945 error("frameselect not right frame");
946 textframescroll(selecttext, dl);
949 void
950 textframescroll(Text *t, int dl)
952 uint q0;
954 if(dl == 0){
955 scrsleep(100);
956 return;
958 if(dl < 0){
959 q0 = textbacknl(t, t->org, -dl);
960 if(selectq > t->org+t->fr.p0)
961 textsetselect(t, t->org+t->fr.p0, selectq);
962 else
963 textsetselect(t, selectq, t->org+t->fr.p0);
964 }else{
965 if(t->org+t->fr.nchars == t->file->b.nc)
966 return;
967 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
968 if(selectq > t->org+t->fr.p1)
969 textsetselect(t, t->org+t->fr.p1, selectq);
970 else
971 textsetselect(t, selectq, t->org+t->fr.p1);
973 textsetorigin(t, q0, TRUE);
977 void
978 textselect(Text *t)
980 uint q0, q1;
981 int b, x, y;
982 int state;
983 enum { None, Cut, Paste };
985 selecttext = t;
986 /*
987 * To have double-clicking and chording, we double-click
988 * immediately if it might make sense.
989 */
990 b = mouse->buttons;
991 q0 = t->q0;
992 q1 = t->q1;
993 selectq = t->org+frcharofpt(&t->fr, mouse->xy);
994 if(clicktext==t && mouse->msec-clickmsec<500)
995 if(q0==q1 && selectq==q0){
996 textdoubleclick(t, &q0, &q1);
997 textsetselect(t, q0, q1);
998 flushimage(display, 1);
999 x = mouse->xy.x;
1000 y = mouse->xy.y;
1001 /* stay here until something interesting happens */
1003 readmouse(mousectl);
1004 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
1005 mouse->xy.x = x; /* in case we're calling frselect */
1006 mouse->xy.y = y;
1007 q0 = t->q0; /* may have changed */
1008 q1 = t->q1;
1009 selectq = q0;
1011 if(mouse->buttons == b){
1012 t->fr.scroll = framescroll;
1013 frselect(&t->fr, mousectl);
1014 /* horrible botch: while asleep, may have lost selection altogether */
1015 if(selectq > t->file->b.nc)
1016 selectq = t->org + t->fr.p0;
1017 t->fr.scroll = nil;
1018 if(selectq < t->org)
1019 q0 = selectq;
1020 else
1021 q0 = t->org + t->fr.p0;
1022 if(selectq > t->org+t->fr.nchars)
1023 q1 = selectq;
1024 else
1025 q1 = t->org+t->fr.p1;
1027 if(q0 == q1){
1028 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
1029 textdoubleclick(t, &q0, &q1);
1030 clicktext = nil;
1031 }else{
1032 clicktext = t;
1033 clickmsec = mouse->msec;
1035 }else
1036 clicktext = nil;
1037 textsetselect(t, q0, q1);
1038 flushimage(display, 1);
1039 state = None; /* what we've done; undo when possible */
1040 while(mouse->buttons){
1041 mouse->msec = 0;
1042 b = mouse->buttons;
1043 if((b&1) && (b&6)){
1044 if(state==None && t->what==Body){
1045 seq++;
1046 filemark(t->w->body.file);
1048 if(b & 2){
1049 if(state==Paste && t->what==Body){
1050 winundo(t->w, TRUE);
1051 textsetselect(t, q0, t->q1);
1052 state = None;
1053 }else if(state != Cut){
1054 cut(t, t, nil, TRUE, TRUE, nil, 0);
1055 state = Cut;
1057 }else{
1058 if(state==Cut && t->what==Body){
1059 winundo(t->w, TRUE);
1060 textsetselect(t, q0, t->q1);
1061 state = None;
1062 }else if(state != Paste){
1063 paste(t, t, nil, TRUE, FALSE, nil, 0);
1064 state = Paste;
1067 textscrdraw(t);
1068 clearmouse();
1070 flushimage(display, 1);
1071 while(mouse->buttons == b)
1072 readmouse(mousectl);
1073 clicktext = nil;
1077 void
1078 textshow(Text *t, uint q0, uint q1, int doselect)
1080 int qe;
1081 int nl;
1082 uint q;
1084 if(t->what != Body){
1085 if(doselect)
1086 textsetselect(t, q0, q1);
1087 return;
1089 if(t->w!=nil && t->fr.maxlines==0)
1090 colgrow(t->col, t->w, 1);
1091 if(doselect)
1092 textsetselect(t, q0, q1);
1093 qe = t->org+t->fr.nchars;
1094 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache)))
1095 textscrdraw(t);
1096 else{
1097 if(t->w->nopen[QWevent] > 0)
1098 nl = 3*t->fr.maxlines/4;
1099 else
1100 nl = t->fr.maxlines/4;
1101 q = textbacknl(t, q0, nl);
1102 /* avoid going backwards if trying to go forwards - long lines! */
1103 if(!(q0>t->org && q<t->org))
1104 textsetorigin(t, q, TRUE);
1105 while(q0 > t->org+t->fr.nchars)
1106 textsetorigin(t, t->org+1, FALSE);
1110 static
1111 int
1112 region(int a, int b)
1114 if(a < b)
1115 return -1;
1116 if(a == b)
1117 return 0;
1118 return 1;
1121 void
1122 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1124 if(p1<=f->p0 || p0>=f->p1){
1125 /* no overlap */
1126 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1127 return;
1129 if(p0>=f->p0 && p1<=f->p1){
1130 /* entirely inside */
1131 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1132 return;
1135 /* they now are known to overlap */
1137 /* before selection */
1138 if(p0 < f->p0){
1139 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1140 p0 = f->p0;
1141 pt0 = frptofchar(f, p0);
1143 /* after selection */
1144 if(p1 > f->p1){
1145 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1146 p1 = f->p1;
1148 /* inside selection */
1149 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1152 void
1153 textsetselect(Text *t, uint q0, uint q1)
1155 int p0, p1, ticked;
1157 /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1158 t->q0 = q0;
1159 t->q1 = q1;
1160 /* compute desired p0,p1 from q0,q1 */
1161 p0 = q0-t->org;
1162 p1 = q1-t->org;
1163 ticked = 1;
1164 if(p0 < 0){
1165 ticked = 0;
1166 p0 = 0;
1168 if(p1 < 0)
1169 p1 = 0;
1170 if(p0 > t->fr.nchars)
1171 p0 = t->fr.nchars;
1172 if(p1 > t->fr.nchars){
1173 ticked = 0;
1174 p1 = t->fr.nchars;
1176 if(p0==t->fr.p0 && p1==t->fr.p1){
1177 if(p0 == p1 && ticked != t->fr.ticked)
1178 frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
1179 return;
1181 /* screen disagrees with desired selection */
1182 if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1183 /* no overlap or too easy to bother trying */
1184 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1185 if(p0 != p1 || ticked)
1186 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1187 goto Return;
1189 /* overlap; avoid unnecessary painting */
1190 if(p0 < t->fr.p0){
1191 /* extend selection backwards */
1192 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1193 }else if(p0 > t->fr.p0){
1194 /* trim first part of selection */
1195 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1197 if(p1 > t->fr.p1){
1198 /* extend selection forwards */
1199 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1200 }else if(p1 < t->fr.p1){
1201 /* trim last part of selection */
1202 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1205 Return:
1206 t->fr.p0 = p0;
1207 t->fr.p1 = p1;
1211 * Release the button in less than DELAY ms and it's considered a null selection
1212 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1214 enum {
1215 DELAY = 2,
1216 MINMOVE = 4
1219 uint
1220 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1222 uint p0, p1, q, tmp;
1223 ulong msec;
1224 Point mp, pt0, pt1, qt;
1225 int reg, b;
1227 mp = mc->m.xy;
1228 b = mc->m.buttons;
1229 msec = mc->m.msec;
1231 /* remove tick */
1232 if(f->p0 == f->p1)
1233 frtick(f, frptofchar(f, f->p0), 0);
1234 p0 = p1 = frcharofpt(f, mp);
1235 pt0 = frptofchar(f, p0);
1236 pt1 = frptofchar(f, p1);
1237 reg = 0;
1238 frtick(f, pt0, 1);
1239 do{
1240 q = frcharofpt(f, mc->m.xy);
1241 if(p1 != q){
1242 if(p0 == p1)
1243 frtick(f, pt0, 0);
1244 if(reg != region(q, p0)){ /* crossed starting point; reset */
1245 if(reg > 0)
1246 selrestore(f, pt0, p0, p1);
1247 else if(reg < 0)
1248 selrestore(f, pt1, p1, p0);
1249 p1 = p0;
1250 pt1 = pt0;
1251 reg = region(q, p0);
1252 if(reg == 0)
1253 frdrawsel0(f, pt0, p0, p1, col, display->white);
1255 qt = frptofchar(f, q);
1256 if(reg > 0){
1257 if(q > p1)
1258 frdrawsel0(f, pt1, p1, q, col, display->white);
1260 else if(q < p1)
1261 selrestore(f, qt, q, p1);
1262 }else if(reg < 0){
1263 if(q > p1)
1264 selrestore(f, pt1, p1, q);
1265 else
1266 frdrawsel0(f, qt, q, p1, col, display->white);
1268 p1 = q;
1269 pt1 = qt;
1271 if(p0 == p1)
1272 frtick(f, pt0, 1);
1273 flushimage(f->display, 1);
1274 readmouse(mc);
1275 }while(mc->m.buttons == b);
1276 if(mc->m.msec-msec < DELAY && p0!=p1
1277 && abs(mp.x-mc->m.xy.x)<MINMOVE
1278 && abs(mp.y-mc->m.xy.y)<MINMOVE) {
1279 if(reg > 0)
1280 selrestore(f, pt0, p0, p1);
1281 else if(reg < 0)
1282 selrestore(f, pt1, p1, p0);
1283 p1 = p0;
1285 if(p1 < p0){
1286 tmp = p0;
1287 p0 = p1;
1288 p1 = tmp;
1290 pt0 = frptofchar(f, p0);
1291 if(p0 == p1)
1292 frtick(f, pt0, 0);
1293 selrestore(f, pt0, p0, p1);
1294 /* restore tick */
1295 if(f->p0 == f->p1)
1296 frtick(f, frptofchar(f, f->p0), 1);
1297 flushimage(f->display, 1);
1298 *p1p = p1;
1299 return p0;
1302 int
1303 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1305 uint p0, p1;
1306 int buts;
1308 p0 = xselect(&t->fr, mousectl, high, &p1);
1309 buts = mousectl->m.buttons;
1310 if((buts & mask) == 0){
1311 *q0 = p0+t->org;
1312 *q1 = p1+t->org;
1315 while(mousectl->m.buttons)
1316 readmouse(mousectl);
1317 return buts;
1320 int
1321 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1323 int buts;
1325 *tp = nil;
1326 buts = textselect23(t, q0, q1, but2col, 4);
1327 if(buts & 4)
1328 return 0;
1329 if(buts & 1){ /* pick up argument */
1330 *tp = argtext;
1331 return 1;
1333 return 1;
1336 int
1337 textselect3(Text *t, uint *q0, uint *q1)
1339 int h;
1341 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1342 return h;
1345 static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1346 static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1347 static Rune left2[] = { '\n', 0 };
1348 static Rune left3[] = { '\'', '"', '`', 0 };
1350 static
1351 Rune *left[] = {
1352 left1,
1353 left2,
1354 left3,
1355 nil
1357 static
1358 Rune *right[] = {
1359 right1,
1360 left2,
1361 left3,
1362 nil
1365 void
1366 textdoubleclick(Text *t, uint *q0, uint *q1)
1368 int c, i;
1369 Rune *r, *l, *p;
1370 uint q;
1372 if(textclickhtmlmatch(t, q0, q1))
1373 return;
1375 for(i=0; left[i]!=nil; i++){
1376 q = *q0;
1377 l = left[i];
1378 r = right[i];
1379 /* try matching character to left, looking right */
1380 if(q == 0)
1381 c = '\n';
1382 else
1383 c = textreadc(t, q-1);
1384 p = runestrchr(l, c);
1385 if(p != nil){
1386 if(textclickmatch(t, c, r[p-l], 1, &q))
1387 *q1 = q-(c!='\n');
1388 return;
1390 /* try matching character to right, looking left */
1391 if(q == t->file->b.nc)
1392 c = '\n';
1393 else
1394 c = textreadc(t, q);
1395 p = runestrchr(r, c);
1396 if(p != nil){
1397 if(textclickmatch(t, c, l[p-r], -1, &q)){
1398 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1399 *q0 = q;
1400 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1401 (*q0)++;
1403 return;
1407 /* try filling out word to right */
1408 while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1409 (*q1)++;
1410 /* try filling out word to left */
1411 while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1412 (*q0)--;
1415 int
1416 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1418 Rune c;
1419 int nest;
1421 nest = 1;
1422 for(;;){
1423 if(dir > 0){
1424 if(*q == t->file->b.nc)
1425 break;
1426 c = textreadc(t, *q);
1427 (*q)++;
1428 }else{
1429 if(*q == 0)
1430 break;
1431 (*q)--;
1432 c = textreadc(t, *q);
1434 if(c == cr){
1435 if(--nest==0)
1436 return 1;
1437 }else if(c == cl)
1438 nest++;
1440 return cl=='\n' && nest==1;
1443 // Is the text starting at location q an html tag?
1444 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1445 // Set *q1, if non-nil, to the location after the tag.
1446 static int
1447 ishtmlstart(Text *t, uint q, uint *q1)
1449 int c, c1, c2;
1451 if(q+2 > t->file->b.nc)
1452 return 0;
1453 if(textreadc(t, q++) != '<')
1454 return 0;
1455 c = textreadc(t, q++);
1456 c1 = c;
1457 c2 = c;
1458 while(c != '>') {
1459 if(q >= t->file->b.nc)
1460 return 0;
1461 c2 = c;
1462 c = textreadc(t, q++);
1464 if(q1)
1465 *q1 = q;
1466 if(c1 == '/') // closing tag
1467 return -1;
1468 if(c2 == '/' || c2 == '!') // open + close tag or comment
1469 return 0;
1470 return 1;
1473 // Is the text ending at location q an html tag?
1474 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1475 // Set *q0, if non-nil, to the start of the tag.
1476 static int
1477 ishtmlend(Text *t, uint q, uint *q0)
1479 int c, c1, c2;
1481 if(q < 2)
1482 return 0;
1483 if(textreadc(t, --q) != '>')
1484 return 0;
1485 c = textreadc(t, --q);
1486 c1 = c;
1487 c2 = c;
1488 while(c != '<') {
1489 if(q == 0)
1490 return 0;
1491 c1 = c;
1492 c = textreadc(t, --q);
1494 if(q0)
1495 *q0 = q;
1496 if(c1 == '/') // closing tag
1497 return -1;
1498 if(c2 == '/' || c2 == '!') // open + close tag or comment
1499 return 0;
1500 return 1;
1503 int
1504 textclickhtmlmatch(Text *t, uint *q0, uint *q1)
1506 int depth, n;
1507 uint q, nq;
1509 q = *q0;
1510 // after opening tag? scan forward for closing tag
1511 if(ishtmlend(t, q, nil) == 1) {
1512 depth = 1;
1513 while(q < t->file->b.nc) {
1514 n = ishtmlstart(t, q, &nq);
1515 if(n != 0) {
1516 depth += n;
1517 if(depth == 0) {
1518 *q1 = q;
1519 return 1;
1521 q = nq;
1522 continue;
1524 q++;
1528 // before closing tag? scan backward for opening tag
1529 if(ishtmlstart(t, q, nil) == -1) {
1530 depth = -1;
1531 while(q > 0) {
1532 n = ishtmlend(t, q, &nq);
1533 if(n != 0) {
1534 depth += n;
1535 if(depth == 0) {
1536 *q0 = q;
1537 return 1;
1539 q = nq;
1540 continue;
1542 q--;
1546 return 0;
1549 uint
1550 textbacknl(Text *t, uint p, uint n)
1552 int i, j;
1554 /* look for start of this line if n==0 */
1555 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1556 n = 1;
1557 i = n;
1558 while(i-->0 && p>0){
1559 --p; /* it's at a newline now; back over it */
1560 if(p == 0)
1561 break;
1562 /* at 128 chars, call it a line anyway */
1563 for(j=128; --j>0 && p>0; p--)
1564 if(textreadc(t, p-1)=='\n')
1565 break;
1567 return p;
1570 void
1571 textsetorigin(Text *t, uint org, int exact)
1573 int i, a, fixup;
1574 Rune *r;
1575 uint n;
1577 if(org>0 && !exact){
1578 /* org is an estimate of the char posn; find a newline */
1579 /* don't try harder than 256 chars */
1580 for(i=0; i<256 && org<t->file->b.nc; i++){
1581 if(textreadc(t, org) == '\n'){
1582 org++;
1583 break;
1585 org++;
1588 a = org-t->org;
1589 fixup = 0;
1590 if(a>=0 && a<t->fr.nchars){
1591 frdelete(&t->fr, 0, a);
1592 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1594 else if(a<0 && -a<t->fr.nchars){
1595 n = t->org - org;
1596 r = runemalloc(n);
1597 bufread(&t->file->b, org, r, n);
1598 frinsert(&t->fr, r, r+n, 0);
1599 free(r);
1600 }else
1601 frdelete(&t->fr, 0, t->fr.nchars);
1602 t->org = org;
1603 textfill(t);
1604 textscrdraw(t);
1605 textsetselect(t, t->q0, t->q1);
1606 if(fixup && t->fr.p1 > t->fr.p0)
1607 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1610 void
1611 textreset(Text *t)
1613 t->file->seq = 0;
1614 t->eq0 = ~0;
1615 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1616 textsetselect(t, t->org, t->org);
1617 frdelete(&t->fr, 0, t->fr.nchars);
1618 t->org = 0;
1619 t->q0 = 0;
1620 t->q1 = 0;
1621 filereset(t->file);
1622 bufreset(&t->file->b);