This project has only been slightly tested on Windows, MacOS and linux. If you find bugs, please open an issue! It is also at the early stages of development and is subject to changes in the future
I made this neovim plugin for my personal use. I wanted a simple interface to
set up per project configurations without having to create .lua
files in my
project folders.
In this demo, clangd (C++ LSP) does not work because the project is not built
with the CMAKE_EXPORT_COMPILE_COMMANDS variable. Thus, I execute
:LightProjectsConfig
to open the config file, modify the configure
command,
switch back to my cpp project, execute the configure
command in a ToggleTerm
window, and restart my LSP with :LspRestart
.
- Create keybindings for common (custom) tasks, such as
run
,build
,debug
, etc. - The key bindings get loaded on
VimEnter
andDirChanged
autocmds - Optional support for ToggleTerm
to execute a command in the float terminal (using
:TermExec cmd='my_cmd'<CR>
) - Supports an additionnal callback to be ran when the project is loaded
- Command
LightProjectsConfig
(orlp.open_config()
): Opens the config file - Command
LightProjectsReload
(orlp.reload()
): reloads the config file. It triggersreload_callback
set in setup. - Command
LightProjectsSwitch
(orlp.telescope_project_picker()
): opens a telescope window to switch project. If the chosen project has anentry_point
defined, opens the specified file. If not, justcd
into the directory - Command
LightProjectToggle
(orlua lp.toggle_project()
): toggles the project. This is the command that is ran onVimEnter
andDirChanged
. - Supports git bare repository with branches in the same folder. Checkout the git bare repositories section for more information.
- Notifications when a project is loaded using
nvim-notify (optional, enabled with
use_notify = true
in the config)
For example using vim-plug:
Plug 'LucLabarriere/light-projects.nvim'
or lazy.nvim:
{
'LucLabarriere/light-projects.nvim',
lazy = false,
dependencies = {
'nvim-lua/plenary.nvim',
'nvim-telescope/telescope.nvim',
},
config = function()
-- Setup here
end
}
note that nvim-lua/plenary.nvim
and nvim-telescope/telescope.nvim
are
dependencies so make sure to load them before
An example of a config file is given in the repository. The configuration is explained below.
Define your key mappings:
local lp = require('light-projects')
lp.keymaps = {
configure = '<leader>cc',
build = '<leader>bb',
source = '<leader>so',
run = '<leader>rr',
test = '<leader>tt',
bench = '<leader>ce',
debug = '<leader>dd',
clean = '<leader>cle',
tidy = '<leader>ti',
build_and_run = '<leader>br',
build_and_test = '<leader>bt',
build_and_bench = '<leader>be',
build_and_debug = '<leader>bd',
-- ...
}
In the example, the names chosen are arbitrary, chose whatever command name you like
Then define a bunch of presets in the presets dictionary. For example, in the
example below, I define a preset called "lua" that I will use for my neovim
config files (see the Command types section for more infos on
the type
argument.
lp.presets.lua = {
cmds = {
source = { cmd = 'source %', type = lp.cmdtypes.raw },
},
}
For my python projects I use:
lp.presets.python = {
cmds = {
run = { cmd = 'python ${app_executable}', type = lp.cmdtypes.toggleterm },
}
}
The ${app_executable}
variable will have to be set for each project. Note that
in these examples, the source
and run
entries correspond to the ones defined
in the lp.keymaps
dictionary
A more complicated example is given below. In there, I define the cpp
preset
for my C++ projects. A bunch of variables are defined in the variables
dictionary (those can be overwritten in the project specific configurations).
Notice also that some variables (${app_executable}
for example, remain
undefined in the preset)
lp.presets.cpp = {
variables = {
c_compiler = 'clang',
cxx_compiler = 'clang++',
config = 'Debug',
},
cmds = {
configure = {
cmd =
'$env:VCPKG_FEATURE_FLAGS="manifests";'
.. 'cmake . -B build'
.. ' -DCMAKE_CXX_COMPILER=${cxx_compiler}'
.. ' -DCMAKE_C_COMPILER=${c_compiler}'
.. ' -G "Ninja Multi-Config"'
.. ' -DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
},
build = { cmd = 'cmake --build build --config ${config}' },
run = { cmd = 'build/${config}/${app_executable}' },
test = { cmd = 'cd build; ctest' },
bench = { cmd = 'build/benchmarks/${config}/${bench_executable}' },
clean = { cmd = 'rm -rf build' },
debug = { cmd = 'DapContinue', type = lp.cmdtypes.raw },
build_and_run = { cmd = { 'build', 'run' }, type = lp.cmdtypes.sequential },
build_and_test = { cmd = { 'build', 'test' }, type = lp.cmdtypes.sequential },
build_and_bench = { cmd = { 'build', 'bench' }, type = lp.cmdtypes.sequential },
build_and_debug = { cmd = { 'build', 'debug' }, type = lp.cmdtypes.sequential },
},
}
Also in this example, note that the build_and_x
commands are sequential
commands, meaning that the cmd
entry has to be given as a table of command
names to execute in order.
In the setup call, a bunch of additional features and configs can be used.
lp.setup {
-- Possible values:
-- - 0 : silent
-- - 1 : prints the name of the current project
-- - 2 : prints the current path as well
-- - 3 : prints each registered command
verbose = 0,
-- Don't modify this line to be able to use the LightProjectsConfig command
config_path = string.sub(debug.getinfo(1, "S").source, 2),
-- To use nvim-notify (you probably need to add it to dependencies)
use_notify = true
-- Reloading the config
-- For example using Lazy.nvim:
reload_callback = function()
local plugin = require("lazy.core.config").plugins["light-projects.nvim"]
require("lazy.core.loader").reload(plugin)
end,
-- Using vim-plug, you can source your config file instead
-- By default, run the commands using :TermExec cmd='my_cmd'<CR>
-- Available cmdtypes:
-- lp.cmdtypes.raw
-- lp.cmdtypes.toggleterm
-- lp.cmdtypes.sequential
-- lp.cmdtypes.lua_function
default_cmdtype = lp.cmdtypes.toggleterm,
projects = {
nvim_config = {
preset = lp.presets.lua,
path = '~/.config/nvim',
-- The entry_point key allows to specify a file to open when
-- using the LightProjectsSwitch command
entry_point = 'init.lua',
},
-- Example of a project using bare git repo
light_projects = {
preset = lp.presets.lua,
path = '~/work/light-projects.nvim/.git',
entry_point = 'lua/light-projects.lua',
bare_git = true,
},
vkengine = {
preset = lp.presets.cpp,
path = '~/work/vkengine',
entry_point = 'src/main.cpp',
variables = {
app_executable = 'vkengine',
bench_executable = 'benchmarks',
test_executable = 'tests',
},
callback = function()
print("This function is executed when the vkengine project gets loaded")
end
},
pysand = {
preset = lp.presets.python,
path = '~/work/pysand',
entry_point = 'pysand.py',
variables = {
app_executable = 'pysand.py',
},
}
}
}
For example, cloning this repository with these commands:
git clone https://github.com/LucLabarriere/light-projects.nvim.git --bare light-projects.nvim/.git
cd light-projects.nvim
git worktree add main
git worktree add dev
will initialize a bare git repository pointing to the github repo, with two
worktrees called main
and dev
storing local copies of the repository in these
branches. Setting up this project with these settings:
light_projects = {
preset= lp.presets.lua,
path = '~/work/light-projects.nvim/.git',
entry_point = 'lua/light-projects.lua',
bare_git = true,
},
will allow for automatic detection of project branches in the
LightProjectSwitch
command:
As of today, four command types are available:
lp.cmdtypes.raw
: The command is executed as a vim command (using:cmd<CR>
)lp.cmdtypes.toggleterm
: The command is executed as a ToggleTerm command (using:TermExec cmd='cmd'<CR>
)lp.cmdtypes.lua_function
: The command is executed as a lua function. For example, thisrun
command prints "Hello" when executed
run = { cmd = function() print("Hello") end, type = lp.cmdtypes.lua_function }
lp.cmdtypes.sequential
: Executes the given command in order. This is kind of experimental and may have undefined behaviors if mixed command types are passed (So far, it works well for ToggleTerm commmands).
If you're like me, you have your nvim config files stored in a remote repository, shared accross multiple computers. If so, you might want to define your projects in a separate config file. Here is how I do it using lazy.nvim. I have a global config shared accross computers:
{
'LucLabarriere/light-projects.nvim',
lazy = false,
dependencies = {
'rcarriga/nvim-notify',
'nvim-lua/plenary.nvim',
'nvim-telescope/telescope.nvim',
},
keys = {
{ "<leader>lr", "<cmd>LightProjectsReload<CR>", "n" },
{ "<leader>ls", "<cmd>LightProjectsSwitch<CR>", "n" },
{ "<leader>lc", "<cmd>LightProjectsConfig<CR>", "n" },
},
config = function()
local lp = require('light-projects')
lp.keymaps = {
configure = '<leader>cc',
build = '<leader>bb',
source = '<leader>so',
run = '<leader>rr',
-- ... configure addition keymaps here
}
lp.presets.lua = {
cmds = {
source = { cmd = 'source %', type = lp.cmdtypes.raw },
},
}
-- ... configure additional presets here
local setup_args = {
verbose = 0,
reload_callback = function()
local plugin = require("lazy.core.config").plugins["light-projects.nvim"]
require("lazy.core.loader").reload(plugin)
end,
default_cmdtype = lp.cmdtypes.toggleterm,
use_notify = true,
-- configure global settings here
}
-- Load a computer specific config file and call a custom function to setup the projects
dofile(vim.fn.expand("~/.nvimenv.lua")).setup_light_projects(lp, setup_args)
end
}
Then, in ~/.nvimenv.lua
:
local M = {}
M.setup_light_projects = function(lp, setup_args)
-- Setting config path here will make the LightProjectsConfig command open this file
setup_args.config_path = string.sub(debug.getinfo(1, "S").source, 2)
setup_args.projects = {
nvim_config = {
preset = lp.presets.lua,
path = '~/.config/nvim',
entry_point = 'init.lua',
},
-- .. setup additional projects here
}
lp.setup(setup_args)
end
return M
I had a hard time setting up sequential commands. I found a hack by using the nvim server (created by default at startup) to request the next command to be executed. When a ToggleTerm command is executed, here is command used:
cmd && nvim --server server_name --remote-send "<ESC>:sleep 10m | lua require('light-projects').execute_next_cmd()<CR>"
In which the :sleep 10m
seems necessary on windows (if you have an idea why,
please let me know!).
This project is available under the MIT license. Feel free to use, modify, copy, etc. Also if you think of something that is missing, consider opening an issue or creating a pull request.