Skip to content

Latest commit

Β 

History

83 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌿 eda.nvim

Explore as a tree, edit as a buffer β€” a file explorer for Neovim that combines hierarchical navigation with buffer-native file operations.

CI Neovim License: MIT

Demo

Tree View

Tree View

Buffer Editing

Buffer Editing

Split Operation

Split Operation

Preview

File Preview Directory Preview
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
Replace Overlay Two Explorers

Filter & Inspect

Git Changes Filter Inspect Float
Git Changes Filter Inspect Float

Layouts

Split Replace
Split Replace

Why eda.nvim?

  • ✏️ Buffer-native editing meets tree view β€” Edit the buffer to rename, delete, create, and move files, then :w to 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 ActionContext as 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.

Features

  • Buffer-native editing β€” Rename, delete, and create files by editing the buffer, then :w to 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_netrw option for seamless default browsing
  • Highlight groups β€” Customize the tree, filesystem state, operations, dialogs, and previews
  • Event hooks β€” EdaTreeOpen, EdaTreeClose, EdaMutationPre, EdaMutationPost, EdaRootChanged for plugin integration. See doc/eda.nvim.txt for event payload details.

Requirements

  • 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.

Installation

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,
})

Quick Start

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 directory

Tip

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.

Configuration

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.

Actions

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.

Defining Custom Actions

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.

Example: open a terminal in the directory under the cursor

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",
  },
})

Recipes

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,
  },
})

Documentation

The maintained reference is doc/eda.md, also available in Neovim as :help eda.nvim.

Contributing

Contributions are welcome! See CONTRIBUTING.md for development setup and guidelines.

License

MIT Β© wadackel

About

🌿 Explore as a tree, edit as a buffer β€” a file explorer for Neovim that combines hierarchical navigation with buffer-native file operations.

Topics

Resources

Code of conduct

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages