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