Something went wrong. Try again.
A floating `vim.ui.input` buffer that auto-resizes to fit its contents.
Something went wrong. Try again.
4.8 kB · 170 lines
Lua
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171local utils = require("lib.utils")local defaults = require("lib.config").defaults
---@class multinput.Input---@field config multinput.Config---@field winnr integer---@field bufnr integer---@field on_confirm fun(input?: string)local Input = {}
local group = vim.api.nvim_create_augroup("multinput.nvim", { clear = true })
---@param config multinput.Config---@param opts any---@param on_confirm fun(input?: string)function Input:new(config, opts, on_confirm) local i = { config = vim.tbl_deep_extend("force", defaults, config, { win = { title = opts.prompt or "Input: " }, }), default = opts.default or "", on_confirm = on_confirm or function() end, } setmetatable(i, self) self.__index = self return iend
---@param default stringfunction Input:open(default) self.mode = vim.fn.mode()
-- Position window relative to the cursor, such that it doesn't overlap with the cursor's line. self.parent_win = vim.api.nvim_get_current_win() local cursor_row = vim.api.nvim_win_get_cursor(self.parent_win)[1] local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1 } or { anchor = "SW", row = 0 } self.config = vim.tbl_deep_extend("keep", self.config, { win = win_config })
-- Create buffer and floating window. self.bufnr = vim.api.nvim_create_buf(false, true) utils.set_options({ filetype = "multinput", buftype = "prompt", bufhidden = "wipe", modifiable = true, }, { buf = self.bufnr }) vim.api.nvim_buf_set_var(self.bufnr, "completion", self.config.completion) vim.fn.prompt_setprompt(self.bufnr, "")
self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win)
local winhighlight = self.config.winhighlight if winhighlight and winhighlight ~= "" then winhighlight = "Search:None," .. winhighlight else winhighlight = "Search:None" end utils.set_options({ wrap = true, linebreak = true, winhighlight = winhighlight, }, { win = self.winnr })
-- Write default value and put cursor at the end vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, true, { default })
self:resize() self:autocmds() self:mappings()
vim.api.nvim_win_call(self.winnr, function() vim.cmd("startinsert!") end)end
---@param result string?function Input:close(result) vim.cmd("stopinsert") vim.api.nvim_win_close(self.winnr, true) if vim.api.nvim_win_is_valid(self.parent_win) then vim.api.nvim_set_current_win(self.parent_win) if self.mode == "i" then vim.cmd("startinsert") end end self.on_confirm(result)end
---@param height integer---@return booleanfunction Input:set_numbers(height) if self.config.opts.numbers == "always" or (self.config.opts.numbers == "multiline" and height > 1) then utils.set_option_if_globally_enabled("number", self.winnr) utils.set_option_if_globally_enabled("relativenumber", self.winnr) end
return vim.api.nvim_get_option_value("number", { win = self.winnr }) or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr })end
---@param width integer---@param height integerfunction Input:set_size(width, height) local h = utils.clamp(height, self.config.height.min, self.config.height.max) vim.api.nvim_win_set_height(self.winnr, h)
local w = utils.clamp(width + self.config.padding, self.config.width.min, self.config.width.max)
local has_numbers = self:set_numbers(h) w = has_numbers and w + utils.get_linenr_width(self.winnr, self.bufnr) or w w = w + 2 -- HACK: add padding to avoid glitches vim.api.nvim_win_set_width(self.winnr, w)end
function Input:resize() local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) local line = table.concat(text, "")
if line == "" then self:set_size(0, 1) return end
local lines = utils.split_wrapped_lines(line, self.config.width.max)
local lens = vim.tbl_map(function(l) return vim.fn.strdisplaywidth(l) end, lines)
self:set_size(math.max(unpack(lens)), #lines)end
function Input:autocmds() vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { group = group, buffer = self.bufnr, callback = function() self:resize() end, })end
function Input:mappings() ---@param mode string|string[] ---@param lhs string ---@param rhs string|function local function map(mode, lhs, rhs) vim.keymap.set(mode, lhs, rhs, { buffer = self.bufnr }) end
local function confirm() self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]) end
map({ "n", "i", "v" }, "<cr>", confirm) map({ "i" }, "<a-cr>", function() vim.cmd("stopinsert") confirm() end)
map("n", "<esc>", function() self:close() end)
map("n", "q", function() self:close() end)end
return Input