Skip to content
Rocky Zhang edited this page Jun 18, 2022 · 49 revisions

Welcome to the neo-tree.nvim wiki!

This is a place to share configuration recipes for functionality that others may want to copy or learn from.

Commands

Open with System Viewer

Thanks to @wavded for this recipe! This command will allow you to open a file or directory using the OS default viewer. This example is for macOS, but you should be able to adapt it to another OS easily:

require("neo-tree").setup({
  filesystem = {
    window = {
      mappings = {
        ["o"] = "system_open",
      },
    },
    commands = {
      system_open = function(state)
        local node = state.tree:get_node()
        local path = node:get_id()
        -- macOs specific -- open file in default application in the background
        vim.api.nvim_command("silent !open -g " .. path)
      end,
    },
  },
})

Open and Clear Search

Sometimes you may want to add a filter and leave that way because the filter captures the files you want to work on now. Other times you may just be using the filter as a quick way to find a file you want to open. In the latter case, you may want to clear the search when you open that file. This custom command offers that choice:

      require("neo-tree").setup({
        popup_border_style = "NC",
        filesystem = {
          window = {
            mappings = {
              ["o"] = "open_and_clear_filter"
            },
          },
          commands = {
            open_and_clear_filter = function (state)
              local node = state.tree:get_node()
              if node and node.type == "file" then
                local file_path = node:get_id()
                -- reuse built-in commands to open and clear filter
                local cmds = require("neo-tree.sources.filesystem.commands")
                cmds.open(state)
                cmds.clear_filter(state)
                -- reveal the selected file without focusing the tree
                require("neo-tree.sources.filesystem").navigate(state, state.path, file_path)
              end
            end,
          }
        }
      })

Run Command

Similar to the '.' command in nvim-tree. Primes the ":" command with the full path of the chosen node.

require("neo-tree").setup({
  filesystem = {
    window = {
      mappings = {
        ["i"] = "run_command",
      },
    },
    commands = {
      run_command = function(state)
        local node = state.tree:get_node()
        local path = node:get_id()
        vim.api.nvim_input(": " .. path .. "<Home>")
      end,
    },
  },
})

Find with telescope

Find/grep for a file under the current node using Telescope and select it.

local function getTelescopeOpts(state, path)
  return {
    cwd = path,
    search_dirs = { path },
    attach_mappings = function (prompt_bufnr, map)
      local actions = require "telescope.actions"
      actions.select_default:replace(function()
        actions.close(prompt_bufnr)
        local action_state = require "telescope.actions.state"
        local selection = action_state.get_selected_entry()
        local filename = selection.filename
        if (filename == nil) then
          filename = selection[1]
        end
        -- any way to open the file without triggering auto-close event of neo-tree?
        require("neo-tree.sources.filesystem").navigate(state, state.path, filename)
      end)
      return true
    end
  }
end
require("neo-tree").setup({
  filesystem = {
    window = {
      mappings = {
        ["tf"] = "telescope_find",
        ["tg"] = "telescope_grep",
      },
    },
    commands = {
      telescope_find = function(state)
        local node = state.tree:get_node()
        local path = node:get_id()
        require('telescope.builtin').find_files(getTelescopeOpts(state, path))
      end,
      telescope_grep = function(state)
        local node = state.tree:get_node()
        local path = node:get_id()
        require('telescope.builtin').live_grep(getTelescopeOpts(state, path))
      end,
    },
  },
})

Trash (macOS)

Move the current item or all selections to the Trash bin and support "put back" feature.

Requirement: brew install trash

-- Trash the target
local function trash(state)
  local tree = state.tree
  local node = tree:get_node()
  if node.type == "message" then
    return
  end
  local _, name = utils.split_path(node.path)
  local msg = string.format("Are you sure you want to trash '%s'?", name)
  inputs.confirm(msg, function(confirmed)
    if not confirmed then
      return
    end
    vim.api.nvim_command("silent !trash -F " .. node.path)
    cmds.refresh(state)
  end)
end

-- Trash the selections (visual mode)
local function trash_visual(state, selected_nodes)
  local paths_to_trash = {}
  for _, node in ipairs(selected_nodes) do
    if node.type ~= 'message' then
      table.insert(paths_to_trash, node.path)
    end
  end
  local msg = "Are you sure you want to trash " .. #paths_to_trash .. " items?"
  inputs.confirm(msg, function(confirmed)
    if not confirmed then
      return
    end
    for _, path in ipairs(paths_to_trash) do
      vim.api.nvim_command("silent !trash -F " .. path)
    end
    cmds.refresh(state)
  end)
end

Components

Components are the blocks of text that get rendered for an item in the tree. Built-in components include things like "icon", "name", or "git_status". Adding a custom component involves two steps: defining a function to implement that component, and then using that component in a renderer.

Custom Icons

If you want to override any built-in component, just add a component in your config with the same name. The example below is copied from the default icon component, which you can override to add your own special handling.

