17 Image *textcols[NCOL];
18 static Rune Ldot[] = { '.', 0 };
21 TABDIR = 3 /* width of tabs in directory windows */
25 textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
30 t->scrollr.max.x = r.min.x+Scrollwid;
32 r.min.x += Scrollwid+Scrollgap;
37 memmove(t->fr.cols, cols, sizeof t->fr.cols);
38 textredraw(t, r, rf->f, screen, -1);
42 textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
47 frinit(&t->fr, r, f, b, t->fr.cols);
49 rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */
51 draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
52 /* use no wider than 3-space tabs in a directory */
56 maxt = min(TABDIR, maxtab);
60 t->fr.maxtab = maxt*stringwidth(f, "0");
61 if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
62 if(t->fr.maxlines > 0){
64 textcolumnate(t, t->w->dlp, t->w->ndl);
69 textsetselect(t, t->q0, t->q1);
74 textresize(Text *t, Rectangle r, int keepextra)
81 r.max.y -= Dy(r)%t->fr.font->height;
85 t->scrollr.max.x = r.min.x+Scrollwid;
87 r.min.x += Scrollwid+Scrollgap;
89 textredraw(t, r, t->fr.font, t->fr.b, odx);
90 if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){
91 /* draw background in bottom fringe of window */
93 r.min.y = t->fr.r.max.y;
94 r.max.y = t->all.max.y;
95 draw(screen, r, t->fr.cols[BACK], nil, ZP);
105 filedeltext(t->file, t);
121 dircmp(const void *a, const void *b)
128 n = min(da->nr, db->nr);
129 i = memcmp(da->r, db->r, n*sizeof(Rune));
132 return da->nr - db->nr;
136 textcolumnate(Text *t, Dirlist **dlp, int ndl)
138 int i, j, w, colw, mint, maxt, ncol, nrow;
141 static Rune Lnl[] = { '\n', 0 };
142 static Rune Ltab[] = { '\t', 0 };
144 if(t->file->ntext > 1)
146 mint = stringwidth(t->fr.font, "0");
147 /* go for narrower tabs if set more than 3 wide */
148 t->fr.maxtab = min(maxtab, TABDIR)*mint;
151 for(i=0; i<ndl; i++){
154 if(maxt-w%maxt < mint || w%maxt==0)
164 ncol = max(1, Dx(t->fr.r)/colw);
165 nrow = (ndl+ncol-1)/ncol;
168 for(i=0; i<nrow; i++){
169 for(j=i; j<ndl; j+=nrow){
171 fileinsert(t->file, q1, dl->r, dl->nr);
176 if(maxt-w%maxt < mint){
177 fileinsert(t->file, q1, Ltab, 1);
182 fileinsert(t->file, q1, Ltab, 1);
187 fileinsert(t->file, q1, Lnl, 1);
193 textload(Text *t, uint q0, char *file, int setqid)
197 int fd, i, j, n, ndl, nulls;
204 if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body)
206 if(t->w->isdir && t->file->nname==0){
207 warning(nil, "empty directory name");
211 warning(nil, "will not open self mount point %s\n", file);
214 fd = open(file, OREAD);
216 warning(nil, "can't open %s: %r\n", file);
221 warning(nil, "can't fstat %s: %r\n", file);
226 if(d->qid.type & QTDIR){
227 /* this is checked in get() but it's possible the file changed underfoot */
228 if(t->file->ntext > 1){
229 warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
233 t->w->filemenu = FALSE;
234 if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
235 rp = runemalloc(t->file->nname+1);
236 runemove(rp, t->file->name, t->file->nname);
237 rp[t->file->nname] = '/';
238 winsetname(t->w, rp, t->file->nname+1);
244 while((n=dirread(fd, &dbuf)) > 0){
246 dl = emalloc(sizeof(Dirlist));
247 j = strlen(dbuf[i].name);
248 tmp = emalloc(j+1+1);
249 memmove(tmp, dbuf[i].name, j);
250 if(dbuf[i].qid.type & QTDIR)
253 dl->r = bytetorune(tmp, &dl->nr);
254 dl->wid = stringwidth(t->fr.font, tmp);
257 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
262 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
265 textcolumnate(t, dlp, ndl);
269 t->w->filemenu = TRUE;
271 h = sha1(nil, 0, nil, nil);
272 q1 = q0 + fileload(t->file, q0, fd, &nulls, h);
276 sha1(nil, 0, t->file->sha1, h);
279 memset(t->file->sha1, 0, sizeof t->file->sha1);
281 t->file->dev = d->dev;
282 t->file->mtime = d->mtime;
283 t->file->qidpath = d->qid.path;
287 for(q=q0; q<q1; q+=n){
291 bufread(&t->file->b, q, rp, n);
294 else if(q <= t->org+t->fr.nchars)
295 frinsert(&t->fr, rp, rp+n, q-t->org);
296 if(t->fr.lastlinefull)
300 for(i=0; i<t->file->ntext; i++){
301 u = t->file->text[i];
303 if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */
305 textresize(u, u->all, TRUE);
306 textbacknl(u, u->org, 0); /* go to beginning of line */
308 textsetselect(u, q0, q0);
311 warning(nil, "%s: NUL bytes elided\n", file);
321 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
326 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
328 textinsert(t, q0, r, n, tofile);
354 textdelete(t, q0, q0+initial, tofile);
357 textinsert(t, q0, tp, n, tofile);
366 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
371 if(tofile && t->ncache != 0)
372 error("text.insert");
376 fileinsert(t->file, q0, r, n);
379 t->w->utflastqid = -1;
381 if(t->file->ntext > 1)
382 for(i=0; i<t->file->ntext; i++){
383 u = t->file->text[i];
385 u->w->dirty = TRUE; /* always a body */
386 textinsert(u, q0, r, n, FALSE);
387 textsetselect(u, u->q0, u->q1);
401 else if(q0 <= t->org+t->fr.nchars)
402 frinsert(&t->fr, r, r+n, q0-t->org);
408 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
410 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
429 if(t->fr.lastlinefull || t->nofill)
435 n = t->file->b.nc-(t->org+t->fr.nchars);
438 if(n > 2000) /* educated guess at reasonable amount */
440 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
442 * it's expensive to frinsert more than we need, so
445 nl = t->fr.maxlines-t->fr.nlines;
454 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
455 }while(t->fr.lastlinefull == FALSE);
460 textdelete(Text *t, uint q0, uint q1, int tofile)
466 if(tofile && t->ncache != 0)
467 error("text.delete");
472 filedelete(t->file, q0, q1);
475 t->w->utflastqid = -1;
477 if(t->file->ntext > 1)
478 for(i=0; i<t->file->ntext; i++){
479 u = t->file->text[i];
481 u->w->dirty = TRUE; /* always a body */
482 textdelete(u, q0, q1, FALSE);
483 textsetselect(u, u->q0, u->q1);
489 t->iq1 -= min(n, t->iq1-q0);
491 t->q0 -= min(n, t->q0-q0);
493 t->q1 -= min(n, t->q1-q0);
496 else if(q0 < t->org+t->fr.nchars){
498 if(p1 > t->fr.nchars)
505 frdelete(&t->fr, p0, p1);
512 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
517 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
519 *p0 = min(q0, t->file->b.nc);
520 *p1 = min(q1, t->file->b.nc);
524 textreadc(Text *t, uint q)
528 if(t->cq0<=q && q<t->cq0+t->ncache)
529 r = t->cache[q-t->cq0];
531 bufread(&t->file->b, q, &r, 1);
536 textbswidth(Text *t, Rune c)
542 /* there is known to be at least one character to erase */
543 if(c == 0x08) /* ^H: erase character */
548 r = textreadc(t, q-1);
549 if(r == '\n'){ /* eat at most one more character */
550 if(q == t->q0) /* eat the newline */
556 if(eq && skipping) /* found one; stop skipping */
558 else if(!eq && !skipping)
567 textfilewidth(Text *t, uint q0, int oneelement)
574 r = textreadc(t, q-1);
577 if(oneelement && r=='/')
585 textcomplete(Text *t)
596 /* control-f: filename completion; works back to white space or / */
597 if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
599 nstr = textfilewidth(t, t->q0, TRUE);
600 str = runemalloc(nstr);
601 npath = textfilewidth(t, t->q0-nstr, FALSE);
602 path = runemalloc(npath);
609 for(i=0; i<nstr; i++)
610 str[i] = textreadc(t, q++);
611 q = t->q0-nstr-npath;
612 for(i=0; i<npath; i++)
613 path[i] = textreadc(t, q++);
614 /* is path rooted? if not, we need to make it relative to window path */
615 if(npath>0 && path[0]=='/')
616 dir = runestr(path, npath);
618 dir = dirname(t, nil, 0);
619 if(dir.nr + 1 + npath > nelem(tmp)){
625 dir.r = runestrdup(Ldot);
627 runemove(tmp, dir.r, dir.nr);
629 runemove(tmp+dir.nr+1, path, npath);
633 dir = cleanrname(dir);
636 s = smprint("%.*S", nstr, str);
637 dirs = smprint("%.*S", dir.nr, dir.r);
638 c = complete(dirs, s);
641 warning(nil, "error attempting completion: %r\n");
646 warning(nil, "%.*S%s%.*S*%s\n",
648 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
650 c->nmatch ? "" : ": no matches in:");
651 for(i=0; i<c->nfile; i++)
652 warning(nil, " %s\n", c->filename[i]);
656 rp = runesmprint("%s", c->string);
668 texttype(Text *t, Rune r)
676 if(t->what!=Body && t->what!=Tag && r=='\n')
679 t->w->tagsafe = FALSE;
687 textshow(t, t->q0-1, t->q0-1, TRUE);
691 if(t->q1 < t->file->b.nc)
692 textshow(t, t->q1+1, t->q1+1, TRUE);
697 n = t->fr.maxlines/3;
702 n = mousescrollsize(t->fr.maxlines);
707 n = 2*t->fr.maxlines/3;
709 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
710 textsetorigin(t, q0, TRUE);
715 n = t->fr.maxlines/3;
720 n = mousescrollsize(t->fr.maxlines);
723 n = 2*t->fr.maxlines/3;
725 q0 = textbacknl(t, t->org, n);
726 textsetorigin(t, q0, TRUE);
730 if(t->org > t->iq1) {
731 q0 = textbacknl(t, t->iq1, 1);
732 textsetorigin(t, q0, TRUE);
734 textshow(t, 0, 0, FALSE);
738 if(t->iq1 > t->org+t->fr.nchars) {
739 if(t->iq1 > t->file->b.nc) {
740 // should not happen, but does. and it will crash textbacknl.
741 t->iq1 = t->file->b.nc;
743 q0 = textbacknl(t, t->iq1, 1);
744 textsetorigin(t, q0, TRUE);
746 textshow(t, t->file->b.nc, t->file->b.nc, FALSE);
748 case 0x01: /* ^A: beginning of line */
750 /* go to where ^U would erase, if not already at BOL */
752 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
753 nnb = textbswidth(t, 0x15);
754 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
756 case 0x05: /* ^E: end of line */
759 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
761 textshow(t, q0, q0, TRUE);
763 case Kcmd+'c': /* %C: copy */
765 cut(t, t, nil, TRUE, FALSE, nil, 0);
767 case Kcmd+'z': /* %Z: undo */
769 undo(t, nil, nil, TRUE, 0, nil, 0);
771 case Kcmd+'Z': /* %-shift-Z: redo */
773 undo(t, nil, nil, FALSE, 0, nil, 0);
777 /* expand tag to show all text */
778 if(!t->w->tagexpand){
779 t->w->tagexpand = TRUE;
780 winresize(t->w, t->w->r, FALSE, TRUE);
785 /* shrink tag to single line */
787 t->w->tagexpand = FALSE;
789 winresize(t->w, t->w->r, FALSE, TRUE);
797 /* cut/paste must be done after the seq++/filemark */
799 case Kcmd+'x': /* %X: cut */
805 cut(t, t, nil, TRUE, TRUE, nil, 0);
806 textshow(t, t->q0, t->q0, 1);
809 case Kcmd+'v': /* %V: paste */
815 paste(t, t, nil, TRUE, FALSE, nil, 0);
816 textshow(t, t->q0, t->q1, 1);
823 cut(t, t, nil, TRUE, TRUE, nil, 0);
826 textshow(t, t->q0, t->q0, 1);
828 case 0x06: /* ^F: complete */
831 rp = textcomplete(t);
835 break; /* fall through to normal insertion case */
839 textsetselect(t, t->eq0, t->q0);
841 textsetselect(t, t->q0, t->eq0);
847 case 0x08: /* ^H: erase character */
848 case 0x15: /* ^U: erase line */
849 case 0x17: /* ^W: erase word */
850 if(t->q0 == 0) /* nothing to erase */
852 nnb = textbswidth(t, r);
855 /* if selection is at beginning of window, avoid deleting invisible text */
862 for(i=0; i<t->file->ntext; i++){
863 u = t->file->text[i];
869 error("text.type backspace");
873 textdelete(u, q1-n, q1, FALSE);
876 if(u->eq0==q1 || u->eq0==~0)
879 textdelete(u, q0, q0+nb, TRUE);
881 textsetselect(u, u->q0, u->q1);
883 textsetselect(t, q0, q0);
886 for(i=0; i<t->file->ntext; i++)
887 textfill(t->file->text[i]);
891 if(t->w->autoindent){
892 /* find beginning of previous line using backspace code */
893 nnb = textbswidth(t, 0x15); /* ^U case */
894 rp = runemalloc(nnb + 1);
897 for(i=0; i<nnb; i++){
898 r = textreadc(t, t->q0-nnb+i);
899 if(r != ' ' && r != '\t')
904 break; /* fall through to normal code */
906 /* otherwise ordinary character; just insert, typically in caches of all texts */
907 for(i=0; i<t->file->ntext; i++){
908 u = t->file->text[i];
913 else if(t->q0 != u->cq0+u->ncache)
914 error("text.type cq1");
916 * Change the tag before we add to ncache,
917 * so that if the window body is resized the
918 * commit will not find anything in ncache.
920 if(u->what==Body && u->ncache == 0){
925 textinsert(u, t->q0, rp, nr, FALSE);
927 textsetselect(u, u->q0, u->q1);
928 if(u->ncache+nr > u->ncachealloc){
929 u->ncachealloc += 10 + nr;
930 u->cache = runerealloc(u->cache, u->ncachealloc);
932 runemove(u->cache+u->ncache, rp, nr);
937 textsetselect(t, t->q0+nr, t->q0+nr);
938 if(r=='\n' && t->w!=nil)
944 textcommit(Text *t, int tofile)
949 fileinsert(t->file, t->cq0, t->cache, t->ncache);
952 t->w->utflastqid = -1;
957 static Text *clicktext;
958 static uint clickmsec;
959 static Text *selecttext;
963 * called from frame library
966 framescroll(Frame *f, int dl)
968 if(f != &selecttext->fr)
969 error("frameselect not right frame");
970 textframescroll(selecttext, dl);
974 textframescroll(Text *t, int dl)
983 q0 = textbacknl(t, t->org, -dl);
984 if(selectq > t->org+t->fr.p0)
985 textsetselect(t, t->org+t->fr.p0, selectq);
987 textsetselect(t, selectq, t->org+t->fr.p0);
989 if(t->org+t->fr.nchars == t->file->b.nc)
991 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
992 if(selectq > t->org+t->fr.p1)
993 textsetselect(t, t->org+t->fr.p1, selectq);
995 textsetselect(t, selectq, t->org+t->fr.p1);
997 textsetorigin(t, q0, TRUE);
1007 enum { None, Cut, Paste };
1011 * To have double-clicking and chording, we double-click
1012 * immediately if it might make sense.
1017 selectq = t->org+frcharofpt(&t->fr, mouse->xy);
1018 if(clicktext==t && mouse->msec-clickmsec<500)
1019 if(q0==q1 && selectq==q0){
1020 textdoubleclick(t, &q0, &q1);
1021 textsetselect(t, q0, q1);
1022 flushimage(display, 1);
1025 /* stay here until something interesting happens */
1027 readmouse(mousectl);
1028 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
1029 mouse->xy.x = x; /* in case we're calling frselect */
1031 q0 = t->q0; /* may have changed */
1035 if(mouse->buttons == b){
1036 t->fr.scroll = framescroll;
1037 frselect(&t->fr, mousectl);
1038 /* horrible botch: while asleep, may have lost selection altogether */
1039 if(selectq > t->file->b.nc)
1040 selectq = t->org + t->fr.p0;
1042 if(selectq < t->org)
1045 q0 = t->org + t->fr.p0;
1046 if(selectq > t->org+t->fr.nchars)
1049 q1 = t->org+t->fr.p1;
1052 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
1053 textdoubleclick(t, &q0, &q1);
1057 clickmsec = mouse->msec;
1061 textsetselect(t, q0, q1);
1062 flushimage(display, 1);
1063 state = None; /* what we've done; undo when possible */
1064 while(mouse->buttons){
1068 if(state==None && t->what==Body){
1070 filemark(t->w->body.file);
1073 if(state==Paste && t->what==Body){
1074 winundo(t->w, TRUE);
1075 textsetselect(t, q0, t->q1);
1077 }else if(state != Cut){
1078 cut(t, t, nil, TRUE, TRUE, nil, 0);
1082 if(state==Cut && t->what==Body){
1083 winundo(t->w, TRUE);
1084 textsetselect(t, q0, t->q1);
1086 }else if(state != Paste){
1087 paste(t, t, nil, TRUE, FALSE, nil, 0);
1094 flushimage(display, 1);
1095 while(mouse->buttons == b)
1096 readmouse(mousectl);
1102 textshow(Text *t, uint q0, uint q1, int doselect)
1110 if(t->what != Body){
1112 textsetselect(t, q0, q1);
1115 if(t->w!=nil && t->fr.maxlines==0)
1116 colgrow(t->col, t->w, 1);
1118 textsetselect(t, q0, q1);
1119 qe = t->org+t->fr.nchars;
1120 tsd = FALSE; /* do we call textscrdraw? */
1121 nc = t->file->b.nc+t->ncache;
1125 else if(q0==qe && qe==nc){
1126 if(textreadc(t, nc-1) == '\n'){
1127 if(t->fr.nlines<t->fr.maxlines)
1136 if(t->w->nopen[QWevent] > 0)
1137 nl = 3*t->fr.maxlines/4;
1139 nl = t->fr.maxlines/4;
1140 q = textbacknl(t, q0, nl);
1141 /* avoid going backwards if trying to go forwards - long lines! */
1142 if(!(q0>t->org && q<t->org))
1143 textsetorigin(t, q, TRUE);
1144 while(q0 > t->org+t->fr.nchars)
1145 textsetorigin(t, t->org+1, FALSE);
1151 region(int a, int b)
1161 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1163 if(p1<=f->p0 || p0>=f->p1){
1165 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1168 if(p0>=f->p0 && p1<=f->p1){
1169 /* entirely inside */
1170 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1174 /* they now are known to overlap */
1176 /* before selection */
1178 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1180 pt0 = frptofchar(f, p0);
1182 /* after selection */
1184 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1187 /* inside selection */
1188 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1192 textsetselect(Text *t, uint q0, uint q1)
1196 /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1199 /* compute desired p0,p1 from q0,q1 */
1209 if(p0 > t->fr.nchars)
1211 if(p1 > t->fr.nchars){
1215 if(p0==t->fr.p0 && p1==t->fr.p1){
1216 if(p0 == p1 && ticked != t->fr.ticked)
1217 frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
1221 sysfatal("acme: textsetselect p0=%d p1=%d q0=%ud q1=%ud t->org=%d nchars=%d", p0, p1, q0, q1, (int)t->org, (int)t->fr.nchars);
1222 /* screen disagrees with desired selection */
1223 if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1224 /* no overlap or too easy to bother trying */
1225 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1226 if(p0 != p1 || ticked)
1227 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1230 /* overlap; avoid unnecessary painting */
1232 /* extend selection backwards */
1233 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1234 }else if(p0 > t->fr.p0){
1235 /* trim first part of selection */
1236 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1239 /* extend selection forwards */
1240 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1241 }else if(p1 < t->fr.p1){
1242 /* trim last part of selection */
1243 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1252 * Release the button in less than DELAY ms and it's considered a null selection
1253 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1261 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1263 uint p0, p1, q, tmp;
1265 Point mp, pt0, pt1, qt;
1274 frtick(f, frptofchar(f, f->p0), 0);
1275 p0 = p1 = frcharofpt(f, mp);
1276 pt0 = frptofchar(f, p0);
1277 pt1 = frptofchar(f, p1);
1281 q = frcharofpt(f, mc->m.xy);
1285 if(reg != region(q, p0)){ /* crossed starting point; reset */
1287 selrestore(f, pt0, p0, p1);
1289 selrestore(f, pt1, p1, p0);
1292 reg = region(q, p0);
1294 frdrawsel0(f, pt0, p0, p1, col, display->white);
1296 qt = frptofchar(f, q);
1299 frdrawsel0(f, pt1, p1, q, col, display->white);
1302 selrestore(f, qt, q, p1);
1305 selrestore(f, pt1, p1, q);
1307 frdrawsel0(f, qt, q, p1, col, display->white);
1314 flushimage(f->display, 1);
1316 }while(mc->m.buttons == b);
1317 if(mc->m.msec-msec < DELAY && p0!=p1
1318 && abs(mp.x-mc->m.xy.x)<MINMOVE
1319 && abs(mp.y-mc->m.xy.y)<MINMOVE) {
1321 selrestore(f, pt0, p0, p1);
1323 selrestore(f, pt1, p1, p0);
1331 pt0 = frptofchar(f, p0);
1334 selrestore(f, pt0, p0, p1);
1337 frtick(f, frptofchar(f, f->p0), 1);
1338 flushimage(f->display, 1);
1344 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1349 p0 = xselect(&t->fr, mousectl, high, &p1);
1350 buts = mousectl->m.buttons;
1351 if((buts & mask) == 0){
1356 while(mousectl->m.buttons)
1357 readmouse(mousectl);
1362 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1367 buts = textselect23(t, q0, q1, but2col, 4);
1370 if(buts & 1){ /* pick up argument */
1378 textselect3(Text *t, uint *q0, uint *q1)
1382 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1386 static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1387 static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1388 static Rune left2[] = { '\n', 0 };
1389 static Rune left3[] = { '\'', '"', '`', 0 };
1407 textdoubleclick(Text *t, uint *q0, uint *q1)
1413 if(textclickhtmlmatch(t, q0, q1))
1416 for(i=0; left[i]!=nil; i++){
1420 /* try matching character to left, looking right */
1424 c = textreadc(t, q-1);
1425 p = runestrchr(l, c);
1427 if(textclickmatch(t, c, r[p-l], 1, &q))
1431 /* try matching character to right, looking left */
1432 if(q == t->file->b.nc)
1435 c = textreadc(t, q);
1436 p = runestrchr(r, c);
1438 if(textclickmatch(t, c, l[p-r], -1, &q)){
1439 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1441 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1448 /* try filling out word to right */
1449 while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1451 /* try filling out word to left */
1452 while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1457 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1465 if(*q == t->file->b.nc)
1467 c = textreadc(t, *q);
1473 c = textreadc(t, *q);
1481 return cl=='\n' && nest==1;
1484 // Is the text starting at location q an html tag?
1485 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1486 // Set *q1, if non-nil, to the location after the tag.
1488 ishtmlstart(Text *t, uint q, uint *q1)
1492 if(q+2 > t->file->b.nc)
1494 if(textreadc(t, q++) != '<')
1496 c = textreadc(t, q++);
1500 if(q >= t->file->b.nc)
1503 c = textreadc(t, q++);
1507 if(c1 == '/') // closing tag
1509 if(c2 == '/' || c2 == '!') // open + close tag or comment
1514 // Is the text ending at location q an html tag?
1515 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1516 // Set *q0, if non-nil, to the start of the tag.
1518 ishtmlend(Text *t, uint q, uint *q0)
1524 if(textreadc(t, --q) != '>')
1526 c = textreadc(t, --q);
1533 c = textreadc(t, --q);
1537 if(c1 == '/') // closing tag
1539 if(c2 == '/' || c2 == '!') // open + close tag or comment
1545 textclickhtmlmatch(Text *t, uint *q0, uint *q1)
1551 // after opening tag? scan forward for closing tag
1552 if(ishtmlend(t, q, nil) == 1) {
1554 while(q < t->file->b.nc) {
1555 n = ishtmlstart(t, q, &nq);
1569 // before closing tag? scan backward for opening tag
1570 if(ishtmlstart(t, q, nil) == -1) {
1573 n = ishtmlend(t, q, &nq);
1591 textbacknl(Text *t, uint p, uint n)
1595 /* look for start of this line if n==0 */
1596 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1599 while(i-->0 && p>0){
1600 --p; /* it's at a newline now; back over it */
1603 /* at 128 chars, call it a line anyway */
1604 for(j=128; --j>0 && p>0; p--)
1605 if(textreadc(t, p-1)=='\n')
1612 textsetorigin(Text *t, uint org, int exact)
1618 if(org>0 && !exact && textreadc(t, org-1) != '\n'){
1619 /* org is an estimate of the char posn; find a newline */
1620 /* don't try harder than 256 chars */
1621 for(i=0; i<256 && org<t->file->b.nc; i++){
1622 if(textreadc(t, org) == '\n'){
1631 if(a>=0 && a<t->fr.nchars){
1632 frdelete(&t->fr, 0, a);
1633 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1635 else if(a<0 && -a<t->fr.nchars){
1638 bufread(&t->file->b, org, r, n);
1639 frinsert(&t->fr, r, r+n, 0);
1642 frdelete(&t->fr, 0, t->fr.nchars);
1646 textsetselect(t, t->q0, t->q1);
1647 if(fixup && t->fr.p1 > t->fr.p0)
1648 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1656 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1657 textsetselect(t, t->org, t->org);
1658 frdelete(&t->fr, 0, t->fr.nchars);
1663 bufreset(&t->file->b);