local api = vim.api local fn = vim.fn local Config = require("avante.config") local Utils = require("avante.utils") ---@class PromptInput ---@field bufnr integer | nil ---@field winid integer | nil ---@field win_opts table ---@field shortcuts_hints_winid integer | nil ---@field augroup integer | nil ---@field start_insert boolean ---@field submit_callback function | nil ---@field cancel_callback function | nil ---@field close_on_submit boolean ---@field spinner_chars table ---@field spinner_index integer ---@field spinner_timer uv_timer_t | nil ---@field spinner_active boolean local PromptInput = {} ---@class PromptInputOptions ---@field start_insert? boolean ---@field submit_callback? fun(input: string):nil ---@field cancel_callback? fun():nil ---@field close_on_submit? boolean ---@field win_opts? table ---@param opts? PromptInputOptions function PromptInput:new(opts) opts = opts or {} local obj = setmetatable({}, { __index = self }) obj.bufnr = nil obj.winid = nil obj.shortcuts_hints_winid = nil obj.augroup = api.nvim_create_augroup("PromptInput", { clear = true }) obj.start_insert = opts.start_insert or false obj.submit_callback = opts.submit_callback obj.cancel_callback = opts.cancel_callback obj.close_on_submit = opts.close_on_submit or false obj.win_opts = opts.win_opts obj.spinner_chars = { "⡀", "⠄", "⠂", "⠁", "⠈", "⠐", "⠠", "⢀", "⣀", "⢄", "⢂", "⢁", "⢈", "⢐", "⢠", "⣠", "⢤", "⢢", "⢡", "⢨", "⢰", "⣰", "⢴", "⢲", "⢱", "⢸", "⣸", "⢼", "⢺", "⢹", "⣹", "⢽", "⢻", "⣻", "⢿", "⣿", "⣶", "⣤", "⣀", } obj.spinner_index = 1 obj.spinner_timer = nil obj.spinner_active = false return obj end function PromptInput:open() self:close() local bufnr = api.nvim_create_buf(false, true) self.bufnr = bufnr vim.bo[bufnr].filetype = "AvanteInput" Utils.mark_as_sidebar_buffer(bufnr) local win_opts = vim.tbl_extend("force", { relative = "cursor", width = 40, height = 2, row = 1, col = 0, style = "minimal", border = Config.windows.edit.border, title = { { "Input", "FloatTitle" } }, title_pos = "center", }, self.win_opts) local winid = api.nvim_open_win(bufnr, true, win_opts) self.winid = winid api.nvim_set_option_value("wrap", false, { win = winid }) api.nvim_set_option_value("cursorline", true, { win = winid }) api.nvim_set_option_value("modifiable", true, { buf = bufnr }) self:show_shortcuts_hints() self:setup_keymaps() self:setup_autocmds() if self.start_insert then vim.cmd([[startinsert]]) end end function PromptInput:close() if not self.bufnr then return end self:stop_spinner() self:close_shortcuts_hints() if api.nvim_get_mode().mode == "i" then vim.cmd([[stopinsert]]) end if self.winid and api.nvim_win_is_valid(self.winid) then api.nvim_win_close(self.winid, true) self.winid = nil end if self.bufnr and api.nvim_buf_is_valid(self.bufnr) then api.nvim_buf_delete(self.bufnr, { force = true }) self.bufnr = nil end if self.augroup then api.nvim_del_augroup_by_id(self.augroup) self.augroup = nil end end function PromptInput:cancel() self:close() if self.cancel_callback then self.cancel_callback() end end function PromptInput:submit(input) if self.close_on_submit then self:close() end if self.submit_callback then self.submit_callback(input) end end function PromptInput:show_shortcuts_hints() self:close_shortcuts_hints() if not self.winid or not api.nvim_win_is_valid(self.winid) then return end local win_width = api.nvim_win_get_width(self.winid) local buf_height = api.nvim_buf_line_count(self.bufnr) local hint_text = (vim.fn.mode() ~= "i" and Config.mappings.submit.normal or Config.mappings.submit.insert) .. ": submit" local display_text = hint_text if self.spinner_active then local spinner = self.spinner_chars[self.spinner_index] display_text = spinner .. " " .. hint_text end local buf = api.nvim_create_buf(false, true) api.nvim_buf_set_lines(buf, 0, -1, false, { display_text }) vim.api.nvim_buf_add_highlight(buf, 0, "AvantePopupHint", 0, 0, -1) local width = fn.strdisplaywidth(display_text) local opts = { relative = "win", win = self.winid, width = width, height = 1, row = buf_height, col = math.max(win_width - width, 0), style = "minimal", border = "none", focusable = false, zindex = 100, } self.shortcuts_hints_winid = api.nvim_open_win(buf, false, opts) end function PromptInput:close_shortcuts_hints() if self.shortcuts_hints_winid and api.nvim_win_is_valid(self.shortcuts_hints_winid) then api.nvim_win_close(self.shortcuts_hints_winid, true) self.shortcuts_hints_winid = nil end end function PromptInput:start_spinner() self.spinner_active = true self.spinner_index = 1 if self.spinner_timer then self.spinner_timer:stop() self.spinner_timer:close() self.spinner_timer = nil end self.spinner_timer = vim.loop.new_timer() local spinner_timer = self.spinner_timer if self.spinner_timer then self.spinner_timer:start(0, 100, function() vim.schedule(function() if not self.spinner_active or spinner_timer ~= self.spinner_timer then return end self.spinner_index = (self.spinner_index % #self.spinner_chars) + 1 self:show_shortcuts_hints() end) end) end end function PromptInput:stop_spinner() self.spinner_active = false if self.spinner_timer then self.spinner_timer:stop() self.spinner_timer:close() self.spinner_timer = nil end self:show_shortcuts_hints() end function PromptInput:setup_keymaps() local bufnr = self.bufnr local function get_input() local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) return lines[1] or "" end vim.keymap.set( "i", Config.mappings.submit.insert, function() self:submit(get_input()) end, { buffer = bufnr, noremap = true, silent = true } ) vim.keymap.set( "n", Config.mappings.submit.normal, function() self:submit(get_input()) end, { buffer = bufnr, noremap = true, silent = true } ) vim.keymap.set("n", "", function() self:cancel() end, { buffer = bufnr }) vim.keymap.set("n", "q", function() self:cancel() end, { buffer = bufnr }) end function PromptInput:setup_autocmds() local bufnr = self.bufnr local group = self.augroup api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { group = group, buffer = bufnr, callback = function() self:show_shortcuts_hints() end, }) api.nvim_create_autocmd("ModeChanged", { group = group, pattern = "i:*", callback = function() local cur_buf = api.nvim_get_current_buf() if cur_buf == bufnr then self:show_shortcuts_hints() end end, }) api.nvim_create_autocmd("ModeChanged", { group = group, pattern = "*:i", callback = function() local cur_buf = api.nvim_get_current_buf() if cur_buf == bufnr then self:show_shortcuts_hints() end end, }) local quit_id, close_unfocus quit_id = api.nvim_create_autocmd("QuitPre", { group = group, buffer = bufnr, once = true, nested = true, callback = function() self:cancel() if not quit_id then api.nvim_del_autocmd(quit_id) quit_id = nil end end, }) close_unfocus = api.nvim_create_autocmd("WinLeave", { group = group, buffer = bufnr, callback = function() self:cancel() if close_unfocus then api.nvim_del_autocmd(close_unfocus) close_unfocus = nil end end, }) end return PromptInput