ref: 8daa731518254608fc35d69f39d7a0a9994b6d83
author: sirjofri <sirjofri@sirjofri.de>
date: Mon Jul 1 12:54:42 EDT 2024
adds files
--- /dev/null
+++ b/README
@@ -1,0 +1,24 @@
+Livedoc
+
+What does it do:
+
+- In your currently opened document, it finds the page corresponding to your current cursor location.
+- It builds your document, and plumbs the location to the "image" channel (optional).
+- Repeats every N seconds.
+
+
+Requirements needed to use this program:
+
+- Your document directory needs a proper mkfile.
+- The mkfile needs a rule named "tout" that outputs troff intermediate language output.
+- Your edited documents needs to be directly inside the that directory, not within a subdirectory.
+
+
+Under the hood:
+
+- It creates a memory copy of the whole directory using dircp(1)
+- It copies the buffer contents of your open file to the cloned file in memory
+- It places a specific symbol (default: center dot "·") at the cursor location
+- It scans the troff output to find that symbol, figuring out the page
+- It builds the pdf using mk
+- It copies the pdf to the /tmp directory and plumbs it, using the page number
--- /dev/null
+++ b/livedoc.c
@@ -1,0 +1,411 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <plumb.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-hqp] [-t sleep] [-m mark]\n", argv0);
+ exits("usage");
+}
+
+char Enoacme[] = "not in acme";
+char Enofile[] = "no file";
+
+typedef struct Winpos Winpos;
+struct Winpos {
+ long m;
+ long n;
+};
+
+int winid;
+int ctlfd, addrfd, datafd;
+int sctlfd, stagfd;
+char *stag = nil;
+char *file, *filedir, *targetfile;
+char *ldpath = "/mnt/livedoc";
+char *mark = "·";
+
+int noplumb = 0;
+
+void
+runcmd(char *cmd, char **args)
+{
+ int p[2];
+
+ if (pipe(p) < 0)
+ sysfatal("runcmd: %r");
+
+ switch (fork()) {
+ case -1: /* error */
+ sysfatal("unable to fork for %s: %r", cmd);
+ case 0: /* child */
+ chdir(ldpath); /* ramfs call can silently fail */
+ dup(p[1], 1);
+ close(p[1]);
+ close(p[0]);
+ exec(cmd, args);
+ sysfatal("unable to exec: %r");
+ default: /* parent */
+ close(p[1]);
+ break;
+ }
+
+ wait();
+}
+
+Winpos
+finddot(void)
+{
+ int n;
+ Winpos pos;
+ char data[25]; /* m[11] space[1] n[11] */
+ char *e;
+
+ pos.m = pos.n = -1;
+
+ fprint(ctlfd, "addr=dot");
+
+ n = pread(addrfd, data, 24, 0);
+ if (n != 24) {
+ werrstr("error reading addr file");
+ return pos;
+ }
+
+ data[24] = 0;
+ pos.m = strtol(data, &e, 10);
+ pos.n = strtol(e, nil, 10);
+ return pos;
+}
+
+char *dircpargs[] = {
+ "dircp",
+ nil,
+ "/mnt/livedoc",
+ nil,
+};
+
+char *cleanargs[] = {
+ "rm",
+ "-rf",
+ "/mnt/livedoc/*",
+ nil,
+};
+
+void
+copydata(void)
+{
+ dircpargs[1] = filedir;
+
+// runcmd("/bin/rm", cleanargs);
+ runcmd("/bin/dircp", dircpargs);
+}
+
+void
+dumpfile(Winpos pos)
+{
+ int fd;
+ Biobuf *bin, *bout;
+ char *s;
+
+ fprint(addrfd, "#0,#%ld", pos.m);
+
+ fd = open(targetfile, OWRITE);
+ if (fd < 0)
+ sysfatal("error opening file: %r");
+
+ bin = Bfdopen(datafd, OREAD);
+ bout = Bfdopen(fd, OWRITE);
+
+ while (s = Brdstr(bin, '\n', 0))
+ if (Bprint(bout, "%s", s) == -1)
+ break;
+
+ Bprint(bout, "%s", mark);
+ fprint(addrfd, "#%ld,$", pos.m);
+
+ while (s = Brdstr(bin, '\n', 0)) {
+ if (Bprint(bout, "%s", s) == -1)
+ break;
+ }
+ Bterm(bout);
+}
+
+char *mkpdargs[] = {
+ "mk",
+ "tout",
+ nil,
+};
+
+int
+pagedot(void)
+{
+ int p[2];
+ Biobuf *bin;
+ char *s;
+ int page, ignore;
+
+ if (pipe(p) < 0)
+ sysfatal("unable to pipe: %r");
+
+ switch (fork()) {
+ case -1: /* error */
+ sysfatal("unable to fork for pagedot: %r");
+ case 0: /* child */
+ chdir(ldpath);
+ dup(p[1], 1);
+ close(p[1]);
+ close(p[0]);
+ exec("/bin/mk", mkpdargs);
+ sysfatal("error: %r");
+ default: /* parent */
+ close(p[1]);
+ }
+
+ page = 1;
+ ignore = 0;
+ bin = Bfdopen(p[0], OREAD);
+ while (s = Brdstr(bin, '\n', 1)) {
+ if (ignore)
+ continue;
+
+ if (s[0] == 'p') {
+ page = atoi(&s[1]);
+ }
+ if (strstr(s, mark)) {
+ ignore = 1;
+ }
+ free(s);
+ }
+ Bterm(bin);
+ waitpid();
+
+ return ignore ? page : 1;
+}
+
+char *mkargs[] = {
+ "mk",
+ nil,
+};
+
+void
+compile(void)
+{
+ runcmd("/bin/mk", mkargs);
+}
+
+int round = 0;
+char *pdffile = nil;
+
+void
+copyfile(void)
+{
+ char *s;
+ int to, from, dirfd;
+ int n, l;
+ long r;
+ Dir *dirs;
+ char buf[8192];
+
+ dirfd = open(ldpath, OREAD);
+ if (dirfd < 0)
+ sysfatal("unable to open dir: %r");
+
+ s = nil;
+ while ((n = dirread(dirfd, &dirs)) > 0) {
+ for (int i = 0; i < n; i++) {
+ l = strlen(dirs[i].name);
+ if (strcmp(&dirs[i].name[l-4], ".pdf") == 0) {
+ s = strdup(dirs[i].name);
+ free(dirs);
+ goto Found;
+ }
+ }
+ free(dirs);
+ }
+Found: /* s is pdf filename or nil */
+ close(dirfd);
+ if (!s) {
+ fprint(2, "no pdf file found!\n");
+ return;
+ }
+ from = open(s, OREAD);
+ if (from < 0)
+ sysfatal("cannot open source pdf file: %r");
+ free(s);
+
+ if (pdffile) {
+ remove(pdffile);
+ free(pdffile);
+ pdffile = nil;
+ }
+
+ round++;
+
+ pdffile = smprint("/tmp/livedoc.%d.pdf", round);
+ to = create(pdffile, OWRITE, 0666);
+ if (to < 0)
+ sysfatal("unable to create file: %r");
+
+ seek(from, 0, 0);
+ while (r = read(from, buf, sizeof(buf))) {
+ if (write(to, buf, r) != r)
+ sysfatal("write error");
+ }
+
+ close(from);
+ close(to);
+}
+
+void
+plumbpage(int page)
+{
+ char *s;
+ int fd;
+
+ if (!pdffile)
+ return;
+
+ if (noplumb) {
+ print("%s!%d\n", pdffile, page);
+ return;
+ }
+
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if (fd < 0) {
+ fprint(2, "unable to open plumber: %r\n");
+ return;
+ }
+
+ s = smprint("%s!%d", pdffile, page);
+ plumbsendtext(fd, "Livedoc", "image", "/tmp", s);
+ free(s);
+ close(fd);
+}
+
+int singlerun = 0;
+
+int
+tick(void)
+{
+ Winpos pos;
+ int p;
+ int tfd;
+
+ /* access(2) doesn't work in this case for some reason */
+ tfd = open(stag, OREAD);
+ if (tfd < 0) {
+ return 0;
+ }
+ close(tfd);
+
+ fprint(sctlfd, "dirty");
+
+ copydata();
+ pos = finddot();
+ dumpfile(pos);
+ p = pagedot();
+ compile();
+ copyfile();
+ plumbpage(p);
+
+ fprint(sctlfd, "clean");
+
+ return !singlerun;
+}
+
+char *ramfsargs[] = {
+ "ramfs",
+ "-m",
+ "/mnt/livedoc",
+ nil
+};
+
+void
+main(int argc, char **argv)
+{
+ char *s, sidstr[13];
+ char *winctl, *winaddr, *windata;
+ int sleeptimer = 5000;
+ int n, sid;
+
+ ARGBEGIN{
+ case 'h':
+ usage();
+ return;
+ case 't':
+ sleeptimer = abs(atoi(EARGF(usage())));
+ break;
+ case 'm':
+ mark = EARGF(usage());
+ break;
+ case 'p':
+ noplumb++;
+ break;
+ case 'q':
+ singlerun++;
+ break;
+ }ARGEND;
+
+ s = getenv("winid");
+ if (!s) {
+ fprint(2, Enoacme);
+ exits(Enoacme);
+ }
+ winid = atoi(s);
+
+ file = getenv("%");
+ if (!file) {
+ fprint(2, Enofile);
+ exits(Enofile);
+ }
+
+ filedir = strdup(file);
+ *strrchr(filedir, '/') = 0;
+
+ targetfile = strrchr(file, '/') + 1;
+ targetfile = smprint("%s/%s", ldpath, targetfile);
+
+ winctl = smprint("/mnt/acme/%d/ctl", winid);
+ winaddr = smprint("/mnt/acme/%d/addr", winid);
+ windata = smprint("/mnt/acme/%d/xdata", winid);
+
+ ctlfd = open(winctl, ORDWR);
+ addrfd = open(winaddr, ORDWR);
+ datafd = open(windata, OREAD);
+
+ free(winctl);
+ free(winaddr);
+ free(windata);
+
+ sctlfd = open("/mnt/acme/new/ctl", ORDWR);
+ if (sctlfd < 0)
+ sysfatal("unable to open file: %r");
+
+ read(sctlfd, sidstr, sizeof(sidstr));
+ sid = atoi(sidstr);
+ stag = smprint("/mnt/acme/%d/tag", sid);
+ stagfd = open(stag, ORDWR);
+
+ fprint(sctlfd, "scratch\n");
+ fprint(sctlfd, "name %s/-Livedoc\n", filedir);
+
+ if (ctlfd < 0 || addrfd < 0 || datafd < 0) {
+ sysfatal("unable to open file: %r");
+ }
+
+ runcmd("/bin/ramfs", ramfsargs);
+
+ n = 0;
+ while (tick() && n != -1)
+ n = sleep(sleeptimer);
+
+ close(ctlfd);
+ close(addrfd);
+ close(datafd);
+ free(filedir);
+ free(targetfile);
+ free(stag);
+ exits(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+BIN=/acme/bin/$objtype
+TARG=Livedoc
+OFILES=livedoc.$O
+
+</sys/src/cmd/mkone