fix: close all hint windows when the sidebar is closed (#1952)
This commit is contained in:
@@ -22,8 +22,11 @@ local VIEW_BUFFER_UPDATED_PATTERN = "AvanteViewBufferUpdated"
|
|||||||
local CODEBLOCK_KEYBINDING_NAMESPACE = api.nvim_create_namespace("AVANTE_CODEBLOCK_KEYBINDING")
|
local CODEBLOCK_KEYBINDING_NAMESPACE = api.nvim_create_namespace("AVANTE_CODEBLOCK_KEYBINDING")
|
||||||
local USER_REQUEST_BLOCK_KEYBINDING_NAMESPACE = api.nvim_create_namespace("AVANTE_USER_REQUEST_BLOCK_KEYBINDING")
|
local USER_REQUEST_BLOCK_KEYBINDING_NAMESPACE = api.nvim_create_namespace("AVANTE_USER_REQUEST_BLOCK_KEYBINDING")
|
||||||
local SELECTED_FILES_HINT_NAMESPACE = api.nvim_create_namespace("AVANTE_SELECTED_FILES_HINT")
|
local SELECTED_FILES_HINT_NAMESPACE = api.nvim_create_namespace("AVANTE_SELECTED_FILES_HINT")
|
||||||
local PRIORITY = (vim.hl or vim.highlight).priorities.user
|
|
||||||
local SELECTED_FILES_ICON_NAMESPACE = api.nvim_create_namespace("AVANTE_SELECTED_FILES_ICON")
|
local SELECTED_FILES_ICON_NAMESPACE = api.nvim_create_namespace("AVANTE_SELECTED_FILES_ICON")
|
||||||
|
local INPUT_HINT_NAMESPACE = api.nvim_create_namespace("AVANTE_INPUT_HINT")
|
||||||
|
local STATE_NAMESPACE = api.nvim_create_namespace("AVANTE_STATE")
|
||||||
|
|
||||||
|
local PRIORITY = (vim.hl or vim.highlight).priorities.user
|
||||||
|
|
||||||
local RESP_SEPARATOR = "-------"
|
local RESP_SEPARATOR = "-------"
|
||||||
|
|
||||||
@@ -52,9 +55,10 @@ Sidebar.__index = Sidebar
|
|||||||
---@field state_timer table | nil
|
---@field state_timer table | nil
|
||||||
---@field state_spinner_chars string[]
|
---@field state_spinner_chars string[]
|
||||||
---@field state_spinner_idx integer
|
---@field state_spinner_idx integer
|
||||||
---@field state_ns_id integer
|
|
||||||
---@field state_extmark_id integer | nil
|
---@field state_extmark_id integer | nil
|
||||||
---@field scroll boolean
|
---@field scroll boolean
|
||||||
|
---@field input_hint_window integer | nil
|
||||||
|
---@field ask_opts AskOptions
|
||||||
|
|
||||||
---@param id integer the tabpage id retrieved from api.nvim_get_current_tabpage()
|
---@param id integer the tabpage id retrieved from api.nvim_get_current_tabpage()
|
||||||
function Sidebar:new(id)
|
function Sidebar:new(id)
|
||||||
@@ -78,9 +82,10 @@ function Sidebar:new(id)
|
|||||||
state_timer = nil,
|
state_timer = nil,
|
||||||
state_spinner_chars = { "·", "✢", "✳", "∗", "✻", "✽" },
|
state_spinner_chars = { "·", "✢", "✳", "∗", "✻", "✽" },
|
||||||
state_spinner_idx = 1,
|
state_spinner_idx = 1,
|
||||||
state_ns_id = api.nvim_create_namespace("avante_generate_state"),
|
|
||||||
state_extmark_id = nil,
|
state_extmark_id = nil,
|
||||||
scroll = true,
|
scroll = true,
|
||||||
|
input_hint_window = nil,
|
||||||
|
ask_opts = {},
|
||||||
}, Sidebar)
|
}, Sidebar)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -499,31 +504,6 @@ local function generate_display_content(replacement)
|
|||||||
return replacement.content
|
return replacement.content
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string | nil filepath
|
|
||||||
---@return boolean skip_next_line
|
|
||||||
local function obtain_filepath_from_codeblock(lines, line_number)
|
|
||||||
local line = lines[line_number]
|
|
||||||
local filepath = line:match("^%s*```%w+:(.+)$")
|
|
||||||
if not filepath then
|
|
||||||
local next_line = lines[line_number + 1]
|
|
||||||
if next_line then
|
|
||||||
local filepath2 = next_line:match("[Ff][Ii][Ll][Ee][Pp][Aa][Tt][Hh]:%s*(.+)%s*")
|
|
||||||
if filepath2 then return filepath2, true end
|
|
||||||
local filepath3 = next_line:match("[Ff][Ii][Ll][Ee]:%s*(.+)%s*")
|
|
||||||
if filepath3 then return filepath3, true end
|
|
||||||
end
|
|
||||||
for i = line_number - 1, line_number - 2, -1 do
|
|
||||||
if i < 1 then break end
|
|
||||||
local line_ = lines[i]
|
|
||||||
local filepath4 = line_:match("[Ff][Ii][Ll][Ee][Pp][Aa][Tt][Hh]:%s*`?(.-)`?%s*$")
|
|
||||||
if filepath4 then return filepath4, false end
|
|
||||||
local filepath5 = line_:match("[Ff][Ii][Ll][Ee]:%s*`?(.-)`?%s*$")
|
|
||||||
if filepath5 then return filepath5, false end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return filepath, false
|
|
||||||
end
|
|
||||||
|
|
||||||
---@class AvanteCodeSnippet
|
---@class AvanteCodeSnippet
|
||||||
---@field range integer[]
|
---@field range integer[]
|
||||||
---@field content string
|
---@field content string
|
||||||
@@ -564,66 +544,6 @@ local function tree_sitter_markdown_parse_code_blocks(source)
|
|||||||
return nodes
|
return nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param response_content string
|
|
||||||
---@return table<string, AvanteCodeSnippet[]>
|
|
||||||
local function extract_cursor_planning_code_snippets_map(response_content, current_filepath, current_filetype)
|
|
||||||
local snippets = {}
|
|
||||||
local lines = vim.split(response_content, "\n")
|
|
||||||
local cumulated_content = ""
|
|
||||||
|
|
||||||
-- use tree-sitter-markdown to parse all code blocks in response_content
|
|
||||||
local lang = "unknown"
|
|
||||||
local start_line
|
|
||||||
for _, node in ipairs(tree_sitter_markdown_parse_code_blocks(response_content)) do
|
|
||||||
if node:type() == "language" then
|
|
||||||
lang = vim.treesitter.get_node_text(node, response_content)
|
|
||||||
lang = vim.split(lang, ":")[1]
|
|
||||||
elseif node:type() == "block_continuation" then
|
|
||||||
start_line, _ = node:start()
|
|
||||||
elseif node:type() == "fenced_code_block_delimiter" and start_line ~= nil and node:start() >= start_line then
|
|
||||||
local end_line, _ = node:start()
|
|
||||||
local filepath, skip_next_line = obtain_filepath_from_codeblock(lines, start_line)
|
|
||||||
if filepath == nil or filepath == "" then
|
|
||||||
if lang == current_filetype then
|
|
||||||
filepath = current_filepath
|
|
||||||
else
|
|
||||||
Utils.warn(
|
|
||||||
string.format(
|
|
||||||
"Failed to parse filepath from code block, and current_filetype `%s` is not the same as the filetype `%s` of the current code block, so ignore this code block",
|
|
||||||
current_filetype,
|
|
||||||
lang
|
|
||||||
)
|
|
||||||
)
|
|
||||||
lang = "unknown"
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if skip_next_line then start_line = start_line + 1 end
|
|
||||||
local this_content = table.concat(vim.list_slice(lines, start_line + 1, end_line), "\n")
|
|
||||||
cumulated_content = cumulated_content .. "\n" .. this_content
|
|
||||||
table.insert(snippets, {
|
|
||||||
range = { 0, 0 },
|
|
||||||
content = cumulated_content,
|
|
||||||
lang = lang,
|
|
||||||
filepath = filepath,
|
|
||||||
start_line_in_response_buf = start_line,
|
|
||||||
end_line_in_response_buf = end_line + 1,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
|
|
||||||
local snippets_map = {}
|
|
||||||
for _, snippet in ipairs(snippets) do
|
|
||||||
if snippet.filepath == "" then goto continue end
|
|
||||||
snippets_map[snippet.filepath] = snippets_map[snippet.filepath] or {}
|
|
||||||
table.insert(snippets_map[snippet.filepath], snippet)
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
|
|
||||||
return snippets_map
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param response_content string
|
---@param response_content string
|
||||||
---@return table<string, AvanteCodeSnippet[]>
|
---@return table<string, AvanteCodeSnippet[]>
|
||||||
local function extract_code_snippets_map(response_content)
|
local function extract_code_snippets_map(response_content)
|
||||||
@@ -852,7 +772,7 @@ end
|
|||||||
|
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@return AvanteCodeblock[]
|
---@return AvanteCodeblock[]
|
||||||
local function parse_codeblocks(buf, current_filepath, current_filetype)
|
local function parse_codeblocks(buf)
|
||||||
local codeblocks = {}
|
local codeblocks = {}
|
||||||
local lines = Utils.get_buf_lines(0, -1, buf)
|
local lines = Utils.get_buf_lines(0, -1, buf)
|
||||||
local lang, start_line, valid
|
local lang, start_line, valid
|
||||||
@@ -954,8 +874,6 @@ end
|
|||||||
|
|
||||||
---@param current_cursor boolean
|
---@param current_cursor boolean
|
||||||
function Sidebar:apply(current_cursor)
|
function Sidebar:apply(current_cursor)
|
||||||
local buf_path = api.nvim_buf_get_name(self.code.bufnr)
|
|
||||||
|
|
||||||
local response, response_start_line = self:get_content_between_separators()
|
local response, response_start_line = self:get_content_between_separators()
|
||||||
local all_snippets_map = extract_code_snippets_map(response)
|
local all_snippets_map = extract_code_snippets_map(response)
|
||||||
all_snippets_map = ensure_snippets_no_overlap(all_snippets_map)
|
all_snippets_map = ensure_snippets_no_overlap(all_snippets_map)
|
||||||
@@ -1387,15 +1305,11 @@ function Sidebar:on_mount(opts)
|
|||||||
})
|
})
|
||||||
|
|
||||||
if self.code.bufnr and api.nvim_buf_is_valid(self.code.bufnr) then
|
if self.code.bufnr and api.nvim_buf_is_valid(self.code.bufnr) then
|
||||||
local buf_path = api.nvim_buf_get_name(self.code.bufnr)
|
|
||||||
local current_filepath = Utils.file.is_in_cwd(buf_path) and Utils.relative_path(buf_path) or buf_path
|
|
||||||
local current_filetype = Utils.get_filetype(current_filepath)
|
|
||||||
|
|
||||||
api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, {
|
api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, {
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
buffer = self.result_container.bufnr,
|
buffer = self.result_container.bufnr,
|
||||||
callback = function(ev)
|
callback = function(ev)
|
||||||
codeblocks = parse_codeblocks(ev.buf, current_filepath, current_filetype)
|
codeblocks = parse_codeblocks(ev.buf)
|
||||||
self:bind_sidebar_keys(codeblocks)
|
self:bind_sidebar_keys(codeblocks)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -1405,7 +1319,7 @@ function Sidebar:on_mount(opts)
|
|||||||
pattern = VIEW_BUFFER_UPDATED_PATTERN,
|
pattern = VIEW_BUFFER_UPDATED_PATTERN,
|
||||||
callback = function()
|
callback = function()
|
||||||
if not Utils.is_valid_container(self.result_container) then return end
|
if not Utils.is_valid_container(self.result_container) then return end
|
||||||
codeblocks = parse_codeblocks(self.result_container.bufnr, current_filepath, current_filetype)
|
codeblocks = parse_codeblocks(self.result_container.bufnr)
|
||||||
self:bind_sidebar_keys(codeblocks)
|
self:bind_sidebar_keys(codeblocks)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -1800,7 +1714,7 @@ end
|
|||||||
|
|
||||||
function Sidebar:clear_state()
|
function Sidebar:clear_state()
|
||||||
if self.state_extmark_id then
|
if self.state_extmark_id then
|
||||||
pcall(api.nvim_buf_del_extmark, self.result_container.bufnr, self.state_ns_id, self.state_extmark_id)
|
pcall(api.nvim_buf_del_extmark, self.result_container.bufnr, STATE_NAMESPACE, self.state_extmark_id)
|
||||||
end
|
end
|
||||||
self.state_extmark_id = nil
|
self.state_extmark_id = nil
|
||||||
self.state_spinner_idx = 1
|
self.state_spinner_idx = 1
|
||||||
@@ -1812,7 +1726,7 @@ function Sidebar:render_state()
|
|||||||
if not self.current_state then return end
|
if not self.current_state then return end
|
||||||
local lines = vim.api.nvim_buf_get_lines(self.result_container.bufnr, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(self.result_container.bufnr, 0, -1, false)
|
||||||
if self.state_extmark_id then
|
if self.state_extmark_id then
|
||||||
api.nvim_buf_del_extmark(self.result_container.bufnr, self.state_ns_id, self.state_extmark_id)
|
api.nvim_buf_del_extmark(self.result_container.bufnr, STATE_NAMESPACE, self.state_extmark_id)
|
||||||
end
|
end
|
||||||
local spinner_char = self.state_spinner_chars[self.state_spinner_idx]
|
local spinner_char = self.state_spinner_chars[self.state_spinner_idx]
|
||||||
self.state_spinner_idx = (self.state_spinner_idx % #self.state_spinner_chars) + 1
|
self.state_spinner_idx = (self.state_spinner_idx % #self.state_spinner_chars) + 1
|
||||||
@@ -1837,7 +1751,7 @@ function Sidebar:render_state()
|
|||||||
}
|
}
|
||||||
|
|
||||||
local line_num = math.max(0, #lines - 2)
|
local line_num = math.max(0, #lines - 2)
|
||||||
self.state_extmark_id = api.nvim_buf_set_extmark(self.result_container.bufnr, self.state_ns_id, line_num, 0, {
|
self.state_extmark_id = api.nvim_buf_set_extmark(self.result_container.bufnr, STATE_NAMESPACE, line_num, 0, {
|
||||||
virt_lines = centered_virt_lines,
|
virt_lines = centered_virt_lines,
|
||||||
hl_eol = true,
|
hl_eol = true,
|
||||||
hl_mode = "combine",
|
hl_mode = "combine",
|
||||||
@@ -1969,7 +1883,103 @@ function Sidebar:create_selected_code_container()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local hint_window = nil
|
function Sidebar:close_input_hint()
|
||||||
|
if self.input_hint_window and api.nvim_win_is_valid(self.input_hint_window) then
|
||||||
|
local buf = api.nvim_win_get_buf(self.input_hint_window)
|
||||||
|
if INPUT_HINT_NAMESPACE then api.nvim_buf_clear_namespace(buf, INPUT_HINT_NAMESPACE, 0, -1) end
|
||||||
|
api.nvim_win_close(self.input_hint_window, true)
|
||||||
|
api.nvim_buf_delete(buf, { force = true })
|
||||||
|
self.input_hint_window = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sidebar:get_input_float_window_row()
|
||||||
|
local win_height = api.nvim_win_get_height(self.input_container.winid)
|
||||||
|
local winline = Utils.winline(self.input_container.winid)
|
||||||
|
if winline >= win_height - 1 then return 0 end
|
||||||
|
return winline
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a floating window as a hint
|
||||||
|
function Sidebar:show_input_hint()
|
||||||
|
self:close_input_hint() -- Close the existing hint window
|
||||||
|
|
||||||
|
local hint_text = (fn.mode() ~= "i" and Config.mappings.submit.normal or Config.mappings.submit.insert) .. ": submit"
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
local buf = api.nvim_create_buf(false, true)
|
||||||
|
api.nvim_buf_set_lines(buf, 0, -1, false, { hint_text })
|
||||||
|
api.nvim_buf_set_extmark(buf, INPUT_HINT_NAMESPACE, 0, 0, { hl_group = "AvantePopupHint", end_col = #hint_text })
|
||||||
|
|
||||||
|
-- Get the current window size
|
||||||
|
local win_width = api.nvim_win_get_width(self.input_container.winid)
|
||||||
|
local width = #hint_text
|
||||||
|
|
||||||
|
-- Set the floating window options
|
||||||
|
local win_opts = {
|
||||||
|
relative = "win",
|
||||||
|
win = self.input_container.winid,
|
||||||
|
width = width,
|
||||||
|
height = 1,
|
||||||
|
row = self:get_input_float_window_row(),
|
||||||
|
col = math.max(win_width - width, 0), -- Display in the bottom right corner
|
||||||
|
style = "minimal",
|
||||||
|
border = "none",
|
||||||
|
focusable = false,
|
||||||
|
zindex = 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Create the floating window
|
||||||
|
self.input_hint_window = api.nvim_open_win(buf, false, win_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
if Config.behaviour.enable_token_counting then
|
||||||
|
local input_value = table.concat(api.nvim_buf_get_lines(self.input_container.bufnr, 0, -1, false), "\n")
|
||||||
|
self:get_generate_prompts_options(input_value, function(generate_prompts_options)
|
||||||
|
local tokens = Llm.calculate_tokens(generate_prompts_options) + Utils.tokens.calculate_tokens(input_value)
|
||||||
|
hint_text = "Tokens: " .. tostring(tokens) .. "; " .. hint_text
|
||||||
|
show()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sidebar:close_selected_files_hint()
|
||||||
|
if self.selected_files_container and api.nvim_win_is_valid(self.selected_files_container.winid) then
|
||||||
|
pcall(api.nvim_buf_clear_namespace, self.selected_files_container.bufnr, SELECTED_FILES_HINT_NAMESPACE, 0, -1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sidebar:show_selected_files_hint()
|
||||||
|
self:close_selected_files_hint()
|
||||||
|
|
||||||
|
local cursor_pos = api.nvim_win_get_cursor(self.selected_files_container.winid)
|
||||||
|
local line_number = cursor_pos[1]
|
||||||
|
local col_number = cursor_pos[2]
|
||||||
|
|
||||||
|
local selected_filepaths_ = self.file_selector:get_selected_filepaths()
|
||||||
|
local hint
|
||||||
|
if #selected_filepaths_ == 0 then
|
||||||
|
hint = string.format(" [%s: add] ", Config.mappings.sidebar.add_file)
|
||||||
|
else
|
||||||
|
hint =
|
||||||
|
string.format(" [%s: delete, %s: add] ", Config.mappings.sidebar.remove_file, Config.mappings.sidebar.add_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
api.nvim_buf_set_extmark(
|
||||||
|
self.selected_files_container.bufnr,
|
||||||
|
SELECTED_FILES_HINT_NAMESPACE,
|
||||||
|
line_number - 1,
|
||||||
|
col_number,
|
||||||
|
{
|
||||||
|
virt_text = { { hint, "AvanteInlineHint" } },
|
||||||
|
virt_text_pos = "right_align",
|
||||||
|
hl_group = "AvanteInlineHint",
|
||||||
|
priority = PRIORITY,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
function Sidebar:reload_chat_history()
|
function Sidebar:reload_chat_history()
|
||||||
if not self.code.bufnr or not api.nvim_buf_is_valid(self.code.bufnr) then return end
|
if not self.code.bufnr or not api.nvim_buf_is_valid(self.code.bufnr) then return end
|
||||||
@@ -1992,109 +2002,108 @@ function Sidebar:get_history_messages_for_api()
|
|||||||
return vim.iter(history_messages):filter(function(message) return not message.just_for_display end):totable()
|
return vim.iter(history_messages):filter(function(message) return not message.just_for_display end):totable()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param opts AskOptions
|
---@param request string
|
||||||
function Sidebar:create_input_container(opts)
|
---@param cb fun(opts: AvanteGeneratePromptsOptions): nil
|
||||||
|
function Sidebar:get_generate_prompts_options(request, cb)
|
||||||
|
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
||||||
|
local file_ext = nil
|
||||||
|
|
||||||
|
-- Get file extension safely
|
||||||
|
local buf_name = api.nvim_buf_get_name(self.code.bufnr)
|
||||||
|
if buf_name and buf_name ~= "" then file_ext = vim.fn.fnamemodify(buf_name, ":e") end
|
||||||
|
|
||||||
|
---@type AvanteSelectedCode | nil
|
||||||
|
local selected_code = nil
|
||||||
|
if self.code.selection ~= nil then
|
||||||
|
selected_code = {
|
||||||
|
path = self.code.selection.filepath,
|
||||||
|
file_type = self.code.selection.filetype,
|
||||||
|
content = self.code.selection.content,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local mentions = Utils.extract_mentions(request)
|
||||||
|
request = mentions.new_content
|
||||||
|
|
||||||
|
local project_context = mentions.enable_project_context and file_ext and RepoMap.get_repo_map(file_ext) or nil
|
||||||
|
|
||||||
|
local diagnostics = nil
|
||||||
|
if mentions.enable_diagnostics then
|
||||||
|
if self.code ~= nil and self.code.bufnr ~= nil and self.code.selection ~= nil then
|
||||||
|
diagnostics = Utils.get_current_selection_diagnostics(self.code.bufnr, self.code.selection)
|
||||||
|
else
|
||||||
|
diagnostics = Utils.get_diagnostics(self.code.bufnr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local history_messages = self:get_history_messages_for_api()
|
||||||
|
|
||||||
|
local tools = vim.deepcopy(LLMTools.get_tools(request, history_messages))
|
||||||
|
table.insert(tools, {
|
||||||
|
name = "add_file_to_context",
|
||||||
|
description = "Add a file to the context",
|
||||||
|
---@type AvanteLLMToolFunc<{ rel_path: string }>
|
||||||
|
func = function(input)
|
||||||
|
self.file_selector:add_selected_file(input.rel_path)
|
||||||
|
return "Added file to context", nil
|
||||||
|
end,
|
||||||
|
param = {
|
||||||
|
type = "table",
|
||||||
|
fields = { { name = "rel_path", description = "Relative path to the file", type = "string" } },
|
||||||
|
},
|
||||||
|
returns = {},
|
||||||
|
})
|
||||||
|
|
||||||
|
table.insert(tools, {
|
||||||
|
name = "remove_file_from_context",
|
||||||
|
description = "Remove a file from the context",
|
||||||
|
---@type AvanteLLMToolFunc<{ rel_path: string }>
|
||||||
|
func = function(input)
|
||||||
|
self.file_selector:remove_selected_file(input.rel_path)
|
||||||
|
return "Removed file from context", nil
|
||||||
|
end,
|
||||||
|
param = {
|
||||||
|
type = "table",
|
||||||
|
fields = { { name = "rel_path", description = "Relative path to the file", type = "string" } },
|
||||||
|
},
|
||||||
|
returns = {},
|
||||||
|
})
|
||||||
|
|
||||||
|
local selected_filepaths = self.file_selector.selected_filepaths or {}
|
||||||
|
|
||||||
|
---@type AvanteGeneratePromptsOptions
|
||||||
|
local prompts_opts = {
|
||||||
|
ask = self.ask_opts.ask or true,
|
||||||
|
project_context = vim.json.encode(project_context),
|
||||||
|
selected_filepaths = selected_filepaths,
|
||||||
|
recently_viewed_files = Utils.get_recent_filepaths(),
|
||||||
|
diagnostics = vim.json.encode(diagnostics),
|
||||||
|
history_messages = history_messages,
|
||||||
|
code_lang = filetype,
|
||||||
|
selected_code = selected_code,
|
||||||
|
-- instructions = request,
|
||||||
|
tools = tools,
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.chat_history.system_prompt then
|
||||||
|
prompts_opts.prompt_opts = {
|
||||||
|
system_prompt = self.chat_history.system_prompt,
|
||||||
|
messages = history_messages,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.chat_history.memory then prompts_opts.memory = self.chat_history.memory.content end
|
||||||
|
|
||||||
|
cb(prompts_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sidebar:create_input_container()
|
||||||
if self.input_container then self.input_container:unmount() end
|
if self.input_container then self.input_container:unmount() end
|
||||||
|
|
||||||
if not self.code.bufnr or not api.nvim_buf_is_valid(self.code.bufnr) then return end
|
if not self.code.bufnr or not api.nvim_buf_is_valid(self.code.bufnr) then return end
|
||||||
|
|
||||||
if self.chat_history == nil then self:reload_chat_history() end
|
if self.chat_history == nil then self:reload_chat_history() end
|
||||||
|
|
||||||
---@param request string
|
|
||||||
---@param cb fun(opts: AvanteGeneratePromptsOptions): nil
|
|
||||||
local function get_generate_prompts_options(request, cb)
|
|
||||||
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
|
||||||
local file_ext = nil
|
|
||||||
|
|
||||||
-- Get file extension safely
|
|
||||||
local buf_name = api.nvim_buf_get_name(self.code.bufnr)
|
|
||||||
if buf_name and buf_name ~= "" then file_ext = vim.fn.fnamemodify(buf_name, ":e") end
|
|
||||||
|
|
||||||
---@type AvanteSelectedCode | nil
|
|
||||||
local selected_code = nil
|
|
||||||
if self.code.selection ~= nil then
|
|
||||||
selected_code = {
|
|
||||||
path = self.code.selection.filepath,
|
|
||||||
file_type = self.code.selection.filetype,
|
|
||||||
content = self.code.selection.content,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local mentions = Utils.extract_mentions(request)
|
|
||||||
request = mentions.new_content
|
|
||||||
|
|
||||||
local project_context = mentions.enable_project_context and file_ext and RepoMap.get_repo_map(file_ext) or nil
|
|
||||||
|
|
||||||
local diagnostics = nil
|
|
||||||
if mentions.enable_diagnostics then
|
|
||||||
if self.code ~= nil and self.code.bufnr ~= nil and self.code.selection ~= nil then
|
|
||||||
diagnostics = Utils.get_current_selection_diagnostics(self.code.bufnr, self.code.selection)
|
|
||||||
else
|
|
||||||
diagnostics = Utils.get_diagnostics(self.code.bufnr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local history_messages = self:get_history_messages_for_api()
|
|
||||||
|
|
||||||
local tools = vim.deepcopy(LLMTools.get_tools(request, history_messages))
|
|
||||||
table.insert(tools, {
|
|
||||||
name = "add_file_to_context",
|
|
||||||
description = "Add a file to the context",
|
|
||||||
---@type AvanteLLMToolFunc<{ rel_path: string }>
|
|
||||||
func = function(input)
|
|
||||||
self.file_selector:add_selected_file(input.rel_path)
|
|
||||||
return "Added file to context", nil
|
|
||||||
end,
|
|
||||||
param = {
|
|
||||||
type = "table",
|
|
||||||
fields = { { name = "rel_path", description = "Relative path to the file", type = "string" } },
|
|
||||||
},
|
|
||||||
returns = {},
|
|
||||||
})
|
|
||||||
|
|
||||||
table.insert(tools, {
|
|
||||||
name = "remove_file_from_context",
|
|
||||||
description = "Remove a file from the context",
|
|
||||||
---@type AvanteLLMToolFunc<{ rel_path: string }>
|
|
||||||
func = function(input)
|
|
||||||
self.file_selector:remove_selected_file(input.rel_path)
|
|
||||||
return "Removed file from context", nil
|
|
||||||
end,
|
|
||||||
param = {
|
|
||||||
type = "table",
|
|
||||||
fields = { { name = "rel_path", description = "Relative path to the file", type = "string" } },
|
|
||||||
},
|
|
||||||
returns = {},
|
|
||||||
})
|
|
||||||
|
|
||||||
local selected_filepaths = self.file_selector.selected_filepaths or {}
|
|
||||||
|
|
||||||
---@type AvanteGeneratePromptsOptions
|
|
||||||
local prompts_opts = {
|
|
||||||
ask = opts.ask or true,
|
|
||||||
project_context = vim.json.encode(project_context),
|
|
||||||
selected_filepaths = selected_filepaths,
|
|
||||||
recently_viewed_files = Utils.get_recent_filepaths(),
|
|
||||||
diagnostics = vim.json.encode(diagnostics),
|
|
||||||
history_messages = history_messages,
|
|
||||||
code_lang = filetype,
|
|
||||||
selected_code = selected_code,
|
|
||||||
-- instructions = request,
|
|
||||||
tools = tools,
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.chat_history.system_prompt then
|
|
||||||
prompts_opts.prompt_opts = {
|
|
||||||
system_prompt = self.chat_history.system_prompt,
|
|
||||||
messages = history_messages,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.chat_history.memory then prompts_opts.memory = self.chat_history.memory.content end
|
|
||||||
|
|
||||||
cb(prompts_opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param request string
|
---@param request string
|
||||||
local function handle_submit(request)
|
local function handle_submit(request)
|
||||||
if self.is_generating then
|
if self.is_generating then
|
||||||
@@ -2287,7 +2296,7 @@ function Sidebar:create_input_container(opts)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
get_generate_prompts_options(request, function(generate_prompts_options)
|
self:get_generate_prompts_options(request, function(generate_prompts_options)
|
||||||
---@type AvanteLLMStreamOptions
|
---@type AvanteLLMStreamOptions
|
||||||
---@diagnostic disable-next-line: assign-type-mismatch
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
local stream_options = vim.tbl_deep_extend("force", generate_prompts_options, {
|
local stream_options = vim.tbl_deep_extend("force", generate_prompts_options, {
|
||||||
@@ -2407,77 +2416,11 @@ function Sidebar:create_input_container(opts)
|
|||||||
callback = function() end,
|
callback = function() end,
|
||||||
})
|
})
|
||||||
|
|
||||||
local hint_ns_id = api.nvim_create_namespace("avante_hint")
|
|
||||||
|
|
||||||
-- Close the floating window
|
|
||||||
local function close_hint()
|
|
||||||
if hint_window and api.nvim_win_is_valid(hint_window) then
|
|
||||||
local buf = api.nvim_win_get_buf(hint_window)
|
|
||||||
if hint_ns_id then api.nvim_buf_clear_namespace(buf, hint_ns_id, 0, -1) end
|
|
||||||
api.nvim_win_close(hint_window, true)
|
|
||||||
api.nvim_buf_delete(buf, { force = true })
|
|
||||||
hint_window = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_float_window_row()
|
|
||||||
local win_height = api.nvim_win_get_height(self.input_container.winid)
|
|
||||||
local winline = Utils.winline(self.input_container.winid)
|
|
||||||
if winline >= win_height - 1 then return 0 end
|
|
||||||
return winline
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create a floating window as a hint
|
|
||||||
local function show_hint()
|
|
||||||
close_hint() -- Close the existing hint window
|
|
||||||
|
|
||||||
local hint_text = (fn.mode() ~= "i" and Config.mappings.submit.normal or Config.mappings.submit.insert)
|
|
||||||
.. ": submit"
|
|
||||||
|
|
||||||
local function show()
|
|
||||||
local buf = api.nvim_create_buf(false, true)
|
|
||||||
api.nvim_buf_set_lines(buf, 0, -1, false, { hint_text })
|
|
||||||
api.nvim_buf_set_extmark(buf, hint_ns_id, 0, 0, { hl_group = "AvantePopupHint", end_col = #hint_text })
|
|
||||||
|
|
||||||
-- Get the current window size
|
|
||||||
local win_width = api.nvim_win_get_width(self.input_container.winid)
|
|
||||||
local width = #hint_text
|
|
||||||
|
|
||||||
-- Set the floating window options
|
|
||||||
local win_opts = {
|
|
||||||
relative = "win",
|
|
||||||
win = self.input_container.winid,
|
|
||||||
width = width,
|
|
||||||
height = 1,
|
|
||||||
row = get_float_window_row(),
|
|
||||||
col = math.max(win_width - width, 0), -- Display in the bottom right corner
|
|
||||||
style = "minimal",
|
|
||||||
border = "none",
|
|
||||||
focusable = false,
|
|
||||||
zindex = 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Create the floating window
|
|
||||||
hint_window = api.nvim_open_win(buf, false, win_opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
if Config.behaviour.enable_token_counting then
|
|
||||||
local input_value = table.concat(api.nvim_buf_get_lines(self.input_container.bufnr, 0, -1, false), "\n")
|
|
||||||
get_generate_prompts_options(input_value, function(generate_prompts_options)
|
|
||||||
local tokens = Llm.calculate_tokens(generate_prompts_options) + Utils.tokens.calculate_tokens(input_value)
|
|
||||||
hint_text = "Tokens: " .. tostring(tokens) .. "; " .. hint_text
|
|
||||||
show()
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
show()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "VimResized" }, {
|
api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "VimResized" }, {
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
buffer = self.input_container.bufnr,
|
buffer = self.input_container.bufnr,
|
||||||
callback = function()
|
callback = function()
|
||||||
show_hint()
|
self:show_input_hint()
|
||||||
place_sign_at_first_line(self.input_container.bufnr)
|
place_sign_at_first_line(self.input_container.bufnr)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -2485,14 +2428,14 @@ function Sidebar:create_input_container(opts)
|
|||||||
api.nvim_create_autocmd("QuitPre", {
|
api.nvim_create_autocmd("QuitPre", {
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
buffer = self.input_container.bufnr,
|
buffer = self.input_container.bufnr,
|
||||||
callback = function() close_hint() end,
|
callback = function() self:close_input_hint() end,
|
||||||
})
|
})
|
||||||
|
|
||||||
api.nvim_create_autocmd("WinClosed", {
|
api.nvim_create_autocmd("WinClosed", {
|
||||||
group = self.augroup,
|
group = self.augroup,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
local closed_winid = tonumber(args.match)
|
local closed_winid = tonumber(args.match)
|
||||||
if closed_winid == self.input_container.winid then close_hint() end
|
if closed_winid == self.input_container.winid then self:close_input_hint() end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2516,7 +2459,7 @@ function Sidebar:create_input_container(opts)
|
|||||||
pattern = "*:i",
|
pattern = "*:i",
|
||||||
callback = function()
|
callback = function()
|
||||||
local cur_buf = api.nvim_get_current_buf()
|
local cur_buf = api.nvim_get_current_buf()
|
||||||
if self.input_container and cur_buf == self.input_container.bufnr then show_hint() end
|
if self.input_container and cur_buf == self.input_container.bufnr then self:show_input_hint() end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2526,7 +2469,7 @@ function Sidebar:create_input_container(opts)
|
|||||||
pattern = "i:*",
|
pattern = "i:*",
|
||||||
callback = function()
|
callback = function()
|
||||||
local cur_buf = api.nvim_get_current_buf()
|
local cur_buf = api.nvim_get_current_buf()
|
||||||
if self.input_container and cur_buf == self.input_container.bufnr then show_hint() end
|
if self.input_container and cur_buf == self.input_container.bufnr then self:show_input_hint() end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2535,9 +2478,9 @@ function Sidebar:create_input_container(opts)
|
|||||||
callback = function()
|
callback = function()
|
||||||
local cur_win = api.nvim_get_current_win()
|
local cur_win = api.nvim_get_current_win()
|
||||||
if self.input_container and cur_win == self.input_container.winid then
|
if self.input_container and cur_win == self.input_container.winid then
|
||||||
show_hint()
|
self:show_input_hint()
|
||||||
else
|
else
|
||||||
close_hint()
|
self:close_input_hint()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
@@ -2550,6 +2493,9 @@ function Sidebar:create_input_container(opts)
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- Clear hint when leaving the window
|
||||||
|
self.input_container:on(event.BufLeave, function() self:close_input_hint() end, {})
|
||||||
|
|
||||||
self:refresh_winids()
|
self:refresh_winids()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2616,6 +2562,8 @@ end
|
|||||||
|
|
||||||
---@param opts AskOptions
|
---@param opts AskOptions
|
||||||
function Sidebar:render(opts)
|
function Sidebar:render(opts)
|
||||||
|
self.ask_opts = opts
|
||||||
|
|
||||||
local function get_position()
|
local function get_position()
|
||||||
return (opts and opts.win and opts.win.position) and opts.win.position or calculate_config_window_position()
|
return (opts and opts.win and opts.win.position) and opts.win.position or calculate_config_window_position()
|
||||||
end
|
end
|
||||||
@@ -2651,7 +2599,7 @@ function Sidebar:render(opts)
|
|||||||
|
|
||||||
self.result_container:map("n", Config.mappings.sidebar.close, function() self:shutdown() end)
|
self.result_container:map("n", Config.mappings.sidebar.close, function() self:shutdown() end)
|
||||||
|
|
||||||
self:create_input_container(opts)
|
self:create_input_container()
|
||||||
|
|
||||||
self:create_selected_files_container()
|
self:create_selected_files_container()
|
||||||
|
|
||||||
@@ -2787,37 +2735,6 @@ function Sidebar:create_selected_files_container()
|
|||||||
|
|
||||||
local function remove_file(line_number) self.file_selector:remove_selected_filepaths_with_index(line_number) end
|
local function remove_file(line_number) self.file_selector:remove_selected_filepaths_with_index(line_number) end
|
||||||
|
|
||||||
-- Function to show hint
|
|
||||||
local function show_hint()
|
|
||||||
local cursor_pos = api.nvim_win_get_cursor(self.selected_files_container.winid)
|
|
||||||
local line_number = cursor_pos[1]
|
|
||||||
local col_number = cursor_pos[2]
|
|
||||||
|
|
||||||
local selected_filepaths_ = self.file_selector:get_selected_filepaths()
|
|
||||||
local hint
|
|
||||||
if #selected_filepaths_ == 0 then
|
|
||||||
hint = string.format(" [%s: add] ", Config.mappings.sidebar.add_file)
|
|
||||||
else
|
|
||||||
hint =
|
|
||||||
string.format(" [%s: delete, %s: add] ", Config.mappings.sidebar.remove_file, Config.mappings.sidebar.add_file)
|
|
||||||
end
|
|
||||||
|
|
||||||
api.nvim_buf_clear_namespace(self.selected_files_container.bufnr, SELECTED_FILES_HINT_NAMESPACE, 0, -1)
|
|
||||||
|
|
||||||
api.nvim_buf_set_extmark(
|
|
||||||
self.selected_files_container.bufnr,
|
|
||||||
SELECTED_FILES_HINT_NAMESPACE,
|
|
||||||
line_number - 1,
|
|
||||||
col_number,
|
|
||||||
{
|
|
||||||
virt_text = { { hint, "AvanteInlineHint" } },
|
|
||||||
virt_text_pos = "right_align",
|
|
||||||
hl_group = "AvanteInlineHint",
|
|
||||||
priority = PRIORITY,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Set up keybinding to remove files
|
-- Set up keybinding to remove files
|
||||||
self.selected_files_container:map("n", Config.mappings.sidebar.remove_file, function()
|
self.selected_files_container:map("n", Config.mappings.sidebar.remove_file, function()
|
||||||
local line_number = api.nvim_win_get_cursor(self.selected_files_container.winid)[1]
|
local line_number = api.nvim_win_get_cursor(self.selected_files_container.winid)[1]
|
||||||
@@ -2832,14 +2749,10 @@ function Sidebar:create_selected_files_container()
|
|||||||
)
|
)
|
||||||
|
|
||||||
-- Set up autocmd to show hint on cursor move
|
-- Set up autocmd to show hint on cursor move
|
||||||
self.selected_files_container:on({ event.CursorMoved }, show_hint, {})
|
self.selected_files_container:on({ event.CursorMoved }, function() self:show_selected_files_hint() end, {})
|
||||||
|
|
||||||
-- Clear hint when leaving the window
|
-- Clear hint when leaving the window
|
||||||
self.selected_files_container:on(
|
self.selected_files_container:on(event.BufLeave, function() self:close_selected_files_hint() end, {})
|
||||||
event.BufLeave,
|
|
||||||
function() api.nvim_buf_clear_namespace(self.selected_files_container.bufnr, SELECTED_FILES_HINT_NAMESPACE, 0, -1) end,
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
render()
|
render()
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user