Skip to content

Repository files navigation

Minibuffer

Lua

An experimental general purpose interactive interface for neovim.

minibuffer-demo.mp4

NOTE:

  • This plugin is still under development and will see some breaking changes (feel free to pin to a commit)
  • It depends on an experimental feature in neovim (vim._core.ui2)

This plugin provides an API for an optional unified interactive buffer interface. Instead of having one plugin open a floating pop-up for fuzzy file search, another showing a completion menu at the bottom, another drawing commandline completions above the status bar and yet another drawing a general purpose picker in a different location, you can choose to have one place where interactive input can be shown that feels native to the editor and is predictable. This includes:

  • Running commands with completion.
  • Fuzzy finding files or buffers.
  • Searching text across a project.
  • Input prompts for LSP or Git actions.
  • Even interactive plugin UIs (think Telescope, fzf, mini.pick, etc).
  • Display timely content (think which-key.nvim or mini.clue)

For Neovim, something like this could replace the ad-hoc popup/floating windows many plugins use, giving us a consistent workflow: a single expandable buffer for all kinds of input and interactive tasks.

Goal

The goal of this plugin is to eventually put some simple version of this into neovim core if desired by the maintainers. See this issue

I have integration implementations in lua/minibuffer/integrations with existing plugins.

Prerequisites

  • neovim >= 0.12
  • ui2 enable somewhere early in your init.lua:
require("vim._core.ui2").enable({ enable = true, msg = { targets = "msg" } })

Installation

NOTE: You will want to load minibuffer.nvim as one of your earliest plugins (DO NOT LAZY LOAD).

  • vim.pack
vim.pack.add({
  {
    src = "https://github.com/simifalaye/minibuffer.nvim",
  },
})

local minibuffer = require("minibuffer")

vim.ui.select = require("minibuffer.builtin.ui_select")
vim.ui.input = require("minibuffer.builtin.ui_input")

vim.keymap.set("n", "<leader><CR>", function()
  minibuffer.resume(true)
end)
{
  "simifalaye/minibuffer.nvim",
  init = function()
    local minibuffer = require("minibuffer")

    vim.ui.select = require("minibuffer.builtin.ui_select")
    vim.ui.input = require("minibuffer.builtin.ui_input")

    vim.keymap.set("n", "<leader><CR>", function()
      minibuffer.resume(true)
    end)
  end,
}

Configuration

This plugin can be configured by using vim.g.minibuffer (preferably set before the plugin loads).

-- Default configuration
vim.g.minibuffer = {
  dynamic_window_resize = true, -- Shrink other windows when the minibuffer is expanded
  cmd = {
    -- NOTE: minibuffer cmd is not compatible with command line plugins that force `wildtrigger()` each `wildchar` such as mini.cmdline
    enabled = true, -- Enable command line wildmenu replacement through the minibuffer
    autotrigger = true, -- Display completion suggestions as you type
    dynamic_height = false, -- Whether the completion window should shrink as items disappear.
    max_height = 15, -- Maximum height when using the command line
   },
}

Builtin

Custom Pickers

vim.keymap.set("n", "<leader>;", function()
  require("minibuffer.builtin.history")({ type = "cmd" })
end, { desc = "Find command history" })
vim.keymap.set("n", "<leader>?", function()
  require("minibuffer.builtin.history")({ type = "search" })
end, { desc = "Find command history" })
vim.keymap.set("n", "<leader>'", function()
  require("minibuffer.builtin.marks")()
end, { desc = "Find mark" })
vim.keymap.set(
  "n",
  "<leader>/",
  require("minibuffer.builtin.live-grep"),
  { desc = "Live grep" }
)

vim.keymap.set(
  "n",
  "<leader>fb",
  require("minibuffer.builtin.buffers"),
  { desc = "Find buffers" }
)
vim.keymap.set(
  "n",
  "<leader>ff",
  require("minibuffer.builtin.files"),
  { desc = "Find files" }
)
vim.keymap.set("n", "<leader>fd", function()
  require("minibuffer.builtin.diagnostics")({ scope = "buffer" })
end, { desc = "Find diagnostics" })
vim.keymap.set("n", "<leader>fD", function()
  require("minibuffer.builtin.diagnostics")({ scope = "workspace" })
end, { desc = "Find diagnostics (workspace)" })
vim.keymap.set(
  "n",
  "<leader>fg",
  require("minibuffer.builtin.git-files"),
  { desc = "Find gitfiles" }
)
vim.keymap.set("n", "<leader>fl", function()
  require("minibuffer.builtin.list")({ type = "loclist" })
end, { desc = "Find in loclist" })
vim.keymap.set(
  "n",
  "<leader>fm",
  require("minibuffer.builtin.manpages"),
  { desc = "Find manpages" }
)
vim.keymap.set("n", "<leader>fo", function()
  require("minibuffer.builtin.oldfiles")({ cwd = vim.fn.getcwd() })
end, { desc = "Find oldfiles (cwd)" })
vim.keymap.set(
  "n",
  "<leader>fO",
  require("minibuffer.builtin.oldfiles"),
  { desc = "Find oldfiles (all)" }
)
vim.keymap.set("n", "<leader>fq", function()
  require("minibuffer.builtin.list")({ type = "quickfix" })
end, { desc = "Find in quickfix" })

