summaryrefslogtreecommitdiff
path: root/appl/acme/edit.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/acme/edit.b')
-rw-r--r--appl/acme/edit.b676
1 files changed, 676 insertions, 0 deletions
diff --git a/appl/acme/edit.b b/appl/acme/edit.b
new file mode 100644
index 00000000..92faad1a
--- /dev/null
+++ b/appl/acme/edit.b
@@ -0,0 +1,676 @@
+implement Edit;
+
+include "common.m";
+
+sys: Sys;
+dat: Dat;
+utils: Utils;
+textm: Textm;
+windowm: Windowm;
+rowm: Rowm;
+scroll: Scroll;
+editlog: Editlog;
+editcomd: Editcmd;
+
+sprint, print: import sys;
+FALSE, TRUE, BUFSIZE, Null, Empty, Inactive: import Dat;
+warning, error, strchr: import utils;
+Text: import textm;
+File: import Filem;
+Window: import windowm;
+allwindows: import rowm;
+scrdraw: import scroll;
+elogterm, elogapply: import editlog;
+cmdexec, resetxec: import editcomd;
+
+init(mods : ref Dat->Mods)
+{
+ sys = mods.sys;
+ dat = mods.dat;
+ utils = mods.utils;
+ textm = mods.textm;
+ windowm = mods.windowm;
+ rowm = mods.rowm;
+ scroll = mods.scroll;
+ editlog = mods.editlog;
+ editcomd = mods.editcmd;
+ editing = Inactive;
+}
+
+linex: con "\n";
+wordx: con "\t\n";
+
+cmdtab = array[28] of {
+# cmdc text regexp addr defcmd defaddr count token fn
+ Cmdt ( '\n', 0, 0, 0, 0, aDot, 0, nil, C_nl ),
+ Cmdt ( 'a', 1, 0, 0, 0, aDot, 0, nil, C_a ),
+ Cmdt ( 'b', 0, 0, 0, 0, aNo, 0, linex, C_b ),
+ Cmdt ( 'c', 1, 0, 0, 0, aDot, 0, nil, C_c ),
+ Cmdt ( 'd', 0, 0, 0, 0, aDot, 0, nil, C_d ),
+ Cmdt ( 'e', 0, 0, 0, 0, aNo, 0, wordx, C_e ),
+ Cmdt ( 'f', 0, 0, 0, 0, aNo, 0, wordx, C_f ),
+ Cmdt ( 'g', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
+ Cmdt ( 'i', 1, 0, 0, 0, aDot, 0, nil, C_i ),
+ Cmdt ( 'm', 0, 0, 1, 0, aDot, 0, nil, C_m ),
+ Cmdt ( 'p', 0, 0, 0, 0, aDot, 0, nil, C_p ),
+ Cmdt ( 'r', 0, 0, 0, 0, aDot, 0, wordx, C_e ),
+ Cmdt ( 's', 0, 1, 0, 0, aDot, 1, nil, C_s ),
+ Cmdt ( 't', 0, 0, 1, 0, aDot, 0, nil, C_m ),
+ Cmdt ( 'u', 0, 0, 0, 0, aNo, 2, nil, C_u ),
+ Cmdt ( 'v', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
+ Cmdt ( 'w', 0, 0, 0, 0, aAll, 0, wordx, C_w ),
+ Cmdt ( 'x', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
+ Cmdt ( 'y', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
+ Cmdt ( '=', 0, 0, 0, 0, aDot, 0, linex, C_eq ),
+ Cmdt ( 'B', 0, 0, 0, 0, aNo, 0, linex, C_B ),
+ Cmdt ( 'D', 0, 0, 0, 0, aNo, 0, linex, C_D ),
+ Cmdt ( 'X', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
+ Cmdt ( 'Y', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
+ Cmdt ( '<', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ Cmdt ( '|', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ Cmdt ( '>', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
+ # deliberately unimplemented
+ # Cmdt ( 'k', 0, 0, 0, 0, aDot, 0, nil, C_k ),
+ # Cmdt ( 'n', 0, 0, 0, 0, aNo, 0, nil, C_n ),
+ # Cmdt ( 'q', 0, 0, 0, 0, aNo, 0, nil, C_q ),
+ # Cmdt ( '!', 0, 0, 0, 0, aNo, 0, linex, C_plan9 ),
+ Cmdt (0, 0, 0, 0, 0, 0, 0, nil, -1 )
+};
+
+cmdstartp: string;
+cmdendp: int;
+cmdp: int;
+editerrc: chan of string;
+
+lastpat : ref String;
+patset: int;
+
+# cmdlist: ref List;
+# addrlist: ref List;
+# stringlist: ref List;
+
+editwaitproc(pid : int, sync: chan of int)
+{
+ fd : ref Sys->FD;
+ n : int;
+
+ sys->pctl(Sys->FORKFD, nil);
+ w := sprint("#p/%d/wait", pid);
+ fd = sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ error("fd == nil in editwaitproc");
+ sync <-= sys->pctl(0, nil);
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;){
+ if ((n = sys->read(fd, buf, len buf))<0)
+ error("bad read in editwaitproc");
+ status = string buf[0:n];
+ dat->cwait <-= status;
+ }
+}
+
+editthread()
+{
+ cmdp: ref Cmd;
+
+ mypid := sys->pctl(0, nil);
+ sync := chan of int;
+ spawn editwaitproc(mypid, sync);
+ yourpid := <- sync;
+ while((cmdp=parsecmd(0)) != nil){
+# ocurfile = curfile;
+# loaded = curfile && !curfile->unread;
+ if(cmdexec(curtext, cmdp) == 0)
+ break;
+ freecmd();
+ }
+ editerrc <-= nil;
+ utils->postnote(Utils->PNPROC, mypid, yourpid, "kill");
+}
+
+allelogterm(w: ref Window)
+{
+ elogterm(w.body.file);
+}
+
+alleditinit(w: ref Window)
+{
+ w.tag.commit(TRUE);
+ w.body.commit(TRUE);
+ w.body.file.editclean = FALSE;
+}
+
+allupdate(w: ref Window)
+{
+ t: ref Text;
+ i: int;
+ f: ref File;
+
+ t = w.body;
+ f = t.file;
+ if(f.curtext != t) # do curtext only
+ return;
+ if(f.elog.typex == Null)
+ elogterm(f);
+ else if(f.elog.typex != Empty){
+ elogapply(f);
+ if(f.editclean){
+ f.mod = FALSE;
+ for(i=0; i<f.ntext; i++)
+ f.text[i].w.dirty = FALSE;
+ }
+ t.setselect(t.q0, t.q1);
+ scrdraw(t);
+ w.settag();
+ }
+}
+
+editerror(s: string)
+{
+ # print("%s", s);
+ freecmd();
+ allwindows(ALLELOGTERM, nil); # truncate the edit logs
+ editerrc <-= s;
+ exit;
+}
+
+editcmd(ct: ref Text, r: string, n: int)
+{
+ err: string;
+
+ if(n == 0)
+ return;
+ if(2*n > BUFSIZE){
+ warning(nil, "string too long\n");
+ return;
+ }
+
+ allwindows(ALLEDITINIT, nil);
+ cmdstartp = r[0:n];
+ if(r[n-1] != '\n')
+ cmdstartp[n++] = '\n';
+ cmdendp = n;
+ cmdp = 0;
+ if(ct.w == nil)
+ curtext = nil;
+ else
+ curtext = ct.w.body;
+ resetxec();
+ if(editerrc == nil){
+ editerrc = chan of string;
+ lastpat = allocstring(0);
+ }
+ spawn editthread();
+ err = <- editerrc;
+ editing = Inactive;
+ if(err != nil)
+ warning(nil, sprint("Edit: %s\n", err));
+
+ # update everyone whose edit log has data
+ allwindows(ALLUPDATE, nil);
+}
+
+getch(): int
+{
+ if(cmdp == cmdendp)
+ return -1;
+ return cmdstartp[cmdp++];
+}
+
+nextc(): int
+{
+ if(cmdp == cmdendp)
+ return -1;
+ return cmdstartp[cmdp];
+}
+
+ungetch()
+{
+ if(--cmdp < 0)
+ error("ungetch");
+}
+
+getnum(signok: int): int
+{
+ n: int;
+ c, sign: int;
+
+ n = 0;
+ sign = 1;
+ if(signok>1 && nextc()=='-'){
+ sign = -1;
+ getch();
+ }
+ if((c=nextc())<'0' || '9'<c) # no number defaults to 1
+ return sign;
+ while('0'<=(c=getch()) && c<='9')
+ n = n*10 + (c-'0');
+ ungetch();
+ return sign*n;
+}
+
+cmdskipbl(): int
+{
+ c: int;
+ do
+ c = getch();
+ while(c==' ' || c=='\t');
+ if(c >= 0)
+ ungetch();
+ return c;
+}
+
+# Check that list has room for one more element.
+# growlist(l: ref List)
+# {
+# if(l.elems == nil || l.nalloc==0){
+# l.nalloc = INCR;
+# l.elems = array[INCR] of Listelement;
+# l.nused = 0;
+# }else if(l.nused == l.nalloc){
+# old := l.elems;
+# l.elems = array[l.nalloc+INCR] of Listelement;
+# l.elems[0:] = old[0:l.nalloc];
+# l.nalloc += INCR;
+# }
+# }
+
+# Remove the ith element from the list
+# dellist(l: ref List, i: int)
+# {
+# l.elems[i:] = l.elems[i+1:l.nused];
+# l.nused--;
+# }
+
+# Add a new element, whose position is i, to the list
+# inslist(l: ref List, i: int, val: int)
+# {
+# growlist(l);
+# l.elems[i+1:] = l.elems[i:l.nused];
+# l.elems[i] = val;
+# l.nused++;
+# }
+
+# listfree(l: ref List)
+# {
+# l.elems = nil;
+# }
+
+allocstring(n: int): ref String
+{
+ s: ref String;
+
+ s = ref String;
+ s.n = n;
+ s.r = string array[s.n] of { * => byte '\0' };
+ return s;
+}
+
+freestring(s: ref String)
+{
+ s.r = nil;
+}
+
+newcmd(): ref Cmd
+{
+ p: ref Cmd;
+
+ p = ref Cmd;
+ # inslist(cmdlist, cmdlist.nused, p);
+ return p;
+}
+
+newstring(n: int): ref String
+{
+ p: ref String;
+
+ p = allocstring(n);
+ # inslist(stringlist, stringlist.nused, p);
+ return p;
+}
+
+newaddr(): ref Addr
+{
+ p: ref Addr;
+
+ p = ref Addr;
+ # inslist(addrlist, addrlist.nused, p);
+ return p;
+}
+
+freecmd()
+{
+ # i: int;
+
+ # cmdlist.elems = nil;
+ # addrlist.elems = nil;
+ # stringlist.elems = nil;
+ # cmdlist.nused = addrlist.nused = stringlist.nused = 0;
+}
+
+okdelim(c: int)
+{
+ if(c=='\\' || ('a'<=c && c<='z')
+ || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+ editerror(sprint("bad delimiter %c\n", c));
+}
+
+atnl()
+{
+ c: int;
+
+ cmdskipbl();
+ c = getch();
+ if(c != '\n')
+ editerror(sprint("newline expected (saw %c)", c));
+}
+
+Straddc(s: ref String, c: int)
+{
+ s.r[s.n++] = c;
+}
+
+getrhs(s: ref String, delim: int, cmd: int)
+{
+ c: int;
+
+ while((c = getch())>0 && c!=delim && c!='\n'){
+ if(c == '\\'){
+ if((c=getch()) <= 0)
+ error("bad right hand side");
+ if(c == '\n'){
+ ungetch();
+ c='\\';
+ }else if(c == 'n')
+ c='\n';
+ else if(c!=delim && (cmd=='s' || c!='\\')) # s does its own
+ Straddc(s, '\\');
+ }
+ Straddc(s, c);
+ }
+ ungetch(); # let client read whether delimiter, '\n' or whatever
+}
+
+collecttoken(end: string): ref String
+{
+ c: int;
+
+ s := newstring(0);
+
+ while((c=nextc())==' ' || c=='\t')
+ Straddc(s, getch()); # blanks significant for getname()
+ while((c=getch())>0 && strchr(end, c)<0)
+ Straddc(s, c);
+ if(c != '\n')
+ atnl();
+ return s;
+}
+
+collecttext(): ref String
+{
+ s: ref String;
+ begline, i, c, delim: int;
+
+ s = newstring(0);
+ if(cmdskipbl()=='\n'){
+ getch();
+ i = 0;
+ do{
+ begline = i;
+ while((c = getch())>0 && c!='\n'){
+ i++;
+ Straddc(s, c);
+ }
+ i++;
+ Straddc(s, '\n');
+ if(c < 0)
+ return s;
+ }while(s.r[begline]!='.' || s.r[begline+1]!='\n');
+ s.r[s.n-2] = '\0';
+ }else{
+ okdelim(delim = getch());
+ getrhs(s, delim, 'a');
+ if(nextc()==delim)
+ getch();
+ atnl();
+ }
+ return s;
+}
+
+cmdlookup(c: int): int
+{
+ i: int;
+
+ for(i=0; cmdtab[i].cmdc; i++)
+ if(cmdtab[i].cmdc == c)
+ return i;
+ return -1;
+}
+
+parsecmd(nest: int): ref Cmd
+{
+ i, c: int;
+ cp, ncp: ref Cmd;
+ cmd: ref Cmd;
+
+ cmd = ref Cmd;
+ cmd.next = cmd.cmd = nil;
+ cmd.re = nil;
+ cmd.flag = cmd.num = 0;
+ cmd.addr = compoundaddr();
+ if(cmdskipbl() == -1)
+ return nil;
+ if((c=getch())==-1)
+ return nil;
+ cmd.cmdc = c;
+ if(cmd.cmdc=='c' && nextc()=='d'){ # sleazy two-character case
+ getch(); # the 'd'
+ cmd.cmdc='c'|16r100;
+ }
+ i = cmdlookup(cmd.cmdc);
+ if(i >= 0){
+ if(cmd.cmdc == '\n'){
+ cp = newcmd();
+ *cp = *cmd;
+ return cp;
+ # let nl_cmd work it all out
+ }
+ ct := cmdtab[i];
+ if(ct.defaddr==aNo && cmd.addr != nil)
+ editerror("command takes no address");
+ if(ct.count)
+ cmd.num = getnum(ct.count);
+ if(ct.regexp){
+ # x without pattern -> .*\n, indicated by cmd.re==0
+ # X without pattern is all files
+ if((ct.cmdc!='x' && ct.cmdc!='X') ||
+ ((c = nextc())!=' ' && c!='\t' && c!='\n')){
+ cmdskipbl();
+ if((c = getch())=='\n' || c<0)
+ editerror("no address");
+ okdelim(c);
+ cmd.re = getregexp(c);
+ if(ct.cmdc == 's'){
+ cmd.text = newstring(0);
+ getrhs(cmd.text, c, 's');
+ if(nextc() == c){
+ getch();
+ if(nextc() == 'g')
+ cmd.flag = getch();
+ }
+
+ }
+ }
+ }
+ if(ct.addr && (cmd.mtaddr=simpleaddr())==nil)
+ editerror("bad address");
+ if(ct.defcmd){
+ if(cmdskipbl() == '\n'){
+ getch();
+ cmd.cmd = newcmd();
+ cmd.cmd.cmdc = ct.defcmd;
+ }else if((cmd.cmd = parsecmd(nest))==nil)
+ error("defcmd");
+ }else if(ct.text)
+ cmd.text = collecttext();
+ else if(ct.token != nil)
+ cmd.text = collecttoken(ct.token);
+ else
+ atnl();
+ }else
+ case(cmd.cmdc){
+ '{' =>
+ cp = nil;
+ do{
+ if(cmdskipbl()=='\n')
+ getch();
+ ncp = parsecmd(nest+1);
+ if(cp != nil)
+ cp.next = ncp;
+ else
+ cmd.cmd = ncp;
+ }while((cp = ncp) != nil);
+ break;
+ '}' =>
+ atnl();
+ if(nest==0)
+ editerror("right brace with no left brace");
+ return nil;
+ 'c'|16r100 =>
+ editerror("unimplemented command cd");
+ * =>
+ editerror(sprint("unknown command %c", cmd.cmdc));
+ }
+ cp = newcmd();
+ *cp = *cmd;
+ return cp;
+}
+
+getregexp(delim: int): ref String
+{
+ buf, r: ref String;
+ i, c: int;
+
+ buf = allocstring(0);
+ for(i=0; ; i++){
+ if((c = getch())=='\\'){
+ if(nextc()==delim)
+ c = getch();
+ else if(nextc()=='\\'){
+ Straddc(buf, c);
+ c = getch();
+ }
+ }else if(c==delim || c=='\n')
+ break;
+ if(i >= BUFSIZE)
+ editerror("regular expression too long");
+ Straddc(buf, c);
+ }
+ if(c!=delim && c)
+ ungetch();
+ if(buf.n > 0){
+ patset = TRUE;
+ freestring(lastpat);
+ lastpat = buf;
+ }else
+ freestring(buf);
+ if(lastpat.n == 0)
+ editerror("no regular expression defined");
+ r = newstring(lastpat.n);
+ k := lastpat.n;
+ for(j := 0; j < k; j++)
+ r.r[j] = lastpat.r[j]; # newstring put \0 at end
+ return r;
+}
+
+simpleaddr(): ref Addr
+{
+ addr: Addr;
+ ap, nap: ref Addr;
+
+ addr.next = nil;
+ addr.left = nil;
+ case(cmdskipbl()){
+ '#' =>
+ addr.typex = getch();
+ addr.num = getnum(1);
+ break;
+ '0' to '9' =>
+ addr.num = getnum(1);
+ addr.typex='l';
+ break;
+ '/' or '?' or '"' =>
+ addr.re = getregexp(addr.typex = getch());
+ break;
+ '.' or
+ '$' or
+ '+' or
+ '-' or
+ '\'' =>
+ addr.typex = getch();
+ break;
+ * =>
+ return nil;
+ }
+ if((addr.next = simpleaddr()) != nil)
+ case(addr.next.typex){
+ '.' or
+ '$' or
+ '\'' =>
+ if(addr.typex!='"')
+ editerror("bad address syntax");
+ break;
+ '"' =>
+ editerror("bad address syntax");
+ break;
+ 'l' or
+ '#' =>
+ if(addr.typex=='"')
+ break;
+ if(addr.typex!='+' && addr.typex!='-'){
+ # insert the missing '+'
+ nap = newaddr();
+ nap.typex='+';
+ nap.next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ '/' or
+ '?' =>
+ if(addr.typex!='+' && addr.typex!='-'){
+ # insert the missing '+'
+ nap = newaddr();
+ nap.typex='+';
+ nap.next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ '+' or
+ '-' =>
+ break;
+ * =>
+ error("simpleaddr");
+ }
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+
+compoundaddr(): ref Addr
+{
+ addr: Addr;
+ ap, next: ref Addr;
+
+ addr.left = simpleaddr();
+ if((addr.typex = cmdskipbl())!=',' && addr.typex!=';')
+ return addr.left;
+ getch();
+ next = addr.next = compoundaddr();
+ if(next != nil && (next.typex==',' || next.typex==';') && next.left==nil)
+ editerror("bad address syntax");
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+