This version implements some off-list review comments...
1. Discard explicit checking whether command exists and it's
permissions since shell already does and reports error.
2. Remove unnecessary bzero call.
3. Document minor deviation from emacs behaviour in README.
Index: README
===================================================================
RCS file: /cvs/src/usr.bin/mg/README,v
retrieving revision 1.8
diff -u -p -r1.8 README
--- README 1 Aug 2011 12:15:23 -0000 1.8
+++ README 20 Mar 2012 17:54:12 -0000
@@ -61,7 +61,9 @@ recognized as special cases.
On systems with 16 bit integers, the kill buffer cannot exceed 32767
bytes.
-
+Unlike GNU Emacs, Mg's minibuffer isn't multi-line aware and hence
+some commands like "shell-command-on-region" always pop up a buffer to
+display output irrespective of output's size.
New implementation oddities:
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 16 Mar 2012 04:59:14 -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.36
diff -u -p -r1.36 funmap.c
--- funmap.c 14 Mar 2012 13:56:35 -0000 1.36
+++ funmap.c 16 Mar 2012 04:59:14 -0000
@@ -113,6 +113,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",},
@@ -166,6 +167,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.47
diff -u -p -r1.47 keymap.c
--- keymap.c 14 Mar 2012 13:56:35 -0000 1.47
+++ keymap.c 16 Mar 2012 04:59:14 -0000
@@ -135,7 +135,7 @@ static PF cXcar[] = {
#endif /* !NO_MACRO */
setfillcol, /* f */
gotoline, /* g */
- rescan, /* h */
+ markbuffer, /* h */
fileinsert, /* i */
rescan, /* j */
killbuffer_cmd, /* k */
@@ -257,7 +257,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 16 Mar 2012 04:59:14 -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 21 Mar 2012 17:45:24 -0000
@@ -9,9 +9,25 @@
* 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 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 +382,228 @@ 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, 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);
+ return (pipeio(cmdbuf));
+}
+
+/*
+ * 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;
+ int len, loffs;
+ char *l;
+
+ linep = region->r_linep;
+ loffs = region->r_offset;
+ /* Reset the offset, needed only for first line of region. */
+ region->r_offset = 0;
+ if ((l = get_line(linep, &len)) == NULL)
+ return;
+ /* Take care of region's offset on first and last line */
+ len -= loffs;
+ len = (len < region->r_size) ? len : region->r_size;
+ if ((send(fd, l + loffs, 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 doesn'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)memmove(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)
+{
+ int len;
+ char buf[BUFSIZ], *p, *q;
+
+ if ((len = read(fd, buf, BUFSIZ - 1)) == 0)
+ return (FALSE);
+ buf[len] = '\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);
}