From 53947249650197a3ee54fbf2c59aff28ced6b49680e8102df75ccfb37a7345b0 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Tue, 28 Feb 2023 11:12:24 +0000 Subject: [PATCH] - Add 675-nb-subproc-runner.patch (gh#martanne/vis!675) with support for the non-blocking subprocess runner. OBS-URL: https://build.opensuse.org/package/show/editors/vis?expand=0&rev=19 --- 675-nb-subproc-runner.patch | 425 ++++++++++++++++++++++++++++++++++++ vis.changes | 6 + vis.spec | 9 +- 3 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 675-nb-subproc-runner.patch diff --git a/675-nb-subproc-runner.patch b/675-nb-subproc-runner.patch new file mode 100644 index 0000000..51c68a4 --- /dev/null +++ b/675-nb-subproc-runner.patch @@ -0,0 +1,425 @@ +From 25713f5bf36f6a33333793e963142abe3753c9ab Mon Sep 17 00:00:00 2001 +From: xomachine +Date: Sun, 25 Feb 2018 01:55:48 +0300 +Subject: [PATCH 01/19] Subprocess lua API extension + +--- + Makefile | 1 + lua/vis.lua | 2 + vis-lua.c | 82 +++++++++++++++++++++++++ + vis-lua.h | 4 - + vis-subprocess.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + vis-subprocess.h | 23 +++++++ + vis.c | 5 + + 7 files changed, 291 insertions(+), 2 deletions(-) + create mode 100644 vis-subprocess.c + create mode 100644 vis-subprocess.h + +--- a/Makefile ++++ b/Makefile +@@ -26,6 +26,7 @@ SRC = array.c \ + vis-prompt.c \ + vis-registers.c \ + vis-text-objects.c \ ++ vis-subprocess.c \ + $(REGEX_SRC) + + ELF = vis vis-menu vis-digraph +--- a/lua/vis.lua ++++ b/lua/vis.lua +@@ -152,6 +152,7 @@ local events = { + WIN_OPEN = "Event::WIN_OPEN", -- see @{win_open} + WIN_STATUS = "Event::WIN_STATUS", -- see @{win_status} + TERM_CSI = "Event::TERM_CSI", -- see @{term_csi} ++ PROCESS_RESPONSE = "Event::PROCESS_RESPONSE", -- see @{process_response} + } + + events.file_close = function(...) events.emit(events.FILE_CLOSE, ...) end +@@ -167,6 +168,7 @@ events.win_highlight = function(...) eve + events.win_open = function(...) events.emit(events.WIN_OPEN, ...) end + events.win_status = function(...) events.emit(events.WIN_STATUS, ...) end + events.term_csi = function(...) events.emit(events.TERM_CSI, ...) end ++events.process_response = function(...) events.emit(events.PROCESS_RESPONSE, ...) end + + local handlers = {} + +--- a/vis-lua.c ++++ b/vis-lua.c +@@ -23,6 +23,7 @@ + + #include "vis-lua.h" + #include "vis-core.h" ++#include "vis-subprocess.h" + #include "text-motions.h" + #include "util.h" + +@@ -52,6 +53,13 @@ + #define debug(...) do { } while (0) + #endif + ++typedef struct { ++ /* Lua stream structure for the process input stream */ ++ FILE *f; ++ lua_CFunction closef; ++ Process *handler; ++} ProcessStream; ++ + static void window_status_update(Vis *vis, Win *win) { + char left_parts[4][255] = { "", "", "", "" }; + char right_parts[4][32] = { "", "", "", "" }; +@@ -162,6 +170,9 @@ void vis_lua_win_close(Vis *vis, Win *wi + void vis_lua_win_highlight(Vis *vis, Win *win) { } + void vis_lua_win_status(Vis *vis, Win *win) { window_status_update(vis, win); } + void vis_lua_term_csi(Vis *vis, const long *csi) { } ++void vis_lua_process_response(Vis *vis, const char *name, ++ char *buffer, size_t len, ResponseType rtype) { } ++ + + #else + +@@ -1368,6 +1379,47 @@ static int redraw(lua_State *L) { + return 0; + } + /*** ++ * Closes a stream returned by @{Vis.communicate}. ++ * ++ * @function close ++ * @tparam io.file inputfd the stream to be closed ++ * @treturn bool the same with @{io.close} ++ */ ++static int close_subprocess(lua_State *L) { ++ luaL_Stream *file = luaL_checkudata(L, -1, "FILE*"); ++ int result = fclose(file->f); ++ if (result == 0) { ++ file->f = NULL; ++ file->closef = NULL; ++ } ++ return luaL_fileresult(L, result == 0, NULL); ++} ++/*** ++ * Open new process and return its input handler. ++ * When the process will quit or will output anything to stdout or stderr, ++ * the @{process_response} event will be fired. ++ * ++ * The editor core won't be blocked while the external process is running. ++ * ++ * @function communicate ++ * @tparam string name the name of subprocess (to distinguish processes in the @{process_response} event) ++ * @tparam string command the command to execute ++ * @return the file handle to write data to the process, in case of error the return values are equivalent to @{io.open} error values. ++ */ ++static int communicate_func(lua_State *L) { ++ Vis *vis = obj_ref_check(L, 1, "vis"); ++ const char *name = luaL_checkstring(L, 2); ++ const char *cmd = luaL_checkstring(L, 3); ++ ProcessStream *inputfd = (ProcessStream *)lua_newuserdata(L, sizeof(ProcessStream)); ++ luaL_setmetatable(L, LUA_FILEHANDLE); ++ inputfd->handler = vis_process_communicate(vis, name, cmd, (void **)(&(inputfd->closef))); ++ if (inputfd->handler) { ++ inputfd->f = fdopen(inputfd->handler->inpfd, "w"); ++ inputfd->closef = &close_subprocess; ++ } ++ return inputfd->f ? 1 : luaL_fileresult(L, inputfd->f != NULL, name); ++} ++/*** + * Currently active window. + * @tfield Window win + * @see windows +@@ -1524,6 +1576,7 @@ static const struct luaL_Reg vis_lua[] = + { "exit", exit_func }, + { "pipe", pipe_func }, + { "redraw", redraw }, ++ { "communicate", communicate_func }, + { "__index", vis_index }, + { "__newindex", vis_newindex }, + { NULL, NULL }, +@@ -3135,5 +3188,34 @@ void vis_lua_term_csi(Vis *vis, const lo + } + lua_pop(L, 1); + } ++/*** ++ * The response received from the process started via @{Vis:communicate}. ++ * @function process_response ++ * @tparam string name the name of process given to @{Vis:communicate} ++ * @tparam string response_type can be "STDOUT" or "STDERR" if new output was received in corresponding channel, "SIGNAL" if the process was terminated by a signal or "EXIT" when the process terminated normally ++ * @tparam string|int buffer the available content sent by process; it becomes the exit code number if response\_type is "EXIT", or the signal number if response\_type is "SIGNAL" ++ */ ++void vis_lua_process_response(Vis *vis, const char *name, ++ char *buffer, size_t len, ResponseType rtype) { ++ lua_State *L = vis->lua; ++ if (!L) ++ return; ++ vis_lua_event_get(L, "process_response"); ++ if (lua_isfunction(L, -1)) { ++ lua_pushstring(L, name); ++ if (rtype == EXIT || rtype == SIGNAL) ++ lua_pushinteger(L, len); ++ else ++ lua_pushlstring(L, buffer, len); ++ switch (rtype){ ++ case STDOUT: lua_pushstring(L, "STDOUT"); break; ++ case STDERR: lua_pushstring(L, "STDERR"); break; ++ case SIGNAL: lua_pushstring(L, "SIGNAL"); break; ++ case EXIT: lua_pushstring(L, "EXIT"); break; ++ } ++ pcall(vis, L, 3, 0); ++ } ++ lua_pop(L, 1); ++} + + #endif +--- a/vis-lua.h ++++ b/vis-lua.h +@@ -7,10 +7,11 @@ + #include + #else + typedef struct lua_State lua_State; ++typedef void* lua_CFunction; + #endif + + #include "vis.h" +- ++#include "vis-subprocess.h" + /* add a directory to consider when loading lua files */ + bool vis_lua_path_add(Vis*, const char *path); + /* get semicolon separated list of paths to load lua files +@@ -38,5 +39,6 @@ void vis_lua_win_close(Vis*, Win*); + void vis_lua_win_highlight(Vis*, Win*); + void vis_lua_win_status(Vis*, Win*); + void vis_lua_term_csi(Vis*, const long *); ++void vis_lua_process_response(Vis *, const char *, char *, size_t, ResponseType); + + #endif +--- /dev/null ++++ b/vis-subprocess.c +@@ -0,0 +1,176 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "vis-lua.h" ++#include "vis-subprocess.h" ++ ++/* Maximum amount of data what can be read from IPC pipe per event */ ++#define MAXBUFFER 1024 ++ ++/* Pool of information about currently running subprocesses */ ++static Process *process_pool; ++ ++Process *new_in_pool() { ++ /* Adds new empty process information structure to the process pool and ++ * returns it */ ++ Process *newprocess = (Process *)malloc(sizeof(Process)); ++ if (!newprocess) return NULL; ++ newprocess->next = process_pool; ++ process_pool = newprocess; ++ return newprocess; ++} ++ ++void destroy(Process **pointer) { ++ /* Removes the subprocess information from the pool, sets invalidator to NULL ++ * and frees resources. */ ++ Process *target = *pointer; ++ if (target->outfd != -1) close(target->outfd); ++ if (target->errfd != -1) close(target->errfd); ++ if (target->inpfd != -1) close(target->inpfd); ++ /* marking stream as closed for lua */ ++ if (target->invalidator) *(target->invalidator) = NULL; ++ if (target->name) free(target->name); ++ *pointer = target->next; ++ free(target); ++} ++ ++Process *vis_process_communicate(Vis *vis, const char *name, ++ const char *command, void **invalidator) { ++ /* Starts new subprocess by passing the `command` to the shell and ++ * returns the subprocess information structure, containing file descriptors ++ * of the process. ++ * Also stores the subprocess information to the internal pool to track ++ * its status and responses. ++ * `name` - the string than should contain an unique name of the subprocess. ++ * This name will be passed to the PROCESS_RESPONSE event handler ++ * to distinguish running subprocesses. ++ * `invalidator` - a pointer to the pointer which shows that the subprocess ++ * is invalid when set to NULL. When subprocess dies, it is being set to NULL. ++ * If the pointer is set to NULL by an external code, the subprocess will be ++ * killed on the next main loop iteration. */ ++ int pin[2], pout[2], perr[2]; ++ pid_t pid = (pid_t)-1; ++ if (pipe(perr) == -1) goto closeerr; ++ if (pipe(pout) == -1) goto closeouterr; ++ if (pipe(pin) == -1) goto closeall; ++ pid = fork(); ++ if (pid == -1) ++ vis_info_show(vis, "fork failed: %s", strerror(errno)); ++ else if (pid == 0){ /* child process */ ++ sigset_t sigterm_mask; ++ sigemptyset(&sigterm_mask); ++ sigaddset(&sigterm_mask, SIGTERM); ++ if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) { ++ fprintf(stderr, "failed to reset signal mask"); ++ exit(EXIT_FAILURE); ++ } ++ dup2(pin[0], STDIN_FILENO); ++ dup2(pout[1], STDOUT_FILENO); ++ dup2(perr[1], STDERR_FILENO); ++ } ++ else { /* main process */ ++ Process *new = new_in_pool(); ++ if (!new) { ++ vis_info_show(vis, "Can not create process: %s", strerror(errno)); ++ goto closeall; ++ } ++ new->name = strdup(name); ++ if (!new->name) { ++ vis_info_show(vis, "Can not copy process name: %s", strerror(errno)); ++ /* pop top element (which is `new`) from the pool */ ++ destroy(&process_pool); ++ goto closeall; ++ } ++ new->outfd = pout[0]; ++ new->errfd = perr[0]; ++ new->inpfd = pin[1]; ++ new->pid = pid; ++ new->invalidator = invalidator; ++ close(pin[0]); ++ close(pout[1]); ++ close(perr[1]); ++ return new; ++ } ++closeall: ++ close(pin[0]); ++ close(pin[1]); ++closeouterr: ++ close(pout[0]); ++ close(pout[1]); ++closeerr: ++ close(perr[0]); ++ close(perr[1]); ++ if (pid == 0) { /* start command in child process */ ++ execlp(vis->shell, vis->shell, "-c", command, (char*)NULL); ++ fprintf(stderr, "exec failed: %s(%d)\n", strerror(errno), errno); ++ exit(1); ++ } ++ else ++ vis_info_show(vis, "process creation failed: %s", strerror(errno)); ++ return NULL; ++} ++ ++int vis_process_before_tick(fd_set *readfds) { ++ /* Adds file descriptors of currently running subprocesses to the `readfds` ++ * to track their readiness and returns maximum file descriptor value ++ * to pass it to the `pselect` call */ ++ Process **pointer = &process_pool; ++ int maxfd = 0; ++ while (*pointer) { ++ Process *current = *pointer; ++ if (current->outfd != -1) { ++ FD_SET(current->outfd, readfds); ++ maxfd = maxfd < current->outfd ? current->outfd : maxfd; ++ } ++ if (current->errfd != -1) { ++ FD_SET(current->errfd, readfds); ++ maxfd = maxfd < current->errfd ? current->errfd : maxfd; ++ } ++ pointer = ¤t->next; ++ } ++ return maxfd; ++} ++ ++void read_and_fire(Vis* vis, int fd, const char *name, ResponseType rtype) { ++ /* Reads data from the given subprocess file descriptor `fd` and fires ++ * the PROCESS_RESPONSE event in Lua with given subprocess `name`, ++ * `rtype` and the read data as arguments. */ ++ static char buffer[MAXBUFFER]; ++ size_t obtained = read(fd, &buffer, MAXBUFFER-1); ++ if (obtained > 0) ++ vis_lua_process_response(vis, name, buffer, obtained, rtype); ++} ++ ++void vis_process_tick(Vis *vis, fd_set *readfds) { ++ /* Checks if `readfds` contains file discriptors of subprocesses from ++ * the pool. If so, reads the data from them and fires corresponding events. ++ * Also checks if subprocesses from pool is dead or need to be killed then ++ * raises event or kills it if necessary. */ ++ Process **pointer = &process_pool; ++ while (*pointer) { ++ Process *current = *pointer; ++ if (current->outfd != -1 && FD_ISSET(current->outfd, readfds)) ++ read_and_fire(vis, current->outfd, current->name, STDOUT); ++ if (current->errfd != -1 && FD_ISSET(current->errfd, readfds)) ++ read_and_fire(vis, current->errfd, current->name, STDERR); ++ int status; ++ pid_t wpid = waitpid(current->pid, &status, WNOHANG); ++ if (wpid == -1) vis_message_show(vis, strerror(errno)); ++ else if (wpid == current->pid) goto just_destroy; ++ else if(!*(current->invalidator)) goto kill_and_destroy; ++ pointer = ¤t->next; ++ continue; ++kill_and_destroy: ++ kill(current->pid, SIGTERM); ++ waitpid(current->pid, &status, 0); ++just_destroy: ++ if (WIFSIGNALED(status)) ++ vis_lua_process_response(vis, current->name, NULL, WTERMSIG(status), SIGNAL); ++ else ++ vis_lua_process_response(vis, current->name, NULL, WEXITSTATUS(status), EXIT); ++ destroy(pointer); ++ } ++} +--- /dev/null ++++ b/vis-subprocess.h +@@ -0,0 +1,23 @@ ++#ifndef VIS_SUBPROCESS_H ++#define VIS_SUBPROCESS_H ++#include "vis-core.h" ++#include ++ ++struct Process { ++ char *name; ++ int outfd; ++ int errfd; ++ int inpfd; ++ pid_t pid; ++ void **invalidator; ++ struct Process *next; ++}; ++ ++typedef struct Process Process; ++typedef enum { STDOUT, STDERR, SIGNAL, EXIT } ResponseType; ++ ++Process *vis_process_communicate(Vis *, const char *command, const char *name, ++ void **invalidator); ++int vis_process_before_tick(fd_set *); ++void vis_process_tick(Vis *, fd_set *); ++#endif +--- a/vis.c ++++ b/vis.c +@@ -28,6 +28,7 @@ + #include "vis-core.h" + #include "sam.h" + #include "ui.h" ++#include "vis-subprocess.h" + + + static void macro_replay(Vis *vis, const Macro *macro); +@@ -1412,7 +1413,8 @@ int vis_run(Vis *vis) { + + vis_update(vis); + idle.tv_sec = vis->mode->idle_timeout; +- int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset); ++ int r = pselect(vis_process_before_tick(&fds) + 1, &fds, NULL, NULL, ++ timeout, &emptyset); + if (r == -1 && errno == EINTR) + continue; + +@@ -1420,6 +1422,7 @@ int vis_run(Vis *vis) { + /* TODO save all pending changes to a ~suffixed file */ + vis_die(vis, "Error in mainloop: %s\n", strerror(errno)); + } ++ vis_process_tick(vis, &fds); + + if (!FD_ISSET(STDIN_FILENO, &fds)) { + if (vis->mode->idle) diff --git a/vis.changes b/vis.changes index 68ceabb..d90a4df 100644 --- a/vis.changes +++ b/vis.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Feb 28 11:07:05 UTC 2023 - Matej Cepl + +- Add 675-nb-subproc-runner.patch (gh#martanne/vis!675) with + support for the non-blocking subprocess runner. + ------------------------------------------------------------------- Tue Nov 1 15:07:35 UTC 2022 - Matej Cepl diff --git a/vis.spec b/vis.spec index 700cefa..85dc5b8 100644 --- a/vis.spec +++ b/vis.spec @@ -1,7 +1,7 @@ # # spec file for package vis # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -26,6 +26,9 @@ Group: Productivity/Text/Editors URL: https://github.com/martanne/vis Source0: https://github.com/martanne/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz Source1: https://github.com/martanne/vis-test/releases/download/v%{test_version}/vis-test-%{test_version}.tar.gz +# PATCH-FEATURE-UPSTREAM 675-nb-subproc-runner.patch gh#martanne/vis!675 mcepl@suse.com +# adds support for the non-blocking subprocess runner +Patch0: 675-nb-subproc-runner.patch BuildRequires: libselinux-devel BuildRequires: libtermkey-devel BuildRequires: lua-devel @@ -35,6 +38,7 @@ BuildRequires: tar BuildRequires: tre-devel Requires: lua ExclusiveArch: x86_64 %{ix86} +Suggests: par_text %description Vis aims to be a modern, legacy free, simple yet efficient editor combining the strengths of both vi(m) and sam. @@ -42,7 +46,8 @@ Vis aims to be a modern, legacy free, simple yet efficient editor combining the It extends vi's modal editing with built-in support for multiple cursors/selections and combines it with sam's structural regular expression based command language. %prep -%setup -q +%autosetup -p1 + tar -xC test/ --strip-components 1 -f %{SOURCE1} %build