a modular lightweight neovim code runner plugin integrating Snacks terminal
- Neovim (>= 0.7)
- Snacks.nvim (>= 2.11.0)
- better Terminal
- which-key (>= 3.15.0)
- custom leader key menu group name
- These both come default with lazy.nvim
With lazy.nvim
{
"dchae/canter.nvim",
opts = {}
},Install normally, and add this line to your init.lua:
require("canter.nvim").setup()Pass your config table into the setup() function or opts if you use lazy.nvim.
opts = {
debug = false,
-- File extension to runner/interpreter mapping
runners = {},
-- Terminal configuration
terminal = {
type = "snacks", -- "snacks" or "builtin"
-- Options for built-in terminal
builtin_opts = {
position = "vsplit", -- "vsplit", "split", or "float"
escape_keymap = true, -- escape terminal mode with <Esc>
},
-- Options for Snacks.nvim terminal
snacks_opts = {
win = {
position = "bottom",
relative = "editor",
},
interactive = false,
},
},
-- Default keymaps (can be overridden)
keymaps = {
["<leader><cr><cr>"] = {
cmd = ":CanterRun<CR>",
desc = "Run current file (Auto)",
},
["<leader><cr>w"] = {
cmd = ":CanterWait<CR>",
desc = "Run current file (Wait)",
},
},
}NOTE - does not come with runners by default, you must add your own.
runners: table([file_extension] = run command)terminal: table of options passed to terminaltype: type of terminalbuiltin_opts: options for built-in terminalsnacks_opts: options for Snacks.nvim terminal
keymaps: table of keybindings and their descriptions
The current filename will be interpolated into the runner command.
For example, node %s will become node 'myFile.js'.
opts = {
runners = {
["js"] = "node %s",
["ts"] = "bun %s",
["rb"] = "ruby %s",
["py"] = "python %s",
["cpp"] = "make run",
},
}"Run current file (Auto)"
- if file contains a shebang on the first line, the plugin will attempt to:
- make the file executable via
chmod - execute the current file
- make the file executable via
- else, if the file has a corresponding runner
- execute the current file via its runner command in
Snacks.terminal
- execute the current file via its runner command in
"Run current file (Wait)"
- same as above, but stops before actually executing so you can add flags or confirm the command before pressing enter.
- necessarily, the terminal is interactive by default in this mode.
When using the built-in terminal in wait mode:
- Press
<Esc>to exit terminal mode and return to normal mode (ifescape_keymapis enabled) - Alternatively, use the default Neovim terminal escape sequence:
<C-\><C-n>
test.js
#!/usr/bin/env node
console.log("Hello, world!");
// "Hello, world!"All keybinds can be customized in the config. The defaults are:
<Leader><CR><CR>: Run current file (Auto)- executes current file in terminal
- default behaviour is non-interactive; file will run and then any key will dismiss terminal
<Leader><CR>w: Run current file (Wait)- loads current file run command in terminal
- default behaviour is interactive
To customize keybinds, modify the keymaps table in your config:
opts = {
keymaps = {
-- Override default run binding
["<leader>r"] = {
cmd = ":CanterRun<CR>",
desc = "Run current file"
},
-- Add new binding
["<leader>rw"] = {
cmd = ":CanterWait<CR>",
desc = "Run and wait"
}
}
}- should work with native terminal when Snacks is not available
- option to autosave before running
- refactor terminal code to a separate module
- automatically scan and resolve runners for a given file extension
- prompt to set or confirm runner when new filetype is encountered
- native support for runner flags
- better compiled language support
- should be able to compile, show build result, and run with one command
- native command to toggle or undo chmod make executable
- this plugin grew out of the custom keymap script I was using, which was in turn inspired by u/linkarzu script on r/neovim
