18 char Enoname[] = "no file name given";
27 int append(File*, Cmd*, long);
29 void pfilename(File*);
30 void looper(File*, Cmd*, int);
31 void filelooper(Cmd*, int);
32 void linelooper(File*, Cmd*);
33 Address lineaddr(long, Address, int);
34 int filematch(File*, String*);
35 File *tofile(String*);
36 Rune* cmdname(File *f, String *s, int);
37 void runpipe(Text*, int, Rune*, int, int);
55 mkaddr(Address *a, File *f)
57 a->r.q0 = f->curtext->q0;
58 a->r.q1 = f->curtext->q1;
63 cmdexec(Text *t, Cmd *cp)
75 if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
76 !utfrune("bBnqUXY!", cp->cmdc) &&
77 !(cp->cmdc=='D' && cp->u.text))
78 editerror("no current window");
79 i = cmdlookup(cp->cmdc); /* will be -1 for '{' */
86 if(i>=0 && cmdtab[i].defaddr != aNo){
87 if((ap=cp->addr)==0 && cp->cmdc!='\n'){
88 cp->addr = ap = newaddr();
90 if(cmdtab[i].defaddr == aAll)
92 }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
95 if(cmdtab[i].defaddr == aAll)
98 if(cp->addr){ /* may be false for '\n' (only) */
99 static Address none = {0,0,nil};
102 addr = cmdaddress(ap, dot, 0);
104 addr = cmdaddress(ap, none, 0);
113 dot = cmdaddress(cp->addr, dot, 0);
114 for(cp = cp->u.cmd; cp; cp = cp->next){
115 if(dot.r.q1 > t->file->b.nc)
116 editerror("dot extends past end of buffer during { command");
124 editerror("unknown command %c in cmdexec", cp->cmdc);
125 i = (*cmdtab[i].fn)(t, cp);
132 edittext(Window *w, int q, Rune *r, int nr)
139 return "permission denied";
141 eloginsert(f, q, r, nr);
144 collection = runerealloc(collection, ncollection+nr+1);
145 runemove(collection+ncollection, r, nr);
147 collection[ncollection] = '\0';
150 return "unknown state in edittext";
154 /* string is known to be NUL-terminated */
156 filelist(Text *t, Rune *r, int nr)
160 r = skipbl(r, nr, &nr);
162 return runestrdup(r);
163 /* use < command to collect text */
165 runpipe(t, '<', r+1, nr-1, Collecting);
170 a_cmd(Text *t, Cmd *cp)
172 return append(t->file, cp, addr.r.q1);
176 b_cmd(Text *t, Cmd *cp)
181 f = tofile(cp->u.text);
184 curtext = f->curtext;
189 B_cmd(Text *t, Cmd *cp)
194 list = filelist(t, cp->u.text->r, cp->u.text->n);
199 r = skipbl(r, nr, &nr);
201 new(t, t, nil, 0, 0, r, 0);
203 s = findbl(r, nr, &nr);
205 new(t, t, nil, 0, 0, r, runestrlen(r));
207 r = skipbl(s+1, nr-1, &nr);
214 c_cmd(Text *t, Cmd *cp)
216 elogreplace(t->file, addr.r.q0, addr.r.q1, cp->u.text->r, cp->u.text->n);
223 d_cmd(Text *t, Cmd *cp)
226 if(addr.r.q1 > addr.r.q0)
227 elogdelete(t->file, addr.r.q0, addr.r.q1);
236 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
237 colclose(t->col, t->w, TRUE);
241 D_cmd(Text *t, Cmd *cp)
243 Rune *list, *r, *s, *n;
249 list = filelist(t, cp->u.text->r, cp->u.text->n);
254 dir = dirname(t, nil, 0);
257 r = skipbl(r, nr, &nr);
259 s = findbl(r, nr, &nr);
261 /* first time through, could be empty string, meaning delete file empty name */
263 if(r[0]=='/' || nn==0 || dir.nr==0){
264 rs.r = runestrdup(r);
267 n = runemalloc(dir.nr+1+nn);
268 runemove(n, dir.r, dir.nr);
270 runemove(n+dir.nr+1, r, nn);
271 rs = cleanrname(runestr(n, dir.nr+1+nn));
273 w = lookfile(rs.r, rs.nr);
275 snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
282 r = skipbl(s+1, nr-1, &nr);
290 readloader(void *v, uint q0, Rune *r, int nr)
293 eloginsert(v, q0, r, nr);
298 e_cmd(Text *t, Cmd *cp)
302 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
310 if(winclean(t->w, TRUE)==FALSE)
311 editerror(""); /* winclean generated message already */
315 allreplaced = (q0==0 && q1==f->b.nc);
316 name = cmdname(f, cp->u.text, cp->cmdc=='e');
319 i = runestrlen(name);
320 samename = runeeq(name, i, t->file->name, t->file->nname);
321 s = runetobyte(name, i);
325 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
330 isdir = (d!=nil && (d->qid.type&QTDIR));
334 snprint(tmp, sizeof tmp, "%s is a directory", s);
338 elogdelete(f, q0, q1);
340 loadfile(fd, q1, &nulls, readloader, f, nil);
344 warning(nil, "%s: NUL bytes elided\n", s);
345 else if(allreplaced && samename)
350 static Rune Lempty[] = { 0 };
352 f_cmd(Text *t, Cmd *cp)
358 if(cp->u.text == nil){
364 name = cmdname(t->file, str, TRUE);
371 g_cmd(Text *t, Cmd *cp)
373 if(t->file != addr.f){
374 warning(nil, "internal error: g_cmd f!=addr.f\n");
377 if(rxcompile(cp->re->r) == FALSE)
378 editerror("bad regexp in g command");
379 if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
382 return cmdexec(t, cp->u.cmd);
388 i_cmd(Text *t, Cmd *cp)
390 return append(t->file, cp, addr.r.q0);
394 copy(File *f, Address addr2)
401 for(p=addr.r.q0; p<addr.r.q1; p+=ni){
405 bufread(&f->b, p, buf, ni);
406 eloginsert(addr2.f, addr2.r.q1, buf, ni);
412 move(File *f, Address addr2)
414 if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
415 elogdelete(f, addr.r.q0, addr.r.q1);
417 }else if(addr.r.q0 >= addr2.r.q1){
419 elogdelete(f, addr.r.q0, addr.r.q1);
420 }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
421 ; /* move to self; no-op */
423 editerror("move overlaps itself");
427 m_cmd(Text *t, Cmd *cp)
431 mkaddr(&dot, t->file);
432 addr2 = cmdaddress(cp->u.mtaddr, dot, 0);
434 move(t->file, addr2);
436 copy(t->file, addr2);
441 p_cmd(Text *t, Cmd *cp)
444 return pdisplay(t->file);
448 s_cmd(Text *t, Cmd *cp)
450 int i, j, k, c, m, n, nrp, didsub;
459 if(rxcompile(cp->re->r) == FALSE)
460 editerror("bad regexp in s command");
465 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
466 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
467 if(sel.r[0].q0 == op){
478 rp = erealloc(rp, nrp*sizeof(Rangeset));
482 buf = allocstring(0);
483 for(m=0; m<nrp; m++){
487 for(i = 0; i<cp->u.text->n; i++)
488 if((c = cp->u.text->r[i])=='\\' && i<cp->u.text->n-1){
489 c = cp->u.text->r[++i];
490 if('1'<=c && c<='9') {
492 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
493 err = "replacement string too long";
496 bufread(&t->file->b, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
497 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
498 Straddc(buf, rbuf[k]);
504 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
505 err = "right hand side too long in substitution";
508 bufread(&t->file->b, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
509 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
510 Straddc(buf, rbuf[k]);
512 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
513 delta -= sel.r[0].q1-sel.r[0].q0;
522 if(!didsub && nest==0)
523 editerror("no substitution");
537 u_cmd(Text *t, Cmd *cp)
548 while(n-->0 && t->file->seq!=oseq){
550 undo(t, nil, nil, flag, 0, nil, 0);
556 w_cmd(Text *t, Cmd *cp)
563 editerror("can't write file with pending modifications");
564 r = cmdname(f, cp->u.text, FALSE);
566 editerror("no name specified for 'w' command");
567 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
568 /* r is freed by putfile */
573 x_cmd(Text *t, Cmd *cp)
576 looper(t->file, cp, cp->cmdc=='x');
578 linelooper(t->file, cp);
583 X_cmd(Text *t, Cmd *cp)
587 filelooper(cp, cp->cmdc=='X');
592 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
600 r = skipbl(cr, ncr, &n);
602 editerror("no command specified for %c", cmd);
604 if(state == Inserting){
608 if(cmd == '<' || cmd=='|')
609 elogdelete(t->file, t->q0, t->q1);
618 dir = dirname(t, nil, 0);
619 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
625 if(t!=nil && t->w!=nil)
626 incref(&t->w->ref); /* run will decref */
627 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
629 if(t!=nil && t->w!=nil)
634 * The editoutlk exists only so that we can tell when
635 * the editout file has been closed. It can get closed *after*
636 * the process exits because, since the process cannot be
637 * connected directly to editout (no 9P kernel support),
638 * the process is actually connected to a pipe to another
639 * process (arranged via 9pserve) that reads from the pipe
640 * and then writes the data in the pipe to editout using
641 * 9P transactions. This process might still have a couple
642 * writes left to copy after the original process has exited.
648 qlock(q); /* wait for file to close */
652 if(t!=nil && t->w!=nil)
657 pipe_cmd(Text *t, Cmd *cp)
659 runpipe(t, cp->cmdc, cp->u.text->r, cp->u.text->n, Inserting);
664 nlcount(Text *t, long q0, long q1, long *pnr)
679 bufread(&t->file->b, q0, buf, nbuf);
682 if(buf[i++] == '\n') {
701 printposn(Text *t, int mode)
705 if (t != nil && t->file != nil && t->file->name != nil)
706 warning(nil, "%.*S:", t->file->nname, t->file->name);
710 warning(nil, "#%d", addr.r.q0);
711 if(addr.r.q1 != addr.r.q0)
712 warning(nil, ",#%d", addr.r.q1);
718 l1 = 1+nlcount(t, 0, addr.r.q0, nil);
719 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
720 /* check if addr ends with '\n' */
721 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
723 warning(nil, "%lud", l1);
725 warning(nil, ",%lud", l2);
730 l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
731 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
734 warning(nil, "%lud+#%d", l1, r1);
736 warning(nil, ",%lud+#%d", l2, r2);
743 eq_cmd(Text *t, Cmd *cp)
747 switch(cp->u.text->n){
752 if(cp->u.text->r[0] == '#'){
756 if(cp->u.text->r[0] == '+'){
757 mode = PosnLineChars;
762 editerror("newline expected");
769 nl_cmd(Text *t, Cmd *cp)
776 /* First put it on newline boundaries */
778 addr = lineaddr(0, a, -1);
779 a = lineaddr(0, a, 1);
781 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
783 addr = lineaddr(1, a, 1);
786 textshow(t, addr.r.q0, addr.r.q1, 1);
791 append(File *f, Cmd *cp, long p)
793 if(cp->u.text->n > 0)
794 eloginsert(f, p, cp->u.text->r, cp->u.text->n);
816 bufread(&f->b, p1, buf, np);
818 warning(nil, "%S", buf);
822 f->curtext->q0 = addr.r.q0;
823 f->curtext->q1 = addr.r.q1;
834 /* same check for dirty as in settag, but we know ncache==0 */
835 dirty = !w->isdir && !w->isscratch && f->mod;
836 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
837 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
841 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
845 for(i=0; i<nrp; i++){
846 f->curtext->q0 = rp[i].q0;
847 f->curtext->q1 = rp[i].q1;
848 cmdexec(f->curtext, cp);
853 looper(File *f, Cmd *cp, int xy)
862 if(rxcompile(cp->re->r) == FALSE)
863 editerror("bad regexp in %c command", cp->cmdc);
866 for(p = r.q0; p<=r.q1; ){
867 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
870 tr.q0 = op, tr.q1 = r.q1;
871 p = r.q1+1; /* exit next loop */
873 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
884 tr.q0 = op, tr.q1 = sel.r[0].q0;
888 rp = erealloc(rp, nrp*sizeof(Range));
891 loopcmd(f, cp->u.cmd, rp, nrp);
897 linelooper(File *f, Cmd *cp)
909 a3.r.q0 = a3.r.q1 = r.q0;
910 a = lineaddr(0, a3, 1);
912 for(p = r.q0; p<r.q1; p = a3.r.q1){
914 if(p!=r.q0 || linesel.q1==p){
915 a = lineaddr(1, a3, 1);
918 if(linesel.q0 >= r.q1)
920 if(linesel.q1 >= r.q1)
922 if(linesel.q1 > linesel.q0)
923 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
926 rp = erealloc(rp, nrp*sizeof(Range));
932 loopcmd(f, cp->u.cmd, rp, nrp);
943 } loopstruct; /* only one; X and Y can't nest */
946 alllooper(Window *w, void *v)
954 /* if(w->isscratch || w->isdir) */
957 /* only use this window if it's the current window for the file */
958 if(t->file->curtext != t)
960 /* if(w->nopen[QWevent] > 0) */
962 /* no auto-execute on files without names */
963 if(cp->re==nil && t->file->nname==0)
965 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
966 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
972 alllocker(Window *w, void *v)
981 filelooper(Cmd *cp, int XY)
986 editerror("can't nest %c command", "YX"[XY]);
991 if(loopstruct.w) /* error'ed out last time */
995 allwindows(alllooper, &loopstruct);
997 * add a ref to all windows to keep safe windows accessed by X
998 * that would not otherwise have a ref to hold them up during
999 * the shenanigans. note this with globalincref so that any
1000 * newly created windows start with an extra reference.
1002 allwindows(alllocker, (void*)1);
1004 for(i=0; i<loopstruct.nw; i++)
1005 cmdexec(&loopstruct.w[i]->body, cp->u.cmd);
1006 allwindows(alllocker, (void*)0);
1016 nextmatch(File *f, String *r, long p, int sign)
1018 if(rxcompile(r->r) == FALSE)
1019 editerror("bad regexp in command address");
1021 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1022 editerror("no match for regexp");
1023 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1026 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1027 editerror("address");
1030 if(!rxbexecute(f->curtext, p, &sel))
1031 editerror("no match for regexp");
1032 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1035 if(!rxbexecute(f->curtext, p, &sel))
1036 editerror("address");
1041 File *matchfile(String*);
1042 Address charaddr(long, Address, int);
1043 Address lineaddr(long, Address, int);
1046 cmdaddress(Addr *ap, Address a, int sign)
1055 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1063 a.r.q0 = a.r.q1 = f->b.nc;
1067 editerror("can't handle '");
1068 /* a.r = f->mark; */
1077 nextmatch(f, ap->u.re, sign>=0? a.r.q1 : a.r.q0, sign);
1082 f = matchfile(ap->u.re);
1087 a.r.q0 = 0, a.r.q1 = f->b.nc;
1093 a1 = cmdaddress(ap->u.left, a, 0);
1095 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1096 if(ap->type == ';'){
1099 f->curtext->q0 = a1.r.q0;
1100 f->curtext->q1 = a1.r.q1;
1103 a2 = cmdaddress(ap->next, a, 0);
1105 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->b.nc;
1107 editerror("addresses in different files");
1108 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1110 editerror("addresses out of order");
1118 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1119 a = lineaddr(1L, a, sign);
1122 error("cmdaddress");
1125 }while(ap = ap->next); /* assign = */
1135 alltofile(Window *w, void *v)
1143 if(w->isscratch || w->isdir)
1146 /* only use this window if it's the current window for the file */
1147 if(t->file->curtext != t)
1149 /* if(w->nopen[QWevent] > 0) */
1151 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1161 rr.r = skipbl(r->r, r->n, &rr.n);
1164 allwindows(alltofile, &t);
1166 editerror("no such file\"%S\"", rr.r);
1171 allmatchfile(Window *w, void *v)
1177 if(w->isscratch || w->isdir)
1180 /* only use this window if it's the current window for the file */
1181 if(t->file->curtext != t)
1183 /* if(w->nopen[QWevent] > 0) */
1185 if(filematch(w->body.file, tp->r)){
1187 editerror("too many files match \"%S\"", tp->r->r);
1188 tp->f = w->body.file;
1193 matchfile(String *r)
1199 allwindows(allmatchfile, &tf);
1202 editerror("no file matches \"%S\"", r->r);
1207 filematch(File *f, String *r)
1212 int match, i, dirty;
1215 /* compile expr first so if we get an error, we haven't allocated anything */
1216 if(rxcompile(r->r) == FALSE)
1217 editerror("bad regexp in file match");
1220 /* same check for dirty as in settag, but we know ncache==0 */
1221 dirty = !w->isdir && !w->isscratch && f->mod;
1222 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1223 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1224 rbuf = bytetorune(buf, &i);
1226 match = rxexecute(nil, rbuf, 0, i, &s);
1232 charaddr(long l, Address addr, int sign)
1235 addr.r.q0 = addr.r.q1 = l;
1237 addr.r.q1 = addr.r.q0 -= l;
1239 addr.r.q0 = addr.r.q1 += l;
1240 if(addr.r.q0<0 || addr.r.q1>addr.f->b.nc)
1241 editerror("address out of range");
1246 lineaddr(long l, Address addr, int sign)
1257 if(sign==0 || addr.r.q1==0){
1258 a.r.q0 = a.r.q1 = 0;
1264 if(sign==0 || addr.r.q1==0){
1269 n = textreadc(f->curtext, p++)=='\n';
1273 editerror("address out of range");
1274 if(textreadc(f->curtext, p++) == '\n')
1279 while(p < f->b.nc && textreadc(f->curtext, p++)!='\n')
1287 for(n = 0; n<l; ){ /* always runs once */
1290 editerror("address out of range");
1292 c = textreadc(f->curtext, p-1);
1293 if(c != '\n' || ++n != l)
1301 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1316 allfilecheck(Window *w, void *v)
1318 struct Filecheck *fp;
1323 if(w->body.file == fp->f)
1325 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1326 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1330 cmdname(File *f, String *str, int set)
1334 struct Filecheck fc;
1341 /* no name; use existing */
1344 r = runemalloc(f->nname+1);
1345 runemove(r, f->name, f->nname);
1348 s = skipbl(s, n, &n);
1353 r = runemalloc(n+1);
1356 newname = dirname(f->curtext, runestrdup(s), n);
1358 r = runemalloc(n+1); /* NUL terminate */
1359 runemove(r, newname.r, n);
1365 allwindows(allfilecheck, &fc);
1370 if(set && !runeeq(r, n, f->name, f->nname)){
1373 f->curtext->w->dirty = TRUE;
1374 winsetname(f->curtext->w, r, n);