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
textinit(Text * t,File * f,Rectangle r,Reffont * rf,Image * cols[NCOL])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
textredraw(Text * t,Rectangle r,Font * f,Image * b,int odx)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
textresize(Text * t,Rectangle r,int keepextra)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
textclose(Text * t)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
dircmp(const void * a,const void * b)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
textcolumnate(Text * t,Dirlist ** dlp,int ndl)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
textload(Text * t,uint q0,char * file,int setqid)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
textbsinsert(Text * t,uint q0,Rune * r,uint n,int tofile,int * nrp)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
textinsert(Text * t,uint q0,Rune * r,uint n,int tofile)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
typecommit(Text * t)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
textfill(Text * t)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
textdelete(Text * t,uint q0,uint q1,int tofile)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
textconstrain(Text * t,uint q0,uint q1,uint * p0,uint * p1)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
textreadc(Text * t,uint q)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
textbswidth(Text * t,Rune c)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
textfilewidth(Text * t,uint q0,int oneelement)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*
textcomplete(Text * t)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
texttype(Text * t,Rune r)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
textcommit(Text * t,int tofile)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
framescroll(Frame * f,int dl)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
textframescroll(Text * t,int dl)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
textselect(Text * t)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
textshow(Text * t,uint q0,uint q1,int doselect)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
region(int a,int b)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
selrestore(Frame * f,Point pt0,uint p0,uint p1)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
textsetselect(Text * t,uint q0,uint q1)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
xselect(Frame * f,Mousectl * mc,Image * col,uint * p1p)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
textselect23(Text * t,uint * q0,uint * q1,Image * high,int mask)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
textselect2(Text * t,uint * q0,uint * q1,Text ** tp)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
textselect3(Text * t,uint * q0,uint * q1)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
textdoubleclick(Text * t,uint * q0,uint * q1)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
textclickmatch(Text * t,int cl,int cr,int dir,uint * q)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
ishtmlstart(Text * t,uint q,uint * q1)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
ishtmlend(Text * t,uint q,uint * q0)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
textclickhtmlmatch(Text * t,uint * q0,uint * q1)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
textbacknl(Text * t,uint p,uint n)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
textsetorigin(Text * t,uint org,int exact)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
textreset(Text * t)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 }
1665