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(&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;
+       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);
 }

Reply via email to