|  | #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 "dat.h" | 
|  | #include "edit.h" | 
|  | #include "fns.h" | 
|  |  | 
|  | int	Glooping; | 
|  | int	nest; | 
|  | char	Enoname[] = "no file name given"; | 
|  |  | 
|  | Address	addr; | 
|  | File	*menu; | 
|  | Rangeset	sel; | 
|  | extern	Text*	curtext; | 
|  | Rune	*collection; | 
|  | int	ncollection; | 
|  |  | 
|  | int	append(File*, Cmd*, long); | 
|  | int	pdisplay(File*); | 
|  | void	pfilename(File*); | 
|  | void	looper(File*, Cmd*, int); | 
|  | void	filelooper(Cmd*, int); | 
|  | void	linelooper(File*, Cmd*); | 
|  | Address	lineaddr(long, Address, int); | 
|  | int	filematch(File*, String*); | 
|  | File	*tofile(String*); | 
|  | Rune*	cmdname(File *f, String *s, int); | 
|  | void	runpipe(Text*, int, Rune*, int, int); | 
|  |  | 
|  | void | 
|  | clearcollection(void) | 
|  | { | 
|  | free(collection); | 
|  | collection = nil; | 
|  | ncollection = 0; | 
|  | } | 
|  |  | 
|  | void | 
|  | resetxec(void) | 
|  | { | 
|  | Glooping = nest = 0; | 
|  | clearcollection(); | 
|  | } | 
|  |  | 
|  | void | 
|  | mkaddr(Address *a, File *f) | 
|  | { | 
|  | a->r.q0 = f->curtext->q0; | 
|  | a->r.q1 = f->curtext->q1; | 
|  | a->f = f; | 
|  | } | 
|  |  | 
|  | int | 
|  | cmdexec(Text *t, Cmd *cp) | 
|  | { | 
|  | int i; | 
|  | Addr *ap; | 
|  | File *f; | 
|  | Window *w; | 
|  | Address dot; | 
|  |  | 
|  | if(t == nil) | 
|  | w = nil; | 
|  | else | 
|  | w = t->w; | 
|  | if(w==nil && (cp->addr==0 || cp->addr->type!='"') && | 
|  | !utfrune("bBnqUXY!", cp->cmdc) && | 
|  | !(cp->cmdc=='D' && cp->u.text)) | 
|  | editerror("no current window"); | 
|  | i = cmdlookup(cp->cmdc);	/* will be -1 for '{' */ | 
|  | f = nil; | 
|  | if(t && t->w){ | 
|  | t = &t->w->body; | 
|  | f = t->file; | 
|  | f->curtext = t; | 
|  | } | 
|  | if(i>=0 && cmdtab[i].defaddr != aNo){ | 
|  | if((ap=cp->addr)==0 && cp->cmdc!='\n'){ | 
|  | cp->addr = ap = newaddr(); | 
|  | ap->type = '.'; | 
|  | if(cmdtab[i].defaddr == aAll) | 
|  | ap->type = '*'; | 
|  | }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){ | 
|  | ap->next = newaddr(); | 
|  | ap->next->type = '.'; | 
|  | if(cmdtab[i].defaddr == aAll) | 
|  | ap->next->type = '*'; | 
|  | } | 
|  | if(cp->addr){	/* may be false for '\n' (only) */ | 
|  | static Address none = {0,0,nil}; | 
|  | if(f){ | 
|  | mkaddr(&dot, f); | 
|  | addr = cmdaddress(ap, dot, 0); | 
|  | }else	/* a " */ | 
|  | addr = cmdaddress(ap, none, 0); | 
|  | f = addr.f; | 
|  | t = f->curtext; | 
|  | } | 
|  | } | 
|  | switch(cp->cmdc){ | 
|  | case '{': | 
|  | mkaddr(&dot, f); | 
|  | if(cp->addr != nil) | 
|  | dot = cmdaddress(cp->addr, dot, 0); | 
|  | for(cp = cp->u.cmd; cp; cp = cp->next){ | 
|  | if(dot.r.q1 > t->file->b.nc) | 
|  | editerror("dot extends past end of buffer during { command"); | 
|  | t->q0 = dot.r.q0; | 
|  | t->q1 = dot.r.q1; | 
|  | cmdexec(t, cp); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | if(i < 0) | 
|  | editerror("unknown command %c in cmdexec", cp->cmdc); | 
|  | i = (*cmdtab[i].fn)(t, cp); | 
|  | return i; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | char* | 
|  | edittext(Window *w, int q, Rune *r, int nr) | 
|  | { | 
|  | File *f; | 
|  |  | 
|  | f = w->body.file; | 
|  | switch(editing){ | 
|  | case Inactive: | 
|  | return "permission denied"; | 
|  | case Inserting: | 
|  | eloginsert(f, q, r, nr); | 
|  | return nil; | 
|  | case Collecting: | 
|  | collection = runerealloc(collection, ncollection+nr+1); | 
|  | runemove(collection+ncollection, r, nr); | 
|  | ncollection += nr; | 
|  | collection[ncollection] = '\0'; | 
|  | return nil; | 
|  | default: | 
|  | return "unknown state in edittext"; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* string is known to be NUL-terminated */ | 
|  | Rune* | 
|  | filelist(Text *t, Rune *r, int nr) | 
|  | { | 
|  | if(nr == 0) | 
|  | return nil; | 
|  | r = skipbl(r, nr, &nr); | 
|  | if(r[0] != '<') | 
|  | return runestrdup(r); | 
|  | /* use < command to collect text */ | 
|  | clearcollection(); | 
|  | runpipe(t, '<', r+1, nr-1, Collecting); | 
|  | return collection; | 
|  | } | 
|  |  | 
|  | int | 
|  | a_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | return append(t->file, cp, addr.r.q1); | 
|  | } | 
|  |  | 
|  | int | 
|  | b_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | File *f; | 
|  |  | 
|  | USED(t); | 
|  | f = tofile(cp->u.text); | 
|  | if(nest == 0) | 
|  | pfilename(f); | 
|  | curtext = f->curtext; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | B_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Rune *list, *r, *s; | 
|  | int nr; | 
|  |  | 
|  | list = filelist(t, cp->u.text->r, cp->u.text->n); | 
|  | if(list == nil) | 
|  | editerror(Enoname); | 
|  | r = list; | 
|  | nr = runestrlen(r); | 
|  | r = skipbl(r, nr, &nr); | 
|  | if(nr == 0) | 
|  | new(t, t, nil, 0, 0, r, 0); | 
|  | else while(nr > 0){ | 
|  | s = findbl(r, nr, &nr); | 
|  | *s = '\0'; | 
|  | new(t, t, nil, 0, 0, r, runestrlen(r)); | 
|  | if(nr > 0) | 
|  | r = skipbl(s+1, nr-1, &nr); | 
|  | } | 
|  | clearcollection(); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | c_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n); | 
|  | t->q0 = addr.r.q0; | 
|  | t->q1 = addr.r.q1; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | d_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | USED(cp); | 
|  | if(addr.r.q1 > addr.r.q0) | 
|  | elogdelete(t->file, addr.r.q0, addr.r.q1); | 
|  | t->q0 = addr.r.q0; | 
|  | t->q1 = addr.r.q0; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | void | 
|  | D1(Text *t) | 
|  | { | 
|  | if(t->w->body.file->ntext>1 || winclean(t->w, FALSE)) | 
|  | colclose(t->col, t->w, TRUE); | 
|  | } | 
|  |  | 
|  | int | 
|  | D_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Rune *list, *r, *s, *n; | 
|  | int nr, nn; | 
|  | Window *w; | 
|  | Runestr dir, rs; | 
|  | char buf[128]; | 
|  |  | 
|  | list = filelist(t, cp->u.text->r, cp->u.text->n); | 
|  | if(list == nil){ | 
|  | D1(t); | 
|  | return TRUE; | 
|  | } | 
|  | dir = dirname(t, nil, 0); | 
|  | r = list; | 
|  | nr = runestrlen(r); | 
|  | r = skipbl(r, nr, &nr); | 
|  | do{ | 
|  | s = findbl(r, nr, &nr); | 
|  | *s = '\0'; | 
|  | /* first time through, could be empty string, meaning delete file empty name */ | 
|  | nn = runestrlen(r); | 
|  | if(r[0]=='/' || nn==0 || dir.nr==0){ | 
|  | rs.r = runestrdup(r); | 
|  | rs.nr = nn; | 
|  | }else{ | 
|  | n = runemalloc(dir.nr+1+nn); | 
|  | runemove(n, dir.r, dir.nr); | 
|  | n[dir.nr] = '/'; | 
|  | runemove(n+dir.nr+1, r, nn); | 
|  | rs = cleanrname(runestr(n, dir.nr+1+nn)); | 
|  | } | 
|  | w = lookfile(rs.r, rs.nr); | 
|  | if(w == nil){ | 
|  | snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r); | 
|  | free(rs.r); | 
|  | editerror(buf); | 
|  | } | 
|  | free(rs.r); | 
|  | D1(&w->body); | 
|  | if(nr > 0) | 
|  | r = skipbl(s+1, nr-1, &nr); | 
|  | }while(nr > 0); | 
|  | clearcollection(); | 
|  | free(dir.r); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static int | 
|  | readloader(void *v, uint q0, Rune *r, int nr) | 
|  | { | 
|  | if(nr > 0) | 
|  | eloginsert(v, q0, r, nr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | e_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Rune *name; | 
|  | File *f; | 
|  | int i, isdir, q0, q1, fd, nulls, samename, allreplaced; | 
|  | char *s, tmp[128]; | 
|  | Dir *d; | 
|  |  | 
|  | f = t->file; | 
|  | q0 = addr.r.q0; | 
|  | q1 = addr.r.q1; | 
|  | if(cp->cmdc == 'e'){ | 
|  | if(winclean(t->w, TRUE)==FALSE) | 
|  | editerror("");	/* winclean generated message already */ | 
|  | q0 = 0; | 
|  | q1 = f->b.nc; | 
|  | } | 
|  | allreplaced = (q0==0 && q1==f->b.nc); | 
|  | name = cmdname(f, cp->u.text, cp->cmdc=='e'); | 
|  | if(name == nil) | 
|  | editerror(Enoname); | 
|  | i = runestrlen(name); | 
|  | samename = runeeq(name, i, t->file->name, t->file->nname); | 
|  | s = runetobyte(name, i); | 
|  | free(name); | 
|  | fd = open(s, OREAD); | 
|  | if(fd < 0){ | 
|  | snprint(tmp, sizeof tmp, "can't open %s: %r", s); | 
|  | free(s); | 
|  | editerror(tmp); | 
|  | } | 
|  | d = dirfstat(fd); | 
|  | isdir = (d!=nil && (d->qid.type&QTDIR)); | 
|  | free(d); | 
|  | if(isdir){ | 
|  | close(fd); | 
|  | snprint(tmp, sizeof tmp, "%s is a directory", s); | 
|  | free(s); | 
|  | editerror(tmp); | 
|  | } | 
|  | elogdelete(f, q0, q1); | 
|  | nulls = 0; | 
|  | loadfile(fd, q1, &nulls, readloader, f); | 
|  | free(s); | 
|  | close(fd); | 
|  | if(nulls) | 
|  | warning(nil, "%s: NUL bytes elided\n", s); | 
|  | else if(allreplaced && samename) | 
|  | f->editclean = TRUE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static Rune Lempty[] = { 0 }; | 
|  | int | 
|  | f_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Rune *name; | 
|  | String *str; | 
|  | String empty; | 
|  |  | 
|  | if(cp->u.text == nil){ | 
|  | empty.n = 0; | 
|  | empty.r = Lempty; | 
|  | str = ∅ | 
|  | }else | 
|  | str = cp->u.text; | 
|  | name = cmdname(t->file, str, TRUE); | 
|  | free(name); | 
|  | pfilename(t->file); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | g_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | if(t->file != addr.f){ | 
|  | warning(nil, "internal error: g_cmd f!=addr.f\n"); | 
|  | return FALSE; | 
|  | } | 
|  | if(rxcompile(cp->re->r) == FALSE) | 
|  | editerror("bad regexp in g command"); | 
|  | if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){ | 
|  | t->q0 = addr.r.q0; | 
|  | t->q1 = addr.r.q1; | 
|  | return cmdexec(t, cp->u.cmd); | 
|  | } | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | i_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | return append(t->file, cp, addr.r.q0); | 
|  | } | 
|  |  | 
|  | void | 
|  | copy(File *f, Address addr2) | 
|  | { | 
|  | long p; | 
|  | int ni; | 
|  | Rune *buf; | 
|  |  | 
|  | buf = fbufalloc(); | 
|  | for(p=addr.r.q0; p<addr.r.q1; p+=ni){ | 
|  | ni = addr.r.q1-p; | 
|  | if(ni > RBUFSIZE) | 
|  | ni = RBUFSIZE; | 
|  | bufread(&f->b, p, buf, ni); | 
|  | eloginsert(addr2.f, addr2.r.q1, buf, ni); | 
|  | } | 
|  | fbuffree(buf); | 
|  | } | 
|  |  | 
|  | void | 
|  | move(File *f, Address addr2) | 
|  | { | 
|  | if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){ | 
|  | elogdelete(f, addr.r.q0, addr.r.q1); | 
|  | copy(f, addr2); | 
|  | }else if(addr.r.q0 >= addr2.r.q1){ | 
|  | copy(f, addr2); | 
|  | elogdelete(f, addr.r.q0, addr.r.q1); | 
|  | }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){ | 
|  | ; /* move to self; no-op */ | 
|  | }else | 
|  | editerror("move overlaps itself"); | 
|  | } | 
|  |  | 
|  | int | 
|  | m_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Address dot, addr2; | 
|  |  | 
|  | mkaddr(&dot, t->file); | 
|  | addr2 = cmdaddress(cp->u.mtaddr, dot, 0); | 
|  | if(cp->cmdc == 'm') | 
|  | move(t->file, addr2); | 
|  | else | 
|  | copy(t->file, addr2); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | p_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | USED(cp); | 
|  | return pdisplay(t->file); | 
|  | } | 
|  |  | 
|  | int | 
|  | s_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | int i, j, k, c, m, n, nrp, didsub; | 
|  | long p1, op, delta; | 
|  | String *buf; | 
|  | Rangeset *rp; | 
|  | char *err; | 
|  | Rune *rbuf; | 
|  |  | 
|  | n = cp->num; | 
|  | op= -1; | 
|  | if(rxcompile(cp->re->r) == FALSE) | 
|  | editerror("bad regexp in s command"); | 
|  | nrp = 0; | 
|  | rp = nil; | 
|  | delta = 0; | 
|  | didsub = FALSE; | 
|  | for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){ | 
|  | if(sel.r[0].q0 == sel.r[0].q1){	/* empty match? */ | 
|  | if(sel.r[0].q0 == op){ | 
|  | p1++; | 
|  | continue; | 
|  | } | 
|  | p1 = sel.r[0].q1+1; | 
|  | }else | 
|  | p1 = sel.r[0].q1; | 
|  | op = sel.r[0].q1; | 
|  | if(--n>0) | 
|  | continue; | 
|  | nrp++; | 
|  | rp = erealloc(rp, nrp*sizeof(Rangeset)); | 
|  | rp[nrp-1] = sel; | 
|  | } | 
|  | rbuf = fbufalloc(); | 
|  | buf = allocstring(0); | 
|  | for(m=0; m<nrp; m++){ | 
|  | buf->n = 0; | 
|  | buf->r[0] = '\0'; | 
|  | sel = rp[m]; | 
|  | for(i = 0; i<cp->u.text->n; i++) | 
|  | if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){ | 
|  | c = cp->u.text->r[++i]; | 
|  | if('1'<=c && c<='9') { | 
|  | j = c-'0'; | 
|  | if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){ | 
|  | err = "replacement string too long"; | 
|  | goto Err; | 
|  | } | 
|  | bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0); | 
|  | for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++) | 
|  | Straddc(buf, rbuf[k]); | 
|  | }else | 
|  | Straddc(buf, c); | 
|  | }else if(c!='&') | 
|  | Straddc(buf, c); | 
|  | else{ | 
|  | if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){ | 
|  | err = "right hand side too long in substitution"; | 
|  | goto Err; | 
|  | } | 
|  | bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0); | 
|  | for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++) | 
|  | Straddc(buf, rbuf[k]); | 
|  | } | 
|  | elogreplace(t->file, sel.r[0].q0, sel.r[0].q1,  buf->r, buf->n); | 
|  | delta -= sel.r[0].q1-sel.r[0].q0; | 
|  | delta += buf->n; | 
|  | didsub = 1; | 
|  | if(!cp->flag) | 
|  | break; | 
|  | } | 
|  | free(rp); | 
|  | freestring(buf); | 
|  | fbuffree(rbuf); | 
|  | if(!didsub && nest==0) | 
|  | editerror("no substitution"); | 
|  | t->q0 = addr.r.q0; | 
|  | t->q1 = addr.r.q1; | 
|  | return TRUE; | 
|  |  | 
|  | Err: | 
|  | free(rp); | 
|  | freestring(buf); | 
|  | fbuffree(rbuf); | 
|  | editerror(err); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | int | 
|  | u_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | int n, oseq, flag; | 
|  |  | 
|  | n = cp->num; | 
|  | flag = TRUE; | 
|  | if(n < 0){ | 
|  | n = -n; | 
|  | flag = FALSE; | 
|  | } | 
|  | oseq = -1; | 
|  | while(n-->0 && t->file->seq!=oseq){ | 
|  | oseq = t->file->seq; | 
|  | undo(t, nil, nil, flag, 0, nil, 0); | 
|  | } | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | w_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Rune *r; | 
|  | File *f; | 
|  |  | 
|  | f = t->file; | 
|  | if(f->seq == seq) | 
|  | editerror("can't write file with pending modifications"); | 
|  | r = cmdname(f, cp->u.text, FALSE); | 
|  | if(r == nil) | 
|  | editerror("no name specified for 'w' command"); | 
|  | putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r)); | 
|  | /* r is freed by putfile */ | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | x_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | if(cp->re) | 
|  | looper(t->file, cp, cp->cmdc=='x'); | 
|  | else | 
|  | linelooper(t->file, cp); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | X_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | USED(t); | 
|  |  | 
|  | filelooper(cp, cp->cmdc=='X'); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | void | 
|  | runpipe(Text *t, int cmd, Rune *cr, int ncr, int state) | 
|  | { | 
|  | Rune *r, *s; | 
|  | int n; | 
|  | Runestr dir; | 
|  | Window *w; | 
|  | QLock *q; | 
|  |  | 
|  | r = skipbl(cr, ncr, &n); | 
|  | if(n == 0) | 
|  | editerror("no command specified for %c", cmd); | 
|  | w = nil; | 
|  | if(state == Inserting){ | 
|  | w = t->w; | 
|  | t->q0 = addr.r.q0; | 
|  | t->q1 = addr.r.q1; | 
|  | if(cmd == '<' || cmd=='|') | 
|  | elogdelete(t->file, t->q0, t->q1); | 
|  | } | 
|  | s = runemalloc(n+2); | 
|  | s[0] = cmd; | 
|  | runemove(s+1, r, n); | 
|  | n++; | 
|  | dir.r = nil; | 
|  | dir.nr = 0; | 
|  | if(t != nil) | 
|  | dir = dirname(t, nil, 0); | 
|  | if(dir.nr==1 && dir.r[0]=='.'){	/* sigh */ | 
|  | free(dir.r); | 
|  | dir.r = nil; | 
|  | dir.nr = 0; | 
|  | } | 
|  | editing = state; | 
|  | if(t!=nil && t->w!=nil) | 
|  | incref(&t->w->ref);	/* run will decref */ | 
|  | run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE); | 
|  | free(s); | 
|  | if(t!=nil && t->w!=nil) | 
|  | winunlock(t->w); | 
|  | qunlock(&row.lk); | 
|  | recvul(cedit); | 
|  | /* | 
|  | * The editoutlk exists only so that we can tell when | 
|  | * the editout file has been closed.  It can get closed *after* | 
|  | * the process exits because, since the process cannot be | 
|  | * connected directly to editout (no 9P kernel support), | 
|  | * the process is actually connected to a pipe to another | 
|  | * process (arranged via 9pserve) that reads from the pipe | 
|  | * and then writes the data in the pipe to editout using | 
|  | * 9P transactions.  This process might still have a couple | 
|  | * writes left to copy after the original process has exited. | 
|  | */ | 
|  | if(w) | 
|  | q = &w->editoutlk; | 
|  | else | 
|  | q = &editoutlk; | 
|  | qlock(q);	/* wait for file to close */ | 
|  | qunlock(q); | 
|  | qlock(&row.lk); | 
|  | editing = Inactive; | 
|  | if(t!=nil && t->w!=nil) | 
|  | winlock(t->w, 'M'); | 
|  | } | 
|  |  | 
|  | int | 
|  | pipe_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | long | 
|  | nlcount(Text *t, long q0, long q1) | 
|  | { | 
|  | long nl; | 
|  | Rune *buf; | 
|  | int i, nbuf; | 
|  |  | 
|  | buf = fbufalloc(); | 
|  | nbuf = 0; | 
|  | i = nl = 0; | 
|  | while(q0 < q1){ | 
|  | if(i == nbuf){ | 
|  | nbuf = q1-q0; | 
|  | if(nbuf > RBUFSIZE) | 
|  | nbuf = RBUFSIZE; | 
|  | bufread(&t->file->b, q0, buf, nbuf); | 
|  | i = 0; | 
|  | } | 
|  | if(buf[i++] == '\n') | 
|  | nl++; | 
|  | q0++; | 
|  | } | 
|  | fbuffree(buf); | 
|  | return nl; | 
|  | } | 
|  |  | 
|  | void | 
|  | printposn(Text *t, int charsonly) | 
|  | { | 
|  | long l1, l2; | 
|  |  | 
|  | if (t != nil && t->file != nil && t->file->name != nil) | 
|  | warning(nil, "%.*S:", t->file->nname, t->file->name); | 
|  | if(!charsonly){ | 
|  | l1 = 1+nlcount(t, 0, addr.r.q0); | 
|  | l2 = l1+nlcount(t, addr.r.q0, addr.r.q1); | 
|  | /* check if addr ends with '\n' */ | 
|  | if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n') | 
|  | --l2; | 
|  | warning(nil, "%lud", l1); | 
|  | if(l2 != l1) | 
|  | warning(nil, ",%lud", l2); | 
|  | warning(nil, "\n"); | 
|  | return; | 
|  | } | 
|  | warning(nil, "#%d", addr.r.q0); | 
|  | if(addr.r.q1 != addr.r.q0) | 
|  | warning(nil, ",#%d", addr.r.q1); | 
|  | warning(nil, "\n"); | 
|  | } | 
|  |  | 
|  | int | 
|  | eq_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | int charsonly; | 
|  |  | 
|  | switch(cp->u.text->n){ | 
|  | case 0: | 
|  | charsonly = FALSE; | 
|  | break; | 
|  | case 1: | 
|  | if(cp->u.text->r[0] == '#'){ | 
|  | charsonly = TRUE; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | SET(charsonly); | 
|  | editerror("newline expected"); | 
|  | } | 
|  | printposn(t, charsonly); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | nl_cmd(Text *t, Cmd *cp) | 
|  | { | 
|  | Address a; | 
|  | File *f; | 
|  |  | 
|  | f = t->file; | 
|  | if(cp->addr == 0){ | 
|  | /* First put it on newline boundaries */ | 
|  | mkaddr(&a, f); | 
|  | addr = lineaddr(0, a, -1); | 
|  | a = lineaddr(0, a, 1); | 
|  | addr.r.q1 = a.r.q1; | 
|  | if(addr.r.q0==t->q0 && addr.r.q1==t->q1){ | 
|  | mkaddr(&a, f); | 
|  | addr = lineaddr(1, a, 1); | 
|  | } | 
|  | } | 
|  | textshow(t, addr.r.q0, addr.r.q1, 1); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | append(File *f, Cmd *cp, long p) | 
|  | { | 
|  | if(cp->u.text->n > 0) | 
|  | eloginsert(f, p, cp->u.text->r, cp->u.text->n); | 
|  | f->curtext->q0 = p; | 
|  | f->curtext->q1 = p; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | int | 
|  | pdisplay(File *f) | 
|  | { | 
|  | long p1, p2; | 
|  | int np; | 
|  | Rune *buf; | 
|  |  | 
|  | p1 = addr.r.q0; | 
|  | p2 = addr.r.q1; | 
|  | if(p2 > f->b.nc) | 
|  | p2 = f->b.nc; | 
|  | buf = fbufalloc(); | 
|  | while(p1 < p2){ | 
|  | np = p2-p1; | 
|  | if(np>RBUFSIZE-1) | 
|  | np = RBUFSIZE-1; | 
|  | bufread(&f->b, p1, buf, np); | 
|  | buf[np] = '\0'; | 
|  | warning(nil, "%S", buf); | 
|  | p1 += np; | 
|  | } | 
|  | fbuffree(buf); | 
|  | f->curtext->q0 = addr.r.q0; | 
|  | f->curtext->q1 = addr.r.q1; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | void | 
|  | pfilename(File *f) | 
|  | { | 
|  | int dirty; | 
|  | Window *w; | 
|  |  | 
|  | w = f->curtext->w; | 
|  | /* same check for dirty as in settag, but we know ncache==0 */ | 
|  | dirty = !w->isdir && !w->isscratch && f->mod; | 
|  | warning(nil, "%c%c%c %.*S\n", " '"[dirty], | 
|  | '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); | 
|  | } | 
|  |  | 
|  | void | 
|  | loopcmd(File *f, Cmd *cp, Range *rp, long nrp) | 
|  | { | 
|  | long i; | 
|  |  | 
|  | for(i=0; i<nrp; i++){ | 
|  | f->curtext->q0 = rp[i].q0; | 
|  | f->curtext->q1 = rp[i].q1; | 
|  | cmdexec(f->curtext, cp); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | looper(File *f, Cmd *cp, int xy) | 
|  | { | 
|  | long p, op, nrp; | 
|  | Range r, tr; | 
|  | Range *rp; | 
|  |  | 
|  | r = addr.r; | 
|  | op= xy? -1 : r.q0; | 
|  | nest++; | 
|  | if(rxcompile(cp->re->r) == FALSE) | 
|  | editerror("bad regexp in %c command", cp->cmdc); | 
|  | nrp = 0; | 
|  | rp = nil; | 
|  | for(p = r.q0; p<=r.q1; ){ | 
|  | if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */ | 
|  | if(xy || op>r.q1) | 
|  | break; | 
|  | tr.q0 = op, tr.q1 = r.q1; | 
|  | p = r.q1+1;	/* exit next loop */ | 
|  | }else{ | 
|  | if(sel.r[0].q0==sel.r[0].q1){	/* empty match? */ | 
|  | if(sel.r[0].q0==op){ | 
|  | p++; | 
|  | continue; | 
|  | } | 
|  | p = sel.r[0].q1+1; | 
|  | }else | 
|  | p = sel.r[0].q1; | 
|  | if(xy) | 
|  | tr = sel.r[0]; | 
|  | else | 
|  | tr.q0 = op, tr.q1 = sel.r[0].q0; | 
|  | } | 
|  | op = sel.r[0].q1; | 
|  | nrp++; | 
|  | rp = erealloc(rp, nrp*sizeof(Range)); | 
|  | rp[nrp-1] = tr; | 
|  | } | 
|  | loopcmd(f, cp->u.cmd, rp, nrp); | 
|  | free(rp); | 
|  | --nest; | 
|  | } | 
|  |  | 
|  | void | 
|  | linelooper(File *f, Cmd *cp) | 
|  | { | 
|  | long nrp, p; | 
|  | Range r, linesel; | 
|  | Address a, a3; | 
|  | Range *rp; | 
|  |  | 
|  | nest++; | 
|  | nrp = 0; | 
|  | rp = nil; | 
|  | r = addr.r; | 
|  | a3.f = f; | 
|  | a3.r.q0 = a3.r.q1 = r.q0; | 
|  | a = lineaddr(0, a3, 1); | 
|  | linesel = a.r; | 
|  | for(p = r.q0; p<r.q1; p = a3.r.q1){ | 
|  | a3.r.q0 = a3.r.q1; | 
|  | if(p!=r.q0 || linesel.q1==p){ | 
|  | a = lineaddr(1, a3, 1); | 
|  | linesel = a.r; | 
|  | } | 
|  | if(linesel.q0 >= r.q1) | 
|  | break; | 
|  | if(linesel.q1 >= r.q1) | 
|  | linesel.q1 = r.q1; | 
|  | if(linesel.q1 > linesel.q0) | 
|  | if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){ | 
|  | a3.r = linesel; | 
|  | nrp++; | 
|  | rp = erealloc(rp, nrp*sizeof(Range)); | 
|  | rp[nrp-1] = linesel; | 
|  | continue; | 
|  | } | 
|  | break; | 
|  | } | 
|  | loopcmd(f, cp->u.cmd, rp, nrp); | 
|  | free(rp); | 
|  | --nest; | 
|  | } | 
|  |  | 
|  | struct Looper | 
|  | { | 
|  | Cmd *cp; | 
|  | int	XY; | 
|  | Window	**w; | 
|  | int	nw; | 
|  | } loopstruct;	/* only one; X and Y can't nest */ | 
|  |  | 
|  | void | 
|  | alllooper(Window *w, void *v) | 
|  | { | 
|  | Text *t; | 
|  | struct Looper *lp; | 
|  | Cmd *cp; | 
|  |  | 
|  | lp = v; | 
|  | cp = lp->cp; | 
|  | /*	if(w->isscratch || w->isdir) */ | 
|  | /*		return; */ | 
|  | t = &w->body; | 
|  | /* only use this window if it's the current window for the file */ | 
|  | if(t->file->curtext != t) | 
|  | return; | 
|  | /*	if(w->nopen[QWevent] > 0) */ | 
|  | /*		return; */ | 
|  | /* no auto-execute on files without names */ | 
|  | if(cp->re==nil && t->file->nname==0) | 
|  | return; | 
|  | if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){ | 
|  | lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*)); | 
|  | lp->w[lp->nw++] = w; | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | alllocker(Window *w, void *v) | 
|  | { | 
|  | if(v) | 
|  | incref(&w->ref); | 
|  | else | 
|  | winclose(w); | 
|  | } | 
|  |  | 
|  | void | 
|  | filelooper(Cmd *cp, int XY) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | if(Glooping++) | 
|  | editerror("can't nest %c command", "YX"[XY]); | 
|  | nest++; | 
|  |  | 
|  | loopstruct.cp = cp; | 
|  | loopstruct.XY = XY; | 
|  | if(loopstruct.w)	/* error'ed out last time */ | 
|  | free(loopstruct.w); | 
|  | loopstruct.w = nil; | 
|  | loopstruct.nw = 0; | 
|  | allwindows(alllooper, &loopstruct); | 
|  | /* | 
|  | * add a ref to all windows to keep safe windows accessed by X | 
|  | * that would not otherwise have a ref to hold them up during | 
|  | * the shenanigans.  note this with globalincref so that any | 
|  | * newly created windows start with an extra reference. | 
|  | */ | 
|  | allwindows(alllocker, (void*)1); | 
|  | globalincref = 1; | 
|  | for(i=0; i<loopstruct.nw; i++) | 
|  | cmdexec(&loopstruct.w[i]->body, cp->u.cmd); | 
|  | allwindows(alllocker, (void*)0); | 
|  | globalincref = 0; | 
|  | free(loopstruct.w); | 
|  | loopstruct.w = nil; | 
|  |  | 
|  | --Glooping; | 
|  | --nest; | 
|  | } | 
|  |  | 
|  | void | 
|  | nextmatch(File *f, String *r, long p, int sign) | 
|  | { | 
|  | if(rxcompile(r->r) == FALSE) | 
|  | editerror("bad regexp in command address"); | 
|  | if(sign >= 0){ | 
|  | if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) | 
|  | editerror("no match for regexp"); | 
|  | if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){ | 
|  | if(++p>f->b.nc) | 
|  | p = 0; | 
|  | if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel)) | 
|  | editerror("address"); | 
|  | } | 
|  | }else{ | 
|  | if(!rxbexecute(f->curtext, p, &sel)) | 
|  | editerror("no match for regexp"); | 
|  | if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){ | 
|  | if(--p<0) | 
|  | p = f->b.nc; | 
|  | if(!rxbexecute(f->curtext, p, &sel)) | 
|  | editerror("address"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | File	*matchfile(String*); | 
|  | Address	charaddr(long, Address, int); | 
|  | Address	lineaddr(long, Address, int); | 
|  |  | 
|  | Address | 
|  | cmdaddress(Addr *ap, Address a, int sign) | 
|  | { | 
|  | File *f = a.f; | 
|  | Address a1, a2; | 
|  |  | 
|  | do{ | 
|  | switch(ap->type){ | 
|  | case 'l': | 
|  | case '#': | 
|  | a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign); | 
|  | break; | 
|  |  | 
|  | case '.': | 
|  | mkaddr(&a, f); | 
|  | break; | 
|  |  | 
|  | case '$': | 
|  | a.r.q0 = a.r.q1 = f->b.nc; | 
|  | break; | 
|  |  | 
|  | case '\'': | 
|  | editerror("can't handle '"); | 
|  | /*			a.r = f->mark; */ | 
|  | break; | 
|  |  | 
|  | case '?': | 
|  | sign = -sign; | 
|  | if(sign == 0) | 
|  | sign = -1; | 
|  | /* fall through */ | 
|  | case '/': | 
|  | nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign); | 
|  | a.r = sel.r[0]; | 
|  | break; | 
|  |  | 
|  | case '"': | 
|  | f = matchfile(ap->u.re); | 
|  | mkaddr(&a, f); | 
|  | break; | 
|  |  | 
|  | case '*': | 
|  | a.r.q0 = 0, a.r.q1 = f->b.nc; | 
|  | return a; | 
|  |  | 
|  | case ',': | 
|  | case ';': | 
|  | if(ap->u.left) | 
|  | a1 = cmdaddress(ap->u.left, a, 0); | 
|  | else | 
|  | a1.f = a.f, a1.r.q0 = a1.r.q1 = 0; | 
|  | if(ap->type == ';'){ | 
|  | f = a1.f; | 
|  | a = a1; | 
|  | f->curtext->q0 = a1.r.q0; | 
|  | f->curtext->q1 = a1.r.q1; | 
|  | } | 
|  | if(ap->next) | 
|  | a2 = cmdaddress(ap->next, a, 0); | 
|  | else | 
|  | a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc; | 
|  | if(a1.f != a2.f) | 
|  | editerror("addresses in different files"); | 
|  | a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1; | 
|  | if(a.r.q1 < a.r.q0) | 
|  | editerror("addresses out of order"); | 
|  | return a; | 
|  |  | 
|  | case '+': | 
|  | case '-': | 
|  | sign = 1; | 
|  | if(ap->type == '-') | 
|  | sign = -1; | 
|  | if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-') | 
|  | a = lineaddr(1L, a, sign); | 
|  | break; | 
|  | default: | 
|  | error("cmdaddress"); | 
|  | return a; | 
|  | } | 
|  | }while(ap = ap->next);	/* assign = */ | 
|  | return a; | 
|  | } | 
|  |  | 
|  | struct Tofile{ | 
|  | File		*f; | 
|  | String	*r; | 
|  | }; | 
|  |  | 
|  | void | 
|  | alltofile(Window *w, void *v) | 
|  | { | 
|  | Text *t; | 
|  | struct Tofile *tp; | 
|  |  | 
|  | tp = v; | 
|  | if(tp->f != nil) | 
|  | return; | 
|  | if(w->isscratch || w->isdir) | 
|  | return; | 
|  | t = &w->body; | 
|  | /* only use this window if it's the current window for the file */ | 
|  | if(t->file->curtext != t) | 
|  | return; | 
|  | /*	if(w->nopen[QWevent] > 0) */ | 
|  | /*		return; */ | 
|  | if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname)) | 
|  | tp->f = t->file; | 
|  | } | 
|  |  | 
|  | File* | 
|  | tofile(String *r) | 
|  | { | 
|  | struct Tofile t; | 
|  | String rr; | 
|  |  | 
|  | rr.r = skipbl(r->r, r->n, &rr.n); | 
|  | t.f = nil; | 
|  | t.r = &rr; | 
|  | allwindows(alltofile, &t); | 
|  | if(t.f == nil) | 
|  | editerror("no such file\"%S\"", rr.r); | 
|  | return t.f; | 
|  | } | 
|  |  | 
|  | void | 
|  | allmatchfile(Window *w, void *v) | 
|  | { | 
|  | struct Tofile *tp; | 
|  | Text *t; | 
|  |  | 
|  | tp = v; | 
|  | if(w->isscratch || w->isdir) | 
|  | return; | 
|  | t = &w->body; | 
|  | /* only use this window if it's the current window for the file */ | 
|  | if(t->file->curtext != t) | 
|  | return; | 
|  | /*	if(w->nopen[QWevent] > 0) */ | 
|  | /*		return; */ | 
|  | if(filematch(w->body.file, tp->r)){ | 
|  | if(tp->f != nil) | 
|  | editerror("too many files match \"%S\"", tp->r->r); | 
|  | tp->f = w->body.file; | 
|  | } | 
|  | } | 
|  |  | 
|  | File* | 
|  | matchfile(String *r) | 
|  | { | 
|  | struct Tofile tf; | 
|  |  | 
|  | tf.f = nil; | 
|  | tf.r = r; | 
|  | allwindows(allmatchfile, &tf); | 
|  |  | 
|  | if(tf.f == nil) | 
|  | editerror("no file matches \"%S\"", r->r); | 
|  | return tf.f; | 
|  | } | 
|  |  | 
|  | int | 
|  | filematch(File *f, String *r) | 
|  | { | 
|  | char *buf; | 
|  | Rune *rbuf; | 
|  | Window *w; | 
|  | int match, i, dirty; | 
|  | Rangeset s; | 
|  |  | 
|  | /* compile expr first so if we get an error, we haven't allocated anything */ | 
|  | if(rxcompile(r->r) == FALSE) | 
|  | editerror("bad regexp in file match"); | 
|  | buf = fbufalloc(); | 
|  | w = f->curtext->w; | 
|  | /* same check for dirty as in settag, but we know ncache==0 */ | 
|  | dirty = !w->isdir && !w->isscratch && f->mod; | 
|  | snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty], | 
|  | '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name); | 
|  | rbuf = bytetorune(buf, &i); | 
|  | fbuffree(buf); | 
|  | match = rxexecute(nil, rbuf, 0, i, &s); | 
|  | free(rbuf); | 
|  | return match; | 
|  | } | 
|  |  | 
|  | Address | 
|  | charaddr(long l, Address addr, int sign) | 
|  | { | 
|  | if(sign == 0) | 
|  | addr.r.q0 = addr.r.q1 = l; | 
|  | else if(sign < 0) | 
|  | addr.r.q1 = addr.r.q0 -= l; | 
|  | else if(sign > 0) | 
|  | addr.r.q0 = addr.r.q1 += l; | 
|  | if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc) | 
|  | editerror("address out of range"); | 
|  | return addr; | 
|  | } | 
|  |  | 
|  | Address | 
|  | lineaddr(long l, Address addr, int sign) | 
|  | { | 
|  | int n; | 
|  | int c; | 
|  | File *f = addr.f; | 
|  | Address a; | 
|  | long p; | 
|  |  | 
|  | a.f = f; | 
|  | if(sign >= 0){ | 
|  | if(l == 0){ | 
|  | if(sign==0 || addr.r.q1==0){ | 
|  | a.r.q0 = a.r.q1 = 0; | 
|  | return a; | 
|  | } | 
|  | a.r.q0 = addr.r.q1; | 
|  | p = addr.r.q1-1; | 
|  | }else{ | 
|  | if(sign==0 || addr.r.q1==0){ | 
|  | p = 0; | 
|  | n = 1; | 
|  | }else{ | 
|  | p = addr.r.q1-1; | 
|  | n = textreadc(f->curtext, p++)=='\n'; | 
|  | } | 
|  | while(n < l){ | 
|  | if(p >= f->b.nc) | 
|  | editerror("address out of range"); | 
|  | if(textreadc(f->curtext, p++) == '\n') | 
|  | n++; | 
|  | } | 
|  | a.r.q0 = p; | 
|  | } | 
|  | while(p < f->b.nc && textreadc(f->curtext, p++)!='\n') | 
|  | ; | 
|  | a.r.q1 = p; | 
|  | }else{ | 
|  | p = addr.r.q0; | 
|  | if(l == 0) | 
|  | a.r.q1 = addr.r.q0; | 
|  | else{ | 
|  | for(n = 0; n<l; ){	/* always runs once */ | 
|  | if(p == 0){ | 
|  | if(++n != l) | 
|  | editerror("address out of range"); | 
|  | }else{ | 
|  | c = textreadc(f->curtext, p-1); | 
|  | if(c != '\n' || ++n != l) | 
|  | p--; | 
|  | } | 
|  | } | 
|  | a.r.q1 = p; | 
|  | if(p > 0) | 
|  | p--; | 
|  | } | 
|  | while(p > 0 && textreadc(f->curtext, p-1)!='\n')	/* lines start after a newline */ | 
|  | p--; | 
|  | a.r.q0 = p; | 
|  | } | 
|  | return a; | 
|  | } | 
|  |  | 
|  | struct Filecheck | 
|  | { | 
|  | File	*f; | 
|  | Rune	*r; | 
|  | int nr; | 
|  | }; | 
|  |  | 
|  | void | 
|  | allfilecheck(Window *w, void *v) | 
|  | { | 
|  | struct Filecheck *fp; | 
|  | File *f; | 
|  |  | 
|  | fp = v; | 
|  | f = w->body.file; | 
|  | if(w->body.file == fp->f) | 
|  | return; | 
|  | if(runeeq(fp->r, fp->nr, f->name, f->nname)) | 
|  | warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r); | 
|  | } | 
|  |  | 
|  | Rune* | 
|  | cmdname(File *f, String *str, int set) | 
|  | { | 
|  | Rune *r, *s; | 
|  | int n; | 
|  | struct Filecheck fc; | 
|  | Runestr newname; | 
|  |  | 
|  | r = nil; | 
|  | n = str->n; | 
|  | s = str->r; | 
|  | if(n == 0){ | 
|  | /* no name; use existing */ | 
|  | if(f->nname == 0) | 
|  | return nil; | 
|  | r = runemalloc(f->nname+1); | 
|  | runemove(r, f->name, f->nname); | 
|  | return r; | 
|  | } | 
|  | s = skipbl(s, n, &n); | 
|  | if(n == 0) | 
|  | goto Return; | 
|  |  | 
|  | if(s[0] == '/'){ | 
|  | r = runemalloc(n+1); | 
|  | runemove(r, s, n); | 
|  | }else{ | 
|  | newname = dirname(f->curtext, runestrdup(s), n); | 
|  | n = newname.nr; | 
|  | r = runemalloc(n+1);	/* NUL terminate */ | 
|  | runemove(r, newname.r, n); | 
|  | free(newname.r); | 
|  | } | 
|  | fc.f = f; | 
|  | fc.r = r; | 
|  | fc.nr = n; | 
|  | allwindows(allfilecheck, &fc); | 
|  | if(f->nname == 0) | 
|  | set = TRUE; | 
|  |  | 
|  | Return: | 
|  | if(set && !runeeq(r, n, f->name, f->nname)){ | 
|  | filemark(f); | 
|  | f->mod = TRUE; | 
|  | f->curtext->w->dirty = TRUE; | 
|  | winsetname(f->curtext->w, r, n); | 
|  | } | 
|  | return r; | 
|  | } |