If you want to add custom icons based on a file or directory name, you can access a node's name with node.name, and the full path with node.get_id() or node.path.

      local highlights = require("neo-tree.ui.highlights")

      require("neo-tree").setup({
        filesystem = {
          components = {
            icon = function(config, node, state)
              local icon = config.default or " "
              local padding = config.padding or " "
              local highlight = config.highlight or highlights.FILE_ICON

              if node.type == "directory" then
                highlight = highlights.DIRECTORY_ICON
                if node:is_expanded() then
                  icon = config.folder_open or "-"
                else
                  icon = config.folder_closed or "+"
                end
              elseif node.type == "file" then
                local success, web_devicons = pcall(require, "nvim-web-devicons")
                if success then
                  local devicon, hl = web_devicons.get_icon(node.name, node.ext)
                  icon = devicon or icon
                  highlight = hl or highlight
                end
              end

              return {
                text = icon .. padding,
                highlight = highlight,
              }
            end,
      })

Harpoon Index

Neo-tree with harpoon index

This example adds the index number of a file that has been marked with Harpoon:

      require("neo-tree").setup({
        filesystem = {
          components = {
            harpoon_index = function(config, node, state)
              local Marked = require("harpoon.mark")
              local path = node:get_id()
              local succuss, index = pcall(Marked.get_index_of, path)
              if succuss and index and index > 0 then
                return {
                  text = string.format(" ⥤ %d", index), -- <-- Add your favorite harpoon like arrow here
                  highlight = config.highlight or "NeoTreeDirectoryIcon",
                }
              else
                return {}
              end
            end
          },
          renderers = {
           file = {
             {"icon"},
             {"name", use_git_status_colors = true},
             {"harpoon_index"}, --> This is what actually adds the component in where you want it
             {"diagnostics"},
             {"git_status", highlight = "NeoTreeDimText"},
           }
          }
        },
      })

Events

Auto Close on Open File

This example uses the file_open event to close the Neo-tree window when a file is opened. This applies to all windows and all sources at once.

      require("neo-tree").setup({
        event_handlers = {

          {
            event = "file_opened",
            handler = function(file_path)
              --auto close
              require("neo-tree").close_all()
            end
          },

        }
      })

Clear Search after Opening File

NOTE: This is no longer necessary as of v1.28. You can now use the "fuzzy_finder" command instead, which will clear the search after a file is opened.

If you want to use the search feature as a fuzzy finder rather than a sticky filter, you may want to clear the search as soon as a file is chosen. One way to handle that was shown above with a custom command, but you could also handle that in a more universal way by handling the "file_opened" event:

      require("neo-tree").setup({
        event_handlers = {

          {
            event = "file_opened",
            handler = function(file_path)
              require("neo-tree.sources.filesystem").reset_search(state)
            end
          },

        }
      })

Handle Rename or Move File Event

If you want to take some custom action after a file has been renamed, you can handle the "file_renamed" in your config and add your code to the handler. The most obvious use for this would be to cleanup references to that file within your project.

require("neo-tree").setup({
  events = {
    {
      event = "file_renamed",
      handler = function(args)
        -- fix references to file
        print(args.source, " renamed to ", args.destination)
      end
    },
    {
      event = "file_moved",
      handler = function(args)
        -- fix references to file
        print(args.source, " moved to ", args.destination)
      end
    },
  }
})

Custom Window Chooser for File Open commands

If you want to change the logic for where to open a file when using the built-in "open", "open_split", and "open_vsplit" commands, the way to do that is by handling the "file_open_requested" event. Below is an example which includes the default logic used by Neo-tree.

NOTE: If you do open the file successfully, you must return { handled = true } to prevent the next handler from opening the file again. If there are situations where you do want to pass it back to the built-in logic to be handled, return { handled = false }.

"neo-tree").setup({
  event_handlers = {
    {
      event = "file_open_requested",
      handler = function(args)
        local state = args.state
        local path = args.path
        local open_cmd = args.open_cmd or "edit"

        -- use last window if possible
        local suitable_window_found = false
        local nt = require("neo-tree")
        if nt.config.open_files_in_last_window then
          local prior_window = nt.get_prior_window()
          if prior_window > 0 then
            local success = pcall(vim.api.nvim_set_current_win, prior_window)
            if success then
              suitable_window_found = true
            end
          end
        end

        -- find a suitable window to open the file in
        if not suitable_window_found then
          if state.window.position == "right" then
            vim.cmd("wincmd t")
          else
            vim.cmd("wincmd w")
          end
        end
        local attempts = 0
        while attempts < 4 and vim.bo.filetype == "neo-tree" do
          attempts = attempts + 1
          vim.cmd("wincmd w")
        end
        if vim.bo.filetype == "neo-tree" then
          -- Neo-tree must be the only window, restore it's status as a sidebar
          local winid = vim.api.nvim_get_current_win()
          local width = require("neo-tree.utils").get_value(state, "window.width", 40)
          vim.cmd("vsplit " .. path)
          vim.api.nvim_win_set_width(winid, width)
        else
          vim.cmd(open_cmd .. " " .. path)
        end

        -- If you don't return this, it will proceed to open the file using built-in logic.
        return { handled = true }
      end
    },
  },
})
Clone this wiki locally