Skip to content

Commit

Permalink
support piping a buffer to an external process
Browse files Browse the repository at this point in the history
Currently only Text objects can be piped to external commands.
This is tedious if data not available in any file should be passed
to an external process (e.g. building options and passing them to
vis-menu).

This adds the option to pass a buffer to _vis_pipe and provides wrapper
functions for the original behavior and the new one.
  • Loading branch information
fischerling authored and rnpnr committed Sep 13, 2024
1 parent d8276d9 commit c56b57f
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 22 deletions.
2 changes: 1 addition & 1 deletion text-io.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Text *text_load_method(const char *filename, enum TextLoadMethod method) {
return text_loadat_method(AT_FDCWD, filename, method);
}

static ssize_t write_all(int fd, const char *buf, size_t count) {
ssize_t write_all(int fd, const char *buf, size_t count) {
size_t rem = count;
while (rem > 0) {
ssize_t written = write(fd, buf, rem > INT_MAX ? INT_MAX : rem);
Expand Down
6 changes: 6 additions & 0 deletions text.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@ ssize_t text_write_range(const Text*, const Filerange*, int fd);
* this text instance.
*/
bool text_mmaped(const Text*, const char *ptr);

/**
* Write complete buffer to file descriptor.
* @return The number of bytes written or ``-1`` in case of an error.
*/
ssize_t write_all(int fd, const char *buf, size_t count);
/** @} */

#endif
30 changes: 27 additions & 3 deletions vis-lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1234,25 +1234,49 @@ static int exit_func(lua_State *L) {
* @treturn string stdout the data written to stdout
* @treturn string stderr the data written to stderr
*/
/***
* Pipe a string to external process and collect output.
*
* The editor core will be blocked while the external process is running.
*
* @function pipe
* @tparam string text the text written to the external command
* @tparam string command the command to execute
* @tparam[opt] bool fullscreen whether command is a fullscreen program (e.g. curses based)
* @treturn int code the exit status of the executed command
* @treturn string stdout the data written to stdout
* @treturn string stderr the data written to stderr
*/
static int pipe_func(lua_State *L) {
Vis *vis = obj_ref_check(L, 1, "vis");
int cmd_idx = 4;
char *out = NULL, *err = NULL;
const char *text = NULL;
File *file = vis->win ? vis->win->file : NULL;
Filerange range = text_range_new(0, 0);
if (lua_gettop(L) <= 3) {
if (lua_gettop(L) == 2) {
cmd_idx = 2;
} else if (lua_gettop(L) == 3) {
text = luaL_checkstring(L, 2);
if (text != NULL)
cmd_idx = 3;
else
cmd_idx = 2;
} else if (!(lua_isnil(L, 2) && lua_isnil(L, 3))) {
file = obj_ref_check(L, 2, VIS_LUA_TYPE_FILE);
range = getrange(L, 3);
}
const char *cmd = luaL_checkstring(L, cmd_idx);
bool fullscreen = lua_isboolean(L, cmd_idx + 1) && lua_toboolean(L, cmd_idx + 1);

if (!file)
if (!text && !file)
return luaL_error(L, "vis:pipe(cmd = '%s'): win not open, file can't be nil", cmd);

int status = vis_pipe_collect(vis, file, &range, (const char*[]){ cmd, NULL }, &out, &err, fullscreen);
int status;
if (text)
status = vis_pipe_buf_collect(vis, text, (const char*[]){ cmd, NULL }, &out, &err, fullscreen);
else
status = vis_pipe_collect(vis, file, &range, (const char*[]){ cmd, NULL }, &out, &err, fullscreen);
lua_pushinteger(L, status);
if (out)
lua_pushstring(L, out);
Expand Down
74 changes: 56 additions & 18 deletions vis.c
Original file line number Diff line number Diff line change
Expand Up @@ -1593,17 +1593,17 @@ Regex *vis_regex(Vis *vis, const char *pattern) {
return regex;
}

int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
static int _vis_pipe(Vis *vis, File *file, Filerange *range, const char* buf, const char *argv[],
void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len),
bool fullscreen) {

/* if an invalid range was given, stdin (i.e. key board input) is passed
* through the external command. */
Text *text = file->text;
Text *text = file != NULL ? file->text : NULL;
int pin[2], pout[2], perr[2], status = -1;
bool interactive = !text_range_valid(range);
Filerange rout = interactive ? text_range_new(0, 0) : *range;
bool interactive = buf == NULL && (range == NULL || !text_range_valid(range));
Filerange rout = (interactive || buf != NULL) ? text_range_new(0, 0) : *range;

if (pipe(pin) == -1)
return -1;
Expand Down Expand Up @@ -1654,7 +1654,7 @@ int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
* closed. Some programs behave differently when used
* in a pipeline.
*/
if (text_range_size(range) == 0)
if (range && text_range_size(range) == 0)
dup2(null, STDIN_FILENO);
else
dup2(pin[0], STDIN_FILENO);
Expand Down Expand Up @@ -1688,7 +1688,7 @@ int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
close(perr[1]);
close(null);

if (file->name) {
if (file != NULL && file->name) {
char *name = strrchr(file->name, '/');
setenv("vis_filepath", file->name, 1);
setenv("vis_filename", name ? name+1 : file->name, 1);
Expand Down Expand Up @@ -1737,20 +1737,36 @@ int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
}

if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
ssize_t written = 0;
Filerange junk = rout;
if (junk.end > junk.start + PIPE_BUF)
junk.end = junk.start + PIPE_BUF;
ssize_t len = text_write_range(text, &junk, pin[1]);
if (len > 0) {
rout.start += len;
if (text_range_size(&rout) == 0) {
close(pin[1]);
pin[1] = -1;
if (text_range_size(&rout)) {
if (junk.end > junk.start + PIPE_BUF)
junk.end = junk.start + PIPE_BUF;
written = text_write_range(text, &junk, pin[1]);
if (written > 0) {
rout.start += written;
if (text_range_size(&rout) == 0) {
close(pin[1]);
pin[1] = -1;
}
}
} else {
} else if (buf != NULL) {
size_t len = strlen(buf);
if (len > 0) {
if (len > PIPE_BUF)
len = PIPE_BUF;

written = write_all(pin[1], buf, len);
if (written > 0) {
buf += written;
}
}
}

if (written <= 0) {
close(pin[1]);
pin[1] = -1;
if (len == -1)
if (written == -1)
vis_info_show(vis, "Error writing to external command");
}
}
Expand Down Expand Up @@ -1823,16 +1839,30 @@ int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
return -1;
}

int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len),
bool fullscreen) {
return _vis_pipe(vis, file, range, NULL, argv, stdout_context, read_stdout, stderr_context, read_stderr, fullscreen);
}

int vis_pipe_buf(Vis *vis, const char* buf, const char *argv[],
void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len),
bool fullscreen) {
return _vis_pipe(vis, NULL, NULL, buf, argv, stdout_context, read_stdout, stderr_context, read_stderr, fullscreen);
}

static ssize_t read_buffer(void *context, char *data, size_t len) {
buffer_append(context, data, len);
return len;
}

int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err, bool fullscreen) {
static int _vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char* buf, const char *argv[], char **out, char **err, bool fullscreen) {
Buffer bufout, buferr;
buffer_init(&bufout);
buffer_init(&buferr);
int status = vis_pipe(vis, file, range, argv,
int status = _vis_pipe(vis, file, range, buf, argv,
&bufout, out ? read_buffer : NULL,
&buferr, err ? read_buffer : NULL,
fullscreen);
Expand All @@ -1847,6 +1877,14 @@ int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[],
return status;
}

int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err, bool fullscreen) {
return _vis_pipe_collect(vis, file, range, NULL, argv, out, err, fullscreen);
}

int vis_pipe_buf_collect(Vis *vis, const char* buf, const char *argv[], char **out, char **err, bool fullscreen) {
return _vis_pipe_collect(vis, NULL, NULL, buf, argv, out, err, fullscreen);
}

bool vis_cmd(Vis *vis, const char *cmdline) {
if (!cmdline)
return true;
Expand Down
14 changes: 14 additions & 0 deletions vis.h
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,20 @@ int vis_pipe(Vis*, File*, Filerange*, const char *argv[],
*/
int vis_pipe_collect(Vis*, File*, Filerange*, const char *argv[], char **out, char **err, bool fullscreen);

/**
* Pipe a buffer to an external process, return its exit status and capture
* everything that is written to stdout/stderr.
* @param argv Argument list, must be ``NULL`` terminated.
* @param out Data written to ``stdout``, will be ``NUL`` terminated.
* @param err Data written to ``stderr``, will be ``NUL`` terminated.
* @param fullscreen Whether the external process is a fullscreen program (e.g. curses based)
* @rst
* .. warning:: The pointers stored in ``out`` and ``err`` need to be `free(3)`-ed
* by the caller.
* @endrst
*/
int vis_pipe_buf_collect(Vis*, const char*, const char *argv[], char **out, char **err, bool fullscreen);

/**
* @}
* @defgroup vis_keys
Expand Down

0 comments on commit c56b57f

Please sign in to comment.