Skip to content

Commit

Permalink
Bump to alpha.13, update wrapping to support connections, add spawn_w…
Browse files Browse the repository at this point in the history
…rap function, add support for builder to use --cmd and --shell
  • Loading branch information
chipsenkbeil committed Jul 14, 2023
1 parent e9b8fa8 commit 479e40c
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ RUN mkdir -p $opt_lsp_dir \
&& ln -s $opt_bin_dir/lua-language-server /usr/local/bin/lua-language-server

# Install distant binary and make sure its in a path for everyone
ARG distant_version=0.20.0-alpha.12
ARG distant_version=0.20.0-alpha.13
ARG distant_host=x86_64-unknown-linux-musl
RUN curl -L sh.distant.dev | sh -s -- --install-dir "$opt_bin_dir" --distant-version $distant_version --distant-host $distant_host --run-as-admin \
&& ln -s "$opt_bin_dir/distant" /usr/local/bin/distant \
Expand Down
5 changes: 3 additions & 2 deletions lua/distant-core/builder/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ function M.shell(cmd)
end

--- @param cmd string|string[]
--- @param use_cmd_arg? boolean
--- @return distant.core.builder.SpawnCmdBuilder
function M.spawn(cmd)
return DistantSpawnCmdBuilder:new(cmd)
function M.spawn(cmd, use_cmd_arg)
return DistantSpawnCmdBuilder:new(cmd, use_cmd_arg)
end

return M
59 changes: 57 additions & 2 deletions lua/distant-core/builder/spawn.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ M.__index = M

--- Creates a new `spawn` cmd
--- @param cmd string|string[] #command to execute on the remote machine
--- @param use_cmd_arg? boolean # if true, will pass `cmd` as str via `--cmd <cmd>` instead of using `-- <cmd>`
--- @return distant.core.builder.SpawnCmdBuilder
function M:new(cmd)
function M:new(cmd, use_cmd_arg)
local instance = {}
setmetatable(instance, M)

Expand All @@ -22,18 +23,26 @@ function M:new(cmd)
allowed = {
'config',
'cache',
'cmd',
'connection',
'current-dir',
'environment',
'log-file',
'log-level',
'lsp',
'pty',
'shell',
'unix-socket',
'windows-pipe',
}
})
:set_tail(cmd)

if use_cmd_arg then
--- @cast instance distant.core.builder.SpawnCmdBuilder
instance = instance:set_cmd(cmd)
else
instance.cmd = instance.cmd:set_tail(cmd)
end

return instance
end
Expand Down Expand Up @@ -64,6 +73,39 @@ function M:set_cache(path)
return self
end

--- Sets `--cmd <cmd>`
--- @param cmd string
--- @return distant.core.builder.SpawnCmdBuilder
function M:set_cmd(cmd)
vim.validate({ cmd = { cmd, 'string' } })

-- NOTE: Normally, when a string is set using the
-- builder, it is NOT quoted, which means that
-- cmd = "echo hello" would turn into
-- `--cmd echo hello`. So, we need to provide
-- quoting for the value.
--
-- To that affect, we are going to check if the
-- trimmed command begins and ends with matching
-- single or double quotes. If not, we wrap it in
-- single quotes for now, unless using cmd.exe,
-- in which case we wrap in double quotes.
cmd = vim.trim(cmd)
if (
not (vim.startswith(cmd, '"') and vim.endswith(cmd, '"'))
and not (vim.startswith(cmd, "'") and vim.endswith(cmd, "'"))
) then
if vim.o.shell == 'cmd.exe' then
cmd = '"' .. cmd:gsub('"', '""') .. '"'
else
cmd = "'" .. cmd:gsub("'", "'\\''") .. "'"
end
end

self.cmd:set('cmd', cmd)
return self
end

--- Sets `--connection <id>`
--- @param id string
--- @return distant.core.builder.SpawnCmdBuilder
Expand Down Expand Up @@ -134,6 +176,19 @@ function M:set_pty()
return self
end

--- Sets `--shell` or `--shell <path>`
--- @param value boolean|string
--- @return distant.core.builder.SpawnCmdBuilder
function M:set_shell(value)
vim.validate({ value = { value, { 'boolean', 'string' } } })
if value == true then
self.cmd:set('shell')
else
self.cmd:set('shell', value)
end
return self
end

