#include <u.h>
#include <libc.h>
#include <thread.h>
#include <error.h>
#include <b.h>
#include <omero.h>
#include <keyboard.h>
#include "ox.h"
static char* homedir;
static Edit* msgedit;
static char* where; // ...is ox creating panels
static int space; // omero workspace
Edit** edits;
int nedits;
int debug;
int
omerogone(void)
{
dprint("omero is going\n");
postnote(PNGROUP, getpid(), "omerogone");
threadexitsall("omerogone");
return 1;
}
static Edit*
findedit(char* p)
{
int i;
for(i = 0; i < nedits; i++)
if(edits[i] && !strcmp(edits[i]->name, p))
return edits[i];
return nil;
}
Edit*
musthavemsgs(char* msgs)
{
char* s;
Edit* e;
if(msgs == nil)
s = msgs = smprint("[%s]", homedir);
else
s = nil;
assert(msgs[0] == '[');
e = findedit(msgs);
if(e == nil){
e = editfile(msgs, 1);
if(e){
openpanelctl(e->gtext);
panelctl(e->gtext, "mark 1");
closepanelctl(e->gtext);
if (s != nil)
msgedit = e;
} else
fprint(2, "%s: can't create %s panel\n", argv0, s);
}
free(s);
return e;
}
void
musthaveedits(void)
{
int i;
for(i = 0; i < nedits; i++)
if(edits[i])
return;
// lastedit was gone. terminate.
postnote(PNGROUP, getpid(), "noedits");
sysfatal("noedits");
}
static void
dumpedit(Edit* e)
{
char* sts = "????";
switch(e->sts){
case Sclean:
sts = "clean";
break;
case Sdirty:
sts = "dirty";
break;
case Stemp:
sts = "temp";
break;
}
fprint(2, "ox: ed %-30s\t(%s, %s t=%011ld)\n\tgcol %s\n",
e->name, e->dir, sts, e->atime, e->gcolname);
}
void
dumpedits(void)
{
int i;
fprint(2, "panels:\n");
paneldump(2);
fprint(2, "edits:\n");
for(i = 0; i < nedits; i++)
if(edits[i])
dumpedit(edits[i]);
fprint(2, "\n");
}
static void
freeedit(Edit* e)
{
if(e == msgedit)
msgedit = nil;
free(e->name);
free(e->dir);
free(e->text);
free(e->lastev);
free(e->gcolname);
memset(e, 3, sizeof(*e)); // poison
free(e);
}
void
msgprint(Edit* e, char* fmt, ...)
{
static int nested;
va_list arg;
char* msg;
char* txt;
if(nested){
// perhaps a msgprint called while
// trying to do a msgprint because of an
// error. Ignore.
return;
}
nested++;
if(e == nil){
musthavemsgs(nil);
e = msgedit;
}
if(e == nil){
musthavemsgs(nil);
e = msgedit;
}
if(e != nil){
va_start(arg, fmt);
msg = vsmprint(fmt, arg);
va_end(arg);
txt = smprint("%s%s", e ? e->text : "", msg);
free(e->text);
e->text = txt;
openpanel(e->gtext, OWRITE|OTRUNC);
writepanel(e->gtext, txt, strlen(txt));
closepanel(e->gtext);
free(msg);
}
nested--;
}
char*
cleanpath(char* file, char* dir)
{
assert(file && file[0]);
if(file[0] != '/' && dir != nil)
file = smprint("%s/%s", dir, file);
else
file = estrdup(file);
return cleanname(file);
}
char*
filedir(char* file)
{
Dir* d;
char* s;
file = estrdup(file);
d = dirstat(file);
if(d && !(d->qid.type&QTDIR)){
s = utfrrune(file, '/');
if(s)
*s = 0;
}
free(d);
return file;
}
void
cleanedit(Edit* e, Dir* d)
{
assert(e->sts != Stemp);
e->sts = Sclean;
if(d)
e->qid = d->qid;
else {
d = dirstat(e->name);
if(d){
e->qid = d->qid;
free(d);
}
}
}
static char*
gettagops(char* tag)
{
char* s;
if (tag == nil || *tag == 0)
return "";
if (s = utfrune(tag, ']'))
return s+1;
for(s = tag; *s == ' ' || *s == '\t'; s++)
;
if (*s == 0)
return "";
while( *s && *s != ' ' && *s != '\t')
s++;
return s;
}
void
updatetag(Edit* e, int keep)
{
static char* home = nil;
char* ops;
char* utag;
char* nutag;
char* s;
long l;
assert(e);
if (home == nil)
home = getenv("home");
if(e->gtag == nil) // No tag panel!
return;
nutag = emalloc(Tagmax);
s = strecpy(nutag, nutag+Tagmax, e->name);
if (keep){
utag = readallpanel(e->gtag, &l);
ops = gettagops(utag);
strecpy(s, nutag+Tagmax, ops);
free(utag);
} else {
if(e->qid.type&QTDIR){
if (!strcmp(e->name, home))
ops = " Exit Done Get ";
else
ops = " Done Get ";
}else
ops = " Done Put Get ";
s = strecpy(s, nutag+Tagmax, ops);
tagcmds(e->name, s, Tagmax - (s - nutag));
}
l = strlen(nutag);
openpanel(e->gtag, OWRITE|OTRUNC);
if(writepanel(e->gtag, nutag, l) != l)
fprint(2, "%s: updatetag: %r\n", argv0);
closepanel(e->gtag);
free(nutag);
}
char*
gettagpath(Edit* e)
{
long l;
char* t;
char* s;
if(e->gtag == nil || openpanel(e->gtag, OREAD) < 0)
return nil;
t = emalloc(Tagmax);
l = readpanel(e->gtag, t, Tagmax-1);
if(l >= 0){
t[l] = 0;
s = utfrune(t, ' ');
if (s)
*s = 0;
s = utfrune(t, '\t');
if (s)
*s = 0;
} else {
free(t);
t = nil;
}
closepanel(e->gtag);
return t;
}
int
getsts(Panel* gtext, Sts* e)
{
char ctls[512];
char* s;
char* q;
int rc;
openpanelctl(gtext);
rc = readpanelctl(gtext, ctls, sizeof(ctls)-1);
closepanelctl(gtext);
if(rc <= 0){
fprint(2, "%s: getsts: %r\n", argv0);
return e->sts;
}
ctls[rc] = 0;
if(e->sts != Stemp)
if(utfutf(ctls, "dirty"))
e->sts = Sdirty;
else
e->sts = Sclean;
s = utfutf(ctls, "mark ");
if(s != nil){
s += 5;
e->mark = strtod(s, &q);
}
s = utfutf(ctls, "sel ");
if(s != nil){
s += 4;
e->ss = strtod(s, &q);
e->se = strtod(q, &s);
}
return e->sts;
}
static int
isfixedfont(char* path)
{
char* s;
s = utfrrune(path, '/');
if(s == nil)
s = path;
if(!strcmp(s, "mkfile"))
return 1;
s = utfrrune(path, '.');
if(s)
path = s;
return !strcmp(path, ".c") || !strcmp(path, ".h") || !strcmp(path, ".y");
}
void
updatetext(Edit* e)
{
long l;
char* ctl;
if(openpanel(e->gtext, OWRITE|OTRUNC) < 0){
msgprint(nil, "openpanel: %s: %r\n", e->name);
return;
}
if(e->text != nil && (l=strlen(e->text)) != 0){
if(writepanel(e->gtext, e->text, l) != l){
dprint("%s: %r\n", e->name);
msgprint(nil, "writepanel: %s: %r\n", e->name);
} else
dprint("ox: writepanel %s: %ld bytes\n", e->name, l);
}
if(isfixedfont(e->name) || (e->qid.type&QTDIR) || (e->text != nil && !strncmp(e->text, "#!/", 3)))
ctl = "font T\nsel 0 0\n";
else
ctl = "sel 0 0\n";
openpanelctl(e->gtext);
panelctl(e->gtext, ctl);
closepanelctl(e->gtext);
closepanel(e->gtext);
}
static int
cmpent(void* a, void* a2)
{
Dir* s1 = a;
Dir* s2 = a2;
return strcmp(s1->name, s2->name);
}
static void
fmtdir(Edit* e, Dir* dents, int ndents)
{
int i, col;
char* s;
char* se;
int l;
int maxw, wl;
free(e->text);
e->text = nil;
if(ndents == 0){
e->text = estrdup("no files\n");
return;
}
maxw = 0;
for(i = 0; i < ndents; i++)
if (maxw < strlen(dents[i].name) + 1)
maxw = strlen(dents[i].name) + 1;
l = (maxw+1) * ndents + 2;
s = e->text = emalloc(l);
se = e->text + l;
col = 0;
for(i = 0; i < ndents; i++){
s = strecpy(s, se, dents[i].name);
wl = strlen(dents[i].name);
if(dents[i].qid.type&QTDIR){
*s++ = '/';
wl++;
}
col++;
if(col == 5 || i == ndents - 1){
*s++ = '\n';
col = 0;
} else {
while(wl < maxw){
*s++ = ' ';
wl++;
}
*s++ = '\t';
}
*s = 0;
}
}
static void
readdir(Edit* e)
{
Dir* dents;
int ndents;
int fd;
fd = open(e->name, OREAD);
if(fd < 0){
msgprint(nil, "reading %s: %r\n", e->name);
e->text = estrdup("can't read");
return;
}
ndents = dirreadall(fd, &dents);
close(fd);
if (ndents > 0)
qsort(dents, ndents, sizeof(Dir), cmpent);
if (ndents >= 0)
fmtdir(e, dents, ndents);
free(dents);
}
int
loadfile(Edit* e, char* file)
{
Dir* d;
assert(e->sts != Stemp);
d = dirstat(file);
if(d == nil)
free(e->name);
e->name = estrdup(file);
free(e->dir);
e->dir = filedir(e->name);
free(e->text);
e->text = nil;
if(d != nil)
if(d->qid.type&QTDIR)
readdir(e);
else
e->text = readfstr(e->name);
else
msgprint(nil, "%s: new file\n", file);
if(e->text == nil)
e->qid.path = 0;
dprint("ox: loaded %ld bytes into %p\n",
e->text ? strlen(e->text) : 0, e->text);
cleanedit(e, d);
free(d);
return 1;
}
static void
addedit(Edit* e)
{
int i;
for(i = 0; i < nedits; i++)
if(edits[i] == nil){
edits[i] = e;
return;
}
if((nedits%16) == 0)
edits = erealloc(edits, (nedits+16)*sizeof(Edit*));
edits[nedits++] = e;
}
static Edit*
newedit(char* file, int temp)
{
Edit* e;
char* s;
assert(!temp || file[0] == '[');
e = emalloc(sizeof(Edit));
memset(e, 0, sizeof(Edit));
e->atime = time(nil);
if(temp){
e->name = estrdup(file);
e->text = estrdup("");
e->sts = Stemp;
if(!strcmp(file, "[Cmds]"))
e->dir = estrdup(homedir);
else {
file = estrdup(file);
if(s = utfrrune(file, ']'))
*s = 0;
if(s = utfrune(file, ' '))
*s = 0;
e->dir = filedir(file+1);
free(file);
}
addedit(e);
} else
if(loadfile(e, file))
addedit(e);
else {
freeedit(e);
e = nil;
}
return e;
}
void
deledit(Edit* e)
{
int i;
dprint("deledit %p: ", e);
for(i = 0; i < nedits; i++)
if(edits[i] == e){
edits[i] = nil;
dprint("%s\n", e->name);
freeedit(e);
return;
}
abort();
}
static int
lrueditcmp(void* a1, void* a2)
{
Edit** e1 = a1;
Edit** e2 = a2;
if(*e1 == *e2)
return 0;
/* place nil entries at the start.
*/
if(*e1 == nil)
return -1;
if(*e2 == nil)
return 1;
return (*e1)->atime - (*e2)->atime;
}
static void
cleanpolicy(void)
{
int i;
long now;
Edit* e;
int nused;
qsort(edits, nedits, sizeof(edits[0]), lrueditcmp);
if(debug)
dumpedits();
nused = 0;
for(i = 0; i < nedits; i++)
if(edits[i])
nused++;
if(nused <= Dnrun)
return;
now = time(nil);
for(i = 0; i < nedits; i++)
if(e = edits[i])
if(e->sts == Stemp && cdone(e, 0, nil, 0)){
if(--nused <= Dnwins)
break;
} else
if(e->sts != Sdirty && now - e->atime > Dtime)
if(cdone(e, 0, nil, 0) && --nused <= Dnwins)
break;
}
static void
updatewhere(char* path)
{
char* p;
free(where);
where = estrdup(path);
p = utfutf(where, "/col:ox.");
assert(p);
*p = 0;
}
static void
closefile(Edit* e, int exiting)
{
dprint("ox: closefile for %s\n", e->name);
if(!exiting)
if(e->sts != Stemp && !(e->qid.type&QTDIR))
if(getsts(e->gtext, e) == Sdirty){
// BUG: should locate at /tmp
// the backup file for the text graph.
// it's named BCK.<graph name>
// Then should move it to <e->name>.unsaved
return;
}
if(e->gtext)
removepanel(e->gtext);
e->gtext = nil;
if(e->gtag)
removepanel(e->gtag);
e->gtag = nil;
if(e->gcol)
removepanel(e->gcol);
e->gcol = nil;
if (e->pid == 0){
deledit(e);
musthaveedits();
if (debug)dumpedits();
}
/* Otherwise, there is an xcmdoutthread
* using the edit. That thread will close the edit
* when safe to do so.
*/
}
Edit*
editfile(char* path, int temp)
{
Edit* e;
Panel* w;
static int nb;
if(e = findedit(path))
return e;
cleanpolicy();
e = newedit(path, temp);
if (e == nil)
return nil;
w = createpanel("ox", "col", where);
if(w == nil){
if(where != nil){
/* Maybe where was wrong for now */
free(where);
where = nil;
w = createpanel("ox", "col", where);
}
if(w == nil)
goto fail;
}
if(where == nil && w->repl != nil)
updatewhere(w->repl->path);
if(debug)
dumpedit(e);
nb++;
e->gcol = w;
e->gcolname = estrdup(strrchr(w->repl->path, '/')+1);
e->gtag = createsubpanel(e->gcol, "tag:oxedit");
if(e->gtag == nil)
goto fail;
updatetag(e, 0);
e->gtext= createsubpanel(e->gcol, "text:oxedit");
if(e->gtext == nil)
goto fail;
updatetext(e);
if(debug)
dumpedits();
if (space)
panelctl(w, "space %d", space);
closepanelctl(w);
return e;
fail:
if(w != nil)
closepanelctl(w);
closefile(e, 0);
return nil;
}
void
look(Edit* e, char* what, char* path)
{
char* p;
char* addr;
int i;
if (!what)
return;
dprint("%s: look %s\n", e->name, what);
p = cleanpath(what, e->dir);
addr = nil;
/* 1. Try to open (and set address)
*/
if(access(p, AEXIST) < 0){
addr = utfrune(p, ':');
if (addr != nil){
if (what[0] == ':')
goto Search;
*addr++ = 0;
if (access(p, AEXIST) < 0){
free(p);
p = addr = nil;
}
} else {
free(p);
p = nil;
}
}
if (p != nil){
evhistory("ox", "look", p);
if (mustplumb(p))
goto Plumb;
e = editfile(p, 0);
if (addr){
*--addr = ':';
openpanelctl(e->gtext);
panelctl(e->gtext, "search %s\n", addr);
closepanelctl(e->gtext);
}
openpanelctl(e->gcol);
panelctl(e->gcol, "show\nnomin\n");
closepanelctl(e->gcol);
free(p);
return;
}
/* 2. Try to plumb
*/
Plumb:
if (plumblook(e->dir, what)){
free(p);
return;
}
Search:
/* 3. Try searching for the string
* BUG: should search in path, not in e->gtext.
*/
USED(path);
if (what != nil){
openpanelctl(e->gtext);
if (what[0] == ':' && what[1] == '/'){
i = addr2ln(e, what+2);
panelctl(e->gtext, "search :%d\n", i);
} else
panelctl(e->gtext, "search %s\n", what);
closepanelctl(e->gtext);
}
free(p);
}
static void
interrupt(int pid)
{
char fn[40];
seprint(fn, fn+sizeof(fn), "/proc/%d/notepg", pid);
writefstr(fn, "interrupt");
}
static void
wreplctl(char* path, void* buf, long l)
{
char* f;
f = smprint("%s/ctl", path);
writef(f, buf, l);
free(f);
}
static void
textevent(Edit* e, char* path, char* ev, char* arg, int istag)
{
char* narg;
char* s;
if(!istag && !strcmp(ev, "interrupt")){
e->atime = time(nil);
if(e->pid != 0)
interrupt(e->pid);
} else if(!istag && !strcmp(ev, "dirty")){
e->atime = time(nil);
if(e->sts != Stemp)
e->sts = Sdirty;
} else if(!strcmp(ev, "look") && arg != nil){
e->atime = time(nil);
look(e, arg+12, path);
} else if(!strcmp(ev, "args") && arg != nil){
e->atime = time(nil);
wreplctl(path, "cut\npaste\n", 3+1+5+1);
s = readfstr("/dev/snarf");
narg = smprint("%s %s", arg + 12, s ? s : "");
free(s);
run(e, narg, istag, path);
free(narg);
} else if(!strcmp(ev, "exec") && arg != nil){
e->atime = time(nil);
s = smprint("cd %s ; %s", e->dir, arg+12);
evhistory("ox", "exec", s);
free(s);
run(e, arg + 12, istag, path);
} else if (!istag && !strcmp(ev, "exit")){
closefile(e, 1);
}
}
static Panel*
openxpanel(char* dir)
{
Panel* g;
g = emalloc(sizeof(Panel));
memset(g, 0, sizeof(Panel));
g->nrepl = 1;
g->repl = emalloc(sizeof(Repl));
memset(g->repl, 0, sizeof(Repl));
g->repl->path = estrdup(dir);
return g;
}
static void
closexpanel(Panel* g)
{
free(g->repl->path);
free(g->repl);
free(g);
}
void
externrunevent(char* path, char* ev, char* arg)
{
char* narg;
Panel* gtext;
char* s;
narg = nil;
if(!strcmp(ev, "args") && arg){
gtext = openxpanel(path);
wreplctl(path, "cut\npaste\n", 3+1+5+1);
s = readfstr("/dev/snarf");
narg = smprint("%s %s", arg, s ? s : "");
arg = narg;
free(s);
} else if(!strcmp(ev, "exec"))
gtext = openxpanel(path);
else
return;
dprint("editrun %s\n", arg);
if(!editrun(gtext, "/tmp", arg, path))
xcmd(nil, "/tmp", arg, nil, nil, nil);
free(narg);
closexpanel(gtext);
}
static int
cmdonsel(Oev* ev)
{
if(!strcmp(ev->ev, "args"))
return 1;
if(!strcmp(ev->ev, "exec") && ev->arg)
if(strchr(":|<>", ev->arg[12]) || !strncmp(ev->arg+12, "E ", 2))
return 1;
return 0;
}
static void
event(Oev* ev)
{
int i;
Edit* e;
char* p;
char* s;
if(cmdonsel(ev)){
free(ev->path);
ev->path = nil;
ev->path = readfstr("/dev/sel");
dprint("edit event for %s\n", ev->path);
if(ev->path == nil){
msgprint(nil, "No selection. /dev/sel: %r\n");
return;
}
}
if(!strcmp(ev->ev, "path")){
s = smprint("/devs%s", ev->arg);
updatewhere(s);
free(s);
return;
}
e = nil;
p = nil;
for(i = 0; i < nedits; i++)
if(edits[i] && edits[i]->gcolname)
if(p = strstr(ev->path, edits[i]->gcolname)){
e = edits[i];
break;
}
if(e != nil)
textevent(e, ev->path, ev->ev, ev->arg, strstr(p, "/tag:") != nil);
else {
dumpedits();
dprint("event w/o edit %s %s (path %s)\n", ev->ev, ev->arg, ev->path);
externrunevent(ev->path, ev->ev, ev->arg ? ev->arg + 12 : nil);
}
}
static void
usage(void)
{
fprint(2, "usage: %s [-d] [-s spacenb] [-r machine] [file]\n", argv0);
sysfatal("usage");
}
void
threadmain(int argc, char* argv[])
{
Channel* ec;
Oev e;
extern int omerodebug;
char* remote;
remote = nil;
ARGBEGIN{
case 's':
space = atoi(EARGF(usage()));
break;
case 'd':
debug++;
break;
case 'r':
remote = EARGF(usage());
break;
default:
usage();
}ARGEND;
if (argc > 1)
usage();
if (debug> 1)
omerodebug++;
if (remote != nil){
fprint(2, "remote ox: space %d machine %s omero %s\n", space, remote, getenv("omero"));
threadexits(nil);
}
inittagcmds();
homedir = getenv("home");
cleanname(homedir);
plumbinit();
cmdinit();
ec = omeroeventchan(nil);
editfile(homedir, 0);
while(recv(ec, &e) != -1){
dprint("user: %s %s %s\n", e.path, e.ev, e.arg);
event(&e);
clearoev(&e);
}
}
|