xref: /plan9/sys/src/cmd/abaco/text.c (revision 7ab27030036b6c877a6f81728daeda263d1ca3cf)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <memdraw.h>
5 #include <thread.h>
6 #include <cursor.h>
7 #include <mouse.h>
8 #include <keyboard.h>
9 #include <frame.h>
10 #include <plumb.h>
11 #include <html.h>
12 #include "dat.h"
13 #include "fns.h"
14 
15 Image	*tagcols[NCOL];
16 Image	*textcols[NCOL];
17 
18 
19 void
textinit(Text * t,Image * b,Rectangle r,Font * f,Image * cols[NCOL])20 textinit(Text *t, Image *b, Rectangle r, Font *f, Image *cols[NCOL])
21 {
22 	t->all = r;
23 	t->scrollr = r;
24 	t->scrollr.max.x = r.min.x+Scrollsize;
25 	t->lastsr = ZR;
26 	r.min.x += Scrollsize+Scrollgap;
27 	t->rs.nr = 0;
28 	memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
29 	textredraw(t, r, f, b);
30 }
31 
32 void
textredraw(Text * t,Rectangle r,Font * f,Image * b)33 textredraw(Text *t, Rectangle r, Font *f, Image *b)
34 {
35 	Rectangle r1;
36 
37 	frinit(t, r, f, b, t->Frame.cols);
38 	r1 = t->r;
39 	r1.min.x -= Scrollsize+Scrollgap;	/* back fill to scroll bar */
40 	draw(t->b, r1, t->cols[BACK], nil, ZP);
41 	t->maxtab = Maxtab*stringwidth(f, "0");
42 	textfill(t);
43 	textsetselect(t, t->q0, t->q1);
44 }
45 
46 int
textresize(Text * t,Image * b,Rectangle r)47 textresize(Text *t, Image *b, Rectangle r)
48 {
49 	if(Dy(r) > 0)
50 		r.max.y -= Dy(r)%t->font->height;
51 	else
52 		r.max.y = r.min.y;
53 
54 	t->all = r;
55 	t->scrollr = r;
56 	t->scrollr.max.x = r.min.x+Scrollsize;
57 	t->lastsr = ZR;
58 	r.min.x += Scrollsize+Scrollgap;
59 	frclear(t, 0);
60 	textredraw(t, r, t->font, b);
61 	if(t->what == Textarea)
62 		textscrdraw(t);
63 	return r.max.y;
64 }
65 
66 void
textclose(Text * t)67 textclose(Text *t)
68 {
69 	closerunestr(&t->rs);
70 	frclear(t, 1);
71 }
72 
73 void
textinsert(Text * t,uint q0,Rune * r,uint n)74 textinsert(Text *t, uint q0, Rune *r, uint n)
75 {
76 	if(n == 0)
77 		return;
78 
79 	t->rs.r = runerealloc(t->rs.r, t->rs.nr+n);
80 	runemove(t->rs.r+q0+n, t->rs.r+q0, t->rs.nr-q0);
81 	runemove(t->rs.r+q0, r, n);
82 	t->rs.nr += n;
83 	if(q0 < t->q1)
84 		t->q1 += n;
85 	if(q0 < t->q0)
86 		t->q0 += n;
87 	if(q0 < t->org)
88 		t->org += n;
89 	else if(q0 <= t->org+t->nchars)
90 		frinsert(t, r, r+n, q0-t->org);
91 }
92 
93 void
textfill(Text * t)94 textfill(Text *t)
95 {
96 	Rune *rp;
97 	int i, n, m, nl;
98 
99 	if(t->lastlinefull)
100 		return;
101 	rp = runemalloc(BUFSIZE*8);
102 	do{
103 		n = t->rs.nr-(t->org+t->nchars);
104 		if(n == 0)
105 			break;
106 		if(n > 2000)	/* educated guess at reasonable amount */
107 			n = 2000;
108 		runemove(rp, t->rs.r+(t->org+t->nchars), n);
109 		/*
110 		 * it's expensive to frinsert more than we need, so
111 		 * count newlines.
112 		 */
113 		nl = t->maxlines-t->nlines;
114 		m = 0;
115 		for(i=0; i<n; ){
116 			if(rp[i++] == '\n'){
117 				m++;
118 				if(m >= nl)
119 					break;
120 			}
121 		}
122 		frinsert(t, rp, rp+i, t->nchars);
123 	}while(t->lastlinefull == FALSE);
124 	free(rp);
125 }
126 
127 void
textdelete(Text * t,uint q0,uint q1)128 textdelete(Text *t, uint q0, uint q1)
129 {
130 	uint n, p0, p1;
131 
132 	n = q1-q0;
133 	if(n == 0)
134 		return;
135 
136 	runemove(t->rs.r+q0, t->rs.r+q1, t->rs.nr-q1);
137 	t->rs.nr -= n;
138 	if(q0 < t->q0)
139 		t->q0 -= min(n, t->q0-q0);
140 	if(q0 < t->q1)
141 		t->q1 -= min(n, t->q1-q0);
142 	if(q1 <= t->org)
143 		t->org -= n;
144 	else if(q0 < t->org+t->nchars){
145 		p1 = q1 - t->org;
146 		if(p1 > t->nchars)
147 			p1 = t->nchars;
148 		if(q0 < t->org){
149 			t->org = q0;
150 			p0 = 0;
151 		}else
152 			p0 = q0 - t->org;
153 		frdelete(t, p0, p1);
154 		textfill(t);
155 	}
156 	t->rs.r[t->rs.nr] = L'\0';
157 }
158 
159 int
textbswidth(Text * t,Rune c)160 textbswidth(Text *t, Rune c)
161 {
162 	uint q, eq;
163 	Rune r;
164 	int skipping;
165 
166 	/* there is known to be at least one character to erase */
167 	if(c == 0x08)	/* ^H: erase character */
168 		return 1;
169 	q = t->q0;
170 	skipping = TRUE;
171 	while(q > 0){
172 		r = t->rs.r[q-1];
173 		if(r == '\n'){		/* eat at most one more character */
174 			if(q == t->q0)	/* eat the newline */
175 				--q;
176 			break;
177 		}
178 		if(c == 0x17){
179 			eq = isalnum(r);
180 			if(eq && skipping)	/* found one; stop skipping */
181 				skipping = FALSE;
182 			else if(!eq && !skipping)
183 				break;
184 		}
185 		--q;
186 	}
187 	return t->q0-q;
188 }
189 
190 void
texttype(Text * t,Rune r)191 texttype(Text *t, Rune r)
192 {
193 	uint q0, q1;
194 	int nb, n;
195 	int nr;
196 	Rune *rp;
197 
198 	if(t->what!=Textarea && r=='\n'){
199 		if(t->what==Urltag)
200 			get(t, t, XXX, XXX, nil, 0);
201 		return;
202 	}
203 	if(t->what==Tag && (r==Kscrollonedown || r==Kscrolloneup))
204 		return;
205 
206 	nr = 1;
207 	rp = &r;
208 	switch(r){
209 	case Kleft:
210 		if(t->q0 > 0)
211 			textshow(t, t->q0-1, t->q0-1, TRUE);
212 		return;
213 	case Kright:
214 		if(t->q1 < t->rs.nr)
215 			textshow(t, t->q1+1, t->q1+1, TRUE);
216 		return;
217 	case Kdown:
218 		n = t->maxlines/3;
219 		goto case_Down;
220 	case Kscrollonedown:
221 		n = mousescrollsize(t->maxlines);
222 		if(n <= 0)
223 			n = 1;
224 		goto case_Down;
225 	case Kpgdown:
226 		n = 2*t->maxlines/3;
227 	case_Down:
228 		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
229 		textsetorigin(t, q0, TRUE);
230 		return;
231 	case Kup:
232 		n = t->maxlines/3;
233 		goto case_Up;
234 	case Kscrolloneup:
235 		n = mousescrollsize(t->maxlines);
236 		goto case_Up;
237 	case Kpgup:
238 		n = 2*t->maxlines/3;
239 	case_Up:
240 		q0 = textbacknl(t, t->org, n);
241 		textsetorigin(t, q0, TRUE);
242 		return;
243 	case Khome:
244 		textshow(t, 0, 0, FALSE);
245 		return;
246 	case Kend:
247 		textshow(t, t->rs.nr, t->rs.nr, FALSE);
248 		return;
249 	case 0x01:	/* ^A: beginning of line */
250 		/* go to where ^U would erase, if not already at BOL */
251 		nb = 0;
252 		if(t->q0>0 && t->rs.r[t->q0-1]!='\n')
253 			nb = textbswidth(t, 0x15);
254 		textshow(t, t->q0-nb, t->q0-nb, TRUE);
255 		return;
256 	case 0x05:	/* ^E: end of line */
257 		q0 = t->q0;
258 		while(q0<t->rs.nr && t->rs.r[q0]!='\n')
259 			q0++;
260 		textshow(t, q0, q0, TRUE);
261 		return;
262 	}
263 	if(t->q1 > t->q0)
264 		cut(t, t, TRUE, TRUE, nil, 0);
265 
266 	textshow(t, t->q0, t->q0, TRUE);
267 	switch(r){
268 	case 0x08:	/* ^H: erase character */
269 	case 0x15:	/* ^U: erase line */
270 	case 0x17:	/* ^W: erase word */
271 		if(t->q0 == 0)	/* nothing to erase */
272 			return;
273 		nb = textbswidth(t, r);
274 		q1 = t->q0;
275 		q0 = q1-nb;
276 		/* if selection is at beginning of window, avoid deleting invisible text */
277 		if(q0 < t->org){
278 			q0 = t->org;
279 			nb = q1-q0;
280 		}
281 		if(nb > 0){
282 			textdelete(t, q0, q0+nb);
283 			textsetselect(t, q0, q0);
284 		}
285 		return;
286 	}
287 	/* otherwise ordinary character; just insert */
288 	textinsert(t, t->q0, &r, 1);
289 	if(rp != &r)
290 		free(rp);
291 	textsetselect(t, t->q0+nr, t->q0+nr);
292 	if(t->what == Textarea)
293 		textscrdraw(t);
294 }
295 
296 static	Text	*clicktext;
297 static	uint	clickmsec;
298 static	Text	*selecttext;
299 static	uint	selectq;
300 
301 /*
302  * called from frame library
303  */
304 void
framescroll(Frame * f,int dl)305 framescroll(Frame *f, int dl)
306 {
307 	if(f != &selecttext->Frame)
308 		error("frameselect not right frame");
309 	textframescroll(selecttext, dl);
310 }
311 
312 void
textframescroll(Text * t,int dl)313 textframescroll(Text *t, int dl)
314 {
315 	uint q0;
316 
317 	if(dl == 0){
318 		scrsleep(100);
319 		return;
320 	}
321 	if(dl < 0){
322 		q0 = textbacknl(t, t->org, -dl);
323 		if(selectq > t->org+t->p0)
324 			textsetselect(t, t->org+t->p0, selectq);
325 		else
326 			textsetselect(t, selectq, t->org+t->p0);
327 	}else{
328 		if(t->org+t->nchars == t->rs.nr)
329 			return;
330 		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
331 		if(selectq > t->org+t->p1)
332 			textsetselect(t, t->org+t->p1, selectq);
333 		else
334 			textsetselect(t, selectq, t->org+t->p1);
335 	}
336 	textsetorigin(t, q0, TRUE);
337 }
338 
339 void
textselect(Text * t)340 textselect(Text *t)
341 {
342 	uint q0, q1;
343 	int b, x, y;
344 	int state;
345 
346 	selecttext = t;
347 	/*
348 	 * To have double-clicking and chording, we double-click
349 	 * immediately if it might make sense.
350 	 */
351 	b = mouse->buttons;
352 	q0 = t->q0;
353 	q1 = t->q1;
354 	selectq = t->org+frcharofpt(t, mouse->xy);
355 	if(clicktext==t && mouse->msec-clickmsec<500)
356 	if(q0==q1 && selectq==q0){
357 		textdoubleclick(t, &q0, &q1);
358 		textsetselect(t, q0, q1);
359 		flushimage(display, 1);
360 		x = mouse->xy.x;
361 		y = mouse->xy.y;
362 		/* stay here until something interesting happens */
363 		do
364 			readmouse(mousectl);
365 		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
366 		mouse->xy.x = x;	/* in case we're calling frselect */
367 		mouse->xy.y = y;
368 		q0 = t->q0;	/* may have changed */
369 		q1 = t->q1;
370 		selectq = q0;
371 	}
372 	if(mouse->buttons == b){
373 		t->Frame.scroll = framescroll;
374 		frselect(t, mousectl);
375 		/* horrible botch: while asleep, may have lost selection altogether */
376 		if(selectq > t->rs.nr)
377 			selectq = t->org + t->p0;
378 		t->Frame.scroll = nil;
379 		if(selectq < t->org)
380 			q0 = selectq;
381 		else
382 			q0 = t->org + t->p0;
383 		if(selectq > t->org+t->nchars)
384 			q1 = selectq;
385 		else
386 			q1 = t->org+t->p1;
387 	}
388 	if(q0 == q1){
389 		if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
390 			textdoubleclick(t, &q0, &q1);
391 			clicktext = nil;
392 		}else{
393 			clicktext = t;
394 			clickmsec = mouse->msec;
395 		}
396 	}else
397 		clicktext = nil;
398 	textsetselect(t, q0, q1);
399 	flushimage(display, 1);
400 	state = 0;	/* undo when possible; +1 for cut, -1 for paste */
401 	while(mouse->buttons){
402 		mouse->msec = 0;
403 		b = mouse->buttons;
404 		if((b&1) && (b&6)){
405 			if(b & 2){
406 				if(state==-1 && t->what==Textarea){
407 					textsetselect(t, q0, t->q0);
408 					state = 0;
409 				}else if(state != 1){
410 					cut(t, t, TRUE, TRUE, nil, 0);
411 					state = 1;
412 				}
413 			}else{
414 				if(state==1 && t->what==Textarea){
415 					textsetselect(t, q0, t->q1);
416 					state = 0;
417 				}else if(state != -1){
418 					paste(t, t, TRUE, FALSE, nil, 0);
419 					state = -1;
420 				}
421 			}
422 			textscrdraw(t);
423 		}
424 		flushimage(display, 1);
425 		while(mouse->buttons == b)
426 			readmouse(mousectl);
427 		clicktext = nil;
428 	}
429 }
430 
431 void
textshow(Text * t,uint q0,uint q1,int doselect)432 textshow(Text *t, uint q0, uint q1, int doselect)
433 {
434 	int qe;
435 	int nl;
436 	uint q;
437 
438 	if(t->what != Textarea){
439 		if(doselect)
440 			textsetselect(t, q0, q1);
441 		return;
442 	}
443 	if(doselect)
444 		textsetselect(t, q0, q1);
445 	qe = t->org+t->nchars;
446 	if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->rs.nr)))
447 		textscrdraw(t);
448 	else{
449 		nl = t->maxlines/4;
450 		q = textbacknl(t, q0, nl);
451 		/* avoid going backwards if trying to go forwards - long lines! */
452 		if(!(q0>t->org && q<t->org))
453 			textsetorigin(t, q, TRUE);
454 		while(q0 > t->org+t->nchars)
455 			textsetorigin(t, t->org+1, FALSE);
456 	}
457 }
458 
459 static
460 int
region(int a,int b)461 region(int a, int b)
462 {
463 	if(a < b)
464 		return -1;
465 	if(a == b)
466 		return 0;
467 	return 1;
468 }
469 
470 void
selrestore(Frame * f,Point pt0,uint p0,uint p1)471 selrestore(Frame *f, Point pt0, uint p0, uint p1)
472 {
473 	if(p1<=f->p0 || p0>=f->p1){
474 		/* no overlap */
475 		frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
476 		return;
477 	}
478 	if(p0>=f->p0 && p1<=f->p1){
479 		/* entirely inside */
480 		frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
481 		return;
482 	}
483 
484 	/* they now are known to overlap */
485 
486 	/* before selection */
487 	if(p0 < f->p0){
488 		frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
489 		p0 = f->p0;
490 		pt0 = frptofchar(f, p0);
491 	}
492 	/* after selection */
493 	if(p1 > f->p1){
494 		frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
495 		p1 = f->p1;
496 	}
497 	/* inside selection */
498 	frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
499 }
500 
501 void
textsetselect(Text * t,uint q0,uint q1)502 textsetselect(Text *t, uint q0, uint q1)
503 {
504 	int p0, p1;
505 
506 	/* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
507 	t->q0 = q0;
508 	t->q1 = q1;
509 	/* compute desired p0,p1 from q0,q1 */
510 	p0 = q0-t->org;
511 	p1 = q1-t->org;
512 	if(p0 < 0)
513 		p0 = 0;
514 	if(p1 < 0)
515 		p1 = 0;
516 	if(p0 > t->nchars)
517 		p0 = t->nchars;
518 	if(p1 > t->nchars)
519 		p1 = t->nchars;
520 	if(p0==t->p0 && p1==t->p1)
521 		return;
522 	/* screen disagrees with desired selection */
523 	if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
524 		/* no overlap or too easy to bother trying */
525 		frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
526 		frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
527 		goto Return;
528 	}
529 	/* overlap; avoid unnecessary painting */
530 	if(p0 < t->p0){
531 		/* extend selection backwards */
532 		frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
533 	}else if(p0 > t->p0){
534 		/* trim first part of selection */
535 		frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
536 	}
537 	if(p1 > t->p1){
538 		/* extend selection forwards */
539 		frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
540 	}else if(p1 < t->p1){
541 		/* trim last part of selection */
542 		frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
543 	}
544 
545     Return:
546 	t->p0 = p0;
547 	t->p1 = p1;
548 }
549 
550 
551 /*
552  * Release the button in less than DELAY ms and it's considered a null selection
553  * if the mouse hardly moved, regardless of whether it crossed a char boundary.
554  */
555 enum {
556 	DELAY = 2,
557 	MINMOVE = 4,
558 };
559 
560 uint
xselect(Frame * f,Mousectl * mc,Image * col,uint * p1p)561 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)	/* when called, button is down */
562 {
563 	uint p0, p1, q, tmp;
564 	ulong msec;
565 	Point mp, pt0, pt1, qt;
566 	int reg, b;
567 
568 	mp = mc->xy;
569 	b = mc->buttons;
570 	msec = mc->msec;
571 
572 	/* remove tick */
573 	if(f->p0 == f->p1)
574 		frtick(f, frptofchar(f, f->p0), 0);
575 	p0 = p1 = frcharofpt(f, mp);
576 	pt0 = frptofchar(f, p0);
577 	pt1 = frptofchar(f, p1);
578 	reg = 0;
579 	frtick(f, pt0, 1);
580 	do{
581 		q = frcharofpt(f, mc->xy);
582 		if(p1 != q){
583 			if(p0 == p1)
584 				frtick(f, pt0, 0);
585 			if(reg != region(q, p0)){	/* crossed starting point; reset */
586 				if(reg > 0)
587 					selrestore(f, pt0, p0, p1);
588 				else if(reg < 0)
589 					selrestore(f, pt1, p1, p0);
590 				p1 = p0;
591 				pt1 = pt0;
592 				reg = region(q, p0);
593 				if(reg == 0)
594 					frdrawsel0(f, pt0, p0, p1, col, display->white);
595 			}
596 			qt = frptofchar(f, q);
597 			if(reg > 0){
598 				if(q > p1)
599 					frdrawsel0(f, pt1, p1, q, col, display->white);
600 
601 				else if(q < p1)
602 					selrestore(f, qt, q, p1);
603 			}else if(reg < 0){
604 				if(q > p1)
605 					selrestore(f, pt1, p1, q);
606 				else
607 					frdrawsel0(f, qt, q, p1, col, display->white);
608 			}
609 			p1 = q;
610 			pt1 = qt;
611 		}
612 		if(p0 == p1)
613 			frtick(f, pt0, 1);
614 		flushimage(f->display, 1);
615 		readmouse(mc);
616 	}while(mc->buttons == b);
617 	if(mc->msec-msec < DELAY && p0!=p1
618 	&& abs(mp.x-mc->xy.x)<MINMOVE
619 	&& abs(mp.y-mc->xy.y)<MINMOVE) {
620 		if(reg > 0)
621 			selrestore(f, pt0, p0, p1);
622 		else if(reg < 0)
623 			selrestore(f, pt1, p1, p0);
624 		p1 = p0;
625 	}
626 	if(p1 < p0){
627 		tmp = p0;
628 		p0 = p1;
629 		p1 = tmp;
630 	}
631 	pt0 = frptofchar(f, p0);
632 	if(p0 == p1)
633 		frtick(f, pt0, 0);
634 	selrestore(f, pt0, p0, p1);
635 	/* restore tick */
636 	if(f->p0 == f->p1)
637 		frtick(f, frptofchar(f, f->p0), 1);
638 	flushimage(f->display, 1);
639 	*p1p = p1;
640 	return p0;
641 }
642 
643 int
textselect23(Text * t,uint * q0,uint * q1,Image * high,int mask)644 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
645 {
646 	uint p0, p1;
647 	int buts;
648 
649 	p0 = xselect(t, mousectl, high, &p1);
650 	buts = mousectl->buttons;
651 	if((buts & mask) == 0){
652 		*q0 = p0+t->org;
653 		*q1 = p1+t->org;
654 	}
655 
656 	while(mousectl->buttons)
657 		readmouse(mousectl);
658 	return buts;
659 }
660 
661 int
textselect2(Text * t,uint * q0,uint * q1,Text ** tp)662 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
663 {
664 	int buts;
665 
666 	*tp = nil;
667 	buts = textselect23(t, q0, q1, but2col, 4);
668 	if(buts & 4)
669 		return 0;
670 	if(buts & 1){	/* pick up argument */
671 		*tp = argtext;
672 		return 1;
673 	}
674 	return 1;
675 }
676 
677 int
textselect3(Text * t,uint * q0,uint * q1)678 textselect3(Text *t, uint *q0, uint *q1)
679 {
680 	int h;
681 
682 	h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
683 	return h;
684 }
685 
686 static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
687 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
688 static Rune left2[] =  { L'\n', 0 };
689 static Rune left3[] =  { L'\'', L'"', L'`', 0 };
690 
691 static
692 Rune *left[] = {
693 	left1,
694 	left2,
695 	left3,
696 	nil
697 };
698 static
699 Rune *right[] = {
700 	right1,
701 	left2,
702 	left3,
703 	nil
704 };
705 
706 void
textdoubleclick(Text * t,uint * q0,uint * q1)707 textdoubleclick(Text *t, uint *q0, uint *q1)
708 {
709 	int c, i;
710 	Rune *r, *l, *p;
711 	uint q;
712 
713 	if(t->rs.nr == 0)
714 		return;
715 
716 	for(i=0; left[i]!=nil; i++){
717 		q = *q0;
718 		l = left[i];
719 		r = right[i];
720 		/* try matching character to left, looking right */
721 		if(q == 0)
722 			c = '\n';
723 		else
724 			c = t->rs.r[q-1];
725 		p = runestrchr(l, c);
726 		if(p != nil){
727 			if(textclickmatch(t, c, t->rs.r[p-l], 1, &q))
728 				*q1 = q-(c!='\n');
729 			return;
730 		}
731 		/* try matching character to right, looking left */
732 		if(q == t->rs.nr)
733 			c = '\n';
734 		else
735 			c = t->rs.r[q];
736 		p = runestrchr(r, c);
737 		if(p != nil){
738 			if(textclickmatch(t, c, l[p-r], -1, &q)){
739 				*q1 = *q0+(*q0<t->rs.nr && c=='\n');
740 				*q0 = q;
741 				if(c!='\n' || q!=0 || t->rs.r[0]=='\n')
742 					(*q0)++;
743 			}
744 			return;
745 		}
746 	}
747 	/* try filling out word to right */
748 	while(*q1<t->rs.nr && isalnum(t->rs.r[*q1]))
749 		(*q1)++;
750 	/* try filling out word to left */
751 	while(*q0>0 && isalnum(t->rs.r[*q0-1]))
752 		(*q0)--;
753 }
754 
755 int
textclickmatch(Text * t,int cl,int cr,int dir,uint * q)756 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
757 {
758 	Rune c;
759 	int nest;
760 
761 	nest = 1;
762 	for(;;){
763 		if(dir > 0){
764 			if(*q == t->rs.nr)
765 				break;
766 			c = t->rs.r[*q];
767 			(*q)++;
768 		}else{
769 			if(*q == 0)
770 				break;
771 			(*q)--;
772 			c = t->rs.r[*q];
773 		}
774 		if(c == cr){
775 			if(--nest==0)
776 				return 1;
777 		}else if(c == cl)
778 			nest++;
779 	}
780 	return cl=='\n' && nest==1;
781 }
782 
783 uint
textbacknl(Text * t,uint p,uint n)784 textbacknl(Text *t, uint p, uint n)
785 {
786 	int i, j;
787 
788 	/* look for start of this line if n==0 */
789 	if(n==0 && p>0 && t->rs.r[p-1]!='\n')
790 		n = 1;
791 	i = n;
792 	while(i-->0 && p>0){
793 		--p;	/* it's at a newline now; back over it */
794 		if(p == 0)
795 			break;
796 		/* at 128 chars, call it a line anyway */
797 		for(j=128; --j>0 && p>0; p--)
798 			if(t->rs.r[p-1]=='\n')
799 				break;
800 	}
801 	return p;
802 }
803 
804 void
textsetorigin(Text * t,uint org,int exact)805 textsetorigin(Text *t, uint org, int exact)
806 {
807 	int i, a, fixup;
808 	Rune *r;
809 	uint n;
810 
811 	if(org>0 && !exact){
812 		/* org is an estimate of the char posn; find a newline */
813 		/* don't try harder than 256 chars */
814 		for(i=0; i<256 && org<t->rs.nr; i++){
815 			if(t->rs.r[org] == '\n'){
816 				org++;
817 				break;
818 			}
819 			org++;
820 		}
821 	}
822 	a = org-t->org;
823 	fixup = 0;
824 	if(a>=0 && a<t->nchars){
825 		frdelete(t, 0, a);
826 		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
827 	}else if(a<0 && -a<t->nchars){
828 		n = t->org - org;
829 		r = runemalloc(n);
830 		runemove(r, t->rs.r+org, n);
831 		frinsert(t, r, r+n, 0);
832 		free(r);
833 	}else
834 		frdelete(t, 0, t->nchars);
835 	t->org = org;
836 	textfill(t);
837 	textscrdraw(t);
838 	textsetselect(t, t->q0, t->q1);
839 	if(fixup && t->p1 > t->p0)
840 		frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
841 }
842 
843 void
textset(Text * t,Rune * r,int n)844 textset(Text *t, Rune*r, int n)
845 {
846 	textdelete(t, 0, t->rs.nr);
847 	textinsert(t, 0, r, n);
848 	textsetselect(t, t->q0, t->q1);
849 }
850 
851 void
textmouse(Text * t,Point xy,int but)852 textmouse(Text *t, Point xy, int but)
853 {
854 	Text *argt;
855 	uint q0, q1;
856 
857 	if(ptinrect(xy, t->scrollr)){
858 		if(t->what == Columntag)
859 			rowdragcol(&row, t->col, but);
860 		else if(t->what == Tag)
861 			coldragwin(t->col, t->w, but);
862 		else if(t->what == Textarea)
863 			textscroll(t, but);
864 		if(t->col)
865 			activecol = t->col;
866 		return;
867 	}
868 	if(but == 1){
869 		selpage = nil;
870 		textselect(t);
871 		argtext = t;
872 		seltext = t;
873 	}else if(but == 2){
874 		if(textselect2(t, &q0, &q1, &argt))
875 			execute(t, q0, q1, argt);
876 	}else if(but == 3){
877 		if(textselect3(t, &q0, &q1))
878 			look3(t, q0, q1);
879 	}
880 }
881