local oil = require('oil') local util = require('oil.util') local M = {} M.show_help = { callback = function() local config = require('oil.config') require('oil.keymap_util').show_help(config.keymaps) end, desc = 'Show default keymaps', } M.select = { desc = 'Open the entry under the cursor', callback = function(opts) opts = opts or {} local callback = opts.callback opts.callback = nil oil.select(opts, callback) end, parameters = { vertical = { type = 'boolean', desc = 'Open the buffer in a vertical split', }, horizontal = { type = 'boolean', desc = 'Open the buffer in a horizontal split', }, split = { type = '"aboveleft"|"belowright"|"topleft"|"botright"', desc = 'Split modifier', }, tab = { type = 'boolean', desc = 'Open the buffer in a new tab', }, close = { type = 'boolean', desc = 'Close the original oil buffer once selection is made', }, }, } M.select_vsplit = { desc = 'Open the entry under the cursor in a vertical split', deprecated = true, callback = function() oil.select({ vertical = true }) end, } M.select_split = { desc = 'Open the entry under the cursor in a horizontal split', deprecated = true, callback = function() oil.select({ horizontal = true }) end, } M.select_tab = { desc = 'Open the entry under the cursor in a new tab', deprecated = true, callback = function() oil.select({ tab = true }) end, } M.preview = { desc = 'Open the entry under the cursor in a preview window, or close the preview window if already open', parameters = { vertical = { type = 'boolean', desc = 'Open the buffer in a vertical split', }, horizontal = { type = 'boolean', desc = 'Open the buffer in a horizontal split', }, split = { type = '"aboveleft"|"belowright"|"topleft"|"botright"', desc = 'Split modifier', }, }, callback = function(opts) local entry = oil.get_cursor_entry() if not entry then vim.notify('Could not find entry under cursor', vim.log.levels.ERROR) return end local winid = util.get_preview_win() if winid then local cur_id = vim.w[winid].oil_entry_id if entry.id == cur_id then vim.api.nvim_win_close(winid, true) if util.is_floating_win() then local layout = require('oil.layout') local win_opts = layout.get_fullscreen_win_opts() vim.api.nvim_win_set_config(0, win_opts) end return end end oil.open_preview(opts) end, } M.preview_scroll_down = { desc = 'Scroll down in the preview window', callback = function() local winid = util.get_preview_win() if winid then vim.api.nvim_win_call(winid, function() vim.cmd.normal({ args = { vim.api.nvim_replace_termcodes('', true, true, true) }, bang = true, }) end) end end, } M.preview_scroll_up = { desc = 'Scroll up in the preview window', callback = function() local winid = util.get_preview_win() if winid then vim.api.nvim_win_call(winid, function() vim.cmd.normal({ args = { vim.api.nvim_replace_termcodes('', true, true, true) }, bang = true, }) end) end end, } M.preview_scroll_left = { desc = 'Scroll left in the preview window', callback = function() local winid = util.get_preview_win() if winid then vim.api.nvim_win_call(winid, function() vim.cmd.normal({ 'zH', bang = true }) end) end end, } M.preview_scroll_right = { desc = 'Scroll right in the preview window', callback = function() local winid = util.get_preview_win() if winid then vim.api.nvim_win_call(winid, function() vim.cmd.normal({ 'zL', bang = true }) end) end end, } M.parent = { desc = 'Navigate to the parent path', callback = oil.open, } M.close = { desc = 'Close oil and restore original buffer', callback = function(opts) opts = opts or {} oil.close(opts) end, parameters = { exit_if_last_buf = { type = 'boolean', desc = 'Exit vim if oil is closed as the last buffer', }, }, } M.close_float = { desc = 'Close oil if the window is floating, otherwise do nothing', callback = function(opts) if vim.w.is_oil_win then opts = opts or {} oil.close(opts) end end, parameters = { exit_if_last_buf = { type = 'boolean', desc = 'Exit vim if oil is closed as the last buffer', }, }, } ---@param cmd string ---@param silent? boolean local function cd(cmd, silent) local dir = oil.get_current_dir() if dir then vim.cmd({ cmd = cmd, args = { dir } }) if not silent then vim.notify(string.format('CWD: %s', dir), vim.log.levels.INFO) end else vim.notify('Cannot :cd; not in a directory', vim.log.levels.WARN) end end M.cd = { desc = ':cd to the current oil directory', callback = function(opts) opts = opts or {} local cmd = 'cd' if opts.scope == 'tab' then cmd = 'tcd' elseif opts.scope == 'win' then cmd = 'lcd' end cd(cmd, opts.silent) end, parameters = { scope = { type = 'nil|"tab"|"win"', desc = 'Scope of the directory change (e.g. use |:tcd| or |:lcd|)', }, silent = { type = 'boolean', desc = 'Do not show a message when changing directories', }, }, } M.tcd = { desc = ':tcd to the current oil directory', deprecated = true, callback = function() cd('tcd') end, } M.open_cwd = { desc = "Open oil in Neovim's current working directory", callback = function() oil.open(vim.fn.getcwd()) end, } M.toggle_hidden = { desc = 'Toggle hidden files and directories', callback = function() require('oil.view').toggle_hidden() end, } M.open_terminal = { desc = 'Open a terminal in the current directory', callback = function() local config = require('oil.config') 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) vim.api.nvim_set_current_buf(bufnr) if vim.fn.has('nvim-0.11') == 1 then vim.fn.jobstart(vim.o.shell, { cwd = dir, term = true }) else ---@diagnostic disable-next-line: deprecated vim.fn.termopen(vim.o.shell, { cwd = dir }) end 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 if vim.fn.has('nvim-0.11') == 1 then term_id = vim.fn.jobstart(cmd, { term = true }) else ---@diagnostic disable-next-line: deprecated term_id = vim.fn.termopen(cmd) end 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, } ---Copied from vim.ui.open in Neovim 0.10+ ---@param path string ---@return nil|string[] cmd ---@return nil|string error local function get_open_cmd(path) if vim.fn.has('mac') == 1 then return { 'open', path } elseif vim.fn.has('win32') == 1 then if vim.fn.executable('rundll32') == 1 then return { 'rundll32', 'url.dll,FileProtocolHandler', path } else return nil, 'rundll32 not found' end elseif vim.fn.executable('explorer.exe') == 1 then return { 'explorer.exe', path } elseif vim.fn.executable('xdg-open') == 1 then return { 'xdg-open', path } else return nil, 'no handler found' end end M.open_external = { desc = 'Open the entry under the cursor in an external program', callback = function() local entry = oil.get_cursor_entry() local dir = oil.get_current_dir() if not entry or not dir then return end local path = dir .. entry.name if vim.ui.open then vim.ui.open(path) return end local cmd, err = get_open_cmd(path) if not cmd then vim.notify(string.format('Could not open %s: %s', path, err), vim.log.levels.ERROR) return end local jid = vim.fn.jobstart(cmd, { detach = true }) assert(jid > 0, 'Failed to start job') end, } M.refresh = { desc = 'Refresh current directory list', callback = function(opts) opts = opts or {} if vim.bo.modified and not opts.force then local ok, choice = pcall(vim.fn.confirm, 'Discard changes?', 'No\nYes') if not ok or choice ~= 2 then return end end vim.cmd.edit({ bang = true }) -- :h CTRL-L-default vim.cmd.nohlsearch() end, parameters = { force = { desc = 'When true, do not prompt user if they will be discarding changes', type = 'boolean', }, }, } local function open_cmdline_with_path(path) local escaped = vim.api.nvim_replace_termcodes(': ' .. vim.fn.fnameescape(path) .. '', true, false, true) vim.api.nvim_feedkeys(escaped, 'n', false) end M.open_cmdline = { desc = 'Open vim cmdline with current entry as an argument', callback = function(opts) opts = vim.tbl_deep_extend('keep', opts or {}, { shorten_path = true, }) local config = require('oil.config') local fs = require('oil.fs') local entry = oil.get_cursor_entry() if not entry then return end local bufname = vim.api.nvim_buf_get_name(0) local scheme, path = util.parse_url(bufname) if not scheme then return end local adapter = config.get_adapter_by_scheme(scheme) if not adapter or not path or adapter.name ~= 'files' then return end local fullpath = fs.posix_to_os_path(path) .. entry.name if opts.modify then fullpath = vim.fn.fnamemodify(fullpath, opts.modify) end if opts.shorten_path then fullpath = fs.shorten_path(fullpath) end open_cmdline_with_path(fullpath) end, parameters = { modify = { desc = 'Modify the path with |fnamemodify()| using this as the mods argument', type = 'string', }, shorten_path = { desc = 'Use relative paths when possible', type = 'boolean', }, }, } M.yank_entry = { desc = 'Yank the filepath of the entry under the cursor to a register', callback = function(opts) opts = opts or {} local entry = oil.get_cursor_entry() local dir = oil.get_current_dir() if not entry or not dir then return end local name = entry.name if entry.type == 'directory' then name = name .. '/' end local path = dir .. name if opts.modify then path = vim.fn.fnamemodify(path, opts.modify) end vim.fn.setreg(vim.v.register, path) end, parameters = { modify = { desc = 'Modify the path with |fnamemodify()| using this as the mods argument', type = 'string', }, }, } M.copy_entry_path = { desc = 'Yank the filepath of the entry under the cursor to a register', deprecated = true, callback = function() local entry = oil.get_cursor_entry() local dir = oil.get_current_dir() if not entry or not dir then return end vim.fn.setreg(vim.v.register, dir .. entry.name) end, } M.copy_entry_filename = { desc = 'Yank the filename of the entry under the cursor to a register', deprecated = true, callback = function() local entry = oil.get_cursor_entry() if not entry then return end vim.fn.setreg(vim.v.register, entry.name) end, } M.copy_to_system_clipboard = { desc = 'Copy the entry under the cursor to the system clipboard', callback = function() require('oil.clipboard').copy_to_system_clipboard() end, } M.paste_from_system_clipboard = { desc = 'Paste the system clipboard into the current oil directory', callback = function(opts) require('oil.clipboard').paste_from_system_clipboard(opts and opts.delete_original) end, parameters = { delete_original = { type = 'boolean', desc = 'Delete the original file after copying', }, }, } M.open_cmdline_dir = { desc = 'Open vim cmdline with current directory as an argument', deprecated = true, callback = function() local fs = require('oil.fs') local dir = oil.get_current_dir() if dir then open_cmdline_with_path(fs.shorten_path(dir)) end end, } M.change_sort = { desc = 'Change the sort order', callback = function(opts) opts = opts or {} if opts.sort then oil.set_sort(opts.sort) return end local sort_cols = { 'name', 'size', 'atime', 'mtime', 'ctime', 'birthtime' } vim.ui.select(sort_cols, { prompt = 'Sort by', kind = 'oil_sort_col' }, function(col) if not col then return end vim.ui.select( { 'ascending', 'descending' }, { prompt = 'Sort order', kind = 'oil_sort_order' }, function(order) if not order then return end order = order == 'ascending' and 'asc' or 'desc' oil.set_sort({ { 'type', 'asc' }, { col, order }, }) end ) end) end, parameters = { sort = { type = 'oil.SortSpec[]', desc = 'List of columns plus direction (see |oil.set_sort|) instead of interactive selection', }, }, } M.toggle_trash = { desc = 'Jump to and from the trash for the current directory', callback = function() local fs = require('oil.fs') local bufname = vim.api.nvim_buf_get_name(0) local scheme, path = util.parse_url(bufname) local bufnr = vim.api.nvim_get_current_buf() local url if scheme == 'oil://' then url = 'oil-trash://' .. path elseif scheme == 'oil-trash://' then url = 'oil://' .. path -- The non-linux trash implementations don't support per-directory trash, -- so jump back to the stored source buffer. if not fs.is_linux then local src_bufnr = vim.b.oil_trash_toggle_src if src_bufnr and vim.api.nvim_buf_is_valid(src_bufnr) then url = vim.api.nvim_buf_get_name(src_bufnr) end end else vim.notify('No trash found for buffer', vim.log.levels.WARN) return end vim.cmd.edit({ args = { url } }) vim.b.oil_trash_toggle_src = bufnr end, } M.send_to_qflist = { desc = 'Sends files in the current oil directory to the quickfix list, replacing the previous entries.', callback = function(opts) opts = vim.tbl_deep_extend('keep', opts or {}, { target = 'qflist', action = 'r', only_matching_search = false, }) util.send_to_quickfix({ target = opts.target, action = opts.action, only_matching_search = opts.only_matching_search, }) end, parameters = { target = { type = '"qflist"|"loclist"', desc = 'The target list to send files to', }, action = { type = '"r"|"a"', desc = 'Replace or add to current quickfix list (see |setqflist-action|)', }, only_matching_search = { type = 'boolean', desc = 'Whether to only add the files that matches the last search. This option only applies when search highlighting is active', }, }, } M.add_to_qflist = { desc = 'Adds files in the current oil directory to the quickfix list, keeping the previous entries.', deprecated = true, callback = function() util.send_to_quickfix({ target = 'qflist', mode = 'a', }) end, } M.send_to_loclist = { desc = 'Sends files in the current oil directory to the location list, replacing the previous entries.', deprecated = true, callback = function() util.send_to_quickfix({ target = 'loclist', mode = 'r', }) end, } M.add_to_loclist = { desc = 'Adds files in the current oil directory to the location list, keeping the previous entries.', deprecated = true, callback = function() util.send_to_quickfix({ target = 'loclist', mode = 'a', }) end, } ---List actions for documentation generation ---@private M._get_actions = function() local ret = {} for name, action in pairs(M) do if type(action) == 'table' and action.desc then table.insert(ret, { name = name, desc = action.desc, deprecated = action.deprecated, parameters = action.parameters, }) end end return ret end return M