A refined version of the diff with 2 less bugs and shorter by ~40 or
so lines. Changes from previous version...

Reuse region_get_data() to extract C string from selected region
instead of custom line by line iteration, thereby reducing number of
send(2) syscalls.

Fix a bug which causes gratuitous text from previous invocation to
appear if previous command input's last line doesn't have a '\n'.

Fix a crash issue when a region from "Shell Command Ouput" buffer is
pipe'd to external command.

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      5 Apr 2012 19:02:18 -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    10 Apr 2012 17:05:07 -0000
@@ -9,9 +9,25 @@
  * internal use.
  */
 
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
 #include "def.h"
 
+#define TIMEOUT 10000
+
+static char leftover[BUFSIZ];
+
 static int     getregion(struct region *);
+static  int    iomux(int);
+static  int    pipeio(const char *);
+static  int     preadin(int, struct buffer *);
+static  void    pwriteout(int, char **, int *);
 static int     setsize(struct region *, RSIZE);
 
 /*
@@ -366,4 +382,216 @@ 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;
+       char *text, *textcopy;
+       
+       if (getregion(&region) != TRUE)
+               return (FALSE);
+       
+       if ((text = malloc(region.r_size + 1)) == NULL)
+               return (ABORT);
+       
+       region_get_data(&region, text, region.r_size);
+       textcopy = text;
+       fcntl(fd, F_SETFL, O_NONBLOCK);
+       
+       /* 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, &textcopy, &region.r_size);       
+               else if (pfd[0].revents & POLLIN)
+                       if (preadin(fd, bp) == FALSE)
+                               break;
+       }
+       close(fd);
+       free(text);
+       /* In case if last line doesn't have a '\n' add the leftover 
+        * characters to buffer.
+        */
+       if (leftover[0] != '\0') {
+               addline(bp, leftover);
+               leftover[0] = '\0';
+       }
+       if (nfds == 0) {
+               ewprintf("poll timed out");
+               return (FALSE);
+       } else if (nfds == -1) {
+               ewprintf("poll error");
+               return (FALSE);
+       }
+       return (popbuftop(bp, WNONE));
+}
+
+/*
+ * Write some text from region to fd. Once done shutdown the 
+ * write end.
+ */
+void
+pwriteout(int fd, char **text, int *len)
+{
+       int w;
+
+       if (((w = send(fd, *text, *len, MSG_NOSIGNAL)) == -1)) {
+               switch(errno) {
+               case EPIPE:
+                       *len = -1;
+                       break;
+               case EAGAIN:
+                       return;
+               }
+       } else
+               *len -= w;
+
+       *text += w;
+       if (*len <= 0)
+               shutdown(fd, SHUT_WR);          
+}
+
+/*
+ * 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;
+       static int nooutput;
+       char buf[BUFSIZ], *p, *q;
+
+       if ((len = read(fd, buf, BUFSIZ - 1)) == 0) {
+               if (nooutput == 0)
+                       addline(bp, "(Shell command succeeded with no output)");
+               nooutput = 0;
+               return (FALSE);
+       }
+       nooutput = 1;
+       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