master ohmu/acme2k / src / cmd / acme / text.c
ohmu  ·  2026-09-21
   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
  16Image	*tagcols[NCOL];
  17Image	*textcols[NCOL];
  18static Rune Ldot[] = { '.', 0 };
  19
  20enum{
  21	TABDIR = 3	/* width of tabs in directory windows */
  22};
  23
  24void
  25textinit(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
  41void
  42textredraw(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
  73int
  74textresize(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
 100void
 101textclose(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
 120int
 121dircmp(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
 135void
 136textcolumnate(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
 192int
 193textload(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
 320uint
 321textbsinsert(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
 365void
 366textinsert(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
 414void
 415typecommit(Text *t)
 416{
 417	if(t->w != nil)
 418		wincommit(t->w, t);
 419	else
 420		textcommit(t, TRUE);
 421}
 422
 423void
 424textfill(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
 459void
 460textdelete(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
 516void
 517textconstrain(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
 523Rune
 524textreadc(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
 535int
 536textbswidth(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
 566int
 567textfilewidth(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
 584Rune*
 585textcomplete(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
 667void
 668texttype(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 Kscrollonedown:
 695		if(t->what == Tag)
 696			goto Tagdown;
 697		n = mousescrollsize(t->fr.maxlines);
 698		if(n <= 0)
 699			n = 1;
 700		goto case_Down;
 701	case Kpgdown:
 702		n = 2*t->fr.maxlines/3;
 703	case_Down:
 704		q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
 705		textsetorigin(t, q0, TRUE);
 706		return;	
 707	case Kscrolloneup:
 708		if(t->what == Tag)
 709			goto Tagup;
 710		n = mousescrollsize(t->fr.maxlines);
 711		goto case_Up;
 712	case Kpgup:
 713		n = 2*t->fr.maxlines/3;
 714	case_Up:
 715		q0 = textbacknl(t, t->org, n);
 716		textsetorigin(t, q0, TRUE);
 717		return;
 718
 719/* 
 720 *  Keybindings for moving the cursor up and 
 721 *  down the text via up and down arrow keys.
 722 */
 723	
 724	case Kdown:
 725		if(t->what == Tag)
 726 			goto Tagdown;
 727		typecommit(t);
 728		/* 1rst check for being in the last line*/	
 729		q0 = t->q0;
 730		q1 = q0;
 731		if (q1) q1--;
 732		nnb = 0;
 733		while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
 734			q0++;
 735		if (q0 == (t->file->b.nc)-1) {
 736			textshow(t, q0, q0, TRUE);
 737			return;
 738		}
 739		q0++;
 740		/* find old pos in ln */
 741		while(q1>1 && textreadc(t, q1)!='\n'){
 742			nnb++;
 743			q1--;
 744		}
 745		/* go right until reachg pos or \n */
 746		while(q0<t->file->b.nc && (nnb>0 && textreadc(t, q0)!='\n')){
 747			q0++;
 748			nnb--;
 749		}
 750		if (q0>1 && q0<t->file->b.nc)
 751			textshow(t, q0, q0, TRUE);
 752		return;
 753	case Kup:
 754		if(t->what == Tag)
 755			goto Tagup;
 756		typecommit(t);
 757		nnb = 0;
 758		if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
 759			nnb = textbswidth(t, 0x15); 
 760		/* BOL - 1 if not first line of txt BOL*/
 761		if( t->q0-nnb > 1  && textreadc(t, t->q0-nnb-1)=='\n' ) nnb++;
 762		textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
 763		return;
 764
 765/*
 766 *  Home and End now respectively take you to the
 767 *  beginning and to the end of the line respectively.
 768 *  Also keep the original ^A and ^E keybindings.
 769 */
 770
 771	case 0x01:
 772	case Khome:
 773		typecommit(t);
 774		/* go to where ^U would erase, if not already at BOL */
 775		nnb = 0;
 776		if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
 777			nnb = textbswidth(t, 0x15);
 778		textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
 779		return;
 780	case 0x05:
 781	case Kend:
 782		typecommit(t);
 783		q0 = t->q0;
 784		while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
 785			q0++;
 786		textshow(t, q0, q0, TRUE);
 787		return;
 788		
 789/* I'll keep the MAC-keybindings 'cuz im such a nice guy */
 790
 791	case Kcmd+'c':	/* %C: copy */
 792		typecommit(t);
 793		cut(t, t, nil, TRUE, FALSE, nil, 0);
 794		return;
 795	case Kcmd+'z':	/* %Z: undo */
 796	 	typecommit(t);
 797		undo(t, nil, nil, TRUE, 0, nil, 0);
 798		return;
 799	case Kcmd+'Z':	/* %-shift-Z: redo */
 800	 	typecommit(t);
 801		undo(t, nil, nil, FALSE, 0, nil, 0);
 802		return;	
 803
 804/*
 805 *  Adding Windows and X11 -compatible Ctrl+C, Ctrl+Z and
 806 *  Ctrl+Y (for redoing). TODO: find out how to check out for
 807 *  shift press, for Ctrl+Shift+Z in this godforsaken switch()
 808 */
 809
 810	case 0x03:	/* Ctrl+C: copy */
 811		typecommit(t);
 812		cut(t, t, nil, TRUE, FALSE, nil, 0);
 813		return;
 814	case 0x1A:	/* Ctrl+Z: undo */
 815	 	typecommit(t);
 816		undo(t, nil, nil, TRUE, 0, nil, 0);
 817		return;
 818	case 0x19:	/* Ctrl+Y: redo */
 819	 	typecommit(t);
 820		undo(t, nil, nil, FALSE, 0, nil, 0);
 821		return;
 822
 823	Tagdown:
 824		/* expand tag to show all text */
 825		if(!t->w->tagexpand){
 826		t->w->tagexpand = TRUE;
 827		winresize(t->w, t->w->r, FALSE, TRUE);
 828		}
 829		return;
 830	Tagup:
 831		/* shrink tag to single line */
 832		if(t->w->tagexpand){
 833			t->w->tagexpand = FALSE;
 834			t->w->taglines = 1;
 835			winresize(t->w, t->w->r, FALSE, TRUE);
 836		}
 837		return;
 838	}
 839	
 840
 841	
 842	if(t->what == Body){
 843		seq++;
 844		filemark(t->file);
 845	}
 846	
 847	/* cut/paste must be done after the seq++/filemark */
 848	
 849	switch(r){
 850	case Kcmd+'x':	/* %X: cut */
 851		typecommit(t);
 852		if(t->what == Body){
 853			seq++;
 854			filemark(t->file);
 855		}
 856		cut(t, t, nil, TRUE, TRUE, nil, 0);
 857		textshow(t, t->q0, t->q0, 1);
 858		t->iq1 = t->q0;
 859		return;
 860	case Kcmd+'v':	/* %V: paste */
 861		typecommit(t);
 862		if(t->what == Body){
 863			seq++;
 864			filemark(t->file);
 865		}
 866		paste(t, t, nil, TRUE, FALSE, nil, 0);
 867		textshow(t, t->q0, t->q1, 1);
 868		t->iq1 = t->q1;
 869		return;
 870	
 871	/* Same for Windows / X11 */
 872	
 873	case 0x18:	/* Ctrl+X: cut */
 874		typecommit(t);
 875		if(t->what == Body){
 876			seq++;
 877			filemark(t->file);
 878		}
 879		cut(t, t, nil, TRUE, TRUE, nil, 0);
 880		textshow(t, t->q0, t->q0, 1);
 881		t->iq1 = t->q0;
 882		return;
 883	case 0x16:	/* Ctrl+V: paste */
 884		typecommit(t);
 885		if(t->what == Body){
 886			seq++;
 887			filemark(t->file);
 888		}
 889		paste(t, t, nil, TRUE, FALSE, nil, 0);
 890		textshow(t, t->q0, t->q1, 1);
 891		t->iq1 = t->q1;
 892		return;
 893	}
 894
 895	if(t->q1 > t->q0){
 896		if(t->ncache != 0)
 897			error("text.type");
 898		cut(t, t, nil, TRUE, TRUE, nil, 0);
 899		t->eq0 = ~0;
 900	}
 901	textshow(t, t->q0, t->q0, 1);
 902	
 903	switch(r){
 904	case 0x06:	/* ^F: complete */
 905	case Kins:
 906		typecommit(t);
 907		rp = textcomplete(t);
 908		if(rp == nil)
 909			return;
 910		nr = runestrlen(rp);
 911		break;	/* fall through to normal insertion case */
 912	case 0x1B:
 913		if(t->eq0 != ~0) {
 914			if(t->eq0 <= t->q0)
 915				textsetselect(t, t->eq0, t->q0);
 916			else
 917				textsetselect(t, t->q0, t->eq0);
 918		}
 919		if(t->ncache > 0)
 920			typecommit(t);
 921		t->iq1 = t->q0;
 922		return;
 923
 924/*
 925 *  quick hack for DELETE-key, just added it to see what happens and
 926 *  apparently it works like ^U and erases whole lines. i should learn c
 927 */
 928 
 929		
 930	case 0x08:	/* ^H: erase character */
 931	case 0x15:	/* ^U: erase line */
 932	case 0x7F:
 933	case 0x17:	/* ^W: erase  word */
 934		if(t->q0 == 0)	/* nothing to erase */
 935			return;
 936		nnb = textbswidth(t, r);
 937		q1 = t->q0;
 938		q0 = q1-nnb;
 939		/* if selection is at beginning of window, avoid deleting invisible text */
 940		if(q0 < t->org){
 941			q0 = t->org;
 942			nnb = q1-q0;
 943		}
 944		if(nnb <= 0)
 945			return;
 946		for(i=0; i<t->file->ntext; i++){
 947			u = t->file->text[i];
 948			u->nofill = TRUE;
 949			nb = nnb;
 950			n = u->ncache;
 951			if(n > 0){
 952				if(q1 != u->cq0+n)
 953					error("text.type backspace");
 954				if(n > nb)
 955					n = nb;
 956				u->ncache -= n;
 957				textdelete(u, q1-n, q1, FALSE);
 958				nb -= n;
 959			}
 960			if(u->eq0==q1 || u->eq0==~0)
 961				u->eq0 = q0;
 962			if(nb && u==t)
 963				textdelete(u, q0, q0+nb, TRUE);
 964			if(u != t)
 965				textsetselect(u, u->q0, u->q1);
 966			else
 967				textsetselect(t, q0, q0);
 968			u->nofill = FALSE;
 969		}
 970		for(i=0; i<t->file->ntext; i++)
 971			textfill(t->file->text[i]);
 972		t->iq1 = t->q0;
 973		return;
 974	case '\n':
 975		if(t->w->autoindent){
 976			/* find beginning of previous line using backspace code */
 977			nnb = textbswidth(t, 0x15); /* ^U case */
 978			rp = runemalloc(nnb + 1);
 979			nr = 0;
 980			rp[nr++] = r;
 981			for(i=0; i<nnb; i++){
 982				r = textreadc(t, t->q0-nnb+i);
 983				if(r != ' ' && r != '\t')
 984					break;
 985				rp[nr++] = r;
 986			}
 987		}
 988		break; /* fall through to normal code */
 989	}
 990	/* otherwise ordinary character; just insert, typically in caches of all texts */
 991	for(i=0; i<t->file->ntext; i++){
 992		u = t->file->text[i];
 993		if(u->eq0 == ~0)
 994			u->eq0 = t->q0;
 995		if(u->ncache == 0)
 996			u->cq0 = t->q0;
 997		else if(t->q0 != u->cq0+u->ncache)
 998			error("text.type cq1");
 999		/*
1000		 * Change the tag before we add to ncache,
1001		 * so that if the window body is resized the
1002		 * commit will not find anything in ncache.
1003		 */
1004		if(u->what==Body && u->ncache == 0){
1005			u->needundo = TRUE;
1006			winsettag(t->w);
1007			u->needundo = FALSE;
1008		}
1009		textinsert(u, t->q0, rp, nr, FALSE);
1010		if(u != t)
1011			textsetselect(u, u->q0, u->q1);
1012		if(u->ncache+nr > u->ncachealloc){
1013			u->ncachealloc += 10 + nr;
1014			u->cache = runerealloc(u->cache, u->ncachealloc);
1015		}
1016		runemove(u->cache+u->ncache, rp, nr);
1017		u->ncache += nr;
1018	}
1019	if(rp != &r)
1020		free(rp);
1021	textsetselect(t, t->q0+nr, t->q0+nr);
1022	if(r=='\n' && t->w!=nil)
1023		wincommit(t->w, t);
1024	t->iq1 = t->q0;
1025}
1026
1027void
1028textcommit(Text *t, int tofile)
1029{
1030	if(t->ncache == 0)
1031		return;
1032	if(tofile)
1033		fileinsert(t->file, t->cq0, t->cache, t->ncache);
1034	if(t->what == Body){
1035		t->w->dirty = TRUE;
1036		t->w->utflastqid = -1;
1037	}
1038	t->ncache = 0;
1039}
1040
1041static	Text	*clicktext;
1042static	uint	clickmsec;
1043static	Text	*selecttext;
1044static	uint	selectq;
1045
1046/*
1047 * called from frame library
1048 */
1049void
1050framescroll(Frame *f, int dl)
1051{
1052	if(f != &selecttext->fr)
1053		error("frameselect not right frame");
1054	textframescroll(selecttext, dl);
1055}
1056
1057void
1058textframescroll(Text *t, int dl)
1059{
1060	uint q0;
1061
1062	if(dl == 0){
1063		scrsleep(100);
1064		return;
1065	}
1066	if(dl < 0){
1067		q0 = textbacknl(t, t->org, -dl);
1068		if(selectq > t->org+t->fr.p0)
1069			textsetselect(t, t->org+t->fr.p0, selectq);
1070		else
1071			textsetselect(t, selectq, t->org+t->fr.p0);
1072	}else{
1073		if(t->org+t->fr.nchars == t->file->b.nc)
1074			return;
1075		q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
1076		if(selectq > t->org+t->fr.p1)
1077			textsetselect(t, t->org+t->fr.p1, selectq);
1078		else
1079			textsetselect(t, selectq, t->org+t->fr.p1);
1080	}
1081	textsetorigin(t, q0, TRUE);
1082}
1083
1084
1085void
1086textselect(Text *t)
1087{
1088	uint q0, q1;
1089	int b, x, y;
1090	int state;
1091	enum { None, Cut, Paste };
1092
1093	selecttext = t;
1094	/*
1095	 * To have double-clicking and chording, we double-click
1096	 * immediately if it might make sense.
1097	 */
1098	b = mouse->buttons;
1099	q0 = t->q0;
1100	q1 = t->q1;
1101	selectq = t->org+frcharofpt(&t->fr, mouse->xy);
1102	if(clicktext==t && mouse->msec-clickmsec<500)
1103	if(q0==q1 && selectq==q0){
1104		textdoubleclick(t, &q0, &q1);
1105		textsetselect(t, q0, q1);
1106		flushimage(display, 1);
1107		x = mouse->xy.x;
1108		y = mouse->xy.y;
1109		/* stay here until something interesting happens */
1110		do
1111			readmouse(mousectl);
1112		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
1113		mouse->xy.x = x;	/* in case we're calling frselect */
1114		mouse->xy.y = y;
1115		q0 = t->q0;	/* may have changed */
1116		q1 = t->q1;
1117		selectq = q0;
1118	}
1119	if(mouse->buttons == b){
1120		t->fr.scroll = framescroll;
1121		frselect(&t->fr, mousectl);
1122		/* horrible botch: while asleep, may have lost selection altogether */
1123		if(selectq > t->file->b.nc)
1124			selectq = t->org + t->fr.p0;
1125		t->fr.scroll = nil;
1126		if(selectq < t->org)
1127			q0 = selectq;
1128		else
1129			q0 = t->org + t->fr.p0;
1130		if(selectq > t->org+t->fr.nchars)
1131			q1 = selectq;
1132		else
1133			q1 = t->org+t->fr.p1;
1134	}
1135	if(q0 == q1){
1136		if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
1137			textdoubleclick(t, &q0, &q1);
1138			clicktext = nil;
1139		}else{
1140			clicktext = t;
1141			clickmsec = mouse->msec;
1142		}
1143	}else
1144		clicktext = nil;
1145	textsetselect(t, q0, q1);
1146	flushimage(display, 1);
1147	state = None;	/* what we've done; undo when possible */
1148	while(mouse->buttons){
1149		mouse->msec = 0;
1150		b = mouse->buttons;
1151		if((b&1) && (b&6)){
1152			if(state==None && t->what==Body){
1153				seq++;
1154				filemark(t->w->body.file);
1155			}
1156			if(b & 2){
1157				if(state==Paste && t->what==Body){
1158					winundo(t->w, TRUE);
1159					textsetselect(t, q0, t->q1);
1160					state = None;
1161				}else if(state != Cut){
1162					cut(t, t, nil, TRUE, TRUE, nil, 0);
1163					state = Cut;
1164				}
1165			}else{
1166				if(state==Cut && t->what==Body){
1167					winundo(t->w, TRUE);
1168					textsetselect(t, q0, t->q1);
1169					state = None;
1170				}else if(state != Paste){
1171					paste(t, t, nil, TRUE, FALSE, nil, 0);
1172					state = Paste;
1173				}
1174			}
1175			textscrdraw(t);
1176			clearmouse();
1177		}
1178		flushimage(display, 1);
1179		while(mouse->buttons == b)
1180			readmouse(mousectl);
1181		clicktext = nil;
1182	}
1183}
1184
1185void
1186textshow(Text *t, uint q0, uint q1, int doselect)
1187{
1188	int qe;
1189	int nl;
1190	int tsd;
1191	int nc;
1192	uint q;
1193
1194	if(t->what != Body){
1195		if(doselect)
1196			textsetselect(t, q0, q1);
1197		return;
1198	}
1199	if(t->w!=nil && t->fr.maxlines==0)
1200		colgrow(t->col, t->w, 1);
1201	if(doselect)
1202		textsetselect(t, q0, q1);
1203	qe = t->org+t->fr.nchars;
1204	tsd = FALSE;	/* do we call textscrdraw? */
1205	nc = t->file->b.nc+t->ncache;
1206	if(t->org <= q0){
1207		if(nc==0 || q0<qe)
1208			tsd = TRUE;
1209		else if(q0==qe && qe==nc){
1210			if(textreadc(t, nc-1) == '\n'){
1211				if(t->fr.nlines<t->fr.maxlines)
1212					tsd = TRUE;
1213			}else
1214				tsd = TRUE;
1215		}
1216	}
1217	if(tsd)
1218		textscrdraw(t);
1219	else{
1220		if(t->w->nopen[QWevent] > 0)
1221			nl = 3*t->fr.maxlines/4;
1222		else
1223			nl = t->fr.maxlines/4;
1224		q = textbacknl(t, q0, nl);
1225		/* avoid going backwards if trying to go forwards - long lines! */
1226		if(!(q0>t->org && q<t->org))
1227			textsetorigin(t, q, TRUE);
1228		while(q0 > t->org+t->fr.nchars)
1229			textsetorigin(t, t->org+1, FALSE);
1230	}
1231}
1232
1233static
1234int
1235region(int a, int b)
1236{
1237	if(a < b)
1238		return -1;
1239	if(a == b)
1240		return 0;
1241	return 1;
1242}
1243
1244void
1245selrestore(Frame *f, Point pt0, uint p0, uint p1)
1246{
1247	if(p1<=f->p0 || p0>=f->p1){
1248		/* no overlap */
1249		frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1250		return;
1251	}
1252	if(p0>=f->p0 && p1<=f->p1){
1253		/* entirely inside */
1254		frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1255		return;
1256	}
1257
1258	/* they now are known to overlap */
1259
1260	/* before selection */
1261	if(p0 < f->p0){
1262		frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1263		p0 = f->p0;
1264		pt0 = frptofchar(f, p0);
1265	}
1266	/* after selection */
1267	if(p1 > f->p1){
1268		frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1269		p1 = f->p1;
1270	}
1271	/* inside selection */
1272	frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1273}
1274
1275void
1276textsetselect(Text *t, uint q0, uint q1)
1277{
1278	int p0, p1, ticked;
1279	
1280	/* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1281	t->q0 = q0;
1282	t->q1 = q1;
1283	/* compute desired p0,p1 from q0,q1 */
1284	p0 = q0-t->org;
1285	p1 = q1-t->org;
1286	ticked = 1;
1287	if(p0 < 0){
1288		ticked = 0;
1289		p0 = 0;
1290	}
1291	if(p1 < 0)
1292		p1 = 0;
1293	if(p0 > t->fr.nchars)
1294		p0 = t->fr.nchars;
1295	if(p1 > t->fr.nchars){
1296		ticked = 0;
1297		p1 = t->fr.nchars;
1298	}
1299	if(p0==t->fr.p0 && p1==t->fr.p1){
1300		if(p0 == p1 && ticked != t->fr.ticked)
1301			frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
1302		return;
1303	}
1304	if(p0 > p1)
1305		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);
1306	/* screen disagrees with desired selection */
1307	if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1308		/* no overlap or too easy to bother trying */
1309		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1310		if(p0 != p1 || ticked)
1311			frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1312		goto Return;
1313	}
1314	/* overlap; avoid unnecessary painting */
1315	if(p0 < t->fr.p0){
1316		/* extend selection backwards */
1317		frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1318	}else if(p0 > t->fr.p0){
1319		/* trim first part of selection */
1320		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1321	}
1322	if(p1 > t->fr.p1){
1323		/* extend selection forwards */
1324		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1325	}else if(p1 < t->fr.p1){
1326		/* trim last part of selection */
1327		frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1328	}
1329
1330    Return:
1331	t->fr.p0 = p0;
1332	t->fr.p1 = p1;
1333}
1334
1335/*
1336 * Release the button in less than DELAY ms and it's considered a null selection
1337 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1338 */
1339enum {
1340	DELAY = 2,
1341	MINMOVE = 4
1342};
1343
1344uint
1345xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)	/* when called, button is down */
1346{
1347	uint p0, p1, q, tmp;
1348	ulong msec;
1349	Point mp, pt0, pt1, qt;
1350	int reg, b;
1351
1352	mp = mc->m.xy;
1353	b = mc->m.buttons;
1354	msec = mc->m.msec;
1355
1356	/* remove tick */
1357	if(f->p0 == f->p1)
1358		frtick(f, frptofchar(f, f->p0), 0);
1359	p0 = p1 = frcharofpt(f, mp);
1360	pt0 = frptofchar(f, p0);
1361	pt1 = frptofchar(f, p1);
1362	reg = 0;
1363	frtick(f, pt0, 1);
1364	do{
1365		q = frcharofpt(f, mc->m.xy);
1366		if(p1 != q){
1367			if(p0 == p1)
1368				frtick(f, pt0, 0);
1369			if(reg != region(q, p0)){	/* crossed starting point; reset */
1370				if(reg > 0)
1371					selrestore(f, pt0, p0, p1);
1372				else if(reg < 0)
1373					selrestore(f, pt1, p1, p0);
1374				p1 = p0;
1375				pt1 = pt0;
1376				reg = region(q, p0);
1377				if(reg == 0)
1378					frdrawsel0(f, pt0, p0, p1, col, display->white);
1379			}
1380			qt = frptofchar(f, q);
1381			if(reg > 0){
1382				if(q > p1)
1383					frdrawsel0(f, pt1, p1, q, col, display->white);
1384
1385				else if(q < p1)
1386					selrestore(f, qt, q, p1);
1387			}else if(reg < 0){
1388				if(q > p1)
1389					selrestore(f, pt1, p1, q);
1390				else
1391					frdrawsel0(f, qt, q, p1, col, display->white);
1392			}
1393			p1 = q;
1394			pt1 = qt;
1395		}
1396		if(p0 == p1)
1397			frtick(f, pt0, 1);
1398		flushimage(f->display, 1);
1399		readmouse(mc);
1400	}while(mc->m.buttons == b);
1401	if(mc->m.msec-msec < DELAY && p0!=p1
1402	&& abs(mp.x-mc->m.xy.x)<MINMOVE
1403	&& abs(mp.y-mc->m.xy.y)<MINMOVE) {
1404		if(reg > 0)
1405			selrestore(f, pt0, p0, p1);
1406		else if(reg < 0)
1407			selrestore(f, pt1, p1, p0);
1408		p1 = p0;
1409	}
1410	if(p1 < p0){
1411		tmp = p0;
1412		p0 = p1;
1413		p1 = tmp;
1414	}
1415	pt0 = frptofchar(f, p0);
1416	if(p0 == p1)
1417		frtick(f, pt0, 0);
1418	selrestore(f, pt0, p0, p1);
1419	/* restore tick */
1420	if(f->p0 == f->p1)
1421		frtick(f, frptofchar(f, f->p0), 1);
1422	flushimage(f->display, 1);
1423	*p1p = p1;
1424	return p0;
1425}
1426
1427int
1428textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1429{
1430	uint p0, p1;
1431	int buts;
1432	
1433	p0 = xselect(&t->fr, mousectl, high, &p1);
1434	buts = mousectl->m.buttons;
1435	if((buts & mask) == 0){
1436		*q0 = p0+t->org;
1437		*q1 = p1+t->org;
1438	}
1439
1440	while(mousectl->m.buttons)
1441		readmouse(mousectl);
1442	return buts;
1443}
1444
1445int
1446textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1447{
1448	int buts;
1449
1450	*tp = nil;
1451	buts = textselect23(t, q0, q1, but2col, 4);
1452	if(buts & 4)
1453		return 0;
1454	if(buts & 1){	/* pick up argument */
1455		*tp = argtext;
1456		return 1;
1457	}
1458	return 1;
1459}
1460
1461int
1462textselect3(Text *t, uint *q0, uint *q1)
1463{
1464	int h;
1465
1466	h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1467	return h;
1468}
1469
1470static Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
1471static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1472static Rune left2[] =  { '\n', 0 };
1473static Rune left3[] =  { '\'', '"', '`', 0 };
1474
1475static
1476Rune *left[] = {
1477	left1,
1478	left2,
1479	left3,
1480	nil
1481};
1482static
1483Rune *right[] = {
1484	right1,
1485	left2,
1486	left3,
1487	nil
1488};
1489
1490void
1491textdoubleclick(Text *t, uint *q0, uint *q1)
1492{
1493	int c, i;
1494	Rune *r, *l, *p;
1495	uint q;
1496
1497	if(textclickhtmlmatch(t, q0, q1))
1498		return;
1499	
1500	for(i=0; left[i]!=nil; i++){
1501		q = *q0;
1502		l = left[i];
1503		r = right[i];
1504		/* try matching character to left, looking right */
1505		if(q == 0)
1506			c = '\n';
1507		else
1508			c = textreadc(t, q-1);
1509		p = runestrchr(l, c);
1510		if(p != nil){
1511			if(textclickmatch(t, c, r[p-l], 1, &q))
1512				*q1 = q-(c!='\n');
1513			return;
1514		}
1515		/* try matching character to right, looking left */
1516		if(q == t->file->b.nc)
1517			c = '\n';
1518		else
1519			c = textreadc(t, q);
1520		p = runestrchr(r, c);
1521		if(p != nil){
1522			if(textclickmatch(t, c, l[p-r], -1, &q)){
1523				*q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1524				*q0 = q;
1525				if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1526					(*q0)++;
1527			}
1528			return;
1529		}
1530	}
1531	
1532	/* try filling out word to right */
1533	while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1534		(*q1)++;
1535	/* try filling out word to left */
1536	while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1537		(*q0)--;
1538}
1539
1540int
1541textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1542{
1543	Rune c;
1544	int nest;
1545
1546	nest = 1;
1547	for(;;){
1548		if(dir > 0){
1549			if(*q == t->file->b.nc)
1550				break;
1551			c = textreadc(t, *q);
1552			(*q)++;
1553		}else{
1554			if(*q == 0)
1555				break;
1556			(*q)--;
1557			c = textreadc(t, *q);
1558		}
1559		if(c == cr){
1560			if(--nest==0)
1561				return 1;
1562		}else if(c == cl)
1563			nest++;
1564	}
1565	return cl=='\n' && nest==1;
1566}
1567
1568// Is the text starting at location q an html tag?
1569// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1570// Set *q1, if non-nil, to the location after the tag.
1571static int
1572ishtmlstart(Text *t, uint q, uint *q1)
1573{
1574	int c, c1, c2;
1575
1576	if(q+2 > t->file->b.nc)
1577		return 0;
1578	if(textreadc(t, q++) != '<')
1579		return 0;
1580	c = textreadc(t, q++);
1581	c1 = c;
1582	c2 = c;
1583	while(c != '>') {
1584		if(q >= t->file->b.nc)
1585			return 0;
1586		c2 = c;
1587		c = textreadc(t, q++);
1588	}
1589	if(q1)
1590		*q1 = q;
1591	if(c1 == '/')	// closing tag
1592		return -1;
1593	if(c2 == '/' || c2 == '!')	// open + close tag or comment
1594		return 0;
1595	return 1;
1596}
1597
1598// Is the text ending at location q an html tag?
1599// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1600// Set *q0, if non-nil, to the start of the tag.
1601static int
1602ishtmlend(Text *t, uint q, uint *q0)
1603{
1604	int c, c1, c2;
1605	
1606	if(q < 2)
1607		return 0;
1608	if(textreadc(t, --q) != '>')
1609		return 0;
1610	c = textreadc(t, --q);
1611	c1 = c;
1612	c2 = c;
1613	while(c != '<') {
1614		if(q == 0)
1615			return 0;
1616		c1 = c;
1617		c = textreadc(t, --q);
1618	}
1619	if(q0)
1620		*q0 = q;
1621	if(c1 == '/')	// closing tag
1622		return -1;
1623	if(c2 == '/' || c2 == '!')	// open + close tag or comment
1624		return 0;
1625	return 1;
1626}
1627
1628int
1629textclickhtmlmatch(Text *t, uint *q0, uint *q1)
1630{
1631	int depth, n;
1632	uint q, nq;
1633	
1634	q = *q0;
1635	// after opening tag?  scan forward for closing tag
1636	if(ishtmlend(t, q, nil) == 1) {
1637		depth = 1;
1638		while(q < t->file->b.nc) {
1639			n = ishtmlstart(t, q, &nq);
1640			if(n != 0) {
1641				depth += n;
1642				if(depth == 0) {
1643					*q1 = q;
1644					return 1;
1645				}
1646				q = nq;
1647				continue;
1648			}
1649			q++;
1650		}
1651	}
1652
1653	// before closing tag?  scan backward for opening tag
1654	if(ishtmlstart(t, q, nil) == -1) {
1655		depth = -1;
1656		while(q > 0) {
1657			n = ishtmlend(t, q, &nq);
1658			if(n != 0) {
1659				depth += n;
1660				if(depth == 0) {
1661					*q0 = q;
1662					return 1;
1663				}
1664				q = nq;
1665				continue;
1666			}
1667			q--;
1668		}
1669	}
1670	
1671	return 0;
1672}
1673
1674uint
1675textbacknl(Text *t, uint p, uint n)
1676{
1677	int i, j;
1678
1679	/* look for start of this line if n==0 */
1680	if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1681		n = 1;
1682	i = n;
1683	while(i-->0 && p>0){
1684		--p;	/* it's at a newline now; back over it */
1685		if(p == 0)
1686			break;
1687		/* at 128 chars, call it a line anyway */
1688		for(j=128; --j>0 && p>0; p--)
1689			if(textreadc(t, p-1)=='\n')
1690				break;
1691	}
1692	return p;
1693}
1694
1695void
1696textsetorigin(Text *t, uint org, int exact)
1697{
1698	int i, a, fixup;
1699	Rune *r;
1700	uint n;
1701
1702	if(org>0 && !exact && textreadc(t, org-1) != '\n'){
1703		/* org is an estimate of the char posn; find a newline */
1704		/* don't try harder than 256 chars */
1705		for(i=0; i<256 && org<t->file->b.nc; i++){
1706			if(textreadc(t, org) == '\n'){
1707				org++;
1708				break;
1709			}
1710			org++;
1711		}
1712	}
1713	a = org-t->org;
1714	fixup = 0;
1715	if(a>=0 && a<t->fr.nchars){
1716		frdelete(&t->fr, 0, a);
1717		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1718	}
1719	else if(a<0 && -a<t->fr.nchars){
1720		n = t->org - org;
1721		r = runemalloc(n);
1722		bufread(&t->file->b, org, r, n);
1723		frinsert(&t->fr, r, r+n, 0);
1724		free(r);
1725	}else
1726		frdelete(&t->fr, 0, t->fr.nchars);
1727	t->org = org;
1728	textfill(t);
1729	textscrdraw(t);
1730	textsetselect(t, t->q0, t->q1);
1731	if(fixup && t->fr.p1 > t->fr.p0)
1732		frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1733}
1734
1735void
1736textreset(Text *t)
1737{
1738	t->file->seq = 0;
1739	t->eq0 = ~0;
1740	/* do t->delete(0, t->nc, TRUE) without building backup stuff */
1741	textsetselect(t, t->org, t->org);
1742	frdelete(&t->fr, 0, t->fr.nchars);
1743	t->org = 0;
1744	t->q0 = 0;
1745	t->q1 = 0;
1746	filereset(t->file);
1747	bufreset(&t->file->b);
1748}