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