Code Monkey home page Code Monkey logo

neo-tree.nvim's Introduction

Neo-tree.nvim

Neo-tree is a Neovim plugin to browse the file system and other tree like structures in whatever style suits you, including sidebars, floating windows, netrw split style, or all of them at once!

Neo-tree filesystem as Sidebar:

This screenshot shows Neo-tree opened in the traditional sidebar layout:

Neo-tree file system sidebar

Neo-tree filesystem Netrw Style

The below screenshot shows Neo-tree opened "netrw style" (:Neotree position=current). When opened in this way, there is more room so the extra detail columns can be shown. This screenshot also shows how the contents can be sorted on any column. In this example, we are sorted on "Size" descending:

Neo-tree file system details

Breaking Changes BAD ๐Ÿ’ฃ ๐Ÿ‘ฟ

The biggest and most important feature of Neo-tree is that we will never knowingly push a breaking change and interrupt your day. Bugs happen, but breaking changes can always be avoided. When breaking changes are needed, there will be a new branch that you can opt into, when it is a good time for you.

See What is a Breaking Change? for details.

See Changelog 3.0 for breaking changes and deprecations in 3.0.

User Experience GOOD ๐Ÿ™‚ ๐Ÿ‘

Aside from being polite about breaking changes, Neo-tree is also focused on the little details of user experience. Everything should work exactly as you would expect a sidebar to work without all of the glitchy behavior that is normally accepted in (neo)vim sidebars. I can't stand glitchy behavior, and neither should you!

  • Neo-tree won't let other buffers take over its window.
  • Neo-tree won't leave its window scrolled to the last line when there is plenty of room to display the whole tree.
  • Neo-tree does not need to be manually refreshed (set use_libuv_file_watcher=true)
  • Neo-tree can intelligently follow the current file (set follow_current_file.enabled=true)
  • Neo-tree is thoughtful about maintaining or setting focus on the right node
  • Neo-tree windows in different tabs are completely separate
  • respect_gitignore actually works!

Neo-tree is smooth, efficient, stable, and pays attention to the little details. If you find anything janky, wanky, broken, or unintuitive, please open an issue so we can fix it.

Minimal Quickstart

Minimal Example for Lazy:

{
    "nvim-neo-tree/neo-tree.nvim",
    branch = "v3.x",
    dependencies = {
      "nvim-lua/plenary.nvim",
      "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
      "MunifTanjim/nui.nvim",
      -- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information
    }
}

Minimal Example for Packer:

use {
  "nvim-neo-tree/neo-tree.nvim",
    branch = "v3.x",
    requires = { 
      "nvim-lua/plenary.nvim",
      "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
      "MunifTanjim/nui.nvim",
      -- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information
    }
  }

After installing, run:

:Neotree

Press ? in the Neo-tree window to view the list of mappings.

Quickstart

Longer Example for Packer:

