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); }