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