This version properly captures data from external command and puts it
into *Shell Command Output* buffer. These are the new commands added
to mg with this diff...
C-x h mark-whole-buffer
M-| shell-command-on-region
Comments?
Index: buffer.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/buffer.c,v
retrieving revision 1.77
diff -u -p -r1.77 buffer.c
--- buffer.c 23 Jan 2011 00:45:03 -0000 1.77
+++ buffer.c 10 Mar 2012 12:30:19 -0000
@@ -792,10 +792,8 @@ notmodified(int f, int n)
return (TRUE);
}
-#ifndef NO_HELP
/*
- * Popbuf and set all windows to top of buffer. Currently only used by
- * help functions.
+ * Popbuf and set all windows to top of buffer.
*/
int
popbuftop(struct buffer *bp, int flags)
@@ -814,7 +812,6 @@ popbuftop(struct buffer *bp, int flags)
}
return (popbuf(bp, flags) != NULL);
}
-#endif
/*
* Return the working directory for the current buffer, terminated
Index: def.h
===================================================================
RCS file: /cvs/src/usr.bin/mg/def.h,v
retrieving revision 1.118
diff -u -p -r1.118 def.h
--- def.h 10 Dec 2011 14:09:48 -0000 1.118
+++ def.h 10 Mar 2012 12:30:19 -0000
@@ -567,6 +567,8 @@ int prefixregion(int, int);
int setprefix(int, int);
int region_get_data(struct region *, char *, int);
void region_put_data(const char *, int);
+int markbuffer(int, int);
+int piperegion(int, int);
/* search.c X */
int forwsearch(int, int);
Index: funmap.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/funmap.c,v
retrieving revision 1.35
diff -u -p -r1.35 funmap.c
--- funmap.c 28 Nov 2011 04:41:39 -0000 1.35
+++ funmap.c 10 Mar 2012 12:30:19 -0000
@@ -119,6 +119,7 @@ static struct funmap functnames[] = {
{localbind, "local-set-key",},
{localunbind, "local-unset-key",},
{makebkfile, "make-backup-files",},
+ {markbuffer, "mark-whole-buffer",},
{do_meta, "meta-key-mode",}, /* better name, anyone? */
{negative_argument, "negative-argument",},
{newline, "newline",},
@@ -172,6 +173,7 @@ static struct funmap functnames[] = {
{setfillcol, "set-fill-column",},
{setmark, "set-mark-command",},
{setprefix, "set-prefix-string",},
+ {piperegion, "shell-command-on-region",},
{shrinkwind, "shrink-window",},
#ifdef NOTAB
{space_to_tabstop, "space-to-tabstop",},
Index: keymap.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/keymap.c,v
retrieving revision 1.46
diff -u -p -r1.46 keymap.c
--- keymap.c 28 Nov 2011 04:41:39 -0000 1.46
+++ keymap.c 10 Mar 2012 12:30:19 -0000
@@ -137,7 +137,7 @@ static PF cXcar[] = {
#endif /* !NO_MACRO */
setfillcol, /* f */
gotoline, /* g */
- rescan, /* h */
+ markbuffer, /* h */
fileinsert, /* i */
rescan, /* j */
killbuffer_cmd, /* k */
@@ -259,7 +259,7 @@ static PF metal[] = {
rescan, /* y */
rescan, /* z */
gotobop, /* { */
- rescan, /* | */
+ piperegion, /* | */
gotoeop /* } */
};
Index: mg.1
===================================================================
RCS file: /cvs/src/usr.bin/mg/mg.1,v
retrieving revision 1.58
diff -u -p -r1.58 mg.1
--- mg.1 9 Feb 2012 09:00:14 -0000 1.58
+++ mg.1 10 Mar 2012 12:30:22 -0000
@@ -196,6 +196,8 @@ call-last-kbd-macro
set-fill-column
.It C-x g
goto-line
+.It C-x h
+mark-whole-buffer
.It C-x i
insert-file
.It C-x k
@@ -260,6 +262,8 @@ copy-region-as-kill
execute-extended-command
.It M-{
backward-paragraph
+.It M-|
+shell-command-on-region
.It M-}
forward-paragraph
.It M-~
@@ -572,6 +576,9 @@ Bind a key mapping in the local (topmost
Unbind a key mapping in the local (topmost) mode.
.It make-backup-files
Toggle generation of backup files.
+.It mark-whole-buffer
+Marks whole buffer as a region by putting dot at the beginning and mark
+at the end of buffer.
.It meta-key-mode
When disabled, the meta key can be used to insert extended-ascii (8-bit)
characters.
@@ -734,6 +741,8 @@ Used by auto-fill-mode.
Sets the mark in the current window to the current dot location.
.It set-prefix-string
Sets the prefix string to be used by the 'prefix-region' command.
+.It shell-command-on-region
+Provide the text in region to the shell command as input.
.It shrink-window
Shrink current window by one line.
The window immediately below is expanded to pick up the slack.
Index: region.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/region.c,v
retrieving revision 1.29
diff -u -p -r1.29 region.c
--- region.c 5 Jun 2009 18:02:06 -0000 1.29
+++ region.c 11 Mar 2012 14:49:46 -0000
@@ -9,9 +9,26 @@
* internal use.
*/
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
#include "def.h"
+#define TIMEOUT 10000
+
+static char leftover[BUFSIZ];
+
+static int exists(const char *);
+static char *get_line(struct line *, int *);
static int getregion(struct region *);
+static int iomux(int);
+static int pipeio(const char *);
+static int preadin(int,struct buffer *);
+static void pwriteout(int,struct region *);
static int setsize(struct region *, RSIZE);
/*
@@ -366,4 +383,295 @@ region_put_data(const char *buf, int len
else
linsert(1, buf[i]);
}
+}
+
+/*
+ * Mark whole buffer by first traversing to end-of-buffer
+ * and then to beginning-of-buffer. Mark, dot are implicitly
+ * set to eob, bob respectively during traversal.
+ */
+int
+markbuffer(int f, int n)
+{
+ if (gotoeob(f,n) == FALSE)
+ return (FALSE);
+ if (gotobob(f,n) == FALSE)
+ return (FALSE);
+ return (TRUE);
+}
+
+/*
+ * Pipe text from current region to external command.
+ */
+/*ARGSUSED */
+int
+piperegion(int f, int n)
+{
+ char *cmd, *cmdcpy, cmdbuf[NFILEN];
+
+ /* C-u M-| is not supported yet */
+ if (n > 1)
+ return (ABORT);
+
+ if (curwp->w_markp == NULL) {
+ ewprintf("The mark is not set now, so there is no region");
+ return (FALSE);
+ }
+ if ((cmd = eread("Shell command on region: ", cmdbuf, sizeof(cmdbuf),
+ EFNEW | EFCR)) == NULL)
+ return (FALSE);
+ else if (cmd[0] == '\0')
+ return (ABORT);
+
+ if ((cmdcpy = strdup(cmdbuf)) == NULL) {
+ ewprintf("out of memory");
+ return (FALSE);
+ }
+ /* if cmd contains arguments then consider only the cmd part
+ * while checking for existence.
+ */
+ if (strchr(cmdcpy, ' ') != NULL)
+ cmdcpy = strsep(&cmdcpy, " ");
+ if (exists(cmdcpy) == FALSE)
+ return (FALSE);
+ free(cmdcpy);
+ return (pipeio(cmdbuf));
+}
+
+/*
+ * Check if the command exists and has necessary permissions.
+ */
+int
+exists(const char *cmd)
+{
+ char fname[MAXPATHLEN], *dir, *path, *tmp;
+ int cmdlen, dlen, found;
+
+ /* Special case if prog contains '/' */
+ if (strchr(cmd, '/')) {
+ if (access(cmd, F_OK) == -1) {
+ ewprintf("%s not found", cmd);
+ return (FALSE);
+ }
+ else if (access(cmd, X_OK) == -1) {
+ ewprintf("%s cannot execute - Permission denied", cmd);
+ return (FALSE);
+ } else
+ return (TRUE);
+ }
+ if ((tmp = getenv("PATH")) == NULL)
+ return (FALSE);
+ if ((path = strdup(tmp)) == NULL) {
+ ewprintf("out of memory");
+ return (FALSE);
+ }
+ found = FALSE;
+ cmdlen = strlen(cmd);
+ while ((dir = strsep(&path, ":")) != NULL) {
+ if (*dir == '\0')
+ *dir = '.';
+
+ dlen = strlen(dir);
+ while (dir[dlen-1] == '/')
+ dir[--dlen] = '\0'; /* strip trailing '/' */
+
+ if (dlen + 1 + cmdlen >= sizeof(fname)) {
+ ewprintf("path too long");
+ return (FALSE);
+ }
+ snprintf(fname, sizeof(fname), "%s/%s", dir, cmd);
+ if(access(fname, F_OK) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (found) {
+ if (access(fname, X_OK) == -1) {
+ ewprintf("%s cannot execute - Permission denied", cmd);
+ return (FALSE);
+ } else
+ return (TRUE);
+ } else {
+ ewprintf("%s not found", cmd);
+ return (FALSE);
+ }
+ return (FALSE);
+}
+
+/*
+ * Create a socketpair, fork and execl cmd passed. STDIN, STDOUT
+ * and STDERR of child process are redirected to socket.
+ */
+int
+pipeio(const char* const cmd)
+{
+ int s[2];
+ char *shellp;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
+ ewprintf("socketpair error");
+ return (FALSE);
+ }
+ switch(fork()) {
+ case -1:
+ ewprintf("Can't fork");
+ return (FALSE);
+ case 0:
+ /* child process */
+ close(s[0]);
+ if (dup2(s[1], STDIN_FILENO) == -1)
+ _exit(1);
+ if (dup2(s[1], STDOUT_FILENO) == -1)
+ _exit(1);
+ if (dup2(s[1], STDERR_FILENO) == -1)
+ _exit(1);
+ if ((shellp = getenv("SHELL")) == NULL)
+ _exit(1);
+ execl(shellp, "sh", "-c", cmd, (char *)NULL);
+ _exit(1);
+ default:
+ /* parent process */
+ close(s[1]);
+ return iomux(s[0]);
+ }
+ return (FALSE);
+}
+
+/*
+ * Multiplex read, write on socket fd passed. First get the region,
+ * find/create *Shell Command Output* buffer and clear it's contents.
+ * Poll on the fd for both read and write readiness.
+ */
+int
+iomux(int fd)
+{
+ struct region region;
+ struct buffer *bp;
+ struct pollfd pfd[1];
+ int nfds;
+
+ if (getregion(®ion) != TRUE)
+ return (FALSE);
+
+ /* there is nothing to write if r_size is zero
+ * but the cmd's output should be read so shutdown
+ * the socket for writing only.
+ */
+ if (region.r_size == 0)
+ shutdown(fd, SHUT_WR);
+
+ bp = bfind("*Shell Command Output*", TRUE);
+ bp->b_flag |= BFREADONLY;
+ if (bclear(bp) != TRUE)
+ return (FALSE);
+
+ pfd[0].fd = fd;
+ pfd[0].events = POLLIN | POLLOUT;
+ while ((nfds = poll(pfd, 1, TIMEOUT)) != -1 ||
+ (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ if (pfd[0].revents & POLLOUT && region.r_size > 0) {
+ pwriteout(fd, ®ion);
+ region.r_linep = lforw(region.r_linep);
+ }
+ else if (pfd[0].revents & POLLIN)
+ if (preadin(fd, bp) == FALSE)
+ break;
+ }
+ close(fd);
+ /* In case if last line doesn't have a '\n' add the leftover
+ * characters to buffer.
+ */
+ if (leftover[0] != '\0')
+ addline(bp, leftover);
+ if (nfds == 0) {
+ ewprintf("poll timed out");
+ return (FALSE);
+ } else if (nfds == -1) {
+ ewprintf("poll error");
+ return (FALSE);
+ }
+ return (popbuftop(bp, WNONE));
+}
+
+/*
+ * Write each line from region to fd. Once done shutdown the
+ * write end.
+ */
+void
+pwriteout(int fd, struct region *region)
+{
+ struct line *linep;
+ char *l;
+ int len;
+
+ linep = region->r_linep;
+ if ((l = get_line(linep, &len)) == NULL)
+ return;
+ if ((send(fd, l, len, MSG_NOSIGNAL) == -1) && (errno == EPIPE))
+ region->r_size = -1;
+ else
+ region->r_size -= len;
+ if (region->r_size <= 0)
+ shutdown(fd, SHUT_WR);
+ free(l);
+}
+
+/*
+ * Since struct line don't have a terminating '\n', make
+ * a copy and append '\n'.
+ */
+char *
+get_line(struct line *ln, int *lenp)
+{
+ int len;
+ char *line;
+
+ len = llength(ln);
+ if (len == INT_MAX)
+ return (NULL);
+
+ if ((line = malloc(len + 1)) == NULL)
+ return (NULL);
+
+ (void)memcpy(line, ltext(ln), len);
+ line[len] = '\n';
+ *lenp = len + 1;
+ return (line);
+}
+
+/*
+ * Read some data from socket fd, break on '\n' and add
+ * to buffer. If couldn't break on newline hold leftover
+ * characters and append in next iteration.
+ */
+int
+preadin(int fd, struct buffer *bp)
+{
+ char buf[BUFSIZ], *p, *q;
+
+ bzero(buf, BUFSIZ);
+ if (read(fd, buf, BUFSIZ - 2) == 0)
+ return (FALSE);
+ buf[BUFSIZ - 1] = '\0';
+ p = q = buf;
+ if (leftover[0] != '\0' && ((q = strchr(p, '\n')) != NULL)) {
+ *q++ = '\0';
+ if (strlcat(leftover, p, sizeof(leftover)) >= sizeof(leftover))
{
+ ewprintf("line too long");
+ return (FALSE);
+ }
+ addline(bp, leftover);
+ leftover[0] = '\0';
+ p = q;
+ }
+ while ((q = strchr(p, '\n')) != NULL) {
+ *q++ = '\0';
+ addline(bp, p);
+ p = q;
+ }
+ if (strlcpy(leftover, p, sizeof(leftover)) >= sizeof(leftover)) {
+ ewprintf("line too long");
+ return (FALSE);
+ }
+ return (TRUE);
}