*mini.input* Get user input MIT License Copyright (c) 2026 Evgeni Chasnovski ------------------------------------------------------------------------------ *MiniInput* Features: - Get user input with fully customizable key and view handling. - Built-in configurable views as floating window, statusline/tabline/winbar, virtual line/text. - Implementation is non-blocking but waits to return the input. It also works in any mode without requiring mode change. See |MiniInput-lifecycle|. - |vim.ui.input()| implementation. To adjust, use |MiniInput.ui_input()| or save-restore `vim.ui.input` manually after calling |MiniInput.setup()|. Sources with more details: - |MiniInput.get()| - |MiniInput.default_key()| - |MiniInput-state| - |MiniInput-examples| - |MiniInput-in-other-plugins| (for plugin authors) # Setup ~ This module needs a setup with `require('mini.input').setup({})` (replace `{}` with your `config` table). It will create global Lua table `MiniInput` which you can use for scripting or manually (with `:lua MiniInput.*`). See |MiniInput.config| for `config` structure and default values. You can override runtime config settings locally to buffer inside `vim.b.miniinput_config` which should have same structure as `MiniInput.config`. See |mini.nvim-buffer-local-config| for more details. # Comparisons ~ - [folke/snacks.nvim#input](https://github.com/folke/snacks.nvim): - Both provide |vim.ui.input()| implementation. - Has asynchronous implementation (i.e. does not wait for user to finish input), while this module has synchronous non-blocking implementation. - Uses floating window and forced Insert mode. This module allows more view customizations and can be used in any mode without interruptions. - |input()|: - Both are synchronous. - Both allow custom highlight and completion. - This module also allows supplying input scope and visibility, customizing keys and view. # Highlight groups ~ *MiniInput-hl-groups* - `MiniInputAdded` - added text during completion navigation. - `MiniInputBorder` - border of a |MiniInput.gen_view.floatwin()| handler. - `MiniInputCaret` - caret symbol shown in a prompt area. - `MiniInputHide` - input is hidden, usually used instead of `MiniInputPrompt`. - `MiniInputHint` - hints shown during completion navigation. - `MiniInputNormal` - basic foreground/background. - `MiniInputPrompt` - input prompt (intention of the input). - `MiniInputSpecial` - special keys (like literal `\t`, `\n`, etc.) in input. # Using in other plugins ~ *MiniInput-in-other-plugins* - Prefer using |vim.ui.input()| for more user coverage. Use |MiniInput.get()| only when getting input synchronously is absolutely necessary. - Perform a `_G.MiniInput ~= nil` check before using any feature. This ensures that user explicitly set up the module. ------------------------------------------------------------------------------ *MiniInput-state* Information about the state of the input. It is passed as a handler argument. Use |MiniInput.get_state()| to get information about the current state (if any). A table with the following fields: - `(number)` - character (not byte) index at which to modify input. Should be from 1 (prepend input) to "input width plus 1" (append input). - `(table|nil)` - information about active completion navigation. If present, it means that completion navigation is in action. Its fields describe the state of navigation: - `(string)` - reference text to the left of caret at the start of completion. Used to compute candidates. Can be empty string. - `(number)` - identifier of current completion item. Can be zero to mean that the base is shown. If not zero, must mean that a `items[id]` candidate is now shown to the left of caret as the part of the input. - `(table)` - string array of completion candidates. May be empty. Are intended to fully replace , so should contain it in any sense. - `(string)` - completion method. Like `"default"`, `"history"`, etc. Can be `""` (empty string) to mean "default method of complete handler". - `(table)` - any information to be reused within the same input session. Note: handlers should not change fields that they don't "own". - `(errmsg)` - first error message caught during input process. - `(table|nil)` - information about current input highlighting. If absent, the whole input is highlighted using `MiniInputNormal` group. Should be an array of highlight ranges. They might be not ordered, overlap, go outside of input width. It is up to the view handler to decide how to interpret them. See |MiniInput.state_to_chunks()| for a helper. Fields of a single highlight range: - `(number)` - character index (one-indexed) of range start. - `(number)` - character index (one-indexed) of range end (inclusive). Should not be smaller than . Can be |math.huge|. - `(string)` - highlight group to use for highlighting. - `(string)` - current user input. Returned by |MiniInput.get()|. - `(table)` - input options, same as in |MiniInput.get()|. - `(string)` - intention of the input. May be empty. - `(string)` - one of `"start"`, `"progress"`, `"accept"`, `"cancel"`. ------------------------------------------------------------------------------ *MiniInput-examples* # General ~ ## Initial input ~ Use `opts.init_keys` to imitate the initial state of the input: >lua MiniInput.get({ init_keys = { 'Default' } }) < ## Custom mappings ~ Override `handlers.key` in |MiniInput.config|: >lua local key_handler = function(state, key) -- - move caret to start of line if key == '\1' then state.caret = 1 -- - clear all input elseif key == vim.keycode('') then state.input, state.caret = '', 1 else -- IMPORTANT: Fall back to processing as usual return MiniInput.default_key(state, key) end end require('mini.input').setup({ handlers = { key = key_handler } }) < ## Basic command line ~ An alternative |Command-line| with highlighting and completion: >lua -- Construct reusable `MiniInput.get()` options local cmdline_opts = { prompt = 'Command', scope = 'editor' } -- - Highlight using bundled Vim tree-sitter parser and default handler local highlight_vim = MiniInput.gen_highlight.treesitter('vim') local highlight_cmdline = function(state) state = highlight_vim(state) or state return MiniInput.default_highlight(state) or state end cmdline_opts.handlers = { highlight = highlight_cmdline } -- - Complete as if it is Command line input cmdline_opts.completion = 'cmdline' -- Create a mapping for `:` local input_cmdline = function() local cmd = MiniInput.get(cmdline_opts) if cmd ~= nil then vim.cmd(cmd) end end vim.keymap.set('n', ':', input_cmdline) < # Handlers ~ ## Key ~ Perform custom actions based on arbitrary conditions: >lua local key_handler = function(state, key) -- Adjust prompt state.opts.prompt = state.opts.prompt:gsub('[?:]%s*$', '') -- Adjust scope if state.opts.prompt == 'Editor action' then state.opts.scope = 'editor' end -- Hide from view and history if state.opts.prompt:find('[Pp]assword') ~= nil then state.opts.hide = true end -- IMPORTANT: Process as usual state = MiniInput.default_key(state, key) or state -- Auto fill and accept if state.input == 'AF' then state.input, state.status = 'Autofilled input', 'accept' end end require('mini.input').setup({ handlers = { key = key_handler } }) < ## View ~ Show no view: >lua require('mini.input').setup({ handlers = { view = function() end } }) < Compute initial style depending on scope: >lua local input = require('mini.input') local view_virtline = input.gen_view.virtual({ style = 'above' }) local view_tabline = input.gen_view.uiline({ style = 'tabline' }) local view_winbar = input.gen_view.uiline({ style = 'winbar' }) local view_handler = function(state) -- NOTE: does not support interactive scope change local scope, view = state.opts.scope, view_tabline if scope == 'buffer' or scope == 'window' then view = view_winbar end if scope == 'cursor' or scope == 'line' then view = view_virtline end return view(state) end input.setup({ handlers = { view = view_handler } }) < Change symbols for caret and hidden input: see |MiniInput.gen_view|. ------------------------------------------------------------------------------ *MiniInput.setup()* `MiniInput.setup`({config}) Module setup Parameters ~ {config} `(table|nil)` Module config table. See |MiniInput.config|. Usage ~ >lua require('mini.input').setup() -- use default config -- OR require('mini.input').setup({}) -- replace {} with your config table < ------------------------------------------------------------------------------ *MiniInput.config* `MiniInput.config` Defaults ~ >lua MiniInput.config = { -- Functions that control input lifecycle handlers = { -- Compute completion candidates complete = nil, -- Compute highlighting of current input highlight = nil, -- Handle input start, every key press, and input end key = nil, -- Show current input state view = nil, }, -- Default input scope: cursor/line/buffer/window/tabpage/editor/project scope = 'editor', } < # Handlers ~ *MiniInput.config.handlers* `config.handlers` defines functions that are applied during |MiniInput-lifecycle|. Every handler takes |MiniInput-state| as the first argument and is expected to either modify it in place or return a new state table. Use |MiniInput.apply_handler()| to apply a handler for a given |MiniInput-state|. They can be set up as part of the config (will be used as default) or passed directly as a part of |MiniInput.get()| call. ## Complete ~ `handlers.complete` is a handler intended to compute completion suggestions. Takes |MiniInput-state| and `method` as arguments and is expected to modify field. Default: |MiniInput.default_complete()|. Only `complete.base` and `complete.items` are expected to be set by a complete handler. Setting and modifying other fields (`complete.id` and `complete.method`) is done in other handlers and as part of |MiniInput.apply_handler()|. Actually showing completion information is up to the view handler. This handler is usually applied manually inside a key handler via using `MiniInput.apply_handler(state, 'complete', method)`. Like, for example, in |MiniInput.default_key()| after or . Here is an example of a simple demo complete handler: >lua local complete_handler = function(state, method) if method == '' or method == 'xy' then local text = vim.fn.strcharpart(state.input, 0, state.caret - 1) local base = text:match('%S*$') state.complete = { base = base, items = { base .. 'x', base .. 'y' } } return end return MiniInput.default_complete(state, method) end require('mini.input').setup({ handlers = { complete = complete_handler } }) < ## Key ~ `handlers.key` is a handler intended to process every user key press. Takes |MiniInput-state| and `key` as arguments. Argument `key` represents a key that needs to be processed: a string if from the user input or `nil` if input needs to be set up, refreshed, or torn down. Default: |MiniInput.default_key()|. A string `key` can be two kinds: - Forwarded from |getcharstr()| verbatim as a result of interactive key press. Meaning it will be in escaped form and not as a |key-notation|: i.e. `"\r"` and not `""`. It also means that all kinds of combos (``, ``), mouse clicks, and wheel scrolls are also forwarded to key handler. - Any string as part of `opts.init_keys` in |MiniInput.get()|. If a string doesn't look like it came from |getcharstr()|, it is usually a good idea to insert this string at caret as is. The suggested overall approach for custom key handler is "if `key` is special - act on it, if can be used in the input - insert at caret, ignore otherwise". It is also important to never change the current mode (as in |vim-modes|) for |MiniInput.get()| to work as expected. Here is an example of a basic custom key handler: >lua local custom_actions = { [vim.keycode('')] = function(state) state.caret = math.max(state.caret - 1, 1) end, [vim.keycode('')] = function(state) local input_width = vim.fn.strchars(state.input) state.caret = math.min(state.caret + 1, input_width + 1) end, [vim.keycode('')] = function(state) state.status = 'accept' end, [vim.keycode('')] = function(state) state.status = 'cancel' end, } local key_handler = function(state, key) -- No need for special setup or teardown if key == nil then return end -- If key is special - act on it if custom_actions[key] then return custom_actions[key](state) end -- If key is not printable - do nothing if vim.fn.match(key, '^[[:print:]]\\+$') < 0 then return end -- Insert at caret local caret, input = state.caret, state.input local before_caret = vim.fn.strcharpart(input, 0, caret - 1) local after_caret = vim.fn.strcharpart(input, caret - 1) state.input = before_caret .. key .. after_caret state.caret = caret + vim.fn.strchars(key) -- No need to return anything as `state` is modified in place end require('mini.input').setup({ handlers = { key = key_handler } }) < ## Highlight ~ `handlers.highlight` is a handler intended to compute and set highlight info about the current input. Takes |MiniInput-state| as the only argument and is expected to modify field. Default: |MiniInput.default_highlight()|. See |MiniInput.gen_highlight| for built-in highlight handler generators. It is usually a good idea to append to a if it already exists. This makes it work more robustly when combining highlights. Here is a basic example that highlights all letters `a`: >lua local hl_handler = function(state) local highlight = {} for col in string.gmatch(state.input, '()a') do -- NOTE: range should use character (not byte) indexes local char_col = vim.fn.charidx(state.input, col) local range = { from = char_col, to = char_col, hl = 'Special' } table.insert(highlight, range) end state.highlight = vim.list_extend(state.highlight or {}, highlight) -- Possibly also apply default handler afterwards return MiniInput.default_highlight(state) end require('mini.input').setup({ handlers = { highlight = hl_handler } }) < ## View ~ `handlers.view` is a handler intended to show the input state on screen. Takes |MiniInput-state| as the only argument. Default: |MiniInput.default_view()|. See |MiniInput.gen_view| for built-in view handler generators. Example of view that uses |nvim_echo()| to show the input: >lua local view_handler = function(state) -- Process start and end of the input lifecycle local is_start = state.status == 'start' local is_end = state.status == 'accept' or state.status == 'cancel' if is_start or is_end then vim.cmd('mode') end if is_end then return end -- Compute text-hl chunks that fit and show them local chunks = MiniInput.state_to_chunks(state, vim.v.echospace) vim.api.nvim_echo(chunks, false, {}) end require('mini.input').setup({ handlers = { view = view_handler } }) < # Scope ~ `config.scope` is a string that defines an input scope. It is meant as an extra information for handlers to tweak their behavior (`view` style, etc.). Possible values: `"cursor"`, `"line"`, `"buffer"`, `"window"`, `"tabpage"`, `"editor"`, `"project"`. ------------------------------------------------------------------------------ *MiniInput.get()* `MiniInput.get`({opts}) Get input from the user # Lifecycle ~ *MiniInput-lifecycle* This module implements custom lifecycle to interact with the user. It starts when calling |MiniInput.get()| and ends when a value is returned. Only one active input is allowed simultaneously. The basic cycle unit is a step that processes a `key` (string or `nil`). It is done by calling relevant handlers in order: key handler with `key` as a second argument, highlight handler, view handler. |MiniInput.apply_handler()| is used to apply each handler and the output of one is used as the input for the next. During a step some state fields are automatically removed: - is removed before applying a key handler to always have the most up to date highlighting. - is removed if it was not changed after applying a key handler but something else in the state besides did change. This is meant as an automatic stop of completion when it is not advancing. If a step sets an ending state (i.e. `"accept"` or `"cancel"`), the input is finished by extra finishing step (see below). The order of operations is as follows: - Create initial |MiniInput-state| based on the input `opts`, with defaults inferred from |MiniInput.config|, and `vim.b.miniinput_config`. - Set to `"start"`. - Advance one step with `key=nil`. This is meant as a "setup" step for handlers. - Set to `"progress"`. - Process `opts.init_keys` one item per step with `key` set to the string item. - Wait for user to press a key (via |getcharstr()|). The key string (in escaped form and not as a |key-notation|; i.e. `"\r"` and not `""`) is then used to advance a step. Note: is hard coded to cancel the input. - Repeat previous step until the ending (`"accept"` or `"cancel"`). - Finish the input: - Perform a "teardown" step with `key=nil`. - If is set, throw an |error()|. - If input is accepted (even if empty) and not hidden, add to the history. Get the whole history with |MiniInput.get_history()|. - Return if is `"accept"`, `nil` otherwise. Parameters ~ {opts} `(table|nil)` Options. Possible fields: - `(string)` - completion method. Default: `''` to use default completion method of the `complete` handler. - `(table)` - same as in |MiniInput.config.handlers|, used only for the duration of the current input. - `(boolean)` - whether input should be hidden. Default: `false`. Note: this does not guarantee a total security of the input, only that the typed characters are expected to not be shown on screen and not added to the history. If set: - The `view` handler is expected to not directly show current input. Like replace characters with pre-defined string or fully not show. - The `complete` and `highlight` handlers are not called. - Accepted input will not be added to the history. - `(table)` - array of string keys that are emulated before asking for the user input. Using values that can be an output of |getcharstr()| should be preferred, but a key handler should work with any string. Default: `{}`. - `(string)` - intention of the input, same as in |input()|. Default: `"Input"`. - `(string)` - same as in |MiniInput.config|. Default: the value from `MiniInput.config` with some hard coded exceptions (on Neovim<0.12.3): - |vim.lsp.buf.rename()| will use `"cursor"` if no `new_name` is supplied. Return ~ `(string|nil)` User input (from the state field) if accepted, even if empty. `nil` if canceled or there was an active input. Usage ~ >lua local input = MiniInput.get({ -- Intention of the input prompt = 'New value', -- The input is for something at cursor scope = 'cursor', -- Emulate pressing `a`, ``, and `b` init_keys = { 'a', vim.keycode(''), 'b' }, }) < ------------------------------------------------------------------------------ *MiniInput.ui_input()* `MiniInput.ui_input`({opts}, {on_confirm}) A |vim.ui.input()| implementation Function which can be used to directly override |vim.ui.input()| to use this module functionality. Set automatically in |MiniInput.setup()|. Usage ~ To preserve original `vim.ui.input()`: >lua local ui_input_orig = vim.ui.input require('mini.input').setup() vim.ui.input = ui_input_orig < ------------------------------------------------------------------------------ *MiniInput.get_state()* `MiniInput.get_state`() Get current input state Return ~ `(table|nil)` Current |MiniInput-state| if input is active, `nil` otherwise. Notes: - For hidden input (`state.opts.hide=true`), both and are `nil` to actually hide the input. ------------------------------------------------------------------------------ *MiniInput.get_history()* `MiniInput.get_history`() Get input history Return ~ `(table)` Array with data about all previous non-hidden inputs (from earliest to latest). Each element is a table with the following fields: - `(string)` - |current-directory| at the time of input's end. - `(string)` - input result. - `(string)` - `opts.prompt` supplied in |MiniInput.get()|. - `(string)` - `opts.scope` supplied in |MiniInput.get()|. ------------------------------------------------------------------------------ *MiniInput.set_history()* `MiniInput.set_history`({history}) Set input history Parameters ~ {history} `(table)` Array describing all previous inputs. Same structure as |MiniInput.get_history()| output. ------------------------------------------------------------------------------ *MiniInput.refresh()* `MiniInput.refresh`() Refresh active input Performs one step of |MiniInput-lifecycle| with `key=nil`. ------------------------------------------------------------------------------ *MiniInput.gen_highlight* `MiniInput.gen_highlight` Highlight generators This is a table with function elements. Call to actually get a view function. ------------------------------------------------------------------------------ *MiniInput.gen_highlight.treesitter()* `MiniInput.gen_highlight.treesitter`({lang}) Highlight with tree-sitter Parameters ~ {lang} `(string)` A language of tree-sitter parser to use. Return ~ `(function)` A highlight handler. Seem |MiniInput.config.handlers|. ------------------------------------------------------------------------------ *MiniInput.gen_view* `MiniInput.gen_view` View generators This is a table with function elements. Call to actually get a view function. Each element accepts