--- Sets `--unix-socket <path>`
--- @param path string
--- @return distant.core.builder.SpawnCmdBuilder
Expand Down
67 changes: 63 additions & 4 deletions lua/distant-core/client.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local Api = require('distant-core.api')
local builder = require('distant-core.builder')
local Job = require('distant-core.job')
local log = require('distant-core.log')
local utils = require('distant-core.utils')

Expand Down Expand Up @@ -283,6 +284,44 @@ function M:spawn_shell(opts)
return job_id, bufnr
end

--- @class distant.core.client.SpawnWrapOpts
--- @field cmd string|string[]
--- @field cwd? string
--- @field env? table<string,string>
--- @field shell? string|string[]|true

--- Spawns a `cmd` created using the `wrap` method,
--- buffering stdout and stderr as it is received.
---
--- @param opts distant.core.client.SpawnWrapOpts
--- @param cb? fun(err?:string, exit_status:distant.core.job.ExitStatus)
--- @return distant.core.Job
function M:spawn_wrap(opts, cb)
vim.validate({
opts = { opts, 'table' },
cb = { cb, validate_callable({ optional = true }) },
})

local cmd = self:wrap({
cmd = opts.cmd,
cwd = opts.cwd,
env = opts.env,
shell = opts.shell,
})

local job = Job:new({ buffer_stdout = true, buffer_stderr = true })

-- Start the job, passing in our optional callback when it's done,
-- and only explicitly error if we do not have a callback to
-- report the error
local ok = job:start({ cmd = cmd }, cb)
if not ok and not cb then
error('Failed to spawn cmd: ' .. vim.inspect(cmd))
end

return job
end

--- @class distant.core.client.WrapOpts
--- @field cmd? string|string[]
--- @field lsp? string|string[]
Expand All @@ -291,10 +330,13 @@ end
--- @field env? table<string,string>
--- @field scheme? string

--- Wraps cmd, lsp, or shell to be invoked via distant. Returns
--- Wraps cmd, lsp, or shell to be invoked via distant for this client. Returns
--- a string if the input is a string, or a list if the input
--- is a list.
---
--- If both `cmd` and `shell` are provided, then `spawn` is invoked with
--- the command and `shell` is used as the `--shell <shell>` parameter.
---
--- @param opts distant.core.client.WrapOpts
--- @return string|string[]
function M:wrap(opts)
Expand All @@ -305,28 +347,44 @@ function M:wrap(opts)
local has_lsp = opts.lsp ~= nil
local has_shell = opts.shell ~= nil

-- We require exactly one of "cmd", "lsp", or "shell" UNLESS
-- we are given "cmd" and "shell", in which case "shell" is used
-- as the `--shell <shell>` parameter for the spawn command
if not has_cmd and not has_lsp and not has_shell then
error('Missing one of ["cmd", "lsp", "shell"] argument')
elseif (has_cmd and has_lsp) or (has_cmd and has_shell) or (has_lsp and has_shell) then
elseif (has_cmd and has_lsp) or (has_lsp and has_shell) then
error('Can only have exactly one of ["cmd", "lsp", "shell"] argument')
end

--- @type string[]
local result = {}

if has_cmd then
local cmd = builder.spawn(opts.cmd)
-- Prefer `--cmd '...'` over `-- ...`
local cmd = builder.spawn(opts.cmd, true)
:set_connection(tostring(self.id))
if type(opts.cwd) == 'string' then
cmd = cmd:set_current_dir(opts.cwd)
end
if opts.env then
cmd = cmd:set_environment(opts.env)
end

-- If we were given a shell, then set it as a parameter here
local shell = opts.shell
if shell then
if type(shell) == 'table' then
shell = table.concat(shell, ' ')
end
cmd = cmd:set_shell(shell)
end

result = cmd:set_from_tbl(self.config.network):as_list()
table.insert(result, 1, self.config.binary)
elseif has_lsp then
local cmd = builder.spawn(opts.lsp):set_lsp(opts.scheme or true)
local cmd = builder.spawn(opts.lsp)
:set_connection(tostring(self.id))
:set_lsp(opts.scheme or true)
if opts.cwd then
cmd = cmd:set_current_dir(opts.cwd)
end
Expand All @@ -339,6 +397,7 @@ function M:wrap(opts)
elseif has_shell then
-- Build with no explicit cmd by default (use $SHELL)
local cmd = builder.shell()
:set_connection(tostring(self.id))

