A floating vim.ui.input buffer that auto-resizes to fit its contents.
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor(nvim): apply stylua formatting

r0nsha (Jul 2, 2025, 2:24 PM +0300) ffe8e832 2f267ae7

+132 -146
+15 -15
lua/lib/config.lua
··· 16 16 ---@field max integer 17 17 18 18 return { 19 - ---@type multinput.Config 20 - defaults = { 21 - opts = { numbers = "never" }, 22 - padding = 5, 23 - width = { min = 20, max = 60 }, 24 - height = { min = 1, max = 6 }, 25 - win = { 26 - title = "Input: ", 27 - style = "minimal", 28 - focusable = true, 29 - relative = "cursor", 30 - col = -1, 31 - width = 1, 32 - height = 1, 33 - }, 19 + ---@type multinput.Config 20 + defaults = { 21 + opts = { numbers = "never" }, 22 + padding = 5, 23 + width = { min = 20, max = 60 }, 24 + height = { min = 1, max = 6 }, 25 + win = { 26 + title = "Input: ", 27 + style = "minimal", 28 + focusable = true, 29 + relative = "cursor", 30 + col = -1, 31 + width = 1, 32 + height = 1, 34 33 }, 34 + }, 35 35 }
+78 -101
lua/lib/input.lua
··· 1 - local utils = require "lib.utils" 1 + local utils = require("lib.utils") 2 2 local defaults = require("lib.config").defaults 3 3 4 4 ---@class multinput.Input ··· 14 14 ---@param opts any 15 15 ---@param on_confirm fun(input?: string) 16 16 function Input:new(config, opts, on_confirm) 17 - local i = {} 18 - i.config = vim.tbl_deep_extend( 19 - "force", 20 - defaults, 21 - config, 22 - { win = { title = opts.prompt or "Input: " } } 23 - ) 24 - i.default = opts.default or "" 25 - i.on_confirm = on_confirm or function() end 26 - setmetatable(i, self) 27 - self.__index = self 28 - return i 17 + local i = {} 18 + i.config = vim.tbl_deep_extend("force", defaults, config, { win = { title = opts.prompt or "Input: " } }) 19 + i.default = opts.default or "" 20 + i.on_confirm = on_confirm or function() end 21 + setmetatable(i, self) 22 + self.__index = self 23 + return i 29 24 end 30 25 31 26 ---@param default string 32 27 function Input:open(default) 33 - -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line. 34 - local curr_win = vim.api.nvim_get_current_win() 35 - local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1] 36 - local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1 } 37 - or { anchor = "SW", row = 0 } 38 - self.config = vim.tbl_deep_extend("keep", self.config, { win = win_config }) 28 + -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line. 29 + local curr_win = vim.api.nvim_get_current_win() 30 + local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1] 31 + local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1 } or { anchor = "SW", row = 0 } 32 + self.config = vim.tbl_deep_extend("keep", self.config, { win = win_config }) 39 33 40 - -- Create buffer and floating window. 41 - self.bufnr = vim.api.nvim_create_buf(false, true) 42 - utils.set_options({ buftype = "prompt", bufhidden = "wipe" }, { buf = self.bufnr }) 43 - vim.fn.prompt_setprompt(self.bufnr, "") 34 + -- Create buffer and floating window. 35 + self.bufnr = vim.api.nvim_create_buf(false, true) 36 + utils.set_options({ buftype = "prompt", bufhidden = "wipe" }, { buf = self.bufnr }) 37 + vim.fn.prompt_setprompt(self.bufnr, "") 44 38 45 - self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win) 46 - utils.set_options( 47 - { wrap = true, linebreak = true, winhighlight = "Search:None" }, 48 - { win = self.winnr } 49 - ) 39 + self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win) 40 + utils.set_options({ wrap = true, linebreak = true, winhighlight = "Search:None" }, { win = self.winnr }) 50 41 51 - -- Write default value and put cursor at the end 52 - vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, true, { default }) 53 - vim.cmd "startinsert" 54 - vim.api.nvim_win_set_cursor( 55 - self.winnr, 56 - { 1, vim.str_utfindex(default, "utf-8") + 1 } 57 - ) 42 + -- Write default value and put cursor at the end 43 + vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, true, { default }) 44 + vim.cmd("startinsert") 45 + vim.api.nvim_win_set_cursor(self.winnr, { 1, vim.str_utfindex(default, "utf-8") + 1 }) 58 46 59 - self:resize() 60 - self:autocmds() 61 - self:mappings() 47 + self:resize() 48 + self:autocmds() 49 + self:mappings() 62 50 end 63 51 64 52 ---@param result string? 65 53 function Input:close(result) 66 - vim.cmd "stopinsert" 67 - vim.api.nvim_win_close(self.winnr, true) 68 - self.on_confirm(result) 54 + vim.cmd("stopinsert") 55 + vim.api.nvim_win_close(self.winnr, true) 56 + self.on_confirm(result) 69 57 end 70 58 71 59 ---@param height integer 72 60 ---@return boolean 73 61 function Input:set_numbers(height) 74 - if 75 - self.config.opts.numbers == "always" 76 - or (self.config.opts.numbers == "multiline" and height > 1) 77 - then 78 - utils.set_option_if_globally_enabled("number", self.winnr) 79 - utils.set_option_if_globally_enabled("relativenumber", self.winnr) 80 - end 62 + if self.config.opts.numbers == "always" or (self.config.opts.numbers == "multiline" and height > 1) then 63 + utils.set_option_if_globally_enabled("number", self.winnr) 64 + utils.set_option_if_globally_enabled("relativenumber", self.winnr) 65 + end 81 66 82 - return vim.api.nvim_get_option_value("number", { win = self.winnr }) 83 - or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr }) 67 + return vim.api.nvim_get_option_value("number", { win = self.winnr }) 68 + or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr }) 84 69 end 85 70 86 71 ---@param width integer 87 72 ---@param height integer 88 73 function Input:set_size(width, height) 89 - local h = utils.clamp(height, self.config.height.min, self.config.height.max) 90 - vim.api.nvim_win_set_height(self.winnr, h) 74 + local h = utils.clamp(height, self.config.height.min, self.config.height.max) 75 + vim.api.nvim_win_set_height(self.winnr, h) 91 76 92 - local w = utils.clamp( 93 - width + self.config.padding, 94 - self.config.width.min, 95 - self.config.width.max 96 - ) 77 + local w = utils.clamp(width + self.config.padding, self.config.width.min, self.config.width.max) 97 78 98 - local has_numbers = self:set_numbers(h) 99 - w = has_numbers and w + utils.get_linenr_width(self.winnr, self.bufnr) or w 100 - w = w + 2 -- HACK: add padding to avoid glitches 101 - vim.api.nvim_win_set_width(self.winnr, w) 79 + local has_numbers = self:set_numbers(h) 80 + w = has_numbers and w + utils.get_linenr_width(self.winnr, self.bufnr) or w 81 + w = w + 2 -- HACK: add padding to avoid glitches 82 + vim.api.nvim_win_set_width(self.winnr, w) 102 83 end 103 84 104 85 function Input:resize() 105 - local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) 106 - local line = table.concat(text, "") 86 + local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) 87 + local line = table.concat(text, "") 107 88 108 - if line == "" then 109 - self:set_size(0, 1) 110 - return 111 - end 89 + if line == "" then 90 + self:set_size(0, 1) 91 + return 92 + end 112 93 113 - local lines = utils.split_wrapped_lines(line, self.config.width.max) 94 + local lines = utils.split_wrapped_lines(line, self.config.width.max) 114 95 115 - local lens = vim.tbl_map(function(l) 116 - return vim.fn.strdisplaywidth(l) 117 - end, lines) 96 + local lens = vim.tbl_map(function(l) 97 + return vim.fn.strdisplaywidth(l) 98 + end, lines) 118 99 119 - self:set_size(math.max(unpack(lens)), #lines) 100 + self:set_size(math.max(unpack(lens)), #lines) 120 101 end 121 102 122 103 function Input:autocmds() 123 - vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { 124 - group = group, 125 - buffer = self.bufnr, 126 - callback = function() 127 - self:resize() 128 - end, 129 - }) 104 + vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { 105 + group = group, 106 + buffer = self.bufnr, 107 + callback = function() 108 + self:resize() 109 + end, 110 + }) 130 111 end 131 112 132 113 function Input:mappings() 133 - ---@param mode string|string[] 134 - ---@param lhs string 135 - ---@param rhs string|function 136 - local function map(mode, lhs, rhs) 137 - vim.keymap.set(mode, lhs, rhs, { buffer = self.bufnr }) 138 - end 114 + ---@param mode string|string[] 115 + ---@param lhs string 116 + ---@param rhs string|function 117 + local function map(mode, lhs, rhs) 118 + vim.keymap.set(mode, lhs, rhs, { buffer = self.bufnr }) 119 + end 139 120 140 - map({ "n", "i", "v" }, "<cr>", function() 141 - self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]) 142 - end) 143 - map({ "i" }, "<a-cr>", function() 144 - vim.api.nvim_feedkeys( 145 - vim.api.nvim_replace_termcodes("<cr>", true, false, true), 146 - "n", 147 - true 148 - ) 149 - end) 121 + map({ "n", "i", "v" }, "<cr>", function() 122 + self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]) 123 + end) 124 + map({ "i" }, "<a-cr>", function() 125 + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<cr>", true, false, true), "n", true) 126 + end) 150 127 151 - map("n", "<esc>", function() 152 - self:close() 153 - end) 128 + map("n", "<esc>", function() 129 + self:close() 130 + end) 154 131 155 - map("n", "q", function() 156 - self:close() 157 - end) 132 + map("n", "q", function() 133 + self:close() 134 + end) 158 135 end 159 136 160 137 return Input
+25 -25
lua/lib/utils.lua
··· 3 3 ---@param options table<string, any> 4 4 ---@param opts vim.api.keyset.option 5 5 function M.set_options(options, opts) 6 - for k, v in pairs(options) do 7 - vim.api.nvim_set_option_value(k, v, opts) 8 - end 6 + for k, v in pairs(options) do 7 + vim.api.nvim_set_option_value(k, v, opts) 8 + end 9 9 end 10 10 11 11 ---@param option string 12 12 ---@param winnr integer 13 13 function M.set_option_if_globally_enabled(option, winnr) 14 - if vim.api.nvim_get_option_value(option, { scope = "global" }) then 15 - vim.api.nvim_set_option_value(option, true, { win = winnr }) 16 - end 14 + if vim.api.nvim_get_option_value(option, { scope = "global" }) then 15 + vim.api.nvim_set_option_value(option, true, { win = winnr }) 16 + end 17 17 end 18 18 19 19 ---@param value number ··· 21 21 ---@param max number 22 22 ---@return number 23 23 function M.clamp(value, min, max) 24 - return math.min(math.max(value, min), max) 24 + return math.min(math.max(value, min), max) 25 25 end 26 26 27 27 ---@param text string 28 28 ---@param width integer 29 29 ---@return string[] 30 30 function M.split_wrapped_lines(text, width) 31 - if text == "" then 32 - return {} 33 - end 31 + if text == "" then 32 + return {} 33 + end 34 34 35 - ---@type string[] 36 - local lines = {} 37 - local textlen = vim.fn.strchars(text, true) 35 + ---@type string[] 36 + local lines = {} 37 + local textlen = vim.fn.strchars(text, true) 38 38 39 - local i = 0 40 - while i < textlen do 41 - local len = i + width <= textlen and width or textlen - i 42 - local new_line = vim.fn.strcharpart(text, i, len) 39 + local i = 0 40 + while i < textlen do 41 + local len = i + width <= textlen and width or textlen - i 42 + local new_line = vim.fn.strcharpart(text, i, len) 43 43 44 - table.insert(lines, new_line) 45 - i = i + len 46 - end 44 + table.insert(lines, new_line) 45 + i = i + len 46 + end 47 47 48 - return lines 48 + return lines 49 49 end 50 50 51 51 ---@param winnr integer 52 52 ---@param bufnr integer 53 53 ---@return integer 54 54 function M.get_linenr_width(winnr, bufnr) 55 - local line_count = vim.api.nvim_buf_line_count(bufnr) 56 - local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1 57 - local numberwidth = vim.api.nvim_get_option_value("numberwidth", { win = winnr }) 58 - return math.max(line_digits, numberwidth) 55 + local line_count = vim.api.nvim_buf_line_count(bufnr) 56 + local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1 57 + local numberwidth = vim.api.nvim_get_option_value("numberwidth", { win = winnr }) 58 + return math.max(line_digits, numberwidth) 59 59 end 60 60 61 61 return M
+5 -5
lua/multinput.lua
··· 1 - local Input = require "lib.input" 1 + local Input = require("lib.input") 2 2 3 3 local M = {} 4 4 5 5 ---@param config? multinput.Config 6 6 function M.setup(config) 7 - vim.ui.input = function(opts, on_confirm) 8 - local input = Input:new(config or {}, opts or {}, on_confirm) 9 - input:open(opts.default or "") 10 - end 7 + vim.ui.input = function(opts, on_confirm) 8 + local input = Input:new(config or {}, opts or {}, on_confirm) 9 + input:open(opts.default or "") 10 + end 11 11 end 12 12 13 13 return M
+9
stylua.toml
··· 1 + column_width = 120 2 + indent_type = "Spaces" 3 + indent_width = 2 4 + quote_style = "AutoPreferDouble" 5 + call_parentheses = "Always" 6 + collapse_simple_statement = "Never" 7 + 8 + [sort_requires] 9 + enabled = true