16 Image *textcols[NCOL];
20 textinit(Text *t, Image *b, Rectangle r, Font *f, Image *cols[NCOL])
24 t->scrollr.max.x = r.min.x+Scrollsize;
26 r.min.x += Scrollsize+Scrollgap;
28 memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
29 textredraw(t, r, f, b);
33 textredraw(Text *t, Rectangle r, Font *f, Image *b)
37 frinit(t, r, f, b, t->Frame.cols);
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");
43 textsetselect(t, t->q0, t->q1);
47 textresize(Text *t, Image *b, Rectangle r)
50 r.max.y -= Dy(r)%t->font->height;
56 t->scrollr.max.x = r.min.x+Scrollsize;
58 r.min.x += Scrollsize+Scrollgap;
60 textredraw(t, r, t->font, b);
61 if(t->what == Textarea)
74 textinsert(Text *t, uint q0, Rune *r, uint n)
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);
89 else if(q0 <= t->org+t->nchars)
90 frinsert(t, r, r+n, q0-t->org);
101 rp = runemalloc(BUFSIZE*8);
103 n = t->rs.nr-(t->org+t->nchars);
106 if(n > 2000) /* educated guess at reasonable amount */
108 runemove(rp, t->rs.r+(t->org+t->nchars), n);
110 * it's expensive to frinsert more than we need, so
113 nl = t->maxlines-t->nlines;
122 frinsert(t, rp, rp+i, t->nchars);
123 }while(t->lastlinefull == FALSE);
128 textdelete(Text *t, uint q0, uint q1)
136 runemove(t->rs.r+q0, t->rs.r+q1, t->rs.nr-q1);
139 t->q0 -= min(n, t->q0-q0);
141 t->q1 -= min(n, t->q1-q0);
144 else if(q0 < t->org+t->nchars){
156 t->rs.r[t->rs.nr] = L'\0';
160 textbswidth(Text *t, Rune c)
166 /* there is known to be at least one character to erase */
167 if(c == 0x08) /* ^H: erase character */
173 if(r == '\n'){ /* eat at most one more character */
174 if(q == t->q0) /* eat the newline */
180 if(eq && skipping) /* found one; stop skipping */
182 else if(!eq && !skipping)
191 texttype(Text *t, Rune r)
198 if(t->what!=Textarea && r=='\n'){
200 get(t, t, XXX, XXX, nil, 0);
203 if(t->what==Tag && (r==Kscrollonedown || r==Kscrolloneup))
211 textshow(t, t->q0-1, t->q0-1, TRUE);
215 textshow(t, t->q1+1, t->q1+1, TRUE);
221 n = mousescrollsize(t->maxlines);
228 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
229 textsetorigin(t, q0, TRUE);
235 n = mousescrollsize(t->maxlines);
240 q0 = textbacknl(t, t->org, n);
241 textsetorigin(t, q0, TRUE);
244 textshow(t, 0, 0, FALSE);
247 textshow(t, t->rs.nr, t->rs.nr, FALSE);
249 case 0x01: /* ^A: beginning of line */
250 /* go to where ^U would erase, if not already at BOL */
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);
256 case 0x05: /* ^E: end of line */
258 while(q0<t->rs.nr && t->rs.r[q0]!='\n')
260 textshow(t, q0, q0, TRUE);
264 cut(t, t, TRUE, TRUE, nil, 0);
266 textshow(t, t->q0, t->q0, TRUE);
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 */
273 nb = textbswidth(t, r);
276 /* if selection is at beginning of window, avoid deleting invisible text */
282 textdelete(t, q0, q0+nb);
283 textsetselect(t, q0, q0);
287 /* otherwise ordinary character; just insert */
288 textinsert(t, t->q0, &r, 1);
291 textsetselect(t, t->q0+nr, t->q0+nr);
292 if(t->what == Textarea)
296 static Text *clicktext;
297 static uint clickmsec;
298 static Text *selecttext;
302 * called from frame library
305 framescroll(Frame *f, int dl)
307 if(f != &selecttext->Frame)
308 error("frameselect not right frame");
309 textframescroll(selecttext, dl);
313 textframescroll(Text *t, int dl)
322 q0 = textbacknl(t, t->org, -dl);
323 if(selectq > t->org+t->p0)
324 textsetselect(t, t->org+t->p0, selectq);
326 textsetselect(t, selectq, t->org+t->p0);
328 if(t->org+t->nchars == t->rs.nr)
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);
334 textsetselect(t, selectq, t->org+t->p1);
336 textsetorigin(t, q0, TRUE);
348 * To have double-clicking and chording, we double-click
349 * immediately if it might make sense.
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);
362 /* stay here until something interesting happens */
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 */
368 q0 = t->q0; /* may have changed */
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;
383 if(selectq > t->org+t->nchars)
389 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
390 textdoubleclick(t, &q0, &q1);
394 clickmsec = mouse->msec;
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){
406 if(state==-1 && t->what==Textarea){
407 textsetselect(t, q0, t->q0);
409 }else if(state != 1){
410 cut(t, t, TRUE, TRUE, nil, 0);
414 if(state==1 && t->what==Textarea){
415 textsetselect(t, q0, t->q1);
417 }else if(state != -1){
418 paste(t, t, TRUE, FALSE, nil, 0);
424 flushimage(display, 1);
425 while(mouse->buttons == b)
432 textshow(Text *t, uint q0, uint q1, int doselect)
438 if(t->what != Textarea){
440 textsetselect(t, q0, q1);
444 textsetselect(t, q0, q1);
445 qe = t->org+t->nchars;
446 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->rs.nr)))
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);
471 selrestore(Frame *f, Point pt0, uint p0, uint p1)
473 if(p1<=f->p0 || p0>=f->p1){
475 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
478 if(p0>=f->p0 && p1<=f->p1){
479 /* entirely inside */
480 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
484 /* they now are known to overlap */
486 /* before selection */
488 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
490 pt0 = frptofchar(f, p0);
492 /* after selection */
494 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
497 /* inside selection */
498 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
502 textsetselect(Text *t, uint q0, uint q1)
506 /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
509 /* compute desired p0,p1 from q0,q1 */
520 if(p0==t->p0 && p1==t->p1)
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);
529 /* overlap; avoid unnecessary painting */
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);
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);
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.
561 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
565 Point mp, pt0, pt1, qt;
574 frtick(f, frptofchar(f, f->p0), 0);
575 p0 = p1 = frcharofpt(f, mp);
576 pt0 = frptofchar(f, p0);
577 pt1 = frptofchar(f, p1);
581 q = frcharofpt(f, mc->xy);
585 if(reg != region(q, p0)){ /* crossed starting point; reset */
587 selrestore(f, pt0, p0, p1);
589 selrestore(f, pt1, p1, p0);
594 frdrawsel0(f, pt0, p0, p1, col, display->white);
596 qt = frptofchar(f, q);
599 frdrawsel0(f, pt1, p1, q, col, display->white);
602 selrestore(f, qt, q, p1);
605 selrestore(f, pt1, p1, q);
607 frdrawsel0(f, qt, q, p1, col, display->white);
614 flushimage(f->display, 1);
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) {
621 selrestore(f, pt0, p0, p1);
623 selrestore(f, pt1, p1, p0);
631 pt0 = frptofchar(f, p0);
634 selrestore(f, pt0, p0, p1);
637 frtick(f, frptofchar(f, f->p0), 1);
638 flushimage(f->display, 1);
644 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
649 p0 = xselect(t, mousectl, high, &p1);
650 buts = mousectl->buttons;
651 if((buts & mask) == 0){
656 while(mousectl->buttons)
662 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
667 buts = textselect23(t, q0, q1, but2col, 4);
670 if(buts & 1){ /* pick up argument */
678 textselect3(Text *t, uint *q0, uint *q1)
682 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
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 };
707 textdoubleclick(Text *t, uint *q0, uint *q1)
716 for(i=0; left[i]!=nil; i++){
720 /* try matching character to left, looking right */
725 p = runestrchr(l, c);
727 if(textclickmatch(t, c, t->rs.r[p-l], 1, &q))
731 /* try matching character to right, looking left */
736 p = runestrchr(r, c);
738 if(textclickmatch(t, c, l[p-r], -1, &q)){
739 *q1 = *q0+(*q0<t->rs.nr && c=='\n');
741 if(c!='\n' || q!=0 || t->rs.r[0]=='\n')
747 /* try filling out word to right */
748 while(*q1<t->rs.nr && isalnum(t->rs.r[*q1]))
750 /* try filling out word to left */
751 while(*q0>0 && isalnum(t->rs.r[*q0-1]))
756 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
780 return cl=='\n' && nest==1;
784 textbacknl(Text *t, uint p, uint n)
788 /* look for start of this line if n==0 */
789 if(n==0 && p>0 && t->rs.r[p-1]!='\n')
793 --p; /* it's at a newline now; back over it */
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')
805 textsetorigin(Text *t, uint org, int 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'){
824 if(a>=0 && a<t->nchars){
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){
830 runemove(r, t->rs.r+org, n);
831 frinsert(t, r, r+n, 0);
834 frdelete(t, 0, t->nchars);
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);
844 textset(Text *t, Rune*r, int n)
846 textdelete(t, 0, t->rs.nr);
847 textinsert(t, 0, r, n);
848 textsetselect(t, t->q0, t->q1);
852 textmouse(Text *t, Point xy, int but)
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)
874 if(textselect2(t, &q0, &q1, &argt))
875 execute(t, q0, q1, argt);
877 if(textselect3(t, &q0, &q1))