use {
  "nvim-neo-tree/neo-tree.nvim",
    branch = "v3.x",
    requires = { 
      "nvim-lua/plenary.nvim",
      "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
      "MunifTanjim/nui.nvim",
      -- "3rd/image.nvim", -- Optional image support in preview window: See `# Preview Mode` for more information
      {
        's1n7ax/nvim-window-picker',
        version = '2.*',
        config = function()
            require 'window-picker'.setup({
                filter_rules = {
                    include_current_win = false,
                    autoselect_one = true,
                    -- filter using buffer options
                    bo = {
                        -- if the file type is one of following, the window will be ignored
                        filetype = { 'neo-tree', "neo-tree-popup", "notify" },
                        -- if the buffer type is one of following, the window will be ignored
                        buftype = { 'terminal', "quickfix" },
                    },
            },
        })
        end,
      },
    },
    config = function ()
      -- If you want icons for diagnostic errors, you'll need to define them somewhere:
      vim.fn.sign_define("DiagnosticSignError",
        {text = "๏— ", texthl = "DiagnosticSignError"})
      vim.fn.sign_define("DiagnosticSignWarn",
        {text = "๏ฑ ", texthl = "DiagnosticSignWarn"})
      vim.fn.sign_define("DiagnosticSignInfo",
        {text = "๏š ", texthl = "DiagnosticSignInfo"})
      vim.fn.sign_define("DiagnosticSignHint",
        {text = "๓ฐŒต", texthl = "DiagnosticSignHint"})

      require("neo-tree").setup({
        close_if_last_window = false, -- Close Neo-tree if it is the last window left in the tab
        popup_border_style = "rounded",
        enable_git_status = true,
        enable_diagnostics = true,
        enable_normal_mode_for_inputs = false, -- Enable normal mode for input dialogs.
        open_files_do_not_replace_types = { "terminal", "trouble", "qf" }, -- when opening files, do not use windows containing these filetypes or buftypes
        sort_case_insensitive = false, -- used when sorting files and directories in the tree
        sort_function = nil , -- use a custom function for sorting files and directories in the tree 
        -- sort_function = function (a,b)
        --       if a.type == b.type then
        --           return a.path > b.path
        --       else
        --           return a.type > b.type
        --       end
        --   end , -- this sorts files and directories descendantly
        default_component_configs = {
          container = {
            enable_character_fade = true
          },
          indent = {
            indent_size = 2,
            padding = 1, -- extra padding on left hand side
            -- indent guides
            with_markers = true,
            indent_marker = "โ”‚",
            last_indent_marker = "โ””",
            highlight = "NeoTreeIndentMarker",
            -- expander config, needed for nesting files
            with_expanders = nil, -- if nil and file nesting is enabled, will enable expanders
            expander_collapsed = "๏‘ ",
            expander_expanded = "๏‘ผ",
            expander_highlight = "NeoTreeExpander",
          },
          icon = {
            folder_closed = "๎—ฟ",
            folder_open = "๎—พ",
            folder_empty = "๓ฐœŒ",
            -- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there
            -- then these will never be used.
            default = "*",
            highlight = "NeoTreeFileIcon"
          },
          modified = {
            symbol = "[+]",
            highlight = "NeoTreeModified",
          },
          name = {
            trailing_slash = false,
            use_git_status_colors = true,
            highlight = "NeoTreeFileName",
          },
          git_status = {
            symbols = {
              -- Change type
              added     = "", -- or "โœš", but this is redundant info if you use git_status_colors on the name
              modified  = "", -- or "๏‘„", but this is redundant info if you use git_status_colors on the name
              deleted   = "โœ–",-- this can only be used in the git_status source
              renamed   = "๓ฐ•",-- this can only be used in the git_status source
              -- Status type
              untracked = "๏„จ",
              ignored   = "๏‘ด",
              unstaged  = "๓ฐ„ฑ",
              staged    = "๏†",
              conflict  = "๎œง",
            }
          },
          -- If you don't want to use these columns, you can set `enabled = false` for each of them individually
          file_size = {
            enabled = true,
            required_width = 64, -- min width of window required to show this column
          },
          type = {
            enabled = true,
            required_width = 122, -- min width of window required to show this column
          },
          last_modified = {
            enabled = true,
            required_width = 88, -- min width of window required to show this column
          },
          created = {
            enabled = true,
            required_width = 110, -- min width of window required to show this column
          },
          symlink_target = {
            enabled = false,
          },
        },
        -- A list of functions, each representing a global custom command
        -- that will be available in all sources (if not overridden in `opts[source_name].commands`)
        -- see `:h neo-tree-custom-commands-global`
        commands = {},
        window = {
          position = "left",
          width = 40,
          mapping_options = {
            noremap = true,
            nowait = true,
          },
          mappings = {
            ["<space>"] = { 
                "toggle_node", 
                nowait = false, -- disable `nowait` if you have existing combos starting with this char that you want to use 
            },
            ["<2-LeftMouse>"] = "open",
            ["<cr>"] = "open",
            ["<esc>"] = "cancel", -- close preview or floating neo-tree window
            ["P"] = { "toggle_preview", config = { use_float = true, use_image_nvim = true } },
            -- Read `# Preview Mode` for more information
            ["l"] = "focus_preview",
            ["S"] = "open_split",
            ["s"] = "open_vsplit",
            -- ["S"] = "split_with_window_picker",
            -- ["s"] = "vsplit_with_window_picker",
            ["t"] = "open_tabnew",
            -- ["<cr>"] = "open_drop",
            -- ["t"] = "open_tab_drop",
            ["w"] = "open_with_window_picker",
            --["P"] = "toggle_preview", -- enter preview mode, which shows the current node without focusing
            ["C"] = "close_node",
            -- ['C'] = 'close_all_subnodes',
            ["z"] = "close_all_nodes",
            --["Z"] = "expand_all_nodes",
            ["a"] = { 
              "add",
              -- this command supports BASH style brace expansion ("x{a,b,c}" -> xa,xb,xc). see `:h neo-tree-file-actions` for details
              -- some commands may take optional config options, see `:h neo-tree-mappings` for details
              config = {
                show_path = "none" -- "none", "relative", "absolute"
              }
            },
            ["A"] = "add_directory", -- also accepts the optional config.show_path option like "add". this also supports BASH style brace expansion.
            ["d"] = "delete",
            ["r"] = "rename",
            ["y"] = "copy_to_clipboard",
            ["x"] = "cut_to_clipboard",
            ["p"] = "paste_from_clipboard",
            ["c"] = "copy", -- takes text input for destination, also accepts the optional config.show_path option like "add":
            -- ["c"] = {
            --  "copy",
            --  config = {
            --    show_path = "none" -- "none", "relative", "absolute"
            --  }
            --}
            ["m"] = "move", -- takes text input for destination, also accepts the optional config.show_path option like "add".
            ["q"] = "close_window",
            ["R"] = "refresh",
            ["?"] = "show_help",
            ["<"] = "prev_source",
            [">"] = "next_source",
            ["i"] = "show_file_details",
          }
        },
        nesting_rules = {},
        filesystem = {
          filtered_items = {
            visible = false, -- when true, they will just be displayed differently than normal items
            hide_dotfiles = true,
            hide_gitignored = true,
            hide_hidden = true, -- only works on Windows for hidden files/directories
            hide_by_name = {
              --"node_modules"
            },
            hide_by_pattern = { -- uses glob style patterns
              --"*.meta",
              --"*/src/*/tsconfig.json",
            },
            always_show = { -- remains visible even if other settings would normally hide it
              --".gitignored",
            },
            never_show = { -- remains hidden even if visible is toggled to true, this overrides always_show
              --".DS_Store",
              --"thumbs.db"
            },
            never_show_by_pattern = { -- uses glob style patterns
              --".null-ls_*",
            },
          },
          follow_current_file = {
            enabled = false, -- This will find and focus the file in the active buffer every time
            --               -- the current file is changed while the tree is open.
            leave_dirs_open = false, -- `false` closes auto expanded dirs, such as with `:Neotree reveal`
          },
          group_empty_dirs = false, -- when true, empty folders will be grouped together
          hijack_netrw_behavior = "open_default", -- netrw disabled, opening a directory opens neo-tree
                                                  -- in whatever position is specified in window.position
                                -- "open_current",  -- netrw disabled, opening a directory opens within the
                                                  -- window like netrw would, regardless of window.position
                                -- "disabled",    -- netrw left alone, neo-tree does not handle opening dirs
          use_libuv_file_watcher = false, -- This will use the OS level file watchers to detect changes
                                          -- instead of relying on nvim autocmd events.
          window = {
            mappings = {
              ["<bs>"] = "navigate_up",
              ["."] = "set_root",
              ["H"] = "toggle_hidden",
              ["/"] = "fuzzy_finder",
              ["D"] = "fuzzy_finder_directory",
              ["#"] = "fuzzy_sorter", -- fuzzy sorting using the fzy algorithm
              -- ["D"] = "fuzzy_sorter_directory",
              ["f"] = "filter_on_submit",
              ["<c-x>"] = "clear_filter",
              ["[g"] = "prev_git_modified",
              ["]g"] = "next_git_modified",
              ["o"] = { "show_help", nowait=false, config = { title = "Order by", prefix_key = "o" }},
              ["oc"] = { "order_by_created", nowait = false },
              ["od"] = { "order_by_diagnostics", nowait = false },
              ["og"] = { "order_by_git_status", nowait = false },
              ["om"] = { "order_by_modified", nowait = false },
              ["on"] = { "order_by_name", nowait = false },
              ["os"] = { "order_by_size", nowait = false },
              ["ot"] = { "order_by_type", nowait = false },
              -- ['<key>'] = function(state) ... end,
            },
            fuzzy_finder_mappings = { -- define keymaps for filter popup window in fuzzy_finder_mode
              ["<down>"] = "move_cursor_down",
              ["<C-n>"] = "move_cursor_down",
              ["<up>"] = "move_cursor_up",
              ["<C-p>"] = "move_cursor_up",
              -- ['<key>'] = function(state, scroll_padding) ... end,
            },
          },

          commands = {} -- Add a custom command or override a global one using the same function name
        },
        buffers = {
          follow_current_file = {
            enabled = true, -- This will find and focus the file in the active buffer every time
            --              -- the current file is changed while the tree is open.
            leave_dirs_open = false, -- `false` closes auto expanded dirs, such as with `:Neotree reveal`
          },
          group_empty_dirs = true, -- when true, empty folders will be grouped together
          show_unloaded = true,
          window = {
            mappings = {
              ["bd"] = "buffer_delete",
              ["<bs>"] = "navigate_up",
              ["."] = "set_root",
              ["o"] = { "show_help", nowait=false, config = { title = "Order by", prefix_key = "o" }},
              ["oc"] = { "order_by_created", nowait = false },
              ["od"] = { "order_by_diagnostics", nowait = false },
              ["om"] = { "order_by_modified", nowait = false },
              ["on"] = { "order_by_name", nowait = false },
              ["os"] = { "order_by_size", nowait = false },
              ["ot"] = { "order_by_type", nowait = false },
            }
          },
        },
        git_status = {
          window = {
            position = "float",
            mappings = {
              ["A"]  = "git_add_all",
              ["gu"] = "git_unstage_file",
              ["ga"] = "git_add_file",
              ["gr"] = "git_revert_file",
              ["gc"] = "git_commit",
              ["gp"] = "git_push",
              ["gg"] = "git_commit_and_push",
              ["o"] = { "show_help", nowait=false, config = { title = "Order by", prefix_key = "o" }},
              ["oc"] = { "order_by_created", nowait = false },
              ["od"] = { "order_by_diagnostics", nowait = false },
              ["om"] = { "order_by_modified", nowait = false },
              ["on"] = { "order_by_name", nowait = false },
              ["os"] = { "order_by_size", nowait = false },
              ["ot"] = { "order_by_type", nowait = false },
            }
          }
        }
      })

      vim.cmd([[nnoremap \ :Neotree reveal<cr>]])
    end
}

