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(&region) != 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, &region);
+                       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);
 }

Reply via email to