plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

text.c (33763B)


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