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