aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile34
-rw-r--r--src/lua.patch619
-rw-r--r--src/textadept.c9
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
}