feat: floating input (#721)
* feat: add floating input to ask method
Open a floating input similar to the "edit" input for the "ask" input.
Enabled in config via `Config.windows.ask.floating` or by passing
`{ floating = true }` to the `api.ask` method.
Includes logic to ensure the sidebar uses the correct buffer and selection
if an existing sidebar is open for another code buffer.
Also refactored the `selection` module to extract the floating input
logic into a new `PromptInput` class.
* docs: update config options
* feat: more accurate annotations to prevent user misunderstandings
---------
Co-authored-by: yetone <yetoneful@gmail.com>
This commit is contained in:
committed by
GitHub
parent
b19573cb2a
commit
964715be64
@@ -3,6 +3,7 @@ local Config = require("avante.config")
|
||||
local Llm = require("avante.llm")
|
||||
local Provider = require("avante.providers")
|
||||
local RepoMap = require("avante.repo_map")
|
||||
local PromptInput = require("avante.prompt_input")
|
||||
|
||||
local api = vim.api
|
||||
local fn = vim.fn
|
||||
@@ -11,19 +12,14 @@ local NAMESPACE = api.nvim_create_namespace("avante_selection")
|
||||
local SELECTED_CODE_NAMESPACE = api.nvim_create_namespace("avante_selected_code")
|
||||
local PRIORITY = vim.highlight.priorities.user
|
||||
|
||||
local EDITING_INPUT_START_SPINNER_PATTERN = "AvanteEditingInputStartSpinner"
|
||||
local EDITING_INPUT_STOP_SPINNER_PATTERN = "AvanteEditingInputStopSpinner"
|
||||
|
||||
---@class avante.Selection
|
||||
---@field selection avante.SelectionResult | nil
|
||||
---@field cursor_pos table | nil
|
||||
---@field shortcuts_extmark_id integer | nil
|
||||
---@field selected_code_extmark_id integer | nil
|
||||
---@field augroup integer | nil
|
||||
---@field editing_input_bufnr integer | nil
|
||||
---@field editing_input_winid integer | nil
|
||||
---@field editing_input_shortcuts_hints_winid integer | nil
|
||||
---@field code_winid integer | nil
|
||||
---@field prompt_input PromptInput | nil
|
||||
local Selection = {}
|
||||
|
||||
Selection.did_setup = false
|
||||
@@ -36,10 +32,8 @@ function Selection:new(id)
|
||||
augroup = api.nvim_create_augroup("avante_selection_" .. id, { clear = true }),
|
||||
selection = nil,
|
||||
cursor_pos = nil,
|
||||
editing_input_bufnr = nil,
|
||||
editing_input_winid = nil,
|
||||
editing_input_shortcuts_hints_winid = nil,
|
||||
code_winid = nil,
|
||||
prompt_input = nil,
|
||||
}, { __index = self })
|
||||
end
|
||||
|
||||
@@ -80,13 +74,11 @@ function Selection:close_shortcuts_hints_popup()
|
||||
end
|
||||
|
||||
function Selection:close_editing_input()
|
||||
self:close_editing_input_shortcuts_hints()
|
||||
Llm.cancel_inflight_request()
|
||||
if api.nvim_get_mode().mode == "i" then vim.cmd([[stopinsert]]) end
|
||||
if self.editing_input_winid and api.nvim_win_is_valid(self.editing_input_winid) then
|
||||
api.nvim_win_close(self.editing_input_winid, true)
|
||||
self.editing_input_winid = nil
|
||||
if self.prompt_input then
|
||||
self.prompt_input:close()
|
||||
self.prompt_input = nil
|
||||
end
|
||||
Llm.cancel_inflight_request()
|
||||
if self.code_winid and api.nvim_win_is_valid(self.code_winid) then
|
||||
local code_bufnr = api.nvim_win_get_buf(self.code_winid)
|
||||
api.nvim_buf_clear_namespace(code_bufnr, SELECTED_CODE_NAMESPACE, 0, -1)
|
||||
@@ -105,156 +97,6 @@ function Selection:close_editing_input()
|
||||
api.nvim_win_set_cursor(self.code_winid, { row, col })
|
||||
end)
|
||||
end
|
||||
if self.editing_input_bufnr and api.nvim_buf_is_valid(self.editing_input_bufnr) then
|
||||
api.nvim_buf_delete(self.editing_input_bufnr, { force = true })
|
||||
self.editing_input_bufnr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Selection:close_editing_input_shortcuts_hints()
|
||||
if self.editing_input_shortcuts_hints_winid and api.nvim_win_is_valid(self.editing_input_shortcuts_hints_winid) then
|
||||
api.nvim_win_close(self.editing_input_shortcuts_hints_winid, true)
|
||||
self.editing_input_shortcuts_hints_winid = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Selection:show_editing_input_shortcuts_hints()
|
||||
self:close_editing_input_shortcuts_hints()
|
||||
|
||||
if not self.editing_input_winid or not api.nvim_win_is_valid(self.editing_input_winid) then return end
|
||||
|
||||
local win_width = api.nvim_win_get_width(self.editing_input_winid)
|
||||
local buf_height = api.nvim_buf_line_count(self.editing_input_bufnr)
|
||||
-- spinner string: "⡀⠄⠂⠁⠈⠐⠠⢀⣀⢄⢂⢁⢈⢐⢠⣠⢤⢢⢡⢨⢰⣰⢴⢲⢱⢸⣸⢼⢺⢹⣹⢽⢻⣻⢿⣿⣶⣤⣀"
|
||||
local spinner_chars = {
|
||||
"⡀",
|
||||
"⠄",
|
||||
"⠂",
|
||||
"⠁",
|
||||
"⠈",
|
||||
"⠐",
|
||||
"⠠",
|
||||
"⢀",
|
||||
"⣀",
|
||||
"⢄",
|
||||
"⢂",
|
||||
"⢁",
|
||||
"⢈",
|
||||
"⢐",
|
||||
"⢠",
|
||||
"⣠",
|
||||
"⢤",
|
||||
"⢢",
|
||||
"⢡",
|
||||
"⢨",
|
||||
"⢰",
|
||||
"⣰",
|
||||
"⢴",
|
||||
"⢲",
|
||||
"⢱",
|
||||
"⢸",
|
||||
"⣸",
|
||||
"⢼",
|
||||
"⢺",
|
||||
"⢹",
|
||||
"⣹",
|
||||
"⢽",
|
||||
"⢻",
|
||||
"⣻",
|
||||
"⢿",
|
||||
"⣿",
|
||||
"⣶",
|
||||
"⣤",
|
||||
"⣀",
|
||||
}
|
||||
local spinner_index = 1
|
||||
local timer = nil
|
||||
|
||||
local hint_text = (vim.fn.mode() ~= "i" and Config.mappings.submit.normal or Config.mappings.submit.insert)
|
||||
.. ": submit"
|
||||
|
||||
local buf = api.nvim_create_buf(false, true)
|
||||
api.nvim_buf_set_lines(buf, 0, -1, false, { hint_text })
|
||||
vim.api.nvim_buf_add_highlight(buf, 0, "AvantePopupHint", 0, 0, -1)
|
||||
|
||||
local function update_spinner()
|
||||
spinner_index = (spinner_index % #spinner_chars) + 1
|
||||
local spinner = spinner_chars[spinner_index]
|
||||
local new_text = spinner .. " " .. hint_text
|
||||
|
||||
api.nvim_buf_set_lines(buf, 0, -1, false, { new_text })
|
||||
|
||||
if
|
||||
not self.editing_input_shortcuts_hints_winid
|
||||
or not api.nvim_win_is_valid(self.editing_input_shortcuts_hints_winid)
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
local win_config = vim.api.nvim_win_get_config(self.editing_input_shortcuts_hints_winid)
|
||||
|
||||
local new_width = fn.strdisplaywidth(new_text)
|
||||
|
||||
if win_config.width ~= new_width then
|
||||
win_config.width = new_width
|
||||
win_config.col = math.max(win_width - new_width, 0)
|
||||
vim.api.nvim_win_set_config(self.editing_input_shortcuts_hints_winid, win_config)
|
||||
end
|
||||
end
|
||||
|
||||
local function stop_spinner()
|
||||
if timer then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
timer = nil
|
||||
end
|
||||
api.nvim_buf_set_lines(buf, 0, -1, false, { hint_text })
|
||||
|
||||
if
|
||||
not self.editing_input_shortcuts_hints_winid
|
||||
or not api.nvim_win_is_valid(self.editing_input_shortcuts_hints_winid)
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
local win_config = vim.api.nvim_win_get_config(self.editing_input_shortcuts_hints_winid)
|
||||
|
||||
if win_config.width ~= #hint_text then
|
||||
win_config.width = #hint_text
|
||||
win_config.col = math.max(win_width - #hint_text, 0)
|
||||
vim.api.nvim_win_set_config(self.editing_input_shortcuts_hints_winid, win_config)
|
||||
end
|
||||
end
|
||||
|
||||
api.nvim_create_autocmd("User", {
|
||||
pattern = EDITING_INPUT_START_SPINNER_PATTERN,
|
||||
callback = function()
|
||||
timer = vim.uv.new_timer()
|
||||
if timer then timer:start(0, 100, vim.schedule_wrap(function() update_spinner() end)) end
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd("User", {
|
||||
pattern = EDITING_INPUT_STOP_SPINNER_PATTERN,
|
||||
callback = function() stop_spinner() end,
|
||||
})
|
||||
|
||||
local width = fn.strdisplaywidth(hint_text)
|
||||
|
||||
local opts = {
|
||||
relative = "win",
|
||||
win = self.editing_input_winid,
|
||||
width = width,
|
||||
height = 1,
|
||||
row = buf_height,
|
||||
col = math.max(win_width - width, 0),
|
||||
style = "minimal",
|
||||
border = "none",
|
||||
focusable = false,
|
||||
zindex = 100,
|
||||
}
|
||||
|
||||
self.editing_input_shortcuts_hints_winid = api.nvim_open_win(buf, false, opts)
|
||||
end
|
||||
|
||||
function Selection:create_editing_input()
|
||||
@@ -266,9 +108,9 @@ function Selection:create_editing_input()
|
||||
end
|
||||
|
||||
local code_bufnr = api.nvim_get_current_buf()
|
||||
local code_wind = api.nvim_get_current_win()
|
||||
self.cursor_pos = api.nvim_win_get_cursor(code_wind)
|
||||
self.code_winid = code_wind
|
||||
local code_winid = api.nvim_get_current_win()
|
||||
self.cursor_pos = api.nvim_win_get_cursor(code_winid)
|
||||
self.code_winid = code_winid
|
||||
local code_lines = api.nvim_buf_get_lines(code_bufnr, 0, -1, false)
|
||||
local code_content = table.concat(code_lines, "\n")
|
||||
|
||||
@@ -303,58 +145,7 @@ function Selection:create_editing_input()
|
||||
priority = PRIORITY,
|
||||
})
|
||||
|
||||
local bufnr = api.nvim_create_buf(false, true)
|
||||
|
||||
self.editing_input_bufnr = bufnr
|
||||
|
||||
local win_opts = {
|
||||
relative = "cursor",
|
||||
width = 40,
|
||||
height = 2,
|
||||
row = 1,
|
||||
col = 0,
|
||||
style = "minimal",
|
||||
border = Config.windows.edit.border,
|
||||
title = { { "edit selected block", "FloatTitle" } },
|
||||
title_pos = "center",
|
||||
}
|
||||
|
||||
local winid = api.nvim_open_win(bufnr, true, win_opts)
|
||||
|
||||
self.editing_input_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_editing_input_shortcuts_hints()
|
||||
|
||||
api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
group = self.augroup,
|
||||
buffer = bufnr,
|
||||
callback = function() self:show_editing_input_shortcuts_hints() end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd("ModeChanged", {
|
||||
group = self.augroup,
|
||||
pattern = "i:*",
|
||||
callback = function()
|
||||
local cur_buf = api.nvim_get_current_buf()
|
||||
if cur_buf == bufnr then self:show_editing_input_shortcuts_hints() end
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd("ModeChanged", {
|
||||
group = self.augroup,
|
||||
pattern = "*:i",
|
||||
callback = function()
|
||||
local cur_buf = api.nvim_get_current_buf()
|
||||
if cur_buf == bufnr then self:show_editing_input_shortcuts_hints() end
|
||||
end,
|
||||
})
|
||||
|
||||
---@param input string
|
||||
local function submit_input(input)
|
||||
local submit_input = function(input)
|
||||
local full_response = ""
|
||||
local start_line = self.selection.range.start.line
|
||||
local finish_line = self.selection.range.finish.line
|
||||
@@ -363,7 +154,8 @@ function Selection:create_editing_input()
|
||||
|
||||
local need_prepend_indentation = false
|
||||
|
||||
api.nvim_exec_autocmds("User", { pattern = EDITING_INPUT_START_SPINNER_PATTERN })
|
||||
self.prompt_input:start_spinner()
|
||||
|
||||
---@type AvanteChunkParser
|
||||
local on_chunk = function(chunk)
|
||||
full_response = full_response .. chunk
|
||||
@@ -397,7 +189,7 @@ function Selection:create_editing_input()
|
||||
)
|
||||
return
|
||||
end
|
||||
api.nvim_exec_autocmds("User", { pattern = EDITING_INPUT_STOP_SPINNER_PATTERN })
|
||||
self.prompt_input:stop_spinner()
|
||||
vim.defer_fn(function() self:close_editing_input() end, 0)
|
||||
end
|
||||
|
||||
@@ -422,63 +214,32 @@ function Selection:create_editing_input()
|
||||
})
|
||||
end
|
||||
|
||||
---@return string
|
||||
local get_bufnr_input = function()
|
||||
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() submit_input(get_bufnr_input()) end,
|
||||
{ buffer = bufnr, noremap = true, silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
Config.mappings.submit.normal,
|
||||
function() submit_input(get_bufnr_input()) end,
|
||||
{ buffer = bufnr, noremap = true, silent = true }
|
||||
)
|
||||
vim.keymap.set("n", "<Esc>", function() self:close_editing_input() end, { buffer = bufnr })
|
||||
vim.keymap.set("n", "q", function() self:close_editing_input() end, { buffer = bufnr })
|
||||
|
||||
local quit_id, close_unfocus
|
||||
quit_id = api.nvim_create_autocmd("QuitPre", {
|
||||
group = self.augroup,
|
||||
buffer = bufnr,
|
||||
once = true,
|
||||
nested = true,
|
||||
callback = function()
|
||||
self:close_editing_input()
|
||||
if not quit_id then
|
||||
api.nvim_del_autocmd(quit_id)
|
||||
quit_id = nil
|
||||
end
|
||||
end,
|
||||
local prompt_input = PromptInput:new({
|
||||
submit_callback = submit_input,
|
||||
cancel_callback = function() self:close_editing_input() end,
|
||||
win_opts = {
|
||||
border = Config.windows.edit.border,
|
||||
title = { { "edit selected block", "FloatTitle" } },
|
||||
},
|
||||
start_insert = Config.windows.edit.start_insert,
|
||||
})
|
||||
|
||||
close_unfocus = api.nvim_create_autocmd("WinLeave", {
|
||||
group = self.augroup,
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
self:close_editing_input()
|
||||
if close_unfocus then
|
||||
api.nvim_del_autocmd(close_unfocus)
|
||||
close_unfocus = nil
|
||||
end
|
||||
end,
|
||||
})
|
||||
self.prompt_input = prompt_input
|
||||
|
||||
prompt_input:open()
|
||||
|
||||
api.nvim_create_autocmd("InsertEnter", {
|
||||
group = self.augroup,
|
||||
buffer = bufnr,
|
||||
buffer = prompt_input.bufnr,
|
||||
once = true,
|
||||
desc = "Setup the completion of helpers in the input buffer",
|
||||
callback = function()
|
||||
local has_cmp, cmp = pcall(require, "cmp")
|
||||
if has_cmp then
|
||||
cmp.register_source("avante_mentions", require("cmp_avante.mentions").new(Utils.get_mentions(), bufnr))
|
||||
cmp.register_source(
|
||||
"avante_mentions",
|
||||
require("cmp_avante.mentions").new(Utils.get_mentions(), prompt_input.bufnr)
|
||||
)
|
||||
cmp.setup.buffer({
|
||||
enabled = true,
|
||||
sources = {
|
||||
@@ -488,13 +249,6 @@ function Selection:create_editing_input()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd("User", {
|
||||
pattern = "AvanteEditSubmitted",
|
||||
callback = function(ev)
|
||||
if ev.data and ev.data.request then submit_input(ev.data.request) end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function Selection:setup_autocmds()
|
||||
|
||||
Reference in New Issue
Block a user