greyjoy.nvim
greyjoy.nvim
is a highly extensible and pluggable pattern/file based launcher/runner.
Neovim >=0.11
Install the plugin with your package manager:
[!important] Greyjoy is pluggable and has no default extensions so in order run anything you need at least one extension enabled Check the extensions section.
[!tip] It's a good idea to run
:checkhealth greyjoy
to see if everything is set up correctly.
Basic example using the generic plugin to get started
{
"desdic/greyjoy.nvim",
config = function()
greyjoy.setup({
extensions = {
generic = {
commands = {
["run {filename}"] = { command = { "python3 {filename}" }, filetype = "python" },
},
},
},
})
greyjoy.load_extension("generic")
end
}
Once installed and reloaded you can use
Command | Description | Requires |
---|---|---|
:Greyjoy |
Show run list | None |
:Greyjoyedit |
Edit command from run list (only persist during session) | None |
:GreyjoyRunLast |
Run last command | None |
:GreyjoyTelescope |
Show/Edit run list | Telescope |
:GreyjoyFzf |
Show/Edit run list | Fzf-lua |
Greyjoy uses vim.ui.select
and built-in terminal/buffers but integration with a few plugins are available but requires dependencies
Plugin |
---|
Toggleterm |
Telescope |
Fzf-lua |
Available when installed.
{
ui = {
buffer = { -- width and height for the buffer output
width = math.ceil(math.min(vim.o.columns, math.max(80, vim.o.columns - 20))),
height = math.ceil(math.min(vim.o.lines, math.max(20, vim.o.lines - 10))),
},
toggleterm = { -- by default no size is defined for the toggleterm by
-- greyjoy.nvim it will be dependent on the user configured size for toggle
-- term.
size = nil,
},
term = {
width_pct = 0.2, -- 20% of screen
},
telescope = {
keys = {
select = "<CR>", -- enter
edit = "<C-e>", -- CTRL-e
},
}
},
toggleterm = {
-- default_group_id can be a number or a function that takes a string as parameter.
-- The string passed as parameter is the name of the plugin so its possible to do logic based
-- on plugin name and function should always return a number like:
-- default_group_id = function(plugin) return 1 end
default_group_id = 1,
},
enable = true,
border = "rounded", -- style for vim.ui.selector
style = "minimal",
show_command = false, -- show command to run in menu
show_command_in_output = true, -- show command that was just executed in output
patterns = {".git", ".svn"}, -- patterns to find the root of the project
output_result = require("greyjoy.terminals").buffer,
extensions = {}, -- extensions configurations
run_groups = {}, -- See `Run groups`
overrides = {}, -- Stores internal overrides of commands
}
Per default all plugins use the same terminal/buffer but this behaviour can be overridden if you are using toggleterm
.
Toggleterm supports multiple terminals so you can group extensions into having different terminal.
Specify group_id
or create a function to assign number based on plugin name.
So if you want all extensions to run under id
1 (default) but the docker_compose
you would like to have another group you can configure it via
extensions = {
docker_compose = { group_id = 2 },
},
Now all docker compose's exec is running in a secondary terminal (group_id 2) and all the others in group_id 1
Some extension can be slow or not always required so its possible to group extensions into groups.
{
...
run_groups = { fast = { "generic", "makefile", "cargo", "docker_compose" } },
...
}
Invoking :Greyjoy fast
now only runs the defined extensions.
Hooks can be invoked before and after a command and no default ones are defined.
An example of running a target via a makefile can put the errors in the quickfix list just like running it via the :make
:
return {
"desdic/greyjoy.nvim",
keys = {
{ "<leader>gr", "<cmd>Greyjoy<CR>", desc = "[G]reyjoy [r]un" },
},
cmd = { "Greyjoy", "Greyedit", "GreyjoyRunLast" },
config = function()
local greyjoy = require("greyjoy")
local tmpmakename = nil
local pre_make = function(command)
tmpmakename = os.tmpname()
table.insert(command.command, "2>&1")
table.insert(command.command, "|")
table.insert(command.command, "tee")
table.insert(command.command, tmpmakename)
end
-- A bit hacky solution to checking when tee has flushed its file
local post_make = function()
vim.cmd(":cexpr []")
local cmd = { "inotifywait", "-e", "close_write", tmpmakename }
local job_id = vim.fn.jobstart(cmd, {
stdout_buffered = true,
on_exit = function(_, _, _)
if tmpmakename ~= nil then
vim.cmd(":cgetfile " .. tmpmakename)
os.remove(tmpmakename)
end
end,
})
if job_id <= 0 then
vim.notify("Failed to start inotifywait!")
end
end
greyjoy.setup({
ui = {
term = {
height = 10,
},
},
output_results = require("greyjoy.terminals").term,
extensions = {
makefile = {
pre_hook = pre_make,
post_hook = post_make,
},
}
})
greyjoy.load_extension("makefile")
end,
}
Two types of extensions are currently supported. global
always runs and file
will only run on specific files being present.
Plugin | Type | Description |
---|---|---|
Generic | global | Handles condition based running of commands |
File | global | Handles running command bases on configuration file per project |
Makefile | file | Parses makefile and lists targets and runable |
vscode_tasks | file | Parses vscode tasks file |
kitchen | file | Handles kitchen targets |
cargo | file | Gives a default run list when Cargo.toml is available |
docker_compose | file | List docker compose targets as runable |
generic
extension is a global module that does not take into account if we are in a project (found via the patterns). Commands to run can be matched using filetype
, filename
, filepath
Example:
generic = {
commands = {
["run {filename}"] = {
command = {"python3 {filename}"}, -- can be a single string or multiple but is still a single command
filetype = "python", -- only runs if filetype is python and filename is test.py
filename = "test.py"
},
["run {filename}"] = {
command = {"go", "run", "{filename}"},
filetype = "go" -- run if filetype is go
},
["cmake --build target"] = {
command = { "cd", "{rootdir}", "&&", "cmake", "--build", "{rootdir}/target" },
condition = function(n) -- custom conditions can be added
return condition.file_exists("CMakeLists.txt", n)
and condition.directory_exists("target", n)
end,
},
["cmake -S . -B target"] = {
command = { "cd", "{rootdir}", "&&", "cmake", "-S", ".", "-B", "{rootdir}/target" },
condition = function(n)
return condition.file_exists("CMakeLists.txt", n)
and not condition.directory_exists("target", n)
end,
},
...
}
},
file
extension is a global module that only reads greyjoy.json
(can be changed) from within your project
Example configuration:
file = {
filename = "myrunner.json"
},
The configuration file is a simple key value. Value can be a string or an array (same result).
{
"build all": ["make", "build"],
"do cleanup": "make clean"
}
The makefile
extension is file based and will only trigger if a Makefile
is located in the project root. It finds all targets for a Makefile
.
[!important] requires
make
andawk
to work.
The vscode_tasks
extension is file based and will only trigger if .vscode/tasks.json
exists in the project root
[!important] requires
kitchen
andawk
to work.
[!tip] kitchen is quite slow so its possible to create a group without it and only use it when needed.
The kitchen
extension is also file based and looks for .kitchen.yml
(chefdk or cinc-workstation).
This extension can be configured to only include specific targets
extensions = {
kitchen = {
targets = { "converge", "verify", "destroy", "test", "login" },
include_all = false,
}
}
The cargo
extension is file based and looks for Cargo.toml
and requires cargo
[!important] requires
cargo
.
The docker_compose
extension is file based and looks for docker-compose.yml
.
[!important] requires
docker-compose
ordocker compose
.
Simple substitutions can be use to make more specific runners
variable | expands to |
---|---|
{filename} | current filename |
{filepath} | path of current file |
{rootdir} | path of root (configured via patterns in config) |
Condition functions can be applied to the generic
extension in case the built-in isn't enough.
Function | Description |
---|---|
require("greyjoy.conditions").file_exists | Check if file exists |
require("greyjoy.conditions").directory_exists | Check if directory exists |
Displaying the output of a command is based on the function defined in output_result
. Default it just outputs to a buffer but you can write a function for your custom need.
Function | Description |
---|---|
require("greyjoy.terminals").buffer | Default, outputs into a buffer |
require("greyjoy.terminals").term | Opens a terminal in the bottom |
require("greyjoy.terminals").toggleterm | Use toggleterm (requires toggleterm) |
Documentation and a more comprehensive example can be found in the documentation
Pull requests are very welcome! Make sure your patches are well tested. Ideally create a topic branch for every separate change you make. For example:
git checkout -b my-new-feature
)git commit -am 'Added some feature'
)git push origin my-new-feature
)Thanks to
@TheSafdarAwan for PR #22
@costowell for PR #32