/do-the-needful.nvim

Neovim task runner that uses tmux windows to do the needful please

Primary LanguageLua

do-the-needful

Task runner that uses tmux windows to do the needful please. A Telescope picker makes selecting tasks simple. Task commands can use tokens that are parsed at execution time.

do-the-needful

About

  • Tasks can be defined in in setup opts, project or global config
  • Tasks run in tmux windows with configurable options such as to close automatically or to keep current window's focus
  • Task tags make it easy to filter with Telescope picker
  • Tokens can be defined globally or scoped to a task and are parsed by an evaluated function or user input
  • When editing a new project or global tasks, a default config will be created if one doesn't exist

Screenshots

Actions picker
Actions picker (:Telescope do-the-needful)
Task selection picker
Task selection picker (:Telescope do-the-needful please)
Prompting for input
Prompting for input using ask function
Task spawned
Spawned task will close upon completion

Usage

API

require("do-the-needful").please() -- Opens task picker
require("do-the-needful").actions() -- Opens picker to do the needful or edit configs
require("do-the-needful").edit_config("project")
require("do-the-needful").edit_config("global")

Telescope opts can be passed into .please() and .actions() functions

Telescope pickers

:Telescope do-the-needful
-- Displays picker to do the needful please or to edit task configs

:Telescope do-the-needful please
-- Displays task picker

:Telescope do-the-needful project
-- Edit project config

:Telescope do-the-needful global
-- Edit global config

Features

Tasks

Tasks can be defined in 3 places:

  • Setup opts
  • Global config: .tasks.json located in vim.fn.stdpath("data")
  • Project config: .tasks.json in the project directory

Tasks are selected using a Telescope picker

Tmux windows

Tasks run in a new tmux window with the following default options:

window = {
  name = "name", -- name of tmux window
  close = false, -- close window after execution
  keep_current = false, -- keep focus on current window when running task
  open_relative = true, -- open window after/before current window
  relative = "after", -- relative direction if open_relative = true
  -- after or before
}

Task metadata

Tasks metadata can be defined to make it easier to filter with Telescope picker

tags = { "eza", "home", "files" },

Global token replacement

The following task fields are parsed for tokens

  • cmd
  • name
  • cwd

${tokens} can be defined to be replaced in task the configuration:

global_tokens = {
  ["${cwd}"] = vim.fn.cwd,
  ["${do-the-needful}"] = "please",
  ["${projectname}"] = function()
    return vim.fn.system("basename $(git rev-parse --show-toplevel)")
  end
},

Prompting for input

Tasks can be configured to prompt for input. Token values are replaced by global_tokens values or evaluated ask_functions:

Ask tokens are defined in each task's ask table (opt) or json object (project and global)

ask = { -- Used to prompt for input to be passed into task
  ["${dir}"] = {
    title = "Which directory to search", -- defaults to the name of token
    type = "function", -- function or string
    default = "get_cwd", --[[ defaults to "" if omitted.  If ask.type is a value
    other than "function", the literal value of default will be used.  If
    ask.type is "function", the named function in the ask_functions table will
    be evaluated for the default value passed into vim.ui.input ]]
  },
}

Setup

Example Lazy.nvim config

local opts = {
  tasks = {
    {
      name = "eza", -- name of task
      cmd = "eza ${dir}", -- command to run
      cwd = "~", -- working directory to run task
      tags = { "eza", "home", "files" }, -- task metadata used for searching
      ask = { -- Used to prompt for input to be passed into task
        ["${dir}"] = {
          title = "Which directory to search", -- defaults to the name of token
          type = "function", -- function or string
          default = "get_cwd", -- defaults to "".  If ask.type is string, the literal
          -- value of default will be used.  If ask.type is function the named
          -- function in the ask_functions section will be evaluated for the default
        },
      },
      window = { -- all window options are optional
        name = "Eza ~", -- name of tmux window
        close = false, -- close window after execution
        keep_current = false, -- switch to window when running task
        open_relative = true, -- open window after/before current window
        relative = "after", -- relative direction if open_relative = true
      },
    },
    {
      name = "ripgrep current directory",
      cmd = "rg ${pattern} ${cwd}",
      tags = { "ripgrep", "cwd", "search", "pattern" },
      ask = {
        ["${pattern}"] = {
          title = "Pattern to use",
          default = "error",
        },
      },
      window = {
        name = "Ripgrep",
        close = false,
        keep_current = true,
      },
    },
  },
  edit_mode = "buffer", -- buffer, tab, split, vsplit
  config_file = ".tasks.json", -- name of json config file for project/global config
  config_order = { -- default: { project, global, opts }.  Order in which
    -- tasks are aggregated
    "project", -- .task.json in project directory
    "global", -- .tasks.json in stdpath('data')
    "opts", -- tasks defined in setup opts
  },
  tag_source = true, -- display #project, #global, or #opt after tags
  global_tokens = {
    ["${cwd}"] = vim.fn.getcwd,
    ["${do-the-needful}"] = "please",
    ["${projectname}"] = function()
      return vim.fn.system("basename $(git rev-parse --show-toplevel)")
    end,
  },
  ask_functions = {
    get_cwd = function()
      return vim.fn.getcwd()
    end,
    current_file = function()
      return vim.fn.expand("%")
    end,
  },
}

return {
  "catgoose/do-the-needful.nvim",
  event = "BufReadPre",
  keys = {
    { "<leader>;", [[<cmd>Telescope do-the-needful please<cr>]], "n" },
    { "<leader>:", [[<cmd>Telescope do-the-needful<cr>]], "n" },
  },
  dependencies = "nvim-lua/plenary.nvim",
  opts = opts,
}

Telescope setup

In your Telescope setup load the do-the-needful extension

telescope.load_extension("do-the-needful")

Telescope defaults can be set in Telescope setup:

require("telescope").setup({
  ...
  extensions = {
    ["do-the-needful"] = {
      winblend = 10,
    },
  }
})

Telescope options can also be passed into please or actions to override the above set defaults:

require("do-the-needful").please({ winblend = 5 })
require("do-the-needful").actions({ prompt_title = "Actions" })

Configuration

Default setup opts

{
  log_level = "warn",
  tasks = {},
  edit_mode = "buffer",
  config = ".tasks.json",
  config_order = {
   "project",
   "global",
   "opts",
  },
  tag_source = true,
  global_tokens = {
    ["${cwd}"] = vim.fn.getcwd,
    ["${do-the-needful}"] = "please",
  },
  ask_functions = {},
}

Ask functions

Ask functions can be defined to evaluate default values for the token prompt:

ask_functions = {
  ["get_cwd"] = vim.fn.getcwd,
  ["current_file"] = function()
    return vim.fn.expand("%")
  end,
}

Ask tokens

The value for default can refer to a literal value or a defined ask_function.

If the value of ask.type is "function" the corresponding ask_function defined in setup opts will be evaluated upon task selection. This value will be used for the default value in the token prompt dialog.

In the following example the ask_function dir will be evaluated and replace the token ${dir} in the task command.

{
  "ask": {
    "${dir}": {
      "title": "Which directory?",
      "type": "function",
      "default": "dir"
    }
  }
}
...
  ask_functions = {
    dir = vim.fn.getcwd
  }
...

Global tokens defaults

Token Description Type Value
${cwd} CWD for task function vim.fn.getcwd
${do-the-needful} Do the needful string "please"

Editing project and global configs

The Telescope picker will easily let you choose which config to edit

:Telescope do-the-needful

Project config

require("do-the-needful").edit_config("project")
:Telescope do-the-needful project

Global config

require("do-the-needful").edit_config("global")
:Telescope do-the-needful global

New configs

When calling the task config editing functions if the respective .tasks.json does not exist, an example task will be created

{
  "tasks": [
    {
      "name": "",
      "cmd": "",
      "tags": [""],
      "window": {
        "name": "",
        "close": false,
        "keep_current": false,
        "open_relative": true,
        "relative": "after"
      }
    }
  ]
}

tasks JSON schema

{
  tasks: Array<{
    name: string;
    cmd: string;
    tags: string[];
    ask: {
      "${token}": {
        title: string;
        type: "string" | "function";
        default: string;
      };
    };
    window: {
      name: string;
      close: boolean;
      keep_current: boolean;
      open_relative: boolean;
      relative: "before" | "after";
    };
  }>;
}

Todo

  • Add api to add new task to a config
  • Zellij and terminal support
  • Support split in config
  • Support sending to task window on autocmd

Extra

Neovim

My other neovim projects

Tmux

Tmux theme:

kanagawa-tmux