The above configuration is not everything that can be changed, it's just the parts you might want to change first.

See :h neo-tree for full documentation. You can also preview that online at doc/neo-tree.txt, although it's best viewed within vim.

To see all of the default config options with commentary, you can view it online at lua/neo-tree/defaults.lua. You can also paste it into a buffer after installing Neo-tree by running:

:lua require("neo-tree").paste_default_config()

The :Neotree Command

The single :Neotree command accepts a range of arguments that give you full control over the details of what and where it will show. For example, the following command will open a file browser on the right hand side, "revealing" the currently active file:

:Neotree filesystem reveal right

Arguments can be specified as either a key=value pair or just as the value. The key=value form is more verbose but may help with clarity. For example, the command above can also be specified as:

:Neotree source=filesystem reveal=true position=right

All arguments are optional and can be specified in any order. If you issue the command without any arguments, it will use default values for everything. For example:

:Neotree

will open the filesystem source on the left hand side and focus it, if you are using the default config.

Tab Completion

Neotree supports tab completion for all arguments. Once a given argument has a value, it will stop suggesting those completions. It will also offer completions for paths. The simplest way to disambiguate a path from another type of argument is to start them with / or ./.

Arguments

Here is the full list of arguments you can use:

action

What to do. Can be one of:

Option Description
focus Show and/or switch focus to the specified Neotree window. DEFAULT
show Show the window, but keep focus on your current window.
close Close the window(s) specified. Can be combined with "position" and/or "source" to specify which window(s) to close.

source

What to show. Can be one of:

Option Description
filesystem Show a file browser. DEFAULT
buffers Show a list of currently open buffers.
git_status Show the output of git status in a tree layout.
last Equivalent to the last source used

position

Where to show it, can be one of:

Option Description
left Open as left hand sidebar. DEFAULT
right Open as right hand sidebar.
top Open as top window.
bottom Open as bottom window.
float Open as floating window.
current Open within the current window, like netrw or vinegar would.

toggle

This is a boolean flag. Adding this means that the window will be closed if it is already open.

dir

The directory to set as the root/cwd of the specified window. If you include a directory as one of the arguments, it will be assumed to be this option, you don't need the full dir=/path. You may use any value that can be passed to the 'expand' function, such as %:p:h:h to specify two directories up from the current file. For example:

:Neotree ./relative/path
:Neotree /home/user/relative/path
:Neotree dir=/home/user/relative/path
:Neotree position=current dir=relative/path

git_base

The base that is used to calculate the git status for each dir/file. By default it uses HEAD, so it shows all changes that are not yet committed. You can for example work on a feature branch, and set it to main. It will show all changes that happened on the feature branch and main since you branched off.

Any git ref, commit, tag, or sha will work.

:Neotree main
:Neotree v1.0
:Neotree git_base=8fe34be
:Neotree git_base=HEAD

reveal

This is a boolean flag. Adding this will make Neotree automatically find and focus the current file when it opens.

reveal_file

