diff options
author | 2018-10-14 19:50:17 -0400 | |
---|---|---|
committer | 2018-10-14 19:50:17 -0400 | |
commit | 5e86b286cf366e0db1a361d36dba4dac6d6dd843 (patch) | |
tree | 94a27c66408588e218e965e5ec55528f2671f372 | |
parent | fca69ee9e7b7593005d6692911dfa2d081759762 (diff) | |
download | textadept-5e86b286cf366e0db1a361d36dba4dac6d6dd843.tar.gz textadept-5e86b286cf366e0db1a361d36dba4dac6d6dd843.zip |
Experimentally move external lspawn module into Lua os module as a patch.
-rw-r--r-- | CHANGELOG.md | 20 | ||||
-rw-r--r-- | TECHNOLOGY.md | 4 | ||||
-rw-r--r-- | core/.os.luadoc | 86 | ||||
-rw-r--r-- | core/init.lua | 83 | ||||
-rw-r--r-- | modules/lua/api | 4 | ||||
-rw-r--r-- | modules/lua/tags | 2 | ||||
-rw-r--r-- | modules/textadept/editing.lua | 2 | ||||
-rw-r--r-- | modules/textadept/menu.lua | 2 | ||||
-rw-r--r-- | modules/textadept/run.lua | 8 | ||||
-rw-r--r-- | src/Makefile | 34 | ||||
-rw-r--r-- | src/lua.patch | 619 | ||||
-rw-r--r-- | src/textadept.c | 9 |
12 files changed, 742 insertions, 131 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 368e1fac..f247905a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1084,7 +1084,7 @@ Changes: * Improved API documentation lookup behind the caret. * [Refactored snippets][] to longer display placeholder text. -* [`spawn()`][] can now optionally specify the child's environment. +* [`os.spawn()`][] can now optionally specify the child's environment. * Added Gherkin lexer. * Updated to [Scintilla][] 3.6.1. @@ -1100,7 +1100,7 @@ Changes: [PGP -- 8.3 Modules]: download/textadept_8.3.modules.zip.asc [`buffer:count_characters()`]: api.html#buffer.count_characters [Refactored snippets]: http://foicica.com/lists/code/201509/2687.html -[`spawn()`]: api.html#spawn +[`os.spawn()`]: api.html#os.spawn [Scintilla]: http://scintilla.org ## 8.2 (01 Sep 2015) @@ -1291,7 +1291,7 @@ Changes: * Upgraded to Lua 5.3, LPeg 0.12.2, lfs 1.6.3, and lspawn 1.2. * Removed `keys.LANGUAGE_MODULE_PREFIX`, but left that prefix unused on all platforms. -* [`textadept.editing.filter_through()`][] now uses [`spawn()`][]. +* [`textadept.editing.filter_through()`][] now uses [`os.spawn()`][]. * Removed long-hand [compile and run macros][] in favor of shorter ones. * [`textadept.bookmarks.toggle()`][] accepts an optional line to bookmark. * Added support for Rust and TOML. @@ -1311,7 +1311,7 @@ Changes: [PGP -- 8.0 alpha Linux x86_64]: download/textadept_8.0_alpha.x86_64.tgz.asc [PGP -- 8.0 alpha Modules]: download/textadept_8.0_alpha.modules.zip.asc [`textadept.editing.filter_through()`]: api.html#textadept.editing.filter_through -[`spawn()`]: api.html#spawn +[`os.spawn()`]: api.html#os.spawn [compile and run macros]: api.html#textadept.run.compile\_commands [`textadept.bookmarks.toggle()`]: api.html#textadept.bookmarks.toggle [`spawn_proc:kill()`]: api.html#spawn_proc:kill @@ -1578,7 +1578,7 @@ Bugfixes: Changes: -* Mac OSX GUI version can truly [`spawn()`][] processes now. +* Mac OSX GUI version can truly [`os.spawn()`][] processes now. * Improved performance for lexers with no grammars and no fold rules. * Updated to [Scintilla][] 3.5.1. @@ -1613,7 +1613,7 @@ Bugfixes: Changes: -* Terminal version can truly [`spawn()`][] processes now. +* Terminal version can truly [`os.spawn()`][] processes now. * Added Linux .desktop files for menus and launchers. * Indicate presence of a BOM in the statusbar. * Switch to previous buffer after closing a buffer. @@ -1631,7 +1631,7 @@ Changes: [PGP -- 7.6 Linux x86_64]: download/textadept_7.6.x86_64.tgz.asc [PGP -- 7.6 Modules]: download/textadept_7.6.modules.zip.asc [`buffer:del_word_right()`]: api.html#buffer.del_word_right -[`spawn()`]: api.html#spawn +[`os.spawn()`]: api.html#os.spawn [`lfs.dir_foreach()`]: api.html#lfs.dir_foreach [Scintilla]: http://scintilla.org @@ -1729,7 +1729,7 @@ Bugfixes: Changes: * Added reST and YAML lexers and official language modules for each. -* Use [`spawn()`][] for launching help. +* Use [`os.spawn()`][] for launching help. * Renamed `io.set_buffer_encoding()` to [`buffer:set_encoding()`][]. * Removed Adeptsense in favor of [autocompleter functions][], but kept existing [api file format][]. @@ -1762,7 +1762,7 @@ Changes: [PGP -- 7.3 Linux]: download/textadept_7.3.i386.tgz.asc [PGP -- 7.3 Linux x86_64]: download/textadept_7.3.x86_64.tgz.asc [PGP -- 7.3 Modules]: download/textadept_7.3.modules.zip.asc -[`spawn()`]: api.html#spawn +[`os.spawn()`]: api.html#os.spawn [`buffer:set_encoding()`]: api.html#buffer.set_encoding [autocompleter functions]: api.html#textadept.editing.autocompleters [api file format]: api.html#textadept.editing.api_files @@ -1879,7 +1879,7 @@ Changes: [PGP -- 7.2 beta 3 Modules]: download/textadept_7.2_beta_3.modules.zip.asc [optionselect]: api.html#ui.dialogs.optionselect [`ui.SILENT_PRINT`]: api.html#ui.silent_print -[spawn processes]: api.html#spawn +[spawn processes]: api.html#os.spawn [Snapopen]: manual.html#Quick.Open [building projects]: api.html#_M.Build.a.Project [LuaJIT]: http://luajit.org diff --git a/TECHNOLOGY.md b/TECHNOLOGY.md index 81ccba97..8b39ced5 100644 --- a/TECHNOLOGY.md +++ b/TECHNOLOGY.md @@ -46,9 +46,6 @@ Lua and includes a few external libraries. =[LuaFileSystem][] - Library for accessing directories and file attributes= Textadept uses LFS for accessing the host filesystem. -=[lspawn][]\* - Lua module for spawning processes= - Textadept uses lspawn for spawning asynchronous processes. - \* A Foicica.com project. [GTK+]: http://www.gtk.org @@ -56,7 +53,6 @@ Lua and includes a few external libraries. [Lua]: http://www.lua.org [LPeg]: http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html [LuaFileSystem]: http://keplerproject.github.io/luafilesystem/ -[lspawn]: http://foicica.com/hg/lspawn [gtDialog]: http://foicica.com/gtdialog/ [ncurses]: http://invisible-island.net/ncurses/ [pdcurses]: http://pdcurses.sourceforge.net/ diff --git a/core/.os.luadoc b/core/.os.luadoc new file mode 100644 index 00000000..4dd473fa --- /dev/null +++ b/core/.os.luadoc @@ -0,0 +1,86 @@ +-- Copyright 2007-2018 Mitchell mitchell.att.foicica.com. See LICENSE. +-- This is a DUMMY FILE used for making LuaDoc for built-in functions in the +-- os table. + +--- Extends Lua's `os` library to provide process spawning capabilities. +module('os') + +--- +-- Spawns an interactive child process *argv* in a separate thread, returning +-- a handle to that process. +-- On Windows, *argv* is passed to `cmd.exe`: `%COMSPEC% /c [argv]`. +-- At the moment, only the Windows terminal version spawns processes in the same +-- thread. +-- @param argv A command line string that contains the program's name followed +-- by arguments to pass to it. `PATH` is searched for program names. +-- @param cwd Optional current working directory (cwd) for the child +-- process. The default value is `nil`, which inherits the parent's cwd. +-- @param env Optional list of environment variables for the child process. +-- Each element in the list is a 'KEY=VALUE' string. The default value is +-- `nil`, which inherits the parent's environment. +-- This parameter should be omitted completely instead of specifying `nil`. +-- @param stdout_cb Optional Lua function that accepts a string parameter for a +-- block of standard output read from the child. Stdout is read asynchronously +-- in 1KB or 0.5KB blocks (depending on the platform), or however much data is +-- available at the time. +-- At the moment, only the Win32 terminal version sends all output, whether it +-- be stdout or stderr, to this callback after the process finishes. +-- @param stderr_cb Optional Lua function that accepts a string parameter for a +-- block of standard error read from the child. Stderr is read asynchronously +-- in 1KB or 0.5kB blocks (depending on the platform), or however much data is +-- available at the time. +-- @param exit_cb Optional Lua function that is called when the child process +-- finishes. The child's exit status is passed. +-- @return proc or nil plus an error message on failure +-- @usage os.spawn('lua buffer.filename', nil, print) +-- @usage proc = os.spawn('lua -e "print(io.read())"', nil, print) +-- proc:write('foo\n') +-- @class function +-- @name os.spawn +local spawn + +--- +-- Returns the status of process *spawn_proc*, which is either "running" or +-- "terminated". +-- @return "running" or "terminated" +function spawn_proc:status() end + +--- +-- Blocks until process *spawn_proc* finishes. +function spawn_proc:wait() end + +--- +-- Reads and returns stdout from process *spawn_proc*, according to string +-- format or number *arg*. +-- Similar to Lua's `io.read()` and blocks for input. *spawn_proc* must still be +-- running. If an error occurs while reading, returns `nil`, an error code, and +-- an error message. +-- Ensure any read operations read all stdout available, as the stdout callback +-- function passed to `os.spawn()` will not be called until the stdout buffer is +-- clear. +-- @param arg Optional argument similar to those in Lua's `io.read()`, but "n" +-- is not supported. The default value is "l", which reads a line. +-- @return string of bytes read +function spawn_proc:read(arg) end + +--- +-- Writes string input to the stdin of process *spawn_proc*. +-- Note: On Linux, if more than 65536 bytes (64K) are to be written, it is +-- possible those bytes need to be written in 65536-byte (64K) chunks, or the +-- process may not receive all input. However, it is also possible that there is +-- a limit on how many bytes can be written in a short period of time, perhaps +-- 196608 bytes (192K). +-- @param ... Standard input for *spawn_proc*. +function spawn_proc:write(...) end + +--- +-- Closes standard input for process *spawn_proc*, effectively sending an EOF +-- (end of file) to it. +function spawn_proc:close() end + +--- +-- Kills running process *spawn_proc*, or sends it Unix signal *signal*. +-- @param signal Optional Unix signal to send to *spawn_proc*. The default value +-- is 9 (`SIGKILL`), which kills the process. +function spawn_proc:kill() end +]] diff --git a/core/init.lua b/core/init.lua index 3070181e..25c28043 100644 --- a/core/init.lua +++ b/core/init.lua @@ -18,7 +18,7 @@ keys = require('keys') _M = {} -- language modules table -- pdcurses compatibility. if CURSES and WIN32 then - function spawn(argv, cwd, ...) + function os.spawn(argv, cwd, ...) local current_dir = lfs.currentdir() if cwd then lfs.chdir(cwd) end local p = io.popen(argv..' 2>&1') @@ -154,85 +154,4 @@ local reset -- @class function -- @name timeout local timeout - --- The function below comes from the lspawn module. - ---- --- Spawns an interactive child process *argv* in a separate thread, returning --- a handle to that process. --- On Windows, *argv* is passed to `cmd.exe`: `%COMSPEC% /c [argv]`. --- At the moment, only the Windows terminal version spawns processes in the same --- thread. --- @param argv A command line string that contains the program's name followed --- by arguments to pass to it. `PATH` is searched for program names. --- @param cwd Optional current working directory (cwd) for the child --- process. The default value is `nil`, which inherits the parent's cwd. --- @param env Optional list of environment variables for the child process. --- Each element in the list is a 'KEY=VALUE' string. The default value is --- `nil`, which inherits the parent's environment. --- This parameter should be omitted completely instead of specifying `nil`. --- @param stdout_cb Optional Lua function that accepts a string parameter for a --- block of standard output read from the child. Stdout is read asynchronously --- in 1KB or 0.5KB blocks (depending on the platform), or however much data is --- available at the time. --- At the moment, only the Win32 terminal version sends all output, whether it --- be stdout or stderr, to this callback after the process finishes. --- @param stderr_cb Optional Lua function that accepts a string parameter for a --- block of standard error read from the child. Stderr is read asynchronously --- in 1KB or 0.5kB blocks (depending on the platform), or however much data is --- available at the time. --- @param exit_cb Optional Lua function that is called when the child process --- finishes. The child's exit status is passed. --- @return proc or nil plus an error message on failure --- @usage spawn('lua buffer.filename', nil, print) --- @usage proc = spawn('lua -e "print(io.read())"', nil, print) --- proc:write('foo\n') --- @class function --- @name spawn -local spawn - ---- --- Returns the status of process *spawn_proc*, which is either "running" or --- "terminated". --- @return "running" or "terminated" -function spawn_proc:status() end - ---- --- Blocks until process *spawn_proc* finishes. -function spawn_proc:wait() end - ---- --- Reads and returns stdout from process *spawn_proc*, according to string --- format or number *arg*. --- Similar to Lua's `io.read()` and blocks for input. *spawn_proc* must still be --- running. If an error occurs while reading, returns `nil`, an error code, and --- an error message. --- Ensure any read operations read all stdout available, as the stdout callback --- function passed to `spawn()` will not be called until the stdout buffer is --- clear. --- @param arg Optional argument similar to those in Lua's `io.read()`, but "n" --- is not supported. The default value is "l", which reads a line. --- @return string of bytes read -function spawn_proc:read(arg) end - ---- --- Writes string input to the stdin of process *spawn_proc*. --- Note: On Linux, if more than 65536 bytes (64K) are to be written, it is --- possible those bytes need to be written in 65536-byte (64K) chunks, or the --- process may not receive all input. However, it is also possible that there is --- a limit on how many bytes can be written in a short period of time, perhaps --- 196608 bytes (192K). --- @param ... Standard input for *spawn_proc*. -function spawn_proc:write(...) end - ---- --- Closes standard input for process *spawn_proc*, effectively sending an EOF --- (end of file) to it. -function spawn_proc:close() end - ---- --- Kills running process *spawn_proc*, or sends it Unix signal *signal*. --- @param signal Optional Unix signal to send to *spawn_proc*. The default value --- is 9 (`SIGKILL`), which kills the process. -function spawn_proc:kill() end ]] diff --git a/modules/lua/api b/modules/lua/api index 18b12779..9482fa50 100644 --- a/modules/lua/api +++ b/modules/lua/api @@ -923,7 +923,7 @@ rawlen _G.rawlen(v)\nReturns the length of the object `v`,\nwhich must be a tabl rawset _G.rawset(table, index, value)\nSets the real value of `table[index]` to `value`, without invoking the\n`__newindex` metamethod. `table` must be a table, `index` any value different\nfrom nil and NaN, and `value` any Lua value.\n\nThis function returns `table`. read file:read(···)\nReads the file `file`, according to the given formats, which specify\nwhat to read. For each format, the function returns a string or a number\nwith the characters read, or nil if it cannot read data with the specified\nformat. (In this latter case, the function does not read subsequent formats.)\nWhen called without formats, it uses a default format that reads the next\nline (see below).\n\nThe available formats are\n "n": reads a numeral and returns it as a float or an integer, following the\n lexical conventions of Lua. (The numeral may have leading spaces and a\n sign.) This format always reads the longest input sequence that is a\n valid prefix for a number; if that prefix does not form a valid number\n (e.g., an empty string, "0x", or "3.4e-"), it is discarded and the\n function returns nil.\n "a": reads the whole file, starting at the current position. On end of\n file, it returns the empty string.\n "l": reads the next line skipping the end of line, returning nil on\n end of file. This is the default format.\n "L": reads the next line keeping the end-of-line character (if present),\n returning nil on end of file.\n *number*: reads a string with up to this number of bytes, returning nil on\n end of file. If *number* is zero, it reads nothing and returns an\n empty string, or nil on end of file.\n\nThe formats "l" and "L" should be used only for text files. read io.read(···)\nEquivalent to `io.input():read(···)`. -read spawn_proc:read(arg)\nReads and returns stdout from process *spawn_proc*, according to string\nformat or number *arg*.\nSimilar to Lua's `io.read()` and blocks for input. *spawn_proc* must still be\nrunning. If an error occurs while reading, returns `nil`, an error code, and\nan error message.\nEnsure any read operations read all stdout available, as the stdout callback\nfunction passed to `spawn()` will not be called until the stdout buffer is\nclear.\n@param arg Optional argument similar to those in Lua's `io.read()`, but "n"\n is not supported. The default value is "l", which reads a line.\n@return string of bytes read +read spawn_proc:read(arg)\nReads and returns stdout from process *spawn_proc*, according to string\nformat or number *arg*.\nSimilar to Lua's `io.read()` and blocks for input. *spawn_proc* must still be\nrunning. If an error occurs while reading, returns `nil`, an error code, and\nan error message.\nEnsure any read operations read all stdout available, as the stdout callback\nfunction passed to `os.spawn()` will not be called until the stdout buffer is\nclear.\n@param arg Optional argument similar to those in Lua's `io.read()`, but "n"\n is not supported. The default value is "l", which reads a line.\n@return string of bytes read read_only buffer.read_only (bool)\nWhether or not the buffer is read-only.\nThe default value is `false`. recent_files io.recent_files (table)\nList of recently opened files, the most recent being towards the top. rectangular_selection_anchor buffer.rectangular_selection_anchor (number)\nThe rectangular selection's anchor position. @@ -1064,7 +1064,7 @@ snippets _G.snippets (table)\nMap of snippet triggers with their snippet text or snippets textadept.snippets (module)\nSnippets for Textadept. sort table.sort(list [, comp])\nSorts list elements in a given order, *in-place*, from `list[1]` to\n`list[#list]`. If `comp` is given, then it must be a function that receives\ntwo list elements and returns true when the first element must come before\nthe second in the final order (so that, after the sort, `i < j` implies\n`not comp(list[j],list[i])` will be true after the sort). If `comp` is not\ngiven, then the standard Lua operator `<` is used instead.\n\nNote that the `comp` function must not define a string partial order over the\nelements in the list; that is, it must be asymmetric and transitive.\nOtherwise, no valid sort may be possible.\n\nThe sort algorithm is not stable; that is, elements not comparable by the\ngiven order (e.g., equal elements) may have their relative positions changed\nby the sort. space lexer.space (pattern)\nA pattern that matches any whitespace character ('\t', '\v', '\f', '\n',\n'\r', space). -spawn _G.spawn(argv, cwd, env, stdout_cb, stderr_cb, exit_cb)\nSpawns an interactive child process *argv* in a separate thread, returning\na handle to that process.\nOn Windows, *argv* is passed to `cmd.exe`: `%COMSPEC% /c [argv]`.\nAt the moment, only the Windows terminal version spawns processes in the same\nthread.\n@param argv A command line string that contains the program's name followed\n by arguments to pass to it. `PATH` is searched for program names.\n@param cwd Optional current working directory (cwd) for the child\n process. The default value is `nil`, which inherits the parent's cwd.\n@param env Optional list of environment variables for the child process.\n Each element in the list is a 'KEY=VALUE' string. The default value is\n `nil`, which inherits the parent's environment.\n This parameter should be omitted completely instead of specifying `nil`.\n@param stdout_cb Optional Lua function that accepts a string parameter for a\n block of standard output read from the child. Stdout is read asynchronously\n in 1KB or 0.5KB blocks (depending on the platform), or however much data is\n available at the time.\n At the moment, only the Win32 terminal version sends all output, whether it\n be stdout or stderr, to this callback after the process finishes.\n@param stderr_cb Optional Lua function that accepts a string parameter for a\n block of standard error read from the child. Stderr is read asynchronously\n in 1KB or 0.5kB blocks (depending on the platform), or however much data is\n available at the time.\n@param exit_cb Optional Lua function that is called when the child process\n finishes. The child's exit status is passed.\n@usage spawn('lua buffer.filename', nil, print)\n@usage proc = spawn('lua -e "print(io.read())"', nil, print)\n proc:write('foo\n')\n@return proc or nil plus an error message on failure +spawn os.spawn(argv, cwd, env, stdout_cb, stderr_cb, exit_cb)\nSpawns an interactive child process *argv* in a separate thread, returning\na handle to that process.\nOn Windows, *argv* is passed to `cmd.exe`: `%COMSPEC% /c [argv]`.\nAt the moment, only the Windows terminal version spawns processes in the same\nthread.\n@param argv A command line string that contains the program's name followed\n by arguments to pass to it. `PATH` is searched for program names.\n@param cwd Optional current working directory (cwd) for the child\n process. The default value is `nil`, which inherits the parent's cwd.\n@param env Optional list of environment variables for the child process.\n Each element in the list is a 'KEY=VALUE' string. The default value is\n `nil`, which inherits the parent's environment.\n This parameter should be omitted completely instead of specifying `nil`.\n@param stdout_cb Optional Lua function that accepts a string parameter for a\n block of standard output read from the child. Stdout is read asynchronously\n in 1KB or 0.5KB blocks (depending on the platform), or however much data is\n available at the time.\n At the moment, only the Win32 terminal version sends all output, whether it\n be stdout or stderr, to this callback after the process finishes.\n@param stderr_cb Optional Lua function that accepts a string parameter for a\n block of standard error read from the child. Stderr is read asynchronously\n in 1KB or 0.5kB blocks (depending on the platform), or however much data is\n available at the time.\n@param exit_cb Optional Lua function that is called when the child process\n finishes. The child's exit status is passed.\n@usage os.spawn('lua buffer.filename', nil, print)\n@usage proc = os.spawn('lua -e "print(io.read())"', nil, print)\n proc:write('foo\n')\n@return proc or nil plus an error message on failure split view.split(view, vertical)\nSplits the view into top and bottom views (unless *vertical* is `true`),\nfocuses the new view, and returns both the old and new views.\nIf *vertical* is `false`, splits the view vertically into left and\nright views.\nEmits a `VIEW_NEW` event.\n@param view The view to split.\n@param vertical Optional flag indicating whether or not to split the view\n vertically. The default value is `false`, for horizontal.\n@return old view and new view.\n@see events.VIEW_NEW sqrt math.sqrt(x)\nReturns the square root of `x`. (You can also use the expression `x^0.5`\nto compute this value.) standard_dropdown ui.dialogs.standard_dropdown(options)\nPrompts the user with a drop-down item selection dialog defined by dialog\noptions table *options* and with localized "Ok" and "Cancel" buttons,\nreturning the selected button's index along with the selected item's index.\nIf *options*.`string_output` is `true`, returns the selected button's label\nalong with the selected item's text.\nIf the dialog closed due to *options*.`exit_onchange`, returns `4` along with\neither the selected item's index or its text. If the dialog timed out,\nreturns `0` or `"timeout"`. If the user canceled the dialog, returns `-1` or\n`"delete"`.\n@param options Table of key-value option pairs for the drop-down dialog.\n\n * `title`: The dialog's title text.\n * `text`: The dialog's main message text.\n * `items`: The list of string items to show in the drop-down.\n * `no_cancel`: Do not display the "Cancel" button. The default value is\n `false`.\n * `exit_onchange`: Close the dialog after selecting a new item. The default\n value is `false`.\n * `select`: The index of the initially selected list item. The default\n value is `1`.\n * `string_output`: Return the selected button's label (instead of its\n index) and the selected item's text (instead of its index). If no item\n was selected, returns the dialog's exit status (instead of its exit\n code). The default value is `false`.\n * `width`: The dialog's pixel width.\n * `height`: The dialog's pixel height.\n * `float`: Show the dialog on top of all desktop windows. The default value\n is `false`.\n * `timeout`: The integer number of seconds the dialog waits for the user to\n select a button before timing out. Dialogs do not time out by default.\n@return selected button or exit code, selected item diff --git a/modules/lua/tags b/modules/lua/tags index 4929c495..727d414c 100644 --- a/modules/lua/tags +++ b/modules/lua/tags @@ -1072,7 +1072,7 @@ snippets _ 0;" m class:textadept snippets _ 0;" t sort _ 0;" f class:table space _ 0;" F class:lexer -spawn _ 0;" f +spawn _ 0;" f class:os split _ 0;" f class:view sqrt _ 0;" f class:math standard_dropdown _ 0;" f class:ui.dialogs diff --git a/modules/textadept/editing.lua b/modules/textadept/editing.lua index b8d7e341..cf449234 100644 --- a/modules/textadept/editing.lua +++ b/modules/textadept/editing.lua @@ -599,7 +599,7 @@ function M.filter_through(command) }), command) local output = buffer.target_text for i = 1, #commands do - local p = assert(spawn(commands[i])) + local p = assert(os.spawn(commands[i])) p:write(output) p:close() output = p:read('a') or '' diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua index da9703b2..f82d36f6 100644 --- a/modules/textadept/menu.lua +++ b/modules/textadept/menu.lua @@ -44,7 +44,7 @@ local function set_encoding(encoding) end local function open_page(url) local cmd = (WIN32 and 'start ""') or (OSX and 'open') or 'xdg-open' - spawn(string.format('%s "%s"', cmd, not OSX and url or 'file://'..url)) + os.spawn(string.format('%s "%s"', cmd, not OSX and url or 'file://'..url)) end --- diff --git a/modules/textadept/run.lua b/modules/textadept/run.lua index c81e3aa4..f8324bfe 100644 --- a/modules/textadept/run.lua +++ b/modules/textadept/run.lua @@ -158,7 +158,7 @@ local function compile_or_run(filename, commands) local event = commands == M.compile_commands and events.COMPILE_OUTPUT or events.RUN_OUTPUT local ext_or_lexer = commands[ext] and ext or lexer - local function emit_output(output) + local function emit(output) for line in output:gmatch('[^\r\n]+') do events.emit(event, line, ext_or_lexer) end @@ -167,7 +167,7 @@ local function compile_or_run(filename, commands) cwd = working_dir or dirname if cwd ~= dirname then events.emit(event, '> cd '..cwd) end events.emit(event, '> '..command:iconv('UTF-8', _CHARSET)) - proc = assert(spawn(command, cwd, emit_output, emit_output, function(status) + proc = assert(os.spawn(command, cwd, emit, emit, function(status) events.emit(event, '> exit status: '..status) end)) end @@ -283,7 +283,7 @@ function M.build(root_directory) if not command then return end -- Prepare to run the command. preferred_view = view - local function emit_output(output) + local function emit(output) for line in output:gmatch('[^\r\n]+') do events.emit(events.BUILD_OUTPUT, line) end @@ -292,7 +292,7 @@ function M.build(root_directory) cwd = working_dir or root_directory events.emit(events.BUILD_OUTPUT, '> cd '..cwd) events.emit(events.BUILD_OUTPUT, '> '..command:iconv('UTF-8', _CHARSET)) - proc = assert(spawn(command, cwd, emit_output, emit_output, function(status) + proc = assert(os.spawn(command, cwd, emit, emit, function(status) events.emit(events.BUILD_OUTPUT, '> exit status: '..status) end)) end 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 } |