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