Skip to content

Commit 0cb562a

Browse files
committed
initial working commit
1 parent ddf2e66 commit 0cb562a

File tree

5 files changed

+281
-2
lines changed

5 files changed

+281
-2
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 156 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,156 @@
1-
# canter
2-
lightweight neovim code runner plugin that works with Snacks.terminal
1+
# canter.nvim
2+
3+
## Introduction
4+
5+
lightweight neovim code runner plugin that works with Snacks.nvim terminal
6+
7+
## Requirements
8+
9+
- Neovim (>= 0.7)
10+
- [Snacks.nvim](https://github.com/folke/snacks.nvim) (>= 2.11.0)
11+
- needed for Terminal
12+
- [which-key](https://github.com/folke/which-key.nvim) (>= 3.15.0)
13+
- only needed for custom leader key menu group name
14+
15+
## Installation
16+
17+
### With [lazy.nvim](https://github.com/folke/lazy.nvim)
18+
19+
```lua
20+
{
21+
"dchae/canter.nvim",
22+
opts = {}
23+
},
24+
```
25+
26+
### Other package managers
27+
28+
Install normally, and add this line to your `init.lua`:
29+
30+
```lua
31+
require("canter.nvim").setup()
32+
```
33+
34+
## Configuration
35+
36+
Pass your config table into the `setup()` function or `opts` if you use lazy.nvim.
37+
38+
### Options
39+
40+
#### Default options
41+
```lua
42+
opts = {
43+
-- File extension to runner/interpreter mapping
44+
runners = {},
45+
46+
-- Terminal configuration (passed to Snacks.nvim)
47+
Snacks_terminal_opts = {
48+
win = {
49+
position = "bottom",
50+
relative = "editor"
51+
},
52+
interactive = false
53+
},
54+
55+
-- Default keymaps (can be overridden)
56+
keymaps = {
57+
["<leader><cr><cr>"] = {
58+
cmd = ":CanterRun<CR>",
59+
desc = "Run current file (Auto)"
60+
},
61+
["<leader><cr>w"] = {
62+
cmd = ":CanterWait<CR>",
63+
desc = "Run current file (Wait)"
64+
}
65+
}
66+
}
67+
```
68+
69+
**NOTE** - does not come with runners by default, you must add your own.
70+
71+
- `runners`: table `([file_extension] = runner/interpreter)`
72+
- `Snacks_terminal_opts`: table of options passed to Snacks.nvim terminal
73+
- `win`: window positioning options
74+
- `interactive`: whether terminal starts in interactive mode
75+
- `keymaps`: table of keybindings and their descriptions
76+
77+
#### Example config
78+
```lua
79+
opts = {
80+
runners = {
81+
["js"] = "node",
82+
["rb"] = "ruby",
83+
["py"] = "python"
84+
},
85+
Snacks_terminal_opts = {
86+
win = {
87+
position = "right", -- Change terminal position to right
88+
relative = "editor"
89+
},
90+
interactive = true -- Always start in interactive mode
91+
}
92+
}
93+
```
94+
95+
## Usage
96+
97+
"Run current file (Auto)"
98+
99+
- if file contains a shebang on the first line, the plugin will attempt to:
100+
1. make the file executable via `chmod`
101+
2. execute the current file
102+
- else, if the file has a corresponding runner
103+
1. execute the current file via its runner in `Snacks.terminal`
104+
105+
"Run current file (Wait)"
106+
107+
- same as above, but stops before actually executing so you can add flags or confirm the command before pressing enter.
108+
- necessarily, the terminal is interactive by default in this mode.
109+
110+
### Shebang example with node
111+
`test.js`
112+
```js
113+
#!/usr/bin/env node
114+
115+
console.log("Hello, world!");
116+
// "Hello, world!"
117+
```
118+
119+
### Default Keybinds
120+
121+
All keybinds can be customized in the config. The defaults are:
122+
123+
- `<Leader><CR><CR>`: Run current file (Auto)
124+
- executes current file in terminal
125+
- default behaviour is non-interactive; file will run and then any key will dismiss terminal
126+
- `<Leader><CR>w`: Run current file (Wait)
127+
- loads current file run command in terminal
128+
- default behaviour is interactive
129+
130+
To customize keybinds, modify the `keymaps` table in your config:
131+
132+
```lua
133+
opts = {
134+
keymaps = {
135+
-- Override default run binding
136+
["<leader>r"] = {
137+
cmd = ":CanterRun<CR>",
138+
desc = "Run current file"
139+
},
140+
-- Add new binding
141+
["<leader>rw"] = {
142+
cmd = ":CanterWait<CR>",
143+
desc = "Run and wait"
144+
}
145+
}
146+
}
147+
```
148+
149+
## Roadmap
150+
151+
- should work with vsplit terminal when Snacks is not available
152+
- automatically scan and resolve runners for a given file extension
153+
- better support for runner flags
154+
155+
## Credits
156+
- plugin inspired by keymap script by u/linkarzu on r/neovim

lua/canter/init.lua

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
-- Helper functions
2+
local function get_current_file_path()
3+
local filepath = vim.fn.expand("%:p:.") -- Get the current filepath, relative to current dir if possible
4+
return vim.fn.shellescape(filepath)
5+
end
6+
7+
---@param runImmediately (boolean)
8+
---@param opts (table)
9+
local function run_current_file(runImmediately, opts)
10+
local filepath = get_current_file_path()
11+
local file_extension = vim.fn.expand("%:e")
12+
local first_line = vim.fn.getline(1)
13+
14+
local runners = opts.runners
15+
local snacks_opts = opts.Snacks_terminal_opts
16+
17+
if string.match(first_line, "^#!/") then -- If first line starts with shebang (e.g. #!/usr/bin/env node)
18+
vim.cmd("!chmod +x " .. filepath) -- Make the file executable
19+
20+
if not string.match(filepath, "/") then -- if filepath is relative add "./"
21+
filepath = "./" .. filepath
22+
end
23+
24+
if runImmediately then
25+
Snacks.terminal(filepath, snacks_opts)
26+
vim.cmd("startinsert")
27+
else
28+
Snacks.terminal.open()
29+
vim.cmd("startinsert")
30+
vim.api.nvim_input(filepath)
31+
end
32+
elseif runners[file_extension] then -- if we have a valid runner for the filetype
33+
local runner = runners[file_extension] -- Get runner
34+
local cmd = runner .. " " .. filepath -- Runner and filepath (e.g. node script.js)
35+
if runImmediately then
36+
Snacks.terminal(cmd, snacks_opts)
37+
vim.cmd("startinsert")
38+
else
39+
Snacks.terminal.open()
40+
vim.cmd("startinsert")
41+
vim.api.nvim_input(cmd)
42+
end
43+
else
44+
vim.cmd("echo 'Error: Could not resolve interpreter or find shebang line.'")
45+
end
46+
end
47+
48+
local default_opts = { -- default options
49+
runners = {},
50+
Snacks_terminal_opts = {
51+
win = {
52+
position = "bottom",
53+
relative = "editor"
54+
},
55+
interactive = false
56+
},
57+
keymaps = {
58+
["<leader><cr><cr>"] = {
59+
cmd = ":CanterRun<CR>",
60+
desc = "Run current file (Auto)"
61+
},
62+
["<leader><cr>w"] = {
63+
cmd = ":CanterWait<CR>",
64+
desc = "Run current file (Wait)"
65+
}
66+
}
67+
}
68+
69+
local M = {}
70+
71+
---@param user_opts (table | nil)
72+
function M.setup(user_opts)
73+
user_opts = user_opts or {}
74+
local opts = vim.tbl_deep_extend("force", default_opts, user_opts)
75+
76+
local function run_current_file_auto()
77+
run_current_file(true, opts) -- Pass the entire opts table, not just runners
78+
end
79+
local function run_current_file_wait()
80+
run_current_file(false, opts) -- Pass the entire opts table, not just runners
81+
end
82+
83+
-- Add keymaps if configured
84+
if opts.keymaps then
85+
for key, mapping in pairs(opts.keymaps) do
86+
vim.keymap.set('n', key, mapping.cmd, {
87+
desc = mapping.desc,
88+
silent = true
89+
})
90+
end
91+
end
92+
93+
-- Register which-key group
94+
local ok, wk = pcall(require, "which-key")
95+
if ok then
96+
wk.add({{
97+
"<leader><cr>",
98+
group = "Canter (run code)"
99+
}})
100+
end
101+
102+
local commands = {
103+
CanterRun = run_current_file_auto,
104+
CanterWait = run_current_file_wait
105+
}
106+
107+
for cmd, fn in pairs(commands) do
108+
vim.api.nvim_create_user_command(cmd, fn, {
109+
nargs = 0
110+
})
111+
end
112+
end
113+
114+
return M

plugin/canter.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Prevent loading multiple times
2+
if vim.g.loaded_canter == 1 then
3+
return
4+
end
5+
vim.g.loaded_canter = 1
6+
7+
-- Require canter
8+
require("canter").setup()

stylua.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
indent_type = "Spaces"
2+
indent_width = 3
3+
column_width = 80

0 commit comments

Comments
 (0)