···11-local utils = require("lib.utils")
11+local utils = require "lib.utils"
2233---@class multinput.Input
44---@field config multinput.Config
···88local Input = {}
991010local defaults = {
1111- opts = { numbers = "multiline" },
1212- padding = 5,
1313- width = { min = 10, max = 50 },
1414- height = { min = 1, max = 8 },
1515- win = {
1616- title = "Input: ",
1717- style = "minimal",
1818- focusable = true,
1919- relative = "cursor",
2020- col = -1,
2121- height = 1,
2222- },
1111+ opts = { numbers = "multiline" },
1212+ padding = 5,
1313+ width = { min = 10, max = 50 },
1414+ height = { min = 1, max = 8 },
1515+ win = {
1616+ title = "Input: ",
1717+ style = "minimal",
1818+ focusable = true,
1919+ relative = "cursor",
2020+ col = -1,
2121+ height = 1,
2222+ },
2323}
24242525local group = vim.api.nvim_create_augroup("multinput.nvim", { clear = true })
···2828---@param opts any
2929---@param on_confirm fun(input?: string)
3030function Input:new(config, opts, on_confirm)
3131- local i = {}
3232- i.config = vim.tbl_deep_extend("force", defaults, config, { win = { title = opts.prompt or "Input: " } })
3333- i.default = opts.default or ""
3434- i.on_confirm = on_confirm or function() end
3535- setmetatable(i, self)
3636- self.__index = self
3737- return i
3131+ local i = {}
3232+ i.config = vim.tbl_deep_extend(
3333+ "force",
3434+ defaults,
3535+ config,
3636+ { win = { title = opts.prompt or "Input: " } }
3737+ )
3838+ i.default = opts.default or ""
3939+ i.on_confirm = on_confirm or function() end
4040+ setmetatable(i, self)
4141+ self.__index = self
4242+ return i
3843end
39444045---@param default string
4146function Input:open(default)
4242- local width =
4343- utils.clamp(vim.fn.strdisplaywidth(default) + self.config.padding, self.config.width.min, self.config.width.max)
4747+ local width = utils.clamp(
4848+ vim.fn.strdisplaywidth(default) + self.config.padding,
4949+ self.config.width.min,
5050+ self.config.width.max
5151+ )
44524545- -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line.
4646- local curr_win = vim.api.nvim_get_current_win()
4747- local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1]
4848- local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1, width = width }
4949- or { anchor = "SW", row = 0, width = width }
5050- self.config = vim.tbl_deep_extend("keep", self.config, { win = win_config })
5353+ -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line.
5454+ local curr_win = vim.api.nvim_get_current_win()
5555+ local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1]
5656+ local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1, width = width }
5757+ or { anchor = "SW", row = 0, width = width }
5858+ self.config = vim.tbl_deep_extend("keep", self.config, { win = win_config })
51595252- -- Create buffer and floating window.
5353- self.bufnr = vim.api.nvim_create_buf(false, true)
5454- utils.set_options(
5555- { buftype = "prompt", bufhidden = "wipe", textwidth = self.config.width.max },
5656- { buf = self.bufnr }
5757- )
5858- vim.fn.prompt_setprompt(self.bufnr, "")
6060+ -- Create buffer and floating window.
6161+ self.bufnr = vim.api.nvim_create_buf(false, true)
6262+ utils.set_options(
6363+ { buftype = "prompt", bufhidden = "wipe", textwidth = self.config.width.max },
6464+ { buf = self.bufnr }
6565+ )
6666+ vim.fn.prompt_setprompt(self.bufnr, "")
59676060- self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win)
6161- utils.set_options({ wrap = true, linebreak = true, winhighlight = "Search:None" }, { win = self.winnr })
6868+ self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win)
6969+ utils.set_options(
7070+ { wrap = true, linebreak = true, winhighlight = "Search:None" },
7171+ { win = self.winnr }
7272+ )
62736363- -- Write default value and put cursor at the end
6464- vim.api.nvim_buf_set_text(self.bufnr, 0, 0, 0, 0, { default })
6565- vim.cmd("startinsert")
6666- vim.api.nvim_win_set_cursor(self.winnr, { 1, vim.str_utfindex(default, "utf-8") + 1 })
7474+ -- Write default value and put cursor at the end
7575+ vim.api.nvim_buf_set_text(self.bufnr, 0, 0, 0, 0, { default })
7676+ vim.cmd "startinsert"
7777+ vim.api.nvim_win_set_cursor(
7878+ self.winnr,
7979+ { 1, vim.str_utfindex(default, "utf-8") + 1 }
8080+ )
67816868- self:resize()
6969- self:autocmds()
7070- self:mappings()
8282+ self:resize()
8383+ self:autocmds()
8484+ self:mappings()
7185end
72867387---@param result string?
7488function Input:close(result)
7575- vim.cmd("stopinsert")
7676- vim.api.nvim_win_close(self.winnr, true)
7777- self.on_confirm(result)
8989+ vim.cmd "stopinsert"
9090+ vim.api.nvim_win_close(self.winnr, true)
9191+ self.on_confirm(result)
7892end
79938094function Input:resize()
8181- local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false)
8282- local line = table.concat(text, "")
9595+ local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false)
9696+ local line = table.concat(text, "")
83978484- if line == "" then
8585- vim.api.nvim_win_set_width(
8686- self.winnr,
8787- utils.clamp(self.config.padding, self.config.width.min, self.config.width.max)
8888- )
8989- vim.api.nvim_win_set_height(self.winnr, 1)
9090- return
9191- end
9898+ if line == "" then
9999+ vim.api.nvim_win_set_width(
100100+ self.winnr,
101101+ utils.clamp(
102102+ self.config.padding,
103103+ self.config.width.min,
104104+ self.config.width.max
105105+ )
106106+ )
107107+ vim.api.nvim_win_set_height(self.winnr, 1)
108108+ return
109109+ end
921109393- local lines = utils.split_wrapped_lines(line, self.config.width.max)
111111+ local lines = utils.split_wrapped_lines(line, self.config.width.max)
941129595- local lens = vim.tbl_map(function(l)
9696- return vim.fn.strdisplaywidth(l)
9797- end, lines)
9898- local width =
9999- utils.clamp(math.max(unpack(lens)) + self.config.padding, self.config.width.min, self.config.width.max + #lines)
100100- vim.api.nvim_win_set_width(self.winnr, width)
113113+ local lens = vim.tbl_map(function(l)
114114+ return vim.fn.strdisplaywidth(l)
115115+ end, lines)
116116+ local width = utils.clamp(
117117+ math.max(unpack(lens)) + self.config.padding,
118118+ self.config.width.min,
119119+ self.config.width.max + #lines
120120+ )
121121+ vim.api.nvim_win_set_width(self.winnr, width)
101122102102- local height = utils.clamp(#lines, self.config.height.min, self.config.height.max)
103103- vim.api.nvim_win_set_height(self.winnr, height)
123123+ local height = utils.clamp(#lines, self.config.height.min, self.config.height.max)
124124+ vim.api.nvim_win_set_height(self.winnr, height)
104125105105- if self.config.opts.numbers == "always" or (self.config.opts.numbers == "multiline" and height > 1) then
106106- utils.set_option_if_globally_enabled("number", self.winnr)
107107- utils.set_option_if_globally_enabled("relativenumber", self.winnr)
108108- end
126126+ if
127127+ self.config.opts.numbers == "always"
128128+ or (self.config.opts.numbers == "multiline" and height > 1)
129129+ then
130130+ utils.set_option_if_globally_enabled("number", self.winnr)
131131+ utils.set_option_if_globally_enabled("relativenumber", self.winnr)
132132+ end
109133110110- if
111111- vim.api.nvim_get_option_value("number", { win = self.winnr })
112112- or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr })
113113- then
114114- vim.api.nvim_win_set_width(self.winnr, width + utils.get_linenr_width())
115115- end
134134+ if
135135+ vim.api.nvim_get_option_value("number", { win = self.winnr })
136136+ or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr })
137137+ then
138138+ vim.api.nvim_win_set_width(self.winnr, width + utils.get_linenr_width())
139139+ end
116140end
117141118142function Input:autocmds()
119119- vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
120120- group = group,
121121- buffer = self.bufnr,
122122- callback = function()
123123- self:resize()
124124- end,
125125- })
143143+ vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
144144+ group = group,
145145+ buffer = self.bufnr,
146146+ callback = function()
147147+ self:resize()
148148+ end,
149149+ })
126150end
127151128152function Input:mappings()
129129- ---@param mode string|string[]
130130- ---@param lhs string
131131- ---@param rhs string|function
132132- local function map(mode, lhs, rhs)
133133- vim.keymap.set(mode, lhs, rhs, { buffer = self.bufnr })
134134- end
153153+ ---@param mode string|string[]
154154+ ---@param lhs string
155155+ ---@param rhs string|function
156156+ local function map(mode, lhs, rhs)
157157+ vim.keymap.set(mode, lhs, rhs, { buffer = self.bufnr })
158158+ end
135159136136- map({ "n", "i", "v" }, "<cr>", function()
137137- self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1])
138138- end)
139139- map({ "i" }, "<a-cr>", function()
140140- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<cr>", true, false, true), "n", true)
141141- end)
160160+ map({ "n", "i", "v" }, "<cr>", function()
161161+ self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1])
162162+ end)
163163+ map({ "i" }, "<a-cr>", function()
164164+ vim.api.nvim_feedkeys(
165165+ vim.api.nvim_replace_termcodes("<cr>", true, false, true),
166166+ "n",
167167+ true
168168+ )
169169+ end)
142170143143- map("n", "<esc>", function()
144144- self:close()
145145- end)
171171+ map("n", "<esc>", function()
172172+ self:close()
173173+ end)
146174147147- map("n", "q", function()
148148- self:close()
149149- end)
175175+ map("n", "q", function()
176176+ self:close()
177177+ end)
150178end
151179152180return Input
+25-25
lua/lib/utils.lua
···33---@param options table<string, any>
44---@param opts vim.api.keyset.option
55function M.set_options(options, opts)
66- for k, v in pairs(options) do
77- vim.api.nvim_set_option_value(k, v, opts)
88- end
66+ for k, v in pairs(options) do
77+ vim.api.nvim_set_option_value(k, v, opts)
88+ end
99end
10101111---@param option string
1212---@param winnr integer
1313function M.set_option_if_globally_enabled(option, winnr)
1414- if vim.api.nvim_get_option_value(option, { scope = "global" }) then
1515- vim.api.nvim_set_option_value(option, true, { win = winnr })
1616- end
1414+ if vim.api.nvim_get_option_value(option, { scope = "global" }) then
1515+ vim.api.nvim_set_option_value(option, true, { win = winnr })
1616+ end
1717end
18181919---@param value number
···2121---@param max number
2222---@return number
2323function M.clamp(value, min, max)
2424- return math.min(math.max(value, min), max)
2424+ return math.min(math.max(value, min), max)
2525end
26262727---@param text string
2828---@param width integer
2929---@return string[]
3030function M.split_wrapped_lines(text, width)
3131- if text == "" then
3232- return {}
3333- end
3131+ if text == "" then
3232+ return {}
3333+ end
34343535- ---@type string[]
3636- local lines = {}
3737- local textlen = vim.fn.strchars(text, true)
3535+ ---@type string[]
3636+ local lines = {}
3737+ local textlen = vim.fn.strchars(text, true)
38383939- local i = 0
4040- while i < textlen do
4141- local len = i + width <= textlen and width or textlen - i
4242- local new_line = vim.fn.strcharpart(text, i, len)
3939+ local i = 0
4040+ while i < textlen do
4141+ local len = i + width <= textlen and width or textlen - i
4242+ local new_line = vim.fn.strcharpart(text, i, len)
43434444- table.insert(lines, new_line)
4545- i = i + len
4646- end
4444+ table.insert(lines, new_line)
4545+ i = i + len
4646+ end
47474848- return lines
4848+ return lines
4949end
50505151function M.get_linenr_width()
5252- local line_count = vim.api.nvim_buf_line_count(0)
5353- local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1
5454- local numberwidth_opt = vim.opt.numberwidth:get()
5555- return math.max(line_digits, numberwidth_opt)
5252+ local line_count = vim.api.nvim_buf_line_count(0)
5353+ local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1
5454+ local numberwidth_opt = vim.opt.numberwidth:get()
5555+ return math.max(line_digits, numberwidth_opt)
5656end
57575858return M
+5-5
lua/multinput.lua
···11-local Input = require("lib.input")
11+local Input = require "lib.input"
2233local M = {}
4455---@param config? multinput.Config
66function M.setup(config)
77- vim.ui.input = function(opts, on_confirm)
88- local input = Input:new(config or {}, opts or {}, on_confirm)
99- input:open(opts.default or "")
1010- end
77+ vim.ui.input = function(opts, on_confirm)
88+ local input = Input:new(config or {}, opts or {}, on_confirm)
99+ input:open(opts.default or "")
1010+ end
1111end
12121313return M