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;
}
|