--- /dev/null
+/* $OpenBSD: region.c,v 1.30 2012/04/11 17:51:10 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ * Region based commands.
+ * The routines in this file deal with the region, that magic space between
+ * "." and mark. Some functions are commands. Some functions are just for
+ * 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);
+
+/*
+ * Kill the region. Ask "getregion" to figure out the bounds of the region.
+ * Move "." to the start, and kill the characters. Mark is cleared afterwards.
+ */
+/* ARGSUSED */
+int
+killregion(int f, int n)
+{
+ int s;
+ struct region region;
+
+ if ((s = getregion(®ion)) != TRUE)
+ return (s);
+ /* This is a kill-type command, so do magic kill buffer stuff. */
+ if ((lastflag & CFKILL) == 0)
+ kdelete();
+ thisflag |= CFKILL;
+ curwp->w_dotp = region.r_linep;
+ curwp->w_doto = region.r_offset;
+ curwp->w_dotline = region.r_lineno;
+ s = ldelete(region.r_size, KFORW | KREG);
+ clearmark(FFARG, 0);
+
+ return (s);
+}
+
+/*
+ * Copy all of the characters in the region to the kill buffer,
+ * clearing the mark afterwards.
+ * This is a bit like a kill region followed by a yank.
+ */
+/* ARGSUSED */
+int
+copyregion(int f, int n)
+{
+ struct line *linep;
+ struct region region;
+ int loffs;
+ int s;
+
+ if ((s = getregion(®ion)) != TRUE)
+ return (s);
+
+ /* kill type command */
+ if ((lastflag & CFKILL) == 0)
+ kdelete();
+ thisflag |= CFKILL;
+
+ /* current line */
+ linep = region.r_linep;
+
+ /* current offset */
+ loffs = region.r_offset;
+
+ while (region.r_size--) {
+ if (loffs == llength(linep)) { /* End of line. */
+ if ((s = kinsert('\n', KFORW)) != TRUE)
+ return (s);
+ linep = lforw(linep);
+ loffs = 0;
+ } else { /* Middle of line. */
+ if ((s = kinsert(lgetc(linep, loffs), KFORW)) != TRUE)
+ return (s);
+ ++loffs;
+ }
+ }
+ clearmark(FFARG, 0);
+
+ return (TRUE);
+}
+
+/*
+ * Lower case region. Zap all of the upper case characters in the region to
+ * lower case. Use the region code to set the limits. Scan the buffer, doing
+ * the changes. Call "lchange" to ensure that redisplay is done in all
+ * buffers.
+ */
+/* ARGSUSED */
+int
+lowerregion(int f, int n)
+{
+ struct line *linep;
+ struct region region;
+ int loffs, c, s;
+
+ if ((s = checkdirty(curbp)) != TRUE)
+ return (s);
+ if (curbp->b_flag & BFREADONLY) {
+ ewprintf("Buffer is read-only");
+ return (FALSE);
+ }
+
+ if ((s = getregion(®ion)) != TRUE)
+ return (s);
+
+ undo_add_change(region.r_linep, region.r_offset, region.r_size);
+
+ lchange(WFFULL);
+ linep = region.r_linep;
+ loffs = region.r_offset;
+ while (region.r_size--) {
+ if (loffs == llength(linep)) {
+ linep = lforw(linep);
+ loffs = 0;
+ } else {
+ c = lgetc(linep, loffs);
+ if (ISUPPER(c) != FALSE)
+ lputc(linep, loffs, TOLOWER(c));
+ ++loffs;
+ }
+ }
+ return (TRUE);
+}
+
+/*
+ * Upper case region. Zap all of the lower case characters in the region to
+ * upper case. Use the region code to set the limits. Scan the buffer,
+ * doing the changes. Call "lchange" to ensure that redisplay is done in all
+ * buffers.
+ */
+/* ARGSUSED */
+int
+upperregion(int f, int n)
+{
+ struct line *linep;
+ struct region region;
+ int loffs, c, s;
+
+ if ((s = checkdirty(curbp)) != TRUE)
+ return (s);
+ if (curbp->b_flag & BFREADONLY) {
+ ewprintf("Buffer is read-only");
+ return (FALSE);
+ }
+ if ((s = getregion(®ion)) != TRUE)
+ return (s);
+
+ undo_add_change(region.r_linep, region.r_offset, region.r_size);
+
+ lchange(WFFULL);
+ linep = region.r_linep;
+ loffs = region.r_offset;
+ while (region.r_size--) {
+ if (loffs == llength(linep)) {
+ linep = lforw(linep);
+ loffs = 0;
+ } else {
+ c = lgetc(linep, loffs);
+ if (ISLOWER(c) != FALSE)
+ lputc(linep, loffs, TOUPPER(c));
+ ++loffs;
+ }
+ }
+ return (TRUE);
+}
+
+/*
+ * This routine figures out the bound of the region in the current window,
+ * and stores the results into the fields of the REGION structure. Dot and
+ * mark are usually close together, but I don't know the order, so I scan
+ * outward from dot, in both directions, looking for mark. The size is kept
+ * in a long. At the end, after the size is figured out, it is assigned to
+ * the size field of the region structure. If this assignment loses any bits,
+ * then we print an error. This is "type independent" overflow checking. All
+ * of the callers of this routine should be ready to get an ABORT status,
+ * because I might add a "if regions is big, ask before clobbering" flag.
+ */
+static int
+getregion(struct region *rp)
+{
+ struct line *flp, *blp;
+ long fsize, bsize;
+
+ if (curwp->w_markp == NULL) {
+ ewprintf("No mark set in this window");
+ return (FALSE);
+ }
+
+ /* "r_size" always ok */
+ if (curwp->w_dotp == curwp->w_markp) {
+ rp->r_linep = curwp->w_dotp;
+ rp->r_lineno = curwp->w_dotline;
+ if (curwp->w_doto < curwp->w_marko) {
+ rp->r_offset = curwp->w_doto;
+ rp->r_size = (RSIZE)(curwp->w_marko - curwp->w_doto);
+ } else {
+ rp->r_offset = curwp->w_marko;
+ rp->r_size = (RSIZE)(curwp->w_doto - curwp->w_marko);
+ }
+ return (TRUE);
+ }
+ /* get region size */
+ flp = blp = curwp->w_dotp;
+ bsize = curwp->w_doto;
+ fsize = llength(flp) - curwp->w_doto + 1;
+ while (lforw(flp) != curbp->b_headp || lback(blp) != curbp->b_headp) {
+ if (lforw(flp) != curbp->b_headp) {
+ flp = lforw(flp);
+ if (flp == curwp->w_markp) {
+ rp->r_linep = curwp->w_dotp;
+ rp->r_offset = curwp->w_doto;
+ rp->r_lineno = curwp->w_dotline;
+ return (setsize(rp,
+ (RSIZE)(fsize + curwp->w_marko)));
+ }
+ fsize += llength(flp) + 1;
+ }
+ if (lback(blp) != curbp->b_headp) {
+ blp = lback(blp);
+ bsize += llength(blp) + 1;
+ if (blp == curwp->w_markp) {
+ rp->r_linep = blp;
+ rp->r_offset = curwp->w_marko;
+ rp->r_lineno = curwp->w_markline;
+ return (setsize(rp,
+ (RSIZE)(bsize - curwp->w_marko)));
+ }
+ }
+ }
+ ewprintf("Bug: lost mark");
+ return (FALSE);
+}
+
+/*
+ * Set size, and check for overflow.
+ */
+static int
+setsize(struct region *rp, RSIZE size)
+{
+ rp->r_size = size;
+ if (rp->r_size != size) {
+ ewprintf("Region is too large");
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+#define PREFIXLENGTH 40
+static char prefix_string[PREFIXLENGTH] = {'>', '\0'};
+
+/*
+ * Prefix the region with whatever is in prefix_string. Leaves dot at the
+ * beginning of the line after the end of the region. If an argument is
+ * given, prompts for the line prefix string.
+ */
+/* ARGSUSED */
+int
+prefixregion(int f, int n)
+{
+ struct line *first, *last;
+ struct region region;
+ char *prefix = prefix_string;
+ int nline;
+ int s;
+
+ if ((s = checkdirty(curbp)) != TRUE)
+ return (s);
+ if (curbp->b_flag & BFREADONLY) {
+ ewprintf("Buffer is read-only");
+ return (FALSE);
+ }
+ if ((f == TRUE) && ((s = setprefix(FFRAND, 1)) != TRUE))
+ return (s);
+
+ /* get # of lines to affect */
+ if ((s = getregion(®ion)) != TRUE)
+ return (s);
+ first = region.r_linep;
+ last = (first == curwp->w_dotp) ? curwp->w_markp : curwp->w_dotp;
+ for (nline = 1; first != last; nline++)
+ first = lforw(first);
+
+ /* move to beginning of region */
+ curwp->w_dotp = region.r_linep;
+ curwp->w_doto = region.r_offset;
+ curwp->w_dotline = region.r_lineno;
+
+ /* for each line, go to beginning and insert the prefix string */
+ while (nline--) {
+ (void)gotobol(FFRAND, 1);
+ for (prefix = prefix_string; *prefix; prefix++)
+ (void)linsert(1, *prefix);
+ (void)forwline(FFRAND, 1);
+ }
+ (void)gotobol(FFRAND, 1);
+ return (TRUE);
+}
+
+/*
+ * Set line prefix string. Used by prefixregion.
+ */
+/* ARGSUSED */
+int
+setprefix(int f, int n)
+{
+ char buf[PREFIXLENGTH], *rep;
+ int retval;
+
+ if (prefix_string[0] == '\0')
+ rep = eread("Prefix string: ", buf, sizeof(buf),
+ EFNEW | EFCR);
+ else
+ rep = eread("Prefix string (default %s): ", buf, sizeof(buf),
+ EFNUL | EFNEW | EFCR, prefix_string);
+ if (rep == NULL)
+ return (ABORT);
+ if (rep[0] != '\0') {
+ (void)strlcpy(prefix_string, rep, sizeof(prefix_string));
+ retval = TRUE;
+ } else if (rep[0] == '\0' && prefix_string[0] != '\0') {
+ /* CR -- use old one */
+ retval = TRUE;
+ } else
+ retval = FALSE;
+ return (retval);
+}
+
+int
+region_get_data(struct region *reg, char *buf, int len)
+{
+ int i, off;
+ struct line *lp;
+
+ off = reg->r_offset;
+ lp = reg->r_linep;
+ for (i = 0; i < len; i++) {
+ if (off == llength(lp)) {
+ lp = lforw(lp);
+ if (lp == curbp->b_headp)
+ break;
+ off = 0;
+ buf[i] = '\n';
+ } else {
+ buf[i] = lgetc(lp, off);
+ off++;
+ }
+ }
+ buf[i] = '\0';
+ return (i);
+}
+
+void
+region_put_data(const char *buf, int len)
+{
+ int i;
+
+ for (i = 0; buf[i] != '\0' && i < len; i++) {
+ if (buf[i] == '\n')
+ lnewline();
+ 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 || (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(®ion) != TRUE)
+ return (FALSE);
+
+ if ((text = malloc(region.r_size + 1)) == NULL)
+ return (ABORT);
+
+ region_get_data(®ion, 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, ®ion.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);
+}