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