Explore as a tree, edit as a buffer โ a file explorer for Neovim that combines hierarchical navigation with buffer-native file operations.
| Split | Replace |
|---|---|
![]() |
![]() |
- โ๏ธ Buffer-native editing meets tree view โ Edit the buffer to rename, delete, create, and move files, then
:wto apply. Combines oil.nvim's buffer-editing paradigm with a full collapsible tree view - โก Progressive async rendering โ The target file's ancestor chain is scanned first, so the cursor lands instantly even in large repositories. Remaining directories load in the background
- ๐งฉ Extensible action system โ Every operation lives in a named registry. Custom actions receive the same
ActionContextas built-in ones, making them first-class citizens - ๐จ Rich customization โ 60+ highlight groups across 6 categories, function-based config options (
header.format,ignore_patterns,preview.max_file_size), and event hooks for plugin integration
For architecture and design decisions, see ARCHITECTURE.md.
- Buffer-native editing โ Rename, delete, and create files by editing the buffer, then
:wto apply - Tree view with hierarchy โ Collapsible directory tree, not flat per-directory listing
- Progressive async rendering โ Ancestor chain scanned first for instant cursor placement
- Git integration โ Async status detection with visual indicators
- Multiple layouts โ
float,split_left,split_right,replace - Extensible action system โ Named registry with custom actions as first-class citizens
- netrw replacement โ
hijack_netrwoption for seamless default browsing - 60+ highlight groups โ Full appearance customization across 6 categories
- Event hooks โ
EdaTreeOpen,EdaMutationPost, etc. for plugin integration
- Neovim >= 0.11
- git (optional, for git status integration)
- mini.icons or nvim-web-devicons (optional, for file icons)
lazy.nvim
{
"wadackel/eda.nvim",
opts = {},
}mini.deps
local add = MiniDeps.add
add("wadackel/eda.nvim")
require("eda").setup()packer.nvim
use({
"wadackel/eda.nvim",
config = function()
require("eda").setup()
end,
})require("eda").setup()Open the explorer with the :Eda command:
:Eda " Open in current directory (float)
:Eda kind=split_left " Open as left sidebar
:Eda ~/projects " Open specific directoryTip
Set hijack_netrw = true to use eda as the default directory browser. See the Replace netrw recipe for details.
See Configuration below for all options, or :help eda.nvim for the full reference.
Below are all available options with their default values. You only need to specify the options you want to change โ everything is deep-merged with the defaults.
require("eda").setup({
-- Markers used to detect the project root
root_markers = { ".git", ".hg" },
-- Show hidden/dotfiles by default
show_hidden = true,
-- Show git-ignored files by default
show_gitignored = true,
-- Show only files with git changes (toggle via `gs`, default off)
show_only_git_changes = false,
-- Lua patterns matched against file/directory name (not glob)
-- Accepts a function: fun(root_path): string[]
ignore_patterns = {},
window = {
-- Layout: "float", "split_left", "split_right", "replace"
kind = "float",
-- Border style (see :help nvim_open_win)
border = "rounded",
-- Per-kind window dimensions (string percentage or number or function)
kinds = {
float = { width = "94%", height = "80%" },
replace = {},
split_left = { width = "30%" },
split_right = { width = "30%" },
},
-- Buffer-local options applied to the eda buffer
buf_opts = {
filetype = "eda",
buftype = "acwrite",
},
-- Window-local options applied to the eda window
win_opts = {
number = false,
relativenumber = false,
wrap = false,
signcolumn = "no",
cursorline = true,
foldcolumn = "0",
},
},
-- Use eda as the default directory browser (replaces netrw)
hijack_netrw = false,
-- Close explorer window after selecting a file
close_on_select = false,
-- Confirmation dialogs (boolean or table; true = all defaults below)
confirm = {
-- Confirm before deleting files
delete = true,
-- Confirm moves: true, false, or "overwrite_only"
move = "overwrite_only",
-- Confirm creation: true, false, or integer (threshold count)
create = false,
-- Path display in confirm dialogs: "full", "short", "minimal", or fun(path, root_path): string
path_format = "short",
-- Signs shown in confirm dialogs
signs = {
create = "", -- nf-oct-plus
delete = "", -- nf-oct-circle_slash
move = "", -- nf-oct-arrow_right
},
},
-- Use trash instead of permanent delete
delete_to_trash = true,
-- Follow symbolic links when scanning
follow_symlinks = true,
-- Directories with more entries than this skip sorting for performance
large_dir_threshold = 5000,
-- Maximum depth for initial directory expansion
expand_depth = 5,
-- Automatically reveal the focused file in the tree
update_focused_file = {
-- Enable auto-reveal
enable = false,
-- Also change the tree root to the file's project root
update_root = false,
},
icon = {
-- Separator between icon and file name
separator = " ",
-- Icon provider: "mini_icons", "nvim_web_devicons", or "none"
provider = "mini_icons",
-- Directory glyphs keyed by open/empty state
directory = {
collapsed = "๓ฐ",
expanded = "๓ฐฐ",
empty = "๓ฐ",
empty_open = "๓ฐท",
},
-- Optional hook to override icons per node. Returning nil falls through
-- to the built-in directory glyphs and the provider lookup.
-- See `doc/eda.md` for full reference.
--
-- custom = function(name, node)
-- if name == "justfile" then return "๓ฑ", "EdaFileIcon" end
-- return nil
-- end,
custom = nil,
},
git = {
-- Enable git status integration
enabled = true,
-- Git status icons
icons = {
untracked = "", -- nf-oct-question
added = "", -- nf-oct-plus
modified = "", -- nf-oct-diff
deleted = "", -- nf-oct-circle_slash
renamed = "", -- nf-oct-arrow_right
staged = "", -- nf-oct-check
conflict = "", -- nf-oct-alert
ignored = "โ",
},
},
indent = {
-- Indentation width per nesting level
width = 2,
},
preview = {
-- Enable file preview panel
enabled = false,
-- Debounce delay in milliseconds before showing preview
debounce = 100,
-- Maximum file size in bytes to preview (also accepts fun(path): integer)
max_file_size = 102400,
},
-- Show full filename in a floating window when truncated in narrow windows
full_name = {
-- Enable floating window for truncated filenames
enabled = true,
},
-- Header displayed above the tree (set to false to disable entirely)
header = {
-- Format: "short", or fun(root_path): string|false
format = "short",
-- Position: "left", "center", "right"
position = "left",
-- Show a divider line below the header
divider = false,
},
-- Set default_mappings = false to clear all defaults before applying yours
-- Key mappings: string = built-in action, function = custom, false = disable
mappings = {
["<CR>"] = "select", -- Open file or toggle directory
["<2-LeftMouse>"] = "select", -- Open file or toggle directory
["<C-t>"] = "select_tab", -- Open file in new tab
["|"] = "select_vsplit", -- Open file in vertical split
["-"] = "select_split", -- Open file in horizontal split
["q"] = "close", -- Close explorer
["^"] = "parent", -- Navigate to parent directory
["~"] = "cwd", -- Change root to cwd
["gC"] = "cd", -- Change root to directory
["W"] = "collapse_recursive", -- Collapse directory recursively
["E"] = "expand_recursive", -- Expand directory recursively
["gW"] = "collapse_all", -- Collapse all directories
["gE"] = "expand_all", -- Expand all directories
["yp"] = "yank_path", -- Yank relative path
["yP"] = "yank_path_absolute", -- Yank absolute path
["yn"] = "yank_name", -- Yank file name
["<C-l>"] = "refresh", -- Refresh file tree
["<C-h>"] = "collapse_node", -- Collapse node or go to parent
["g."] = "toggle_hidden", -- Toggle hidden files
["gi"] = "toggle_gitignored", -- Toggle gitignored files
["gs"] = "toggle_git_changes", -- Toggle git-changes filter
["[c"] = "prev_git_change", -- Jump to previous git change
["]c"] = "next_git_change", -- Jump to next git change
["m"] = "mark_toggle", -- Toggle mark on node
["D"] = "mark_bulk_delete", -- Delete marked nodes
["go"] = "system_open", -- Open with system application
["K"] = "inspect", -- Inspect node data
["gd"] = "duplicate", -- Duplicate file
["gx"] = "cut", -- Cut selected nodes
["gy"] = "copy", -- Copy selected nodes
["gp"] = "paste", -- Paste from register
["g?"] = "help", -- Show keymap help
["ga"] = "actions", -- Open action picker
["<C-f>"] = "preview_scroll_down", -- Scroll preview down (half page)
["<C-b>"] = "preview_scroll_up", -- Scroll preview up (half page)
["<C-w>v"] = "split", -- Open split pane
["<C-w>s"] = "vsplit", -- Open horizontal split pane
},
-- Callback to customize highlight groups: fun(groups: table)
on_highlight = nil,
-- Window picker function for file selection: fun(): integer?
select_window = nil,
})Tip
See :help eda.nvim for detailed descriptions of each option, available actions, events, and highlight groups.
Common customization patterns. See :help eda.nvim for the full configuration reference.
Replace netrw
Use eda.nvim as the default directory browser. :edit <directory>, :Explore, and other netrw entry points will open eda instead.
require("eda").setup({
hijack_netrw = true,
})LSP file operations
Notify language servers when files are renamed or moved via the EdaMutationPost event. Works with nvim-lsp-file-operations or a manual handler.
vim.api.nvim_create_autocmd("User", {
pattern = "EdaMutationPost",
callback = function(ev)
-- ev.data.operations contains { type, src, dst } entries
-- ev.data.results contains the operation outcomes
local ok, lsp_ops = pcall(require, "lsp-file-operations")
if ok then
lsp_ops.did_rename(ev.data.operations)
end
end,
})Window picker integration
Use nvim-window-picker (or any picker that returns a window ID) to choose where files open.
require("eda").setup({
select_window = function()
return require("window-picker").pick_window()
end,
})Custom header with git branch
Show the current git branch in the header instead of the directory path.
require("eda").setup({
header = {
format = function(root_path)
local result = vim.system(
{ "git", "-C", root_path, "branch", "--show-current" },
{ text = true }
):wait()
if result.code == 0 and result.stdout ~= "" then
return result.stdout:gsub("\n", "")
end
return vim.fn.fnamemodify(root_path, ":~")
end,
position = "left",
},
})Project-aware ignore patterns
Dynamically filter files based on project type. Patterns use Lua pattern syntax (not glob).
require("eda").setup({
ignore_patterns = function(root_path)
local patterns = { "%.DS_Store$" }
if vim.uv.fs_stat(root_path .. "/package.json") then
table.insert(patterns, "^node_modules$")
end
if vim.uv.fs_stat(root_path .. "/Cargo.toml") then
table.insert(patterns, "^target$")
end
return patterns
end,
})Customize highlights
Override highlight groups to match your colorscheme. The on_highlight callback receives the groups table before it is applied โ modify entries in-place.
require("eda").setup({
on_highlight = function(groups)
groups.EdaDirectoryName = { fg = "#89b4fa", bold = true }
groups.EdaDirectoryIcon = { fg = "#89b4fa" }
-- Apply git status colors to file names (transparent by default)
groups.EdaGitModifiedName = { link = "EdaGitModified" }
groups.EdaGitAddedName = { link = "EdaGitAdded" }
end,
})Customize icons
Combine icon.provider, icon.directory, and the icon.custom hook to fully control every icon. This example builds a minimal UI with plain Unicode characters โ no Nerd Font required.
require("eda").setup({
icon = {
provider = "none",
directory = {
collapsed = "โธ",
expanded = "โพ",
empty = "โธ",
empty_open = "โพ",
},
custom = function(name, node)
if node.type == "directory" then
return nil
end
return "ยท", "EdaFileIcon"
end,
},
})Register custom actions
Add project-specific actions to the registry. They appear in the action picker (ga) and can be mapped to keys.
local action = require("eda.action")
action.register("open_terminal", function(ctx)
local node = ctx.buffer:get_cursor_node(ctx.window.winid)
local dir = node and node.is_dir and node.path
or node and vim.fn.fnamemodify(node.path, ":h")
or ctx.explorer.root_path
vim.cmd("split | terminal")
vim.fn.chansend(vim.b.terminal_job_id, "cd " .. vim.fn.shellescape(dir) .. "\n")
end, { desc = "Open terminal in directory" })
-- Map it
require("eda").setup({
mappings = {
["<C-\\>"] = "open_terminal",
},
}):help eda.nvimโ Full reference (configuration, actions, API, events, highlights)- CHANGELOG.md โ Release history
- ARCHITECTURE.md โ Architecture, design philosophy, and trade-offs
- CONTRIBUTING.md โ Development setup and guidelines
Contributions are welcome! See CONTRIBUTING.md for development setup and guidelines.





