]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/abaco/text.c
8c145a1544a5c1e339f1c827dfd005d3efa25511
[plan9front.git] / sys / src / cmd / abaco / text.c
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
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
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
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
67 textclose(Text *t)
68 {
69         closerunestr(&t->rs);
70         frclear(t, 1);
71 }
72
73 void
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }