feat: builtin support for editing files over ssh (#27)
This commit is contained in:
parent
75b710e311
commit
ca4da68aae
18 changed files with 593 additions and 291 deletions
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
|
|
@ -37,6 +37,7 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- nvim_tag: v0.8.0
|
- nvim_tag: v0.8.0
|
||||||
- nvim_tag: v0.8.1
|
- nvim_tag: v0.8.1
|
||||||
|
- nvim_tag: v0.8.2
|
||||||
|
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
|
||||||
|
|
@ -184,12 +184,10 @@ Note that file operations work _across adapters_. This means that you can use oi
|
||||||
This adapter allows you to browse files over ssh, much like netrw. To use it, simply open a buffer using the following name template:
|
This adapter allows you to browse files over ssh, much like netrw. To use it, simply open a buffer using the following name template:
|
||||||
|
|
||||||
```
|
```
|
||||||
nvim oil-ssh://[username@]hostname[:port]/[path]
|
nvim scp://[username@]hostname[:port]/[path]
|
||||||
```
|
```
|
||||||
|
|
||||||
This should look familiar. In fact, if you replace `oil-ssh://` with `sftp://`, this is the exact same url format that netrw uses.
|
This may look familiar. In fact, this is the exact same url format that netrw uses.
|
||||||
|
|
||||||
While this adapter effectively replaces netrw for directory browsing, it still relies on netrw for file editing. When you open a file from oil, it will use the `scp://host/path/to/file.txt` format that triggers remote editing via netrw.
|
|
||||||
|
|
||||||
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/bash` binary as well as standard unix commands (`rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/bash` binary as well as standard unix commands (`rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,13 +175,20 @@ end
|
||||||
M.normalize_url = function(url, callback)
|
M.normalize_url = function(url, callback)
|
||||||
local scheme, path = util.parse_url(url)
|
local scheme, path = util.parse_url(url)
|
||||||
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
local os_path = vim.fn.fnamemodify(fs.posix_to_os_path(path), ":p")
|
||||||
local realpath = vim.loop.fs_realpath(os_path) or os_path
|
vim.loop.fs_realpath(os_path, function(err, new_os_path)
|
||||||
local norm_path = util.addslash(fs.os_to_posix_path(realpath))
|
local realpath = new_os_path or os_path
|
||||||
if norm_path ~= os_path then
|
vim.loop.fs_stat(
|
||||||
callback(scheme .. fs.os_to_posix_path(norm_path))
|
realpath,
|
||||||
else
|
vim.schedule_wrap(function(stat_err, stat)
|
||||||
callback(util.addslash(url))
|
if not stat or stat.type == "directory" then
|
||||||
end
|
local norm_path = util.addslash(fs.os_to_posix_path(realpath))
|
||||||
|
callback(scheme .. norm_path)
|
||||||
|
else
|
||||||
|
callback(realpath)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
|
|
@ -303,12 +310,6 @@ M.is_modifiable = function(bufnr)
|
||||||
return bit.band(rwx, 2) ~= 0
|
return bit.band(rwx, 2) ~= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
|
||||||
M.url_to_buffer_name = function(url)
|
|
||||||
local _, path = util.parse_url(url)
|
|
||||||
return fs.posix_to_os_path(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ local cache = require("oil.cache")
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local files = require("oil.adapters.files")
|
local files = require("oil.adapters.files")
|
||||||
|
local loading = require("oil.loading")
|
||||||
local permissions = require("oil.adapters.files.permissions")
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
local ssh_connection = require("oil.adapters.ssh.connection")
|
local sshfs = require("oil.adapters.ssh.sshfs")
|
||||||
local pathutil = require("oil.pathutil")
|
local pathutil = require("oil.pathutil")
|
||||||
local shell = require("oil.shell")
|
local shell = require("oil.shell")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
|
|
@ -85,62 +86,13 @@ local function get_connection(url, allow_retry)
|
||||||
res.path = ""
|
res.path = ""
|
||||||
local key = url_to_str(res)
|
local key = url_to_str(res)
|
||||||
local conn = _connections[key]
|
local conn = _connections[key]
|
||||||
if not conn or (allow_retry and conn.connection_error) then
|
if not conn or (allow_retry and conn:get_connection_error()) then
|
||||||
conn = ssh_connection.new(res)
|
conn = sshfs.new(res)
|
||||||
_connections[key] = conn
|
_connections[key] = conn
|
||||||
end
|
end
|
||||||
return conn
|
return conn
|
||||||
end
|
end
|
||||||
|
|
||||||
local typechar_map = {
|
|
||||||
l = "link",
|
|
||||||
d = "directory",
|
|
||||||
p = "fifo",
|
|
||||||
s = "socket",
|
|
||||||
["-"] = "file",
|
|
||||||
c = "file", -- character special file
|
|
||||||
b = "file", -- block special file
|
|
||||||
}
|
|
||||||
---@param line string
|
|
||||||
---@return string Name of entry
|
|
||||||
---@return oil.EntryType
|
|
||||||
---@return nil|table Metadata for entry
|
|
||||||
local function parse_ls_line(line)
|
|
||||||
local typechar, perms, refcount, user, group, rem =
|
|
||||||
line:match("^(.)(%S+)%s+(%d+)%s+(%S+)%s+(%S+)%s+(.*)$")
|
|
||||||
if not typechar then
|
|
||||||
error(string.format("Could not parse '%s'", line))
|
|
||||||
end
|
|
||||||
local type = typechar_map[typechar] or "file"
|
|
||||||
|
|
||||||
local meta = {
|
|
||||||
user = user,
|
|
||||||
group = group,
|
|
||||||
mode = permissions.parse(perms),
|
|
||||||
refcount = tonumber(refcount),
|
|
||||||
}
|
|
||||||
local name, size, date, major, minor
|
|
||||||
if typechar == "c" or typechar == "b" then
|
|
||||||
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
|
||||||
meta.major = tonumber(major)
|
|
||||||
meta.minor = tonumber(minor)
|
|
||||||
else
|
|
||||||
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
|
||||||
meta.size = tonumber(size)
|
|
||||||
end
|
|
||||||
meta.iso_modified_date = date
|
|
||||||
if type == "link" then
|
|
||||||
local link
|
|
||||||
name, link = unpack(vim.split(name, " -> ", { plain = true }))
|
|
||||||
if vim.endswith(link, "/") then
|
|
||||||
link = link:sub(1, #link - 1)
|
|
||||||
end
|
|
||||||
meta.link = link
|
|
||||||
end
|
|
||||||
|
|
||||||
return name, type, meta
|
|
||||||
end
|
|
||||||
|
|
||||||
local ssh_columns = {}
|
local ssh_columns = {}
|
||||||
ssh_columns.permissions = {
|
ssh_columns.permissions = {
|
||||||
render = function(entry, conf)
|
render = function(entry, conf)
|
||||||
|
|
@ -171,8 +123,7 @@ ssh_columns.permissions = {
|
||||||
perform_action = function(action, callback)
|
perform_action = function(action, callback)
|
||||||
local res = parse_url(action.url)
|
local res = parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
local octal = permissions.mode_to_octal_str(action.value)
|
conn:chmod(action.value, res.path, callback)
|
||||||
conn:run(string.format("chmod %s '%s'", octal, res.path), callback)
|
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,24 +181,9 @@ M.normalize_url = function(url, callback)
|
||||||
path = "."
|
path = "."
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd = string.format(
|
conn:realpath(path, function(err, abspath)
|
||||||
'if ! readlink -f "%s" 2>/dev/null; then [[ "%s" == /* ]] && echo "%s" || echo "$PWD/%s"; fi',
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
path
|
|
||||||
)
|
|
||||||
conn:run(cmd, function(err, lines)
|
|
||||||
if err then
|
if err then
|
||||||
vim.notify(string.format("Error normalizing url %s: %s", url, err), vim.log.levels.WARN)
|
vim.notify(string.format("Error normalizing url %s: %s", url, err), vim.log.levels.WARN)
|
||||||
return callback(url)
|
|
||||||
end
|
|
||||||
local abspath = table.concat(lines, "")
|
|
||||||
if vim.endswith(abspath, ".") then
|
|
||||||
abspath = abspath:sub(1, #abspath - 1)
|
|
||||||
end
|
|
||||||
abspath = util.addslash(abspath)
|
|
||||||
if abspath == res.path then
|
|
||||||
callback(url)
|
callback(url)
|
||||||
else
|
else
|
||||||
res.path = abspath
|
res.path = abspath
|
||||||
|
|
@ -256,81 +192,19 @@ M.normalize_url = function(url, callback)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local dir_meta = {}
|
|
||||||
|
|
||||||
---@param url string
|
---@param url string
|
||||||
---@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 = parse_url(url)
|
||||||
|
|
||||||
local path_postfix = ""
|
|
||||||
if res.path ~= "" then
|
|
||||||
path_postfix = string.format(" '%s'", res.path)
|
|
||||||
end
|
|
||||||
local conn = get_connection(url)
|
|
||||||
cache.begin_update_url(url)
|
cache.begin_update_url(url)
|
||||||
local function cb(err, data)
|
local conn = get_connection(url)
|
||||||
|
conn:list_dir(url, res.path, function(err, data)
|
||||||
if err or not data then
|
if err or not data then
|
||||||
cache.end_update_url(url)
|
cache.end_update_url(url)
|
||||||
end
|
end
|
||||||
callback(err, data)
|
callback(err, data)
|
||||||
end
|
|
||||||
conn:run("ls -fl" .. path_postfix, function(err, lines)
|
|
||||||
if err then
|
|
||||||
if err:match("No such file or directory%s*$") then
|
|
||||||
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
|
||||||
-- and edit a not-yet-existing directory.
|
|
||||||
return cb()
|
|
||||||
else
|
|
||||||
return cb(err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local any_links = false
|
|
||||||
local entries = {}
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
if line ~= "" and not line:match("^total") then
|
|
||||||
local name, type, meta = parse_ls_line(line)
|
|
||||||
if name == "." then
|
|
||||||
dir_meta[url] = meta
|
|
||||||
elseif name ~= ".." then
|
|
||||||
if type == "link" then
|
|
||||||
any_links = true
|
|
||||||
end
|
|
||||||
local cache_entry = cache.create_entry(url, name, type)
|
|
||||||
entries[name] = cache_entry
|
|
||||||
cache_entry[FIELD.meta] = meta
|
|
||||||
cache.store_entry(url, cache_entry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if any_links then
|
|
||||||
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
|
||||||
-- resolve the type of the link target
|
|
||||||
conn:run("ls -fLl" .. path_postfix, function(link_err, link_lines)
|
|
||||||
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
|
||||||
if link_err and not link_err:match("^1:") then
|
|
||||||
return cb(link_err)
|
|
||||||
end
|
|
||||||
for _, line in ipairs(link_lines) do
|
|
||||||
if line ~= "" and not line:match("^total") then
|
|
||||||
local ok, name, type, meta = pcall(parse_ls_line, line)
|
|
||||||
if ok and name ~= "." and name ~= ".." then
|
|
||||||
local cache_entry = entries[name]
|
|
||||||
if cache_entry[FIELD.type] == "link" then
|
|
||||||
cache_entry[FIELD.meta].link_stat = {
|
|
||||||
type = type,
|
|
||||||
size = meta.size,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
cb()
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
cb()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -338,33 +212,27 @@ end
|
||||||
---@return boolean
|
---@return boolean
|
||||||
M.is_modifiable = function(bufnr)
|
M.is_modifiable = function(bufnr)
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local meta = dir_meta[bufname]
|
local conn = get_connection(bufname)
|
||||||
if not meta then
|
local dir_meta = conn:get_dir_meta(bufname)
|
||||||
|
if not dir_meta then
|
||||||
-- Directories that don't exist yet are modifiable
|
-- Directories that don't exist yet are modifiable
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
local conn = get_connection(bufname)
|
local meta = conn:get_meta()
|
||||||
if not conn.meta.user or not conn.meta.groups then
|
if not meta.user or not meta.groups then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local rwx
|
local rwx
|
||||||
if meta.user == conn.meta.user then
|
if dir_meta.user == meta.user then
|
||||||
rwx = bit.rshift(meta.mode, 6)
|
rwx = bit.rshift(dir_meta.mode, 6)
|
||||||
elseif vim.tbl_contains(conn.meta.groups, meta.group) then
|
elseif vim.tbl_contains(meta.groups, dir_meta.group) then
|
||||||
rwx = bit.rshift(meta.mode, 3)
|
rwx = bit.rshift(dir_meta.mode, 3)
|
||||||
else
|
else
|
||||||
rwx = meta.mode
|
rwx = dir_meta.mode
|
||||||
end
|
end
|
||||||
return bit.band(rwx, 2) ~= 0
|
return bit.band(rwx, 2) ~= 0
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
|
||||||
M.url_to_buffer_name = function(url)
|
|
||||||
local _, rem = util.parse_url(url)
|
|
||||||
-- Let netrw handle editing files
|
|
||||||
return "scp://" .. rem
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
|
|
@ -399,16 +267,16 @@ M.perform_action = function(action, cb)
|
||||||
local res = parse_url(action.url)
|
local res = 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:run(string.format("mkdir -p '%s'", res.path), cb)
|
conn:mkdir(res.path, cb)
|
||||||
elseif action.entry_type == "link" and action.link then
|
elseif action.entry_type == "link" and action.link then
|
||||||
conn:run(string.format("ln -s '%s' '%s'", action.link, res.path), cb)
|
conn:mklink(res.path, action.link, cb)
|
||||||
else
|
else
|
||||||
conn:run(string.format("touch '%s'", 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 = parse_url(action.url)
|
||||||
local conn = get_connection(action.url)
|
local conn = get_connection(action.url)
|
||||||
conn:run(string.format("rm -rf '%s'", 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)
|
||||||
|
|
@ -418,14 +286,14 @@ M.perform_action = function(action, cb)
|
||||||
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
|
||||||
shell.run({ "scp", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
shell.run({ "scp", "-C", "-r", url_to_scp(src_res), url_to_scp(dest_res) }, function(err)
|
||||||
if err then
|
if err then
|
||||||
return cb(err)
|
return cb(err)
|
||||||
end
|
end
|
||||||
src_conn:run(string.format("rm -rf '%s'", src_res.path), cb)
|
src_conn:rm(src_res.path, cb)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
src_conn:run(string.format("mv '%s' '%s'", src_res.path, dest_res.path), cb)
|
src_conn:mv(src_res.path, dest_res.path, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb("We should never attempt to move across adapters")
|
cb("We should never attempt to move across adapters")
|
||||||
|
|
@ -439,9 +307,9 @@ M.perform_action = function(action, cb)
|
||||||
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.host ~= dest_conn.host then
|
if src_conn.host ~= dest_conn.host then
|
||||||
shell.run({ "scp", "-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)
|
||||||
end
|
end
|
||||||
src_conn:run(string.format("cp -r '%s' '%s'", src_res.path, dest_res.path), cb)
|
src_conn:cp(src_res.path, dest_res.path, cb)
|
||||||
else
|
else
|
||||||
local src_arg
|
local src_arg
|
||||||
local dest_arg
|
local dest_arg
|
||||||
|
|
@ -454,7 +322,7 @@ M.perform_action = function(action, cb)
|
||||||
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(parse_url(action.dest_url))
|
||||||
end
|
end
|
||||||
shell.run({ "scp", "-r", src_arg, dest_arg }, cb)
|
shell.run({ "scp", "-C", "-r", src_arg, dest_arg }, cb)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
cb(string.format("Bad action type: %s", action.type))
|
cb(string.format("Bad action type: %s", action.type))
|
||||||
|
|
@ -463,4 +331,63 @@ end
|
||||||
|
|
||||||
M.supports_xfer = { files = true }
|
M.supports_xfer = { files = true }
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.read_file = function(bufnr)
|
||||||
|
loading.set_loading(bufnr, true)
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local url = parse_url(bufname)
|
||||||
|
local scp_url = url_to_scp(url)
|
||||||
|
local basename = pathutil.basename(bufname)
|
||||||
|
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
||||||
|
fs.mkdirp(tmpdir)
|
||||||
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXX"))
|
||||||
|
vim.loop.fs_close(fd)
|
||||||
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
|
shell.run({ "scp", "-C", scp_url, tmpfile }, function(err)
|
||||||
|
loading.set_loading(bufnr, false)
|
||||||
|
vim.bo[bufnr].modifiable = true
|
||||||
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { silent = true } })
|
||||||
|
if err then
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, vim.split(err, "\n"))
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {})
|
||||||
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
vim.cmd.read({ args = { tmpfile }, mods = { silent = true } })
|
||||||
|
end)
|
||||||
|
vim.loop.fs_unlink(tmpfile)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, 1, true, {})
|
||||||
|
end
|
||||||
|
vim.bo[bufnr].modified = false
|
||||||
|
vim.bo[bufnr].filetype = vim.filetype.match({ buf = bufnr, filename = basename })
|
||||||
|
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { silent = true } })
|
||||||
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.write_file = function(bufnr)
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
vim.bo[bufnr].modifiable = false
|
||||||
|
local url = parse_url(bufname)
|
||||||
|
local scp_url = url_to_scp(url)
|
||||||
|
local tmpdir = fs.join(vim.fn.stdpath("cache"), "oil")
|
||||||
|
local fd, tmpfile = vim.loop.fs_mkstemp(fs.join(tmpdir, "ssh_XXXXXXXX"))
|
||||||
|
vim.loop.fs_close(fd)
|
||||||
|
vim.cmd.doautocmd({ args = { "BufWritePre", bufname }, mods = { silent = true } })
|
||||||
|
vim.cmd.write({ args = { tmpfile }, bang = true, mods = { silent = true } })
|
||||||
|
local tmp_bufnr = vim.fn.bufadd(tmpfile)
|
||||||
|
|
||||||
|
shell.run({ "scp", "-C", tmpfile, scp_url }, function(err)
|
||||||
|
if err then
|
||||||
|
vim.notify(string.format("Error writing file: %s", err), vim.log.levels.ERROR)
|
||||||
|
end
|
||||||
|
vim.bo[bufnr].modifiable = true
|
||||||
|
vim.bo[bufnr].modified = false
|
||||||
|
vim.cmd.doautocmd({ args = { "BufWritePost", bufname }, mods = { silent = true } })
|
||||||
|
vim.loop.fs_unlink(tmpfile)
|
||||||
|
vim.api.nvim_buf_delete(tmp_bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
228
lua/oil/adapters/ssh/sshfs.lua
Normal file
228
lua/oil/adapters/ssh/sshfs.lua
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
local cache = require("oil.cache")
|
||||||
|
local permissions = require("oil.adapters.files.permissions")
|
||||||
|
local SSHConnection = require("oil.adapters.ssh.connection")
|
||||||
|
local util = require("oil.util")
|
||||||
|
local FIELD = require("oil.constants").FIELD
|
||||||
|
local SSHFS = {}
|
||||||
|
|
||||||
|
local typechar_map = {
|
||||||
|
l = "link",
|
||||||
|
d = "directory",
|
||||||
|
p = "fifo",
|
||||||
|
s = "socket",
|
||||||
|
["-"] = "file",
|
||||||
|
c = "file", -- character special file
|
||||||
|
b = "file", -- block special file
|
||||||
|
}
|
||||||
|
---@param line string
|
||||||
|
---@return string Name of entry
|
||||||
|
---@return oil.EntryType
|
||||||
|
---@return nil|table Metadata for entry
|
||||||
|
local function parse_ls_line(line)
|
||||||
|
local typechar, perms, refcount, user, group, rem =
|
||||||
|
line:match("^(.)(%S+)%s+(%d+)%s+(%S+)%s+(%S+)%s+(.*)$")
|
||||||
|
if not typechar then
|
||||||
|
error(string.format("Could not parse '%s'", line))
|
||||||
|
end
|
||||||
|
local type = typechar_map[typechar] or "file"
|
||||||
|
|
||||||
|
local meta = {
|
||||||
|
user = user,
|
||||||
|
group = group,
|
||||||
|
mode = permissions.parse(perms),
|
||||||
|
refcount = tonumber(refcount),
|
||||||
|
}
|
||||||
|
local name, size, date, major, minor
|
||||||
|
if typechar == "c" or typechar == "b" then
|
||||||
|
major, minor, date, name = rem:match("^(%d+)%s*,%s*(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
meta.major = tonumber(major)
|
||||||
|
meta.minor = tonumber(minor)
|
||||||
|
else
|
||||||
|
size, date, name = rem:match("^(%d+)%s+(%S+%s+%d+%s+%d%d:?%d%d)%s+(.*)")
|
||||||
|
meta.size = tonumber(size)
|
||||||
|
end
|
||||||
|
meta.iso_modified_date = date
|
||||||
|
if type == "link" then
|
||||||
|
local link
|
||||||
|
name, link = unpack(vim.split(name, " -> ", { plain = true }))
|
||||||
|
if vim.endswith(link, "/") then
|
||||||
|
link = link:sub(1, #link - 1)
|
||||||
|
end
|
||||||
|
meta.link = link
|
||||||
|
end
|
||||||
|
|
||||||
|
return name, type, meta
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param url oil.sshUrl
|
||||||
|
function SSHFS.new(url)
|
||||||
|
return setmetatable({
|
||||||
|
conn = SSHConnection.new(url),
|
||||||
|
}, {
|
||||||
|
__index = SSHFS,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function SSHFS:get_connection_error()
|
||||||
|
return self.conn.connection_error
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param value integer
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:chmod(value, path, callback)
|
||||||
|
local octal = permissions.mode_to_octal_str(value)
|
||||||
|
self.conn:run(string.format("chmod %s '%s'", octal, path), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
function SSHFS:open_terminal()
|
||||||
|
self.conn:open_terminal()
|
||||||
|
end
|
||||||
|
|
||||||
|
function SSHFS:realpath(path, callback)
|
||||||
|
local cmd = string.format(
|
||||||
|
'if ! readlink -f "%s" 2>/dev/null; then [[ "%s" == /* ]] && echo "%s" || echo "$PWD/%s"; fi',
|
||||||
|
path,
|
||||||
|
path,
|
||||||
|
path,
|
||||||
|
path
|
||||||
|
)
|
||||||
|
self.conn:run(cmd, function(err, lines)
|
||||||
|
if err then
|
||||||
|
return callback(err)
|
||||||
|
end
|
||||||
|
local abspath = table.concat(lines, "")
|
||||||
|
-- If the path was "." then the abspath might be /path/to/., so we need to trim that final '.'
|
||||||
|
if vim.endswith(abspath, ".") then
|
||||||
|
abspath = abspath:sub(1, #abspath - 1)
|
||||||
|
end
|
||||||
|
self.conn:run(string.format("ls -fld '%s'", abspath), function(ls_err, ls_lines)
|
||||||
|
local type
|
||||||
|
if ls_err then
|
||||||
|
-- If the file doesn't exist, treat it like a not-yet-existing directory
|
||||||
|
type = "directory"
|
||||||
|
else
|
||||||
|
local _
|
||||||
|
_, type = parse_ls_line(ls_lines[1])
|
||||||
|
end
|
||||||
|
if type == "directory" then
|
||||||
|
abspath = util.addslash(abspath)
|
||||||
|
end
|
||||||
|
callback(nil, abspath)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir_meta = {}
|
||||||
|
|
||||||
|
function SSHFS:list_dir(url, path, callback)
|
||||||
|
local path_postfix = ""
|
||||||
|
if path ~= "" then
|
||||||
|
path_postfix = string.format(" '%s'", path)
|
||||||
|
end
|
||||||
|
self.conn:run("ls -fl" .. path_postfix, function(err, lines)
|
||||||
|
if err then
|
||||||
|
if err:match("No such file or directory%s*$") then
|
||||||
|
-- If the directory doesn't exist, treat the list as a success. We will be able to traverse
|
||||||
|
-- and edit a not-yet-existing directory.
|
||||||
|
return callback()
|
||||||
|
else
|
||||||
|
return callback(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local any_links = false
|
||||||
|
local entries = {}
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line ~= "" and not line:match("^total") then
|
||||||
|
local name, type, meta = parse_ls_line(line)
|
||||||
|
if name == "." then
|
||||||
|
dir_meta[url] = meta
|
||||||
|
elseif name ~= ".." then
|
||||||
|
if type == "link" then
|
||||||
|
any_links = true
|
||||||
|
end
|
||||||
|
local cache_entry = cache.create_entry(url, name, type)
|
||||||
|
entries[name] = cache_entry
|
||||||
|
cache_entry[FIELD.meta] = meta
|
||||||
|
cache.store_entry(url, cache_entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if any_links then
|
||||||
|
-- If there were any soft links, then we need to run another ls command with -L so that we can
|
||||||
|
-- resolve the type of the link target
|
||||||
|
self.conn:run("ls -fLl" .. path_postfix, function(link_err, link_lines)
|
||||||
|
-- Ignore exit code 1. That just means one of the links could not be resolved.
|
||||||
|
if link_err and not link_err:match("^1:") then
|
||||||
|
return callback(link_err)
|
||||||
|
end
|
||||||
|
for _, line in ipairs(link_lines) do
|
||||||
|
if line ~= "" and not line:match("^total") then
|
||||||
|
local ok, name, type, meta = pcall(parse_ls_line, line)
|
||||||
|
if ok and name ~= "." and name ~= ".." then
|
||||||
|
local cache_entry = entries[name]
|
||||||
|
if cache_entry[FIELD.type] == "link" then
|
||||||
|
cache_entry[FIELD.meta].link_stat = {
|
||||||
|
type = type,
|
||||||
|
size = meta.size,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
callback()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:mkdir(path, callback)
|
||||||
|
self.conn:run(string.format("mkdir -p '%s'", path), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:touch(path, callback)
|
||||||
|
self.conn:run(string.format("touch '%s'", path), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param link string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:mklink(path, link, callback)
|
||||||
|
self.conn:run(string.format("ln -s '%s' '%s'", link, path), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:rm(path, callback)
|
||||||
|
self.conn:run(string.format("rm -rf '%s'", path), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param src string
|
||||||
|
---@param dest string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:mv(src, dest, callback)
|
||||||
|
self.conn:run(string.format("mv '%s' '%s'", src, dest), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param src string
|
||||||
|
---@param dest string
|
||||||
|
---@param callback fun(err: nil|string)
|
||||||
|
function SSHFS:cp(src, dest, callback)
|
||||||
|
self.conn:run(string.format("cp -r '%s' '%s'", src, dest), callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
function SSHFS:get_dir_meta(url)
|
||||||
|
return dir_meta[url]
|
||||||
|
end
|
||||||
|
|
||||||
|
function SSHFS:get_meta()
|
||||||
|
return self.conn.meta
|
||||||
|
end
|
||||||
|
|
||||||
|
return SSHFS
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
local cache = require("oil.cache")
|
local cache = require("oil.cache")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@param url string
|
||||||
|
---@param callback fun(url: string)
|
||||||
|
M.normalize_url = function(url, callback)
|
||||||
|
callback(url)
|
||||||
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param column_defs string[]
|
---@param column_defs string[]
|
||||||
---@param cb fun(err: nil|string, entries: nil|oil.InternalEntry[])
|
---@param cb fun(err: nil|string, entries: nil|oil.InternalEntry[])
|
||||||
|
|
@ -36,11 +42,6 @@ M.is_modifiable = function(bufnr)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param url string
|
|
||||||
M.url_to_buffer_name = function(url)
|
|
||||||
error("Test adapter cannot open files")
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param action oil.Action
|
---@param action oil.Action
|
||||||
---@return string
|
---@return string
|
||||||
M.render_action = function(action)
|
M.render_action = function(action)
|
||||||
|
|
@ -59,4 +60,14 @@ M.perform_action = function(action, cb)
|
||||||
cb()
|
cb()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.read_file = function(bufnr)
|
||||||
|
-- pass
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
M.write_file = function(bufnr)
|
||||||
|
-- pass
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,11 @@ local default_config = {
|
||||||
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
-- reason, I'm taking them out of the section above so they won't show up in the autogen docs.
|
||||||
default_config.adapters = {
|
default_config.adapters = {
|
||||||
["oil://"] = "files",
|
["oil://"] = "files",
|
||||||
["oil-ssh://"] = "ssh",
|
["scp://"] = "ssh",
|
||||||
}
|
}
|
||||||
-- When opening the parent of a file, substitute these url schemes
|
-- For backwards compatibility
|
||||||
default_config.remap_schemes = {
|
default_config.adapter_aliases = {
|
||||||
["scp://"] = "oil-ssh://",
|
["oil-ssh://"] = "scp://",
|
||||||
["sftp://"] = "oil-ssh://",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
|
||||||
146
lua/oil/init.lua
146
lua/oil/init.lua
|
|
@ -14,13 +14,14 @@ local M = {}
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field list fun(path: string, cb: fun(err: nil|string, entries: nil|oil.InternalEntry[]))
|
---@field list fun(path: string, cb: fun(err: nil|string, entries: nil|oil.InternalEntry[]))
|
||||||
---@field is_modifiable fun(bufnr: integer): boolean
|
---@field is_modifiable fun(bufnr: integer): boolean
|
||||||
---@field url_to_buffer_name fun(url: string): string
|
|
||||||
---@field get_column fun(name: string): nil|oil.ColumnDefinition
|
---@field get_column fun(name: string): nil|oil.ColumnDefinition
|
||||||
---@field normalize_url nil|fun(url: string, callback: fun(url: string))
|
---@field normalize_url fun(url: string, callback: fun(url: string))
|
||||||
---@field get_parent nil|fun(bufname: string): string
|
---@field get_parent nil|fun(bufname: string): string
|
||||||
---@field supports_xfer nil|table<string, boolean>
|
---@field supports_xfer nil|table<string, boolean>
|
||||||
---@field render_action nil|fun(action: oil.Action): string
|
---@field render_action nil|fun(action: oil.Action): string
|
||||||
---@field perform_action nil|fun(action: oil.Action, cb: fun(err: nil|string))
|
---@field perform_action nil|fun(action: oil.Action, cb: fun(err: nil|string))
|
||||||
|
---@field read_file fun(bufnr: integer)
|
||||||
|
---@field write_file fun(bufnr: integer)
|
||||||
|
|
||||||
---Get the entry on a specific line (1-indexed)
|
---Get the entry on a specific line (1-indexed)
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
|
|
@ -189,7 +190,6 @@ M.get_buffer_parent_url = function(bufname)
|
||||||
local parent_url = util.addslash(scheme .. parent)
|
local parent_url = util.addslash(scheme .. parent)
|
||||||
return parent_url, basename
|
return parent_url, basename
|
||||||
else
|
else
|
||||||
scheme = config.remap_schemes[scheme] or scheme
|
|
||||||
local adapter = config.get_adapter_by_scheme(scheme)
|
local adapter = config.get_adapter_by_scheme(scheme)
|
||||||
local parent_url
|
local parent_url
|
||||||
if adapter and adapter.get_parent then
|
if adapter and adapter.get_parent then
|
||||||
|
|
@ -370,7 +370,6 @@ M.select = function(opts)
|
||||||
local scheme, dir = util.parse_url(bufname)
|
local scheme, dir = util.parse_url(bufname)
|
||||||
local child = dir .. entry.name
|
local child = dir .. entry.name
|
||||||
local url = scheme .. child
|
local url = scheme .. child
|
||||||
local buffer_name
|
|
||||||
if
|
if
|
||||||
entry.type == "directory"
|
entry.type == "directory"
|
||||||
or (
|
or (
|
||||||
|
|
@ -380,7 +379,6 @@ M.select = function(opts)
|
||||||
and entry.meta.link_stat.type == "directory"
|
and entry.meta.link_stat.type == "directory"
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
buffer_name = util.addslash(url)
|
|
||||||
-- If this is a new directory BUT we think we already have an entry with this name, disallow
|
-- If this is a new directory BUT we think we already have an entry with this name, disallow
|
||||||
-- entry. This prevents the case of MOVE /foo -> /bar + CREATE /foo.
|
-- entry. This prevents the case of MOVE /foo -> /bar + CREATE /foo.
|
||||||
-- If you enter the new /foo, it will show the contents of the old /foo.
|
-- If you enter the new /foo, it will show the contents of the old /foo.
|
||||||
|
|
@ -392,7 +390,6 @@ M.select = function(opts)
|
||||||
if util.is_floating_win() then
|
if util.is_floating_win() then
|
||||||
vim.api.nvim_win_close(0, false)
|
vim.api.nvim_win_close(0, false)
|
||||||
end
|
end
|
||||||
buffer_name = adapter.url_to_buffer_name(url)
|
|
||||||
end
|
end
|
||||||
local mods = {
|
local mods = {
|
||||||
vertical = opts.vertical,
|
vertical = opts.vertical,
|
||||||
|
|
@ -405,7 +402,7 @@ M.select = function(opts)
|
||||||
local cmd = opts.split and "split" or "edit"
|
local cmd = opts.split and "split" or "edit"
|
||||||
vim.cmd({
|
vim.cmd({
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
args = { buffer_name },
|
args = { url },
|
||||||
mods = mods,
|
mods = mods,
|
||||||
})
|
})
|
||||||
if opts.preview then
|
if opts.preview then
|
||||||
|
|
@ -514,19 +511,69 @@ M.save = function(opts)
|
||||||
mutator.try_write_changes(opts.confirm)
|
mutator.try_write_changes(opts.confirm)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function restore_alt_buf()
|
||||||
|
local config = require("oil.config")
|
||||||
|
local view = require("oil.view")
|
||||||
|
if vim.bo.filetype == "oil" then
|
||||||
|
view.set_win_options()
|
||||||
|
vim.api.nvim_win_set_var(0, "oil_did_enter", true)
|
||||||
|
elseif vim.w.oil_did_enter then
|
||||||
|
vim.api.nvim_win_del_var(0, "oil_did_enter")
|
||||||
|
-- We are entering a non-oil buffer *after* having been in an oil buffer
|
||||||
|
local has_orig, orig_buffer = pcall(vim.api.nvim_win_get_var, 0, "oil_original_buffer")
|
||||||
|
if has_orig and vim.api.nvim_buf_is_valid(orig_buffer) then
|
||||||
|
if vim.api.nvim_get_current_buf() ~= orig_buffer then
|
||||||
|
-- If we are editing a new file after navigating around oil, set the alternate buffer
|
||||||
|
-- to be the last buffer we were in before opening oil
|
||||||
|
vim.fn.setreg("#", orig_buffer)
|
||||||
|
else
|
||||||
|
-- If we are editing the same buffer that we started oil from, set the alternate to be
|
||||||
|
-- what it was before we opened oil
|
||||||
|
local has_orig_alt, alt_buffer =
|
||||||
|
pcall(vim.api.nvim_win_get_var, 0, "oil_original_alternate")
|
||||||
|
if has_orig_alt and vim.api.nvim_buf_is_valid(alt_buffer) then
|
||||||
|
vim.fn.setreg("#", alt_buffer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if config.restore_win_options then
|
||||||
|
view.restore_win_options()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
local function load_oil_buffer(bufnr)
|
local function load_oil_buffer(bufnr)
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
local keymap_util = require("oil.keymap_util")
|
||||||
local loading = require("oil.loading")
|
local loading = require("oil.loading")
|
||||||
local util = require("oil.util")
|
local util = require("oil.util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
local adapter = config.get_adapter_by_scheme(bufname)
|
local scheme, path = util.parse_url(bufname)
|
||||||
vim.bo[bufnr].buftype = "acwrite"
|
if config.adapter_aliases[scheme] then
|
||||||
vim.bo[bufnr].filetype = "oil"
|
if scheme == "oil-ssh://" then
|
||||||
vim.bo[bufnr].bufhidden = "hide"
|
vim.notify_once(
|
||||||
vim.bo[bufnr].syntax = "oil"
|
'The "oil-ssh://" url scheme is deprecated, use "scp://" instead.\nSupport will be removed on 2023-06-01.',
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
end
|
||||||
|
scheme = config.adapter_aliases[scheme]
|
||||||
|
bufname = scheme .. path
|
||||||
|
util.rename_buffer(bufnr, bufname)
|
||||||
|
end
|
||||||
|
|
||||||
|
local adapter = config.get_adapter_by_scheme(scheme)
|
||||||
|
|
||||||
|
if vim.endswith(bufname, "/") then
|
||||||
|
-- This is a small quality-of-life thing. If the buffer name ends with a `/`, we know it's a
|
||||||
|
-- directory, and can set the filetype early. This is helpful for adapters with a lot of latency
|
||||||
|
-- (e.g. ssh) because it will set up the filetype keybinds at the *beginning* of the loading
|
||||||
|
-- process.
|
||||||
|
vim.bo[bufnr].filetype = "oil"
|
||||||
|
keymap_util.set_keymaps("", config.keymaps, bufnr)
|
||||||
|
end
|
||||||
loading.set_loading(bufnr, true)
|
loading.set_loading(bufnr, true)
|
||||||
local function finish(new_url)
|
local function finish(new_url)
|
||||||
if new_url ~= bufname then
|
if new_url ~= bufname then
|
||||||
|
|
@ -535,23 +582,31 @@ local function load_oil_buffer(bufnr)
|
||||||
-- have BufReadCmd called for it
|
-- have BufReadCmd called for it
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
bufname = new_url
|
||||||
end
|
end
|
||||||
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { emsg_silent = true } })
|
if vim.endswith(bufname, "/") then
|
||||||
view.initialize(bufnr)
|
vim.cmd.doautocmd({ args = { "BufReadPre", bufname }, mods = { emsg_silent = true } })
|
||||||
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { emsg_silent = true } })
|
view.initialize(bufnr)
|
||||||
|
vim.cmd.doautocmd({ args = { "BufReadPost", bufname }, mods = { emsg_silent = true } })
|
||||||
|
else
|
||||||
|
vim.bo[bufnr].buftype = "acwrite"
|
||||||
|
adapter.read_file(bufnr)
|
||||||
|
end
|
||||||
|
restore_alt_buf()
|
||||||
end
|
end
|
||||||
|
|
||||||
if adapter.normalize_url then
|
adapter.normalize_url(bufname, finish)
|
||||||
adapter.normalize_url(bufname, finish)
|
|
||||||
else
|
|
||||||
finish(util.addslash(bufname))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Initialize oil
|
---Initialize oil
|
||||||
---@param opts nil|table
|
---@param opts nil|table
|
||||||
M.setup = function(opts)
|
M.setup = function(opts)
|
||||||
local config = require("oil.config")
|
local config = require("oil.config")
|
||||||
|
|
||||||
|
-- Disable netrw
|
||||||
|
vim.g.loaded_netrw = 1
|
||||||
|
vim.g.loaded_netrwPlugin = 1
|
||||||
|
|
||||||
config.setup(opts)
|
config.setup(opts)
|
||||||
set_colors()
|
set_colors()
|
||||||
vim.api.nvim_create_user_command("Oil", function(args)
|
vim.api.nvim_create_user_command("Oil", function(args)
|
||||||
|
|
@ -574,6 +629,9 @@ M.setup = function(opts)
|
||||||
for scheme in pairs(config.adapters) do
|
for scheme in pairs(config.adapters) do
|
||||||
table.insert(patterns, scheme .. "*")
|
table.insert(patterns, scheme .. "*")
|
||||||
end
|
end
|
||||||
|
for scheme in pairs(config.adapter_aliases) do
|
||||||
|
table.insert(patterns, scheme .. "*")
|
||||||
|
end
|
||||||
local scheme_pattern = table.concat(patterns, ",")
|
local scheme_pattern = table.concat(patterns, ",")
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd("ColorScheme", {
|
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||||
|
|
@ -595,10 +653,16 @@ M.setup = function(opts)
|
||||||
pattern = scheme_pattern,
|
pattern = scheme_pattern,
|
||||||
nested = true,
|
nested = true,
|
||||||
callback = function(params)
|
callback = function(params)
|
||||||
vim.cmd.doautocmd({ args = { "BufWritePre", params.file }, mods = { silent = true } })
|
local bufname = vim.api.nvim_buf_get_name(params.buf)
|
||||||
M.save()
|
if vim.endswith(bufname, "/") then
|
||||||
vim.bo[params.buf].modified = false
|
vim.cmd.doautocmd({ args = { "BufWritePre", params.file }, mods = { silent = true } })
|
||||||
vim.cmd.doautocmd({ args = { "BufWritePost", params.file }, mods = { silent = true } })
|
M.save()
|
||||||
|
vim.bo[params.buf].modified = false
|
||||||
|
vim.cmd.doautocmd({ args = { "BufWritePost", params.file }, mods = { silent = true } })
|
||||||
|
else
|
||||||
|
local adapter = config.get_adapter_by_scheme(bufname)
|
||||||
|
adapter.write_file(params.buf)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd("BufWinLeave", {
|
vim.api.nvim_create_autocmd("BufWinLeave", {
|
||||||
|
|
@ -617,34 +681,15 @@ M.setup = function(opts)
|
||||||
group = aug,
|
group = aug,
|
||||||
pattern = "*",
|
pattern = "*",
|
||||||
callback = function()
|
callback = function()
|
||||||
|
local util = require("oil.util")
|
||||||
local view = require("oil.view")
|
local view = require("oil.view")
|
||||||
if vim.bo.filetype == "oil" then
|
local scheme = util.parse_url(vim.api.nvim_buf_get_name(0))
|
||||||
view.set_win_options()
|
if scheme and config.adapters[scheme] then
|
||||||
vim.api.nvim_win_set_var(0, "oil_did_enter", true)
|
|
||||||
view.maybe_set_cursor()
|
view.maybe_set_cursor()
|
||||||
elseif vim.w.oil_did_enter then
|
else
|
||||||
vim.api.nvim_win_del_var(0, "oil_did_enter")
|
-- Only run this logic if we are *not* in an oil buffer.
|
||||||
-- We are entering a non-oil buffer *after* having been in an oil buffer
|
-- Oil buffers have to run it in BufReadCmd after confirming they are a directory or a file
|
||||||
local has_orig, orig_buffer = pcall(vim.api.nvim_win_get_var, 0, "oil_original_buffer")
|
restore_alt_buf()
|
||||||
if has_orig and vim.api.nvim_buf_is_valid(orig_buffer) then
|
|
||||||
if vim.api.nvim_get_current_buf() ~= orig_buffer then
|
|
||||||
-- If we are editing a new file after navigating around oil, set the alternate buffer
|
|
||||||
-- to be the last buffer we were in before opening oil
|
|
||||||
vim.fn.setreg("#", orig_buffer)
|
|
||||||
else
|
|
||||||
-- If we are editing the same buffer that we started oil from, set the alternate to be
|
|
||||||
-- what it was before we opened oil
|
|
||||||
local has_orig_alt, alt_buffer =
|
|
||||||
pcall(vim.api.nvim_win_get_var, 0, "oil_original_alternate")
|
|
||||||
if has_orig_alt and vim.api.nvim_buf_is_valid(alt_buffer) then
|
|
||||||
vim.fn.setreg("#", alt_buffer)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if config.restore_win_options then
|
|
||||||
view.restore_win_options()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
@ -722,6 +767,7 @@ M.setup = function(opts)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
maybe_hijack_directory_buffer(0)
|
maybe_hijack_directory_buffer(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -487,6 +487,7 @@ M.try_write_changes = function(confirm)
|
||||||
end
|
end
|
||||||
|
|
||||||
local actions = M.create_actions_from_diffs(all_diffs)
|
local actions = M.create_actions_from_diffs(all_diffs)
|
||||||
|
-- TODO(2023-06-01) If no one has reported data loss by this time, we can remove the disclaimer
|
||||||
disclaimer.show(function(disclaimed)
|
disclaimer.show(function(disclaimed)
|
||||||
if not disclaimed then
|
if not disclaimed then
|
||||||
return unlock()
|
return unlock()
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,36 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.run = function(cmd, callback)
|
M.run = function(cmd, opts, callback)
|
||||||
|
if not callback then
|
||||||
|
callback = opts
|
||||||
|
opts = {}
|
||||||
|
end
|
||||||
local stdout
|
local stdout
|
||||||
local stderr = {}
|
local stderr = {}
|
||||||
local jid = vim.fn.jobstart(cmd, {
|
local jid = vim.fn.jobstart(
|
||||||
stdout_buffered = true,
|
cmd,
|
||||||
stderr_buffered = true,
|
vim.tbl_deep_extend("keep", opts, {
|
||||||
on_stdout = function(j, output)
|
stdout_buffered = true,
|
||||||
stdout = output
|
stderr_buffered = true,
|
||||||
end,
|
on_stdout = function(j, output)
|
||||||
on_stderr = function(j, output)
|
stdout = output
|
||||||
stderr = output
|
end,
|
||||||
end,
|
on_stderr = function(j, output)
|
||||||
on_exit = vim.schedule_wrap(function(j, code)
|
stderr = output
|
||||||
if code == 0 then
|
end,
|
||||||
callback(nil, stdout)
|
on_exit = vim.schedule_wrap(function(j, code)
|
||||||
else
|
if code == 0 then
|
||||||
local err = table.concat(stderr, "\n")
|
callback(nil, stdout)
|
||||||
if err == "" then
|
else
|
||||||
err = "Unknown error"
|
local err = table.concat(stderr, "\n")
|
||||||
|
if err == "" then
|
||||||
|
err = "Unknown error"
|
||||||
|
end
|
||||||
|
callback(err)
|
||||||
end
|
end
|
||||||
callback(err)
|
end),
|
||||||
end
|
})
|
||||||
end),
|
)
|
||||||
})
|
|
||||||
local exe
|
local exe
|
||||||
if type(cmd) == "string" then
|
if type(cmd) == "string" then
|
||||||
exe = vim.split(cmd, "%s+")[1]
|
exe = vim.split(cmd, "%s+")[1]
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,10 @@ M.rename_buffer = function(src_bufnr, dest_buf_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local bufname = vim.api.nvim_buf_get_name(src_bufnr)
|
local bufname = vim.api.nvim_buf_get_name(src_bufnr)
|
||||||
local scheme = M.parse_url(bufname)
|
-- If this buffer is not literally a file on disk, then we can use the simple
|
||||||
-- If this buffer has a scheme (is not literally a file on disk), then we can use the simple
|
|
||||||
-- rename logic. The only reason we can't use nvim_buf_set_name on files is because vim will
|
-- rename logic. The only reason we can't use nvim_buf_set_name on files is because vim will
|
||||||
-- think that the new buffer conflicts with the file next time it tries to save.
|
-- think that the new buffer conflicts with the file next time it tries to save.
|
||||||
if scheme or vim.fn.isdirectory(bufname) == 1 then
|
if not vim.loop.fs_stat(dest_buf_name) then
|
||||||
-- This will fail if the dest buf name already exists
|
-- This will fail if the dest buf name already exists
|
||||||
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
local ok = pcall(vim.api.nvim_buf_set_name, src_bufnr, dest_buf_name)
|
||||||
if ok then
|
if ok then
|
||||||
|
|
@ -159,19 +158,10 @@ end
|
||||||
local function get_possible_buffer_names_from_url(url)
|
local function get_possible_buffer_names_from_url(url)
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
local scheme, path = M.parse_url(url)
|
local scheme, path = M.parse_url(url)
|
||||||
local ret = {}
|
if config.adapters[scheme] == "files" then
|
||||||
for k, v in pairs(config.remap_schemes) do
|
|
||||||
if v == scheme then
|
|
||||||
if k ~= "default" then
|
|
||||||
table.insert(ret, k .. path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if vim.tbl_isempty(ret) then
|
|
||||||
return { fs.posix_to_os_path(path) }
|
return { fs.posix_to_os_path(path) }
|
||||||
else
|
|
||||||
return ret
|
|
||||||
end
|
end
|
||||||
|
return { url }
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param entry_type oil.EntryType
|
---@param entry_type oil.EntryType
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,12 @@ M.initialize = function(bufnr)
|
||||||
if bufnr == 0 then
|
if bufnr == 0 then
|
||||||
bufnr = vim.api.nvim_get_current_buf()
|
bufnr = vim.api.nvim_get_current_buf()
|
||||||
end
|
end
|
||||||
|
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.bo[bufnr].buftype = "acwrite"
|
||||||
|
vim.bo[bufnr].syntax = "oil"
|
||||||
|
vim.bo[bufnr].filetype = "oil"
|
||||||
session[bufnr] = true
|
session[bufnr] = true
|
||||||
for k, v in pairs(config.buf_options) do
|
for k, v in pairs(config.buf_options) do
|
||||||
vim.api.nvim_buf_set_option(bufnr, k, v)
|
vim.api.nvim_buf_set_option(bufnr, k, v)
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,67 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
local oil = require("oil")
|
local oil = require("oil")
|
||||||
local test_util = require("tests.test_util")
|
local test_util = require("tests.test_util")
|
||||||
local fs = require("oil.fs")
|
local fs = require("oil.fs")
|
||||||
|
|
||||||
describe("Alternate buffer", function()
|
a.describe("Alternate buffer", function()
|
||||||
after_each(function()
|
after_each(function()
|
||||||
test_util.reset_editor()
|
test_util.reset_editor()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets previous buffer as alternate", function()
|
a.it("sets previous buffer as alternate", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets previous buffer as alternate when editing oil://", function()
|
a.it("sets previous buffer as alternate when editing oil://", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
vim.cmd.edit({ args = { "oil://" .. fs.os_to_posix_path(vim.fn.getcwd()) } })
|
vim.cmd.edit({ args = { "oil://" .. fs.os_to_posix_path(vim.fn.getcwd()) } })
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("preserves alternate buffer if editing the same file", function()
|
a.it("preserves alternate buffer if editing the same file", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("preserves alternate buffer if discarding changes", function()
|
a.it("preserves alternate buffer if discarding changes", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
oil.close()
|
oil.close()
|
||||||
|
assert.equals("bar", vim.fn.expand("%"))
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("sets previous buffer as alternate after multi-dir hops", function()
|
a.it("sets previous buffer as alternate after multi-dir hops", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("floating window", function()
|
a.describe("floating window", function()
|
||||||
it("sets previous buffer as alternate", function()
|
a.it("sets previous buffer as alternate", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
oil.open_float()
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
||||||
-- replicated it by closing the current window and then doing the edit
|
-- replicated it by closing the current window and then doing the edit
|
||||||
vim.api.nvim_win_close(0, true)
|
vim.api.nvim_win_close(0, true)
|
||||||
|
|
@ -58,10 +69,11 @@ describe("Alternate buffer", function()
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("preserves alternate buffer if editing the same file", function()
|
a.it("preserves alternate buffer if editing the same file", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
oil.open_float()
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
-- This is lazy, but testing the actual select logic is more difficult. We can simply
|
||||||
-- replicated it by closing the current window and then doing the edit
|
-- replicated it by closing the current window and then doing the edit
|
||||||
vim.api.nvim_win_close(0, true)
|
vim.api.nvim_win_close(0, true)
|
||||||
|
|
@ -69,10 +81,11 @@ describe("Alternate buffer", function()
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("preserves alternate buffer if discarding changes", function()
|
a.it("preserves alternate buffer if discarding changes", function()
|
||||||
vim.cmd.edit({ args = { "foo" } })
|
vim.cmd.edit({ args = { "foo" } })
|
||||||
vim.cmd.edit({ args = { "bar" } })
|
vim.cmd.edit({ args = { "bar" } })
|
||||||
oil.open_float()
|
oil.open_float()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
oil.close()
|
oil.close()
|
||||||
assert.equals("foo", vim.fn.expand("#"))
|
assert.equals("foo", vim.fn.expand("#"))
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
59
tests/move_rename_spec.lua
Normal file
59
tests/move_rename_spec.lua
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
local fs = require("oil.fs")
|
||||||
|
local test_util = require("tests.test_util")
|
||||||
|
local util = require("oil.util")
|
||||||
|
|
||||||
|
describe("update_moved_buffers", function()
|
||||||
|
after_each(function()
|
||||||
|
test_util.reset_editor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames moved buffers", function()
|
||||||
|
vim.cmd.edit({ args = { "oil-test:///foo/bar.txt" } })
|
||||||
|
util.update_moved_buffers("file", "oil-test:///foo/bar.txt", "oil-test:///foo/baz.txt")
|
||||||
|
assert.equals("oil-test:///foo/baz.txt", vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames moved buffers when they are normal files", function()
|
||||||
|
local tmpdir = fs.join(vim.loop.fs_realpath(vim.fn.stdpath("cache")), "oil", "test")
|
||||||
|
local testfile = fs.join(tmpdir, "foo.txt")
|
||||||
|
vim.cmd.edit({ args = { testfile } })
|
||||||
|
util.update_moved_buffers(
|
||||||
|
"file",
|
||||||
|
"oil://" .. fs.os_to_posix_path(testfile),
|
||||||
|
"oil://" .. fs.os_to_posix_path(fs.join(tmpdir, "bar.txt"))
|
||||||
|
)
|
||||||
|
assert.equals(fs.join(tmpdir, "bar.txt"), vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames directories", function()
|
||||||
|
vim.cmd.edit({ args = { "oil-test:///foo/" } })
|
||||||
|
util.update_moved_buffers("directory", "oil-test:///foo/", "oil-test:///bar/")
|
||||||
|
assert.equals("oil-test:///bar/", vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames subdirectories", function()
|
||||||
|
vim.cmd.edit({ args = { "oil-test:///foo/bar/" } })
|
||||||
|
util.update_moved_buffers("directory", "oil-test:///foo/", "oil-test:///baz/")
|
||||||
|
assert.equals("oil-test:///baz/bar/", vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames subfiles", function()
|
||||||
|
vim.cmd.edit({ args = { "oil-test:///foo/bar.txt" } })
|
||||||
|
util.update_moved_buffers("directory", "oil-test:///foo/", "oil-test:///baz/")
|
||||||
|
assert.equals("oil-test:///baz/bar.txt", vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Renames subfiles when they are normal files", function()
|
||||||
|
local tmpdir = fs.join(vim.loop.fs_realpath(vim.fn.stdpath("cache")), "oil", "test")
|
||||||
|
local foo = fs.join(tmpdir, "foo")
|
||||||
|
local bar = fs.join(tmpdir, "bar")
|
||||||
|
local testfile = fs.join(foo, "foo.txt")
|
||||||
|
vim.cmd.edit({ args = { testfile } })
|
||||||
|
util.update_moved_buffers(
|
||||||
|
"directory",
|
||||||
|
"oil://" .. fs.os_to_posix_path(foo),
|
||||||
|
"oil://" .. fs.os_to_posix_path(bar)
|
||||||
|
)
|
||||||
|
assert.equals(fs.join(bar, "foo.txt"), vim.api.nvim_buf_get_name(0))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
@ -15,14 +15,14 @@ a.describe("regression tests", function()
|
||||||
vim.cmd.wincmd({ args = { "p" } })
|
vim.cmd.wincmd({ args = { "p" } })
|
||||||
assert.equals("markdown", vim.bo.filetype)
|
assert.equals("markdown", vim.bo.filetype)
|
||||||
vim.cmd.edit({ args = { "%:p:h" } })
|
vim.cmd.edit({ args = { "%:p:h" } })
|
||||||
a.util.sleep(10)
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("oil", vim.bo.filetype)
|
assert.equals("oil", vim.bo.filetype)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- https://github.com/stevearc/oil.nvim/issues/37
|
-- https://github.com/stevearc/oil.nvim/issues/37
|
||||||
a.it("places the cursor on correct entry when opening on file", function()
|
a.it("places the cursor on correct entry when opening on file", function()
|
||||||
vim.cmd.edit({ args = { "." } })
|
vim.cmd.edit({ args = { "." } })
|
||||||
a.util.sleep(10)
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
local entry = oil.get_cursor_entry()
|
local entry = oil.get_cursor_entry()
|
||||||
assert.not_equals("README.md", entry and entry.name)
|
assert.not_equals("README.md", entry and entry.name)
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.reset_editor = function()
|
M.reset_editor = function()
|
||||||
|
|
@ -16,4 +17,13 @@ M.reset_editor = function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.wait_for_autocmd = a.wrap(function(autocmd, cb)
|
||||||
|
vim.api.nvim_create_autocmd(autocmd, {
|
||||||
|
pattern = "*",
|
||||||
|
nested = true,
|
||||||
|
once = true,
|
||||||
|
callback = vim.schedule_wrap(cb),
|
||||||
|
})
|
||||||
|
end, 2)
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ describe("url", function()
|
||||||
{ "/foo/bar.txt", "oil:///foo/", "bar.txt" },
|
{ "/foo/bar.txt", "oil:///foo/", "bar.txt" },
|
||||||
{ "oil:///foo/bar.txt", "oil:///foo/", "bar.txt" },
|
{ "oil:///foo/bar.txt", "oil:///foo/", "bar.txt" },
|
||||||
{ "oil:///", "oil:///" },
|
{ "oil:///", "oil:///" },
|
||||||
{ "scp://user@hostname:8888//bar.txt", "oil-ssh://user@hostname:8888//", "bar.txt" },
|
{ "scp://user@hostname:8888//bar.txt", "scp://user@hostname:8888//", "bar.txt" },
|
||||||
{ "oil-ssh://user@hostname:8888//", "oil-ssh://user@hostname:8888//" },
|
{ "scp://user@hostname:8888//", "scp://user@hostname:8888//" },
|
||||||
}
|
}
|
||||||
for _, case in ipairs(cases) do
|
for _, case in ipairs(cases) do
|
||||||
local input, expected, expected_basename = unpack(case)
|
local input, expected, expected_basename = unpack(case)
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,40 @@
|
||||||
|
require("plenary.async").tests.add_to_env()
|
||||||
local oil = require("oil")
|
local oil = require("oil")
|
||||||
local test_util = require("tests.test_util")
|
local test_util = require("tests.test_util")
|
||||||
|
|
||||||
describe("window options", function()
|
a.describe("window options", function()
|
||||||
after_each(function()
|
after_each(function()
|
||||||
test_util.reset_editor()
|
test_util.reset_editor()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("Restores window options on close", function()
|
a.it("Restores window options on close", function()
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
oil.close()
|
oil.close()
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("Restores window options on edit", function()
|
a.it("Restores window options on edit", function()
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("Restores window options on split <filename>", function()
|
a.it("Restores window options on split <filename>", function()
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.split({ args = { "README.md" } })
|
vim.cmd.split({ args = { "README.md" } })
|
||||||
assert.equals("auto", vim.o.signcolumn)
|
assert.equals("auto", vim.o.signcolumn)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("Restores window options on split", function()
|
a.it("Restores window options on split", function()
|
||||||
oil.open()
|
oil.open()
|
||||||
|
test_util.wait_for_autocmd("BufReadPost")
|
||||||
assert.equals("no", vim.o.signcolumn)
|
assert.equals("no", vim.o.signcolumn)
|
||||||
vim.cmd.split()
|
vim.cmd.split()
|
||||||
vim.cmd.edit({ args = { "README.md" } })
|
vim.cmd.edit({ args = { "README.md" } })
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue