======================== Breaking Changes ========================
Announcements | Discussions | Wiki | Credits | Contributing | Roadmap
project.nvim
is a Neovim plugin written in Lua that,
under configurable conditions, automatically sets the user's cwd
to the current project root
and also allows users to manage, access and selectively include their projects in a history.
This plugin allows you to navigate through projects, "bookmark" and/or discard them, according to your needs.
[!NOTE] This was originally forked from ahmedkhalf/project.nvim. Ever since I've decided to extend it and address issues.
I will be maintaining this plugin for the foreseeable future!
[!IMPORTANT] I'm looking for other maintainers in case I'm unable to keep this repo up to date!
https://github.com/user-attachments/assets/436f5cbe-7549-4b8a-b962-4fce3d2a2fc3
https://github.com/user-attachments/assets/376cbca0-a746-4992-8223-8475fcd99fc9
https://github.com/user-attachments/assets/1516ff2e-29d9-4e0d-b592-bf2f79ab8158
Show these much love!
nvim-telescope/telescope-project.nvim
folke/snacks.nvim
coffebar/neovim-project
LintaoAmons/cd-project.nvim
cwd
to the project root directory using either vim.lsp
or pattern matchingcheckhealth
hook :checkhealth project
:h project-nvim
:ProjectLog
, :ProjectLogClear
vim.ui
menu support:Telescope projects
Fzf-Lua
Integrationnvim-tree
Integrationneo-tree
Integration[!IMPORTANT]
Requirements:
- Neovim >= 0.11.0
fd
(REQUIRED FOR SESSION MANAGEMENT)fzf-lua
(OPTIONAL, RECOMMENDED)telescope.nvim
(OPTIONAL, RECOMMENDED)
plenary.nvim
telescope-file-browser.nvim
(OPTIONAL, to replace the builtinfind_files
)nvim-tree.lua
(OPTIONAL)neo-tree.nvim
(OPTIONAL)
Use any plugin manager of your choosing. There currently instructions for the following.
[!TIP] If you want to add instructions for your plugin manager of preference please raise a BLANK ISSUE.
vim-plug
if has('nvim-0.11')
Plug 'DrKJeff16/project.nvim'
" OPTIONAL
Plug 'nvim-telescope/telescope.nvim' | Plug 'nvim-lua/plenary.nvim'
Plug 'ibhagwan/fzf-lua'
lua << EOF
require('project').setup()
EOF
endif
lazy.nvim
{
'DrKJeff16/project.nvim',
version = false, -- Get the latest release
dependencies = { -- OPTIONAL
'nvim-lua/plenary.nvim',
'nvim-telescope/telescope.nvim',
'ibhagwan/fzf-lua',
},
---@module 'project'
---@type Project.Config.Options
opts = {},
}
[!TIP] If you wish to lazy-load this plugin:
{ 'DrKJeff16/project.nvim', lazy = true, version = false, -- Get the latest release cmd = { -- Lazy-load by commands 'Project', 'ProjectAdd', 'ProjectConfig', 'ProjectDelete', 'ProjectHistory', 'ProjectRecents', 'ProjectRoot', 'ProjectSession', }, dependencies = { -- OPTIONAL 'nvim-lua/plenary.nvim', 'nvim-telescope/telescope.nvim', 'ibhagwan/fzf-lua', }, ---@module 'project' ---@type Project.Config.Options opts = {}, }
pckr.nvim
if vim.fn.has('nvim-0.11') == 1 then
require('pckr').add({
{
'DrKJeff16/project.nvim',
requires = { -- OPTIONAL
'nvim-lua/plenary.nvim',
'nvim-telescope/telescope.nvim',
'ibhagwan/fzf-lua',
},
config = function()
require('project').setup()
end,
};
})
end
paq-nvim
local paq = require('paq')
paq({
'savq/paq-nvim',
'DrKJeff16/project.nvim',
'nvim-lua/plenary.nvim', -- OPTIONAL
'nvim-telescope/telescope.nvim', -- OPTIONAL
'ibhagwan/fzf-lua', -- OPTIONAL
})
require('project.nvim').setup()
To enable the plugin you must call setup()
:
require('project').setup()
[!TIP] You can find these in
project/config/defaults.lua
.
[!NOTE] The
Project.Telescope.ActionNames
type is an alias for:---@alias Project.Telescope.ActionNames ---|'browse_project_files' ---|'change_working_directory' ---|'delete_project' ---|'find_project_files' ---|'help_mappings' ---|'recent_project_files' ---|'search_in_project_files'
{
---Options for logging utility.
--- ---
---@type Project.Config.Logging
log = {
---If `true`, it enables logging in the same directory in which your
---history file is stored.
--- ---
---Default: `false`
--- ---
---@type boolean
enabled = false,
---The maximum logfile size (in megabytes).
--- ---
---Default: `1.1`
--- ---
---@type number
max_size = 1.1,
---Path in which the log file will be saved.
--- ---
---Default: `vim.fn.stdpath('state')`
--- ---
---@type string
logpath = vim.fn.stdpath('state'),
},
---Table of options used for `fzf-lua` integration
--- ---
---@type Project.Config.FzfLua
fzf_lua = {
---Determines whether the `fzf-lua` integration is enabled.
---
---If `fzf-lua` is not installed, this won't make a difference.
--- ---
---Default: `false`
--- ---
---@type boolean
enabled = false,
},
---Determines in what filetypes/buftypes the plugin won't execute.
---It's a table with two fields:
---
--- - `ft`: A string array of filetypes to exclude
--- - `bt`: A string array of buftypes to exclude
---
---CREDITS TO [@Zeioth](https://github.com/Zeioth)!:
---[`Zeioth/project.nvim`](https://github.com/Zeioth/project.nvim/commit/95f56b8454f3285b819340d7d769e67242d59b53)
--- ---
---The default value for this one can be found in the project's `README.md`.
--- ---
---@type { ft: string[], bt: string[] }
disable_on = {
ft = {
'',
'TelescopePrompt',
'TelescopeResults',
'alpha',
'checkhealth',
'lazy',
'minimap', -- from `mini.map`
'notify',
'packer',
'qf',
},
bt = {
'help',
'nofile',
'terminal',
},
},
---If `true` your root directory won't be changed automatically,
---so you have the option to manually do so
---using the `:ProjectRoot` command.
--- ---
---Default: `false`
--- ---
---@type boolean
manual_mode = false,
---Methods of detecting the root directory. `'lsp'` uses the native Neovim
---LSP, while `'pattern'` uses vim-rooter like glob pattern matching. Here
---order matters: if one is not detected, the other is used as fallback. You
---can also delete or rearrange the detection methods.
---
---The detection methods get filtered and rid of duplicates during runtime.
--- ---
---Default: `{ 'lsp' , 'pattern' }`
--- ---
---@type ("lsp"|"pattern")[]
detection_methods = { 'lsp', 'pattern' },
---All the patterns used to detect root dir, when **'pattern'** is in
---detection_methods.
---
---See `:h project-nvim.pattern-matching`
--- ---
---Default: `{ '.git', '.github', '_darcs', '.hg', '.bzr', '.svn', 'Pipfile' }`
--- ---
---@type string[]
patterns = {
'.git',
'.github',
'_darcs',
'.hg',
'.bzr',
'.svn',
'Pipfile',
},
---Sets whether to use Pattern Matching rules on the LSP.
---
---If `false`, the Pattern Matching will only apply to the `pattern` detection method.
--- ---
---Default: `true`
--- ---
---@type boolean
allow_patterns_for_lsp = true,
---Determines whether a project will be added if its project root is owned by a different user.
---
---If `false`, it won't add a project if its root is not owned by the
---current nvim `UID` **(UNIX only)**.
--- ---
---Default: `true`
--- ---
---@type boolean
allow_different_owners = true,
---If enabled, set `vim.opt.autochdir` to `true`.
---
---This is disabled by default because the plugin implicitly disables `autochdir`.
--- ---
---Default: `false`
--- ---
---@type boolean
enable_autochdir = false,
---The history size. (by @acristoffers)
---
---This will indicate how many entries will be written to the history file.
---Set to `0` for no limit.
--- ---
---Default: `100`
--- ---
---@type integer
historysize = 100
---Table of options used for the telescope picker.
--- ---
---@type Project.Config.Telescope
telescope = {
---Determines whether the `telescope` picker should be called
---from the `setup()` function.
---
---If telescope is not installed, this doesn't make a difference.
---
---Note that even if set to `false`, you can still load the extension manually.
--- ---
---Default: `false`
--- ---
---@type boolean
enabled = false,
---Determines whether the newest projects come first in the
---telescope picker (`'newest'`), or the oldest (`'oldest'`).
--- ---
---Default: `'newest'`
--- ---
---@type 'oldest'|'newest'
sort = 'newest',
---If you have `telescope-file-browser.nvim` installed, you can enable this
---so that the Telescope picker uses it instead of the `find_files` builtin.
---
---If `true`, use `telescope-file-browser.nvim` instead of builtins.
---In case it is not available, it'll fall back to `find_files`.
--- ---
---Default: `false`
--- ---
---@type boolean
prefer_file_browser = false,
---Set this to `true` if you don't want the file picker to appear
---after you've selected a project.
---
---CREDITS: [UNKNOWN](https://github.com/ahmedkhalf/project.nvim/issues/157#issuecomment-2226419783)
--- ---
---Default: `false`
--- ---
---@type boolean
disable_file_picker = false,
---Table of mappings for the Telescope picker.
---
---Only supports Normal and Insert modes.
--- ---
---Default: check the README
--- ---
---@type table<'n'|'i', table<string, Project.Telescope.ActionNames>>
mappings = {
n = {
b = 'browse_project_files',
d = 'delete_project',
f = 'find_project_files',
r = 'recent_project_files',
s = 'search_in_project_files',
w = 'change_working_directory',
},
i = {
['<C-b>'] = 'browse_project_files',
['<C-d>'] = 'delete_project',
['<C-f>'] = 'find_project_files',
['<C-r>'] = 'recent_project_files',
['<C-s>'] = 'search_in_project_files',
['<C-w>'] = 'change_working_directory',
},
},
},
---Make hidden files visible when using any picker.
--- ---
---Default: `false`
--- ---
---@type boolean
show_hidden = false,
---Table of lsp clients to ignore by name,
---e.g. `{ 'efm', ... }`.
---
---If you have `nvim-lspconfig` installed **see** `:h lspconfig-all`
---for a list of servers.
--- ---
---Default: `{}`
--- ---
---@type string[]
ignore_lsp = {},
---Don't calculate root dir on specific directories,
---e.g. `{ '~/.cargo/*', ... }`.
---
---See the `Pattern Matching` section in the `README.md` for more info.
--- ---
---Default: `{}`
--- ---
---@type string[]
exclude_dirs = {},
---If `false`, you'll get a _notification_ every time
---`project.nvim` changes directory.
---
---This is useful for debugging, or for players that
---enjoy verbose operations.
--- ---
---Default: `true`
--- ---
---@type boolean
silent_chdir = true,
---Determines the scope for changing the directory.
---
---Valid options are:
--- - `'global'`: All your nvim `cwd` will sync to your current buffer's project
--- - `'tab'`: _Per-tab_ `cwd` sync to the current buffer's project
--- - `'win'`: _Per-window_ `cwd` sync to the current buffer's project
--- ---
---Default: `'global'`
--- ---
---@type 'global'|'tab'|'win'
scope_chdir = 'global',
---Hook to run before attaching to a new project.
---
---It recieves `target_dir` and, optionally,
---the `method` used to change directory.
---
---CREDITS: @danilevy1212
--- ---
---Default: `function(target_dir, method) end`
--- ---
---@param target_dir? string
---@param method? string
before_attach = function(target_dir, method) end,
---Hook to run after attaching to a new project.
---**_This only runs if the directory changes successfully._**
---
---It recieves `dir` and, optionally,
---the `method` used to change directory.
---
---CREDITS: @danilevy1212
--- ---
---Default: `function(dir, method) end`
--- ---
---@param dir? string
---@param method? string
on_attach = function(dir, method) end,
---The path where `project.nvim` will store the project history directory,
---containing the project history in it.
---
---For more info, run `:lua vim.print(require('project').get_history_paths())`
--- ---
---Default: `vim.fn.stdpath('data')`
--- ---
---@type string
datapath = vim.fn.stdpath('data'),
}
project.nvim
comes with a vim-rooter
-inspired pattern matching expression engine
to give you better handling of your projects.
For your convenience here come some examples:
To specify the root is a certain directory, prefix it with =
:
patterns = { '=src' }
To specify the root has a certain directory or file (which may be a glob), just add it to the pattern list:
patterns = { '.git', '.github', '*.sln', 'build/env.sh' }
To specify the root has a certain directory as an ancestor (useful for
excluding directories), prefix it with ^
:
patterns = { '^fixtures' }
To specify the root has a certain directory as its direct ancestor / parent
(useful when you put working projects in a common directory), prefix it with >
:
patterns = { '>Latex' }
To exclude a pattern, prefix it with !
:
patterns = { '!.git/worktrees', '!=extras', '!^fixtures', '!build/env.sh' }
[!IMPORTANT]
- Make sure to put your pattern exclusions first, and then the patterns you DO want included.
- If you have
allow_patterns_for_lsp
enabled, it will also work somewhat for your LSP clients.
Make sure these flags are enabled to support nvim-tree.lua
:
require('nvim-tree').setup({
sync_root_with_cwd = true,
respect_buf_cwd = true,
update_focused_file = {
enable = true,
update_root = true,
},
})
You can use :Neotree filesystem ...
when changing a project:
vim.keymap.set('n', '<YOUR-TOGGLE-MAP>', ':Neotree filesystem toggle reveal_force_cwd<CR>', opts)
vim.keymap.set('n', '<YOUR-SHOW-MAP>', ':Neotree filesystem show reveal_force_cwd<CR>', opts)
vim.keymap.set('n', '<YOUR-FLOAT-MAP>', ':Neotree filesystem float reveal_force_cwd<CR>', opts)
-- ... and so on
[!NOTE] Not 100% certain whether the
reveal_force_cwd
flag is necessary, but better safe than sorry!
To enable telescope.nvim
integration use the following
code in your config:
require('telescope').setup(...)
require('telescope').load_extension('projects')
After that you can now call it from the command line:
:Telescope projects
[!TIP] You can also configure the picker when calling
require('telescope').setup()
CREDITS: @ldfwbebp: https://github.com/ahmedkhalf/project.nvim/pull/160require('telescope').setup({ extensions = { projects = { prompt_prefix = " ", layout_strategy = "horizontal", layout_config = { anchor = "N", height = 0.25, width = 0.6, prompt_position = "bottom", }, }, }, })
project.nvim
comes with the following mappings for Telescope:
Normal mode | Insert mode | Action |
---|---|---|
f |
<C-f> |
find_project_files |
b |
<C-b> |
browse_project_files |
d |
<C-d> |
delete_project |
s |
<C-s> |
search_in_project_files |
r |
<C-r> |
recent_project_files |
w |
<C-w> |
change_working_directory |
[!TIP] You can find the Actions in
telescope/_extensions/projects/actions.lua
.
These are the user commands you can call from the cmdline:
:Project
The :Project
command will open a UI window pointing to all the useful operations
this plugin can provide. This one is subject to change, just as vim.ui
is.
[!TIP] See
commands.lua
for more info.
:ProjectFzf
[!IMPORTANT] This command works ONLY if you have
fzf-lua
installed and loaded
The :ProjectFzf
command is a dynamicly enabled User Command that runs
project.nvim
through fzf-lua
.
For now it just executes require('project').run_fzf_lua()
.
[!TIP] See
commands.lua
for more info.
:ProjectTelescope
[!IMPORTANT] This command works ONLY if you have
telescope.nvim
installed and loaded
The :ProjectTelescope
command is a dynamicly enabled User Command that runs
the Telescope projects
picker.
A shortcut, to be honest.
[!TIP] See
commands.lua
for more info.
:ProjectHistory
The :ProjectHistory
command opens the project.nvim
history file in a new tab,
which can be exited by pressing q
in Normal Mode.
If a [!]
is supplied at the end of the command (i.e. :ProjectHistory!
), then it'll close
any instance of a previously opened history file, if found. Otherwise nothing will happen.
[!TIP] See
history.lua
for more info.
:ProjectLog
[!IMPORTANT] This command will not be available unless you set
log.enabled = true
in yoursetup()
.
The :ProjectLog
command opens the project.nvim
log file in a new tab,
which can be exited by pressing q
in Normal Mode.
If a [!]
is supplied at the end of the command (i.e. :ProjectLog!
), then it'll close
any instance of a previously opened log file, if found. Otherwise nothing will happen.
[!TIP] See
log.lua
for more info.
:ProjectLogClear
[!IMPORTANT] This command will not be available unless you set
log.enabled = true
in yoursetup()
.
The :ProjectLogClear
command will delete all the contents of your log file,
assuming there is one.
If this is called from a logfile tab, then it will attempt to close it.
[!NOTE] Once this command is called no more logs will be written for your current Neovim session. You will have to restart.
You could set
vim.g.project_log_cleared
to a value different to1
, BUT THIS SOLUTION IS NOT TESTED FULLY. EXPECT WEIRD BEHAVIOUR IF YOU DO THIS!
[!TIP] See
log.lua
for more info.
:ProjectAdd
The :ProjectAdd
command is a manual hook that opens a prompt to input any
directory through a UI prompt, to be saved to your project history.
If your prompt is valid, your cwd
will be switched to said directory.
Adding a [!] will set the prompt to your cwd.
[!NOTE] This is particularly useful if you've enabled
manual_mode
insetup()
.
[!TIP] See
commands.lua
for more info.
:ProjectRoot
The :ProjectRoot
command is a manual hook to set the working directory to the current
file's root, attempting to use any of the setup()
detection methods
set by the user.
The command is like doing the following in the cmdline:
:lua require('project.api').on_buf_enter()
[!TIP] See
commands.lua
for more info.
:ProjectConfig
The :ProjectConfig
command is a hook to display your current config
using vim.notify(inspect())
The command is like doing the following in the cmdline:
:lua vim.notify(vim.inspect(require('project').get_config()))
[!TIP] See
commands.lua
for more info.
:ProjectDelete
The :ProjectDelete
command is a utility to delete your projects.
If no arguments are given, a popup with a list of your current projects will be opened.
If one or more arguments are passed, it will expect directories separated
by a space. The arguments have to be directories that are returned by get_recent_projects()
.
The arguments can be relative, absolute or un-expanded (~/path/to/project
).
The command will attemptto parse the args and, unless a !
is passed to the command
(:ProjectDelete!
). In that case, invalid args will be ignored.
If there's a successful deletion, you'll recieve a notification denoting success.
[!NOTE] USAGE
" Vim command line :ProjectDelete[!] [/path/to/first [/path/to/second [...]]]
[!TIP]
- See
:h :ProjectDelete
for more info.- See
commands.lua
for more info.
:ProjectSession
[!IMPORTANT] This command requires
fd
to be installed for it to work!
The :ProjectSession
command opens a custom picker with a selection of
your current session projects (stored in History.session_projects
). Bear in mind this table gets
filled on runtime.
If you select a session project, your cwd
will be changed to what you selected.
If the command is called with a !
(:ProjectSession!
) the UI will close.
Otherwise, another custom UI picker will appear for you to select the files/dirs.
Selecting a directory will open another UI picker with its contents, and so on.
[!TIP]
- See
popup.lua
for more info.
The API can be found in api.lua
.
get_project_root()
get_project_root()
is an API utility for finding out
about the current project's root, if any:
---@type string?, string?
local root, lsp_or_method = require('project').get_project_root()
get_recent_projects()
You can get a list of recent projects by running the code below:
local recent_projects = require('project').get_recent_projects() ---@type string[]
vim.notify(vim.inspect(recent_projects))
Where get_recent_projects()
returns either an empty table {}
or a string array { '/path/to/project1', ... }
.
get_config()
If setup()
has been called, it returns a table containing the currently set options.
Otherwise it will return nil
.
local config = require('project').get_config()
-- Using `vim.notify()`
vim.notify(vim.inspect(config))
-- Using `vim.print()`
vim.print(config)
get_history_paths()
If no valid args are passed to this function, it will return the following dictionary:
---@type fun(path: ('datapath'|'projectpath'|'historyfile')?): string|{ datapath: string, projectpath: string, historyfile: string }
local get_history_paths = require('project').get_history_paths
-- A dictionary table containing all return values below
vim.print(get_history_paths())
--- { datapath = <datapath>, projectpath = <projectpath>, historyfile = <historyfile> }
Otherwise, if either 'datapath'
, 'projectpath'
or 'historyfile'
are passed,
it will return the string value of said arg:
-- The directory where `project` sets its `datapath`
vim.print(get_history_paths('datapath'))
-- The directory where `project` saves the project history
vim.print(get_history_paths('projectpath'))
-- The path to where `project` saves its recent projects history
vim.print(get_history_paths('historyfile'))
A set of utilities that get repeated across the board.
[!TIP] You can import them the follow way:
local ProjUtil = require('project.utils.util')
See
util.lua
for further reference.
[!NOTE] These utilities are in part inspired by my own utilities found in
Jnvim
, my own Nvim configuration; particularly theUser API
(WIP).
If you're in a UNIX environment, make sure you have read, write and access permissions
(rwx
) for the projectpath
directory.
[!IMPORTANT] The default value is
vim.fn.stdpath('data')/project_nvim
. See:h stdpath()
for more info.
[!TIP] You can get the value of
projectpath
by running the following in the cmdline::lua vim.print(require('project').get_history_paths('projectpath'))
If you lack the required permissions for that directory, you can either:
chmod 755 <project/path>
(NOT SURE IF THIS WILL FIX IT)