implement Oxex; include "mods.m"; ui, trees, seled, debug, Tree, Edit: import oxedit; # Xcmd start and end events are coordinated by eventproc, that means # that it is safe to use xcmds within a direct call from eventproc. However, # the list is kept synchronized by xcmdproc, which coordinates command # execution. xsc, xec: chan of ref Xcmd; xqc, xsqc: chan of chan of string; xctx: ref Draw->Context; init(d: Oxdat, c: chan of int) { initmods(d->mods); xsc = chan[1] of ref Xcmd; xec = chan[1] of ref Xcmd; xqc = chan[1] of chan of string; xsqc = chan[1] of chan of string; spawn xcmdproc(xsc, xec, xqc, xsqc, c); } xcmdproc(xsc, xec: chan of ref Xcmd, xqc, xsqc: chan of chan of string, ec: chan of int) { for(;;){ alt { x := <-xsc => if (!x.done) xcmds = x::xcmds; ec <-= x.tid; x := <-xec => nl: list of ref Xcmd; for (nl = nil; xcmds != nil; xcmds = tl xcmds) if ((xx := hd xcmds) != x) nl = xx::nl; xcmds = nl; ec <-= x.tid; qc := <-xsqc => qc <-= ftext(1); qc := <-xqc => qc <-= ftext(0); } } } cmdname(s: string): string { (s, nil) = splitl(s, "\n"); if (len s > 30) s = s[0:30] + "..."; return s; } bufwriteproc(buf: string, fd: ref FD, c: chan of int) { pid := pctl(NEWFD, 0::1::2::fd.fd::nil); stderr = fildes(2); fd = fildes(fd.fd); c <-= pid; d:= array of byte buf; buf = nil; write(fd, d, len buf); } bufreadproc(fd: ref FD, c: chan of int, rc: chan of string) { pid := pctl(NEWFD, 0::1::2::fd.fd::nil); stderr = fildes(2); fd = fildes(fd.fd); c <-= pid; d := readfile(fd); fd = nil; if (d == nil) rc <-= ""; else rc <-= string d; } pipein(buf: string): ref FD { p := array[2] of ref FD; if (pipe(p) < 0){ fprint(stderr, "pipe: %r\n"); return nil; } c := chan of int; spawn bufwriteproc(buf, p[1], c); <-c; return p[0]; } pipeout(): (ref FD, chan of string) { p := array[2] of ref FD; if (pipe(p) < 0){ fprint(stderr, "pipe: %r\n"); return (nil, nil); } c := chan of int; rc:= chan of string; spawn bufreadproc(p[0], c, rc); <-c; return (p[1], rc); } xoutproc(x: ref Xcmd, xfd: ref FD, c: chan of int) { edfd: ref FD; pid := pctl(NEWFD, 0::1::2::xfd.fd::nil); stderr = fildes(2); xfd = fildes(xfd.fd); c <-= pid; buf := array[1024] of byte; edfd = nil; name := sprint("[%s %s %d]", x.dir, cmdname(x.cmd), x.pid); for(;;){ nr := read(xfd, buf, len buf); if (nr <= 0) break; for(i:= 0; i < 2; i++){ ui.ctl("hold\n"); if (edfd == nil){ # this is a race, potentially. tr := Tree.find(x.tid); if (tr != nil){ ed := newedit(tr, name, 1, 0); if (ed != nil) edfd = open(ed.body.path+"/data", OWRITE); } } if (edfd == nil) edfd = stderr; seek(edfd, big 0, 2); nw := write(edfd, buf[0:nr], nr); ui.ctl("release\n"); if (nw == nr) break; # try once more by recreating the panel # the user might have deleted it after the first output from x } } x.done = 1; xec <-= x; } xproc(x: ref Xcmd, c: chan of int) { pid := pctl(NEWFD, 0::1::2::x.in.fd::x.out.fd::x.err.fd::nil); dup(x.in.fd, 0); dup(x.out.fd, 1); dup(x.err.fd, 2); x.in = x.out = x.err = nil; c <-= pid; # This executes the command in a new environment, # we should preserve the environment, so that o/live is indeed # a typescript. Each tree could be its own environment. chdir(x.dir); system(xctx, x.cmd); } Xcmd.new(cmd: string, dir: string, in, out: ref FD, tid: int): ref Xcmd { x := ref Xcmd(tid, -1, -1, cmd, dir, in, out, nil, 0); if (x.in == nil) x.in = open("/dev/null", OREAD); if (x.in == nil){ fprint(stderr, "/dev/null: %r\n"); return nil; } p := array[2] of ref FD; if (pipe(p) < 0){ fprint(stderr, "pipe: %r\n"); return nil; } x.err = p[1]; if (x.out == nil) x.out = p[1]; c := chan of int; spawn xproc(x, c); x.pid = <-c; p[1] = nil; spawn xoutproc(x, p[0], c); x.rpid = <-c; xsc <-= x; return x; } ftext(short: int): string { text := ""; nl: list of ref Xcmd; for (nl = xcmds; nl != nil; nl = tl nl) if (short) text += sprint("%d ", (hd nl).pid); else text += sprint("Kill %d\t# %s\n", (hd nl).pid, (hd nl).cmd); if (text == "") text = "none"; return text; } Xcmd.ftext(short: int): string { c := chan of string; if (short) xsqc <-= c; else xqc <-= c; return <-c; } # This are builtin commands, shared by the o/mero interface and # the sam command language newedit(tr: ref Tree, path: string, msg: int, force: int): ref Edit { ed: ref Edit; ed = nil; if (!msg) path = names->cleanname(path); ed = tr.findedit(path); if (!force) for(trl := trees; trl != nil && ed == nil; trl = tl trl) ed = (hd trl).findedit(path); if (ed == nil){ ed = Edit.new(path, tr.tid, msg); if (ed != nil){ # Should probably locate the tree with the maximal # prefix for path shown, and attach the edit to it. tr.addedit(ed); ed.mk(); ed.get(); } } return ed; } msgfd(tr: ref Tree, path: string): ref FD { name := sprint("[%s]", path); ed := newedit(tr, name, 1, 0); if (ed == nil || ed.body == nil) return stderr; fd := open(ed.body.path + "/data", OWRITE|OTRUNC); if (fd == nil) return stderr; return fd; } msg(tr: ref Tree, dir: string, s: string) { fd :=msgfd(tr, dir); data := array of byte s; write(fd, data, len data); } deledit(ed: ref Edit) { tr := Tree.find(ed.tid); if ((e := ed.cleanto("Close", nil)) != nil) msg(tr, ed.dir, sprint("%s: %s\n", ed.path, e)); else ed.close(); } putedit(ed: ref Edit, where: string) { tr := Tree.find(ed.tid); cmd := "Put"; if (where != ed.path) cmd = "New"; if ((e := ed.cleanto(cmd, where)) != nil) msg(tr, ed.dir, sprint("%s: %s\n", ed.path, e)); else { if (ed.put(where) < 0) ed.close(); } } findedit(t: ref Edit, s: string): ref Edit { tr := Tree.find(t.tid); ed := tr.findedit(s); if (ed == nil) for(trl := trees; trl != nil; trl = tl trl) ed = (hd trl).findedit(s); if(ed == nil) msg(tr, nil, sprint("%s: no such edit", s)); return ed; }