16 Image *textcols[NCOL];
19 TABDIR = 3 /* width of tabs in directory windows */
23 textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
28 t->scrollr.max.x = r.min.x+Scrollwid;
30 r.min.x += Scrollwid+Scrollgap;
35 memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
36 textredraw(t, r, rf->f, screen, -1);
40 textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
45 frinit(t, r, f, b, t->Frame.cols);
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 */
53 maxt = min(TABDIR, maxtab);
57 t->maxtab = maxt*stringwidth(f, "0");
58 if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
61 textcolumnate(t, t->w->dlp, t->w->ndl);
66 textsetselect(t, t->q0, t->q1);
71 textresize(Text *t, Rectangle r)
76 r.max.y -= Dy(r)%t->font->height;
82 t->scrollr.max.x = r.min.x+Scrollwid;
84 r.min.x += Scrollwid+Scrollgap;
86 textredraw(t, r, t->font, t->b, odx);
95 filedeltext(t->file, t);
111 dircmp(void *a, void *b)
118 n = min(da->nr, db->nr);
119 i = memcmp(da->r, db->r, n*sizeof(Rune));
122 return da->nr - db->nr;
126 textcolumnate(Text *t, Dirlist **dlp, int ndl)
128 int i, j, w, colw, mint, maxt, ncol, nrow;
132 if(t->file->ntext > 1)
134 mint = stringwidth(t->font, "0");
135 /* go for narrower tabs if set more than 3 wide */
136 t->maxtab = min(maxtab, TABDIR)*mint;
139 for(i=0; i<ndl; i++){
142 if(maxt-w%maxt < mint || w%maxt==0)
152 ncol = max(1, Dx(t->r)/colw);
153 nrow = (ndl+ncol-1)/ncol;
156 for(i=0; i<nrow; i++){
157 for(j=i; j<ndl; j+=nrow){
159 fileinsert(t->file, q1, dl->r, dl->nr);
164 if(maxt-w%maxt < mint){
165 fileinsert(t->file, q1, L"\t", 1);
170 fileinsert(t->file, q1, L"\t", 1);
175 fileinsert(t->file, q1, L"\n", 1);
181 textload(Text *t, uint q0, char *file, int setqid)
185 int fd, i, j, n, ndl, nulls;
191 if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body)
193 if(t->w->isdir && t->file->nname==0){
194 warning(nil, "empty directory name\n");
197 fd = open(file, OREAD);
199 warning(nil, "can't open %s: %r\n", file);
204 warning(nil, "can't fstat %s: %r\n", file);
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);
215 t->w->filemenu = FALSE;
216 if(t->file->nname > 0 && 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);
226 while((n=dirread(fd, &dbuf)) > 0){
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)
235 dl->r = bytetorune(tmp, &dl->nr);
236 dl->wid = stringwidth(t->font, tmp);
239 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
244 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
247 textcolumnate(t, dlp, ndl);
251 t->w->filemenu = TRUE;
252 q1 = q0 + fileload(t->file, q0, fd, &nulls);
255 t->file->dev = d->dev;
256 t->file->mtime = d->mtime;
257 t->file->qidpath = d->qid.path;
261 for(q=q0; q<q1; q+=n){
265 bufread(t->file, q, rp, n);
268 else if(q <= t->org+t->nchars)
269 frinsert(t, rp, rp+n, q-t->org);
274 for(i=0; i<t->file->ntext; i++){
275 u = t->file->text[i];
277 if(u->org > u->file->nc) /* will be 0 because of reset(), but safety first */
279 textresize(u, u->all);
280 textbacknl(u, u->org, 0); /* go to beginning of line */
282 textsetselect(u, q0, q0);
285 warning(nil, "%s: NUL bytes elided\n", file);
295 textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
300 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
302 textinsert(t, q0, r, n, tofile);
328 textdelete(t, q0, q0+initial, tofile);
331 textinsert(t, q0, tp, n, tofile);
340 textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
345 if(tofile && t->ncache != 0)
346 error("text.insert");
350 fileinsert(t->file, q0, r, n);
353 t->w->utflastqid = -1;
355 if(t->file->ntext > 1)
356 for(i=0; i<t->file->ntext; i++){
357 u = t->file->text[i];
359 u->w->dirty = TRUE; /* always a body */
360 textinsert(u, q0, r, n, FALSE);
361 textsetselect(u, u->q0, u->q1);
373 else if(q0 <= t->org+t->nchars)
374 frinsert(t, r, r+n, q0-t->org);
380 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
382 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
401 if(t->lastlinefull || t->nofill)
407 n = t->file->nc-(t->org+t->nchars);
410 if(n > 2000) /* educated guess at reasonable amount */
412 bufread(t->file, t->org+t->nchars, rp, n);
414 * it's expensive to frinsert more than we need, so
417 nl = t->maxlines-t->nlines;
426 frinsert(t, rp, rp+i, t->nchars);
427 }while(t->lastlinefull == FALSE);
432 textdelete(Text *t, uint q0, uint q1, int tofile)
438 if(tofile && t->ncache != 0)
439 error("text.delete");
444 filedelete(t->file, q0, q1);
447 t->w->utflastqid = -1;
449 if(t->file->ntext > 1)
450 for(i=0; i<t->file->ntext; i++){
451 u = t->file->text[i];
453 u->w->dirty = TRUE; /* always a body */
454 textdelete(u, q0, q1, FALSE);
455 textsetselect(u, u->q0, u->q1);
461 t->q0 -= min(n, t->q0-q0);
463 t->q1 -= min(n, t->q1-q0);
466 else if(q0 < t->org+t->nchars){
482 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
487 textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
489 *p0 = min(q0, t->file->nc);
490 *p1 = min(q1, t->file->nc);
494 textreadc(Text *t, uint q)
498 if(t->cq0<=q && q<t->cq0+t->ncache)
499 r = t->cache[q-t->cq0];
501 bufread(t->file, q, &r, 1);
506 spacesindentbswidth(Text *t)
511 col = textbswidth(t, 0x15);
514 r = textreadc(t, q-1);
518 if(--col % t->tabstop == 0)
527 textbswidth(Text *t, Rune c)
533 /* there is known to be at least one character to erase */
534 if(c == 0x08){ /* ^H: erase character */
535 if(t->what == Body && t->w->indent[SPACESINDENT])
536 return spacesindentbswidth(t);
542 r = textreadc(t, q-1);
543 if(r == '\n'){ /* eat at most one more character */
544 if(q == t->q0) /* eat the newline */
550 if(eq && skipping) /* found one; stop skipping */
552 else if(!eq && !skipping)
561 textfilewidth(Text *t, uint q0, int oneelement)
568 r = textreadc(t, q-1);
571 if(oneelement && r=='/')
579 textcomplete(Text *t)
590 /* control-f: filename completion; works back to white space or / */
591 if(t->q0<t->file->nc && textreadc(t, t->q0)>' ') /* must be at end of word */
593 nstr = textfilewidth(t, t->q0, TRUE);
594 str = runemalloc(nstr);
595 npath = textfilewidth(t, t->q0-nstr, FALSE);
596 path = runemalloc(npath);
603 for(i=0; i<nstr; i++)
604 str[i] = textreadc(t, q++);
605 q = t->q0-nstr-npath;
606 for(i=0; i<npath; i++)
607 path[i] = textreadc(t, q++);
608 /* is path rooted? if not, we need to make it relative to window path */
609 if(npath>0 && path[0]=='/')
610 dir = (Runestr){path, npath};
612 dir = dirname(t, nil, 0);
613 if(dir.nr + 1 + npath > nelem(tmp)){
619 dir.r = runestrdup(L".");
621 runemove(tmp, dir.r, dir.nr);
623 runemove(tmp+dir.nr+1, path, npath);
627 dir = cleanrname(dir);
630 s = smprint("%.*S", nstr, str);
631 dirs = smprint("%.*S", dir.nr, dir.r);
632 c = complete(dirs, s);
635 warning(nil, "error attempting completion: %r\n");
640 warning(nil, "%.*S%s%.*S*%s\n",
642 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
644 c->nmatch? "" : ": no matches in:");
645 for(i=0; i<c->nfile; i++)
646 warning(nil, " %s\n", c->filename[i]);
650 rp = runesmprint("%s", c->string);
662 texttype(Text *t, Rune r)
676 textshow(t, t->q0-1, t->q0-1, TRUE);
680 if(t->q1 < t->file->nc)
681 textshow(t, t->q1+1, t->q1+1, TRUE);
687 n = mousescrollsize(t->maxlines);
694 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
696 textsetorigin(t, q0, TRUE);
702 n = mousescrollsize(t->maxlines);
707 q0 = textbacknl(t, t->org, n);
709 textsetorigin(t, q0, TRUE);
713 textshow(t, 0, 0, FALSE);
717 textshow(t, t->file->nc, t->file->nc, FALSE);
719 case 0x01: /* ^A: beginning of line */
721 /* go to where ^U would erase, if not already at BOL */
723 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
724 nnb = textbswidth(t, 0x15);
725 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
727 case 0x05: /* ^E: end of line */
730 while(q0<t->file->nc && textreadc(t, q0)!='\n')
732 textshow(t, q0, q0, TRUE);
742 cut(t, t, nil, TRUE, TRUE, nil, 0);
745 textshow(t, t->q0, t->q0, 1);
749 rp = textcomplete(t);
753 break; /* fall through to normal insertion case */
756 textsetselect(t, t->eq0, t->q0);
760 case 0x08: /* ^H: erase character */
761 case 0x15: /* ^U: erase line */
762 case 0x17: /* ^W: erase word */
763 if(t->q0 == 0) /* nothing to erase */
765 nnb = textbswidth(t, r);
768 /* if selection is at beginning of window, avoid deleting invisible text */
775 for(i=0; i<t->file->ntext; i++){
776 u = t->file->text[i];
782 error("text.type backspace");
786 textdelete(u, q1-n, q1, FALSE);
789 if(u->eq0==q1 || u->eq0==~0)
792 textdelete(u, q0, q0+nb, TRUE);
794 textsetselect(u, u->q0, u->q1);
796 textsetselect(t, q0, q0);
799 for(i=0; i<t->file->ntext; i++)
800 textfill(t->file->text[i]);
803 if(t->what == Body && t->w->indent[SPACESINDENT]){
804 nnb = textbswidth(t, 0x15);
805 if(nnb == 1 && textreadc(t, t->q0-1) == '\n')
807 nnb = t->tabstop - nnb % t->tabstop;
808 rp = runemalloc(nnb);
809 for(nr = 0; nr < nnb; nr++)
814 if(t->what == Body && t->w->indent[AUTOINDENT]){
815 /* find beginning of previous line using backspace code */
816 nnb = textbswidth(t, 0x15); /* ^U case */
817 rp = runemalloc(nnb + 1);
820 for(i=0; i<nnb; i++){
821 r = textreadc(t, t->q0-nnb+i);
822 if(r != ' ' && r != '\t')
827 break; /* fall through to normal code */
829 /* otherwise ordinary character; just insert, typically in caches of all texts */
830 for(i=0; i<t->file->ntext; i++){
831 u = t->file->text[i];
836 else if(t->q0 != u->cq0+u->ncache)
837 error("text.type cq1");
838 textinsert(u, t->q0, rp, nr, FALSE);
840 textsetselect(u, u->q0, u->q1);
841 if(u->ncache+nr > u->ncachealloc){
842 u->ncachealloc += 10 + nr;
843 u->cache = runerealloc(u->cache, u->ncachealloc);
845 runemove(u->cache+u->ncache, rp, nr);
850 textsetselect(t, t->q0+nr, t->q0+nr);
851 if(r=='\n' && t->w!=nil)
856 textcommit(Text *t, int tofile)
861 fileinsert(t->file, t->cq0, t->cache, t->ncache);
864 t->w->utflastqid = -1;
869 static Text *clicktext;
870 static uint clickmsec;
871 static int clickcount;
872 static Point clickpt;
873 static Text *selecttext;
877 * called from frame library
880 framescroll(Frame *f, int dl)
882 if(f != &selecttext->Frame)
883 error("frameselect not right frame");
884 textframescroll(selecttext, dl);
888 textframescroll(Text *t, int dl)
897 q0 = textbacknl(t, t->org, -dl);
898 if(selectq > t->org+t->p0)
899 textsetselect(t, t->org+t->p0, selectq);
901 textsetselect(t, selectq, t->org+t->p0);
903 if(t->org+t->nchars == t->file->nc)
905 q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
906 if(selectq > t->org+t->p1)
907 textsetselect(t, t->org+t->p1, selectq);
909 textsetselect(t, selectq, t->org+t->p1);
911 textsetorigin(t, q0, TRUE);
912 flushimage(display, 1);
925 * To have double-clicking and chording, we double-click
926 * immediately if it might make sense.
931 dx = abs(clickpt.x - w->mc.xy.x);
932 dy = abs(clickpt.y - w->mc.xy.y);
934 selectq = t->org+frcharofpt(t, mouse->xy);
936 if(mouse->msec-clickmsec >= 500 || selecttext != t || clickcount > 3 || dx > 3 || dy > 3)
938 if(clickcount >= 1 && selecttext==t && mouse->msec-clickmsec < 500){
939 textstretchsel(t, selectq, &q0, &q1, clickcount);
940 textsetselect(t, q0, q1);
941 flushimage(display, 1);
944 /* stay here until something interesting happens */
947 dx = abs(mouse->xy.x - x);
948 dy = abs(mouse->xy.y - y);
949 if(mouse->buttons != b || dx >= 3 || dy >= 3)
952 clickmsec = mouse->msec;
954 mouse->xy.x = x; /* in case we're calling frselect */
956 q0 = t->q0; /* may have changed */
958 selectq = t->org+frcharofpt(t, mouse->xy);;
960 if(mouse->buttons == b && clickcount == 0){
961 t->Frame.scroll = framescroll;
962 frselect(t, mousectl);
963 /* horrible botch: while asleep, may have lost selection altogether */
964 if(selectq > t->file->nc)
965 selectq = t->org + t->p0;
966 t->Frame.scroll = nil;
971 if(selectq > t->org+t->nchars)
977 if(q0==t->q0 && mouse->msec-clickmsec<500)
978 textstretchsel(t, selectq, &q0, &q1, clickcount);
981 clickmsec = mouse->msec;
984 textsetselect(t, q0, q1);
985 flushimage(display, 1);
986 state = 0; /* undo when possible; +1 for cut, -1 for paste */
987 while(mouse->buttons){
991 if(state==0 && t->what==Body){
993 filemark(t->w->body.file);
996 if(state==-1 && t->what==Body){
998 textsetselect(t, q0, t->q0);
1000 }else if(state != 1){
1001 cut(t, t, nil, TRUE, TRUE, nil, 0);
1005 if(state==1 && t->what==Body){
1006 winundo(t->w, TRUE);
1007 textsetselect(t, q0, t->q1);
1009 }else if(state != -1){
1010 paste(t, t, nil, TRUE, FALSE, nil, 0);
1017 flushimage(display, 1);
1018 while(mouse->buttons == b)
1019 readmouse(mousectl);
1020 if(mouse->msec-clickmsec >= 500)
1026 textshow(Text *t, uint q0, uint q1, int doselect)
1032 if(t->what != Body){
1034 textsetselect(t, q0, q1);
1037 if(t->w!=nil && t->maxlines==0)
1038 colgrow(t->col, t->w, 1);
1040 textsetselect(t, q0, q1);
1041 qe = t->org+t->nchars;
1042 if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
1045 if(t->w->nopen[QWevent] > 0)
1046 nl = 3*t->maxlines/4;
1049 q = textbacknl(t, q0, nl);
1050 /* avoid going backwards if trying to go forwards - long lines! */
1051 if(!(q0>t->org && q<t->org))
1052 textsetorigin(t, q, TRUE);
1053 while(q0 > t->org+t->nchars)
1054 textsetorigin(t, t->org+1, FALSE);
1060 region(int a, int b)
1070 selrestore(Frame *f, Point pt0, uint p0, uint p1)
1072 if(p1<=f->p0 || p0>=f->p1){
1074 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1077 if(p0>=f->p0 && p1<=f->p1){
1078 /* entirely inside */
1079 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1083 /* they now are known to overlap */
1085 /* before selection */
1087 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1089 pt0 = frptofchar(f, p0);
1091 /* after selection */
1093 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1096 /* inside selection */
1097 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1101 textsetselect(Text *t, uint q0, uint q1)
1105 /* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
1108 /* compute desired p0,p1 from q0,q1 */
1119 if(p0==t->p0 && p1==t->p1)
1121 /* screen disagrees with desired selection */
1122 if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
1123 /* no overlap or too easy to bother trying */
1124 frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
1125 frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
1128 /* overlap; avoid unnecessary painting */
1130 /* extend selection backwards */
1131 frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
1132 }else if(p0 > t->p0){
1133 /* trim first part of selection */
1134 frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
1137 /* extend selection forwards */
1138 frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
1139 }else if(p1 < t->p1){
1140 /* trim last part of selection */
1141 frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
1150 * Release the button in less than DELAY ms and it's considered a null selection
1151 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1159 xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1161 uint p0, p1, q, tmp;
1163 Point mp, pt0, pt1, qt;
1172 frtick(f, frptofchar(f, f->p0), 0);
1173 p0 = p1 = frcharofpt(f, mp);
1174 pt0 = frptofchar(f, p0);
1175 pt1 = frptofchar(f, p1);
1179 q = frcharofpt(f, mc->xy);
1183 if(reg != region(q, p0)){ /* crossed starting point; reset */
1185 selrestore(f, pt0, p0, p1);
1187 selrestore(f, pt1, p1, p0);
1190 reg = region(q, p0);
1192 frdrawsel0(f, pt0, p0, p1, col, display->white);
1194 qt = frptofchar(f, q);
1197 frdrawsel0(f, pt1, p1, q, col, display->white);
1200 selrestore(f, qt, q, p1);
1203 selrestore(f, pt1, p1, q);
1205 frdrawsel0(f, qt, q, p1, col, display->white);
1212 flushimage(f->display, 1);
1214 }while(mc->buttons == b);
1215 if(mc->msec-msec < DELAY && p0!=p1
1216 && abs(mp.x-mc->xy.x)<MINMOVE
1217 && abs(mp.y-mc->xy.y)<MINMOVE) {
1219 selrestore(f, pt0, p0, p1);
1221 selrestore(f, pt1, p1, p0);
1229 pt0 = frptofchar(f, p0);
1232 selrestore(f, pt0, p0, p1);
1235 frtick(f, frptofchar(f, f->p0), 1);
1236 flushimage(f->display, 1);
1242 textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1247 p0 = xselect(t, mousectl, high, &p1);
1248 buts = mousectl->buttons;
1249 if((buts & mask) == 0){
1254 while(mousectl->buttons)
1255 readmouse(mousectl);
1260 textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1265 buts = textselect23(t, q0, q1, but2col, 4);
1268 if(buts & 1){ /* pick up argument */
1276 textselect3(Text *t, uint *q0, uint *q1)
1280 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1284 static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
1285 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
1286 static Rune left2[] = { L'\n', 0 };
1287 static Rune left3[] = { L'\'', L'"', L'`', 0 };
1305 inmode(Rune r, int mode)
1307 return (mode == 1) ? isalnum(r) : r && !isspace(r);
1311 textstretchsel(Text *t, uint mp, uint *q0, uint *q1, int mode)
1319 for(i=0; left[i]!=nil; i++){
1323 /* try matching character to left, looking right */
1327 c = textreadc(t, q-1);
1328 p = runestrchr(l, c);
1330 if(textclickmatch(t, c, r[p-l], 1, &q))
1334 /* try matching character to right, looking left */
1335 if(q == t->file->nc)
1338 c = textreadc(t, q);
1339 p = runestrchr(r, c);
1341 if(textclickmatch(t, c, l[p-r], -1, &q)){
1342 *q1 = *q0+(*q0<t->file->nc && c=='\n');
1344 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1350 /* try filling out word to right */
1351 while(*q1<t->file->nc && inmode(textreadc(t, *q1), mode))
1353 /* try filling out word to left */
1354 while(*q0>0 && inmode(textreadc(t, *q0-1), mode))
1359 textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1367 if(*q == t->file->nc)
1369 c = textreadc(t, *q);
1375 c = textreadc(t, *q);
1383 return cl=='\n' && nest==1;
1387 textbacknl(Text *t, uint p, uint n)
1391 /* look for start of this line if n==0 */
1392 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1395 while(i-->0 && p>0){
1396 --p; /* it's at a newline now; back over it */
1399 /* at 128 chars, call it a line anyway */
1400 for(j=128; --j>0 && p>0; p--)
1401 if(textreadc(t, p-1)=='\n')
1408 textsetorigin(Text *t, uint org, int exact)
1414 if(org>0 && !exact){
1415 /* org is an estimate of the char posn; find a newline */
1416 /* don't try harder than 256 chars */
1417 for(i=0; i<256 && org<t->file->nc; i++){
1418 if(textreadc(t, org) == '\n'){
1427 if(a>=0 && a<t->nchars){
1429 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1431 else if(a<0 && -a<t->nchars){
1434 bufread(t->file, org, r, n);
1435 frinsert(t, r, r+n, 0);
1438 frdelete(t, 0, t->nchars);
1442 textsetselect(t, t->q0, t->q1);
1443 if(fixup && t->p1 > t->p0)
1444 frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
1452 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1453 textsetselect(t, t->org, t->org);
1454 frdelete(t, 0, t->nchars);