aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc André Tanner <mat@brain-dump.org>2015-05-16 18:01:13 +0200
committerMarc André Tanner <mat@brain-dump.org>2015-05-17 12:46:19 +0200
commit1244aeae5c603593aa71df4b67974c11ea1576cd (patch)
tree94b2639fa09505df2a40e31139b43bba187325e3
parent55d76f8a38a1da911bf3162f2b1f60cf91ec94bb (diff)
downloadvis-1244aeae5c603593aa71df4b67974c11ea1576cd.tar.gz
vis-1244aeae5c603593aa71df4b67974c11ea1576cd.tar.xz
Filter command :!
If no range is given then stdin is passed through which allows interactive usage as in :!ls -1 *.c | slmenu For this to work the command needs to use stderr for its user interface and write any data for vis to stdout.
-rw-r--r--config.def.h1
-rw-r--r--editor.h2
-rw-r--r--vis.c216
3 files changed, 219 insertions, 0 deletions
diff --git a/config.def.h b/config.def.h
index 88c032c..d2fab76 100644
--- a/config.def.h
+++ b/config.def.h
@@ -67,6 +67,7 @@ static Command cmds[] = {
{ { "wq", }, cmd_wq, CMD_OPT_FORCE },
{ { "write", "w" }, cmd_write, CMD_OPT_FORCE },
{ { "xit", }, cmd_xit, CMD_OPT_FORCE },
+ { { "!", }, cmd_filter, CMD_OPT_NONE },
{ /* array terminator */ },
};
diff --git a/editor.h b/editor.h
index 7104d01..d985755 100644
--- a/editor.h
+++ b/editor.h
@@ -2,6 +2,7 @@
#define EDITOR_H
#include <curses.h>
+#include <signal.h>
#include <stddef.h>
#include <stdbool.h>
@@ -244,6 +245,7 @@ struct Editor {
Mode *mode_prev; /* previsouly active user mode */
Mode *mode_before_prompt; /* user mode which was active before entering prompt */
volatile bool running; /* exit main loop once this becomes false */
+ volatile sig_atomic_t cancel_filter; /* abort external command */
};
Editor *editor_new(Ui*);
diff --git a/vis.c b/vis.c
index ee30c20..d78ed6b 100644
--- a/vis.c
+++ b/vis.c
@@ -27,6 +27,7 @@
#include <ctype.h>
#include <sys/select.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
@@ -413,6 +414,8 @@ static bool cmd_write(Filerange*, enum CmdOpt, const char *argv[]);
* associate the new name with the buffer. further :w commands
* without arguments will write to the new filename */
static bool cmd_saveas(Filerange*, enum CmdOpt, const char *argv[]);
+/* filter range through external program argv[1] */
+static bool cmd_filter(Filerange*, enum CmdOpt, const char *argv[]);
static void action_reset(Action *a);
static void switchmode_to(Mode *new_mode);
@@ -1641,6 +1644,218 @@ static bool cmd_saveas(Filerange *range, enum CmdOpt opt, const char *argv[]) {
return false;
}
+static void cancel_filter(int sig) {
+ vis->cancel_filter = true;
+}
+
+static bool cmd_filter(Filerange *range, enum CmdOpt opt, const char *argv[]) {
+ /* if an invalid range was given, stdin (i.e. key board input) is passed
+ * through the external command. */
+ Text *text = vis->win->file->text;
+ View *view = vis->win->view;
+ int pin[2], pout[2], perr[2], status = -1;
+ bool interactive = !text_range_valid(range);
+ size_t pos = view_cursor_get(view);
+
+ if (pipe(pin) == -1)
+ return false;
+ if (pipe(pout) == -1) {
+ close(pin[0]);
+ close(pin[1]);
+ return false;
+ }
+
+ if (pipe(perr) == -1) {
+ close(pin[0]);
+ close(pin[1]);
+ close(pout[0]);
+ close(pout[1]);
+ return false;
+ }
+
+ reset_shell_mode();
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ close(pin[0]);
+ close(pin[1]);
+ close(pout[0]);
+ close(pout[1]);
+ close(perr[0]);
+ close(perr[1]);
+ editor_info_show(vis, "fork failure: %s", strerror(errno));
+ return false;
+ } else if (pid == 0) { /* child i.e filter */
+ if (!interactive)
+ dup2(pin[0], STDIN_FILENO);
+ close(pin[0]);
+ close(pin[1]);
+ dup2(pout[1], STDOUT_FILENO);
+ close(pout[1]);
+ close(pout[0]);
+ if (!interactive)
+ dup2(perr[1], STDERR_FILENO);
+ close(perr[0]);
+ close(perr[1]);
+ if (!argv[2])
+ execl("/bin/sh", "sh", "-c", argv[1], NULL);
+ else
+ execvp(argv[1], (char**)argv+1);
+ editor_info_show(vis, "exec failure: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* set up a signal handler to cancel the filter via CTRL-C */
+ struct sigaction sa, oldsa;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = cancel_filter;
+
+ bool restore_signals = sigaction(SIGINT, &sa, &oldsa) == 0;
+ vis->cancel_filter = false;
+
+ close(pin[0]);
+ close(pout[1]);
+ close(perr[1]);
+
+ fcntl(pout[0], F_SETFL, O_NONBLOCK);
+ fcntl(perr[0], F_SETFL, O_NONBLOCK);
+
+ if (interactive)
+ range = &(Filerange){ .start = pos, .end = pos };
+
+ /* ranges which are written to the filter and read back in */
+ Filerange rout = *range;
+ Filerange rin = (Filerange){ .start = range->end, .end = range->end };
+
+ /* The general idea is the following:
+ *
+ * 1) take a snapshot
+ * 2) write [range.start, range.end] to exteneral command
+ * 3) read the output of the external command and insert it after the range
+ * 4) depending on the exit status of the external command
+ * - on success: delete original range
+ * - on failure: revert to previous snapshot
+ *
+ * 2) and 3) happend in small junks
+ */
+
+ text_snapshot(text);
+
+ fd_set rfds, wfds;
+ Buffer errmsg;
+ buffer_init(&errmsg);
+
+ do {
+ if (vis->cancel_filter) {
+ kill(-pid, SIGTERM);
+ editor_info_show(vis, "Command cancelled");
+ break;
+ }
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ if (pin[1] != -1)
+ FD_SET(pin[1], &wfds);
+ if (pout[0] != -1)
+ FD_SET(pout[0], &rfds);
+ if (perr[0] != -1)
+ FD_SET(perr[0], &rfds);
+
+ if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
+ if (errno == EINTR)
+ continue;
+ editor_info_show(vis, "Select failure");
+ break;
+ }
+
+ if (FD_ISSET(pin[1], &wfds)) {
+ Filerange junk = *range;
+ if (junk.end > junk.start + PIPE_BUF)
+ junk.end = junk.start + PIPE_BUF;
+ ssize_t len = text_range_write(text, &junk, pin[1]);
+ if (len > 0) {
+ range->start += len;
+ if (text_range_size(range) == 0) {
+ close(pout[1]);
+ pout[1] = -1;
+ }
+ } else {
+ close(pin[1]);
+ pin[1] = -1;
+ if (len == -1)
+ editor_info_show(vis, "Error writing to external command");
+ }
+ }
+
+ if (FD_ISSET(pout[0], &rfds)) {
+ char buf[BUFSIZ];
+ ssize_t len = read(pout[0], buf, sizeof buf);
+ if (len > 0) {
+ text_insert(text, rin.end, buf, len);
+ rin.end += len;
+ } else if (len == 0) {
+ close(pout[0]);
+ pout[0] = -1;
+ } else if (errno != EINTR && errno != EWOULDBLOCK) {
+ editor_info_show(vis, "Error reading from filter stdout");
+ close(pout[0]);
+ pout[0] = -1;
+ }
+ }
+
+ if (FD_ISSET(perr[0], &rfds)) {
+ char buf[BUFSIZ];
+ ssize_t len = read(perr[0], buf, sizeof buf);
+ if (len > 0) {
+ buffer_append(&errmsg, buf, len);
+ } else if (len == 0) {
+ close(perr[0]);
+ perr[0] = -1;
+ } else if (errno != EINTR && errno != EWOULDBLOCK) {
+ editor_info_show(vis, "Error reading from filter stderr");
+ close(pout[0]);
+ pout[0] = -1;
+ }
+ }
+
+ } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
+
+ if (pin[1] != -1)
+ close(pin[1]);
+ if (pout[0] != -1)
+ close(pout[0]);
+ if (perr[0] != -1)
+ close(perr[0]);
+
+ if (waitpid(pid, &status, 0) == pid && status == 0) {
+ text_delete(text, rout.start, rout.end - rout.start);
+ text_snapshot(text);
+ } else {
+ /* make sure we have somehting to undo */
+ text_insert(text, pos, " ", 1);
+ text_undo(text);
+ }
+
+ view_cursor_to(view, rout.start);
+
+ if (!vis->cancel_filter) {
+ if (status == 0)
+ editor_info_show(vis, "Command succeded");
+ else if (errmsg.len > 0)
+ editor_info_show(vis, "Command failed: %s", errmsg.data);
+ else
+ editor_info_show(vis, "Command failed");
+ }
+
+ if (restore_signals)
+ sigaction(SIGTERM, &oldsa, NULL);
+
+ reset_prog_mode();
+ wclear(stdscr);
+ return status == 0;
+}
+
static Filepos parse_pos(char **cmd) {
size_t pos = EPOS;
View *view = vis->win->view;
@@ -1983,6 +2198,7 @@ static void mainloop() {
sigemptyset(&blockset);
sigaddset(&blockset, SIGWINCH);
sigprocmask(SIG_BLOCK, &blockset, NULL);
+ signal(SIGPIPE, SIG_IGN);
editor_draw(vis);
vis->running = true;