aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2024-05-22 11:37:50 +0200
committerRandy Palamar <randy@rnpnr.xyz>2024-09-13 06:03:59 -0600
commitc56b57fc58c73e96def91f4904d5b6e7c50e3de9 (patch)
treeb0918f3a6b234e4bd9ff5e5d86c2aa09ea622179
parentd8276d916d875695a1000e14a6d1dfd03e689c81 (diff)
downloadvis-c56b57fc58c73e96def91f4904d5b6e7c50e3de9.tar.gz
vis-c56b57fc58c73e96def91f4904d5b6e7c50e3de9.tar.xz
support piping a buffer to an external process
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.
-rw-r--r--text-io.c2
-rw-r--r--text.h6
-rw-r--r--vis-lua.c30
-rw-r--r--vis.c74
-rw-r--r--vis.h14
5 files changed, 104 insertions, 22 deletions
diff --git a/text-io.c b/text-io.c
index 509df35..97fc138 100644
--- a/text-io.c
+++ b/text-io.c
@@ -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);
diff --git a/text.h b/text.h
index 048aef7..3249e21 100644
--- a/text.h
+++ b/text.h
@@ -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
diff --git a/vis-lua.c b/vis-lua.c
index e3e85c3..0a435b4 100644
--- a/vis-lua.c
+++ b/vis-lua.c
@@ -1234,14 +1234,34 @@ 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);
@@ -1249,10 +1269,14 @@ static int pipe_func(lua_State *L) {
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);
diff --git a/vis.c b/vis.c
index 7d3dd6a..10c3df5 100644
--- a/vis.c
+++ b/vis.c
@@ -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;
@@ -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);
@@ -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);
@@ -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");
}
}
@@ -1823,16 +1839,30 @@ err:
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);
@@ -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;
diff --git a/vis.h b/vis.h
index eb8ec92..1a249ee 100644
--- a/vis.h
+++ b/vis.h
@@ -883,6 +883,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
* @{