-- If provided a specific shell, use that instead of default
if type(opts.shell) == 'string' or type(opts.shell) == 'table' then
Expand Down
16 changes: 14 additions & 2 deletions lua/distant-core/job.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ local AuthHandler = require('distant-core.auth')
--- @field private __stderr_lines string[]|nil # if indicated, will collect lines here
--- @field private __on_stdout_line? distant.core.job.OnStdoutLine
--- @field private __on_stderr_line? distant.core.job.OnStderrLine
--- @field private __exit_status distant.core.job.ExitStatus|nil # populated once finished
local M = {}
M.__index = M

Expand Down Expand Up @@ -314,13 +315,18 @@ function M:start(opts, cb)
on_exit = function(_, exit_code, _)
local success = exit_code == 0
local signal = self.to_signal(exit_code)
cb(nil, {
local exit_status = {
success = success,
exit_code = exit_code,
signal = signal,
stdout = self.__stdout_lines or {},
stderr = self.__stderr_lines or {},
})
}

-- Update our job to contain the status
-- and report the status in our callback
self.__exit_status = exit_status
cb(nil, exit_status)
end,
})

Expand Down Expand Up @@ -382,6 +388,12 @@ function M:stderr_lines()
return self.__stderr_lines or {}
end

--- Returns the exit status and other information once the job has finished, otherwise nil.
--- @return distant.core.job.ExitStatus|nil
function M:exit_status()
return self.__exit_status
end

--- @param data any
--- @return integer # number of bytes written, or 0 if failed
function M:write(data)
Expand Down
45 changes: 43 additions & 2 deletions lua/distant/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ function M:setup(settings, opts)
}, '\n'))
return
end

if self:is_initialized() then
log.warn(table.concat({
'distant:setup() called more than once!',
Expand Down Expand Up @@ -842,17 +842,58 @@ end
-- WRAP API
-------------------------------------------------------------------------------


--- @class distant.plugin.SpawnWrapOpts
--- @field client_id? distant.core.manager.ConnectionId # if provided, will wrap using the specified client
--- @field cmd? string|string[] # wraps a regular command
--- @field cwd? string # specifies the current working directory
--- @field env? table<string,string> # specifies environment variables for the spawned process
--- @field shell? string|string[]|true # used to define that the process should be spawned in a shell

--- Performs a client wrapping of the given `cmd`, `lsp`, or `shell` parameter.
---
--- If both `cmd` and `shell` are provided, then `spawn` is invoked with
--- the command and `shell` is used as the `--shell <shell>` parameter.
---
--- If `client_id` is provided, will wrap using the given client; otherwise,
--- will use the active client. Will fail if the client is not available.
---
--- Returns a job representing the spawned process.
---
--- @param opts distant.plugin.SpawnWrapOpts
--- @param cb? fun(err?:string, exit_status:distant.core.job.ExitStatus)
--- @return distant.core.Job
function M:spawn_wrap(opts, cb)
log.fmt_trace('distant:spawn_wrap(%s)', opts)
self:__assert_initialized()

local client = assert(
self:client(opts.client_id),
'Client unavailable for spawning wrapped cmd'
)

return client:spawn_wrap({
cmd = opts.cmd,
cwd = opts.cwd,
env = opts.env,
shell = opts.shell,
}, cb)
end

--- @class distant.plugin.WrapOpts
--- @field client_id? distant.core.manager.ConnectionId # if provided, will wrap using the specified client
--- @field cmd? string|string[] # wraps a regular command
--- @field lsp? string|string[] # wraps an LSP server command
--- @field shell? string|string[]|true # wraps a shell, taking an optional shell command
--- @field shell? string|string[]|true # wraps a shell, taking an optional shell command (or --shell if using `cmd`)
--- @field cwd? string # specifies the current working directory
--- @field env? table<string,string> # specifies environment variables for the spawned process
--- @field scheme? string # if provided, uses this scheme instead of the default (lsp only)

--- Performs a client wrapping of the given `cmd`, `lsp`, or `shell` parameter.
---
--- If both `cmd` and `shell` are provided, then `spawn` is invoked with
--- the command and `shell` is used as the `--shell <shell>` parameter.
---
--- If `client_id` is provided, will wrap using the given client; otherwise,
--- will use the active client. Will fail if the client is not available.
---
Expand Down

0 comments on commit 479e40c

Please sign in to comment.