---
The code for the read/write loops communicating through a pipe with the
external process is very ugly. There must be a better way to implement
the pipe communication but I could not figure it out.
This approach uses a unnecessary amount of memory too since it keeps a lot
of the input data for the external process as well as its output data
in memory at the same time. When testing it on a text file 160MB in size
it seemed to work ok though.
Using a call to 'sed' to implement cmd_substitute seems like a good
way to do it since the exec_external_cmd function can be used for other
purposes as well.
vis.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 181 insertions(+), 1 deletion(-)
diff --git a/vis.c b/vis.c
index 201d88a..e2da4cd 100644
--- a/vis.c
+++ b/vis.c
_AT_@ -30,6 +30,7 @@
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <sys/wait.h>
#include "ui-curses.h"
#include "editor.h"
_AT_@ -413,6 +414,14 @@ 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[]);
+/* Run external commands by forking a child in which to start them. The
+ * bytes from range will be sent to the stdin of the external program and
+ * the stdout will replace the bytes in the range after the program has
+ * finished */
+static bool exec_external_cmd(Filerange *range, enum CmdOpt opt, char * const argv[]);
+/* Run external commands by forking a child in which to start them. Vis
+ * will wait for the program to return before continuing */
+static bool exec_external_cmd_without_range(enum CmdOpt opt, char * const argv[]);
static void action_reset(Action *a);
static void switchmode_to(Mode *new_mode);
_AT_@ -1561,8 +1570,179 @@ static bool cmd_read(Filerange *range, enum CmdOpt opt, const char *argv[]) {
return true;
}
+static bool exec_external_cmd(Filerange *range, enum CmdOpt opt, char * const argv[]) {
+ pid_t pid = 0;
+ int outputpipe[2], inputpipe[2], pret, readcount;
+ char *inbuf;
+ char tmpbuf[BUFSIZ];
+ size_t totalread, inputlen, curpos;
+
+ if (!range)
+ return exec_external_cmd_without_range(opt, argv);
+
+ Buffer *outbuf = calloc(1, sizeof(Buffer));
+
+ pret = pipe(outputpipe);
+ if (pret < 0) {
+ editor_info_show(vis, "Could not make the output pipe for the external command.");
+ return false;
+ }
+ pret = pipe(inputpipe);
+ if (pret < 0) {
+ editor_info_show(vis, "Could not make the input pipe for the external command.");
+ return false;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ editor_info_show(vis, "Could not start the external command.");
+ return false;
+ }
+
+ if (pid == 0) {
+ close(outputpipe[0]);
+ dup2(outputpipe[1], STDOUT_FILENO);
+
+ close(inputpipe[1]);
+ dup2(inputpipe[0], STDIN_FILENO);
+
+ execvp(argv[0], argv);
+ }
+
+ close(inputpipe[0]);
+
+ Text *text = vis->win->file->text;
+ inputlen = range->end-range->start;
+
+ inbuf = malloc(inputlen);
+ if (!inbuf) {
+ editor_info_show(vis, "Could not allocate input buffer for external program.");
+ return false;
+ }
+
+ // Save cursor position in order to restore it after replacing
+ // the filtered text.
+ View *view = vis->win->view;
+ CursorPos curspos = view_cursor_getpos(view);
+ text_bytes_get(text, range->start, inputlen, inbuf);
+
+ int wrote = 0;
+ int totalwritten = 0;
+ int towrite = inputlen;
+
+ readcount = totalread = 0;
+
+ close(outputpipe[1]);
+ if (!buffer_alloc(outbuf, BUFSIZ)) {
+ editor_info_show(vis, "Could not allocate output buffer for external program.");
+ return false;
+ }
+
+ int fret = fcntl(outputpipe[0], F_SETFL, O_NONBLOCK);
+ if (fret < 0) {
+ editor_info_show(vis, "Could not set output pipe to nonblocking.");
+ close(inputpipe[1]);
+ close(outputpipe[0]);
+ return false;
+ }
+
+ fret = fcntl(inputpipe[1], F_SETFL, O_NONBLOCK);
+ if (fret < 0) {
+ editor_info_show(vis, "Could not set input pipe to nonblocking.");
+ close(inputpipe[1]);
+ close(outputpipe[0]);
+ return false;
+ }
+
+ // TODO: This is fugly and should be replaced with a better
+ // version...
+ // In order not to starve the external command or getting stuck when
+ // reading from its stdout we write and read from it in parallel and
+ // asynchronously. As soon as we have written all the text data to
+ // it, we restart another reading loop until the output pipe has
+ // seen an EOF (when read returns zero).
+ while (towrite > totalwritten) {
+ wrote = write(inputpipe[1], inbuf+totalwritten, towrite-totalwritten);
+ if (wrote > 0)
+ totalwritten += wrote;
+
+ readcount = read(outputpipe[0], tmpbuf, sizeof(tmpbuf));
+ if (readcount < 0)
+ continue;
+ totalread += readcount;
+ buffer_append(outbuf, tmpbuf, readcount);
+ }
+ close(inputpipe[1]);
+ free(inbuf);
+
+ while (readcount != 0) {
+ readcount = read(outputpipe[0], tmpbuf, sizeof(tmpbuf));
+ if (readcount < 0)
+ continue;
+
+ totalread += readcount;
+ buffer_append(outbuf, tmpbuf, readcount);
+ }
+ close(outputpipe[0]);
+
+
+ editor_delete(vis, range->start, inputlen);
+ editor_insert(vis, range->start, outbuf->data, totalread);
+
+ buffer_free(outbuf);
+ free(outbuf);
+
+ curpos = text_pos_by_lineno(text, curspos.line);
+ view_cursor_to(view, curpos);
+
+ return true;
+}
+
+static bool exec_external_cmd_without_range(enum CmdOpt opt, char * const argv[]) {
+ pid_t pid = 0;
+ int status;
+
+ pid = fork();
+ if (pid < 0) {
+ editor_info_show(vis, "Could not fork to start the external command.");
+ return false;
+ }
+
+ if (pid == 0) {
+ int null = open("/dev/null", O_WRONLY);
+ if (null < 0)
+ return false;
+ dup2(null, STDOUT_FILENO);
+ execvp(argv[0], argv);
+ }
+
+ // Wait for the child to return.
+ waitpid(pid, &status, 0);
+ return status;
+}
+
static bool cmd_substitute(Filerange *range, enum CmdOpt opt, const char *argv[]) {
- // TODO
+ // Add back the slash we removed before to get the command
+ // recognized.
+ char *cmd;
+ cmd = (char *)argv[0];
+ *(++cmd) = '/';
+
+ while (*cmd) cmd++;
+ switch(*(cmd-1)) {
+ case '/':
+ case 'i':
+ case 'g':
+ break;
+
+ default:
+ editor_info_show(vis, "Sed command not properly terminated.");
+ return false;
+ }
+
+ char * const sedcmd[] = {"sed", (char *)argv[0], (char *)NULL};
+ exec_external_cmd(range, opt, sedcmd);
+
return true;
}
--
2.3.7
Received on Tue May 05 2015 - 18:25:30 CEST
This archive was generated by hypermail 2.3.0 : Tue May 05 2015 - 18:36:28 CEST