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->q1)
383 t->q1 += n;
384 if(q0 < t->q0)
385 t->q0 += n;
386 if(q0 < t->org)
387 t->org += n;
388 else if(q0 <= t->org+t->fr.nchars)
389 frinsert(&t->fr, r, r+n, q0-t->org);
390 if(t->w){
391 c = 'i';
392 if(t->what == Body)
393 c = 'I';
394 if(n <= EVENTSIZE)
395 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
396 else
397 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
401 void
402 typecommit(Text *t)
404 if(t->w != nil)
405 wincommit(t->w, t);
406 else
407 textcommit(t, TRUE);
410 void
411 textfill(Text *t)
413 Rune *rp;
414 int i, n, m, nl;
416 if(t->fr.lastlinefull || t->nofill)
417 return;
418 if(t->ncache > 0)
419 typecommit(t);
420 rp = fbufalloc();
421 do{
422 n = t->file->b.nc-(t->org+t->fr.nchars);
423 if(n == 0)
424 break;
425 if(n > 2000) /* educated guess at reasonable amount */
426 n = 2000;
427 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
428 /*
429 * it's expensive to frinsert more than we need, so
430 * count newlines.
431 */
432 nl = t->fr.maxlines-t->fr.nlines;
433 m = 0;
434 for(i=0; i<n; ){
435 if(rp[i++] == '\n'){
436 m++;
437 if(m >= nl)
438 break;
441 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
442 }while(t->fr.lastlinefull == FALSE);
443 fbuffree(rp);
446 void
447 textdelete(Text *t, uint q0, uint q1, int tofile)
449 uint n, p0, p1;
450 int i, c;
451 Text *u;
453 if(tofile && t->ncache != 0)
454 error("text.delete");
455 n = q1-q0;
456 if(n == 0)
457 return;
458 if(tofile){
459 filedelete(t->file, q0, q1);
460 if(t->what == Body){
461 t->w->dirty = TRUE;
462 t->w->utflastqid = -1;
464 if(t->file->ntext > 1)
465 for(i=0; i<t->file->ntext; i++){
466 u = t->file->text[i];
467 if(u != t){
468 u->w->dirty = TRUE; /* always a body */
469 textdelete(u, q0, q1, FALSE);
470 textsetselect(u, u->q0, u->q1);
471 textscrdraw(u);
475 if(q0 < t->q0)
476 t->q0 -= min(n, t->q0-q0);
477 if(q0 < t->q1)
478 t->q1 -= min(n, t->q1-q0);
479 if(q1 <= t->org)
480 t->org -= n;
481 else if(q0 < t->org+t->fr.nchars){
482 p1 = q1 - t->org;
483 if(p1 > t->fr.nchars)
484 p1 = t->fr.nchars;
485 if(q0 < t->org){
486 t->org = q0;
487 p0 = 0;
488 }else
489 p0 = q0 - t->org;
490 frdelete(&t->fr, p0, p1);
491 textfill(t);
493 if(t->w){
494 c = 'd';
495 if(t->what == Body)
496 c = 'D';
497 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
501 void
502 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
504 *p0 = min(q0, t->file->b.nc);
505 *p1 = min(q1, t->file->b.nc);
508 Rune
509 textreadc(Text *t, uint q)
511 Rune r;
513 if(t->cq0<=q && q<t->cq0+t->ncache)
514 r = t->cache[q-t->cq0];
515 else
516 bufread(&t->file->b, q, &r, 1);
517 return r;
520 int
521 textbswidth(Text *t, Rune c)
523 uint q, eq;
524 Rune r;
525 int skipping;
527 /* there is known to be at least one character to erase */
528 if(c == 0x08) /* ^H: erase character */
529 return 1;
530 q = t->q0;
531 skipping = TRUE;
532 while(q > 0){
533 r = textreadc(t, q-1);
534 if(r == '\n'){ /* eat at most one more character */
535 if(q == t->q0) /* eat the newline */
536 --q;
537 break;
539 if(c == 0x17){
540 eq = isalnum(r);
541 if(eq && skipping) /* found one; stop skipping */
542 skipping = FALSE;
543 else if(!eq && !skipping)
544 break;
546 --q;
548 return t->q0-q;
551 int
552 textfilewidth(Text *t, uint q0, int oneelement)
554 uint q;
555 Rune r;
557 q = q0;
558 while(q > 0){
559 r = textreadc(t, q-1);
560 if(r <= ' ')
561 break;
562 if(oneelement && r=='/')
563 break;
564 --q;
566 return q0-q;
569 Rune*
570 textcomplete(Text *t)
572 int i, nstr, npath;
573 uint q;
574 Rune tmp[200];
575 Rune *str, *path;
576 Rune *rp;
577 Completion *c;
578 char *s, *dirs;
579 Runestr dir;
581 /* control-f: filename completion; works back to white space or / */
582 if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
583 return nil;
584 nstr = textfilewidth(t, t->q0, TRUE);
585 str = runemalloc(nstr);
586 npath = textfilewidth(t, t->q0-nstr, FALSE);
587 path = runemalloc(npath);
589 c = nil;
590 rp = nil;
591 dirs = nil;
593 q = t->q0-nstr;
594 for(i=0; i<nstr; i++)
595 str[i] = textreadc(t, q++);
596 q = t->q0-nstr-npath;
597 for(i=0; i<npath; i++)
598 path[i] = textreadc(t, q++);
599 /* is path rooted? if not, we need to make it relative to window path */
600 if(npath>0 && path[0]=='/')
601 dir = runestr(path, npath);
602 else{
603 dir = dirname(t, nil, 0);
604 if(dir.nr + 1 + npath > nelem(tmp)){
605 free(dir.r);
606 goto Return;
608 if(dir.nr == 0){
609 dir.nr = 1;
610 dir.r = runestrdup(Ldot);
612 runemove(tmp, dir.r, dir.nr);
613 tmp[dir.nr] = '/';
614 runemove(tmp+dir.nr+1, path, npath);
615 free(dir.r);
616 dir.r = tmp;
617 dir.nr += 1+npath;
618 dir = cleanrname(dir);
621 s = smprint("%.*S", nstr, str);
622 dirs = smprint("%.*S", dir.nr, dir.r);
623 c = complete(dirs, s);
624 free(s);
625 if(c == nil){
626 warning(nil, "error attempting completion: %r\n");
627 goto Return;
630 if(!c->advance){
631 warning(nil, "%.*S%s%.*S*%s\n",
632 dir.nr, dir.r,
633 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
634 nstr, str,
635 c->nmatch ? "" : ": no matches in:");
636 for(i=0; i<c->nfile; i++)
637 warning(nil, " %s\n", c->filename[i]);
640 if(c->advance)
641 rp = runesmprint("%s", c->string);
642 else
643 rp = nil;
644 Return:
645 freecompletion(c);
646 free(dirs);
647 free(str);
648 free(path);
649 return rp;
652 void
653 texttype(Text *t, Rune r)
655 uint q0, q1;
656 int nnb, nb, n, i;
657 int nr;
658 Rune *rp;
659 Text *u;
661 if(t->what!=Body && t->what!=Tag && r=='\n')
662 return;
663 if(t->what == Tag)
664 t->w->tagsafe = FALSE;
666 nr = 1;
667 rp = &r;
668 switch(r){
669 case Kleft:
670 if(t->q0 > 0){
671 typecommit(t);
672 textshow(t, t->q0-1, t->q0-1, TRUE);
674 return;
675 case Kright:
676 if(t->q1 < t->file->b.nc){
677 typecommit(t);
678 textshow(t, t->q1+1, t->q1+1, TRUE);
680 return;
681 case Kdown:
682 if(t->what == Tag)
683 goto Tagdown;
684 n = t->fr.maxlines/3;
685 goto case_Down;
686 case Kscrollonedown:
687 if(t->what == Tag)
688 goto Tagdown;
689 n = mousescrollsize(t->fr.maxlines);
690 if(n <= 0)
691 n = 1;
692 goto case_Down;
693 case Kpgdown:
694 n = 2*t->fr.maxlines/3;
695 case_Down:
696 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
697 textsetorigin(t, q0, TRUE);
698 return;
699 case Kup:
700 if(t->what == Tag)
701 goto Tagup;
702 n = t->fr.maxlines/3;
703 goto case_Up;
704 case Kscrolloneup:
705 if(t->what == Tag)
706 goto Tagup;
707 n = mousescrollsize(t->fr.maxlines);
708 goto case_Up;
709 case Kpgup:
710 n = 2*t->fr.maxlines/3;
711 case_Up:
712 q0 = textbacknl(t, t->org, n);
713 textsetorigin(t, q0, TRUE);
714 return;
715 case Khome:
716 typecommit(t);
717 textshow(t, 0, 0, FALSE);
718 return;
719 case Kend:
720 typecommit(t);
721 textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
722 return;
723 case 0x01: /* ^A: beginning of line */
724 typecommit(t);
725 /* go to where ^U would erase, if not already at BOL */
726 nnb = 0;
727 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
728 nnb = textbswidth(t, 0x15);
729 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
730 return;
731 case 0x05: /* ^E: end of line */
732 typecommit(t);
733 q0 = t->q0;
734 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
735 q0++;
736 textshow(t, q0, q0, TRUE);
737 return;
738 case Kcmd+'c': /* %C: copy */
739 typecommit(t);
740 cut(t, t, nil, TRUE, FALSE, nil, 0);
741 return;
743 Tagdown:
744 /* expand tag to show all text */
745 if(!t->w->tagexpand){
746 t->w->tagexpand = TRUE;
747 winresize(t->w, t->w->r, FALSE, TRUE);
749 return;
751 Tagup:
752 /* shrink tag to single line */
753 if(t->w->tagexpand){
754 t->w->tagexpand = FALSE;
755 t->w->taglines = 1;
756 winresize(t->w, t->w->r, FALSE, TRUE);
758 return;
760 if(t->what == Body){
761 seq++;
762 filemark(t->file);
764 /* cut/paste must be done after the seq++/filemark */
765 switch(r){
766 case Kcmd+'x': /* %X: cut */
767 typecommit(t);
768 if(t->what == Body){
769 seq++;
770 filemark(t->file);
772 cut(t, t, nil, TRUE, TRUE, nil, 0);
773 textshow(t, t->q0, t->q0, 1);
774 return;
775 case Kcmd+'v': /* %V: paste */
776 typecommit(t);
777 if(t->what == Body){
778 seq++;
779 filemark(t->file);
781 paste(t, t, nil, TRUE, FALSE, nil, 0);
782 textshow(t, t->q0, t->q1, 1);
783 return;
785 if(t->q1 > t->q0){
786 if(t->ncache != 0)
787 error("text.type");
788 cut(t, t, nil, TRUE, TRUE, nil, 0);
789 t->eq0 = ~0;
791 textshow(t, t->q0, t->q0, 1);
792 switch(r){
793 case 0x06: /* ^F: complete */
794 case Kins:
795 rp = textcomplete(t);
796 if(rp == nil)
797 return;
798 nr = runestrlen(rp);
799 break; /* fall through to normal insertion case */
800 case 0x1B:
801 if(t->eq0 != ~0)
802 textsetselect(t, t->eq0, t->q0);
803 if(t->ncache > 0)
804 typecommit(t);
805 return;
806 case 0x08: /* ^H: erase character */
807 case 0x15: /* ^U: erase line */
808 case 0x17: /* ^W: erase word */
809 if(t->q0 == 0) /* nothing to erase */
810 return;
811 nnb = textbswidth(t, r);
812 q1 = t->q0;
813 q0 = q1-nnb;
814 /* if selection is at beginning of window, avoid deleting invisible text */
815 if(q0 < t->org){
816 q0 = t->org;
817 nnb = q1-q0;
819 if(nnb <= 0)
820 return;
821 for(i=0; i<t->file->ntext; i++){
822 u = t->file->text[i];
823 u->nofill = TRUE;
824 nb = nnb;
825 n = u->ncache;
826 if(n > 0){
827 if(q1 != u->cq0+n)
828 error("text.type backspace");
829 if(n > nb)
830 n = nb;
831 u->ncache -= n;
832 textdelete(u, q1-n, q1, FALSE);
833 nb -= n;
835 if(u->eq0==q1 || u->eq0==~0)
836 u->eq0 = q0;
837 if(nb && u==t)
838 textdelete(u, q0, q0+nb, TRUE);
839 if(u != t)
840 textsetselect(u, u->q0, u->q1);
841 else
842 textsetselect(t, q0, q0);
843 u->nofill = FALSE;
845 for(i=0; i<t->file->ntext; i++)
846 textfill(t->file->text[i]);
847 return;
848 case '\n':
849 if(t->w->autoindent){
850 /* find beginning of previous line using backspace code */
851 nnb = textbswidth(t, 0x15); /* ^U case */
852 rp = runemalloc(nnb + 1);
853 nr = 0;
854 rp[nr++] = r;
855 for(i=0; i<nnb; i++){
856 r = textreadc(t, t->q0-nnb+i);
857 if(r != ' ' && r != '\t')
858 break;
859 rp[nr++] = r;
862 break; /* fall through to normal code */
864 /* otherwise ordinary character; just insert, typically in caches of all texts */
865 for(i=0; i<t->file->ntext; i++){
866 u = t->file->text[i];
867 if(u->eq0 == ~0)
868 u->eq0 = t->q0;
869 if(u->ncache == 0)
870 u->cq0 = t->q0;
871 else if(t->q0 != u->cq0+u->ncache)
872 error("text.type cq1");
873 /*
874 * Change the tag before we add to ncache,
875 * so that if the window body is resized the
876 * commit will not find anything in ncache.
877 */
878 if(u->what==Body && u->ncache == 0){
879 u->needundo = TRUE;
880 winsettag(t->w);
881 u->needundo = FALSE;
883 textinsert(u, t->q0, rp, nr, FALSE);
884 if(u != t)
885 textsetselect(u, u->q0, u->q1);
886 if(u->ncache+nr > u->ncachealloc){
887 u->ncachealloc += 10 + nr;
888 u->cache = runerealloc(u->cache, u->ncachealloc);
890 runemove(u->cache+u->ncache, rp, nr);
891 u->ncache += nr;
893 if(rp != &r)
894 free(rp);
895 textsetselect(t, t->q0+nr, t->q0+nr);
896 if(r=='\n' && t->w!=nil)
897 wincommit(t->w, t);
900 void
901 textcommit(Text *t, int tofile)
903 if(t->ncache == 0)
904 return;
905 if(tofile)
906 fileinsert(t->file, t->cq0, t->cache, t->ncache);
907 if(t->what == Body){
908 t->w->dirty = TRUE;
909 t->w->utflastqid = -1;
911 t->ncache = 0;
914 static Text *clicktext;
915 static uint clickmsec;
916 static Text *selecttext;
917 static uint selectq;
919 /*
920 * called from frame library
921 */
922 void
923 framescroll(Frame *f, int dl)
925 if(f != &selecttext->fr)
926 error("frameselect not right frame");
927 textframescroll(selecttext, dl);
930 void
931 textframescroll(Text *t, int dl)
933 uint q0;
935 if(dl == 0){
936 scrsleep(100);
937 return;
939 if(dl < 0){
940 q0 = textbacknl(t, t->org, -dl);
941 if(selectq > t->org+t->fr.p0)
942 textsetselect(t, t->org+t->fr.p0, selectq);
943 else
944 textsetselect(t, selectq, t->org+t->fr.p0);
945 }else{
946 if(t->org+t->fr.nchars == t->file->b.nc)
947 return;
948 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
949 if(selectq > t->org+t->fr.p1)
950 textsetselect(t, t->org+t->fr.p1, selectq);
951 else
952 textsetselect(t, selectq, t->org+t->fr.p1);
954 textsetorigin(t, q0, TRUE);
958 void
959 textselect(Text *t)
961 uint q0, q1;
962 int b, x, y;
963 int state;
964 enum { None, Cut, Paste };
966 selecttext = t;
967 /*
968 * To have double-clicking and chording, we double-click
969 * immediately if it might make sense.
970 */
971 b = mouse->buttons;
972 q0 = t->q0;
973 q1 = t->q1;
974 selectq = t->org+frcharofpt(&t->fr, mouse->xy);
975 if(clicktext==t && mouse->msec-clickmsec<500)
976 if(q0==q1 && selectq==q0){
977 textdoubleclick(t, &q0, &q1);
978 textsetselect(t, q0, q1);
979 flushimage(display, 1);
980 x = mouse->xy.x;
981 y = mouse->xy.y;
982 /* stay here until something interesting happens */
983 do
984 readmouse(mousectl);
985 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
986 mouse->xy.x = x; /* in case we're calling frselect */
987 mouse->xy.y = y;
988 q0 = t->q0; /* may have changed */
989 q1 = t->q1;
990 selectq = q0;
992 if(mouse->buttons == b){
993 t->fr.scroll = framescroll;
994 frselect(&t->fr, mousectl);
995 /* horrible botch: while asleep, may have lost selection altogether */
996 if(selectq > t->file->b.nc)
997 selectq = t->org + t->fr.p0;
998 t->fr.scroll = nil;
999 if(selectq < t->org)
1000 q0 = selectq;
1001 else
1002 q0 = t->org + t->fr.p0;
1003 if(selectq > t->org+t->fr.nchars)
1004 q1 = selectq;
1005 else
1006 q1 = t->org+t->fr.p1;
1008 if(q0 == q1){
1009 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
1010 textdoubleclick(t, &q0, &q1);
1011 clicktext = nil;
1012 }else{
1013 clicktext = t;
1014 clickmsec = mouse->msec;
1016 }else
1017 clicktext = nil;
1018 textsetselect(t, q0, q1);
1019 flushimage(display, 1);
1020 state = None; /* what we've done; undo when possible */
1021 while(mouse->buttons){
1022 mouse->msec = 0;
1023 b = mouse->buttons;
1024 if((b&1) && (b&6)){
1025 if(state==None && t->what==Body){
1026 seq++;
1027 filemark(t->w->body.file);
1029 if(b & 2){
1030 if(state==Paste && t->what==Body){
1031 winundo(t->w, TRUE);
1032 textsetselect(t, q0, t->q1);
1033 state = None;
1034 }else if(state != Cut){
1035 cut(t, t, nil, TRUE, TRUE, nil, 0);
1036 state = Cut;
1038 }else{
1039 if(state==Cut && t->what==Body){
1040 winundo(t->w, TRUE);
1041 textsetselect(t, q0, t->q1);
1042 state = None;
1043 }else if(state != Paste){
1044 paste(t, t, nil, TRUE, FALSE, nil, 0);
1045 state = Paste;
1048 textscrdraw(t);
1049 clearmouse();
1051 flushimage(display, 1);
1052 while(mouse->buttons == b)
1053 readmouse(mousectl);
1054 clicktext = nil;
1058 void
1059 textshow(Text *t, uint q0, uint q1, int doselect)
1061 int qe;
1062 int nl;
1063 uint q;
1065 if(t->what != Body){
1066 if(doselect)
1067 textsetselect(t, q0, q1);
1068 return;
1070 if(t->w!=nil && t->fr.maxlines==0)
1071 colgrow(t->col, t->w, 1);
1072 if(doselect)
1073 textsetselect(t, q0, q1);
1074 qe = t->org+t->fr.nchars;
1075 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->b.nc+t->ncache)))
1076 textscrdraw(t);
1077 else{
1078 if(t->w->nopen[QWevent] > 0)
1079 nl = 3*t->fr.maxlines/4;
1080 else
1081 nl = t->fr.maxlines/4;
1082 q = textbacknl(t, q0, nl);
1083 /* avoid going backwards if trying to go forwards - long lines! */
1084 if(!(q0>t->org && q<t->org))
1085 textsetorigin(t, q, TRUE);
1086 while(q0 > t->org+t->fr.nchars)
1087 textsetorigin(t, t->org+1, FALSE);
1091 static
1092 int
1093 region(int a, int b)
1095 if(a < b)
1096 return -1;
1097 if(a == b)
1098 return 0;
1099 return 1;
1102 void
1103 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1105 if(p1<=f->p0 || p0>=f->p1){
1106 /* no overlap */
1107 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1108 return;
1110 if(p0>=f->p0 && p1<=f->p1){
1111 /* entirely inside */
1112 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1113 return;
1116 /* they now are known to overlap */
1118 /* before selection */
1119 if(p0 < f->p0){
1120 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1121 p0 = f->p0;
1122 pt0 = frptofchar(f, p0);
1124 /* after selection */
1125 if(p1 > f->p1){
1126 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1127 p1 = f->p1;
1129 /* inside selection */
1130 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1133 void
1134 textsetselect(Text *t, uint q0, uint q1)
1136 int p0, p1, ticked;
1138 /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1139 t->q0 = q0;
1140 t->q1 = q1;
1141 /* compute desired p0,p1 from q0,q1 */
1142 p0 = q0-t->org;
1143 p1 = q1-t->org;
1144 ticked = 1;
1145 if(p0 < 0){
1146 ticked = 0;
1147 p0 = 0;
1149 if(p1 < 0)
1150 p1 = 0;
1151 if(p0 > t->fr.nchars)
1152 p0 = t->fr.nchars;
1153 if(p1 > t->fr.nchars){
1154 ticked = 0;
1155 p1 = t->fr.nchars;
1157 if(p0==t->fr.p0 && p1==t->fr.p1){
1158 if(p0 == p1 && ticked != t->fr.ticked)
1159 frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
1160 return;
1162 /* screen disagrees with desired selection */
1163 if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1164 /* no overlap or too easy to bother trying */
1165 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1166 if(p0 != p1 || ticked)
1167 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1168 goto Return;
1170 /* overlap; avoid unnecessary painting */
1171 if(p0 < t->fr.p0){
1172 /* extend selection backwards */
1173 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1174 }else if(p0 > t->fr.p0){
1175 /* trim first part of selection */
1176 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1178 if(p1 > t->fr.p1){
1179 /* extend selection forwards */
1180 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1181 }else if(p1 < t->fr.p1){
1182 /* trim last part of selection */
1183 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1186 Return:
1187 t->fr.p0 = p0;
1188 t->fr.p1 = p1;
1192 * Release the button in less than DELAY ms and it's considered a null selection
1193 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1195 enum {
1196 DELAY = 2,
1197 MINMOVE = 4
1200 uint
1201 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1203 uint p0, p1, q, tmp;
1204 ulong msec;
1205 Point mp, pt0, pt1, qt;
1206 int reg, b;
1208 mp = mc->m.xy;
1209 b = mc->m.buttons;
1210 msec = mc->m.msec;
1212 /* remove tick */
1213 if(f->p0 == f->p1)
1214 frtick(f, frptofchar(f, f->p0), 0);
1215 p0 = p1 = frcharofpt(f, mp);
1216 pt0 = frptofchar(f, p0);
1217 pt1 = frptofchar(f, p1);
1218 reg = 0;
1219 frtick(f, pt0, 1);
1220 do{
1221 q = frcharofpt(f, mc->m.xy);
1222 if(p1 != q){
1223 if(p0 == p1)
1224 frtick(f, pt0, 0);
1225 if(reg != region(q, p0)){ /* crossed starting point; reset */
1226 if(reg > 0)
1227 selrestore(f, pt0, p0, p1);
1228 else if(reg < 0)
1229 selrestore(f, pt1, p1, p0);
1230 p1 = p0;
1231 pt1 = pt0;
1232 reg = region(q, p0);
1233 if(reg == 0)
1234 frdrawsel0(f, pt0, p0, p1, col, display->white);
1236 qt = frptofchar(f, q);
1237 if(reg > 0){
1238 if(q > p1)
1239 frdrawsel0(f, pt1, p1, q, col, display->white);
1241 else if(q < p1)
1242 selrestore(f, qt, q, p1);
1243 }else if(reg < 0){
1244 if(q > p1)
1245 selrestore(f, pt1, p1, q);
1246 else
1247 frdrawsel0(f, qt, q, p1, col, display->white);
1249 p1 = q;
1250 pt1 = qt;
1252 if(p0 == p1)
1253 frtick(f, pt0, 1);
1254 flushimage(f->display, 1);
1255 readmouse(mc);
1256 }while(mc->m.buttons == b);
1257 if(mc->m.msec-msec < DELAY && p0!=p1
1258 && abs(mp.x-mc->m.xy.x)<MINMOVE
1259 && abs(mp.y-mc->m.xy.y)<MINMOVE) {
1260 if(reg > 0)
1261 selrestore(f, pt0, p0, p1);
1262 else if(reg < 0)
1263 selrestore(f, pt1, p1, p0);
1264 p1 = p0;
1266 if(p1 < p0){
1267 tmp = p0;
1268 p0 = p1;
1269 p1 = tmp;
1271 pt0 = frptofchar(f, p0);
1272 if(p0 == p1)
1273 frtick(f, pt0, 0);
1274 selrestore(f, pt0, p0, p1);
1275 /* restore tick */
1276 if(f->p0 == f->p1)
1277 frtick(f, frptofchar(f, f->p0), 1);
1278 flushimage(f->display, 1);
1279 *p1p = p1;
1280 return p0;
1283 int
1284 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1286 uint p0, p1;
1287 int buts;
1289 p0 = xselect(&t->fr, mousectl, high, &p1);
1290 buts = mousectl->m.buttons;
1291 if((buts & mask) == 0){
1292 *q0 = p0+t->org;
1293 *q1 = p1+t->org;
1296 while(mousectl->m.buttons)
1297 readmouse(mousectl);
1298 return buts;
1301 int
1302 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1304 int buts;
1306 *tp = nil;
1307 buts = textselect23(t, q0, q1, but2col, 4);
1308 if(buts & 4)
1309 return 0;
1310 if(buts & 1){ /* pick up argument */
1311 *tp = argtext;
1312 return 1;
1314 return 1;
1317 int
1318 textselect3(Text *t, uint *q0, uint *q1)
1320 int h;
1322 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1323 return h;
1326 static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1327 static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1328 static Rune left2[] = { '\n', 0 };
1329 static Rune left3[] = { '\'', '"', '`', 0 };
1331 static
1332 Rune *left[] = {
1333 left1,
1334 left2,
1335 left3,
1336 nil
1338 static
1339 Rune *right[] = {
1340 right1,
1341 left2,
1342 left3,
1343 nil
1346 void
1347 textdoubleclick(Text *t, uint *q0, uint *q1)
1349 int c, i;
1350 Rune *r, *l, *p;
1351 uint q;
1353 if(textclickhtmlmatch(t, q0, q1))
1354 return;
1356 for(i=0; left[i]!=nil; i++){
1357 q = *q0;
1358 l = left[i];
1359 r = right[i];
1360 /* try matching character to left, looking right */
1361 if(q == 0)
1362 c = '\n';
1363 else
1364 c = textreadc(t, q-1);
1365 p = runestrchr(l, c);
1366 if(p != nil){
1367 if(textclickmatch(t, c, r[p-l], 1, &q))
1368 *q1 = q-(c!='\n');
1369 return;
1371 /* try matching character to right, looking left */
1372 if(q == t->file->b.nc)
1373 c = '\n';
1374 else
1375 c = textreadc(t, q);
1376 p = runestrchr(r, c);
1377 if(p != nil){
1378 if(textclickmatch(t, c, l[p-r], -1, &q)){
1379 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1380 *q0 = q;
1381 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1382 (*q0)++;
1384 return;
1388 /* try filling out word to right */
1389 while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1390 (*q1)++;
1391 /* try filling out word to left */
1392 while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1393 (*q0)--;
1396 int
1397 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1399 Rune c;
1400 int nest;
1402 nest = 1;
1403 for(;;){
1404 if(dir > 0){
1405 if(*q == t->file->b.nc)
1406 break;
1407 c = textreadc(t, *q);
1408 (*q)++;
1409 }else{
1410 if(*q == 0)
1411 break;
1412 (*q)--;
1413 c = textreadc(t, *q);
1415 if(c == cr){
1416 if(--nest==0)
1417 return 1;
1418 }else if(c == cl)
1419 nest++;
1421 return cl=='\n' && nest==1;
1424 // Is the text starting at location q an html tag?
1425 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1426 // Set *q1, if non-nil, to the location after the tag.
1427 static int
1428 ishtmlstart(Text *t, uint q, uint *q1)
1430 int c, c1, c2;
1432 if(q+2 > t->file->b.nc)
1433 return 0;
1434 if(textreadc(t, q++) != '<')
1435 return 0;
1436 c = textreadc(t, q++);
1437 c1 = c;
1438 c2 = c;
1439 while(c != '>') {
1440 if(q >= t->file->b.nc)
1441 return 0;
1442 c2 = c;
1443 c = textreadc(t, q++);
1445 if(q1)
1446 *q1 = q;
1447 if(c1 == '/') // closing tag
1448 return -1;
1449 if(c2 == '/' || c2 == '!') // open + close tag or comment
1450 return 0;
1451 return 1;
1454 // Is the text ending at location q an html tag?
1455 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1456 // Set *q0, if non-nil, to the start of the tag.
1457 static int
1458 ishtmlend(Text *t, uint q, uint *q0)
1460 int c, c1, c2;
1462 if(q < 2)
1463 return 0;
1464 if(textreadc(t, --q) != '>')
1465 return 0;
1466 c = textreadc(t, --q);
1467 c1 = c;
1468 c2 = c;
1469 while(c != '<') {
1470 if(q == 0)
1471 return 0;
1472 c1 = c;
1473 c = textreadc(t, --q);
1475 if(q0)
1476 *q0 = q;
1477 if(c1 == '/') // closing tag
1478 return -1;
1479 if(c2 == '/' || c2 == '!') // open + close tag or comment
1480 return 0;
1481 return 1;
1484 int
1485 textclickhtmlmatch(Text *t, uint *q0, uint *q1)
1487 int depth, n;
1488 uint q, nq;
1490 q = *q0;
1491 // after opening tag? scan forward for closing tag
1492 if(ishtmlend(t, q, nil) == 1) {
1493 depth = 1;
1494 while(q < t->file->b.nc) {
1495 n = ishtmlstart(t, q, &nq);
1496 if(n != 0) {
1497 depth += n;
1498 if(depth == 0) {
1499 *q1 = q;
1500 return 1;
1502 q = nq;
1503 continue;
1505 q++;
1509 // before closing tag? scan backward for opening tag
1510 if(ishtmlstart(t, q, nil) == -1) {
1511 depth = -1;
1512 while(q > 0) {
1513 n = ishtmlend(t, q, &nq);
1514 if(n != 0) {
1515 depth += n;
1516 if(depth == 0) {
1517 *q0 = q;
1518 return 1;
1520 q = nq;
1521 continue;
1523 q--;
1527 return 0;
1530 uint
1531 textbacknl(Text *t, uint p, uint n)
1533 int i, j;
1535 /* look for start of this line if n==0 */
1536 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1537 n = 1;
1538 i = n;
1539 while(i-->0 && p>0){
1540 --p; /* it's at a newline now; back over it */
1541 if(p == 0)
1542 break;
1543 /* at 128 chars, call it a line anyway */
1544 for(j=128; --j>0 && p>0; p--)
1545 if(textreadc(t, p-1)=='\n')
1546 break;
1548 return p;
1551 void
1552 textsetorigin(Text *t, uint org, int exact)
1554 int i, a, fixup;
1555 Rune *r;
1556 uint n;
1558 if(org>0 && !exact){
1559 /* org is an estimate of the char posn; find a newline */
1560 /* don't try harder than 256 chars */
1561 for(i=0; i<256 && org<t->file->b.nc; i++){
1562 if(textreadc(t, org) == '\n'){
1563 org++;
1564 break;
1566 org++;
1569 a = org-t->org;
1570 fixup = 0;
1571 if(a>=0 && a<t->fr.nchars){
1572 frdelete(&t->fr, 0, a);
1573 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1575 else if(a<0 && -a<t->fr.nchars){
1576 n = t->org - org;
1577 r = runemalloc(n);
1578 bufread(&t->file->b, org, r, n);
1579 frinsert(&t->fr, r, r+n, 0);
1580 free(r);
1581 }else
1582 frdelete(&t->fr, 0, t->fr.nchars);
1583 t->org = org;
1584 textfill(t);
1585 textscrdraw(t);
1586 textsetselect(t, t->q0, t->q1);
1587 if(fixup && t->fr.p1 > t->fr.p0)
1588 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1591 void
1592 textreset(Text *t)
1594 t->file->seq = 0;
1595 t->eq0 = ~0;
1596 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1597 textsetselect(t, t->org, t->org);
1598 frdelete(&t->fr, 0, t->fr.nchars);
1599 t->org = 0;
1600 t->q0 = 0;
1601 t->q1 = 0;
1602 filereset(t->file);
1603 bufreset(&t->file->b);