diff options
author | 2018-10-14 19:50:17 -0400 | |
---|---|---|
committer | 2018-10-14 19:50:17 -0400 | |
commit | 5e86b286cf366e0db1a361d36dba4dac6d6dd843 (patch) | |
tree | 94a27c66408588e218e965e5ec55528f2671f372 /src | |
parent | fca69ee9e7b7593005d6692911dfa2d081759762 (diff) | |
download | textadept-5e86b286cf366e0db1a361d36dba4dac6d6dd843.tar.gz textadept-5e86b286cf366e0db1a361d36dba4dac6d6dd843.zip |
Experimentally move external lspawn module into Lua os module as a patch.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 34 | ||||
-rw-r--r-- | src/lua.patch | 619 | ||||
-rw-r--r-- | src/textadept.c | 9 |
3 files changed, 636 insertions, 26 deletions
diff --git a/src/Makefile b/src/Makefile index 693ed7e0..67f28091 100644 --- a/src/Makefile +++ b/src/Makefile @@ -131,9 +131,9 @@ lua_objs = lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o \ linit.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o \ lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o \ lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \ - lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o + lmathlib.o loadlib.o lstrlib.o ltablib.o lutf8lib.o +lua_oslib_objs = loslib.o loslib-curses.o lua_lib_objs = lpcap.o lpcode.o lpprint.o lptree.o lpvm.o lfs.o -lua_spawn_objs = lspawn.o lspawn-curses.o gtdialog_objs = gtdialog.o gtdialog-curses.o termkey_unix_objs = driver-ti.o driver-csi.o termkey_win32_objs = driver-win32-pdcurses.o @@ -176,11 +176,10 @@ $(textadept_objs): textadept.c $(CROSS)$(CC) -c $(CFLAGS) $(LUA_CFLAGS) $(ta_flags) $< -o $@ $(lua_objs): %.o: lua/src/%.c $(CROSS)$(CC) -c $(CFLAGS) $(LUA_CFLAGS) -ULUA_LIB $< -o $@ +$(lua_oslib_objs): lua/src/loslib.c + $(CROSS)$(CC) -c $(CFLAGS) $(LUA_CFLAGS) -ULUA_LIB -std=c99 -pedantic $< -o $@ $(lua_lib_objs): %.o: lua/src/lib/%.c $(CROSS)$(CC) -c $(CFLAGS) $(LUA_CFLAGS) $< -o $@ -$(lua_spawn_objs): lua/src/lib/lspawn.c - $(CROSS)$(CC) -c $(CFLAGS) $(LUA_CFLAGS) -std=c99 -pedantic $(spawn_flags) \ - $< -o $@ $(gtdialog_objs): gtdialog/gtdialog.c $(CROSS)$(CC) -c $(CFLAGS) -std=c99 -pedantic $(plat_flag) -DNOHELP \ -DLIBRARY $(gtdialog_flags) $< -o $@ @@ -195,33 +194,33 @@ textadept_rc.o: textadept.rc ; $(CROSS)$(WINDRES) $< $@ LexLPeg-curses.o: sci_flags += -DCURSES $(CURSES_CFLAGS) lua_dep_objs = LexLPeg.o LexLPeg-curses.o textadept.o textadept-curses.o \ - $(lua_objs) $(lua_lib_objs) lspawn.o lspawn-curses.o + $(lua_objs) $(lua_oslib_objs) $(lua_lib_objs) $(lua_dep_objs): LUA_CFLAGS += -Ilua/src $(textadept_gtk_objs): ta_flags += $(GTK_CFLAGS) $(textadept_curses_objs): \ ta_flags += -Iscintilla/curses -Itermkey -Icdk $(CURSES_CFLAGS) -lspawn.o: spawn_flags = -DGTK $(GLIB_CFLAGS) +loslib.o: LUA_CFLAGS += -DGTK $(GLIB_CFLAGS) gtdialog.o: gtdialog_flags += $(GTK_CFLAGS) gtdialog-curses.o: gtdialog_flags += -Icdk $(CURSES_CFLAGS) # Executables. textadept: $(sci_objs) $(sci_lex_objs) LexLPeg.o $(sci_gtk_objs) \ - scintilla-marshal.o textadept.o $(lua_objs) $(lua_lib_objs) \ - lspawn.o gtdialog.o + scintilla-marshal.o textadept.o $(lua_objs) loslib.o \ + $(lua_lib_objs) gtdialog.o $(CROSS)$(CXX) $(CXXFLAGS) -o ../$@ $^ $(GTK_LIBS) $(LDFLAGS) textadept-curses: $(sci_objs) $(sci_lex_objs) LexLPeg-curses.o \ $(sci_curses_objs) textadept-curses.o $(lua_objs) \ - $(lua_lib_objs) lspawn-curses.o gtdialog-curses.o termkey.o \ + loslib-curses.o $(lua_lib_objs) gtdialog-curses.o termkey.o \ $(termkey_unix_objs) $(cdk_objs) $(CROSS)$(CXX) $(CXXFLAGS) -o ../$@ $^ $(CURSES_LIBS) $(LDFLAGS) textadept.exe: $(sci_objs) $(sci_lex_objs) LexLPeg.o $(sci_gtk_objs) \ scintilla-marshal.o textadept.o textadept_rc.o $(lua_objs) \ - $(lua_lib_objs) lspawn.o gtdialog.o + loslib.o $(lua_lib_objs) gtdialog.o $(CROSS)$(CXX) $(CXXFLAGS) -o ../$@ $^ $(GTK_LIBS) $(LDFLAGS) textadept-curses.exe: $(sci_objs) $(sci_lex_objs) LexLPeg-curses.o \ $(sci_curses_objs) textadept-curses.o textadept_rc.o \ - $(lua_objs) $(lua_lib_objs) lspawn-curses.o \ + $(lua_objs) loslib-curses.o $(lua_lib_objs) \ gtdialog-curses.o termkey.o $(termkey_win32_objs) \ $(cdk_objs) $(CROSS)$(CXX) $(CXXFLAGS) -o ../$@ $^ $(CURSES_LIBS) $(LDFLAGS) @@ -360,18 +359,14 @@ osx-deps: $(base_deps) gtkosx termkey ifndef NIGHTLY #gtdialog_url = http://foicica.com/gtdialog/download/$@ gtdialog_url = http://foicica.com/hg/gtdialog/archive/$@ - #lspawn_url = http://foicica.com/lspawn/download/$@ - lspawn_url = http://foicica.com/hg/lspawn/archive/$@ else gtdialog_url = http://foicica.com/hg/gtdialog/archive/tip.zip - lspawn_url = http://foicica.com/hg/lspawn/archive/tip.zip endif scintilla_zip = cfad7e56cac1.zip lua_tgz = lua-5.3.5.tar.gz lpeg_tgz = lpeg-1.0.0.tar.gz lfs_zip = v_1_6_3.zip -lspawn_zip = f22d4d50a6ac.zip gtdialog_zip = 6435a42450c7.zip cdk_tgz = cdk-5.0-20150928.tgz termkey_tgz = libtermkey-0.20.tar.gz @@ -395,19 +390,16 @@ scintilla: scintilla.patch | $(scintilla_zip) $(lua_tgz): ; wget http://www.lua.org/ftp/$@ $(lpeg_tgz): ; wget http://www.inf.puc-rio.br/~roberto/lpeg/$@ $(lfs_zip): ; wget http://github.com/keplerproject/luafilesystem/archive/$@ -$(lspawn_zip): ; wget $(lspawn_url) -O $@ lua: lua.patch | $(lua_tgz) if [ -d $@ ]; then rm -r $@; fi mkdir $@ && tar xzf $| -C $@ && mv $@/*/* $@ patch -d $@ -N -p1 < $< -lualibs: lua/src/lib/lpeg lua/src/lib/lfs lua/src/lib/lspawn +lualibs: lua/src/lib/lpeg lua/src/lib/lfs lua/src/lib/lpeg: | $(lpeg_tgz) mkdir -p $@ && tar xzf $| -C $@ && mv $@/*/*.c $@/*/*.h $(dir $@) lua/src/lib/lfs: | $(lfs_zip) if [ -d $@ ]; then rm -r $@; fi mkdir -p $@ && unzip -d $@ $| && mv $@/*/src/*.c $@/*/src/*.h $(dir $@) -lua/src/lib/lspawn: | $(lspawn_zip) - mkdir -p $@ && unzip -d $@ $| && mv $@/*/*.c $(dir $@) $(gtdialog_zip): ; wget $(gtdialog_url) -O $@ gtdialog: | $(gtdialog_zip) ; mkdir $@ && unzip -d $@ $| && mv $@/*/* $@ $(cdk_tgz): ; wget http://invisible-mirror.net/archives/cdk/$@ @@ -439,7 +431,7 @@ $(bombay_zip): ; wget http://foicica.com/hg/bombay/archive/tip.zip -O $@ mkdir $(notdir $@) && unzip -d $(notdir $@) $| && \ mv $(notdir $@)/*/* $(dir $@) $(cloc): ; wget http://prdownloads.sourceforge.net/cloc/$@ -O $@ -sign-deps: | $(scintilla_tgz) $(lua_tgz) $(lpeg_tgz) $(lfs_zip) $(lspawn_zip) \ +sign-deps: | $(scintilla_tgz) $(lua_tgz) $(lpeg_tgz) $(lfs_zip) \ $(gtdialog_zip) $(cdk_tgz) $(termkey_tgz) $(win32gtk_zip) \ $(win32curses_zip) $(pdcurses_zip) $(gtkosx_tgz) @for file in $|; do gpg -ab $$file; done diff --git a/src/lua.patch b/src/lua.patch index e3c4ff60..8a4df4b1 100644 --- a/src/lua.patch +++ b/src/lua.patch @@ -47,3 +47,622 @@ diff -r 8a23edc91533 src/luaconf.h #endif /* } */ +--- a/src/loslib.c 2018-10-14 14:54:46.915282217 -0400 ++++ b/src/loslib.c 2018-10-14 16:02:59.583627265 -0400 +@@ -4,6 +4,15 @@ + ** See Copyright Notice in lua.h + */ + ++// Defines for Textadept's process spawning extension. ++#if __linux__ ++#define _XOPEN_SOURCE 1 // for kill from signal.h ++#define _XOPEN_SOURCE_EXTENDED 1 // for kill from signal.h ++#if !GTK ++#define _GNU_SOURCE 1 // for execvpe from unistd.h ++#endif ++#endif ++ + #define loslib_c + #define LUA_LIB + +@@ -382,6 +391,11 @@ + return 0; + } + ++// Forward declarations and exports for Textadept's process spawning extension. ++static int os_spawn(lua_State *L); ++int os_spawn_pushfds(lua_State *L); ++int os_spawn_readfds(lua_State *L); ++ + + static const luaL_Reg syslib[] = { + {"clock", os_clock}, +@@ -393,6 +407,7 @@ + {"remove", os_remove}, + {"rename", os_rename}, + {"setlocale", os_setlocale}, ++ {"spawn", os_spawn}, + {"time", os_time}, + {"tmpname", os_tmpname}, + {NULL, NULL} +@@ -404,6 +419,580 @@ + + LUAMOD_API int luaopen_os (lua_State *L) { + luaL_newlib(L, syslib); ++#if !GTK ++ // Need to keep track of running processes for monitoring fds and pids. ++ lua_newtable(L), lua_setfield(L, LUA_REGISTRYINDEX, "spawn_procs"); ++#endif ++ return 1; ++} ++ ++ ++// Process spawning extension for Textadept using GLib or POSIX. ++// Copyright 2012-2018 Mitchell mitchell.att.foicica.com. See LICENSE. ++ ++#include <signal.h> ++//#include <stdlib.h> ++//#include <string.h> ++#include <unistd.h> ++#if GTK ++#include <glib.h> ++#endif ++#if !_WIN32 ++#if !GTK ++#include <errno.h> ++#include <sys/select.h> ++#endif ++#include <sys/wait.h> ++#include <signal.h> ++#else ++#include <fcntl.h> ++#include <windows.h> ++#endif ++ ++#if _WIN32 ++#define waitpid(pid, ...) WaitForSingleObject(pid, INFINITE) ++#define kill(pid, _) TerminateProcess(pid, 1) ++#define g_io_channel_unix_new g_io_channel_win32_new_fd ++#define close CloseHandle ++#define FD(handle) _open_osfhandle((intptr_t)handle, _O_RDONLY) ++#if !GTK ++// The following macro is only for quieting compiler warnings. Spawning in Win32 ++// console is not supported. ++#define read(fd, buf, len) read((int)fd, buf, len) ++#endif ++#endif ++#define l_setcfunction(l, n, name, f) \ ++ (lua_pushcfunction(l, f), lua_setfield(l, (n > 0) ? n : n - 1, name)) ++#define l_reffunction(l, n) \ ++ (luaL_argcheck(l, lua_isfunction(l, n) || lua_isnoneornil(l, n), n, \ ++ "function or nil expected"), \ ++ lua_pushvalue(l, n), luaL_ref(l, LUA_REGISTRYINDEX)) ++ ++typedef struct { ++ lua_State *L; ++ int ref; ++#if !_WIN32 ++ int pid, fstdin, fstdout, fstderr; ++#else ++ HANDLE pid, fstdin, fstdout, fstderr; ++#endif ++#if GTK ++ GIOChannel *cstdout, *cstderr; ++#endif ++ int stdout_cb, stderr_cb, exit_cb; ++} PStream; ++ ++/** p:status() Lua function. */ ++static int lp_status(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ lua_pushstring(L, p->pid ? "running" : "terminated"); + return 1; + } + ++/** p:wait() Lua function. */ ++static int lp_wait(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ luaL_argcheck(L, p->pid, 1, "process terminated"); ++ waitpid(p->pid, NULL, 0); ++ return 0; ++} ++ ++/** p:read() Lua function. */ ++static int lp_read(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ luaL_argcheck(L, p->pid, 1, "process terminated"); ++ char *c = (char *)luaL_optstring(L, 2, "l"); ++ if (*c == '*') c++; // skip optional '*' (for compatibility) ++ luaL_argcheck(L, *c == 'l' || *c == 'L' || *c == 'a' || lua_isnumber(L, 2), 2, ++ "invalid option"); ++#if GTK ++ char *buf; ++ size_t len; ++ GError *error = NULL; ++ GIOStatus status = G_IO_STATUS_NORMAL; ++ if (!g_io_channel_get_buffered(p->cstdout)) ++ g_io_channel_set_buffered(p->cstdout, TRUE); // needed for functions below ++ if (!lua_isnumber(L, 2)) { ++ if (*c == 'l' || *c == 'L') { ++ GString *s = g_string_new(NULL); ++ status = g_io_channel_read_line_string(p->cstdout, s, NULL, &error); ++ len = s->len, buf = g_string_free(s, FALSE); ++ } else if (*c == 'a') { ++ status = g_io_channel_read_to_end(p->cstdout, &buf, &len, &error); ++ if (status == G_IO_STATUS_EOF) status = G_IO_STATUS_NORMAL; ++ } ++ } else { ++ size_t bytes = (size_t)lua_tointeger(L, 2); ++ buf = malloc(bytes); ++ status = g_io_channel_read_chars(p->cstdout, buf, bytes, &len, &error); ++ } ++ if ((g_io_channel_get_buffer_condition(p->cstdout) & G_IO_IN) == 0) ++ g_io_channel_set_buffered(p->cstdout, FALSE); // needed for stdout callback ++ if (*c == 'l' && buf[len - 1] == '\n') len--; ++ if (*c == 'l' && buf[len - 1] == '\r') len--; ++ lua_pushlstring(L, buf, len); ++ free(buf); ++ if (status != G_IO_STATUS_NORMAL) { ++ lua_pushnil(L); ++ if (status == G_IO_STATUS_EOF) return 1; ++ lua_pushinteger(L, error->code), lua_pushstring(L, error->message); ++ return 3; ++ } else return 1; ++#else ++ int len = 0; ++ if (!lua_isnumber(L, 2)) { ++ luaL_Buffer buf; ++ luaL_buffinit(L, &buf); ++ int n; ++ char ch; ++ while ((n = read(p->fstdout, &ch, 1)) > 0) { ++ if ((ch != '\r' && ch != '\n') || *c == 'L' || *c == 'a') ++ luaL_addchar(&buf, ch), len++; ++ if (ch == '\n' && *c != 'a') break; ++ } ++ if (n < 0 && len == 0) len = n; ++ luaL_pushresult(&buf); ++ if (n == 0 && len == 0 && *c != 'a') lua_pushnil(L); // EOF ++ } else { ++ size_t bytes = (size_t)lua_tointeger(L, 2); ++ char *buf = malloc(bytes); ++ if ((len = read(p->fstdout, buf, bytes)) > 0) ++ lua_pushlstring(L, buf, len); ++ else if (len == 0) ++ lua_pushnil(L); // EOF ++ free(buf); ++ } ++ if (len < 0) { ++ lua_pushnil(L); ++ lua_pushinteger(L, errno), lua_pushstring(L, strerror(errno)); ++ return 3; ++ } else return 1; ++#endif ++} ++ ++/** p:write() Lua function. */ ++static int lp_write(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ luaL_argcheck(L, p->pid, 1, "process terminated"); ++ for (int i = 2; i <= lua_gettop(L); i++) { ++ size_t len; ++ const char *s = luaL_checklstring(L, i, &len); ++#if !_WIN32 ++ len = write(p->fstdin, s, len); // assign result to fix compiler warning ++#else ++ DWORD len_written; ++ WriteFile(p->fstdin, s, len, &len_written, NULL); ++#endif ++ } ++ return 0; ++} ++ ++/** p:close() Lua function. */ ++static int lp_close(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ luaL_argcheck(L, p->pid, 1, "process terminated"); ++ return (close(p->fstdin), 0); ++} ++ ++/** p:kill() Lua function. */ ++static int lp_kill(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ if (p->pid) kill(p->pid, luaL_optinteger(L, 2, SIGKILL)); ++ return 0; ++} ++ ++/** tostring(p) Lua function. */ ++static int lp_tostring(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ if (p->pid) ++ lua_pushfstring(L, "process (pid=%d)", p->pid); ++ else ++ lua_pushstring(L, "process (terminated)"); ++ return 1; ++} ++ ++#if GTK ++/** __gc Lua metamethod. */ ++static int lp_gc(lua_State *L) { ++ PStream *p = (PStream *)luaL_checkudata(L, 1, "ta_spawn"); ++ if (p->pid) { ++ // lua_close() was called, forcing GC. Disconnect listeners since GTK is ++ // still running and may try to invoke callbacks. ++ g_source_remove_by_user_data(p); // disconnect cstdout watch ++ g_source_remove_by_user_data(p); // disconnect cstderr watch ++ g_source_remove_by_user_data(p); // disconnect child watch ++ } ++ return 0; ++} ++ ++/** Signal that channel output is available for reading. */ ++static int ch_read(GIOChannel *source, GIOCondition cond, void *data) { ++ PStream *p = (PStream *)data; ++ if (!p->pid || !(cond & G_IO_IN)) return FALSE; ++ char buf[BUFSIZ]; ++ size_t len = 0; ++ do { ++ int status = g_io_channel_read_chars(source, buf, BUFSIZ, &len, NULL); ++ int r = (source == p->cstdout) ? p->stdout_cb : p->stderr_cb; ++ if (status == G_IO_STATUS_NORMAL && len > 0 && r > 0) { ++ lua_rawgeti(p->L, LUA_REGISTRYINDEX, r); ++ lua_pushlstring(p->L, buf, len); ++ if (lua_pcall(p->L, 1, 0, 0) != LUA_OK) ++ fprintf(stderr, "Lua: %s\n", lua_tostring(p->L, -1)), lua_pop(p->L, 1); ++ } ++ } while (len == BUFSIZ); ++ return p->pid && !(cond & G_IO_HUP); ++} ++ ++/** ++ * Creates a new channel that monitors a file descriptor for output. ++ * @param fd File descriptor returned by `g_spawn_async_with_pipes()` or ++ * `_open_osfhandle()`. ++ * @param p PStream to notify when output is available for reading. ++ * @param watch Whether or not to watch for output to send to a Lua callback. ++ */ ++static GIOChannel *new_channel(int fd, PStream *p, int watch) { ++ GIOChannel *channel = g_io_channel_unix_new(fd); ++ g_io_channel_set_encoding(channel, NULL, NULL); ++ g_io_channel_set_buffered(channel, FALSE); ++ if (watch) { ++ g_io_add_watch(channel, G_IO_IN | G_IO_HUP, ch_read, p); ++ g_io_channel_unref(channel); ++ } ++ return channel; ++} ++ ++/** Signal that the child process finished. */ ++static void p_exit(GPid pid, int status, void *data) { ++ PStream *p = (PStream *)data; ++ if (p->exit_cb != LUA_REFNIL) { ++ lua_rawgeti(p->L, LUA_REGISTRYINDEX, p->exit_cb); ++ lua_pushinteger(p->L, status); ++ if (lua_pcall(p->L, 1, 0, 0) != LUA_OK) ++ fprintf(stderr, "Lua: %s\n", lua_tostring(p->L, -1)), lua_pop(p->L, 1); ++ } ++#if _WIN32 ++ close(p->pid); ++#endif ++ close(p->fstdin), close(p->fstdout), close(p->fstderr); ++ luaL_unref(p->L, LUA_REGISTRYINDEX, p->stdout_cb); ++ luaL_unref(p->L, LUA_REGISTRYINDEX, p->stderr_cb); ++ luaL_unref(p->L, LUA_REGISTRYINDEX, p->exit_cb); ++ luaL_unref(p->L, LUA_REGISTRYINDEX, p->ref); // allow proc to be collected ++ p->pid = 0; ++} ++#elif !_WIN32 ++/** ++ * Pushes onto the stack an fd_set of all spawned processes for use with ++ * `select()` and `os_spawn_readfds()` and returns the `nfds` to pass to ++ * `select()`. ++ */ ++int os_spawn_pushfds(lua_State *L) { ++ int nfds = 1; ++ fd_set *fds = (fd_set *)lua_newuserdata(L, sizeof(fd_set)); ++ FD_ZERO(fds); ++ lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); ++ lua_pushnil(L); ++ while (lua_next(L, -2)) { ++ PStream *p = (PStream *)lua_touserdata(L, -2); ++ FD_SET(p->fstdout, fds); ++ FD_SET(p->fstderr, fds); ++ if (p->fstdout >= nfds) nfds = p->fstdout + 1; ++ if (p->fstderr >= nfds) nfds = p->fstderr + 1; ++ lua_pop(L, 1); // value ++ } ++ lua_pop(L, 1); // spawn_procs ++ return nfds; ++} ++ ++/** Signal that a fd has output to read. */ ++static void fd_read(int fd, PStream *p) { ++ char buf[BUFSIZ]; ++ ssize_t len; ++ do { ++ len = read(fd, buf, BUFSIZ); ++ int r = (fd == p->fstdout) ? p->stdout_cb : p->stderr_cb; ++ if (len > 0 && r > 0) { ++ lua_rawgeti(p->L, LUA_REGISTRYINDEX, r); ++ lua_pushlstring(p->L, buf, len); ++ if (lua_pcall(p->L, 1, 0, 0) != LUA_OK) ++ fprintf(stderr, "Lua: %s\n", lua_tostring(p->L, -1)), lua_pop(p->L, 1); ++ } ++ } while (len == BUFSIZ); ++} ++ ++/** ++ * Reads any output from the fds in the fd_set at the top of the stack and ++ * returns the number of fds read from. ++ * Also signals any registered child processes that have finished and cleans up ++ * after them. ++ */ ++int os_spawn_readfds(lua_State *L) { ++ int n = 0; ++ fd_set *fds = (fd_set *)lua_touserdata(L, -1); ++ lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); ++ lua_pushnil(L); ++ while (lua_next(L, -2)) { ++ PStream *p = (PStream *)lua_touserdata(L, -2); ++ // Read output if any is available. ++ if (FD_ISSET(p->fstdout, fds)) fd_read(p->fstdout, p), n++; ++ if (FD_ISSET(p->fstderr, fds)) fd_read(p->fstderr, p), n++; ++ // Check process status. ++ int status; ++ if (waitpid(p->pid, &status, WNOHANG) > 0) { ++ fd_read(p->fstdout, p), fd_read(p->fstderr, p); // read anything left ++ if (p->exit_cb != LUA_REFNIL) { ++ lua_rawgeti(L, LUA_REGISTRYINDEX, p->exit_cb); ++ lua_pushinteger(L, status); ++ if (lua_pcall(L, 1, 0, 0) != LUA_OK) ++ fprintf(stderr, "Lua: %s\n", lua_tostring(L, -1)), lua_pop(L, 1); ++ } ++ close(p->fstdin), close(p->fstdout), close(p->fstderr); ++ luaL_unref(L, LUA_REGISTRYINDEX, p->stdout_cb); ++ luaL_unref(L, LUA_REGISTRYINDEX, p->stderr_cb); ++ luaL_unref(L, LUA_REGISTRYINDEX, p->exit_cb); ++ luaL_unref(L, LUA_REGISTRYINDEX, p->ref); // allow proc to be collected ++ p->pid = 0; ++ lua_pushnil(L), lua_replace(L, -2), lua_settable(L, -3); // t[proc] = nil ++ lua_pushnil(L); // push nil for next call to lua_next() ++ } else lua_pop(L, 1); // value ++ } ++ lua_pop(L, 1); // spawn_procs ++ return n; ++} ++#endif ++ ++/** spawn() Lua function. */ ++static int os_spawn(lua_State *L) { ++#if !_WIN32 ++#if GTK ++ char **argv = NULL; ++ GError *error = NULL; ++ if (!g_shell_parse_argv(luaL_checkstring(L, 1), NULL, &argv, &error)) { ++ lua_pushfstring(L, "invalid argv: %s", error->message); ++ luaL_argerror(L, 1, lua_tostring(L, -1)); ++ } ++#else ++ lua_newtable(L); ++ const char *param = luaL_checkstring(L, 1), *c = param; ++ while (*c) { ++ while (*c == ' ') c++; ++ param = c; ++ if (*c == '"') { ++ param = ++c; ++ while (*c && *c != '"') c++; ++ } else while (*c && *c != ' ') c++; ++ lua_pushlstring(L, param, c - param); ++ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); ++ if (*c == '"') c++; ++ } ++ int argc = lua_rawlen(L, -1); ++ char **argv = malloc((argc + 1) * sizeof(char *)); ++ for (int i = 0; i < argc; i++) { ++ lua_rawgeti(L, -1, i + 1); ++ size_t len = lua_rawlen(L, -1); ++ char *param = malloc(len + 1); ++ strcpy(param, lua_tostring(L, -1)), param[len] = '\0'; ++ argv[i] = param; ++ lua_pop(L, 1); // param ++ } ++ argv[argc] = NULL; ++ lua_pop(L, 1); // argv ++#endif ++ int envn = 0; ++ char **envp = NULL; ++ if (lua_istable(L, 3)) { ++ envn = lua_rawlen(L, 3), envp = malloc((envn + 1) * sizeof(char *)); ++ for (int i = 0; i < envn; i++) { ++ lua_rawgeti(L, 3, i + 1); ++ size_t len = lua_rawlen(L, -1); ++ char *pair = malloc(len + 1); ++ strcpy(pair, lua_tostring(L, -1)), pair[len] = '\0'; ++ envp[i] = pair; ++ lua_pop(L, 1); // pair ++ } ++ envp[envn] = NULL; ++ } ++#else ++ lua_pushstring(L, getenv("COMSPEC")); ++ lua_pushstring(L, " /c "); ++ lua_pushvalue(L, 1); ++ lua_concat(L, 3); ++ lua_replace(L, 1); // cmd = os.getenv('COMSPEC')..' /c '..cmd ++ wchar_t argv[2048] = {L'\0'}, cwd[MAX_PATH] = {L'\0'}; ++ MultiByteToWideChar(GetACP(), 0, lua_tostring(L, 1), -1, (LPWSTR)&argv, ++ sizeof(argv)); ++ MultiByteToWideChar(GetACP(), 0, lua_tostring(L, 2), -1, (LPWSTR)&cwd, ++ MAX_PATH); ++ char *envp = NULL; ++ if (lua_istable(L, 3)) { ++ luaL_Buffer buf; ++ luaL_buffinit(L, &buf); ++ for (int i = 0; i < lua_rawlen(L, 3); i++) { ++ lua_rawgeti(L, 3, i + 1); ++ luaL_addstring(&buf, lua_tostring(L, -1)), luaL_addchar(&buf, '\0'); ++ lua_pop(L, 1); // pair ++ } ++ luaL_addchar(&buf, '\0'); ++ luaL_pushresult(&buf); ++ envp = malloc(lua_rawlen(L, -1) * sizeof(char)); ++ memcpy(envp, lua_tostring(L, -1), lua_rawlen(L, -1)); ++ lua_pop(L, 1); // buf ++ } ++#endif ++ lua_settop(L, 6); // ensure 6 values so userdata to be pushed is 7th ++ ++ PStream *p = (PStream *)lua_newuserdata(L, sizeof(PStream)); ++ p->L = L, p->ref = 0; ++ p->stdout_cb = l_reffunction(L, !envp ? 3 : 4); ++ p->stderr_cb = l_reffunction(L, !envp ? 4 : 5); ++ p->exit_cb = l_reffunction(L, !envp ? 5 : 6); ++ if (luaL_newmetatable(L, "ta_spawn")) { ++ l_setcfunction(L, -1, "status", lp_status); ++ l_setcfunction(L, -1, "wait", lp_wait); ++ l_setcfunction(L, -1, "read", lp_read); ++ l_setcfunction(L, -1, "write", lp_write); ++ l_setcfunction(L, -1, "close", lp_close); ++ l_setcfunction(L, -1, "kill", lp_kill); ++ l_setcfunction(L, -1, "__tostring", lp_tostring); ++#if GTK ++ l_setcfunction(L, -1, "__gc", lp_gc); ++#endif ++ lua_pushvalue(L, -1), lua_setfield(L, -2, "__index"); ++ } ++ lua_setmetatable(L, -2); ++ ++#if !_WIN32 ++#if GTK ++ GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; ++ if (g_spawn_async_with_pipes(lua_tostring(L, 2), argv, envp, flags, NULL, ++ NULL, &p->pid, &p->fstdin, &p->fstdout, ++ &p->fstderr, &error)) { ++ p->cstdout = new_channel(p->fstdout, p, p->stdout_cb > 0); ++ p->cstderr = new_channel(p->fstderr, p, p->stderr_cb > 0); ++ g_child_watch_add_full(G_PRIORITY_DEFAULT + 1, p->pid, p_exit, p, NULL); ++ lua_pushnil(L); // no error ++ } else { ++ lua_pushnil(L); ++ lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), error->message); ++ } ++ ++ g_strfreev(argv); ++#else ++ // Adapted from Chris Emerson and GLib. ++ // Attempt to create pipes for stdin, stdout, and stderr and fork process. ++ int pstdin[2] = {-1, -1}, pstdout[2] = {-1, -1}, pstderr[2] = {-1, -1}, pid; ++ if (pipe(pstdin) == 0 && pipe(pstdout) == 0 && pipe(pstderr) == 0 && ++ (pid = fork()) >= 0) { ++ if (pid > 0) { ++ // Parent process: register child for monitoring its fds and pid. ++ close(pstdin[0]), close(pstdout[1]), close(pstderr[1]); ++ p->pid = pid; ++ p->fstdin = pstdin[1], p->fstdout = pstdout[0], p->fstderr = pstderr[0]; ++ lua_getfield(L, LUA_REGISTRYINDEX, "spawn_procs"); ++ // spawn_procs is of the form: t[proc] = true ++ lua_pushvalue(L, -2), lua_pushboolean(L, 1), lua_settable(L, -3); ++ lua_pop(L, 1); // spawn_procs ++ lua_pushnil(L); // no error ++ } else if (pid == 0) { ++ // Child process: redirect stdin, stdout, and stderr, chdir, and exec. ++ close(pstdin[1]), close(pstdout[0]), close(pstderr[0]); ++ close(0), close(1), close(2); ++ dup2(pstdin[0], 0), dup2(pstdout[1], 1), dup2(pstderr[1], 2); ++ close(pstdin[0]), close(pstdout[1]), close(pstderr[1]); ++ const char *cwd = lua_tostring(L, 2); ++ if (cwd && chdir(cwd) < 0) { ++ fprintf(stderr, "Failed to change directory '%s' (%s)", cwd, ++ strerror(errno)); ++ exit(EXIT_FAILURE); ++ } ++ extern char **environ; ++#if __linux__ ++ if (!envp) envp = environ; ++ execvpe(argv[0], argv, envp); // does not return on success ++#else ++ if (envp) environ = envp; ++ execvp(argv[0], argv); // does not return on success ++#endif ++ fprintf(stderr, "Failed to execute child process \"%s\" (%s)", argv[0], ++ strerror(errno)); ++ exit(EXIT_FAILURE); ++ } ++ } else { ++ if (pstdin[0] >= 0) close(pstdin[0]), close(pstdin[1]); ++ if (pstdout[0] >= 0) close(pstdout[0]), close(pstdout[1]); ++ if (pstderr[0] >= 0) close(pstderr[0]), close(pstderr[1]); ++ lua_pushnil(L); ++ lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), strerror(errno)); ++ } ++ for (int i = 0; i < argc; i++) free(argv[i]); ++ free(argv); ++ if (envp) { ++ for (int i = 0; i < envn; i++) free(envp[i]); ++ free(envp); ++ } ++#endif ++#else ++#if GTK ++ // Adapted from SciTE. ++ SECURITY_DESCRIPTOR sd; ++ InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); ++ SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); ++ SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), 0, 0}; ++ sa.nLength = sizeof(SECURITY_ATTRIBUTES); ++ sa.lpSecurityDescriptor = &sd; ++ sa.bInheritHandle = TRUE; ++ ++ // Redirect stdin. ++ HANDLE stdin_read = NULL, proc_stdin = NULL; ++ CreatePipe(&stdin_read, &proc_stdin, &sa, 0); ++ SetHandleInformation(proc_stdin, HANDLE_FLAG_INHERIT, 0); ++ // Redirect stdout. ++ HANDLE proc_stdout = NULL, stdout_write = NULL; ++ CreatePipe(&proc_stdout, &stdout_write, &sa, 0); ++ SetHandleInformation(proc_stdout, HANDLE_FLAG_INHERIT, 0); ++ // Redirect stderr. ++ HANDLE proc_stderr = NULL, stderr_write = NULL; ++ CreatePipe(&proc_stderr, &stderr_write, &sa, 0); ++ SetHandleInformation(proc_stderr, HANDLE_FLAG_INHERIT, 0); ++ ++ // Spawn with pipes and no window. ++ // TODO: CREATE_UNICODE_ENVIRONMENT? ++ STARTUPINFOW startup_info = { ++ sizeof(STARTUPINFOW), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, ++ STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES, SW_HIDE, 0, 0, stdin_read, ++ stdout_write, stderr_write ++ }; ++ PROCESS_INFORMATION proc_info = {0, 0, 0, 0}; ++ if (CreateProcessW(NULL, argv, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, ++ envp, *cwd ? cwd : NULL, &startup_info, &proc_info)) { ++ p->pid = proc_info.hProcess; ++ p->fstdin = proc_stdin, p->fstdout = proc_stdout, p->fstderr = proc_stderr; ++ p->cstdout = new_channel(FD(proc_stdout), p, p->stdout_cb > 0); ++ p->cstderr = new_channel(FD(proc_stderr), p, p->stderr_cb > 0); ++ g_child_watch_add(p->pid, p_exit, p); ++ // Close unneeded handles. ++ CloseHandle(proc_info.hThread); ++ CloseHandle(stdin_read); ++ CloseHandle(stdout_write), CloseHandle(stderr_write); ++ lua_pushnil(L); // no error ++ } else { ++ char *message = NULL; ++ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | ++ FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), ++ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, ++ 0, NULL); ++ lua_pushnil(L); ++ lua_pushfstring(L, "%s: %s", lua_tostring(L, 1), message); ++ LocalFree(message); ++ } ++ if (envp) free(envp); ++#else ++ luaL_error(L, "not implemented in this environment"); ++#endif ++#endif ++ if (lua_isuserdata(L, -2)) ++ p->ref = (lua_pushvalue(L, -2), luaL_ref(L, LUA_REGISTRYINDEX)); ++ ++ return 2; ++} diff --git a/src/textadept.c b/src/textadept.c index f40ab00f..a64553df 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -214,8 +214,7 @@ static void new_buffer(sptr_t); static Scintilla *new_view(sptr_t); static int lL_init(lua_State *, int, char **, int); LUALIB_API int luaopen_lpeg(lua_State *), luaopen_lfs(lua_State *); -LUALIB_API int luaopen_spawn(lua_State *); -LUALIB_API int lspawn_pushfds(lua_State *), lspawn_readfds(lua_State *); +LUALIB_API int os_spawn_pushfds(lua_State *), os_spawn_readfds(lua_State *); /** * Emits an event. @@ -1504,7 +1503,7 @@ static int lL_init(lua_State *L, int argc, char **argv, int reinit) { } lua_pushinteger(L, (sptr_t)L), lua_setglobal(L, "_LUA"); luaL_openlibs(L); - lL_openlib(L, lpeg), lL_openlib(L, lfs), lL_openlib(L, spawn); + lL_openlib(L, lpeg), lL_openlib(L, lfs); lua_newtable(L); lua_newtable(L); @@ -2407,12 +2406,12 @@ static TermKeyResult textadept_waitkey(TermKey *tk, TermKeyKey *key) { if (res != TERMKEY_RES_AGAIN && res != TERMKEY_RES_NONE) return res; if (res == TERMKEY_RES_AGAIN) force = TRUE; // Wait for input. - int nfds = lspawn_pushfds(lua); + int nfds = os_spawn_pushfds(lua); fd_set *fds = (fd_set *)lua_touserdata(lua, -1); FD_SET(0, fds); // monitor stdin if (select(nfds, fds, NULL, NULL, force ? &timeout : NULL) > 0) { if (FD_ISSET(0, fds)) termkey_advisereadable(tk); - if (lspawn_readfds(lua) > 0) refresh_all(); + if (os_spawn_readfds(lua) > 0) refresh_all(); } lua_pop(lua, 1); // fd_set } |