A path to a file to reveal. This supersedes the "reveal" flag so there is no need to specify both. Use this if you want to reveal something other than the current file. If you include a path to a file as one of the arguments, it will be assumed to be this option. Like "dir", you can pass any value that can be passed to the 'expand' function. For example:

:Neotree reveal_file=/home/user/my/file.text
:Neotree position=current dir=%:p:h:h reveal_file=%:p
:Neotree current %:p:h:h %:p

One neat trick you can do with this is to open a Neotree window which is focused on the file under the cursor using the <cfile> keyword:

nnoremap gd :Neotree float reveal_file=<cfile> reveal_force_cwd<cr>

reveal_force_cwd

This is a boolean flag. Normally, if you use one of the reveal options and the given file is not within the current working directory, you will be asked if you want to change the current working directory. If you include this flag, it will automatically change the directory without prompting. This option implies "reveal", so you do not need to specify both.

selector

This is a boolean flag. When you specifically set this to false (selector=false) neo-tree will disable the source selector for that neo-tree instance. Otherwise, the source selector will depend on what you specified in the configuration (config.source_selector.{winbar,statusline}).

See :h neo-tree-commands for details and a full listing of available arguments.

File Nesting

See :h neo-tree-file-nesting for more details about file nesting.

Netrw Hijack

:edit .
:[v]split .

If "filesystem.window.position" is set to "current", or if you have specified filesystem.hijack_netrw_behavior = "open_current", then any command that would open a directory will open neo-tree in the specified window.

Sources

Neo-tree is built on the idea of supporting various sources. Sources are basically interface implementations whose job it is to provide a list of hierarchical items to be rendered, along with commands that are appropriate to those items.

filesystem

The default source is filesystem, which displays your files and folders. This is the default source in commands when none is specified.

This source can be used to:

  • Browse the filesystem
  • Control the current working directory of nvim
  • Add/Copy/Delete/Move/Rename files and directories
  • Search the filesystem
  • Monitor git status and lsp diagnostics for the current working directory

buffers

Neo-tree buffers

Another available source is buffers, which displays your open buffers. This is the same list you would see from :ls. To show with the buffers list, use:

:Neotree buffers

git_status

This view take the results of the git status command and display them in a tree. It includes commands for adding, unstaging, reverting, and committing.

The screenshot below shows the result of :Neotree float git_status while the filesystem is open in a sidebar:

Neo-tree git_status

You can specify a different git base here as well. But be aware that it is not possible to unstage / revert a file that is already committed.

:Neotree float git_status git_base=main

document_symbols

Neo-tree document_symbols The document_symbols source lists the symbols in the current document obtained by the LSP request "textDocument/documentSymbols". It currently supports the following features:

  • UI:
    • Display all symbols in the current file with symbol kinds
    • Symbols nesting
    • Configurable kinds' name and icon
    • Auto-refresh symbol list
      • Follow cursor
  • Commands
    • Jump to symbols, open symbol in split,... (open_split and friends)
    • Rename symbols (rename)
    • Preview symbol (preview and friends)
    • Hover docs
    • Call hierarchy
  • LSP
    • LSP Support
    • LSP server selection (ignore, allow_only, use first, use all, etc.)
  • CoC Support

See #879 for the tracking issue of these features.

This source is currently experimental, so in order to use it, you need to first add "document_symbols" to config.sources and open it with the command

:Neotree document_symbols

External Sources

There are more sources available as extensions that are managed outside of this repository. See the wiki for me information.

Source Selector

Neo-tree source selector

You can enable a clickable source selector in either the winbar (requires neovim 0.8+) or the statusline. To do so, set one of these options to true:

    require("neo-tree").setup({
        source_selector = {
            winbar = false,
            statusline = false
        }
    })

There are many configuration options to change the style of these tabs. See lua/neo-tree/defaults.lua for details.

Preview Mode

:h neo-tree-preview-mode

Preview mode will temporarily show whatever file the cursor is on without switching focus from the Neo-tree window. By default, files will be previewed in a new floating window. This can also be configured to automatically choose an existing split by configuring the command like this:

require("neo-tree").setup({
  window = {
    mappings = {
      ["P"] = { "toggle_preview", config = { use_float = false, use_image_nvim = true } },
    }
  }
})

Anything that causes Neo-tree to lose focus will end preview mode. When use_float = false, the window that was taken over by preview mode will revert back to whatever was shown in that window before preview mode began.

If you want to work with the floating preview mode window in autocmds or other custom code, the window will have the neo-tree-preview filetype.

When preview mode is not using floats, the window will have the window local variable neo_tree_preview set to 1 to indicate that it is being used as a preview window. You can refer to this in statusline and winbar configs to mark a window as being used as a preview.

Image Support in Preview Mode

If you have 3rd/image.nvim installed, preview mode supports image rendering by default using kitty graphics protocol or ueberzug (Video). However, if you do not want this feature, you can disable it by changing the option use_image_nvim = false in the mappings config mentioned above.

Configuration and Customization

This is designed to be flexible. The way that is achieved is by making everything a function, or a string that identifies a built-in function. All of the built-in functions can be replaced with your own implementation, or you can add new ones.

Each node in the tree is created from the renderer specified for the given node type, and each renderer is a list of component configs to be rendered in order. Each component is a function, either built-in or specified in your config. Those functions simply return the text and highlight group for the component.

Additionally, there is an events system that you can hook into. If you want to show some new data point related to your files, gather it in the before_render event, create a component to display it, and reference that component in the renderer for the file and/or directory type.

Details on how to configure everything is in the help file at :h neo-tree-configuration or online at neo-tree.txt

Recipes for customizations can be found on the wiki. Recipes include things like adding a component to show the Harpoon index for files, or responding to the "file_opened" event to auto clear the search when you open a file.

Why?

There are many tree plugins for (neo)vim, so why make another one? Well, I wanted something that was:

  1. Easy to maintain and enhance.
  2. Stable.
  3. Easy to customize.

Easy to maintain and enhance

This plugin is designed to grow and be flexible. This is accomplished by making the code as decoupled and functional as possible. Hopefully new contributors will find it easy to work with.