Interesting things you can do when using the minibuffer command line

Doom-emacs M-x file explorer picker

vim.keymap.set("n", "<leader>.", function()
  local buf_path = vim.api.nvim_buf_get_name(0)
  local dir = vim.fn.fnamemodify(buf_path, ":p:h")
  if dir == "" then
    dir = "."
  end
  local cmd = ":e " .. vim.fn.fnameescape(dir) .. "/"
  vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(cmd, true, false, true), "n", true)
end, { desc = "Find file" })

Pick help-tags

vim.keymap.set("n", "<leader>hh", ":h ", { desc = "Help" })

Integrations with existing plugins

Two integration types can be seen below:

  • Using the backend of a plugin with the minibuffer frontend APIs (as seen in the fff.nvim example below)
  • Allowing each plugin to draw their own window but configuring the window settings to put it into the minibuffer container (as seen in the which-key.nvim, mini.pick and fzf.lua examples below)

When possible, the first option is preferred. Some plugins don't expose their data fetching code through their public APIs and in such cases the second option can be used.

In the case of the second option, I have provided some wrappers for the setup functions for each plugin which ensures we have the necessary options set for minibuffer to integrate with the plugin. Feel free to take a look at what options are used in the lua/minibuffer/integrations files.

NOTE: When using lazy.nvim, use config instead of opts to setup your options

FFF.nvim

fff nvim-integration
-- NOTE: after loading plugin
local fff_mb = require("minibuffer.integrations.fff")

vim.keymap.set("n", "<leader><leader>", function()
  fff_mb.file_search({})
end, { desc = "FFFind" })

vim.keymap.set("n", "<leader>/", function()
  fff_mb.content_search({})
end, { desc = "FFFGrep" })

Which-key.nvim

which-key nvim-integration
local opts = {}
local ok, mb_wk = pcall(require, "minibuffer.integrations.which-key")
if ok then
  mb_wk(opts)
else
  require("which-key").setup(opts)
end

mini-pick.nvim

mini pick-integration
local opts = {}
local ok, mb_pick = pcall(require, "minibuffer.integrations.mini-pick")
if ok then
  mb_pick(opts)
else
  require("mini.pick").setup(opts)
end

fzf.lua

local opts = {}
local ok, mb_fzf = pcall(require, "minibuffer.integrations.fzf")
if ok then
  mb_fzf(opts)
else
  require("fzf-lua").setup(opts)
end

lualine

This allows the statusline to display the active statusline for the correct window when the minibuffer is opened:

local opts = {}
local ok, mb_lualine = pcall(require, "minibuffer.integrations.lualine")
if ok then
  mb_lualine(opts)
else
  require("lualine").setup(opts)
end

Custom Statusline Integration

You may notice that when the minibuffer is open in an interactive session (such as select or input), the 'inactive' statusline is shown. This is because you are focussed on the minibuffer window (which doesn't draw another statusline) and so you need to tell your statusline code which window to draw the active statusline for. Here's a simple function you can use to determine whether to draw the active or inactive statusline for a given window:

local function win_is_active()
  local ok, mb = pcall(require, "minibuffer")
  local winid = vim.api.nvim_get_current_win()
  local curwin = (ok and mb.get_active_window()) or tonumber(vim.g.actual_curwin)
  return winid == curwin
end

You can use the function like this in your custom statusline:

local function statusline()
  if win_is_active() then
    -- Display your active statusline
    return "%f %m %= %l:%c"
  else
    -- Display your inactive statusline
    return "%f %m"
  end
end

_G.my_statusline = statusline

vim.go.statusline = "%!v:lua.my_statusline()"

Developer Notes

Find all information and API using: :h minibuffer For examples on how the API can be used, see lua/minibuffer/builtin/*

About

A minimal, extensible minibuffer interface for Neovim. Provides an interactive input area for commands, prompts, and UI interactions, fully scriptable in Lua.

Resources

Stars

140 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages