Neovim: Custom snacks.nvim Picker

2025-03-03
author
Rico Berger
Site Reliability Engineer

Welcome to another blog post about my dotfiles. Today, I will briefly discuss how to implement a custom picker using snacks.nvim. Inspired by a Reddit post, I wanted to create my own command palette, similar to the one in Visual Studio Code. Below, I will share the result.

Command Palette

One feature I have always liked about Visual Studio Code is the command palette, which allows you to quickly change a file's type, run code formatting, and more. Inspired by a Reddit post, I wanted to create my own command palette in Neovim, which allows me to run commands without creating a keymap, as I don't use them frequently enough.

The commands for the command palette are defined in a table, where each entry has a name and an action. The action can be a command if it starts with :, a keymap if it's a string not starting with : or a function if it's a function, similar to the actions in the snacks.nvim dashboard.

The commands are passed as items to the Snacks.picker. When a command is selected, the confirm function is executed, which runs the corresponding action in a similar way to how it is done in the snacks.nvim dashboard.

The command palette is exported as a module and registered in the keys section of the snacks.nvim plugin, so that the command palette can be opened via leader + p.

return {
  {
    "folke/snacks.nvim",
    priority = 1000,
    lazy = false,
    keys = {
      {
        "<leader>p",
        function()
          require("command-palette").show_commands()
        end,
        desc = "Command Palette",
      },
    },
  },
}
local M = {}

M.commands = {
  {
    name = "Copilot: Actions",
    action = "<leader>ca",
  },
  {
    name = "Dim: Toggle",
    action = function()
      local snacks_dim = require("snacks").dim
      if snacks_dim.enabled then
        snacks_dim.disable()
      else
        snacks_dim.enable()
      end
    end,
  },
  {
    name = "Tab: Close",
    action = ":tabclose",
  },
  {
    name = "Tab: New",
    action = ":tabnew",
  },
  {
    name = "Todo Comments: Quickfix List",
    action = ":TodoQuickFix",
  },
  {
    name = "Todo Comments: Location List",
    action = ":TodoLocList",
  },
}

function M.show_commands()
  local items = {}

  for idx, command in ipairs(M.commands) do
    local item = {
      idx = idx,
      name = command.name,
      text = command.name,
      action = command.action,
    }
    table.insert(items, item)
  end

  Snacks.picker({
    title = "Command Palette",
    layout = {
      preset = "default",
      preview = false,
    },
    items = items,
    format = function(item, _)
      return {
        { item.text, item.text_hl },
      }
    end,
    confirm = function(picker, item)
      if type(item.action) == "string" then
        if item.action:find("^:") then
          picker:close()
          return picker:norm(function()
            picker:close()
            vim.cmd(item.action:sub(2))
          end)
        else
          return picker:norm(function()
            picker:close()
            local keys = vim.api.nvim_replace_termcodes(item.action, true, true, true)
            vim.api.nvim_input(keys)
          end)
        end
      end

      return picker:norm(function()
        picker:close()
        item.action()
      end)
    end,
  })
end

return M
# neovim