| #include <u.h> |
| #include <libc.h> |
| #include <draw.h> |
| #include <thread.h> |
| #include <cursor.h> |
| #include <mouse.h> |
| #include <keyboard.h> |
| #include <frame.h> |
| #include <fcall.h> |
| #include <plumb.h> |
| #include <complete.h> |
| #include "dat.h" |
| #include "fns.h" |
| |
| Image *tagcols[NCOL]; |
| Image *textcols[NCOL]; |
| static Rune Ldot[] = { '.', 0 }; |
| |
| enum{ |
| TABDIR = 3 /* width of tabs in directory windows */ |
| }; |
| |
| void |
| textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL]) |
| { |
| t->file = f; |
| t->all = r; |
| t->scrollr = r; |
| t->scrollr.max.x = r.min.x+Scrollwid; |
| t->lastsr = nullrect; |
| r.min.x += Scrollwid+Scrollgap; |
| t->eq0 = ~0; |
| t->ncache = 0; |
| t->reffont = rf; |
| t->tabstop = maxtab; |
| memmove(t->fr.cols, cols, sizeof t->fr.cols); |
| textredraw(t, r, rf->f, screen, -1); |
| } |
| |
| void |
| textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx) |
| { |
| int maxt; |
| Rectangle rr; |
| |
| frinit(&t->fr, r, f, b, t->fr.cols); |
| rr = t->fr.r; |
| rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */ |
| if(!t->fr.noredraw) |
| draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP); |
| /* use no wider than 3-space tabs in a directory */ |
| maxt = maxtab; |
| if(t->what == Body){ |
| if(t->w->isdir) |
| maxt = min(TABDIR, maxtab); |
| else |
| maxt = t->tabstop; |
| } |
| t->fr.maxtab = maxt*stringwidth(f, "0"); |
| if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){ |
| if(t->fr.maxlines > 0){ |
| textreset(t); |
| textcolumnate(t, t->w->dlp, t->w->ndl); |
| textshow(t, 0, 0, 1); |
| } |
| }else{ |
| textfill(t); |
| textsetselect(t, t->q0, t->q1); |
| } |
| } |
| |
| int |
| textresize(Text *t, Rectangle r, int keepextra) |
| { |
| int odx; |
| |
| if(Dy(r) <= 0) |
| r.max.y = r.min.y; |
| else if(!keepextra) |
| r.max.y -= Dy(r)%t->fr.font->height; |
| odx = Dx(t->all); |
| t->all = r; |
| t->scrollr = r; |
| t->scrollr.max.x = r.min.x+Scrollwid; |
| t->lastsr = nullrect; |
| r.min.x += Scrollwid+Scrollgap; |
| frclear(&t->fr, 0); |
| textredraw(t, r, t->fr.font, t->fr.b, odx); |
| if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){ |
| /* draw background in bottom fringe of window */ |
| r.min.x -= Scrollgap; |
| r.min.y = t->fr.r.max.y; |
| r.max.y = t->all.max.y; |
| draw(screen, r, t->fr.cols[BACK], nil, ZP); |
| } |
| return t->all.max.y; |
| } |
| |
| void |
| textclose(Text *t) |
| { |
| free(t->cache); |
| frclear(&t->fr, 1); |
| filedeltext(t->file, t); |
| t->file = nil; |
| rfclose(t->reffont); |
| if(argtext == t) |
| argtext = nil; |
| if(typetext == t) |
| typetext = nil; |
| if(seltext == t) |
| seltext = nil; |
| if(mousetext == t) |
| mousetext = nil; |
| if(barttext == t) |
| barttext = nil; |
| } |
| |
| int |
| dircmp(const void *a, const void *b) |
| { |
| Dirlist *da, *db; |
| int i, n; |
| |
| da = *(Dirlist**)a; |
| db = *(Dirlist**)b; |
| n = min(da->nr, db->nr); |
| i = memcmp(da->r, db->r, n*sizeof(Rune)); |
| if(i) |
| return i; |
| return da->nr - db->nr; |
| } |
| |
| void |
| textcolumnate(Text *t, Dirlist **dlp, int ndl) |
| { |
| int i, j, w, colw, mint, maxt, ncol, nrow; |
| Dirlist *dl; |
| uint q1; |
| static Rune Lnl[] = { '\n', 0 }; |
| static Rune Ltab[] = { '\t', 0 }; |
| |
| if(t->file->ntext > 1) |
| return; |
| mint = stringwidth(t->fr.font, "0"); |
| /* go for narrower tabs if set more than 3 wide */ |
| t->fr.maxtab = min(maxtab, TABDIR)*mint; |
| maxt = t->fr.maxtab; |
| colw = 0; |
| for(i=0; i<ndl; i++){ |
| dl = dlp[i]; |
| w = dl->wid; |
| if(maxt-w%maxt < mint || w%maxt==0) |
| w += mint; |
| if(w % maxt) |
| w += maxt-(w%maxt); |
| if(w > colw) |
| colw = w; |
| } |
| if(colw == 0) |
| ncol = 1; |
| else |
| ncol = max(1, Dx(t->fr.r)/colw); |
| nrow = (ndl+ncol-1)/ncol; |
| |
| q1 = 0; |
| for(i=0; i<nrow; i++){ |
| for(j=i; j<ndl; j+=nrow){ |
| dl = dlp[j]; |
| fileinsert(t->file, q1, dl->r, dl->nr); |
| q1 += dl->nr; |
| if(j+nrow >= ndl) |
| break; |
| w = dl->wid; |
| if(maxt-w%maxt < mint){ |
| fileinsert(t->file, q1, Ltab, 1); |
| q1++; |
| w += mint; |
| } |
| do{ |
| fileinsert(t->file, q1, Ltab, 1); |
| q1++; |
| w += maxt-(w%maxt); |
| }while(w < colw); |
| } |
| fileinsert(t->file, q1, Lnl, 1); |
| q1++; |
| } |
| } |
| |
| int |
| textload(Text *t, uint q0, char *file, int setqid) |
| { |
| Rune *rp; |
| Dirlist *dl, **dlp; |
| int fd, i, j, n, ndl, nulls; |
| uint q, q1; |
| Dir *d, *dbuf; |
| char *tmp; |
| Text *u; |
| |
| if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body) |
| error("text.load"); |
| if(t->w->isdir && t->file->nname==0){ |
| warning(nil, "empty directory name"); |
| return -1; |
| } |
| if(ismtpt(file)){ |
| warning(nil, "will not open self mount point %s\n", file); |
| return -1; |
| } |
| fd = open(file, OREAD); |
| if(fd < 0){ |
| warning(nil, "can't open %s: %r\n", file); |
| return -1; |
| } |
| d = dirfstat(fd); |
| if(d == nil){ |
| warning(nil, "can't fstat %s: %r\n", file); |
| goto Rescue; |
| } |
| nulls = FALSE; |
| if(d->qid.type & QTDIR){ |
| /* this is checked in get() but it's possible the file changed underfoot */ |
| if(t->file->ntext > 1){ |
| warning(nil, "%s is a directory; can't read with multiple windows on it\n", file); |
| goto Rescue; |
| } |
| t->w->isdir = TRUE; |
| t->w->filemenu = FALSE; |
| if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){ |
| rp = runemalloc(t->file->nname+1); |
| runemove(rp, t->file->name, t->file->nname); |
| rp[t->file->nname] = '/'; |
| winsetname(t->w, rp, t->file->nname+1); |
| free(rp); |
| } |
| dlp = nil; |
| ndl = 0; |
| dbuf = nil; |
| while((n=dirread(fd, &dbuf)) > 0){ |
| for(i=0; i<n; i++){ |
| dl = emalloc(sizeof(Dirlist)); |
| j = strlen(dbuf[i].name); |
| tmp = emalloc(j+1+1); |
| memmove(tmp, dbuf[i].name, j); |
| if(dbuf[i].qid.type & QTDIR) |
| tmp[j++] = '/'; |
| tmp[j] = '\0'; |
| dl->r = bytetorune(tmp, &dl->nr); |
| dl->wid = stringwidth(t->fr.font, tmp); |
| free(tmp); |
| ndl++; |
| dlp = realloc(dlp, ndl*sizeof(Dirlist*)); |
| dlp[ndl-1] = dl; |
| } |
| free(dbuf); |
| } |
| qsort(dlp, ndl, sizeof(Dirlist*), dircmp); |
| t->w->dlp = dlp; |
| t->w->ndl = ndl; |
| textcolumnate(t, dlp, ndl); |
| q1 = t->file->b.nc; |
| }else{ |
| t->w->isdir = FALSE; |
| t->w->filemenu = TRUE; |
| q1 = q0 + fileload(t->file, q0, fd, &nulls); |
| } |
| if(setqid){ |
| t->file->dev = d->dev; |
| t->file->mtime = d->mtime; |
| t->file->qidpath = d->qid.path; |
| } |
| close(fd); |
| rp = fbufalloc(); |
| for(q=q0; q<q1; q+=n){ |
| n = q1-q; |
| if(n > RBUFSIZE) |
| n = RBUFSIZE; |
| bufread(&t->file->b, q, rp, n); |
| if(q < t->org) |
| t->org += n; |
| else if(q <= t->org+t->fr.nchars) |
| frinsert(&t->fr, rp, rp+n, q-t->org); |
| if(t->fr.lastlinefull) |
| break; |
| } |
| fbuffree(rp); |
| for(i=0; i<t->file->ntext; i++){ |
| u = t->file->text[i]; |
| if(u != t){ |
| if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */ |
| u->org = 0; |
| textresize(u, u->all, TRUE); |
| textbacknl(u, u->org, 0); /* go to beginning of line */ |
| } |
| textsetselect(u, q0, q0); |
| } |
| if(nulls) |
| warning(nil, "%s: NUL bytes elided\n", file); |
| free(d); |
| return q1-q0; |
| |
| Rescue: |
| close(fd); |
| return -1; |
| } |
| |
| uint |
| textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp) |
| { |
| Rune *bp, *tp, *up; |
| int i, initial; |
| |
| if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */ |
| Err: |
| textinsert(t, q0, r, n, tofile); |
| *nrp = n; |
| return q0; |
| } |
| bp = r; |
| for(i=0; i<n; i++) |
| if(*bp++ == '\b'){ |
| --bp; |
| initial = 0; |
| tp = runemalloc(n); |
| runemove(tp, r, i); |
| up = tp+i; |
| for(; i<n; i++){ |
| *up = *bp++; |
| if(*up == '\b') |
| if(up == tp) |
| initial++; |
| else |
| --up; |
| else |
| up++; |
| } |
| if(initial){ |
| if(initial > q0) |
| initial = q0; |
| q0 -= initial; |
| textdelete(t, q0, q0+initial, tofile); |
| } |
| n = up-tp; |
| textinsert(t, q0, tp, n, tofile); |
| free(tp); |
| *nrp = n; |
| return q0; |
| } |
| goto Err; |
| } |
| |
| void |
| textinsert(Text *t, uint q0, Rune *r, uint n, int tofile) |
| { |
| int c, i; |
| Text *u; |
| |
| if(tofile && t->ncache != 0) |
| error("text.insert"); |
| if(n == 0) |
| return; |
| if(tofile){ |
| fileinsert(t->file, q0, r, n); |
| if(t->what == Body){ |
| t->w->dirty = TRUE; |
| t->w->utflastqid = -1; |
| } |
| if(t->file->ntext > 1) |
| for(i=0; i<t->file->ntext; i++){ |
| u = t->file->text[i]; |
| if(u != t){ |
| u->w->dirty = TRUE; /* always a body */ |
| textinsert(u, q0, r, n, FALSE); |
| textsetselect(u, u->q0, u->q1); |
| textscrdraw(u); |
| } |
| } |
| |
| } |
| if(q0 < t->iq1) |
| t->iq1 += n; |
| if(q0 < t->q1) |
| t->q1 += n; |
| if(q0 < t->q0) |
| t->q0 += n; |
| if(q0 < t->org) |
| t->org += n; |
| else if(q0 <= t->org+t->fr.nchars) |
| frinsert(&t->fr, r, r+n, q0-t->org); |
| if(t->w){ |
| c = 'i'; |
| if(t->what == Body) |
| c = 'I'; |
| if(n <= EVENTSIZE) |
| winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r); |
| else |
| winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n); |
| } |
| } |
| |
| void |
| typecommit(Text *t) |
| { |
| if(t->w != nil) |
| wincommit(t->w, t); |
| else |
| textcommit(t, TRUE); |
| } |
| |
| void |
| textfill(Text *t) |
| { |
| Rune *rp; |
| int i, n, m, nl; |
| |
| if(t->fr.lastlinefull || t->nofill) |
| return; |
| if(t->ncache > 0) |
| typecommit(t); |
| rp = fbufalloc(); |
| do{ |
| n = t->file->b.nc-(t->org+t->fr.nchars); |
| if(n == 0) |
| break; |
| if(n > 2000) /* educated guess at reasonable amount */ |
| n = 2000; |
| bufread(&t->file->b, t->org+t->fr.nchars, rp, n); |
| /* |
| * it's expensive to frinsert more than we need, so |
| * count newlines. |
| */ |
| nl = t->fr.maxlines-t->fr.nlines; |
| m = 0; |
| for(i=0; i<n; ){ |
| if(rp[i++] == '\n'){ |
| m++; |
| if(m >= nl) |
| break; |
| } |
| } |
| frinsert(&t->fr, rp, rp+i, t->fr.nchars); |
| }while(t->fr.lastlinefull == FALSE); |
| fbuffree(rp); |
| } |
| |
| void |
| textdelete(Text *t, uint q0, uint q1, int tofile) |
| { |
| uint n, p0, p1; |
| int i, c; |
| Text *u; |
| |
| if(tofile && t->ncache != 0) |
| error("text.delete"); |
| n = q1-q0; |
| if(n == 0) |
| return; |
| if(tofile){ |
| filedelete(t->file, q0, q1); |
| if(t->what == Body){ |
| t->w->dirty = TRUE; |
| t->w->utflastqid = -1; |
| } |
| if(t->file->ntext > 1) |
| for(i=0; i<t->file->ntext; i++){ |
| u = t->file->text[i]; |
| if(u != t){ |
| u->w->dirty = TRUE; /* always a body */ |
| textdelete(u, q0, q1, FALSE); |
| textsetselect(u, u->q0, u->q1); |
| textscrdraw(u); |
| } |
| } |
| } |
| if(q0 < t->iq1) |
| t->iq1 -= min(n, t->iq1-q0); |
| if(q0 < t->q0) |
| t->q0 -= min(n, t->q0-q0); |
| if(q0 < t->q1) |
| t->q1 -= min(n, t->q1-q0); |
| if(q1 <= t->org) |
| t->org -= n; |
| else if(q0 < t->org+t->fr.nchars){ |
| p1 = q1 - t->org; |
| if(p1 > t->fr.nchars) |
| p1 = t->fr.nchars; |
| if(q0 < t->org){ |
| t->org = q0; |
| p0 = 0; |
| }else |
| p0 = q0 - t->org; |
| frdelete(&t->fr, p0, p1); |
| textfill(t); |
| } |
| if(t->w){ |
| c = 'd'; |
| if(t->what == Body) |
| c = 'D'; |
| winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1); |
| } |
| } |
| |
| void |
| textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1) |
| { |
| *p0 = min(q0, t->file->b.nc); |
| *p1 = min(q1, t->file->b.nc); |
| } |
| |
| Rune |
| textreadc(Text *t, uint q) |
| { |
| Rune r; |
| |
| if(t->cq0<=q && q<t->cq0+t->ncache) |
| r = t->cache[q-t->cq0]; |
| else |
| bufread(&t->file->b, q, &r, 1); |
| return r; |
| } |
| |
| int |
| textbswidth(Text *t, Rune c) |
| { |
| uint q, eq; |
| Rune r; |
| int skipping; |
| |
| /* there is known to be at least one character to erase */ |
| if(c == 0x08) /* ^H: erase character */ |
| return 1; |
| q = t->q0; |
| skipping = TRUE; |
| while(q > 0){ |
| r = textreadc(t, q-1); |
| if(r == '\n'){ /* eat at most one more character */ |
| if(q == t->q0) /* eat the newline */ |
| --q; |
| break; |
| } |
| if(c == 0x17){ |
| eq = isalnum(r); |
| if(eq && skipping) /* found one; stop skipping */ |
| skipping = FALSE; |
| else if(!eq && !skipping) |
| break; |
| } |
| --q; |
| } |
| return t->q0-q; |
| } |
| |
| int |
| textfilewidth(Text *t, uint q0, int oneelement) |
| { |
| uint q; |
| Rune r; |
| |
| q = q0; |
| while(q > 0){ |
| r = textreadc(t, q-1); |
| if(r <= ' ') |
| break; |
| if(oneelement && r=='/') |
| break; |
| --q; |
| } |
| return q0-q; |
| } |
| |
| Rune* |
| textcomplete(Text *t) |
| { |
| int i, nstr, npath; |
| uint q; |
| Rune tmp[200]; |
| Rune *str, *path; |
| Rune *rp; |
| Completion *c; |
| char *s, *dirs; |
| Runestr dir; |
| |
| /* control-f: filename completion; works back to white space or / */ |
| if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */ |
| return nil; |
| nstr = textfilewidth(t, t->q0, TRUE); |
| str = runemalloc(nstr); |
| npath = textfilewidth(t, t->q0-nstr, FALSE); |
| path = runemalloc(npath); |
| |
| c = nil; |
| rp = nil; |
| dirs = nil; |
| |
| q = t->q0-nstr; |
| for(i=0; i<nstr; i++) |
| str[i] = textreadc(t, q++); |
| q = t->q0-nstr-npath; |
| for(i=0; i<npath; i++) |
| path[i] = textreadc(t, q++); |
| /* is path rooted? if not, we need to make it relative to window path */ |
| if(npath>0 && path[0]=='/') |
| dir = runestr(path, npath); |
| else{ |
| dir = dirname(t, nil, 0); |
| if(dir.nr + 1 + npath > nelem(tmp)){ |
| free(dir.r); |
| goto Return; |
| } |
| if(dir.nr == 0){ |
| dir.nr = 1; |
| dir.r = runestrdup(Ldot); |
| } |
| runemove(tmp, dir.r, dir.nr); |
| tmp[dir.nr] = '/'; |
| runemove(tmp+dir.nr+1, path, npath); |
| free(dir.r); |
| dir.r = tmp; |
| dir.nr += 1+npath; |
| dir = cleanrname(dir); |
| } |
| |
| s = smprint("%.*S", nstr, str); |
| dirs = smprint("%.*S", dir.nr, dir.r); |
| c = complete(dirs, s); |
| free(s); |
| if(c == nil){ |
| warning(nil, "error attempting completion: %r\n"); |
| goto Return; |
| } |
| |
| if(!c->advance){ |
| warning(nil, "%.*S%s%.*S*%s\n", |
| dir.nr, dir.r, |
| dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "", |
| nstr, str, |
| c->nmatch ? "" : ": no matches in:"); |
| for(i=0; i<c->nfile; i++) |
| warning(nil, " %s\n", c->filename[i]); |
| } |
| |
| if(c->advance) |
| rp = runesmprint("%s", c->string); |
| else |
| rp = nil; |
| Return: |
| freecompletion(c); |
| free(dirs); |
| free(str); |
| free(path); |
| return rp; |
| } |
| |
| void |
| texttype(Text *t, Rune r) |
| { |
| uint q0, q1; |
| int nnb, nb, n, i; |
| int nr; |
| Rune *rp; |
| Text *u; |
| |
| if(t->what!=Body && t->what!=Tag && r=='\n') |
| return; |
| if(t->what == Tag) |
| t->w->tagsafe = FALSE; |
| |
| nr = 1; |
| rp = &r; |
| switch(r){ |
| case Kleft: |
| typecommit(t); |
| if(t->q0 > 0) |
| textshow(t, t->q0-1, t->q0-1, TRUE); |
| return; |
| case Kright: |
| typecommit(t); |
| if(t->q1 < t->file->b.nc) |
| textshow(t, t->q1+1, t->q1+1, TRUE); |
| return; |
| case Kdown: |
| if(t->what == Tag) |
| goto Tagdown; |
| n = t->fr.maxlines/3; |
| goto case_Down; |
| case Kscrollonedown: |
| if(t->what == Tag) |
| goto Tagdown; |
| n = mousescrollsize(t->fr.maxlines); |
| if(n <= 0) |
| n = 1; |
| goto case_Down; |
| case Kpgdown: |
| n = 2*t->fr.maxlines/3; |
| case_Down: |
| q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height)); |
| textsetorigin(t, q0, TRUE); |
| return; |
| case Kup: |
| if(t->what == Tag) |
| goto Tagup; |
| n = t->fr.maxlines/3; |
| goto case_Up; |
| case Kscrolloneup: |
| if(t->what == Tag) |
| goto Tagup; |
| n = mousescrollsize(t->fr.maxlines); |
| goto case_Up; |
| case Kpgup: |
| n = 2*t->fr.maxlines/3; |
| case_Up: |
| q0 = textbacknl(t, t->org, n); |
| textsetorigin(t, q0, TRUE); |
| return; |
| case Khome: |
| typecommit(t); |
| if(t->org > t->iq1) { |
| q0 = textbacknl(t, t->iq1, 1); |
| textsetorigin(t, q0, TRUE); |
| } else |
| textshow(t, 0, 0, FALSE); |
| return; |
| case Kend: |
| typecommit(t); |
| if(t->iq1 > t->org+t->fr.nchars) { |
| if(t->iq1 > t->file->b.nc) { |
| // should not happen, but does. and it will crash textbacknl. |
| t->iq1 = t->file->b.nc; |
| } |
| q0 = textbacknl(t, t->iq1, 1); |
| textsetorigin(t, q0, TRUE); |
| } else |
| textshow(t, t->file->b.nc, t->file->b.nc, FALSE); |
| return; |
| case 0x01: /* ^A: beginning of line */ |
| typecommit(t); |
| /* go to where ^U would erase, if not already at BOL */ |
| nnb = 0; |
| if(t->q0>0 && textreadc(t, t->q0-1)!='\n') |
| nnb = textbswidth(t, 0x15); |
| textshow(t, t->q0-nnb, t->q0-nnb, TRUE); |
| return; |
| case 0x05: /* ^E: end of line */ |
| typecommit(t); |
| q0 = t->q0; |
| while(q0<t->file->b.nc && textreadc(t, q0)!='\n') |
| q0++; |
| textshow(t, q0, q0, TRUE); |
| return; |
| case Kcmd+'c': /* %C: copy */ |
| typecommit(t); |
| cut(t, t, nil, TRUE, FALSE, nil, 0); |
| return; |
| case Kcmd+'z': /* %Z: undo */ |
| typecommit(t); |
| undo(t, nil, nil, TRUE, 0, nil, 0); |
| return; |
| case Kcmd+'Z': /* %-shift-Z: redo */ |
| typecommit(t); |
| undo(t, nil, nil, FALSE, 0, nil, 0); |
| return; |
| |
| Tagdown: |
| /* expand tag to show all text */ |
| if(!t->w->tagexpand){ |
| t->w->tagexpand = TRUE; |
| winresize(t->w, t->w->r, FALSE, TRUE); |
| } |
| return; |
| |
| Tagup: |
| /* shrink tag to single line */ |
| if(t->w->tagexpand){ |
| t->w->tagexpand = FALSE; |
| t->w->taglines = 1; |
| winresize(t->w, t->w->r, FALSE, TRUE); |
| } |
| return; |
| } |
| if(t->what == Body){ |
| seq++; |
| filemark(t->file); |
| } |
| /* cut/paste must be done after the seq++/filemark */ |
| switch(r){ |
| case Kcmd+'x': /* %X: cut */ |
| typecommit(t); |
| if(t->what == Body){ |
| seq++; |
| filemark(t->file); |
| } |
| cut(t, t, nil, TRUE, TRUE, nil, 0); |
| textshow(t, t->q0, t->q0, 1); |
| t->iq1 = t->q0; |
| return; |
| case Kcmd+'v': /* %V: paste */ |
| typecommit(t); |
| if(t->what == Body){ |
| seq++; |
| filemark(t->file); |
| } |
| paste(t, t, nil, TRUE, FALSE, nil, 0); |
| textshow(t, t->q0, t->q1, 1); |
| t->iq1 = t->q1; |
| return; |
| } |
| if(t->q1 > t->q0){ |
| if(t->ncache != 0) |
| error("text.type"); |
| cut(t, t, nil, TRUE, TRUE, nil, 0); |
| t->eq0 = ~0; |
| } |
| textshow(t, t->q0, t->q0, 1); |
| switch(r){ |
| case 0x06: /* ^F: complete */ |
| case Kins: |
| typecommit(t); |
| rp = textcomplete(t); |
| if(rp == nil) |
| return; |
| nr = runestrlen(rp); |
| break; /* fall through to normal insertion case */ |
| case 0x1B: |
| if(t->eq0 != ~0) { |
| if(t->eq0 <= t->q0) |
| textsetselect(t, t->eq0, t->q0); |
| else |
| textsetselect(t, t->q0, t->eq0); |
| } |
| if(t->ncache > 0) |
| typecommit(t); |
| t->iq1 = t->q0; |
| return; |
| case 0x08: /* ^H: erase character */ |
| case 0x15: /* ^U: erase line */ |
| case 0x17: /* ^W: erase word */ |
| if(t->q0 == 0) /* nothing to erase */ |
| return; |
| nnb = textbswidth(t, r); |
| q1 = t->q0; |
| q0 = q1-nnb; |
| /* if selection is at beginning of window, avoid deleting invisible text */ |
| if(q0 < t->org){ |
| q0 = t->org; |
| nnb = q1-q0; |
| } |
| if(nnb <= 0) |
| return; |
| for(i=0; i<t->file->ntext; i++){ |
| u = t->file->text[i]; |
| u->nofill = TRUE; |
| nb = nnb; |
| n = u->ncache; |
| if(n > 0){ |
| if(q1 != u->cq0+n) |
| error("text.type backspace"); |
| if(n > nb) |
| n = nb; |
| u->ncache -= n; |
| textdelete(u, q1-n, q1, FALSE); |
| nb -= n; |
| } |
| if(u->eq0==q1 || u->eq0==~0) |
| u->eq0 = q0; |
| if(nb && u==t) |
| textdelete(u, q0, q0+nb, TRUE); |
| if(u != t) |
| textsetselect(u, u->q0, u->q1); |
| else |
| textsetselect(t, q0, q0); |
| u->nofill = FALSE; |
| } |
| for(i=0; i<t->file->ntext; i++) |
| textfill(t->file->text[i]); |
| t->iq1 = t->q0; |
| return; |
| case '\n': |
| if(t->w->autoindent){ |
| /* find beginning of previous line using backspace code */ |
| nnb = textbswidth(t, 0x15); /* ^U case */ |
| rp = runemalloc(nnb + 1); |
| nr = 0; |
| rp[nr++] = r; |
| for(i=0; i<nnb; i++){ |
| r = textreadc(t, t->q0-nnb+i); |
| if(r != ' ' && r != '\t') |
| break; |
| rp[nr++] = r; |
| } |
| } |
| break; /* fall through to normal code */ |
| } |
| /* otherwise ordinary character; just insert, typically in caches of all texts */ |
| for(i=0; i<t->file->ntext; i++){ |
| u = t->file->text[i]; |
| if(u->eq0 == ~0) |
| u->eq0 = t->q0; |
| if(u->ncache == 0) |
| u->cq0 = t->q0; |
| else if(t->q0 != u->cq0+u->ncache) |
| error("text.type cq1"); |
| /* |
| * Change the tag before we add to ncache, |
| * so that if the window body is resized the |
| * commit will not find anything in ncache. |
| */ |
| if(u->what==Body && u->ncache == 0){ |
| u->needundo = TRUE; |
| winsettag(t->w); |
| u->needundo = FALSE; |
| } |
| textinsert(u, t->q0, rp, nr, FALSE); |
| if(u != t) |
| textsetselect(u, u->q0, u->q1); |
| if(u->ncache+nr > u->ncachealloc){ |
| u->ncachealloc += 10 + nr; |
| u->cache = runerealloc(u->cache, u->ncachealloc); |
| } |
| runemove(u->cache+u->ncache, rp, nr); |
| u->ncache += nr; |
| } |
| if(rp != &r) |
| free(rp); |
| textsetselect(t, t->q0+nr, t->q0+nr); |
| if(r=='\n' && t->w!=nil) |
| wincommit(t->w, t); |
| t->iq1 = t->q0; |
| } |
| |
| void |
| textcommit(Text *t, int tofile) |
| { |
| if(t->ncache == 0) |
| return; |
| if(tofile) |
| fileinsert(t->file, t->cq0, t->cache, t->ncache); |
| if(t->what == Body){ |
| t->w->dirty = TRUE; |
| t->w->utflastqid = -1; |
| } |
| t->ncache = 0; |
| } |
| |
| static Text *clicktext; |
| static uint clickmsec; |
| static Text *selecttext; |
| static uint selectq; |
| |
| /* |
| * called from frame library |
| */ |
| void |
| framescroll(Frame *f, int dl) |
| { |
| if(f != &selecttext->fr) |
| error("frameselect not right frame"); |
| textframescroll(selecttext, dl); |
| } |
| |
| void |
| textframescroll(Text *t, int dl) |
| { |
| uint q0; |
| |
| if(dl == 0){ |
| scrsleep(100); |
| return; |
| } |
| if(dl < 0){ |
| q0 = textbacknl(t, t->org, -dl); |
| if(selectq > t->org+t->fr.p0) |
| textsetselect(t, t->org+t->fr.p0, selectq); |
| else |
| textsetselect(t, selectq, t->org+t->fr.p0); |
| }else{ |
| if(t->org+t->fr.nchars == t->file->b.nc) |
| return; |
| q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height)); |
| if(selectq > t->org+t->fr.p1) |
| textsetselect(t, t->org+t->fr.p1, selectq); |
| else |
| textsetselect(t, selectq, t->org+t->fr.p1); |
| } |
| textsetorigin(t, q0, TRUE); |
| } |
| |
| |
| void |
| textselect(Text *t) |
| { |
| uint q0, q1; |
| int b, x, y; |
| int state; |
| enum { None, Cut, Paste }; |
| |
| selecttext = t; |
| /* |
| * To have double-clicking and chording, we double-click |
| * immediately if it might make sense. |
| */ |
| b = mouse->buttons; |
| q0 = t->q0; |
| q1 = t->q1; |
| selectq = t->org+frcharofpt(&t->fr, mouse->xy); |
| if(clicktext==t && mouse->msec-clickmsec<500) |
| if(q0==q1 && selectq==q0){ |
| textdoubleclick(t, &q0, &q1); |
| textsetselect(t, q0, q1); |
| flushimage(display, 1); |
| x = mouse->xy.x; |
| y = mouse->xy.y; |
| /* stay here until something interesting happens */ |
| do |
| readmouse(mousectl); |
| while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3); |
| mouse->xy.x = x; /* in case we're calling frselect */ |
| mouse->xy.y = y; |
| q0 = t->q0; /* may have changed */ |
| q1 = t->q1; |
| selectq = q0; |
| } |
| if(mouse->buttons == b){ |
| t->fr.scroll = framescroll; |
| frselect(&t->fr, mousectl); |
| /* horrible botch: while asleep, may have lost selection altogether */ |
| if(selectq > t->file->b.nc) |
| selectq = t->org + t->fr.p0; |
| t->fr.scroll = nil; |
| if(selectq < t->org) |
| q0 = selectq; |
| else |
| q0 = t->org + t->fr.p0; |
| if(selectq > t->org+t->fr.nchars) |
| q1 = selectq; |
| else |
| q1 = t->org+t->fr.p1; |
| } |
| if(q0 == q1){ |
| if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){ |
| textdoubleclick(t, &q0, &q1); |
| clicktext = nil; |
| }else{ |
| clicktext = t; |
| clickmsec = mouse->msec; |
| } |
| }else |
| clicktext = nil; |
| textsetselect(t, q0, q1); |
| flushimage(display, 1); |
| state = None; /* what we've done; undo when possible */ |
| while(mouse->buttons){ |
| mouse->msec = 0; |
| b = mouse->buttons; |
| if((b&1) && (b&6)){ |
| if(state==None && t->what==Body){ |
| seq++; |
| filemark(t->w->body.file); |
| } |
| if(b & 2){ |
| if(state==Paste && t->what==Body){ |
| winundo(t->w, TRUE); |
| textsetselect(t, q0, t->q1); |
| state = None; |
| }else if(state != Cut){ |
| cut(t, t, nil, TRUE, TRUE, nil, 0); |
| state = Cut; |
| } |
| }else{ |
| if(state==Cut && t->what==Body){ |
| winundo(t->w, TRUE); |
| textsetselect(t, q0, t->q1); |
| state = None; |
| }else if(state != Paste){ |
| paste(t, t, nil, TRUE, FALSE, nil, 0); |
| state = Paste; |
| } |
| } |
| textscrdraw(t); |
| clearmouse(); |
| } |
| flushimage(display, 1); |
| while(mouse->buttons == b) |
| readmouse(mousectl); |
| clicktext = nil; |
| } |
| } |
| |
| void |
| textshow(Text *t, uint q0, uint q1, int doselect) |
| { |
| int qe; |
| int nl; |
| int tsd; |
| int nc; |
| uint q; |
| |
| if(t->what != Body){ |
| if(doselect) |
| textsetselect(t, q0, q1); |
| return; |
| } |
| if(t->w!=nil && t->fr.maxlines==0) |
| colgrow(t->col, t->w, 1); |
| if(doselect) |
| textsetselect(t, q0, q1); |
| qe = t->org+t->fr.nchars; |
| tsd = FALSE; /* do we call textscrdraw? */ |
| nc = t->file->b.nc+t->ncache; |
| if(t->org <= q0){ |
| if(nc==0 || q0<qe) |
| tsd = TRUE; |
| else if(q0==qe && qe==nc){ |
| if(textreadc(t, nc-1) == '\n'){ |
| if(t->fr.nlines<t->fr.maxlines) |
| tsd = TRUE; |
| }else |
| tsd = TRUE; |
| } |
| } |
| if(tsd) |
| textscrdraw(t); |
| else{ |
| if(t->w->nopen[QWevent] > 0) |
| nl = 3*t->fr.maxlines/4; |
| else |
| nl = t->fr.maxlines/4; |
| q = textbacknl(t, q0, nl); |
| /* avoid going backwards if trying to go forwards - long lines! */ |
| if(!(q0>t->org && q<t->org)) |
| textsetorigin(t, q, TRUE); |
| while(q0 > t->org+t->fr.nchars) |
| textsetorigin(t, t->org+1, FALSE); |
| } |
| } |
| |
| static |
| int |
| region(int a, int b) |
| { |
| if(a < b) |
| return -1; |
| if(a == b) |
| return 0; |
| return 1; |
| } |
| |
| void |
| selrestore(Frame *f, Point pt0, uint p0, uint p1) |
| { |
| if(p1<=f->p0 || p0>=f->p1){ |
| /* no overlap */ |
| frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]); |
| return; |
| } |
| if(p0>=f->p0 && p1<=f->p1){ |
| /* entirely inside */ |
| frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); |
| return; |
| } |
| |
| /* they now are known to overlap */ |
| |
| /* before selection */ |
| if(p0 < f->p0){ |
| frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]); |
| p0 = f->p0; |
| pt0 = frptofchar(f, p0); |
| } |
| /* after selection */ |
| if(p1 > f->p1){ |
| frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]); |
| p1 = f->p1; |
| } |
| /* inside selection */ |
| frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]); |
| } |
| |
| void |
| textsetselect(Text *t, uint q0, uint q1) |
| { |
| int p0, p1, ticked; |
| |
| /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */ |
| t->q0 = q0; |
| t->q1 = q1; |
| /* compute desired p0,p1 from q0,q1 */ |
| p0 = q0-t->org; |
| p1 = q1-t->org; |
| ticked = 1; |
| if(p0 < 0){ |
| ticked = 0; |
| p0 = 0; |
| } |
| if(p1 < 0) |
| p1 = 0; |
| if(p0 > t->fr.nchars) |
| p0 = t->fr.nchars; |
| if(p1 > t->fr.nchars){ |
| ticked = 0; |
| p1 = t->fr.nchars; |
| } |
| if(p0==t->fr.p0 && p1==t->fr.p1){ |
| if(p0 == p1 && ticked != t->fr.ticked) |
| frtick(&t->fr, frptofchar(&t->fr, p0), ticked); |
| return; |
| } |
| if(p0 > p1) |
| 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); |
| /* screen disagrees with desired selection */ |
| if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){ |
| /* no overlap or too easy to bother trying */ |
| frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0); |
| if(p0 != p1 || ticked) |
| frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1); |
| goto Return; |
| } |
| /* overlap; avoid unnecessary painting */ |
| if(p0 < t->fr.p0){ |
| /* extend selection backwards */ |
| frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1); |
| }else if(p0 > t->fr.p0){ |
| /* trim first part of selection */ |
| frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0); |
| } |
| if(p1 > t->fr.p1){ |
| /* extend selection forwards */ |
| frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1); |
| }else if(p1 < t->fr.p1){ |
| /* trim last part of selection */ |
| frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0); |
| } |
| |
| Return: |
| t->fr.p0 = p0; |
| t->fr.p1 = p1; |
| } |
| |
| /* |
| * Release the button in less than DELAY ms and it's considered a null selection |
| * if the mouse hardly moved, regardless of whether it crossed a char boundary. |
| */ |
| enum { |
| DELAY = 2, |
| MINMOVE = 4 |
| }; |
| |
| uint |
| xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */ |
| { |
| uint p0, p1, q, tmp; |
| ulong msec; |
| Point mp, pt0, pt1, qt; |
| int reg, b; |
| |
| mp = mc->m.xy; |
| b = mc->m.buttons; |
| msec = mc->m.msec; |
| |
| /* remove tick */ |
| if(f->p0 == f->p1) |
| frtick(f, frptofchar(f, f->p0), 0); |
| p0 = p1 = frcharofpt(f, mp); |
| pt0 = frptofchar(f, p0); |
| pt1 = frptofchar(f, p1); |
| reg = 0; |
| frtick(f, pt0, 1); |
| do{ |
| q = frcharofpt(f, mc->m.xy); |
| if(p1 != q){ |
| if(p0 == p1) |
| frtick(f, pt0, 0); |
| if(reg != region(q, p0)){ /* crossed starting point; reset */ |
| if(reg > 0) |
| selrestore(f, pt0, p0, p1); |
| else if(reg < 0) |
| selrestore(f, pt1, p1, p0); |
| p1 = p0; |
| pt1 = pt0; |
| reg = region(q, p0); |
| if(reg == 0) |
| frdrawsel0(f, pt0, p0, p1, col, display->white); |
| } |
| qt = frptofchar(f, q); |
| if(reg > 0){ |
| if(q > p1) |
| frdrawsel0(f, pt1, p1, q, col, display->white); |
| |
| else if(q < p1) |
| selrestore(f, qt, q, p1); |
| }else if(reg < 0){ |
| if(q > p1) |
| selrestore(f, pt1, p1, q); |
| else |
| frdrawsel0(f, qt, q, p1, col, display->white); |
| } |
| p1 = q; |
| pt1 = qt; |
| } |
| if(p0 == p1) |
| frtick(f, pt0, 1); |
| flushimage(f->display, 1); |
| readmouse(mc); |
| }while(mc->m.buttons == b); |
| if(mc->m.msec-msec < DELAY && p0!=p1 |
| && abs(mp.x-mc->m.xy.x)<MINMOVE |
| && abs(mp.y-mc->m.xy.y)<MINMOVE) { |
| if(reg > 0) |
| selrestore(f, pt0, p0, p1); |
| else if(reg < 0) |
| selrestore(f, pt1, p1, p0); |
| p1 = p0; |
| } |
| if(p1 < p0){ |
| tmp = p0; |
| p0 = p1; |
| p1 = tmp; |
| } |
| pt0 = frptofchar(f, p0); |
| if(p0 == p1) |
| frtick(f, pt0, 0); |
| selrestore(f, pt0, p0, p1); |
| /* restore tick */ |
| if(f->p0 == f->p1) |
| frtick(f, frptofchar(f, f->p0), 1); |
| flushimage(f->display, 1); |
| *p1p = p1; |
| return p0; |
| } |
| |
| int |
| textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask) |
| { |
| uint p0, p1; |
| int buts; |
| |
| p0 = xselect(&t->fr, mousectl, high, &p1); |
| buts = mousectl->m.buttons; |
| if((buts & mask) == 0){ |
| *q0 = p0+t->org; |
| *q1 = p1+t->org; |
| } |
| |
| while(mousectl->m.buttons) |
| readmouse(mousectl); |
| return buts; |
| } |
| |
| int |
| textselect2(Text *t, uint *q0, uint *q1, Text **tp) |
| { |
| int buts; |
| |
| *tp = nil; |
| buts = textselect23(t, q0, q1, but2col, 4); |
| if(buts & 4) |
| return 0; |
| if(buts & 1){ /* pick up argument */ |
| *tp = argtext; |
| return 1; |
| } |
| return 1; |
| } |
| |
| int |
| textselect3(Text *t, uint *q0, uint *q1) |
| { |
| int h; |
| |
| h = (textselect23(t, q0, q1, but3col, 1|2) == 0); |
| return h; |
| } |
| |
| static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 }; |
| static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 }; |
| static Rune left2[] = { '\n', 0 }; |
| static Rune left3[] = { '\'', '"', '`', 0 }; |
| |
| static |
| Rune *left[] = { |
| left1, |
| left2, |
| left3, |
| nil |
| }; |
| static |
| Rune *right[] = { |
| right1, |
| left2, |
| left3, |
| nil |
| }; |
| |
| void |
| textdoubleclick(Text *t, uint *q0, uint *q1) |
| { |
| int c, i; |
| Rune *r, *l, *p; |
| uint q; |
| |
| if(textclickhtmlmatch(t, q0, q1)) |
| return; |
| |
| for(i=0; left[i]!=nil; i++){ |
| q = *q0; |
| l = left[i]; |
| r = right[i]; |
| /* try matching character to left, looking right */ |
| if(q == 0) |
| c = '\n'; |
| else |
| c = textreadc(t, q-1); |
| p = runestrchr(l, c); |
| if(p != nil){ |
| if(textclickmatch(t, c, r[p-l], 1, &q)) |
| *q1 = q-(c!='\n'); |
| return; |
| } |
| /* try matching character to right, looking left */ |
| if(q == t->file->b.nc) |
| c = '\n'; |
| else |
| c = textreadc(t, q); |
| p = runestrchr(r, c); |
| if(p != nil){ |
| if(textclickmatch(t, c, l[p-r], -1, &q)){ |
| *q1 = *q0+(*q0<t->file->b.nc && c=='\n'); |
| *q0 = q; |
| if(c!='\n' || q!=0 || textreadc(t, 0)=='\n') |
| (*q0)++; |
| } |
| return; |
| } |
| } |
| |
| /* try filling out word to right */ |
| while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1))) |
| (*q1)++; |
| /* try filling out word to left */ |
| while(*q0>0 && isalnum(textreadc(t, *q0-1))) |
| (*q0)--; |
| } |
| |
| int |
| textclickmatch(Text *t, int cl, int cr, int dir, uint *q) |
| { |
| Rune c; |
| int nest; |
| |
| nest = 1; |
| for(;;){ |
| if(dir > 0){ |
| if(*q == t->file->b.nc) |
| break; |
| c = textreadc(t, *q); |
| (*q)++; |
| }else{ |
| if(*q == 0) |
| break; |
| (*q)--; |
| c = textreadc(t, *q); |
| } |
| if(c == cr){ |
| if(--nest==0) |
| return 1; |
| }else if(c == cl) |
| nest++; |
| } |
| return cl=='\n' && nest==1; |
| } |
| |
| // Is the text starting at location q an html tag? |
| // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />. |
| // Set *q1, if non-nil, to the location after the tag. |
| static int |
| ishtmlstart(Text *t, uint q, uint *q1) |
| { |
| int c, c1, c2; |
| |
| if(q+2 > t->file->b.nc) |
| return 0; |
| if(textreadc(t, q++) != '<') |
| return 0; |
| c = textreadc(t, q++); |
| c1 = c; |
| c2 = c; |
| while(c != '>') { |
| if(q >= t->file->b.nc) |
| return 0; |
| c2 = c; |
| c = textreadc(t, q++); |
| } |
| if(q1) |
| *q1 = q; |
| if(c1 == '/') // closing tag |
| return -1; |
| if(c2 == '/' || c2 == '!') // open + close tag or comment |
| return 0; |
| return 1; |
| } |
| |
| // Is the text ending at location q an html tag? |
| // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />. |
| // Set *q0, if non-nil, to the start of the tag. |
| static int |
| ishtmlend(Text *t, uint q, uint *q0) |
| { |
| int c, c1, c2; |
| |
| if(q < 2) |
| return 0; |
| if(textreadc(t, --q) != '>') |
| return 0; |
| c = textreadc(t, --q); |
| c1 = c; |
| c2 = c; |
| while(c != '<') { |
| if(q == 0) |
| return 0; |
| c1 = c; |
| c = textreadc(t, --q); |
| } |
| if(q0) |
| *q0 = q; |
| if(c1 == '/') // closing tag |
| return -1; |
| if(c2 == '/' || c2 == '!') // open + close tag or comment |
| return 0; |
| return 1; |
| } |
| |
| int |
| textclickhtmlmatch(Text *t, uint *q0, uint *q1) |
| { |
| int depth, n; |
| uint q, nq; |
| |
| q = *q0; |
| // after opening tag? scan forward for closing tag |
| if(ishtmlend(t, q, nil) == 1) { |
| depth = 1; |
| while(q < t->file->b.nc) { |
| n = ishtmlstart(t, q, &nq); |
| if(n != 0) { |
| depth += n; |
| if(depth == 0) { |
| *q1 = q; |
| return 1; |
| } |
| q = nq; |
| continue; |
| } |
| q++; |
| } |
| } |
| |
| // before closing tag? scan backward for opening tag |
| if(ishtmlstart(t, q, nil) == -1) { |
| depth = -1; |
| while(q > 0) { |
| n = ishtmlend(t, q, &nq); |
| if(n != 0) { |
| depth += n; |
| if(depth == 0) { |
| *q0 = q; |
| return 1; |
| } |
| q = nq; |
| continue; |
| } |
| q--; |
| } |
| } |
| |
| return 0; |
| } |
| |
| uint |
| textbacknl(Text *t, uint p, uint n) |
| { |
| int i, j; |
| |
| /* look for start of this line if n==0 */ |
| if(n==0 && p>0 && textreadc(t, p-1)!='\n') |
| n = 1; |
| i = n; |
| while(i-->0 && p>0){ |
| --p; /* it's at a newline now; back over it */ |
| if(p == 0) |
| break; |
| /* at 128 chars, call it a line anyway */ |
| for(j=128; --j>0 && p>0; p--) |
| if(textreadc(t, p-1)=='\n') |
| break; |
| } |
| return p; |
| } |
| |
| void |
| textsetorigin(Text *t, uint org, int exact) |
| { |
| int i, a, fixup; |
| Rune *r; |
| uint n; |
| |
| if(org>0 && !exact){ |
| /* org is an estimate of the char posn; find a newline */ |
| /* don't try harder than 256 chars */ |
| for(i=0; i<256 && org<t->file->b.nc; i++){ |
| if(textreadc(t, org) == '\n'){ |
| org++; |
| break; |
| } |
| org++; |
| } |
| } |
| a = org-t->org; |
| fixup = 0; |
| if(a>=0 && a<t->fr.nchars){ |
| frdelete(&t->fr, 0, a); |
| fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */ |
| } |
| else if(a<0 && -a<t->fr.nchars){ |
| n = t->org - org; |
| r = runemalloc(n); |
| bufread(&t->file->b, org, r, n); |
| frinsert(&t->fr, r, r+n, 0); |
| free(r); |
| }else |
| frdelete(&t->fr, 0, t->fr.nchars); |
| t->org = org; |
| textfill(t); |
| textscrdraw(t); |
| textsetselect(t, t->q0, t->q1); |
| if(fixup && t->fr.p1 > t->fr.p0) |
| frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1); |
| } |
| |
| void |
| textreset(Text *t) |
| { |
| t->file->seq = 0; |
| t->eq0 = ~0; |
| /* do t->delete(0, t->nc, TRUE) without building backup stuff */ |
| textsetselect(t, t->org, t->org); |
| frdelete(&t->fr, 0, t->fr.nchars); |
| t->org = 0; |
| t->q0 = 0; |
| t->q1 = 0; |
| filereset(t->file); |
| bufreset(&t->file->b); |
| } |