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