One big difference between this plugin and the ones that came before it, which is also what finally pushed me over the edge into making a new plugin, is that we now have libraries to build upon that did not exist when other tree plugins were created. Most notably, nui.nvim and plenary.nvm. Building upon shared libraries will go a long way in making neo-tree easy to maintain.

Stable

This project will have releases and release tags that follow a simplified Semantic Versioning scheme. The quickstart instructions will always refer to the latest stable major version. Following the main branch is for contributors and those that always want bleeding edge. There will be branches for v1.x, v2.x, etc which will receive updates after a short testing period in main. You should be safe to follow those branches and be sure your tree won't break in an update. There will also be tags for each release pushed to those branches named v1.1, v1.2, etc. If stability is critical to you, or a bug accidentally make it into v1.x, you can use those tags instead. It's possible we may backport bug fixes to those tags, but no garauntees on that.

There will never be a breaking change within a major version (1.x, 2.x, etc.) If a breaking change is needed, there will be depracation warnings in the prior major version, and the breaking change will happen in the next major version.

Easy to Customize

Neo-tree follows in the spirit of plugins like lualine.nvim and nvim-cokeline. Everything will be configurable and take either strings, tables, or functions. You can take sane defaults or build your tree items from scratch. There should be the ability to add any features you can think of through existing hooks in the setup function.

What is a Breaking Change?

As of v1.30, a breaking change is defined as anything that changes existing:

  • vim commands (:Neotree)
  • configuration options that are passed into the setup() function
  • NeoTree* highlight groups
  • lua functions exported in the following modules that are not prefixed with _:
    • neo-tree
    • neo-tree.events
    • neo-tree.sources.manager
    • neo-tree.sources.* (init.lua files)
    • neo-tree.sources.*.commands
    • neo-tree.ui.renderer
    • neo-tree.utils

If there are other functions you would like to use that are not yet considered part of the public API, please open an issue so we can discuss it.

Contributions

Contributions are encouraged. Please see CONTRIBUTING for more details.

Acknowledgements

Maintainers

First and foremost, this project is a community endeavor and would not survive without the constant stream of features and bug fixes that comes from that community. There have been many valued contributors, but a few have stepped up to become maintainers that generously donate their time to guide the project, help out others, and manage the issues. The current list of maintainers are:

(in alphabetical order)

  • @cseickel
  • @miversen33
  • @nhat-vo
  • @pysan3

Other Projects

This project relies upon these two excellent libraries:

  • nui.nvim for all UI components, including the tree!
  • plenary.nvim for backend utilities, such as scanning the filesystem.

The design is heavily inspired by these excellent plugins:

Everything I know about writing a tree control in lua, I learned from:

neo-tree.nvim's People

Contributors

adoyle-h avatar bwpge avatar coffebar avatar cseickel avatar danilshvalov avatar figsoda avatar georgeguimaraes avatar ghostbuster91 avatar jgottzen avatar konosubakonoakua avatar levouh avatar lnc3l0t avatar loichyan avatar lopi-py avatar mehalter avatar miversen33 avatar mrbjarksen avatar muniftanjim avatar musjj avatar nhat-vo avatar ofseed avatar pynappo avatar pysan3 avatar rapan931 avatar smoka7 avatar tradiff avatar tristone13th avatar ttytm avatar williamhcode avatar zenoli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neo-tree.nvim's Issues

Disable relative line numbers

Great plugin - thank you!

Is there a way to disable relative line numbers from appearing within the neo-tree window automatically?

Floating window support

Support showing neo-tree as a floating window, as an alternative option to showing as a sidebar.

The following may be problematic:

  1. One issue is that the window is currently named split, and should be renamed to something more generic like window.
  2. Another issue is that the "open" commands assume it is a sidebar and choose the window to the left/right of the tree.
  3. Finally, there may be issues with the other pop-up windows being used, when there are pop-ups on another pop-up.

NeoTreeShow is broken in the latest main

Hi!

Just updated to the latest main (b580e39) and NeoTreeShow command seems to be broken. NeoTreeReveal still works and NeoTreeShowToggle is still correctly closing the side-panel.

Best,
Ivan

Handle rapid changes gracefully (the curse of vim-rooter)

Hey!

First of all, thanks for lightning-fast bugfixes! I have another one for you: neo-tree behaves inconsistenly when I switch the project using Telescope project. How to reproduce:

  1. Open any file in a git directory, for example, your neovim config and toggle neo-tree.
  2. Lauch Telescope proejct. As you start typing, Telescope will bring the closest matching project as the current selection under the cursor and neo-tree will detect this change and will automatically switch the root directory on the fly (WOW!)
  3. Select a new project and then a file in this project by pressing Enter. This confuses neo-tree for some reason an it displays the old project root but without any content of the old project directory (screenshot below).

ะกะฝะธะผะพะบ ัะบั€ะฐะฝะฐ ะพั‚ 2022-01-10 13-26-01

Best,
Ivan

Add symbolic link targets

Would you be interested in a pull request adding a component for showing the target of symbolic links?

This is one feature I miss from nvim-tree.

This is how it would currently look:
screenshot_2022-01-14-18:01:06

Add more variations for open commands

