diff --git a/lua/oil/adapters/files.lua b/lua/oil/adapters/files.lua index e0074236..7b92cb94 100644 --- a/lua/oil/adapters/files.lua +++ b/lua/oil/adapters/files.lua @@ -331,6 +331,15 @@ local function list_windows_drives(url, column_defs, cb) end end +---@param url string +M.file_exists = function(url) + local _, path = util.parse_url(url) + assert(path) + local dir = fs.posix_to_os_path(path) + + return fs.file_exists(dir) +end + ---@param url string ---@param column_defs string[] ---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun()) diff --git a/lua/oil/fs.lua b/lua/oil/fs.lua index ac216a18..570bac5d 100644 --- a/lua/oil/fs.lua +++ b/lua/oil/fs.lua @@ -160,6 +160,12 @@ M.mkdirp = function(dir, mode) end end +--- @param path string +--- @return boolean +M.file_exists = function(path) + return uv.fs_stat(path) ~= nil +end + ---@param dir string ---@param cb fun(err: nil|string, entries: nil|{type: oil.EntryType, name: string}) M.listdir = function(dir, cb) diff --git a/lua/oil/init.lua b/lua/oil/init.lua index e5e21edb..be76c2df 100644 --- a/lua/oil/init.lua +++ b/lua/oil/init.lua @@ -17,6 +17,7 @@ local M = {} ---@class (exact) oil.Adapter ---@field name string The unique name of the adapter (this will be set automatically) ---@field list fun(path: string, column_defs: string[], cb: fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun())) Async function to list a directory. +---@field file_exists fun(path: string) Returns true if the file at path exists ---@field is_modifiable fun(bufnr: integer): boolean Return true if this directory is modifiable (allows for directories with read-only permissions). ---@field get_column fun(name: string): nil|oil.ColumnDefinition If the adapter has any adapter-specific columns, return them when fetched by name. ---@field get_parent? fun(bufname: string): string Get the parent url of the given buffer diff --git a/lua/oil/mutator/parser.lua b/lua/oil/mutator/parser.lua index b91350b4..d060d867 100644 --- a/lua/oil/mutator/parser.lua +++ b/lua/oil/mutator/parser.lua @@ -186,8 +186,31 @@ M.parse = function(bufnr) -- mac and windows use case-insensitive filesystems name = name:lower() end + local has_subdir = string.find(name, fs.sep) + local subdir_name = has_subdir + and string.match(name, string.format("([^%s]+)%s", fs.sep, fs.sep)) + or nil if seen_names[name] then table.insert(errors, { message = "Duplicate filename", lnum = i - 1, end_lnum = i, col = 0 }) + elseif has_subdir and adapter.file_exists(parent_url .. name) then + table.insert( + errors, + { message = "Duplicate file in nested path", lnum = i - 1, end_lnum = i, col = 0 } + ) + elseif + has_subdir + and seen_names[subdir_name] + and not adapter.file_exists(parent_url .. subdir_name) + then + table.insert(errors, { + message = "Cannot move a file to a directory which has been renamed in the same action", + lnum = i - 1, + end_lnum = i, + col = 0, + }) + elseif has_subdir then + seen_names[subdir_name] = true + seen_names[name] = true else seen_names[name] = true end @@ -216,6 +239,11 @@ M.parse = function(bufnr) err_message = "No filename found" elseif not entry then err_message = "Could not find existing entry (was the ID changed?)" + elseif + adapter.name ~= "files" + and (parsed_entry.name:match("/") or parsed_entry.name:match(fs.sep)) + then + err_message = "Filename cannot contain path separator" end if err_message then table.insert(errors, {