8000 mini.nvim/lua/mini/files.lua at main · MikaelElkiaer/mini.nvim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
2526 lines (2139 loc) · 89.5 KB

files.lua

File metadata and controls

2526 lines (2139 loc) · 89.5 KB
--- *mini.files* Navigate and manipulate file system
--- *MiniFiles*
---
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Navigate file system using column view (Miller columns) to display nested
--- directories. See |MiniFiles-navigation| for overview.
---
--- - Opt-in preview of file or directory under cursor.
---
--- - Manipulate files and directories by editing text buffers: create, delete,
--- copy, rename, move. See |MiniFiles-manipulation| for overview.
---
--- - Use as default file explorer instead of |netrw|.
---
--- - Configurable:
--- - Filter/prefix/sort of file system entries.
--- - Mappings used for common explorer actions.
--- - UI options: whether to show preview of file/directory under cursor, etc.
---
--- What it doesn't do:
--- - Try to be replacement of system file explorer. It is mostly designed to
--- be used within Neovim to quickly explore file system structure, open
--- files, and perform some quick file system edits.
---
--- - Work on remote locations. Only local file system is supported.
---
--- - Provide built-in interactive toggle of content `filter` and `sort`.
--- See |MiniFiles-examples| for some common examples.
---
--- - Provide out of the box extra information like git or diagnostic status.
--- This can be achieved by setting |extmarks| on appropriate event(s)
--- (see |MiniFiles-events|)
---
--- Notes:
--- - This module is written and thoroughly tested on Linux. Support for other
--- platform/OS (like Windows or MacOS) is a goal, but there is no guarantee.
---
--- - Works on all supported versions but using Neovim>=0.9 is recommended.
---
--- - This module silently reacts to not enough permissions:
--- - In case of missing file, check its or its parent read permissions.
--- - In case of no manipulation result, check write permissions.
---
--- # Dependencies~
---
--- Suggested dependencies (provide extra functionality, will work without them):
--- - Plugin 'nvim-tree/nvim-web-devicons' for filetype icons near the entry
--- name. If missing, default icons will be used.
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.files').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniFiles`
--- which you can use for scripting or manually (with `:lua MiniFiles.*`).
---
--- See |MiniFiles.config| for available config settings.
---
--- You can override runtime config settings (like mappings or window options)
--- locally to buffer inside `vim.b.minifiles_config` which should have same
--- structure as `MiniFiles.config`. See |mini.nvim-buffer-local-config| for
--- more details.
---
--- # Comparisons ~
---
--- - 'nvim-tree/nvim-tree.lua':
--- - Provides tree view of file system, while this module uses column view.
--- - File system manipulation is done with custom set of mappings for each
--- action, while this module is designed to do that by editing text.
--- - Has more out of the box functionality with extra configuration, while
--- this module has not (by design).
---
--- - 'stevearc/oil.nvim':
--- - Uses single window to show information only about currently explored
--- directory, while this module uses column view to show whole currently
--- explored branch.
--- - Also uses text editing to manipulate file system entries.
--- - Can work for remote file systems, while this module can not (by design).
---
--- - 'nvim-neo-tree/neo-tree.nvim':
--- - Compares to this module mostly the same as 'nvim-tree/nvim-tree.lua'.
---
--- # Highlight groups ~
---
--- * `MiniFilesBorder` - border of regular windows.
--- * `MiniFilesBorderModified` - border of windows showing modified buffer.
--- * `MiniFilesDirectory` - text and icon representing directory.
--- * `MiniFilesFile` - text representing file.
--- * `MiniFilesNormal` - basic foreground/background highlighting.
--- * `MiniFilesTitle` - title of regular windows.
--- * `MiniFilesTitleFocused` - title of focused window.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling ~
---
--- This plugin provides only manually started functionality, so no disabling
--- is available.
--- Navigation ~
---
--- Every navigation starts by calling |MiniFiles.open()|, either directly or via
--- mapping (see its help for examples of some common scenarios). It will show
--- an explorer consisting of side-by-side floating windows with the following
--- principles:
---
--- - Explorer shows one branch of nested directories at a time.
---
--- - Explorer consists from several windows:
---
--- - Each window displays entries of a single directory in a modifiable
--- scratch buffer.
---
--- - Windows are organized left to right: for any particular window the left
--- neighbor is its parent directory and right neighbor - its child.
---
--- - Explorer windows are the viewport to some part of current branch, meaning
--- that their opening/closing does not affect the branch. This matters, for
--- example, if there are more elements in the branch than can be shown windows.
---
--- - Every buffer line represents separate file system entry following certain
--- format (not visible for users by default; set |conceallevel| to 0 to see it)
---
--- - Once directory is shown, its buffer is not updated automatically following
--- external file system changes. Manually use |MiniFiles.synchronize()| for that.
---
--- After opening explorer, in-buffer navigation is done the same way as any
--- regular buffer, except without some keys reserved for built-in actions.
---
--- Most common ways to navigate are:
---
--- - Press `j` to move cursor onto next (lower) entry in current directory.
--- - Press `k` to move cursor onto previous (higher) entry in current directory.
--- - Press `l` to expand entry under cursor (see "Go in" action).
--- - Press `h` to focus on parent directory (see "Go out" action).
---
--- Cursor positions in each directory buffer are tracked and saved during
--- navigation. This allows for more convenient repeated navigation to some
--- previously visited branch.
---
--- Available built-in actions (see "Details" for more information): >
---
--- | Action | Keys | Description |
--- |-------------|------|------------------------------------------------|
--- | Close | q | Close explorer |
--- |-------------|------|------------------------------------------------|
--- | Go in | l | Expand entry (show directory or open file) |
--- |-------------|------|------------------------------------------------|
--- | Go in plus | L | Expand entry plus extra action |
--- |-------------|------|------------------------------------------------|
--- | Go out | h | Focus on parent directory |
--- |-------------|------|------------------------------------------------|
--- | Go out plus | H | Focus on parent directory plus extra action |
--- |-------------|------|------------------------------------------------|
--- | Reset | <BS> | Reset current explorer |
--- |-------------|------|------------------------------------------------|
--- | Reveal cwd | @ | Reset current current working directory |
--- |-------------|------|------------------------------------------------|
--- | Show help | g? | Show help window |
--- |-------------|------|------------------------------------------------|
--- | Synchronize | = | Synchronize user edits and/or external changes |
--- |-------------|------|------------------------------------------------|
--- | Trim left | < | Trim left part of branch |
--- |-------------|------|------------------------------------------------|
--- | Trim right | > | Trim right part of branch |
--- |-------------|------|------------------------------------------------|
--- <
--- Details:
---
--- - "Go in":
--- - Always opens file in the latest window before `MiniFiles.open()` call.
--- - Never closes explorer.
--- - Works in linewise Visual mode to expand multiple entries.
---
--- - "Go in plus" is regular "Go in" but closes explorer after opening a file.
---
--- - "Go out plus" is regular "Go out" but trims right part of branch.
---
--- - "Reset" focuses only on "anchor" directory (the one used to open current
--- explorer) and resets all stored directory cursor positions.
---
--- - "Reveal cwd" extends branch to include |current-directory|.
--- If it is not an ancestor of the current branch, nothing is done.
---
--- - "Show help" results into new window with helpful information about current
--- explorer. Press `q` to close it.
---
--- - "Synchronize" parses user edits in directory buffers, applies them (after
--- confirmation), and updates all directory buffers with the most relevant
--- file system information. Can also be used without user edits to show up
--- to date file system entries.
--- See |MiniFiles-manipulation| for more info about file system manipulation.
---
--- - "Trim left" and "Trim right" trim parts of the whole branch, not only its
--- currently visible parts.
---
--- Notes:
---
--- - Each action has exported function with more details about it.
---
--- - Keys can be configured with `mappings` table of |MiniFiles.config|.
---@tag MiniFiles-navigation
--- Manipulation ~
---
--- File system manipulation is done by editing text inside directory buffers,
--- which are shown inside dedicated window(s). See |MiniFiles-navigation| for
--- more information about navigating to a particular directory.
---
--- General workflow:
---
--- - Navigate to the directory in which manipulation should be done.
---
--- - Edit buffer in the way representing file system action.
---
--- - Repeat previous steps until all necessary file system actions are recorded.
--- Note: even if directory buffer is hidden, its modifications are preserved,
--- so you can navigate in and out of directory with modified buffer.
---
--- - Execute |MiniFiles.synchronize()| (default key is `=`). This will prompt
--- confirmation dialog listing all file system actions it is about to perform.
--- READ IT CAREFULLY.
---
--- - Confirm by pressing `y`/`<CR>` (applies edits and updates buffers) or
--- don't confirm by pressing `n`/`<Esc>` (updates buffers without applying edits).
---
--- # How does it work ~
---
--- All manipulation functionality is powered by creating and keeping track of
--- path indexes: text of the form `/xxx` (`xxx` is the number path index) placed
--- at the start of every line representing file system entry.
---
--- By default they are hidden as concealed text (along with prefix separators)
--- for more convenience but you can see them by setting |conceallevel| to 0.
--- DO NOT modify text to the left of entry name.
---
--- During synchronization, actual text for entry name is compared to path index
--- at that line (if present) to deduce which file system action to perform.
---
--- # Supported file system actions ~
---
--- ## Create ~
---
--- - Create file by creating new line with file name (including extension).
---
--- - Create directory by creating new line with directory name followed by `/`.
---
--- - Create file or directory inside nested directories by creating new line
--- with text like 'dir/nested-dir/' or 'dir/nested-dir/file'.
--- Always use `/` on any OS.
---
--- ## Delete ~
---
--- - Delete file or directory by deleting **whole line** describing it.
---
--- - If `options.permanent_delete` is `true`, delete is permanent. Otherwise
--- file system entry is moved to a module-specific trash directory
--- (see |MiniFiles.config| for more details).
---
--- ## Rename ~
---
--- - Rename file or directory by editing its name (not icon or path index to
--- the left of it).
---
--- - With default mappings for `h`/`l` it might be not convenient to rename
--- only part of an entry. You can adopt any of the following approaches:
--- - Use different motions, like |$|, |e|, |f|, etc.
--- - Go into Insert mode and navigate inside it.
--- - Change mappings to be more suited for manipulation and not navigation.
--- See "Mappings" section in |MiniFiles.config|.
---
--- - It is not needed to end directory name with `/`.
---
--- - Cyclic renames ('a' to 'b' and 'b' to 'a') are not supported.
---
--- ## Copy ~
---
--- - Copy file or directory by copying **whole line** describing it and pasting
--- it inside buffer of target directory.
---
--- - Change of target path is allowed. Edit only entry name in target location
--- (not icon or path index to the left of it).
---
--- - Copying inside same parent directory is supported only if target path has
--- different name.
---
--- - Copying inside child directory is supported.
---
--- ## Move ~
---
--- - Move file or directory by cutting **whole line** describing it and then
--- pasting it inside target directory.
---
--- - Change of target path is allowed. Edit only entry name in target location
--- (not icon or path index to the left of it).
---
--- - Moving directory inside itself is not supported.
---
---@tag MiniFiles-manipulation
--- Events ~
---
--- To allow user customization and integration of external tools, certain |User|
--- autocommand events are triggered under common circumstances.
---
--- UI events ~
---
--- - `MiniFilesBufferCreate` - when buffer is created to show a particular
--- directory. Triggered once per directory during one explorer session.
--- Can be used to create buffer-local mappings.
---
--- - `MiniFilesBufferUpdate` - when directory buffer is updated with new content.
--- Can be used for integrations to set |extmarks| with useful information.
---
--- - `MiniFilesWindowOpen` - when new window is opened. Can be used to set
--- window-local settings (like border, 'winblend', etc.)
---
--- - `MiniFilesWindowUpdate` - when a window is updated. Triggers frequently,
--- for example, for every "go in" or "go out" action.
---
--- Callback for each UI event will receive `data` field (see |nvim_create_autocmd()|)
--- with the following information:
---
--- - <buf_id> - index of target buffer.
--- - <win_id> - index of target window. Can be `nil`, like in
--- `MiniFilesBufferCreate` and buffer's first `MiniFilesBufferUpdate` as
--- they are triggered before window is created.
---
--- File action events ~
---
--- - `MiniFilesActionCreate` - after entry is successfully created.
---
--- - `MiniFilesActionDelete` - after entry is successfully deleted.
---
--- - `MiniFilesActionRename` - after entry is successfully renamed.
---
--- - `MiniFilesActionCopy` - after entry is successfully copied.
---
--- - `MiniFilesActionMove` - after entry is successfully moved.
---
--- Callback for each file action event will receive `data` field
--- (see |nvim_create_autocmd()|) with the following information:
---
--- - <action> - string with action name.
--- - <from> - absolute path of entry before action (`nil` for "create" action).
--- - <to> - absolute path of entry after action (`nil` for "delete" action).
---@tag MiniFiles-events
--- Common configuration examples ~
---
--- # Toggle explorer ~
---
--- Use a combination of |MiniFiles.open()| and |MiniFiles.close()|: >
---
--- local minifiles_toggle = function(...)
--- if not MiniFiles.close() then MiniFiles.open(...) end
--- end
---
--- # Customize windows ~
---
--- Create an autocommand for `MiniFilesWindowOpen` event: >
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesWindowOpen',
--- callback = function(args)
--- local win_id = args.data.win_id
---
--- -- Customize window-local settings
--- vim.wo[win_id].winblend = 50
--- vim.api.nvim_win_set_config(win_id, { border = 'double' })
--- end,
--- })
---
--- # Customize icons ~
---
--- Use different directory icon: >
---
--- local my_prefix = function(fs_entry)
--- if fs_entry.fs_type == 'directory' then
--- -- NOTE: it is usually a good idea to use icon followed by space
--- return ' ', 'MiniFilesDirectory'
--- end
--- return MiniFiles.default_prefix(fs_entry)
--- end
---
--- require('mini.files').setup({ content = { prefix = my_prefix } })
---
--- Show no icons: >
---
--- require('mini.files').setup({ content = { prefix = function() end } })
---
--- # Create mapping to show/hide dot-files ~
---
--- Create an autocommand for `MiniFilesBufferCreate` event which calls
--- |MiniFiles.refresh()| with explicit `content.filter` functions: >
---
--- local show_dotfiles = true
---
--- local filter_show = function(fs_entry) return true end
---
--- local filter_hide = function(fs_entry)
--- return not vim.startswith(fs_entry.name, '.')
--- end
---
--- local toggle_dotfiles = function()
--- show_dotfiles = not show_dotfiles
--- local new_filter = show_dotfiles and filter_show or filter_hide
--- MiniFiles.refresh({ content = { filter = new_filter } })
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- local buf_id = args.data.buf_id
--- -- Tweak left-hand side of mapping to your liking
--- vim.keymap.set('n', 'g.', toggle_dotfiles, { buffer = buf_id })
--- end,
--- })
---
--- # Create mappings to modify target window via split ~
---
--- Combine |MiniFiles.get_target_window()| and |MiniFiles.set_target_window()|: >
---
--- local map_split = function(buf_id, lhs, direction)
--- local rhs = function()
--- -- Make new window and set it as target
--- local new_target_window
--- vim.api.nvim_win_call(MiniFiles.get_target_window(), function()
--- vim.cmd(direction .. ' split')
--- new_target_window = vim.api.nvim_get_current_win()
--- end)
---
--- MiniFiles.set_target_window(new_target_window)
--- end
---
--- -- Adding `desc` will result into `show_help` entries
--- local desc = 'Split ' .. direction
--- vim.keymap.set('n', lhs, rhs, { buffer = buf_id, desc = desc })
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- local buf_id = args.data.buf_id
--- -- Tweak keys to your liking
--- map_split(buf_id, 'gs', 'belowright horizontal')
--- map_split(buf_id, 'gv', 'belowright vertical')
--- end,
--- })
---
--- # Create mapping to set current working directory ~
---
--- Use |MiniFiles.get_fs_entry()| together with |vim.fs.dirname()|: >
---
--- local files_set_cwd = function(path)
--- -- Works only if cursor is on the valid file system entry
--- local cur_entry_path = MiniFiles.get_fs_entry().path
--- local cur_directory = vim.fs.dirname(cur_entry_path)
--- vim.fn.chdir(cur_directory)
--- end
---
--- vim.api.nvim_create_autocmd('User', {
--- pattern = 'MiniFilesBufferCreate',
--- callback = function(args)
--- vim.keymap.set('n', 'g~', files_set_cwd, { buffer = args.data.buf_id })
--- end,
--- })
---@tag MiniFiles-examples
---@diagnostic disable:luadoc-miss-type-name
---@alias __minifiles_fs_entry_data_fields - <fs_type> `(string)` - one of "file" or "directory".
--- - <name> `(string)` - basename of an entry (including extension).
--- - <path> `(string)` - full path of an entry.
---@diagnostic disable:undefined-field
---@diagnostic disable:discard-returns
---@diagnostic disable:unused-local
---@diagnostic disable:cast-local-type
-- Module definition ==========================================================
local MiniFiles = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniFiles.config|.
---
---@usage `require('mini.files').setup({})` (replace `{}` with your `config` table).
MiniFiles.setup = function(config)
-- Export module
_G.MiniFiles = MiniFiles
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands(config)
-- Create default highlighting
H.create_default_hl()
end
--stylua: ignore
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # Content ~
---
--- `content.filter` is a predicate which takes file system entry data as input
--- and returns `true`-ish value if it should be shown.
--- Uses |MiniFiles.default_filter()| by default.
---
--- A file system entry data is a table with the following fields:
--- __minifiles_fs_entry_data_fields
---
--- `content.prefix` describes what text (prefix) to show to the left of file
--- system entry name (if any) and how to highlight it. It also takes file
--- system entry data as input and returns tuple of text and highlight group
--- name to be used to highlight prefix. See |MiniFiles-examples| for common
--- examples of how to use it.
--- Note: due to how lines are parsed to detect user edits for file system
--- manipulation, output of `content.prefix` should not contain `/` character.
--- Uses |MiniFiles.default_prefix()| by default.
---
--- `content.sort` describes in which order directory entries should be shown
--- in directory buffer. Takes as input and returns as output an array of file
--- system entry data. Note: techincally, it can be used to filter and modify
--- its elements as well.
--- Uses |MiniFiles.default_sort()| by default.
---
--- # Mappings ~
---
--- `mappings` table can be used to customize buffer-local mappings created in each
--- directory buffer for built-in actions. Entry name corresponds to the function
--- name of the action, value - right hand side of the mapping. Supply empty
--- string to not create a particular mapping.
---
--- Default mappings are mostly designed for consistent navigation experience.
--- Here are some alternatives: >
---
--- -- Close explorer after opening file with `l`
--- mappings = {
--- go_in = 'L',
--- go_in_plus = 'l',
--- }
---
--- -- Don't use `h`/`l` for easier cursor navigation during text edit
--- mappings = {
--- go_in = 'L',
--- go_in_plus = '',
--- go_out = 'H',
--- go_out_plus = '',
--- }
---
--- # Options ~
---
--- `options.use_as_default_explorer` is a boolean indicating whether this module
--- should be used as a default file explorer for editing directories (instead of
--- |netrw| by default).
---
--- `options.permanent_delete` is a boolean indicating whether to perform
--- permanent delete or move into special trash directory.
--- This is a module-specific variant of "remove to trash".
--- Target directory is 'mini.files/trash' inside standard path of Neovim data
--- directory (execute `:echo stdpath('data')` to see its path in your case).
---
--- # Windows ~
---
--- `windows.max_number` is a maximum number of windows allowed to be open
--- simultaneously. For example, use value 1 to always show single window.
--- There is no constraint by default.
---
--- `windows.preview` is a boolean indicating whether to show preview of
--- file/directory under cursor.
---
--- `windows.width_focus` and `windows.width_nofocus` are number of columns used
--- as `width` for focused and non-focused windows respectively.
MiniFiles.config = {
-- Customization of shown content
content = {
-- Predicate for which file system entries to show
filter = nil,
-- What prefix to show to the left of file system entry
prefix = nil,
-- In which order to show file system entries
sort = nil,
},
-- Module mappings created only inside explorer.
-- Use `''` (empty string) to not create one.
mappings = {
close = 'q',
go_in = 'l',
go_in_plus = 'L',
go_out = 'h',
go_out_plus = 'H',
reset = '<BS>',
reveal_cwd = '@',
show_help = 'g?',
synchronize = '=',
trim_left = '<',
trim_right = '>',
},
-- General options
options = {
-- Whether to delete permanently or move into module-specific trash
permanent_delete = true,
-- Whether to use for editing directories
use_as_default_explorer = true,
},
-- Customization of explorer windows
windows = {
-- Maximum number of windows to show side by side
max_number = math.huge,
-- Whether to show preview of file/directory under cursor
preview = false,
-- Width of focused window
width_focus = 50,
-- Width of non-focused window
width_nofocus = 15,
-- Width of preview window
width_preview = 25,
},
}
--minidoc_afterlines_end
--- Open file explorer
---
--- Common ways to use this function:
---
--- - `MiniFiles.open()` - open current working directory in a last used state.
---
--- - `MiniFiles.open(nil, false)` - fresh explorer in current working directory.
---
--- - `MiniFiles.open(vim.api.nvim_buf_get_name(0))` - open directory of current
--- file (in a last used state) with focus on that file.
---
--- - `MiniFiles.open(vim.api.nvim_buf_get_name(0), false)` - fresh explorer in
--- directory of current file.
---
--- - `MiniFiles.open(MiniFiles.get_latest_path())` - open last used `path`
--- (per tabpage). Current working directory for the first time.
---
---@param path string|nil A valid file system path used as anchor.
--- If it is a path to directory, used directly.
--- If it is a path to file, its parent directory is used as anchor while
--- explorer will focus on the supplied file.
--- Default: path of |current-directory|.
---@param use_latest boolean|nil Whether to load explorer state from history
--- (based on the supplied anchor path). Default: `true`.
---@param opts table|nil Table of options overriding |MiniFiles.config| and
--- `vim.b.minifiles_config` for this particular explorer session.
MiniFiles.open = function(path, use_latest, opts)
-- Validate path: allow only valid file system path
path = H.fs_full_path(path or vim.fn.getcwd())
local fs_type = H.fs_get_type(path)
if fs_type == nil then H.error('`path` is not a valid path ("' .. path .. '")') end
-- - Allow file path to use its parent while focusing on file
local entry_name
if fs_type == 'file' then
path, entry_name = H.fs_get_parent(path), H.fs_get_basename(path)
end
-- Validate rest of the arguments
if use_latest == nil then use_latest = true end
-- Properly close possibly opened in the tabpage explorer
local did_close = MiniFiles.close()
if did_close == false then return end
-- Get explorer to open
local explorer
if use_latest then explorer = H.explorer_path_history[path] end
explorer = explorer or H.explorer_new(path)
-- Update explorer data. Don't use current explorer's data to allow more
-- interactive config change by modifying global/local configs.
explorer.opts = H.normalize_opts(nil, opts)
explorer.target_window = vim.api.nvim_get_current_win()
-- Possibly focus on file entry
explorer = H.explorer_focus_on_entry(explorer, path, entry_name)
-- Refresh and register as opened
H.explorer_refresh(explorer)
-- Register latest used path
H.latest_paths[vim.api.nvim_get_current_tabpage()] = path
end
--- Refresh explorer
---
--- Notes:
--- - If in `opts` at least one of `content` entry is not `nil`, all directory
--- buffers are forced to update.
---
---@param opts table|nil Table of options to update.
MiniFiles.refresh = function(opts)
local explorer = H.explorer_get()
if explorer == nil then return end
-- Decide whether buffers should be forcefully updated
local content_opts = (opts or {}).content or {}
local force_update = #vim.tbl_keys(content_opts) > 0
-- Confirm refresh if there is modified buffer
if force_update then force_update = H.explorer_confirm_modified(explorer, 'buffer updates') end
-- Respect explorer local options supplied inside its `open()` call but give
-- current `opts` higher precedence
explorer.opts = H.normalize_opts(explorer.opts, opts)
H.explorer_refresh(explorer, { force_update = force_update })
end
--- Synchronize explorer
---
--- - Parse user edits in directory buffers.
--- - Convert edits to file system actions and apply them after confirmation.
--- - Update all directory buffers with the most relevant file system information.
--- Can be used without user edits to account for external file system changes.
MiniFiles.synchronize = function()
local explorer = H.explorer_get()
if explorer == nil then return end
-- Parse and apply file system operations
local fs_actions = H.explorer_compute_fs_actions(explorer)
if fs_actions ~= nil and H.fs_actions_confirm(fs_actions) then H.fs_actions_apply(fs_actions, explorer.opts) end
H.explorer_refresh(explorer, { force_update = true })
end
--- Reset explorer
---
--- - Show single window focused on anchor directory (which was used as first
--- argument for |MiniFiles.open()|).
--- - Reset all tracked directory cursors to point at first entry.
MiniFiles.reset = function()
local explorer = H.explorer_get()
if explorer == nil then return end
-- Reset branch
explorer.branch = { explorer.anchor }
explorer.depth_focus = 1
-- Reset views
for _, view in pairs(explorer.views) do
view.cursor = { 1, 0 }
end
-- Skip update cursors, as they are already set
H.explorer_refresh(explorer, { skip_update_cursor = true })
end
--- Close explorer
---
---@return boolean|nil Whether closing was done or `nil` if there was nothing to close.
MiniFiles.close = function()
local explorer = H.explorer_get()
if explorer == nil then return nil end
-- Confirm close if there is modified buffer
if not H.explorer_confirm_modified(explorer, 'close') then return false end
-- Focus on target window
explorer = H.explorer_ensure_target_window(explorer)
-- - Use `pcall()` because window might still be invalid
pcall(vim.api.nvim_set_current_win, explorer.target_window)
-- Update currently shown cursors
explorer = H.explorer_update_cursors(explorer)
-- Close shown explorer windows
for i, win_id in pairs(explorer.windows) do
H.window_close(win_id)
explorer.windows[i] = nil
end
-- Close possibly visible help window
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local buf_id = vim.api.nvim_win_get_buf(win_id)
if vim.bo[buf_id].filetype == 'minifiles-help' then vim.api.nvim_win_close(win_id, true) end
end
-- Invalidate views
for path, view in pairs(explorer.views) do
explorer.views[path] = H.view_invalidate_buffer(H.view_encode_cursor(view))
end
-- Update histories and unmark as opened
local tabpage_id, anchor = vim.api.nvim_get_current_tabpage(), explorer.anchor
H.explorer_path_history[anchor] = explorer
H.opened_explorers[tabpage_id] = nil
-- Return `true` indicating success in closing
return true
end
--- Go in entry under cursor
---
--- Depends on entry under cursor:
--- - If directory, focus on it in the window to the right.
--- - If file, open it in the window which was current during |MiniFiles.open()|.
--- Explorer is not closed after that.
MiniFiles.go_in = function()
local explorer = H.explorer_get()
if explorer == nil then return end
local cur_line = vim.fn.line('.')
explorer = H.explorer_go_in_range(explorer, vim.api.nvim_get_current_buf(), cur_line, cur_line)
H.explorer_refresh(explorer)
end
--- Go out to parent directory
---
--- - Focus on window to the left showing parent of current directory.
MiniFiles.go_out = function()
local explorer = H.explorer_get()
if explorer == nil then return end
if explorer.depth_focus == 1 then
explorer = H.explorer_open_root_parent(explorer)
else
explorer.depth_focus = explorer.depth_focus - 1
end
H.explorer_refresh(explorer)
end
--- Trim left part of branch
---
--- - Remove all branch paths to the left of currently focused one. This also
--- results into current window becoming the most left one.
MiniFiles.trim_left = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_trim_branch_left(explorer)
H.explorer_refresh(explorer)
end
--- Trim right part of branch
---
--- - Remove all branch paths to the right of currently focused one. This also
--- results into current window becoming the most right one.
MiniFiles.trim_right = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_trim_branch_right(explorer)
H.explorer_refresh(explorer)
end
--- Reveal current working directory
---
--- - Prepend branch with parent paths until current working directory is reached.
--- Do nothing if not inside it.
MiniFiles.reveal_cwd = function()
local explorer = H.explorer_get()
if explorer == nil then return end
local cwd = H.fs_full_path(vim.fn.getcwd())
local cwd_ancestor_pattern = string.format('^%s/.', vim.pesc(cwd))
while explorer.branch[1]:find(cwd_ancestor_pattern) ~= nil do
-- Add parent to branch
local parent, name = H.fs_get_parent(explorer.branch[1]), H.fs_get_basename(explorer.branch[1])
table.insert(explorer.branch, 1, parent)
explorer.depth_focus = explorer.depth_focus + 1
-- Set cursor on child entry
local parent_view = explorer.views[parent] or {}
parent_view.cursor = name
explorer.views[parent] = parent_view
end
H.explorer_refresh(explorer)
end
--- Show help window
---
--- - Open window with helpful information about currently shown explorer and
--- focus on it. To close it, press `q`.
MiniFiles.show_help = function()
local explorer = H.explorer_get()
if explorer == nil then return end
local buf_id = vim.api.nvim_get_current_buf()
if not H.is_opened_buffer(buf_id) then return end
H.explorer_show_help(buf_id, vim.api.nvim_get_current_win())
end
--- Get file system entry data
---
---@param buf_id number|nil Buffer identifier of valid directory buffer.
--- Default: current buffer.
---@param line number|nil Line number of entry for which to return information.
--- Default: cursor line.
---
---@return table|nil Table of file system entry data with the following fields:
--- __minifiles_fs_entry_data_fields
---
--- Returns `nil` if there is no proper file system entry path at the line.
MiniFiles.get_fs_entry = function(buf_id, line)
buf_id = H.validate_opened_buffer(buf_id)
line = H.validate_line(buf_id, line)
local path_id = H.match_line_path_id(H.get_bufline(buf_id, line))
if path_id == nil then return nil end
local path = H.path_index[path_id]
return { fs_type = H.fs_get_type(path), name = H.fs_get_basename(path), path = path }
end
--- Get target window
---
---@return number|nil Window identifier inside which file will be opened or
--- `nil` if no explorer is opened.
MiniFiles.get_target_window = function()
local explorer = H.explorer_get()
if explorer == nil then return end
explorer = H.explorer_ensure_target_window(explorer)
return explorer.target_window
end
--- Set target window
---
---@param win_id number Window identifier inside which file will be opened.
MiniFiles.set_target_window = function(win_id)
if not H.is_valid_win(win_id) then H.error('`win_id` should be valid window identifier.') end
local explorer = H.explorer_get()
if explorer == nil then return end
explorer.target_window = win_id
end
--- Get latest used anchor path
---
--- Note: if latest used `path` argument for |MiniFiles.open()| was for file,
--- this will return its parent (as it was used as anchor path).
MiniFiles.get_latest_path = function() return H.latest_paths[vim.api.nvim_get_current_tabpage()] end
--- Default filter of file system entries
---
--- Currently does not filter anything out.
---
---@param fs_entry table Table with the following fields:
--- __minifiles_fs_entry_data_fields
---
---@return boolean Always `true`.
MiniFiles.default_filter = function(fs_entry) return true end
--- Default prefix of file system entries
---
--- - For directory return fixed icon and 'MiniFilesDirectory' highlight group.
--- - For file try to use `get_icon()` from 'nvim-tree/nvim-web-devicons'.
--- If missing, return fixed icon and 'MiniFilesFile' highlight group.
---
---@param fs_entry table Table with the following fields:
--- __minifiles_fs_entry_data_fields
---
---@return ... Icon and highlight group name. For more details, see |MiniFiles.config|
--- and |MiniFiles-examples|.
MiniFiles.default_prefix = function(fs_entry)
if fs_entry.fs_type == 'directory' then return ' ', 'MiniFilesDirectory' end
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
if not has_devicons then return ' ', 'MiniFilesFile' end
local icon, hl = devicons.get_icon(fs_entry.name, nil, { default = false })
return (icon or '') .. ' ', hl or 'MiniFilesFile'
end
--- Default sort of file system entries
---
--- Sort directories and files separately (alphabetically ignoring case) and
--- put directories first.
---
---@param fs_entries table Array of file system entry data.
--- Each one is a table with the following fields:
--- __minifiles_fs_entry_data_fields
---
---@return table Sorted array of file system entries.
MiniFiles.default_sort = function(fs_entries)
-- Sort ignoring case
local res = vim.tbl_map(
0