fix: actions.terminal supports ssh adapter (#152)
This commit is contained in:
parent
0e5fca35cd
commit
0ccf95ae5d
4 changed files with 83 additions and 31 deletions
|
|
@ -138,11 +138,32 @@ M.toggle_hidden = {
|
||||||
M.open_terminal = {
|
M.open_terminal = {
|
||||||
desc = "Open a terminal in the current directory",
|
desc = "Open a terminal in the current directory",
|
||||||
callback = function()
|
callback = function()
|
||||||
local dir = oil.get_current_dir()
|
local config = require("oil.config")
|
||||||
if dir then
|
local bufname = vim.api.nvim_buf_get_name(0)
|
||||||
|
local adapter = config.get_adapter_by_scheme(bufname)
|
||||||
|
if not adapter then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if adapter.name == "files" then
|
||||||
|
local dir = oil.get_current_dir()
|
||||||
|
assert(dir, "Oil buffer with files adapter must have current directory")
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
vim.fn.termopen(vim.o.shell, { cwd = dir })
|
||||||
|
elseif adapter.name == "ssh" then
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
local url = require("oil.adapters.ssh").parse_url(bufname)
|
||||||
|
local cmd = require("oil.adapters.ssh.connection").create_ssh_command(url)
|
||||||
|
local term_id = vim.fn.termopen(cmd)
|
||||||
|
if term_id then
|
||||||
|
vim.api.nvim_chan_send(term_id, string.format("cd %s\n", url.path))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify(
|
||||||
|
string.format("Cannot open terminal for unsupported adapter: '%s'", adapter.name),
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ local FIELD_META = constants.FIELD_META
|
||||||
|
|
||||||
---@param oil_url string
|
---@param oil_url string
|
||||||
---@return oil.sshUrl
|
---@return oil.sshUrl
|
||||||
local function parse_url(oil_url)
|
M.parse_url = function(oil_url)
|
||||||
local scheme, url = util.parse_url(oil_url)
|
local scheme, url = util.parse_url(oil_url)
|
||||||
|
assert(scheme and url, string.format("Malformed input url '%s'", oil_url))
|
||||||
local ret = { scheme = scheme }
|
local ret = { scheme = scheme }
|
||||||
local username, rem = url:match("^([^@%s]+)@(.*)$")
|
local username, rem = url:match("^([^@%s]+)@(.*)$")
|
||||||
ret.user = username
|
ret.user = username
|
||||||
|
|
@ -80,11 +81,19 @@ local function url_to_scp(url)
|
||||||
return table.concat(pieces, "")
|
return table.concat(pieces, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param url1 oil.sshUrl
|
||||||
|
---@param url2 oil.sshUrl
|
||||||
|
---@return boolean
|
||||||
|
local function url_hosts_equal(url1, url2)
|
||||||
|
return url1.host == url2.host and url1.port == url2.port and url1.user == url2.user
|
||||||
|
end
|
||||||
|
|
||||||
local _connections = {}
|
local _connections = {}
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param allow_retry nil|boolean
|
---@param allow_retry nil|boolean
|
||||||
|
---@return oil.sshFs
|
||||||
local function get_connection(url, allow_retry)
|
local function get_connection(url, allow_retry)
|
||||||
local res = parse_url(url)
|
local res = M.parse_url(url)
|
||||||
res.scheme = config.adapter_to_scheme.ssh
|
res.scheme = config.adapter_to_scheme.ssh
|
||||||
res.path = ""
|
res.path = ""
|
||||||
local key = url_to_str(res)
|
local key = url_to_str(res)
|
||||||
|
|
@ -124,7 +133,7 @@ ssh_columns.permissions = {
|
||||||
end,
|
end,
|
||||||
|
|
||||||
perform_action = function(action, callback)
|
perform_action = function(action, callback)
|
||||||
local res = parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
conn:chmod(action.value, res.path, callback)
|
conn:chmod(action.value, res.path, callback)
|
||||||
end,
|
end,
|
||||||
|
|
@ -168,7 +177,7 @@ end
|
||||||
---@param bufname string
|
---@param bufname string
|
||||||
---@return string
|
---@return string
|
||||||
M.get_parent = function(bufname)
|
M.get_parent = function(bufname)
|
||||||
local res = parse_url(bufname)
|
local res = M.parse_url(bufname)
|
||||||
res.path = pathutil.parent(res.path)
|
res.path = pathutil.parent(res.path)
|
||||||
return url_to_str(res)
|
return url_to_str(res)
|
||||||
end
|
end
|
||||||
|
|
@ -176,7 +185,7 @@ end
|
||||||
---@param url string
|
---@param url string
|
||||||
---@param callback fun(url: string)
|
---@param callback fun(url: string)
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local res = parse_url(url)
|
local res = M.parse_url(url)
|
||||||
local conn = get_connection(url, true)
|
local conn = get_connection(url, true)
|
||||||
|
|
||||||
local path = res.path
|
local path = res.path
|
||||||
|
|
@ -199,7 +208,7 @@ end
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param callback fun(err: nil|string, entries: nil|oil.InternalEntry[])
|
---@param callback fun(err: nil|string, entries: nil|oil.InternalEntry[])
|
||||||
M.list = function(url, column_defs, callback)
|
M.list = function(url, column_defs, callback)
|
||||||
local res = parse_url(url)
|
local res = M.parse_url(url)
|
||||||
|
|
||||||
cache.begin_update_url(url)
|
cache.begin_update_url(url)
|
||||||
local conn = get_connection(url)
|
local conn = get_connection(url)
|
||||||
|
|
@ -267,7 +276,7 @@ end
|
||||||
---@param cb fun(err: nil|string)
|
---@param cb fun(err: nil|string)
|
||||||
M.perform_action = function(action, cb)
|
M.perform_action = function(action, cb)
|
||||||
if action.type == "create" then
|
if action.type == "create" then
|
||||||
local res = parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
if action.entry_type == "directory" then
|
if action.entry_type == "directory" then
|
||||||
conn:mkdir(res.path, cb)
|
conn:mkdir(res.path, cb)
|
||||||
|
|
@ -277,15 +286,15 @@ M.perform_action = function(action, cb)
|
||||||
conn:touch(res.path, cb)
|
conn:touch(res.path, cb)
|
||||||
end
|
end
|
||||||
elseif action.type == "delete" then
|
elseif action.type == "delete" then
|
||||||
local res = parse_url(action.url)
|
local res = M.parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
conn:rm(res.path, cb)
|
conn:rm(res.path, cb)
|
||||||
elseif action.type == "move" then
|
elseif action.type == "move" then
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
local src_conn = get_connection(action.src_url)
|
local src_conn = get_connection(action.src_url)
|
||||||
local dest_conn = get_connection(action.dest_url)
|
local dest_conn = get_connection(action.dest_url)
|
||||||
if src_conn ~= dest_conn then
|
if src_conn ~= dest_conn then
|
||||||
|
|
@ -305,25 +314,25 @@ M.perform_action = function(action, cb)
|
||||||
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
local src_adapter = config.get_adapter_by_scheme(action.src_url)
|
||||||
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
|
||||||
if src_adapter == M and dest_adapter == M then
|
if src_adapter == M and dest_adapter == M then
|
||||||
local src_res = parse_url(action.src_url)
|
local src_res = M.parse_url(action.src_url)
|
||||||
local dest_res = parse_url(action.dest_url)
|
local dest_res = M.parse_url(action.dest_url)
|
||||||
local src_conn = get_connection(action.src_url)
|
if not url_hosts_equal(src_res, dest_res) then
|
||||||
local dest_conn = get_connection(action.dest_url)
|
|
||||||
if src_conn.host ~= dest_conn.host then
|
|
||||||
shell.run({ "scp", "-C", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
shell.run({ "scp", "-C", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, cb)
|
||||||
|
else
|
||||||
|
local src_conn = get_connection(action.src_url)
|
||||||
|
src_conn:cp(src_res.path, dest_res.path, cb)
|
||||||
end
|
end
|
||||||
src_conn:cp(src_res.path, dest_res.path, cb)
|
|
||||||
else
|
else
|
||||||
local src_arg
|
local src_arg
|
||||||
local dest_arg
|
local dest_arg
|
||||||
if src_adapter == M then
|
if src_adapter == M then
|
||||||
src_arg = url_to_scp(parse_url(action.src_url))
|
src_arg = url_to_scp(M.parse_url(action.src_url))
|
||||||
local _, path = util.parse_url(action.dest_url)
|
local _, path = util.parse_url(action.dest_url)
|
||||||
dest_arg = fs.posix_to_os_path(path)
|
dest_arg = fs.posix_to_os_path(path)
|
||||||
else
|
else
|
||||||
local _, path = util.parse_url(action.src_url)
|
local _, path = util.parse_url(action.src_url)
|
||||||
src_arg = fs.posix_to_os_path(path)
|
src_arg = fs.posix_to_os_path(path)
|
||||||
dest_arg = url_to_scp(parse_url(action.dest_url))
|
dest_arg = url_to_scp(M.parse_url(action.dest_url))
|
||||||
end
|
end
|
||||||
shell.run({ "scp", "-C", "-r", src_arg, dest_arg }, cb)
|
shell.run({ "scp", "-C", "-r", src_arg, dest_arg }, cb)
|
||||||
end
|
end
|
||||||
|
|
@ -338,7 +347,7 @@ M.supports_xfer = { files = true }
|
||||||
M.read_file = function(bufnr)
|
M.read_file = function(bufnr)
|
||||||
loading.set_loading(bufnr, true)
|
loading.set_loading(bufnr, true)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local url = parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local basename = pathutil.basename(bufname)
|
local basename = pathutil.basename(bufname)
|
||||||
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
||||||
|
|
@ -372,7 +381,7 @@ end
|
||||||
M.write_file = function(bufnr)
|
M.write_file = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
vim.bo[bufnr].modifiable = false
|
vim.bo[bufnr].modifiable = false
|
||||||
local url = parse_url(bufname)
|
local url = M.parse_url(bufname)
|
||||||
local scp_url = url_to_scp(url)
|
local scp_url = url_to_scp(url)
|
||||||
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
||||||
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXXXX"))
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXXXX"))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
local layout = require("oil.layout")
|
local layout = require("oil.layout")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
|
|
||||||
|
---@class oil.sshConnection
|
||||||
|
---@field meta {user?: string, groups?: string[]}
|
||||||
local SSHConnection = {}
|
local SSHConnection = {}
|
||||||
|
|
||||||
local function output_extend(agg, output)
|
local function output_extend(agg, output)
|
||||||
|
|
@ -44,7 +47,8 @@ local function get_last_lines(bufnr, num_lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url oil.sshUrl
|
---@param url oil.sshUrl
|
||||||
function SSHConnection.new(url)
|
---@return string[]
|
||||||
|
function SSHConnection.create_ssh_command(url)
|
||||||
local host = url.host
|
local host = url.host
|
||||||
if url.user then
|
if url.user then
|
||||||
host = url.user .. "@" .. host
|
host = url.user .. "@" .. host
|
||||||
|
|
@ -52,19 +56,27 @@ function SSHConnection.new(url)
|
||||||
local command = {
|
local command = {
|
||||||
"ssh",
|
"ssh",
|
||||||
host,
|
host,
|
||||||
|
}
|
||||||
|
if url.port then
|
||||||
|
table.insert(command, "-p")
|
||||||
|
table.insert(command, url.port)
|
||||||
|
end
|
||||||
|
return command
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url oil.sshUrl
|
||||||
|
---@return oil.sshConnection
|
||||||
|
function SSHConnection.new(url)
|
||||||
|
local command = SSHConnection.create_ssh_command(url)
|
||||||
|
vim.list_extend(command, {
|
||||||
"/bin/bash",
|
"/bin/bash",
|
||||||
"--norc",
|
"--norc",
|
||||||
"-c",
|
"-c",
|
||||||
-- HACK: For some reason in my testing if I just have "echo READY" it doesn't appear, but if I echo
|
-- HACK: For some reason in my testing if I just have "echo READY" it doesn't appear, but if I echo
|
||||||
-- anything prior to that, it *will* appear. The first line gets swallowed.
|
-- anything prior to that, it *will* appear. The first line gets swallowed.
|
||||||
"echo '_make_newline_'; echo '===READY==='; exec /bin/bash --norc",
|
"echo '_make_newline_'; echo '===READY==='; exec /bin/bash --norc",
|
||||||
}
|
})
|
||||||
if url.port then
|
|
||||||
table.insert(command, 2, "-p")
|
|
||||||
table.insert(command, 3, url.port)
|
|
||||||
end
|
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
host = host,
|
|
||||||
meta = {},
|
meta = {},
|
||||||
commands = {},
|
commands = {},
|
||||||
connected = false,
|
connected = false,
|
||||||
|
|
@ -84,7 +96,7 @@ function SSHConnection.new(url)
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
self.term_id = term_id
|
self.term_id = term_id
|
||||||
vim.api.nvim_chan_send(term_id, string.format("ssh %s\r\n", host))
|
vim.api.nvim_chan_send(term_id, string.format("ssh %s\r\n", url.host))
|
||||||
util.hack_around_termopen_autocmd(mode)
|
util.hack_around_termopen_autocmd(mode)
|
||||||
|
|
||||||
-- If it takes more than 2 seconds to connect, pop open the terminal
|
-- If it takes more than 2 seconds to connect, pop open the terminal
|
||||||
|
|
@ -131,6 +143,7 @@ function SSHConnection.new(url)
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error fetching ssh connection user: %s", err), vim.log.levels.WARN)
|
vim.notify(string.format("Error fetching ssh connection user: %s", err), vim.log.levels.WARN)
|
||||||
else
|
else
|
||||||
|
assert(lines)
|
||||||
self.meta.user = vim.trim(table.concat(lines, ""))
|
self.meta.user = vim.trim(table.concat(lines, ""))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -141,6 +154,7 @@ function SSHConnection.new(url)
|
||||||
vim.log.levels.WARN
|
vim.log.levels.WARN
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
assert(lines)
|
||||||
self.meta.groups = vim.split(table.concat(lines, ""), "%s+", { trimempty = true })
|
self.meta.groups = vim.split(table.concat(lines, ""), "%s+", { trimempty = true })
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ local constants = require("oil.constants")
|
||||||
local permissions = require("oil.adapters.files.permissions")
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local SSHConnection = require("oil.adapters.ssh.connection")
|
local SSHConnection = require("oil.adapters.ssh.connection")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
|
|
||||||
|
---@class oil.sshFs
|
||||||
|
---@field conn oil.sshConnection
|
||||||
local SSHFS = {}
|
local SSHFS = {}
|
||||||
|
|
||||||
local FIELD_TYPE = constants.FIELD_TYPE
|
local FIELD_TYPE = constants.FIELD_TYPE
|
||||||
|
|
@ -20,7 +23,7 @@ local typechar_map = {
|
||||||
---@param line string
|
---@param line string
|
||||||
---@return string Name of entry
|
---@return string Name of entry
|
||||||
---@return oil.EntryType
|
---@return oil.EntryType
|
||||||
---@return nil|table Metadata for entry
|
---@return table Metadata for entry
|
||||||
local function parse_ls_line(line)
|
local function parse_ls_line(line)
|
||||||
local typechar, perms, refcount, user, group, rem =
|
local typechar, perms, refcount, user, group, rem =
|
||||||
line:match("^(.)(%S+)%s+(%d+)%s+(%S+)%s+(%S+)%s+(.*)$")
|
line:match("^(.)(%S+)%s+(%d+)%s+(%S+)%s+(%S+)%s+(.*)$")
|
||||||
|
|
@ -58,6 +61,7 @@ local function parse_ls_line(line)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url oil.sshUrl
|
---@param url oil.sshUrl
|
||||||
|
---@return oil.sshFs
|
||||||
function SSHFS.new(url)
|
function SSHFS.new(url)
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
conn = SSHConnection.new(url),
|
conn = SSHConnection.new(url),
|
||||||
|
|
@ -94,6 +98,7 @@ function SSHFS:realpath(path, callback)
|
||||||
if err then
|
if err then
|
||||||
return callback(err)
|
return callback(err)
|
||||||
end
|
end
|
||||||
|
assert(lines)
|
||||||
local abspath = table.concat(lines, "")
|
local abspath = table.concat(lines, "")
|
||||||
-- If the path was "." then the abspath might be /path/to/., so we need to trim that final '.'
|
-- If the path was "." then the abspath might be /path/to/., so we need to trim that final '.'
|
||||||
if vim.endswith(abspath, ".") then
|
if vim.endswith(abspath, ".") then
|
||||||
|
|
@ -105,6 +110,7 @@ function SSHFS:realpath(path, callback)
|
||||||
-- If the file doesn't exist, treat it like a not-yet-existing directory
|
-- If the file doesn't exist, treat it like a not-yet-existing directory
|
||||||
type = "directory"
|
type = "directory"
|
||||||
else
|
else
|
||||||
|
assert(ls_lines)
|
||||||
local _
|
local _
|
||||||
_, type = parse_ls_line(ls_lines[1])
|
_, type = parse_ls_line(ls_lines[1])
|
||||||
end
|
end
|
||||||
|
|
@ -133,6 +139,7 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
return callback(err)
|
return callback(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
assert(lines)
|
||||||
local any_links = false
|
local any_links = false
|
||||||
local entries = {}
|
local entries = {}
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
|
|
@ -159,6 +166,7 @@ function SSHFS:list_dir(url, path, callback)
|
||||||
if link_err and not link_err:match("^1:") then
|
if link_err and not link_err:match("^1:") then
|
||||||
return callback(link_err)
|
return callback(link_err)
|
||||||
end
|
end
|
||||||
|
assert(link_lines)
|
||||||
for _, line in ipairs(link_lines) do
|
for _, line in ipairs(link_lines) do
|
||||||
if line ~= "" and not line:match("^total") then
|
if line ~= "" and not line:match("^total") then
|
||||||
local ok, name, type, meta = pcall(parse_ls_line, line)
|
local ok, name, type, meta = pcall(parse_ls_line, line)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue