--- 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.7Received 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