diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..d332644 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 88 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 4 +quote_style = "AutoPreferDouble" +no_call_parentheses = true diff --git a/lua/lib/config.lua b/lua/lib/config.lua index 30fafd5..2b17dc2 100644 --- a/lua/lib/config.lua +++ b/lua/lib/config.lua @@ -16,19 +16,19 @@ ---@field max integer return { - ---@type multinput.Config - defaults = { - opts = { numbers = "multiline" }, - padding = 5, - width = { min = 20, max = 60 }, - height = { min = 1, max = 6 }, - win = { - title = "Input: ", - style = "minimal", - focusable = true, - relative = "cursor", - col = -1, - height = 1, - }, - }, + ---@type multinput.Config + defaults = { + opts = { numbers = "multiline" }, + padding = 5, + width = { min = 20, max = 60 }, + height = { min = 1, max = 6 }, + win = { + title = "Input: ", + style = "minimal", + focusable = true, + relative = "cursor", + col = -1, + height = 1, + }, + }, } diff --git a/lua/lib/input.lua b/lua/lib/input.lua index 83fd72d..db0251e 100644 --- a/lua/lib/input.lua +++ b/lua/lib/input.lua @@ -1,4 +1,4 @@ -local utils = require("lib.utils") +local utils = require "lib.utils" ---@class multinput.Input ---@field config multinput.Config @@ -8,18 +8,18 @@ local utils = require("lib.utils") local Input = {} local defaults = { - opts = { numbers = "multiline" }, - padding = 5, - width = { min = 10, max = 50 }, - height = { min = 1, max = 8 }, - win = { - title = "Input: ", - style = "minimal", - focusable = true, - relative = "cursor", - col = -1, - height = 1, - }, + opts = { numbers = "multiline" }, + padding = 5, + width = { min = 10, max = 50 }, + height = { min = 1, max = 8 }, + win = { + title = "Input: ", + style = "minimal", + focusable = true, + relative = "cursor", + col = -1, + height = 1, + }, } local group = vim.api.nvim_create_augroup("multinput.nvim", { clear = true }) @@ -28,125 +28,153 @@ local group = vim.api.nvim_create_augroup("multinput.nvim", { clear = true }) ---@param opts any ---@param on_confirm fun(input?: string) function Input:new(config, opts, on_confirm) - local i = {} - i.config = vim.tbl_deep_extend("force", defaults, config, { win = { title = opts.prompt or "Input: " } }) - i.default = opts.default or "" - i.on_confirm = on_confirm or function() end - setmetatable(i, self) - self.__index = self - return i + local i = {} + i.config = vim.tbl_deep_extend( + "force", + defaults, + config, + { win = { title = opts.prompt or "Input: " } } + ) + i.default = opts.default or "" + i.on_confirm = on_confirm or function() end + setmetatable(i, self) + self.__index = self + return i end ---@param default string function Input:open(default) - local width = - utils.clamp(vim.fn.strdisplaywidth(default) + self.config.padding, self.config.width.min, self.config.width.max) - - -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line. - local curr_win = vim.api.nvim_get_current_win() - local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1] - local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1, width = width } - or { anchor = "SW", row = 0, width = width } - 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( - { buftype = "prompt", bufhidden = "wipe", textwidth = self.config.width.max }, - { buf = self.bufnr } - ) - vim.fn.prompt_setprompt(self.bufnr, "") - - self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win) - utils.set_options({ wrap = true, linebreak = true, winhighlight = "Search:None" }, { win = self.winnr }) - - -- Write default value and put cursor at the end - vim.api.nvim_buf_set_text(self.bufnr, 0, 0, 0, 0, { default }) - vim.cmd("startinsert") - vim.api.nvim_win_set_cursor(self.winnr, { 1, vim.str_utfindex(default, "utf-8") + 1 }) - - self:resize() - self:autocmds() - self:mappings() + local width = utils.clamp( + vim.fn.strdisplaywidth(default) + self.config.padding, + self.config.width.min, + self.config.width.max + ) + + -- Position window relative to the cursor, such that it doesn't overlap with the cursor's line. + local curr_win = vim.api.nvim_get_current_win() + local cursor_row = vim.api.nvim_win_get_cursor(curr_win)[1] + local win_config = (cursor_row <= 3) and { anchor = "NW", row = 1, width = width } + or { anchor = "SW", row = 0, width = width } + 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( + { buftype = "prompt", bufhidden = "wipe", textwidth = self.config.width.max }, + { buf = self.bufnr } + ) + vim.fn.prompt_setprompt(self.bufnr, "") + + self.winnr = vim.api.nvim_open_win(self.bufnr, true, self.config.win) + utils.set_options( + { wrap = true, linebreak = true, winhighlight = "Search:None" }, + { win = self.winnr } + ) + + -- Write default value and put cursor at the end + vim.api.nvim_buf_set_text(self.bufnr, 0, 0, 0, 0, { default }) + vim.cmd "startinsert" + vim.api.nvim_win_set_cursor( + self.winnr, + { 1, vim.str_utfindex(default, "utf-8") + 1 } + ) + + self:resize() + self:autocmds() + self:mappings() end ---@param result string? function Input:close(result) - vim.cmd("stopinsert") - vim.api.nvim_win_close(self.winnr, true) - self.on_confirm(result) + vim.cmd "stopinsert" + vim.api.nvim_win_close(self.winnr, true) + self.on_confirm(result) 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 - vim.api.nvim_win_set_width( - self.winnr, - utils.clamp(self.config.padding, self.config.width.min, self.config.width.max) - ) - vim.api.nvim_win_set_height(self.winnr, 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) - local width = - utils.clamp(math.max(unpack(lens)) + self.config.padding, self.config.width.min, self.config.width.max + #lines) - vim.api.nvim_win_set_width(self.winnr, width) - - local height = utils.clamp(#lines, self.config.height.min, self.config.height.max) - vim.api.nvim_win_set_height(self.winnr, 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 - - if - vim.api.nvim_get_option_value("number", { win = self.winnr }) - or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr }) - then - vim.api.nvim_win_set_width(self.winnr, width + utils.get_linenr_width()) - end + local text = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) + local line = table.concat(text, "") + + if line == "" then + vim.api.nvim_win_set_width( + self.winnr, + utils.clamp( + self.config.padding, + self.config.width.min, + self.config.width.max + ) + ) + vim.api.nvim_win_set_height(self.winnr, 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) + local width = utils.clamp( + math.max(unpack(lens)) + self.config.padding, + self.config.width.min, + self.config.width.max + #lines + ) + vim.api.nvim_win_set_width(self.winnr, width) + + local height = utils.clamp(#lines, self.config.height.min, self.config.height.max) + vim.api.nvim_win_set_height(self.winnr, 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 + + if + vim.api.nvim_get_option_value("number", { win = self.winnr }) + or vim.api.nvim_get_option_value("relativenumber", { win = self.winnr }) + then + vim.api.nvim_win_set_width(self.winnr, width + utils.get_linenr_width()) + end end function Input:autocmds() - vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { - group = group, - buffer = self.bufnr, - callback = function() - self:resize() - end, - }) + 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 - - map({ "n", "i", "v" }, "", function() - self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]) - end) - map({ "i" }, "", function() - vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", true) - end) - - map("n", "", function() - self:close() - end) - - map("n", "q", function() - self:close() - end) + ---@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 + + map({ "n", "i", "v" }, "", function() + self:close(vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]) + end) + map({ "i" }, "", function() + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("", true, false, true), + "n", + true + ) + end) + + map("n", "", function() + self:close() + end) + + map("n", "q", function() + self:close() + end) end return Input diff --git a/lua/lib/utils.lua b/lua/lib/utils.lua index 039da5f..5710843 100644 --- a/lua/lib/utils.lua +++ b/lua/lib/utils.lua @@ -3,17 +3,17 @@ local M = {} ---@param options table ---@param opts vim.api.keyset.option function M.set_options(options, opts) - for k, v in pairs(options) do - vim.api.nvim_set_option_value(k, v, opts) - end + for k, v in pairs(options) do + vim.api.nvim_set_option_value(k, v, opts) + end end ---@param option string ---@param winnr integer function M.set_option_if_globally_enabled(option, winnr) - if vim.api.nvim_get_option_value(option, { scope = "global" }) then - vim.api.nvim_set_option_value(option, true, { win = winnr }) - end + if vim.api.nvim_get_option_value(option, { scope = "global" }) then + vim.api.nvim_set_option_value(option, true, { win = winnr }) + end end ---@param value number @@ -21,38 +21,38 @@ end ---@param max number ---@return number function M.clamp(value, min, max) - return math.min(math.max(value, min), max) + return math.min(math.max(value, min), max) end ---@param text string ---@param width integer ---@return string[] function M.split_wrapped_lines(text, width) - if text == "" then - return {} - end + if text == "" then + return {} + end - ---@type string[] - local lines = {} - local textlen = vim.fn.strchars(text, true) + ---@type string[] + local lines = {} + local textlen = vim.fn.strchars(text, true) - local i = 0 - while i < textlen do - local len = i + width <= textlen and width or textlen - i - local new_line = vim.fn.strcharpart(text, i, len) + local i = 0 + while i < textlen do + local len = i + width <= textlen and width or textlen - i + local new_line = vim.fn.strcharpart(text, i, len) - table.insert(lines, new_line) - i = i + len - end + table.insert(lines, new_line) + i = i + len + end - return lines + return lines end function M.get_linenr_width() - local line_count = vim.api.nvim_buf_line_count(0) - local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1 - local numberwidth_opt = vim.opt.numberwidth:get() - return math.max(line_digits, numberwidth_opt) + local line_count = vim.api.nvim_buf_line_count(0) + local line_digits = math.floor(math.log10(math.max(1, line_count))) + 1 + local numberwidth_opt = vim.opt.numberwidth:get() + return math.max(line_digits, numberwidth_opt) end return M diff --git a/lua/multinput.lua b/lua/multinput.lua index 0ceecd5..a901e03 100644 --- a/lua/multinput.lua +++ b/lua/multinput.lua @@ -1,13 +1,13 @@ -local Input = require("lib.input") +local Input = require "lib.input" local M = {} ---@param config? multinput.Config function M.setup(config) - vim.ui.input = function(opts, on_confirm) - local input = Input:new(config or {}, opts or {}, on_confirm) - input:open(opts.default or "") - end + vim.ui.input = function(opts, on_confirm) + local input = Input:new(config or {}, opts or {}, on_confirm) + input:open(opts.default or "") + end end return M