1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 05:30:07 +08:00
SpaceVim/lua/spacevim/api/job.lua

307 lines
7.6 KiB
Lua
Raw Normal View History

--=============================================================================
-- job.lua ---
-- Copyright (c) 2016-2022 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
local M = {}
local uv = vim.loop
local _jobs = {}
local _jobid = 0
2023-07-23 23:36:52 +08:00
local function buffered_data(eof, data)
data = data:gsub('\r', '')
local std_data = vim.split(data, '\n')
if #std_data > 1 and std_data[#std_data] == '' then
std_data[1] = eof .. std_data[1]
table.remove(std_data, #std_data)
eof = ''
elseif #std_data > 1 then
std_data[1] = eof .. std_data[1]
eof = std_data[#std_data]
table.remove(std_data, #std_data)
elseif #std_data == 1 and std_data[1] == '' and eof ~= '' then
std_data = { eof }
eof = ''
elseif #std_data == 1 and std_data[#std_data] ~= '' then
eof = std_data[#std_data]
end
return eof, std_data
end
local function new_job_obj(id, handle, opt, state)
local jobobj = {
id = id,
handle = handle,
opt = opt,
state = state,
}
return jobobj
end
local function default_dev() -- {{{
2024-04-16 11:30:52 +08:00
local env = vim.fn.environ()
env['NVIM'] = vim.v.servername
env['NVIM_LISTEN_ADDRESS'] = nil
env['NVIM_LOG_FILE'] = nil
env['VIMRUNTIME'] = nil
return env
end
-- }}}
local function setup_env(env, clear_env) -- {{{
2024-04-16 11:30:52 +08:00
if clear_env then
return env
end
--- @type table<string,string|number>
env = vim.tbl_extend('force', default_dev(), env or {})
2024-04-16 11:30:52 +08:00
local renv = {} --- @type string[]
for k, v in pairs(env) do
renv[#renv + 1] = string.format('%s=%s', k, tostring(v))
end
2024-04-16 11:30:52 +08:00
return renv
end
-- }}}
--- @param cmd string|table<string> Spawns {cmd} as a job.
--- @param opts table job options
--- @return integer # jobid if job run successfully.
--- jobid: if job run successfully
--- 0: if type of cmd is wrong
--- -1: if cmd[1] is not executable
function M.start(cmd, opts)
local command = ''
local argv = {}
if type(cmd) == 'string' then
2024-04-16 14:55:26 +08:00
if cmd == '' then
return 0
end
local shell = vim.fn.split(vim.o.shell)
local shellcmdflag = vim.fn.split(vim.o.shellcmdflag)
-- :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
command = shell[1]
argv = vim.list_slice(shell, 2)
for _, v in ipairs(shellcmdflag) do
table.insert(argv, v)
end
table.insert(argv, cmd)
elseif type(cmd) == 'table' then
2024-04-16 14:57:06 +08:00
if #cmd == 0 then
return 0
end
2024-04-16 14:55:26 +08:00
for _, v in ipairs(cmd) do
if type(v) ~= 'string' then
return 0
end
end
command = cmd[1]
2024-04-16 14:55:26 +08:00
if command == '' then
return 0
end
if vim.fn.executable(command) == 0 then
return -1
end
argv = vim.list_slice(cmd, 2)
else
return 0
end
local stdin = uv.new_pipe()
local stdout = uv.new_pipe()
local stderr = uv.new_pipe()
local opt = {
stdio = { stdin, stdout, stderr },
args = argv,
2023-07-07 00:21:23 +08:00
cwd = opts.cwd or nil,
hide = true,
detached = opts.detached or nil,
env = setup_env(opts.env, opts.clear_env),
}
_jobid = _jobid + 1
local current_id = _jobid
local exit_cb
if opts.on_exit then
exit_cb = function(code, singin)
2024-04-17 21:21:31 +08:00
if stdout and not stdout:is_closing() then
2023-07-15 15:52:00 +08:00
stdout:close()
end
2024-04-17 21:21:31 +08:00
if stderr and not stderr:is_closing() then
2023-07-15 15:52:00 +08:00
stderr:close()
end
2024-04-17 21:21:31 +08:00
if stdin and not stdin:is_closing() then
stdin:close()
end
local job = _jobs['jobid_' .. current_id]
if job and job.handle and not job.handle:is_closing() then
job.handle:close()
end
vim.schedule(function()
opts.on_exit(current_id, code, singin)
end)
end
2023-07-15 15:52:00 +08:00
else
exit_cb = function(code, singin)
2024-04-17 21:21:31 +08:00
if stdout and not stdout:is_closing() then
2023-07-15 15:52:00 +08:00
stdout:close()
end
2024-04-17 21:21:31 +08:00
if stderr and not stderr:is_closing() then
2023-07-15 15:52:00 +08:00
stderr:close()
end
2024-04-17 21:21:31 +08:00
if stdin and not stdin:is_closing() then
stdin:close()
end
local job = _jobs['jobid_' .. current_id]
if job and job.handle and not job.handle:is_closing() then
job.handle:close()
end
2023-07-15 15:52:00 +08:00
end
end
local handle, pid = uv.spawn(command, opt, exit_cb)
_jobs['jobid_' .. _jobid] = new_job_obj(_jobid, handle, opts, {
stdout = stdout,
stderr = stderr,
stdin = stdin,
pid = pid,
2023-07-23 23:36:52 +08:00
stderr_eof = '',
stdout_eof = '',
})
2023-07-23 23:36:52 +08:00
-- logger.debug(vim.inspect(_jobs['jobid_' .. _jobid]))
if opts.on_stdout then
2023-07-13 00:53:03 +08:00
-- define on_stdout function based on stdout's nparams
local nparams = debug.getinfo(opts.on_stdout).nparams
if nparams == 2 then
uv.read_start(stdout, function(err, data)
if data then
2023-07-23 23:36:52 +08:00
local stdout_data
_jobs['jobid_' .. current_id].state.stdout_eof, stdout_data =
2024-04-16 11:30:52 +08:00
buffered_data(_jobs['jobid_' .. current_id].state.stdout_eof, data)
2023-07-13 00:53:03 +08:00
vim.schedule(function()
2023-07-23 23:36:52 +08:00
opts.on_stdout(current_id, stdout_data)
2023-07-13 00:53:03 +08:00
end)
end
end)
else
uv.read_start(stdout, function(err, data)
if data then
2023-07-23 23:36:52 +08:00
local stdout_data
_jobs['jobid_' .. current_id].state.stdout_eof, stdout_data =
2024-04-16 11:30:52 +08:00
buffered_data(_jobs['jobid_' .. current_id].state.stdout_eof, data)
2023-07-13 00:53:03 +08:00
vim.schedule(function()
2023-07-23 23:36:52 +08:00
opts.on_stdout(current_id, stdout_data, 'stdout')
2023-07-13 00:53:03 +08:00
end)
end
end)
end
end
if opts.on_stderr then
2023-07-13 00:53:03 +08:00
local nparams = debug.getinfo(opts.on_stderr).nparams
if nparams == 2 then
uv.read_start(stderr, function(err, data)
if data then
2023-07-23 23:36:52 +08:00
local stderr_data
_jobs['jobid_' .. current_id].state.stderr_eof, stderr_data =
2024-04-16 11:30:52 +08:00
buffered_data(_jobs['jobid_' .. current_id].state.stderr_eof, data)
2023-07-13 00:53:03 +08:00
vim.schedule(function()
2023-07-23 23:36:52 +08:00
opts.on_stderr(current_id, stderr_data)
2023-07-13 00:53:03 +08:00
end)
end
end)
else
uv.read_start(stderr, function(err, data)
if data then
2023-07-23 23:36:52 +08:00
local stderr_data
_jobs['jobid_' .. current_id].state.stderr_eof, stderr_data =
2024-04-16 11:30:52 +08:00
buffered_data(_jobs['jobid_' .. current_id].state.stderr_eof, data)
2023-07-13 00:53:03 +08:00
vim.schedule(function()
2023-07-23 23:36:52 +08:00
opts.on_stderr(current_id, stderr_data, 'stderr')
2023-07-13 00:53:03 +08:00
end)
end
end)
end
end
return current_id
end
function M.send(id, data) -- {{{
2024-04-16 11:30:52 +08:00
local jobobj = _jobs['jobid_' .. id]
2024-04-16 11:30:52 +08:00
if not jobobj then
error('can not find job:' .. id)
end
2024-04-16 11:30:52 +08:00
local stdin = jobobj.state.stdin
2024-04-16 11:30:52 +08:00
if not stdin then
error('no stdin stream for jobid:' .. id)
end
2024-04-16 11:30:52 +08:00
if type(data) == 'table' then
for _, v in ipairs(data) do
stdin:write(v)
stdin:write('\n')
end
elseif type(data) == 'string' then
stdin:write(data)
2023-08-31 22:07:56 +08:00
stdin:write('\n')
2024-04-16 11:30:52 +08:00
elseif data == nil then
stdin:write('', function()
stdin:shutdown(function()
if stdin then
stdin:close()
end
end)
2024-04-16 11:27:49 +08:00
end)
2024-04-16 11:30:52 +08:00
end
end
function M.chanclose(id, t)
local jobobj = _jobs['jobid_' .. id]
if not jobobj then
error('can not find job:' .. id)
end
if t == 'stdin' then
local stdin = jobobj.state.stdin
2024-04-17 21:21:31 +08:00
if stdin and not stdin:is_closing() then
2024-04-16 11:27:49 +08:00
stdin:close()
end
elseif t == 'stdout' then
2024-04-17 21:21:31 +08:00
local stdout = jobobj.state.stdout
if stdout and not stdout:is_closing() then
stdout:close()
end
elseif t == 'stderr' then
2024-04-17 21:21:31 +08:00
local stderr = jobobj.state.stderr
if stderr and not stderr:is_closing() then
stderr:close()
end
else
error('the type only can be:stdout, stdin or stderr')
end
end
function M.stop(id)
local jobobj = _jobs['jobid_' .. id]
if not jobobj then
return
end
local handle = jobobj.handle
handle:kill(6)
end
return M