17 char Enoname[] = "no file name given";
26 int append(File*, Cmd*, long);
28 void pfilename(File*);
29 void looper(File*, Cmd*, int);
30 void filelooper(Text*, Cmd*, int);
31 void linelooper(File*, Cmd*);
32 Address lineaddr(long, Address, int);
33 int filematch(File*, String*);
34 File *tofile(String*);
35 Rune* cmdname(File *f, String *s, int);
36 void runpipe(Text*, int, Rune*, int, int);
54 mkaddr(Address *a, File *f)
56 a->r.q0 = f->curtext->q0;
57 a->r.q1 = f->curtext->q1;
62 cmdexec(Text *t, Cmd *cp)
74 if(w==nil && (cp->addr==0 || cp->addr->type!='"') &&
75 !utfrune("bBnqUXY!", cp->cmdc) &&
76 !(cp->cmdc=='D' && cp->text))
77 editerror("no current window");
78 i = cmdlookup(cp->cmdc); /* will be -1 for '{' */
85 if(i>=0 && cmdtab[i].defaddr != aNo){
86 if((ap=cp->addr)==0 && cp->cmdc!='\n'){
87 cp->addr = ap = newaddr();
89 if(cmdtab[i].defaddr == aAll)
91 }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
94 if(cmdtab[i].defaddr == aAll)
97 if(cp->addr){ /* may be false for '\n' (only) */
98 static Address none = {0,0,nil};
101 addr = cmdaddress(ap, dot, 0);
103 addr = cmdaddress(ap, none, 0);
112 dot = cmdaddress(cp->addr, dot, 0);
113 for(cp = cp->cmd; cp; cp = cp->next){
114 if(dot.r.q1 > t->file->nc)
115 editerror("dot extends past end of buffer during { command");
123 editerror("unknown command %c in cmdexec", cp->cmdc);
124 i = (*cmdtab[i].fn)(t, cp);
131 edittext(Window *w, int q, Rune *r, int nr)
137 return "permission denied";
140 eloginsert(f, q, r, nr);
143 collection = runerealloc(collection, ncollection+nr+1);
144 runemove(collection+ncollection, r, nr);
146 collection[ncollection] = '\0';
149 return "unknown state in edittext";
153 /* string is known to be NUL-terminated */
155 filelist(Text *t, Rune *r, int nr)
159 r = skipbl(r, nr, &nr);
162 if((collection = runestrdup(r)) != nil)
163 ncollection += runestrlen(r);
165 /* use < command to collect text */
166 runpipe(t, '<', r+1, nr-1, Collecting);
171 a_cmd(Text *t, Cmd *cp)
173 return append(t->file, cp, addr.r.q1);
177 b_cmd(Text*, Cmd *cp)
181 f = tofile(cp->text);
184 curtext = f->curtext;
189 B_cmd(Text *t, Cmd *cp)
194 list = filelist(t, cp->text->r, cp->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->text->r, cp->text->n);
225 if(addr.r.q1 > addr.r.q0)
226 elogdelete(t->file, addr.r.q0, addr.r.q1);
235 if(t->w->body.file->ntext>1 || winclean(t->w, FALSE))
236 colclose(t->col, t->w, TRUE);
240 D_cmd(Text *t, Cmd *cp)
242 Rune *list, *r, *s, *n;
248 list = filelist(t, cp->text->r, cp->text->n);
253 dir = dirname(t, nil, 0);
256 r = skipbl(r, nr, &nr);
258 s = findbl(r, nr, &nr);
260 /* first time through, could be empty string, meaning delete file empty name */
262 if(r[0]=='/' || nn==0 || dir.nr==0){
263 rs.r = runestrdup(r);
266 n = runemalloc(dir.nr+1+nn);
267 runemove(n, dir.r, dir.nr);
269 runemove(n+dir.nr+1, r, nn);
270 rs = cleanrname((Runestr){n, dir.nr+1+nn});
272 w = lookfile(rs.r, rs.nr);
274 snprint(buf, sizeof buf, "no such file %.*S", rs.nr, rs.r);
281 r = skipbl(s+1, nr-1, &nr);
289 readloader(void *v, uint q0, Rune *r, int nr)
292 eloginsert(v, q0, r, nr);
297 e_cmd(Text *t, Cmd *cp)
301 int i, isdir, q0, q1, fd, nulls, samename, allreplaced;
309 if(winclean(t->w, TRUE)==FALSE)
310 editerror(""); /* winclean generated message already */
314 allreplaced = (q0==0 && q1==f->nc);
315 name = cmdname(f, cp->text, cp->cmdc=='e');
318 i = runestrlen(name);
319 samename = runeeq(name, i, t->file->name, t->file->nname);
320 s = runetobyte(name, i);
324 snprint(tmp, sizeof tmp, "can't open %s: %r", s);
329 isdir = (d!=nil && (d->qid.type&QTDIR));
333 snprint(tmp, sizeof tmp, "%s is a directory", s);
337 elogdelete(f, q0, q1);
339 loadfile(fd, q1, &nulls, readloader, f);
343 warning(nil, "%s: NUL bytes elided\n", s);
344 else if(allreplaced && samename)
350 f_cmd(Text *t, Cmd *cp)
362 name = cmdname(t->file, str, TRUE);
369 g_cmd(Text *t, Cmd *cp)
371 if(t->file != addr.f){
372 warning(nil, "internal error: g_cmd f!=addr.f\n");
375 if(rxcompile(cp->re->r) == FALSE)
376 editerror("bad regexp in g command");
377 if(rxexecute(t, nil, addr.r.q0, addr.r.q1, &sel) ^ cp->cmdc=='v'){
380 return cmdexec(t, cp->cmd);
386 i_cmd(Text *t, Cmd *cp)
388 return append(t->file, cp, addr.r.q0);
392 copy(File *f, Address addr2)
399 for(p=addr.r.q0; p<addr.r.q1; p+=ni){
403 bufread(f, p, buf, ni);
404 eloginsert(addr2.f, addr2.r.q1, buf, ni);
410 move(File *f, Address addr2)
412 if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
413 elogdelete(f, addr.r.q0, addr.r.q1);
415 }else if(addr.r.q0 >= addr2.r.q1){
417 elogdelete(f, addr.r.q0, addr.r.q1);
418 }else if(addr.r.q0==addr2.r.q0 && addr.r.q1==addr2.r.q1){
419 ; /* move to self; no-op */
421 editerror("move overlaps itself");
425 m_cmd(Text *t, Cmd *cp)
429 mkaddr(&dot, t->file);
430 addr2 = cmdaddress(cp->mtaddr, dot, 0);
432 move(t->file, addr2);
434 copy(t->file, addr2);
441 return pdisplay(t->file);
445 s_cmd(Text *t, Cmd *cp)
447 int i, j, k, c, m, n, nrp, didsub;
456 if(rxcompile(cp->re->r) == FALSE)
457 editerror("bad regexp in s command");
462 for(p1 = addr.r.q0; p1<=addr.r.q1 && rxexecute(t, nil, p1, addr.r.q1, &sel); ){
463 if(sel.r[0].q0 == sel.r[0].q1){ /* empty match? */
464 if(sel.r[0].q0 == op){
475 rp = erealloc(rp, nrp*sizeof(Rangeset));
479 buf = allocstring(0);
480 for(m=0; m<nrp; m++){
484 for(i = 0; i<cp->text->n; i++)
485 if((c = cp->text->r[i])=='\\' && i<cp->text->n-1){
486 c = cp->text->r[++i];
487 if('1'<=c && c<='9') {
489 if(sel.r[j].q1-sel.r[j].q0>RBUFSIZE){
490 err = "replacement string too long";
493 bufread(t->file, sel.r[j].q0, rbuf, sel.r[j].q1-sel.r[j].q0);
494 for(k=0; k<sel.r[j].q1-sel.r[j].q0; k++)
495 Straddc(buf, rbuf[k]);
501 if(sel.r[0].q1-sel.r[0].q0>RBUFSIZE){
502 err = "right hand side too long in substitution";
505 bufread(t->file, sel.r[0].q0, rbuf, sel.r[0].q1-sel.r[0].q0);
506 for(k=0; k<sel.r[0].q1-sel.r[0].q0; k++)
507 Straddc(buf, rbuf[k]);
509 elogreplace(t->file, sel.r[0].q0, sel.r[0].q1, buf->r, buf->n);
510 delta -= sel.r[0].q1-sel.r[0].q0;
519 if(!didsub && nest==0)
520 editerror("no substitution");
534 u_cmd(Text *t, Cmd *cp)
545 while(n-->0 && t->file->seq!=0 && t->file->seq!=oseq){
547 undo(t, nil, nil, flag, 0, nil, 0);
553 w_cmd(Text *t, Cmd *cp)
560 editerror("can't write file with pending modifications");
561 r = cmdname(f, cp->text, FALSE);
563 editerror("no name specified for 'w' command");
564 putfile(f, addr.r.q0, addr.r.q1, r, runestrlen(r));
565 /* r is freed by putfile */
570 x_cmd(Text *t, Cmd *cp)
573 looper(t->file, cp, cp->cmdc=='x');
575 linelooper(t->file, cp);
580 X_cmd(Text *t, Cmd *cp)
582 filelooper(t, cp, cp->cmdc=='X');
587 runpipe(Text *t, int cmd, Rune *cr, int ncr, int state)
594 r = skipbl(cr, ncr, &n);
596 editerror("no command specified for %c", cmd);
598 if(state == Inserting){
602 if(cmd == '<' || cmd=='|')
603 elogdelete(t->file, t->q0, t->q1);
612 dir = dirname(t, nil, 0);
613 if(dir.nr==1 && dir.r[0]=='.'){ /* sigh */
619 if(t!=nil && t->w!=nil)
620 incref(t->w); /* run will decref */
621 run(w, runetobyte(s, n), dir.r, dir.nr, TRUE, nil, nil, TRUE);
623 if(t!=nil && t->w!=nil)
629 if(t!=nil && t->w!=nil)
634 pipe_cmd(Text *t, Cmd *cp)
636 runpipe(t, cp->cmdc, cp->text->r, cp->text->n, Inserting);
641 nlcount(Text *t, long q0, long q1, long *pnr)
656 bufread(t->file, q0, buf, nbuf);
659 if(buf[i++] == '\n'){
678 printposn(Text *t, int mode)
682 if (t != nil && t->file != nil && t->file->name != nil)
683 warning(nil, "%.*S:", t->file->nname, t->file->name);
686 warning(nil, "#%d", addr.r.q0);
687 if(addr.r.q1 != addr.r.q0)
688 warning(nil, ",#%d", addr.r.q1);
693 l1 = 1+nlcount(t, 0, addr.r.q0, nil);
694 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, nil);
695 /* check if addr ends with '\n' */
696 if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && textreadc(t, addr.r.q1-1)=='\n')
698 warning(nil, "%lud", l1);
700 warning(nil, ",%lud", l2);
704 l1 = 1+nlcount(t, 0, addr.r.q0, &r1);
705 l2 = l1+nlcount(t, addr.r.q0, addr.r.q1, &r2);
708 warning(nil, "%lud+#%lud", l1, r1);
710 warning(nil, ",%lud+#%lud", l2, r2);
717 eq_cmd(Text *t, Cmd *cp)
726 if(cp->text->r[0] == '#'){
730 if(cp->text->r[0] == '+'){
731 mode = PosnLineChars;
736 editerror("newline expected");
743 nl_cmd(Text *t, Cmd *cp)
750 /* First put it on newline boundaries */
752 addr = lineaddr(0, a, -1);
753 a = lineaddr(0, a, 1);
755 if(addr.r.q0==t->q0 && addr.r.q1==t->q1){
757 addr = lineaddr(1, a, 1);
760 textshow(t, addr.r.q0, addr.r.q1, 1);
765 append(File *f, Cmd *cp, long p)
768 eloginsert(f, p, cp->text->r, cp->text->n);
790 bufread(f, p1, buf, np);
792 warning(nil, "%S", buf);
796 f->curtext->q0 = addr.r.q0;
797 f->curtext->q1 = addr.r.q1;
808 /* same check for dirty as in settag, but we know ncache==0 */
809 dirty = !w->isdir && !w->isscratch && f->mod;
810 warning(nil, "%c%c%c %.*S\n", " '"[dirty],
811 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
815 loopcmd(File *f, Cmd *cp, Range *rp, long nrp)
819 for(i=0; i<nrp; i++){
820 f->curtext->q0 = rp[i].q0;
821 f->curtext->q1 = rp[i].q1;
822 cmdexec(f->curtext, cp);
827 looper(File *f, Cmd *cp, int xy)
836 if(rxcompile(cp->re->r) == FALSE)
837 editerror("bad regexp in %c command", cp->cmdc);
840 for(p = r.q0; p<=r.q1; ){
841 if(!rxexecute(f->curtext, nil, p, r.q1, &sel)){ /* no match, but y should still run */
844 tr.q0 = op, tr.q1 = r.q1;
845 p = r.q1+1; /* exit next loop */
847 if(sel.r[0].q0==sel.r[0].q1){ /* empty match? */
858 tr.q0 = op, tr.q1 = sel.r[0].q0;
862 rp = erealloc(rp, nrp*sizeof(Range));
865 loopcmd(f, cp->cmd, rp, nrp);
871 linelooper(File *f, Cmd *cp)
883 a3.r.q0 = a3.r.q1 = r.q0;
884 a = lineaddr(0, a3, 1);
886 for(p = r.q0; p<r.q1; p = a3.r.q1){
888 if(p!=r.q0 || linesel.q1==p){
889 a = lineaddr(1, a3, 1);
892 if(linesel.q0 >= r.q1)
894 if(linesel.q1 >= r.q1)
896 if(linesel.q1 > linesel.q0)
897 if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
900 rp = erealloc(rp, nrp*sizeof(Range));
906 loopcmd(f, cp->cmd, rp, nrp);
917 } loopstruct; /* only one; X and Y can't nest */
920 alllooper(Window *w, void *v)
928 // if(w->isscratch || w->isdir)
931 /* only use this window if it's the current window for the file */
932 if(t->file->curtext != t)
934 // if(w->nopen[QWevent] > 0)
936 /* no auto-execute on files without names */
937 if(cp->re==nil && t->file->nname==0)
939 if(cp->re==nil || filematch(t->file, cp->re)==lp->XY){
940 lp->w = erealloc(lp->w, (lp->nw+1)*sizeof(Window*));
946 alllocker(Window *w, void *v)
955 filelooper(Text *t, Cmd *cp, int XY)
961 editerror("can't nest %c command", "YX"[XY]);
966 if(loopstruct.w) /* error'ed out last time */
970 allwindows(alllooper, &loopstruct);
972 * add a ref to all windows to keep safe windows accessed by X
973 * that would not otherwise have a ref to hold them up during
974 * the shenanigans. note this with globalincref so that any
975 * newly created windows start with an extra reference.
977 allwindows(alllocker, (void*)1);
980 * Unlock the window running the X command.
981 * We'll need to lock and unlock each target window in turn.
985 for(i=0; i<loopstruct.nw; i++){
986 targ = &loopstruct.w[i]->body;
988 winlock(targ->w, cp->cmdc);
989 cmdexec(targ, cp->cmd);
994 winlock(t->w, cp->cmdc);
995 allwindows(alllocker, (void*)0);
1005 nextmatch(File *f, String *r, long p, int sign)
1007 if(rxcompile(r->r) == FALSE)
1008 editerror("bad regexp in command address");
1010 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1011 editerror("no match for regexp");
1012 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q0==p){
1015 if(!rxexecute(f->curtext, nil, p, 0x7FFFFFFFL, &sel))
1016 editerror("address");
1019 if(!rxbexecute(f->curtext, p, &sel))
1020 editerror("no match for regexp");
1021 if(sel.r[0].q0==sel.r[0].q1 && sel.r[0].q1==p){
1024 if(!rxbexecute(f->curtext, p, &sel))
1025 editerror("address");
1030 File *matchfile(String*);
1031 Address charaddr(long, Address, int);
1032 Address lineaddr(long, Address, int);
1035 cmdaddress(Addr *ap, Address a, int sign)
1044 a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
1052 a.r.q0 = a.r.q1 = f->nc;
1056 editerror("can't handle '");
1066 nextmatch(f, ap->re, sign>=0? a.r.q1 : a.r.q0, sign);
1071 f = matchfile(ap->re);
1076 a.r.q0 = 0, a.r.q1 = f->nc;
1082 a1 = cmdaddress(ap->left, a, 0);
1084 a1.f = a.f, a1.r.q0 = a1.r.q1 = 0;
1085 if(ap->type == ';'){
1088 f->curtext->q0 = a1.r.q0;
1089 f->curtext->q1 = a1.r.q1;
1092 a2 = cmdaddress(ap->next, a, 0);
1094 a2.f = a.f, a2.r.q0 = a2.r.q1 = f->nc;
1096 editerror("addresses in different files");
1097 a.f = a1.f, a.r.q0 = a1.r.q0, a.r.q1 = a2.r.q1;
1099 editerror("addresses out of order");
1107 if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
1108 a = lineaddr(1L, a, sign);
1111 error("cmdaddress");
1114 }while(ap = ap->next); /* assign = */
1124 alltofile(Window *w, void *v)
1132 if(w->isscratch || w->isdir)
1135 /* only use this window if it's the current window for the file */
1136 if(t->file->curtext != t)
1138 // if(w->nopen[QWevent] > 0)
1140 if(runeeq(tp->r->r, tp->r->n, t->file->name, t->file->nname))
1150 rr.r = skipbl(r->r, r->n, &rr.n);
1153 allwindows(alltofile, &t);
1155 editerror("no such file\"%S\"", rr.r);
1160 allmatchfile(Window *w, void *v)
1166 if(w->isscratch || w->isdir)
1169 /* only use this window if it's the current window for the file */
1170 if(t->file->curtext != t)
1172 // if(w->nopen[QWevent] > 0)
1174 if(filematch(w->body.file, tp->r)){
1176 editerror("too many files match \"%S\"", tp->r->r);
1177 tp->f = w->body.file;
1182 matchfile(String *r)
1188 allwindows(allmatchfile, &tf);
1191 editerror("no file matches \"%S\"", r->r);
1196 filematch(File *f, String *r)
1201 int match, i, dirty;
1204 /* compile expr first so if we get an error, we haven't allocated anything */
1205 if(rxcompile(r->r) == FALSE)
1206 editerror("bad regexp in file match");
1209 /* same check for dirty as in settag, but we know ncache==0 */
1210 dirty = !w->isdir && !w->isscratch && f->mod;
1211 snprint(buf, BUFSIZE, "%c%c%c %.*S\n", " '"[dirty],
1212 '+', " ."[curtext!=nil && curtext->file==f], f->nname, f->name);
1213 rbuf = bytetorune(buf, &i);
1215 match = rxexecute(nil, rbuf, 0, i, &s);
1221 charaddr(long l, Address addr, int sign)
1224 addr.r.q0 = addr.r.q1 = l;
1226 addr.r.q1 = addr.r.q0 -= l;
1228 addr.r.q0 = addr.r.q1 += l;
1229 if(addr.r.q0<0 || addr.r.q1>addr.f->nc)
1230 editerror("address out of range");
1235 lineaddr(long l, Address addr, int sign)
1246 if(sign==0 || addr.r.q1==0){
1247 a.r.q0 = a.r.q1 = 0;
1253 if(sign==0 || addr.r.q1==0){
1258 n = textreadc(f->curtext, p++)=='\n';
1262 editerror("address out of range");
1263 if(textreadc(f->curtext, p++) == '\n')
1268 while(p < f->nc && textreadc(f->curtext, p++)!='\n')
1276 for(n = 0; n<l; ){ /* always runs once */
1279 editerror("address out of range");
1281 c = textreadc(f->curtext, p-1);
1282 if(c != '\n' || ++n != l)
1290 while(p > 0 && textreadc(f->curtext, p-1)!='\n') /* lines start after a newline */
1305 allfilecheck(Window *w, void *v)
1307 struct Filecheck *fp;
1312 if(w->body.file == fp->f)
1314 if(runeeq(fp->r, fp->nr, f->name, f->nname))
1315 warning(nil, "warning: duplicate file name \"%.*S\"\n", fp->nr, fp->r);
1319 cmdname(File *f, String *str, int set)
1323 struct Filecheck fc;
1330 /* no name; use existing */
1333 r = runemalloc(f->nname+1);
1334 runemove(r, f->name, f->nname);
1337 s = skipbl(s, n, &n);
1342 r = runemalloc(n+1);
1345 newname = dirname(f->curtext, runestrdup(s), n);
1347 r = runemalloc(n+1); /* NUL terminate */
1348 runemove(r, newname.r, n);
1354 allwindows(allfilecheck, &fc);
1359 if(set && !runeeq(r, n, f->name, f->nname)){
1362 f->curtext->w->dirty = TRUE;
1363 winsetname(f->curtext->w, r, n);