|  | #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; | 
|  |  | 
|  | 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); | 
|  | } |