summaryrefslogtreecommitdiff
path: root/acme/wiki/src
diff options
context:
space:
mode:
Diffstat (limited to 'acme/wiki/src')
-rw-r--r--acme/wiki/src/awiki.h114
-rw-r--r--acme/wiki/src/main.c60
-rw-r--r--acme/wiki/src/mkfile14
-rw-r--r--acme/wiki/src/util.c89
-rw-r--r--acme/wiki/src/wiki.c602
-rw-r--r--acme/wiki/src/win.c341
6 files changed, 1220 insertions, 0 deletions
diff --git a/acme/wiki/src/awiki.h b/acme/wiki/src/awiki.h
new file mode 100644
index 000000000..19fd61738
--- /dev/null
+++ b/acme/wiki/src/awiki.h
@@ -0,0 +1,114 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+
+/* acme */
+typedef struct Event Event;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int warned;
+ int id;
+ int open;
+ Channel *cevent; /* chan(Event*) */
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winisdirty(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern char* readfile(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+
+typedef struct Treq Treq;
+typedef struct Wiki Wiki;
+
+struct Treq {
+ char *title;
+ Channel *c; /* chan(int) */
+};
+
+struct Wiki {
+ QLock;
+ int isnew;
+ int special;
+ char *arg;
+ char *addr;
+ int n;
+ int dead;
+ Window *win;
+ ulong time;
+ int linked;
+ Wiki *next;
+ Wiki *prev;
+};
+
+extern int debug;
+extern int mapfd;
+extern char *email;
+extern char *dir;
+
+void wikinew(char*);
+int wikiopen(char*, char*);
+int wikiput(Wiki*);
+void wikiget(Wiki*);
+int wikidiff(Wiki*);
+
diff --git a/acme/wiki/src/main.c b/acme/wiki/src/main.c
new file mode 100644
index 000000000..f76cafff9
--- /dev/null
+++ b/acme/wiki/src/main.c
@@ -0,0 +1,60 @@
+#include "awiki.h"
+
+int debug;
+int mapfd;
+char *email;
+char *dir;
+
+void
+usage(void)
+{
+ fprint(2, "usage: Wiki [-e email] [dir]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *s;
+ Dir *d;
+
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'D':
+ debug++;
+ break;
+ case 'e':
+ email = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+ if(argc == 1)
+ dir = argv[0];
+ else
+ dir = "/mnt/wiki";
+
+ if(chdir(dir) < 0){
+ fprint(2, "chdir(%s) fails: %r\n", dir);
+ threadexitsall(nil);
+ }
+
+ if((mapfd = open("map", ORDWR)) < 0){
+ fprint(2, "open(map): %r\n");
+ threadexitsall(nil);
+ }
+
+ if((d = dirstat("1")) == nil){
+ fprint(2, "dirstat(%s/1) fails: %r\n", dir);
+ threadexitsall(nil);
+ }
+ s = emalloc(strlen(d->name)+2);
+ strcpy(s, d->name);
+ strcat(s, "/");
+ wikiopen(s, nil);
+ threadexits(nil);
+}
diff --git a/acme/wiki/src/mkfile b/acme/wiki/src/mkfile
new file mode 100644
index 000000000..89431e702
--- /dev/null
+++ b/acme/wiki/src/mkfile
@@ -0,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=Wiki
+
+OFILES=\
+ main.$O\
+ util.$O\
+ wiki.$O\
+ win.$O\
+
+HFILES=awiki.h
+BIN=../../bin/$objtype
+
+</sys/src/cmd/mkone
diff --git a/acme/wiki/src/util.c b/acme/wiki/src/util.c
new file mode 100644
index 000000000..832d97321
--- /dev/null
+++ b/acme/wiki/src/util.c
@@ -0,0 +1,89 @@
+#include "awiki.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ fprint(2, "Wiki: ");
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
+ va_end(arg);
+ write(2, buf, n);
+ write(2, "\n", 1);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+ char buf[256];
+
+ va_start(arg, fmt);
+ n = vseprint(buf, buf+sizeof buf, fmt, arg) - buf;
+ va_end(arg);
+ if(write(fd, buf, n) != n)
+ error("control file write(%s) error: %r", buf);
+}
diff --git a/acme/wiki/src/wiki.c b/acme/wiki/src/wiki.c
new file mode 100644
index 000000000..ca53c3a09
--- /dev/null
+++ b/acme/wiki/src/wiki.c
@@ -0,0 +1,602 @@
+#include "awiki.h"
+
+Wiki *wlist;
+
+void
+link(Wiki *w)
+{
+ if(w->linked)
+ return;
+ w->linked = 1;
+ w->prev = nil;
+ w->next = wlist;
+ if(wlist)
+ wlist->prev = w;
+ wlist = w;
+}
+
+void
+unlink(Wiki *w)
+{
+ if(!w->linked)
+ return;
+ w->linked = 0;
+
+ if(w->next)
+ w->next->prev = w->prev;
+ if(w->prev)
+ w->prev->next = w->next;
+ else
+ wlist = w->next;
+
+ w->next = nil;
+ w->prev = nil;
+}
+
+void
+wikiname(Window *w, char *name)
+{
+ char *p, *q;
+
+ p = emalloc(strlen(dir)+1+strlen(name)+1+1);
+ strcpy(p, dir);
+ strcat(p, "/");
+ strcat(p, name);
+ for(q=p; *q; q++)
+ if(*q==' ')
+ *q = '_';
+ winname(w, p);
+ free(p);
+}
+
+int
+wikiput(Wiki *w)
+{
+ int fd, n;
+ char buf[1024], *p;
+ Biobuf *b;
+
+ if((fd = open("new", ORDWR)) < 0){
+ fprint(2, "Wiki: cannot open raw: %r\n");
+ return -1;
+ }
+
+ winopenbody(w->win, OREAD);
+ b = w->win->body;
+ if((p = Brdline(b, '\n'))==nil){
+ Short:
+ winclosebody(w->win);
+ fprint(2, "Wiki: no data\n");
+ close(fd);
+ return -1;
+ }
+ write(fd, p, Blinelen(b));
+
+ snprint(buf, sizeof buf, "D%lud\n", w->time);
+ if(email)
+ snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "A%s\n", email);
+
+ if(Bgetc(b) == '#'){
+ p = Brdline(b, '\n');
+ if(p == nil)
+ goto Short;
+ snprint(buf+strlen(buf), sizeof(buf)-strlen(buf), "C%s\n", p);
+ }
+ write(fd, buf, strlen(buf));
+ write(fd, "\n\n", 2);
+
+ while((n = Bread(b, buf, sizeof buf)) > 0)
+ write(fd, buf, n);
+ winclosebody(w->win);
+
+ werrstr("");
+ if((n=write(fd, "", 0)) != 0){
+ fprint(2, "Wiki commit %lud %d %d: %r\n", w->time, fd, n);
+ close(fd);
+ return -1;
+ }
+ seek(fd, 0, 0);
+ if((n = read(fd, buf, 300)) < 0){
+ fprint(2, "Wiki readback: %r\n");
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ buf[n] = '\0';
+ sprint(buf, "%s/", buf);
+ free(w->arg);
+ w->arg = estrdup(buf);
+ w->isnew = 0;
+ wikiget(w);
+ wikiname(w->win, w->arg);
+ return n;
+}
+
+void
+wikiget(Wiki *w)
+{
+ char *p;
+ int fd, normal;
+ Biobuf *bin;
+
+ fprint(w->win->ctl, "dirty\n");
+
+ p = emalloc(strlen(w->arg)+8+1);
+ strcpy(p, w->arg);
+ normal = 1;
+ if(p[strlen(p)-1] == '/'){
+ normal = 0;
+ strcat(p, "current");
+ }else if(strlen(p)>8 && strcmp(p+strlen(p)-8, "/current")==0){
+ normal = 0;
+ w->arg[strlen(w->arg)-7] = '\0';
+ }
+
+ if((fd = open(p, OREAD)) < 0){
+ fprint(2, "Wiki: cannot read %s: %r\n", p);
+ winclean(w->win);
+ return;
+ }
+ free(p);
+
+ winopenbody(w->win, OWRITE);
+ bin = emalloc(sizeof(*bin));
+ Binit(bin, fd, OREAD);
+
+ p = nil;
+ if(!normal){
+ if((p = Brdline(bin, '\n')) == nil){
+ fprint(2, "Wiki: cannot read title: %r\n");
+ winclean(w->win);
+ close(fd);
+ free(bin);
+ return;
+ }
+ p[Blinelen(bin)-1] = '\0';
+ }
+ /* clear window */
+ if(w->win->data < 0)
+ w->win->data = winopenfile(w->win, "data");
+ if(winsetaddr(w->win, ",", 0))
+ write(w->win->data, "", 0);
+
+ if(!normal)
+ Bprint(w->win->body, "%s\n\n", p);
+
+ while(p = Brdline(bin, '\n')){
+ p[Blinelen(bin)-1] = '\0';
+ if(normal)
+ Bprint(w->win->body, "%s\n", p);
+ else{
+ if(p[0]=='D')
+ w->time = strtoul(p+1, 0, 10);
+ else if(p[0]=='#')
+ Bprint(w->win->body, "%s\n", p+1);
+ }
+ }
+ winclean(w->win);
+ free(bin);
+ close(fd);
+}
+
+static int
+iscmd(char *s, char *cmd)
+{
+ int len;
+
+ len = strlen(cmd);
+ return strncmp(s, cmd, len)==0 && (s[len]=='\0' || s[len]==' ' || s[len]=='\t' || s[len]=='\n');
+}
+
+static char*
+skip(char *s, char *cmd)
+{
+ s += strlen(cmd);
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ return s;
+}
+
+int
+wikiload(Wiki *w, char *arg)
+{
+ char *p, *q, *path, *addr;
+ int rv;
+
+ p = nil;
+ if(arg[0] == '/')
+ path = arg;
+ else{
+ p = emalloc(strlen(w->arg)+1+strlen(arg)+1);
+ strcpy(p, w->arg);
+ if(q = strrchr(p, '/')){
+ ++q;
+ *q = '\0';
+ }else
+ *p = '\0';
+ strcat(p, arg);
+ cleanname(p);
+ path = p;
+ }
+ if(addr=strchr(path, ':'))
+ *addr++ = '\0';
+
+ rv = wikiopen(path, addr)==0;
+ free(p);
+ if(rv)
+ return 1;
+ return wikiopen(arg, 0)==0;
+}
+
+/* return 1 if handled, 0 otherwise */
+int
+wikicmd(Wiki *w, char *s)
+{
+ char *p;
+ s = skip(s, "");
+
+ if(iscmd(s, "Del")){
+ if(windel(w->win, 0))
+ w->dead = 1;
+ return 1;
+ }
+ if(iscmd(s, "New")){
+ wikinew(skip(s, "New"));
+ return 1;
+ }
+ if(iscmd(s, "History"))
+ return wikiload(w, "history.txt");
+ if(iscmd(s, "Diff"))
+ return wikidiff(w);
+ if(iscmd(s, "Get")){
+ if(winisdirty(w->win) && !w->win->warned){
+ w->win->warned = 1;
+ fprint(2, "%s/%s modified\n", dir, w->arg);
+ }else{
+ w->win->warned = 0;
+ wikiget(w);
+ }
+ return 1;
+ }
+ if(iscmd(s, "Put")){
+ if((p=strchr(w->arg, '/')) && p[1]!='\0')
+ fprint(2, "%s/%s is read-only\n", dir, w->arg);
+ else
+ wikiput(w);
+ return 1;
+ }
+ return 0;
+}
+
+/* need to expand selection more than default word */
+static long
+eval(Window *w, char *s, ...)
+{
+ char buf[64];
+ va_list arg;
+
+ va_start(arg, s);
+ vsnprint(buf, sizeof buf, s, arg);
+ va_end(arg);
+
+ if(winsetaddr(w, buf, 1)==0)
+ return -1;
+
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ return strtol(buf, 0, 10);
+}
+
+static int
+getdot(Window *w, long *q0, long *q1)
+{
+ char buf[24];
+
+ ctlprint(w->ctl, "addr=dot\n");
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ *q0 = atoi(buf);
+ *q1 = atoi(buf+12);
+ return 0;
+}
+
+static Event*
+expand(Window *w, Event *e, Event *eacme)
+{
+ long q0, q1, x;
+
+ if(getdot(w, &q0, &q1)==0 && q0 <= e->q0 && e->q0 <= q1){
+ e->q0 = q0;
+ e->q1 = q1;
+ return e;
+ }
+
+ q0 = eval(w, "#%lud-/\\[/", e->q0);
+ if(q0 < 0)
+ return eacme;
+ if(eval(w, "#%lud+/\\]/", q0) < e->q0) /* [ closes before us */
+ return eacme;
+ q1 = eval(w, "#%lud+/\\]/", e->q1);
+ if(q1 < 0)
+ return eacme;
+ if((x=eval(w, "#%lud-/\\[/", q1))==-1 || x > e->q1) /* ] opens after us */
+ return eacme;
+ e->q0 = q0+1;
+ e->q1 = q1;
+ return e;
+}
+
+void
+acmeevent(Wiki *wiki, Event *e)
+{
+ Event *ea, *e2, *eq;
+ Window *w;
+ char *s, *t, *buf;
+ int na;
+
+ w = wiki->win;
+ switch(e->c1){ /* origin of action */
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'E': /* write to body or tag; can't affect us */
+ break;
+
+ case 'K': /* type away; we don't care */
+ if(e->c2 == 'I' || e->c2 == 'D')
+ w->warned = 0;
+ break;
+
+ case 'M': /* mouse event */
+ switch(e->c2){ /* type of action */
+ case 'x': /* mouse: button 2 in tag */
+ case 'X': /* mouse: button 2 in body */
+ ea = nil;
+ //e2 = nil;
+ s = e->b;
+ if(e->flag & 2){ /* null string with non-null expansion */
+ e2 = recvp(w->cevent);
+ if(e->nb==0)
+ s = e2->b;
+ }
+ if(e->flag & 8){ /* chorded argument */
+ ea = recvp(w->cevent); /* argument */
+ na = ea->nb;
+ recvp(w->cevent); /* ignore origin */
+ }else
+ na = 0;
+
+ /* append chorded arguments */
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ // DPRINT(2, "exec: %s\n", s);
+ if(!wikicmd(wiki, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l': /* mouse: button 3 in tag */
+ case 'L': /* mouse: button 3 in body */
+ //buf = nil;
+ eq = e;
+ if(e->flag & 2){ /* we do our own expansion for loads */
+ e2 = recvp(w->cevent);
+ eq = expand(w, eq, e2);
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ if(!wikiload(wiki, s))
+ winwriteevent(w, e);
+ break;
+
+ case 'i': /* mouse: text inserted in tag */
+ case 'd': /* mouse: text deleted from tag */
+ break;
+
+ case 'I': /* mouse: text inserted in body */
+ case 'D': /* mouse: text deleted from body */
+ w->warned = 0;
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+}
+
+void
+wikithread(void *v)
+{
+ char tmp[40];
+ Event *e;
+ Wiki *w;
+
+ w = v;
+
+ if(w->isnew){
+ sprint(tmp, "+new+%d", w->isnew);
+ wikiname(w->win, tmp);
+ if(w->arg){
+ winopenbody(w->win, OWRITE);
+ Bprint(w->win->body, "%s\n\n", w->arg);
+ }
+ winclean(w->win);
+ }else if(!w->special){
+ wikiget(w);
+ wikiname(w->win, w->arg);
+ if(w->addr)
+ winselect(w->win, w->addr, 1);
+ }
+ fprint(w->win->ctl, "menu\n");
+ wintagwrite(w->win, "Get History Diff New", 4+8+4+4);
+ winclean(w->win);
+
+ while(!w->dead && (e = recvp(w->win->cevent)))
+ acmeevent(w, e);
+
+ windormant(w->win);
+ unlink(w);
+ free(w->win);
+ free(w->arg);
+ free(w);
+ threadexits(nil);
+}
+
+int
+wikiopen(char *arg, char *addr)
+{
+ Dir *d;
+ char *p;
+ Wiki *w;
+
+/*
+ if(arg==nil){
+ if(write(mapfd, title, strlen(title)) < 0
+ || seek(mapfd, 0, 0) < 0 || (n=read(mapfd, tmp, sizeof(tmp)-2)) < 0){
+ fprint(2, "Wiki: no page '%s' found: %r\n", title);
+ return -1;
+ }
+ if(tmp[n-1] == '\n')
+ tmp[--n] = '\0';
+ tmp[n++] = '/';
+ tmp[n] = '\0';
+ arg = tmp;
+ }
+*/
+
+ /* replace embedded '\n' in links by ' ' */
+ for(p=arg; *p; p++)
+ if(*p=='\n')
+ *p = ' ';
+
+ if(strncmp(arg, dir, strlen(dir))==0 && arg[strlen(dir)]=='/' && arg[strlen(dir)+1])
+ arg += strlen(dir)+1;
+ else if(arg[0] == '/')
+ return -1;
+
+ if((d = dirstat(arg)) == nil)
+ return -1;
+
+ if((d->mode&DMDIR) && arg[strlen(arg)-1] != '/'){
+ p = emalloc(strlen(arg)+2);
+ strcpy(p, arg);
+ strcat(p, "/");
+ arg = p;
+ }else if(!(d->mode&DMDIR) && arg[strlen(arg)-1]=='/'){
+ arg = estrdup(arg);
+ arg[strlen(arg)-1] = '\0';
+ }else
+ arg = estrdup(arg);
+ free(d);
+
+ /* rewrite /current into / */
+ if(strlen(arg) > 8 && strcmp(arg+strlen(arg)-8, "/current")==0)
+ arg[strlen(arg)-8+1] = '\0';
+
+ /* look for window already open */
+ for(w=wlist; w; w=w->next){
+ if(strcmp(w->arg, arg)==0){
+ ctlprint(w->win->ctl, "show\n");
+ return 0;
+ }
+ }
+
+ w = emalloc(sizeof *w);
+ w->arg = arg;
+ w->addr = addr;
+ w->win = newwindow();
+ link(w);
+
+ proccreate(wineventproc, w->win, STACK);
+ threadcreate(wikithread, w, STACK);
+ return 0;
+}
+
+void
+wikinew(char *arg)
+{
+ static int n;
+ Wiki *w;
+
+ w = emalloc(sizeof *w);
+ if(arg)
+ arg = estrdup(arg);
+ w->arg = arg;
+ w->win = newwindow();
+ w->isnew = ++n;
+ proccreate(wineventproc, w->win, STACK);
+ threadcreate(wikithread, w, STACK);
+}
+
+typedef struct Diffarg Diffarg;
+struct Diffarg {
+ Wiki *w;
+ char *dir;
+};
+
+void
+execdiff(void *v)
+{
+ char buf[64];
+ Diffarg *a;
+
+ a = v;
+
+ rfork(RFFDG);
+ close(0);
+ open("/dev/null", OREAD);
+ sprint(buf, "/mnt/wsys/%d/body", a->w->win->id);
+ close(1);
+ open(buf, OWRITE);
+ close(2);
+ open(buf, OWRITE);
+ sprint(buf, "/mnt/wsys/%d", a->w->win->id);
+ bind(buf, "/dev", MBEFORE);
+
+ procexecl(nil, "/acme/wiki/wiki.diff", "wiki.diff", a->dir, nil);
+}
+
+int
+wikidiff(Wiki *w)
+{
+ Diffarg *d;
+ char *p, *q, *r;
+ Wiki *nw;
+
+ p = emalloc(strlen(w->arg)+10);
+ strcpy(p, w->arg);
+ if(q = strchr(p, '/'))
+ *q = '\0';
+ r = estrdup(p);
+ strcat(p, "/+Diff");
+
+ nw = emalloc(sizeof *w);
+ nw->arg = p;
+ nw->win = newwindow();
+ nw->special = 1;
+
+ d = emalloc(sizeof(*d));
+ d->w = nw;
+ d->dir = r;
+ wikiname(nw->win, p);
+ proccreate(wineventproc, nw->win, STACK);
+ proccreate(execdiff, d, STACK);
+ threadcreate(wikithread, nw, STACK);
+ return 1;
+}
+
diff --git a/acme/wiki/src/win.c b/acme/wiki/src/win.c
new file mode 100644
index 000000000..3eec1e9bd
--- /dev/null
+++ b/acme/wiki/src/win.c
@@ -0,0 +1,341 @@
+#include "awiki.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ if(w->cevent == nil)
+ error("cevent is nil: %r");
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ threadsetname("wineventproc");
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winisdirty(Window *w)
+{
+ char m;
+
+ if (seek(w->ctl, 4*(11+1) + 10, 0) < 0)
+ error("control file seek error: %r");
+
+ if(read(w->ctl, &m, 1) != 1)
+ error("control file read error: %r");
+
+ if (m == '0')
+ return 0;
+ else if (m == '1')
+ return 1;
+ else
+ error("can't parse ismodified field: %c", m);
+ return 1; // better safe than sorry
+
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}