Commit Diff


commit - 833216fef8b946895956737d205bcad7031bf06f
commit + 4a3fb87264f8bc03fc62f00ef335056f30d18023
blob - 78214cad951900fdb70f78b7a97d3e4157866760
blob + 7449dbd29e0257cc8bd813f582efd3cb8a8acefc
--- man/man4/acme.4
+++ man/man4/acme.4
@@ -73,7 +73,7 @@ was run.
 The window is created if necessary, but not until text is actually written.
 .TP
 .B consctl
-Is an empty unwritable file present only for compatibility; there is no way
+is an empty unwritable file present only for compatibility; there is no way
 to turn off `echo', for example, under
 .IR acme .
 .TP
@@ -94,9 +94,33 @@ file.
 .B label
 is an empty file, writable without effect, present only for compatibility with
 .BR rio .
+.TP
+.B log
+reports a log of window operations since the opening of the
+.B log
+file.
+Each line describes a single operation using three fields separated by single spaces:
+the decimal window ID, the operation, and the window name.
+Reading from
+.B log
+blocks until there is an operation to report, so reading the file
+can be used to monitor editor activity and react to changes.
+The reported operations are
+.L new
+(window creation),
+.L zerox
+(window creation via zerox),
+.LR get ,
+.LR put ,
+and
+.LR del
+(window deletion).
+The window name can be the empty string; in particular it is empty in
+.L new
+log entries corresponding to windows created by external programs.
 .TP
 .B new
-A directory analogous to the numbered directories
+is a directory analogous to the numbered directories
 .RI ( q.v. ).
 Accessing any
 file in
blob - 1005bae98162cae884817b376d0c1e5ee3c01255
blob + c45bc28d4b694fe8582c02291c5cbaf86ed7ef17
--- src/cmd/acme/acme.c
+++ src/cmd/acme/acme.c
@@ -303,6 +303,7 @@ readfile(Column *c, char *s)
 	winresize(w, w->r, FALSE, TRUE);
 	textscrdraw(&w->body);
 	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
+	xfidlog(w, "new");
 }
 
 char *ignotes[] = {
@@ -866,6 +867,7 @@ newwindowthread(void *v)
 		recvp(cnewwindow);
 		w = makenewwindow(nil);
 		winsettag(w);
+		xfidlog(w, "new");
 		sendp(cnewwindow, w);
 	}
 }
blob - 5c156eb6a0164df436a10add33e6f90a329942bf
blob + df1a6422a7ff9f270551f8f31eef6b75edc34904
--- src/cmd/acme/dat.h
+++ src/cmd/acme/dat.h
@@ -8,6 +8,7 @@ enum
 	Qeditout,
 	Qindex,
 	Qlabel,
+	Qlog,
 	Qnew,
 
 	QWaddr,
@@ -391,6 +392,7 @@ struct Fid
 	Mntdir	*mntdir;
 	int		nrpart;
 	uchar	rpart[UTFmax];
+	vlong	logoff;	// for putlog
 };
 
 
@@ -403,7 +405,6 @@ struct Xfid
 	Fid	*f;
 	uchar	*buf;
 	int	flushed;
-
 };
 
 void		xfidctl(void *);
@@ -418,6 +419,10 @@ void		xfideventwrite(Xfid*, Window*);
 void		xfidindexread(Xfid*);
 void		xfidutfread(Xfid*, Text*, uint, int);
 int		xfidruneread(Xfid*, Text*, uint, uint);
+void		xfidlogopen(Xfid*);
+void		xfidlogread(Xfid*);
+void		xfidlogflush(Xfid*);
+void		xfidlog(Window*, char*);
 
 struct Reffont
 {
blob - 6f8bf3f6be0fbe84fc3f90b13a156ed27ef803b7
blob + 6fe423a6eaad51e2c162b90f9dc30d46bd354ce1
--- src/cmd/acme/exec.c
+++ src/cmd/acme/exec.c
@@ -347,6 +347,7 @@ void
 newcol(Text *et, Text *_0, Text *_1, int _2, int _3, Rune *_4, int _5)
 {
 	Column *c;
+	Window *w;
 
 	USED(_0);
 	USED(_1);
@@ -356,8 +357,11 @@ newcol(Text *et, Text *_0, Text *_1, int _2, int _3, R
 	USED(_5);
 
 	c = rowadd(et->row, nil, -1);
-	if(c)
-		winsettag(coladd(c, nil, nil, -1));
+	if(c) {
+		w = coladd(c, nil, nil, -1);
+		winsettag(w);
+		xfidlog(w, "new");
+	}
 }
 
 void
@@ -562,6 +566,7 @@ zeroxx(Text *et, Text *t, Text *_1, int _2, int _3, Ru
 		nw = coladd(t->w->col, nil, t->w, -1);
 		/* ugly: fix locks so w->unlock works */
 		winlock1(nw, t->w->owner);
+		xfidlog(nw, "zerox");
 	}
 	if(locked)
 		winunlock(t->w);
@@ -627,6 +632,7 @@ get(Text *et, Text *t, Text *argt, int flag1, int _0, 
 		textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
 		textscrdraw(u);
 	}
+	xfidlog(w, "get");
 }
 
 void
@@ -782,6 +788,7 @@ put(Text *et, Text *_0, Text *argt, int _1, int _2, Ru
 	}
 	namer = bytetorune(name, &nname);
 	putfile(f, 0, f->b.nc, namer, nname);
+	xfidlog(w, "put");
 	free(name);
 }
 
blob - e285c908b5ec13c7b26e3fdc40b4898ad886ee4b
blob + 549213d73d54fe443951834dd5171ff59e33764b
--- src/cmd/acme/fsys.c
+++ src/cmd/acme/fsys.c
@@ -71,6 +71,7 @@ Dirtab dirtab[]=
 	{ "editout",	QTFILE,	Qeditout,	0200 },
 	{ "index",		QTFILE,	Qindex,	0400 },
 	{ "label",		QTFILE,	Qlabel,	0600 },
+	{ "log",		QTFILE,	Qlog,	0400 },
 	{ "new",		QTDIR,	Qnew,	0500|DMDIR },
 	{ nil, }
 };
blob - 8d1361101ce411fb1c0b91bfe688b8d9114f1f20
blob + 3c5765a8c5afbd1c6ba773f575211a84eac06423
--- src/cmd/acme/look.c
+++ src/cmd/acme/look.c
@@ -298,6 +298,7 @@ plumbshow(Plumbmsg *m)
 	winsettag(w);
 	textscrdraw(&w->body);
 	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
+	xfidlog(w, "new");
 }
 
 int
@@ -770,6 +771,7 @@ openfile(Text *t, Expand *e)
 			w->autoindent = ow->autoindent;
 		}else
 			w->autoindent = globalautoindent;
+		xfidlog(w, "new");
 	}
 	if(e->a1 == e->a0)
 		eval = FALSE;
@@ -803,6 +805,7 @@ new(Text *et, Text *t, Text *argt, int flag1, int flag
 	int na, nf;
 	Expand e;
 	Runestr rs;
+	Window *w;
 
 	getarg(argt, FALSE, TRUE, &a, &na);
 	if(a){
@@ -814,8 +817,11 @@ new(Text *et, Text *t, Text *argt, int flag1, int flag
 	for(ndone=0; ; ndone++){
 		a = findbl(arg, narg, &na);
 		if(a == arg){
-			if(ndone==0 && et->col!=nil)
-				winsettag(coladd(et->col, nil, nil, -1));
+			if(ndone==0 && et->col!=nil) {
+				w = coladd(et->col, nil, nil, -1);
+				winsettag(w);
+				xfidlog(w, "new");
+			}
 			break;
 		}
 		nf = narg-na;
blob - /dev/null
blob + ebcd4bfd856e6e1063135a4c21e0117fe6cb86e6 (mode 644)
--- /dev/null
+++ src/cmd/acme/logf.c
@@ -0,0 +1,197 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+// State for global log file.
+typedef struct Log Log;
+struct Log
+{
+	QLock lk;
+	Rendez r;
+
+	vlong start; // msg[0] corresponds to 'start' in the global sequence of events
+	
+	// queued events (nev=entries in ev, mev=capacity of p)
+	char **ev;
+	int nev;
+	int mev;
+
+	// open acme/put files that need to read events
+	Fid **f;
+	int nf;
+	int mf;
+	
+	// active (blocked) reads waiting for events
+	Xfid **read;
+	int nread;
+	int mread;
+};
+
+static Log eventlog;
+
+void
+xfidlogopen(Xfid *x)
+{
+	qlock(&eventlog.lk);
+	if(eventlog.nf >= eventlog.mf) {	
+		eventlog.mf = eventlog.mf*2;
+		if(eventlog.mf == 0)
+			eventlog.mf = 8;
+		eventlog.f = erealloc(eventlog.f, eventlog.mf*sizeof eventlog.f[0]);
+	}
+	eventlog.f[eventlog.nf++] = x->f;
+	x->f->logoff = eventlog.start + eventlog.nev;
+
+	qunlock(&eventlog.lk);
+}
+
+void
+xfidlogclose(Xfid *x)
+{
+	int i;
+
+	qlock(&eventlog.lk);
+	for(i=0; i<eventlog.nf; i++) {
+		if(eventlog.f[i] == x->f) {
+			eventlog.f[i] = eventlog.f[--eventlog.nf];
+			break;
+		}
+	}
+	qunlock(&eventlog.lk);
+}
+
+void
+xfidlogread(Xfid *x)
+{
+	char *p;
+	int i;
+	Fcall fc;
+
+	qlock(&eventlog.lk);
+	if(eventlog.nread >= eventlog.mread) {	
+		eventlog.mread = eventlog.mread*2;
+		if(eventlog.mread == 0)
+			eventlog.mread = 8;
+		eventlog.read = erealloc(eventlog.read, eventlog.mread*sizeof eventlog.read[0]);
+	}
+	eventlog.read[eventlog.nread++] = x;
+	
+	if(eventlog.r.l == nil)
+		eventlog.r.l = &eventlog.lk;
+	x->flushed = FALSE;
+	while(x->f->logoff >= eventlog.start+eventlog.nev && !x->flushed)
+		rsleep(&eventlog.r);
+		
+	for(i=0; i<eventlog.nread; i++) {
+		if(eventlog.read[i] == x) {
+			eventlog.read[i] = eventlog.read[--eventlog.nread];
+			break;
+		}
+	}
+
+	if(x->flushed) {
+		qunlock(&eventlog.lk);
+		respond(x, &fc, "read cancelled");
+		return;
+	}
+
+	i = x->f->logoff - eventlog.start;
+	p = estrdup(eventlog.ev[i]);
+	x->f->logoff++;
+	qunlock(&eventlog.lk);
+
+	fc.data = p;
+	fc.count = strlen(p);
+	respond(x, &fc, nil);
+	free(p);	
+}
+
+void
+xfidlogflush(Xfid *x)
+{
+	int i;
+	Xfid *rx;
+
+	qlock(&eventlog.lk);
+	for(i=0; i<eventlog.nread; i++) {
+		rx = eventlog.read[i];
+		if(rx->fcall.tag == x->fcall.oldtag)
+			rx->flushed = TRUE;
+	}
+	qunlock(&eventlog.lk);
+}
+
+/*
+ * add a log entry for op on w.
+ * expected calls:
+ *
+ * op == "new" for each new window
+ *	- caller of coladd or makenewwindow responsible for calling
+ *		xfidlog after setting window name
+ *	- exception: zerox
+ *
+ * op == "zerox" for new window created via zerox
+ *	- called from zeroxx
+ *
+ * op == "get" for Get executed on window
+ *	- called from get
+ *
+ * op == "put" for Put executed on window
+ *	- called from put
+ *
+ * op == "del" for deleted window
+ *	- called from winclose
+ */
+void
+xfidlog(Window *w, char *op)
+{
+	int i, n;
+	vlong min;
+	File *f;
+	char *name;
+
+	qlock(&eventlog.lk);
+	if(eventlog.nev >= eventlog.mev) {
+		// Remove and free any entries that all readers have read.
+		min = eventlog.start + eventlog.nev;
+		for(i=0; i<eventlog.nf; i++) {
+			if(min > eventlog.f[i]->logoff)
+				min = eventlog.f[i]->logoff;
+		}
+		if(min > eventlog.start) {
+			n = min - eventlog.start;
+			for(i=0; i<n; i++)
+				free(eventlog.ev[i]);
+			eventlog.nev -= n;
+			eventlog.start += n;
+			memmove(eventlog.ev, eventlog.ev+n, eventlog.nev*sizeof eventlog.ev[0]);
+		}
+		
+		// Otherwise grow.
+		if(eventlog.nev >= eventlog.mev) {
+			eventlog.mev = eventlog.mev*2;
+			if(eventlog.mev == 0)
+				eventlog.mev = 8;
+			eventlog.ev = erealloc(eventlog.ev, eventlog.mev*sizeof eventlog.ev[0]);
+		}
+	}
+	f = w->body.file;
+	name = runetobyte(f->name, f->nname);
+	if(name == nil)
+		name = estrdup("");
+	eventlog.ev[eventlog.nev++] = smprint("%d %s %s\n", w->id, op, name);
+	free(name);
+	if(eventlog.r.l == nil)
+		eventlog.r.l = &eventlog.lk;
+	rwakeupall(&eventlog.r);
+	qunlock(&eventlog.lk);
+}
blob - a8696a1552d8ab474b01e02a9da4db5d4d75f812
blob + 18bea9e0fd369ac9b2b4ee23389ca323a7be9061
--- src/cmd/acme/mkfile
+++ src/cmd/acme/mkfile
@@ -15,6 +15,7 @@ OFILES=\
 	exec.$O\
 	file.$O\
 	fsys.$O\
+	logf.$O\
 	look.$O\
 	regx.$O\
 	rows.$O\
blob - 4f111ec358fc0be1d63ec844baefd974c2467b9d
blob + 965088e1ec6dbd8c220c5cf28ad12acc3d3d8c21
--- src/cmd/acme/rows.c
+++ src/cmd/acme/rows.c
@@ -776,6 +776,7 @@ rowload(Row *row, char *file, int initing)
 			q0 = q1 = 0;
 		textshow(&w->body, q0, q1, 1);
 		w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
+		xfidlog(w, "new");
 Nextline:
 		l = rdline(b, &line);
 	}
blob - de6cdf8cd4ed676d7178b06ff01c1e122978c322
blob + 28c99ad3fc83f99d4fdeb67b26b5a8e53c575be7
--- src/cmd/acme/util.c
+++ src/cmd/acme/util.c
@@ -97,6 +97,7 @@ errorwin1(Rune *dir, int ndir, Rune **incl, int nincl)
 		w = coladd(row.col[row.ncol-1], nil, nil, -1);
 		w->filemenu = FALSE;
 		winsetname(w, r, n);
+		xfidlog(w, "new");
 	}
 	free(r);
 	for(i=nincl; --i>=0; ){
blob - 1022154dbaadb6c136433391859ab7252f158bc1
blob + 712eb1dcabdf71ee7625ad0602cb48a8162a93f7
--- src/cmd/acme/wind.c
+++ src/cmd/acme/wind.c
@@ -320,6 +320,7 @@ winclose(Window *w)
 	int i;
 
 	if(decref(&w->ref) == 0){
+		xfidlog(w, "del");
 		windirfree(w);
 		textclose(&w->tag);
 		textclose(&w->body);
@@ -644,7 +645,7 @@ Rescue:
 }
 
 int
-winclean(Window *w, int conservative)	/* as it stands, conservative is always TRUE */
+winclean(Window *w, int conservative)
 {
 	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
 		return TRUE;
blob - 671b324f25a5506556dd7a4e013cd94ea57ecfd3
blob + 33732def0a9b664eee2202012beae718ebf99120
--- src/cmd/acme/xfid.c
+++ src/cmd/acme/xfid.c
@@ -63,6 +63,8 @@ xfidflush(Xfid *x)
 	Column *c;
 	Xfid *wx;
 
+	xfidlogflush(x);
+
 	/* search windows for matching tag */
 	qlock(&row.lk);
 	for(j=0; j<row.ncol; j++){
@@ -186,6 +188,9 @@ xfidopen(Xfid *x)
 	}
 	else{
 		switch(q){
+		case Qlog:
+			xfidlogopen(x);
+			break;
 		case Qeditout:
 			if(!canqlock(&editoutlk)){
 				respond(x, &fc, Einuse);
@@ -300,6 +305,9 @@ xfidread(Xfid *x)
 		case Qindex:
 			xfidindexread(x);
 			return;
+		case Qlog:
+			xfidlogread(x);
+			return;
 		default:
 			warning(nil, "unknown qid %d\n", q);
 			break;