๐Ÿš€ greyjoy.nvim

August 15, 2025 ยท View on GitHub

Test

greyjoy.nvim is a highly extensible and pluggable pattern/file based launcher/runner.

โšก๏ธ Requirements

Neovim >=0.11

๐Ÿ“ฆ Installation

Install the plugin with your package manager:

lazy.nvim

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

CommandDescriptionRequires
:Greyjoy Show run listNone
:Greyjoyedit Edit command from run list (only persist during session)None
:GreyjoyRunLastRun last commandNone
:GreyjoyTelescope Show/Edit run listTelescope
:GreyjoyFzf Show/Edit run listFzf-lua
Optional dependencies

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.

โš™๏ธ Configuration

Default options
{
  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

๐Ÿซ‚ Run groups

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

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,
}

โœจ Extensions

Two types of extensions are currently supported. global always runs and file will only run on specific files being present.

PluginTypeDescription
GenericglobalHandles condition based running of commands
FileglobalHandles running command bases on configuration file per project
MakefilefileParses makefile and lists targets and runable
vscode_tasksfileParses vscode tasks file
kitchenfileHandles kitchen targets
cargofileGives a default run list when Cargo.toml is available
docker_composefileList docker compose targets as runable
Generic extension

Generic extension

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

File extension

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"
}

Makefile extension

Makefile extension

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 and awk to work.

Vscode_tasks extension

Vscode_tasks extension

The vscode_tasks extension is file based and will only trigger if .vscode/tasks.json exists in the project root

Kitchen extension

Kitchen extension

Important

requires kitchen and awk 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,
    }
}
Cargo extension

Cargo extension

The cargo extension is file based and looks for Cargo.toml and requires cargo

Important

requires cargo.

Docker_compose extension

Docker_compose extension

The docker_compose extension is file based and looks for docker-compose.yml.

Important

requires docker-compose or docker compose.

๐Ÿงฉ Variables

Simple substitutions can be use to make more specific runners

variableexpands to
{filename}current filename
{filepath}path of current file
{rootdir}path of root (configured via patterns in config)

๐Ÿค Helper functions

๐Ÿคณ Conditions

Condition functions can be applied to the generic extension in case the built-in isn't enough.

FunctionDescription
require("greyjoy.conditions").file_existsCheck if file exists
require("greyjoy.conditions").directory_existsCheck if directory exists
๐Ÿ–ฅ๏ธ Terminal/Buffers

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.

FunctionDescription
require("greyjoy.terminals").bufferDefault, outputs into a buffer
require("greyjoy.terminals").termOpens a terminal in the bottom
require("greyjoy.terminals").toggletermUse toggleterm (requires toggleterm)

๐Ÿ“š Documentation

Documentation and a more comprehensive example can be found in the documentation

Development

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:

  1. Fork the repo
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Thank you / shout outs

  • The extension manager is heavily inspired by the manager in Telescope.nvim

Credits

Thanks to

@TheSafdarAwan for PR #22

@costowell for PR #32