Explore as a tree, edit as a buffer β a file explorer for Neovim that combines hierarchical navigation with buffer-native file operations.
| File Preview | Directory Preview |
|---|---|
![]() |
![]() |
In the replace layout the preview is drawn as an overlay inside the explorer's
own window, so no split is created and neighbouring windows keep their sizes.
Each explorer owns its preview, so two of them can sit side by side with a
different preview in each.
| Replace Overlay | Two Explorers |
|---|---|
![]() |
![]() |
| Git Changes Filter | Inspect Float |
|---|---|
![]() |
![]() |
| Split | Replace |
|---|---|
![]() |
![]() |
- βοΈ Buffer-native editing meets tree view β Edit the buffer to rename, delete, create, and move files, then
:wto apply - β‘ Async filesystem scanning β The requested target's ancestor chain is scanned before the initial render. Directory enumeration and symlink metadata use asynchronous I/O
- π§© 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 β Highlight groups, 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 β Expand and collapse directories within one buffer
- Async filesystem scanning β Directory enumeration and symlink resolution run asynchronously; tree preparation and painting run on the main loop
- Git integration β Async status detection with visual indicators
- Image preview β PNG/JPEG/GIF/WebP/BMP rendered in the preview pane via the Kitty graphics protocol (kitty, Ghostty, WezTerm, including inside tmux)
- 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 - Highlight groups β Customize the tree, filesystem state, operations, dialogs, and previews
- Event hooks β
EdaTreeOpen,EdaTreeClose,EdaMutationPre,EdaMutationPost,EdaRootChangedfor plugin integration. Seedoc/eda.nvim.txtfor event payload details.
- Neovim >= 0.11
- git (optional, for git status integration)
- mini.icons or nvim-web-devicons (optional, for file icons)
- A terminal that implements the Kitty graphics protocol (optional, for image preview; verified with kitty, Ghostty, and WezTerm, detected through the protocol's own capability query so other implementations work too)
- ImageMagick
magick(optional, for previewing image formats other than PNG and for downscaling large PNGs)
Deletion uses system trash by default: Finder through osascript on macOS,
or trash-cli's trash-put on other
platforms. If the backend is missing or fails, eda reports an error and never
falls back to permanent deletion. Run :checkhealth eda to check availability,
or explicitly set delete_to_trash = false to permanently delete files.
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()If you do not use an icon plugin, pass icon = { provider = "none" } to 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 for a customization example, or the full reference for all options.
Pass only the options you want to change; they are merged with the defaults. For example, use a sidebar with file preview and a mapping to toggle the preview:
require("eda").setup({
window = { kind = "split_left" },
preview = { enabled = true },
mappings = {
["<C-p>"] = "toggle_preview",
},
})See the configuration reference for every option and its defaults, and mapping configuration for custom keys.
Built-in and custom actions can be bound through mappings or dispatched
programmatically. Press ga to choose from registered actions, or g? to view
keybinding help.
The action reference covers every built-in action, including file-operation target selection. See default keybindings for the complete mapping table.
Register a function under a name, then map it like any built-in. Custom actions also appear in the actions picker, so they remain discoverable without a dedicated keymap.
local action = require("eda.action")
action.register("my_action", function(ctx)
local node = ctx.buffer:get_cursor_node(ctx.window.winid)
if node then
vim.notify("Selected: " .. node.path)
end
end, { desc = "Show selected file path" })
require("eda").setup({
mappings = {
["<C-x>"] = "my_action",
},
})See the Action API for registration and dispatch parameters, and ActionContext for the handles passed to each action.
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.type == "directory" 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" })
require("eda").setup({
mappings = {
["<C-\\>"] = "open_terminal",
},
})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
Use the native LSP rename-notification recipe to notify supporting servers about successfully completed moves, including partially failed batches. It documents capability setup, workspace and file filters, and a live-server smoke check. This post-operation handler does not request pre-rename workspace edits or guarantee updated imports.
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,
},
})The maintained reference is doc/eda.md, also available in Neovim as
:help eda.nvim.
- Configuration and default keybindings
- Actions and API
- Events and highlight groups
- 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.










