Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

supporting piping a buffer to an external process #1204

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lua/plugins/complete-word.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ vis:map(vis.modes.INSERT, "<C-n>", function()
if #candidates == 1 and candidates[1] == "\n" then return end
candidates = table.concat(candidates, "\n")

local cmd = "printf '" .. candidates .. "' | sort -u | vis-menu"
local status, out, err = vis:pipe(cmd)
local status, out, err = vis:pipe(candidates, "sort -u | vis-menu")
if status ~= 0 or not out then
if err then vis:info(err) end
return
Expand Down
1 change: 1 addition & 0 deletions test/lua/pipe.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
86 changes: 86 additions & 0 deletions test/lua/pipe.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require 'busted.runner'()

local file = vis.win.file

describe("vis.pipe", function()

local FULLSCREEN = true

it("vis.pipe no input", function()
vis:pipe("cat > f")
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal("", f:read("*a"))
f:close()
os.remove("f")
end)

it("vis.pipe no input fullscreen", function()
vis:pipe("cat > f", FULLSCREEN)
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal("", f:read("*a"))
f:close()
os.remove("f")
end)

it("vis.pipe buffer", function()
vis:pipe("foo", "cat > f")
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal(f:read("*a"), "foo")
f:close()
os.remove("f")
end)

it("vis.pipe buffer fullscreen", function()
vis:pipe("foo", "cat > f", FULLSCREEN)
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal(f:read("*a"), "foo")
f:close()
os.remove("f")
end)

it("vis.pipe range", function()
vis:pipe(file, {start=0, finish=3}, "cat > f")
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal(f:read("*a"), "foo")
f:close()
os.remove("f")
end)

it("vis.pipe range fullscreen", function()
vis:pipe(file, {start=0, finish=3}, "cat > f", FULLSCREEN)
local f = io.open("f", "r")
assert.truthy(f)
assert.are.equal(f:read("*a"), "foo")
f:close()
os.remove("f")
end)

it("vis.pipe explicit nil text", function()
assert.has_error(function() vis:pipe(nil, "true") end)
end)

it("vis.pipe explicit nil text fullscreen", function()
assert.has_error(function() vis:pipe(nil, "true", FULLSCREEN) end)
end)

it("vis.pipe explicit nil file", function()
assert.has_error(function() vis:pipe(nil, {start=0, finish=0}, "true") end)
end)

it("vis.pipe explicit nil file fullscreen", function()
assert.has_error(function() vis:pipe(nil, {start=0, finish=0}, "true", FULLSCREEN) end)
end)

it("vis.pipe wrong argument order file, range, cmd", function()
assert.has_error(function() vis:pipe({start=0, finish=0}, vis.win.file, "true") end)
end)

it("vis.pipe wrong argument order fullscreen, cmd", function()
assert.has_error(function() vis:pipe(FULLSCREEN, "true") end)
end)
end)
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
36 changes: 32 additions & 4 deletions vis-lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1234,25 +1234,53 @@ 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) { // vis:pipe(cmd)
cmd_idx = 2;
} else if (!(lua_isnil(L, 2) && lua_isnil(L, 3))) {
} else if (lua_gettop(L) == 3) {
if (lua_isboolean(L, 3)) { // vis:pipe(cmd, fullscreen)
cmd_idx = 2;
} else { // vis:pipe(text, cmd)
text = luaL_checkstring(L, 2);
cmd_idx = 3;
}
} else if (lua_isboolean(L, 4)) { // vis:pipe(text, cmd, fullscreen)
text = luaL_checkstring(L, 2);
cmd_idx = 3;
} else if (!(lua_isnil(L, 2) && lua_isnil(L, 3))) { // vis:pipe(file, range, cmd, [fullscreen])
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
Loading