rooter.nvim changes the working directory to the project root when you open a file. It is inspired by vim-rooter.
This plugin also provides telescope and picker.nvim extensions to fuzzy find recently opened projects.
- โจ Features
- ๐ฆ Installation
- ๐ง Configuration
- โ๏ธ Basic Usage
- ๐งช Development
- ๐ Debug
- ๐ฌ Feedback
- ๐ Credits
- ๐ฃ Self-Promotion
- ๐ License
โจ Features
- Automatic project root detection on
BufEnter/VimEnter - Re-detect root on
BufWritePost(e.g. after creating a new.git/) - Project history caching to disk for persistence across sessions
- Outermost vs nearest root directory support
- Flexible behavior for non-project files (
'','home', or'current') - Configurable buffer exclusion patterns (skip filetree/popup buffers)
- Toggle root detection on/off at runtime (
:Rooter toggle) - Automatic logging via logger.nvim (optional dependency)
- Callback APIs for project switch events
- Command-line interface (
:Rooter) - Telescope integration
- Picker.nvim integration
๐ฆ Installation
using nvim-plug
require('plug').add({ { 'wsdjeg/rooter.nvim', config = function() require('rooter').setup({ root_patterns = { '.git/' }, }) end, } })
๐ง Configuration
require('rooter').setup({ root_patterns = { '.git/' }, outermost = true, enable_cache = true, project_non_root = '', command = 'lcd', exclude_patterns = { '%[denite%]', 'denite%-filter', '%[defx%]', '^git://', -- git.vim '^neo%-tree', -- neo-tree.nvim '^NvimTree_', -- nvim-tree.nvim '^__Tagbar__', -- tagbar.vim }, })
| Option | Type | Default | Description |
|---|---|---|---|
root_patterns |
table<string> |
{ '.git/' } |
Patterns to identify project root. Directories end with /, files do not. |
outermost |
boolean |
true |
When true, find the outermost matching directory. When false, find the nearest (innermost). |
enable_cache |
boolean |
true |
Persist project history to stdpath('data')/nvim-rooter.json for cross-session persistence. |
project_non_root |
string |
'' |
Behavior for files outside any project: '' = keep cwd, 'home' = switch to $HOME, 'current' = switch to file's directory. |
command |
string |
'lcd' |
Vim command used to change directory: 'cd', 'tcd', or 'lcd'. |
exclude_patterns |
table<string> |
see above | Lua patterns matched against the buffer name; matching buffers (filetrees, popups...) are skipped by root detection. Remember to escape magic characters (- โ %-). |
Note: list options (root_patterns, exclude_patterns) are replaced as a whole
when you set them, they are not merged with the defaults.
Example: multiple patterns
require('rooter').setup({ root_patterns = { '.git/', '.hg/', 'Cargo.toml', 'go.mod', 'package.json' }, outermost = false, -- find nearest root command = 'tcd', -- use tab-local cd })
Example: exclude plugin buffers
require('rooter').setup({ exclude_patterns = { '^neo%-tree', -- lua pattern, escape the magic `-` '^NvimTree_', '^myfiletree://', -- add your own plugin buffers }, })
โ๏ธ Basic Usage
Once setup() is called, rooter.nvim automatically changes the working directory
whenever you open a file or switch buffers. You can also use the commands and APIs below.
Commands
This plugin provides a user command :Rooter:
| Command | Description |
|---|---|
:Rooter |
Manually trigger root detection for current buffer. |
:Rooter toggle |
Toggle root detection on/off. |
:Rooter enable |
Enable root detection. |
:Rooter disable |
Disable root detection (keeps current cwd). |
:Rooter clear |
Clear all cached projects. |
:Rooter kill project1 project2 |
Delete all buffers belonging to the specified project(s). |
Telescope extension
Requires telescope.nvim.
:Telescope project
Lists all cached projects sorted by last opened time. Press <CR> to open a
project in a new tab.
Picker.nvim extension
Requires picker.nvim.
:Picker project
Key bindings for picker project:
| Key binding | Description |
|---|---|
<CR> |
Open project in a new tab (default action) |
<C-f> |
Browse project files |
<C-d> |
Delete project from history |
<C-s> |
Search text in project, requires flygrep.nvim |
Callback function
To run custom logic when the project changes, register a callback with
rooter.reg_callback:
-- Update code-runner config based on project's .clang file local c_runner = { exe = 'gcc', targetopt = '-o', usestdin = true, opt = { '-std=c11', '-xc', '-' }, } require('code-runner').setup({ runners = { c = { c_runner, '#TEMP#' }, }, }) local function update_clang_flag() if vim.fn.filereadable('.clang') == 1 then local flags = vim.fn.readfile('.clang') local opt = { '-std=c11' } for _, v in ipairs(flags) do table.insert(opt, v) end table.insert(opt, '-xc') table.insert(opt, '-') c_runner.opt = opt end end require('rooter').reg_callback(update_clang_flag, 'update clang flags')
The callback receives the current project object:
| Field | Description |
|---|---|
path |
Absolute root path (with trailing /) |
name |
Project name (same as b:rooter_project_name) |
opened_time |
Timestamp when the project was opened |
Callbacks are called via pcall so errors won't crash the root detection flow.
You can also pass a Vimscript function name as a string; it receives the project
as a dict argument.
Per-project configuration
Per-project settings can be implemented right inside the callback, by matching
project.name (or project.path for an exact match):
-- Per-project options: map project name -> option table local project_options = { ['my-blog'] = { shiftwidth = 2, expandtab = true, makeprg = 'hugo server', }, ['dotfiles'] = { shiftwidth = 4, }, } -- Options applied when entering a project that is not in the table above local default_options = { shiftwidth = 4, } local function apply_options(opts) for key, value in pairs(opts) do vim.opt[key] = value end end require('rooter').reg_callback(function(project) -- project = { path = '/home/me/my-blog/', name = 'my-blog', opened_time = 1712345678 } apply_options(project_options[project.name] or default_options) end, 'per-project options')
Of course you can do much more than options, for example loading a project-local keymap file, starting an LSP server only for certain projects, etc.
API
| Function | Description |
|---|---|
setup(opt) |
Initialize rooter.nvim with config options and set up autocmds. |
current_root() |
Detect and switch to the project root for the current buffer. Returns the root path. |
current_name() |
Returns the current project name (from b:rooter_project_name). |
toggle() |
Toggle root detection, returns the new state. |
enable() |
Enable root detection, returns true. |
disable() |
Disable root detection, returns false. |
is_enabled() |
Returns whether root detection is currently enabled. |
list() |
Open the project picker (Picker.nvim or Telescope, whichever is available). |
open(project_path) |
Open a project by its path in a new tab. |
clear() |
Clear all cached projects and write empty cache to disk. |
kill_project(name) |
Delete all buffers belonging to the named project. |
reg_callback(func, desc?) |
Register a callback function (or Vimscript function name) to run on project switch. The callback receives the project object (path, name, opened_time). desc is an optional description for logging. |
get_project_history() |
Returns the table of all cached projects. |
๐งช Development
make test # run the test suite make coverage # run tests with luacov, enforces 100% line coverage
The coverage gate covers lua/rooter/*.lua and plugin/rooter.lua. The
telescope/picker integration files are excluded since they cannot be loaded
without their host plugins.
๐ Debug
Install logger.nvim as a dependency. Logging is automatically enabled when logger.nvim is available - no extra config needed.
require('plug').add({ { 'wsdjeg/rooter.nvim', config = function() require('rooter').setup({ root_patterns = { '.git/' }, }) end, depends = { { 'wsdjeg/logger.nvim', config = function() vim.keymap.set( 'n', '<leader>hL', '<cmd>lua require("logger").viewRuntimeLog()<cr>', { silent = true } ) end, }, }, }, })
Sample runtime log:
[ rooter ] [23:22:50:576] [ Info ] start to find root for: D:/wsdjeg/rooter.nvim/lua/rooter/init.lua
[ rooter ] [23:22:50:576] [ Info ] (.git/):D:/wsdjeg/rooter.nvim/
[ rooter ] [23:22:50:576] [ Info ] switch to project:[rooter.nvim]
[ rooter ] [23:22:50:576] [ Info ] rootdir is:D:/wsdjeg/rooter.nvim/
๐ฌ Feedback
If you encounter any bugs or have suggestions, please file an issue in the issue tracker
๐ Credits
๐ฃ Self-Promotion
Like this plugin? Star the repository on GitHub.
Love this plugin? Follow me on GitHub.
๐ License
Licensed under GPL-3.0.

