Skip to content

wadackel/eda.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

9 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

๐ŸŒฟ 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

Git Changes Filter

Git Changes Filter

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. 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 ActionContext as 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.

Features

  • Buffer-native editing โ€” Rename, delete, and create files by editing the buffer, then :w to 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_netrw option for seamless default browsing
  • 60+ highlight groups โ€” Full appearance customization across 6 categories
  • Event hooks โ€” EdaTreeOpen, EdaMutationPost, etc. for plugin integration

Requirements

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()

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 below for all options, or :help eda.nvim for the full reference.

Configuration

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.

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

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

Documentation

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

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

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Contributors

Languages