Projects for Vim and Neovim: switch between them, find files and text, replace across files, run tasks and work with git, all scoped to the project you are in.
- Vim and Neovim: plain Vimscript, Vim 8.2+ and Neovim 0.5+.
- No freezing: searches run in the background.
- Git like an IDE: changelists, shelves, background pull and push.
- Per project: session, history, config and tasks.
- Fast search: with fd, rg or ag (recommended), or find and grep.
Needs Vim 8.2.3591+ or Neovim 0.5.1+, and git for the git features. fd and rg (or ag) are recommended for fast search; without them, find, grep and Vim's :vimgrep are used.
Install with lazy.nvim, vim-plug or Vim packages
-- lazy.nvim
{ 'leafOfTree/vim-project', lazy = false }" vim-plug
Plug 'leafOfTree/vim-project'# Vim's built-in packages
git clone https://github.com/leafOfTree/vim-project ~/.vim/pack/plugins/start/vim-projectFor icons, add a Nerd Font with nerdfont.vim, and glyph-palette.vim to color them.
To uninstall, remove the plugin and config_home (~/.vim/vim-project-config by default).
Add a project and start working:
:Project ~/code/my-app " add a folder as a project and open it
:ProjectList " switch to another project
:ProjectSearchFiles " find a file by name
:ProjectFindInFiles " find text in the projectMapping the commands you use most is worth it, for example:
nnoremap <leader>p :ProjectList<CR>
nnoremap <leader>f :ProjectSearchFiles<CR>
noremap <leader>g :ProjectFindInFiles<CR>For the full reference: :help vim-project (run :helptags ALL first if your plugin manager does not).
The config of the demos, and how to run them
The demos use this config, with npm in place of their scripts. project_root opens each project at its README.md rather than its folder:
let g:vim_project_config = {
\'project_base': ['~/code'],
\'project_root': './README.md',
\'tasks': [
\ {'name': 'test', 'cmd': 'npm test'},
\ {'name': 'dev', 'cmd': 'npm run dev'},
\ {'name': 'lint', 'cmd': 'npm run lint'},
\],
\}
" joshdick/onedark.vim
colorscheme onedark
" The file on the left, the project and its branch on the right
let &statusline = "%f%m%=%{empty(g:vim_project) ? '' : g:vim_project.name.' '.get(g:vim_project, 'branch', '')} "To try the projects of the demos, without changing your own config:
git clone https://github.com/leafOfTree/vim-project && cd vim-project
docs/demo/setup.sh # webshop and five other projects in /tmp/vim-project-demo
vimrc=$PWD/docs/demo/vimrc && cd /tmp/vim-project-demo/projects && nvim -u "$vimrc"Then :ProjectList and pick webshop.
In every list, type to filter. Ctrl-j / Ctrl-k (or the arrow keys) move, Enter opens and Esc closes. See keys in lists.
:ProjectList filters projects by name, note or path. Opening a project quits the current one (saving its session if enabled) and opens the new one's root.
:Project <path> ["note"]adds a folder. A relative path is looked up inproject_base.:ProjectNew <path>creates a new project from a template task, e.g.git clone.:ProjectRename,:ProjectRemove,:ProjectQuitmanage them.project_viewsdefine views of the list, switched with Tab and Shift-Tab.
:ProjectSearchFiles matches fuzzily, so typing a few letters of each part is enough:
| Input | Finds | |
|---|---|---|
pcard |
ProductCard.vue |
file names |
store/c |
src/store/cart.js |
with /, the path too |
comp/pcard |
src/components/ProductCard.vue |
both parts can be shortened |
store/ |
the store directory |
ending with /, directories only |
Recently opened files come first. Ctrl-s, Ctrl-v and Ctrl-t open in a split, vertical split or tab.
The file list is cached and refreshed in the background, so new files show up without waiting.
:ProjectFindInFiles searches as you type; with a visual selection it starts with the selected text. The input is literal and case insensitive: start it with \C to match case, or \E for a regular expression.
To replace, press Ctrl-r, type the replacement after =>, check the preview, and confirm with Ctrl-y. Ctrl-d leaves out a line or file.
:ProjectRun lists the tasks of the project and shows their output as they run. Enter runs or reruns a task, Ctrl-q stops it and Ctrl-o opens its terminal.
let g:vim_project_local_config = {
\'tasks': [
\ {'name': 'test', 'cmd': 'npm test'},
\ {'name': 'dev', 'cmd': 'npm run dev'},
\ {'name': 'script', 'cmd': 'npm run', 'args': 'script name'},
\ {'name': 'terminal', 'cmd': ''},
\],
\}args asks for input appended to the command, cd runs it in a sub folder, show and hide filter its output by patterns, and an empty cmd opens a terminal.
:ProjectGitStatus shows local changes with a diff of the file under the cursor. Group them in changelists, then commit one at a time: only its files are committed.
| Key | Action |
|---|---|
| Enter | open a file, expand or collapse a changelist |
| a / r / d | new, rename, delete a changelist |
| m | move files or a changelist to another changelist |
| c | commit the changelist or selected files (write amend alone on a line to amend) |
| R | roll back the selected files |
| s | shelve: save the changes as a patch and revert them |
| S | unshelve a shelf or one of its files |
| u / p / P | pull, push, pull then push, in the background |
:ProjectGitLogsearches commits by message, author or hash, with the changed files of the selected one beside it.:ProjectGitFileHistoryshows the commits of the current file, or of the selected lines, with their diffs.:ProjectGitBranchswitches, creates and merges branches.:ProjectGitTagchecks out and creates tags.
| List | Key | Action |
|---|---|---|
| log | Enter | show the changed files, and the diff of the one under the cursor |
| log, file history | Ctrl-o | check out |
| file history | Enter | show the diff |
| branches | Enter | switch to it, or create one from New Branch |
| branches | Ctrl-o | merge into the current branch (asks first) |
| tags | Enter | check out, or create one from New Tag |
use_sessionsaves and restores the windows and buffers of each project; withcheck_branch_when_use_session, one per git branch.use_viminfokeeps command and search history, marks and recent files per project.:ProjectConfigopens the project'sinit.vim, sourced when it opens and reloaded when saved. Setg:vim_project_local_configthere to override options for that project.file_mappingsjump to files you edit often, e.g.'rforREADME.md. See:help vim-project-file-mappings.
Set g:vim_project_config before the plugin loads (with lazy.nvim, in init). Only the keys you set change:
let g:vim_project_config = {
\'project_base': ['~/code'],
\'use_session': 1,
\'exclude': ['.git', 'node_modules', 'dist'],
\}| Option | Default | Description |
|---|---|---|
config_home |
'~/.vim/vim-project-config' |
where projects, sessions and per-project config are kept |
project_base |
['~'] |
where relative paths of :Project are looked up |
project_root |
'./' |
folder or file to open with a project |
include / exclude |
[] / ['.git', 'node_modules', ...] |
what search and find in files look at |
use_session / use_viminfo |
0 |
per project session and history |
auto_detect |
'no' |
'ask' or 'always' to add folders with .git you open |
tasks |
[] |
tasks for :ProjectRun |
find_replace_separator |
' => ' |
separator of search and replace text |
file_mappings_prefix |
"'" |
key before file_mappings keys |
Every option, key mapping and the per-project config are in :help vim-project-config.
g:vim_project holds the current project (name, path, branch, ...) and $vim_project its path, e.g.
set statusline=%f%=%{empty(g:vim_project)?'':g:vim_project.name}| Key | Action |
|---|---|
| Enter | open |
| Esc | close |
| Ctrl-j / Ctrl-k, ↓ / ↑ | next / previous item |
| Ctrl-h / Ctrl-l | first / last item |
| Ctrl-n / Ctrl-p | scroll down / up |
| Backspace / Ctrl-w / Ctrl-u | delete a character / word / everything |
| Ctrl-b | paste |
| Ctrl-o | go to the list window (in lists without their own Ctrl-o) |
All of them can be changed with list_mappings. Typed characters are highlighted with the VimProjectMatch group, e.g. highlight VimProjectMatch guifg=#f9e2af gui=bold.
| Command | Description |
|---|---|
:Project <path> ["note"] |
add a folder as a project and open it |
:ProjectNew <path> |
create a new project |
:ProjectList |
list and open projects |
:ProjectOpen <name> |
open a project by name |
:ProjectQuit |
quit the current project |
:ProjectRename <name> <new_name> |
rename a project and its folder |
:ProjectRemove <name> |
remove a project from the list |
:ProjectIgnore <path> |
never offer a folder in auto_detect |
:ProjectSearchFiles |
search files by name |
:ProjectSearchFilesReset |
forget the cached file list |
:ProjectFindInFiles [text] |
find and replace in files |
:ProjectRun |
run tasks |
:ProjectGitStatus, :ProjectGitCommit |
local changes and changelists |
:ProjectGitLog |
search the git log |
:ProjectGitFileHistory |
history of the file or selected lines |
:ProjectGitBranch / :ProjectGitTag |
branches / tags |
:ProjectRoot |
open the project root |
:ProjectConfig / :ProjectAllConfig |
edit the project's init.vim / the project list |
:ProjectConfigReload |
write all buffers and reopen the project |
:ProjectInfo / :ProjectAllInfo |
show the project and config in use |
:ProjectOutput [filter] |
print the projects |
- A command is much slower than in a terminal. Try setting
shelltosh(orcmd.exeon Windows). 'ano longer jumps to mark a.file_mappingsuse'by default; setfile_mappings_prefixto e.g.'<leader>'.- Git push or pull fails asking for credentials. They run in the background without a terminal; use a credential helper or ssh-agent.
- Something else.
:ProjectInfo,:ProjectAllInfoandlet g:vim_project_config.debug = 1show what is going on.
bash test/run.sh # tests with every editor found (or: nvim, vim)
VIM_PROJECT_TEST_CASES=perf bash test/run.sh # time budgets of the code run on every key
vint --error plugin autoload
docs/demo/render.sh # re-record the animations above with VHSSee CONTRIBUTING.md for how the tests, time budgets and demos work.
Thanks to timsofteng for the great idea. It all started with that.






