diff options
Diffstat (limited to 'appl/acme/edit.b')
| -rw-r--r-- | appl/acme/edit.b | 676 |
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; +} + |