If I open the tree with :NeoTreeFloatToggle, it will show the current file (which I'm not sure if is intended or not). If I move somewhere else in the tree, hide the tree, then show it again, I am returned to where the file that was focused when first opening the tree rather than the file that I had previously navigated to before closing it.

For example:

wrong_node_focused_on_reopen.mp4

where I first open the tree and it focuses the current buffer (whether or intended or not). I then change it to focus on another node, close the tree, then when I re-open it the focus is not on the same node as I closed it with

respect_gitignore is not working correctly

At least as how I would expect it to.

The plenary.scandir respect_gitignore flag filters out entries in the current scan IFF a .gitignore file is present in the root of the path it scans. The do_scan function in fs_scan.lua scans with a depth of 1, so each time a folder is opened, a new scan is done with the path of that directory, which has the effect that a .gitignore in the root directory isn't picked up.

An example would be

.config/nvim/
|- lua/
   |- packer_compiled.lua
|- .gitignore 

with .gitignore containing lua/packer_compiled.lua; packer_compiled.lua is still visible in the tree.

One solution is to create a gitignore filter in fs_scan.lua and do the filtering there. This can easily be done by just copying the method plenary.scandir uses.

Another one is just scan the whole tree at once and on any refresh and so on, but that might be detrimental when there are a lot of files.

I have implemented the first solution and it works well. Another benefit of doing it that way is that ignored files could be highlighted differently.

Toggling neotree two times in a row puts it in a broken state

Hi!

The steps to reproduce are the following:

  1. Open a file in a git directory.
  2. Call :NeotreeShowToggle to reveal neo-tree.
  3. Call it one more time to hide it.
  4. From now on calling it will give you a stack trace below.

ะกะฝะธะผะพะบ ัะบั€ะฐะฝะฐ ะพั‚ 2022-01-10 11-09-56

I am using the latest main branch.

Best,
Ivan

New feature: indent guides

Add the ability to enable indent guides like this:

indent guides

I have some developments, so I can open a pull request, if you think it's worth adding this feature.

Allow :NeoTreeFloat to toggle the floating window

Currently calling :NeoTreeFloat twice in a row will try to render it again. I think it would make a lot more sense if it just toggled the floating tree, especially considering the fact that you cannot close it directly with either "Ctrl+w_c" or "Ctrl+w_q" as this results in the following:
Initial state:
image
After "Ctrl+w_c" or "Ctrl+w_q"
image

Binding to close current node

Say that I am in a node like this:

image

Being able to press a key and change the tree to this:

image

(so close the node that the cursor is currently inside of) would be a nice feature to have, similar to how nvim-tree.lua has close_node.

highlight group not found: NeoTreeNormal

packer.nvim: Error running config for neo-tree.nvim: ...acker/start/neo-tree.nvim/lua/neo-tree/ui/highlights.lua:68: Vim(highlight):E411: highlight
group not found: NeoTreeNormal

NVIM version: v0.6.1

  use {
    "nvim-neo-tree/neo-tree.nvim",
    branch = "v1.x",
    requires = { "MunifTanjim/nui.nvim" },
    config = function ()
      require("neo-tree").setup()
    end
  }

Add LSP Diagnostic indicators to files

In the existing filesystem source. This would be just like the git_status, with a utility function called in the default before_render, and a built-in component that renders it.

Add libuv watcher for current git project

This will enable auto updating of git status like we have with diagnostics. I think we can just watch project_root/.git/index and refresh the status whenever that changes.

Can't filter

Any time I attempt to filter the tree I get

Error executing luv callback:
...neo-tree.nvim/lua/neo-tree/sources/common/file-items.lua:51: attempt to index field 'name' (a nil value)
stack traceback:
        [builtin#36]: at 0x562387a7d900

I added a check for item.name's presence. The error is gone, but the filtering doesn't yield any results.

Any idea of what I'm doing wrong?

Cheers

Add buffers source

Essentially ls in tree form. The question is, should it be

  • filtered to the current tab like BufExplorer can do
  • grouped by tab (tab the buffers were last shown in)
  • filtered to the cwd
  • or show all open buffers without filters?

Question: maybe formatter should be used?

What about using stylua? This makes it easier to contribute in one style.

You (or I in a PR) could just add a stylua.toml file in the base of the repo.

In addition, you can create coding style checks before committing (stylua --check for example).

Add merge check to prevent PR merges to release branches

Github branch protection does not seem to cover this situation. I want to ensure that all PRs must be targeting the main branch and only the main branch. I can easily forget to check and accidentally merge a PR directly to a release branch.

Toggle requires two key presses after hiding with <Esc>

Using the float implementation, if I open the tree using something like :NeoTreeFloatToggle it will open with the current buffer focused, as expected. If I hit <Esc> to close the NeoTree window, I then have to use :NeoTreeFloatToggle twice to get it to show up again.

Here is an example:

hit_key_twice_to_toggle.mp4

If using :NeoTreeFloatToggle when the window is visible to hide the the window, it opens on the first repeat of this command as expected.

How to behave when neo-tree becomes the only window

Steps to reproduce:

  • open any file
  • :NeoTreeReveal
  • select that same file in the tree and delete it with d (the opened file then closes and the only window left is NeoTree)
  • select another file and open it with Enter (this closes the NeoTree window, not sure why)
  • :NeoTreeReveal

Expected behaviour: tree viewer should open
Actual behavior: nothing happens, presumably because NeoTree thinks it's already open even though it's not in the buffer list anymore

My config is the default one (an empty table passed to setup). Let me know if you need any further info.

Custom mappings not being used

User supplied mappings are not overriding the default ones.

EDIT: To be more precise, mappings with special characters, that may have multiple forms or are case insensitive, could create conflicts with default mappings. For example, a mapping of <BS> would not overwite the default mapping of <bs>, so both would be applied in an unknown order with unpredictable results.

The fix is to normalize those types of mapping to make sure that user supplied versions properly overwrite the default ones.

Git status is rendered despite being explicitly disabled in renderer

Hey, guys!

First, many thanks for your work on this plugin. I am a big fan: this is perhaps the most fluid and intuitive tree plugin to date.

Second, I've noticed that git status next to a file is being rendered despite being explicitly disabled by me in the config. Please see the screenshot attached:
git_status

On the right you can see the relevant part of my config. As you can notice, I explicitly restrict file renderer to being an icon and a name only. However, in the left panel I see that my config was modified. I believe this is an inconsistency of some sorts.

Best,
Ivan

Allow custom mappings in popup inputs

Currently Ctrl-w will go into Normal mode instead of deleting the previous word, which would be the usual Insert mode behavior.

I tried customizing the Filesystem mappings in the following way:

      mappings = {
        ["<2-LeftMouse>"] = "open",
        ["<cr>"] = "open",
        ["<C-x>"] = "open_split",
        ["<C-v>"] = "open_vsplit",
        ["<C-w>"] = function()
          vim.cmd([[normal! bcw]])
        end,
...

However I believe this does not work because this mapping is not available inside the "Enter Filter Pattern" popup. Although if it did work, it would also cause problems for when wanting to use Ctrl-w in NeoTreeFloat outside of the filter pattern situation.

Basically, is it possible to restore the normal Ctrl-w behavior inside "Enter Filter Pattern" or allow the user to properly customize the keybindings within that particular situation?

One potential avenue that Telescope is using, is to allow different mappings for different modes:

    mappings = {
      i = {
        -- To disable a keymap, put [map] = false
        -- So, to not map "<C-n>", just put
        -- ["<c-x>"] = false,
        ["<esc>"] = actions.close,
        ["<C-w>"] = function()
          vim.cmd([[normal! bcw]])
        end,
        -- Otherwise, just set the mapping to the function that you want it to be.
        -- ["<C-i>"] = actions.select_horizontal,

        -- Add up multiple actions
        -- ["<CR>"] = actions.select_default + actions.center

        -- You can perform as many actions in a row as you like
        -- ["<CR>"] = actions.select_default + actions.center + my_cool_custom_action,
      },
      n = {
        --[[ ["<C-j>"] = actions.move_selection_next,
        ["<C-k>"] = actions.move_selection_previous,
        ["<C-q>"] = actions.smart_send_to_qflist + actions.open_qflist, ]]
        -- ["<C-i>"] = my_cool_custom_action,
      },
    },
  },

Auto closing when opening a file

How it looks like

neo-tree.mov

Possible implementaion

neo-tree/sources/filesystem/commands.lua

M.open = function(state)
-   cc.open(state, fs.toggle_directory)
+   cc.open(state, fs.toggle_directory, fs.close)
end

- M.open_split = function(state) cc.open_split(state, fs.toggle_directory) end
+ M.open_split = function(state) cc.open_split(state, fs.toggle_directory, fs.close) end

- M.open_vsplit = function(state) cc.open_vsplit(state, fs.toggle_directory) end
+ M.open_vsplit = function(state) cc.open_vsplit(state, fs.toggle_directory, fs.close) end

neo-tree/sources/common/commands.lua

- local open_with_cmd = function(state, open_cmd, toggle_directory)
+ local open_with_cmd = function(state, open_cmd, toggle_directory, close_window)
  ...
  if ...
    ...
  else
    ...
+     local auto_close = utils.resolve_config_option(state, "auto_close", false)
+     if auto_close then
+         close_window()
+     end
  end
end

- M.open = function(state, toggle_directory)
+ M.open = function(state, toggle_directory, close_window)
-   open_with_cmd(state, "e", toggle_directory)
+   open_with_cmd(state, "e", toggle_directory, close_window)
end

- M.open_split = function(state, toggle_directory)
+ M.open_split = function(state, toggle_directory, close_window)
-   open_with_cmd(state, "split", toggle_directory)
+   open_with_cmd(state, "split", toggle_directory, close_window)
end

- M.open_vsplit = function(state, toggle_directory)
+ M.open_vsplit = function(state, toggle_directory, close_window)
-   open_with_cmd(state, "vsplit", toggle_directory)
+   open_with_cmd(state, "vsplit", toggle_directory, close_window)
end

Refactor common functionality for sources

In 1.0, sources only had to implement a few simple methods and there was no real shared functionality. Now we have gotten to the point where there is, or should be, a lot of shared code between them.

Most of my life this would mean "extract a base class and/or interface", but I'm not sure if that's the right way to go here. Partly because lua has a pretty shoddy class and type system, but also because this was simply not designed in an OOP fashion from the start.

This issue exists to let others know this refactor is happening and to collect any discussion that may happen around this work. This may or may not create breaking changes that requires a 2.x branch.

[Feature/Bug] Show hidden files by default

Using the following configuration:

require("neo-tree").setup({
  filesystem = {
    window = {
      mappings = {
        ["<CR>"] = "open",
        ["<C-x>"] = "open_split",
        ["<C-v>"] = "open_vsplit",
        [".."] = "navigate_up",
        ["<BS>"] = "close_node",
        ["<C-]>"] = "set_root",
        ["H"] = "toggle_hidden",
        ["I"] = "toggle_gitignore",
        ["R"] = "refresh",
        ["/"] = "filter_as_you_type",
        ["f"] = "filter_on_submit",
        ["<C-l>"] = "clear_filter",
        ["o"] = "add",
        ["dd"] = "delete",
        ["r"] = "rename",
        ["yy"] = "copy_to_clipboard",
        ["x"] = "cut_to_clipboard",
        ["p"] = "paste_from_clipboard",
      },
      position = "float",
      popup = {
        border = {
          top = "",
          style = "rounded",
        },
        size = {
          width = 35,
          height = "100%",
        },
        position = {
          row = "0%",
          col = "0%",
        },
      },
    },
    bind_to_cwd = true,
    filters = {
      show_hidden = true,
      respect_gitignore = false,
    },
    before_render = function(_)
      return
    end,
    components = {
      user_icon = require("user.pack.config.tree.components").user_icon,
    },
    renderers = {
      directory = {
        {
          "user_icon",
          folder_closed = "๎—ฟ",
          folder_open = "๎—พ",
          folder_empty_closed = "๏„”",
          folder_empty_open = "๏„•",
          padding = " ",
        },
        { "current_filter" },
        { "name" },
        {
          "clipboard",
          highlight = highlights.DIM_TEXT,
        },
      },
    },
  },
})

I observe that when trying to try to expand a node for a directory that contains only hidden files with <CR>, nothing is expanded. The behavior of:

    filters = {
      show_hidden = true,
      respect_gitignore = false,
    },

is strictly related to filters (e.g. /), correct? Or is there a bug there and setting show_hidden = true should actually do what is noted here? As it stands right now, without using the (default) mapping of H, there is no other way to have this directory (containing only hidden files) expand. From what I can tell, this might just be a matter of things being copied over to the current state correctly, but I' not entirely sure how things are intended to behave.

This behavior is shown below:

expand_node_with_only_hidden_files.mp4

cursor in float

Love the plugin :)

When I open the float panel in a buffer not pointing to a file, the popup will show with an invisible cursor

image

I can move around, open and close folders, but cursor is hidden.

You can see here, when I open on a file that exists on the fs, the cursor exists (over the icon of snippets.lua)

image

However, in the above case, cursorline does not seem to be set (you can see cursorline highlight in the window the float popped over.

Improve documentation (suggestions welcome)

Basic usage is OK, but we need much better documentation on how to customize Neo-tree. I think the wiki is the place for this.

Things to document include:

  • Basic overview of all the ways it can be customized, with example use cases for each one
  • List all built-in components along with their config options
  • Full explanation of components and renderers
  • Full explanation of mappings to custom functions (or defining commands)
  • Full explanation of events (the vim doc is pretty good, maybe just copy to the wiki)
  • Add some Recipes for custom components
  • Add some Recipes for mapping to a custom function
  • Add some recipes for event_handler usage
  • LINK TO THE WIKI IN THE README!
  • Remove README in lua/sources/filesystem, it's just a maintenance problem

If there is anything else you feel needs to be added or have any ideas at all on this subject, please add a comment. Also, if anything here seems like a particular priority, let me know what you think that order should be.

Files sometimes open in wrong tab

The is due to the "use last window" behavior. If the last window was in another tab, that's where the file opens. The behavior should be use the last window for the current tab, or open a new one.

Automatically expand path to the active file

Hey!

Is there a way to make neo-tree automatically expand the directory nodes to the currently edited file? For example:

  1. neo-tree is showing ~/.config/nvim with all directories collapsed. The currently edited file is init.nvim
  2. I open a new file with :e after/ftplugin/html.vim
  3. neo-tree automatically expands the directories after and ftplugin to show the currently edited file.

Ideally, this should also remember the manually expanded subdirectories. So if I have some other subdirectory my/other/subdir expanded by hand, then automatic expansion of after/ftplugin should not affect it; likewise, nothing opened previously by hand should not be automatically collapsed once I switch out of this subdirectory.

Best,
Ivan

Buffers view should show unloaded buffers as well

The plugin does not display open buffers after starting a session.

2022-01-09_13_37_19

NeoTree buffers. (Left)
Telescope buffers. (Front float)

Open nvim session, run any this command
(
:NeoTreeShow buffers
:NeoTreeFocus buffers
:NeoTreeFloat buffers
)
only 1 buffer.

Run
:Telescope buffers

you can see that there are many more buffers.

Add an event system

This is just a thought right now that needs to be fleshed out. Please comment if you have ideas or suggestions.

Right now the way to customize the behavior of Neo-tree is to override built-in commands or add your own. This works but it requires the user to find and understand the internal implementation, which takes more time than they may want to devote to this. A better, and perhaps safer way would be to create an event system, and they can just add event handlers to their config. Something like:

      require("neo-tree").setup({
        event_handlers = {
          {
            event = "before_render",
            handler = function (state)
              local utils = require("neo-tree.utils")
              state.git_status_lookup = utils.get_git_status()
              state.diagnostics_lookup = utils.get_diagnostic_counts()
            end
          },
          {
            event = "file_opened",
            handler = function(state, file_path)
              --auto close
              require("neo-tree").close(state.name)
            end
          },
          {
            event = "rename",
            handler = function(state, old_file_path, new_file_path)
              -- fix references to file
            end
          },
          {
            event = "move",
            handler = function(state, old_file_path, new_file_path)
              -- fix references to file
            end
          },
        }
      })

`neo-tree` is stuck when switching between splits in the presence of `vim-rooter`.

Hi!

I've found another issue with vim-rooter on the most recent main branch. The sidebar is not feeling well if you jump between the panes that belong to different projects. Here's how to reproduce it:

  1. In a fresh nvim session split your window into two vertical splits and toggle neo-tree sidebar.
  2. In the first split open a file from project P1 (let's say .config/nvim/init.vim). In the second split open a file from P2 (let's say .local/share/nvim/plugged/neo-tree).
  3. You are now in the second split. Your cwd is set to P2 by vim-rooter. Switch to the first split. cwd is set to P1 and the tree is updated. Switch back to the second split. cwd is set back to P2, but the tree is still showing P1's cwd, but now collapsed.

This time I don't think it's related to debouncing and it happens if I wait for 10 seconds before each consequetive switch.

As usual, let me know if you need additional info.

Best,
Ivan

Expected Lua number

:NeoTreeReveal
E5108: Error executing lua ...t/neo-tree.nvim/lua/neo-tree/sources/filesystem/init.lua:54: Expected Lua number
stack traceback:
        [C]: in function 'nvim_buf_line_count'
        ...t/neo-tree.nvim/lua/neo-tree/sources/filesystem/init.lua:54: in function 'reveal_file'
        ...t/neo-tree.nvim/lua/neo-tree/sources/filesystem/init.lua:171: in function 'reveal_current_file'
        [string ":lua"]:1: in main chunk

Add testing

We really need to add a full suite of tests to this project, which can be run both locally and as a merge check. A handful of tests were created in #61 using plenary, which looks pretty good.

What needs to be tested?

  • Every documented config option should do what it says
  • Every bug fix should include a regression test (maybe just for future fixes?)
  • All variations of vim commands (:NeoTree...) should do what the documentation says.

Arrow Background is incorrect

Hi :)

I got this intermittently before, now it is constant #32

image

other icons seem fine.

When I got it intermittently, a second PackerCompile would confusingly make it go away (I would think that would be deterministic). Now it is sticking around

Add Git Status source

Just to show all modified files in the current repo. This will always look at the git project root of whatever the cwd is in, and therefore won't have any navigation commands or bind_to_cwd option.

It would also be nice to add some stage/unstage/revert commands in this view.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.