2024-08-15 17:45:46 +08:00
local api = vim.api
local fn = vim.fn
2024-09-03 05:12:07 -04:00
local Split = require ( " nui.split " )
2024-08-21 21:28:17 +08:00
local event = require ( " nui.utils.autocmd " ) . event
2024-08-17 15:14:30 +08:00
2024-09-03 21:47:01 -04:00
local Provider = require ( " avante.providers " )
2024-09-03 05:12:07 -04:00
local Path = require ( " avante.path " )
local Config = require ( " avante.config " )
local Diff = require ( " avante.diff " )
local Llm = require ( " avante.llm " )
local Utils = require ( " avante.utils " )
local Highlights = require ( " avante.highlights " )
2024-09-26 03:45:49 +08:00
local RepoMap = require ( " avante.repo_map " )
2024-08-15 17:45:46 +08:00
2024-08-21 21:28:17 +08:00
local RESULT_BUF_NAME = " AVANTE_RESULT "
2024-08-17 19:53:45 +08:00
local VIEW_BUFFER_UPDATED_PATTERN = " AvanteViewBufferUpdated "
2024-09-03 05:12:07 -04:00
local CODEBLOCK_KEYBINDING_NAMESPACE = api.nvim_create_namespace ( " AVANTE_CODEBLOCK_KEYBINDING " )
2024-08-15 20:07:44 +08:00
local PRIORITY = vim.highlight . priorities.user
2024-08-15 19:04:15 +08:00
2024-08-17 15:14:30 +08:00
---@class avante.Sidebar
local Sidebar = { }
2024-08-21 21:28:17 +08:00
---@class avante.CodeState
---@field winid integer
---@field bufnr integer
2024-08-17 22:29:05 +08:00
---@field selection avante.SelectionResult | nil
2024-08-17 15:14:30 +08:00
---@class avante.Sidebar
---@field id integer
---@field augroup integer
2024-08-21 21:28:17 +08:00
---@field code avante.CodeState
2024-08-27 14:28:10 +08:00
---@field winids table<string, integer> this table stores the winids of the sidebar components (result, selected_code, input), even though they are destroyed.
---@field result NuiSplit | nil
---@field selected_code NuiSplit | nil
---@field input NuiSplit | nil
2024-08-17 15:14:30 +08:00
2024-08-22 14:46:08 +08:00
---@param id integer the tabpage id retrieved from api.nvim_get_current_tabpage()
2024-08-17 15:14:30 +08:00
function Sidebar : new ( id )
return setmetatable ( {
id = id ,
2024-08-21 21:28:17 +08:00
code = { bufnr = 0 , winid = 0 , selection = nil } ,
2024-08-22 03:38:22 +08:00
winids = {
result = 0 ,
selected_code = 0 ,
input = 0 ,
} ,
2024-08-21 21:28:17 +08:00
result = nil ,
selected_code = nil ,
input = nil ,
2024-08-17 22:29:05 +08:00
} , { __index = self } )
2024-08-17 15:14:30 +08:00
end
function Sidebar : delete_autocmds ( )
2024-09-03 04:19:54 -04:00
if self.augroup then api.nvim_del_augroup_by_id ( self.augroup ) end
2024-08-17 15:14:30 +08:00
self.augroup = nil
end
function Sidebar : reset ( )
self : delete_autocmds ( )
2024-08-21 21:28:17 +08:00
self.code = { bufnr = 0 , winid = 0 , selection = nil }
2024-08-27 14:28:10 +08:00
self.winids = { result = 0 , selected_code = 0 , input = 0 }
2024-08-21 21:28:17 +08:00
self.result = nil
self.selected_code = nil
self.input = nil
2024-08-17 15:14:30 +08:00
end
2024-10-14 20:22:34 -07:00
---@class SidebarOpenOptions: AskOptions
---@field selection? avante.SelectionResult
---@param opts SidebarOpenOptions
2024-09-04 09:15:32 -04:00
function Sidebar : open ( opts )
2024-10-14 20:22:34 -07:00
opts = opts or { }
2024-08-17 22:29:05 +08:00
local in_visual_mode = Utils.in_visual_mode ( ) and self : in_code_win ( )
2024-08-21 21:28:17 +08:00
if not self : is_open ( ) then
2024-08-23 02:23:45 -04:00
self : reset ( )
2024-08-23 18:17:58 +08:00
self : initialize ( )
2024-10-14 20:22:34 -07:00
if opts.selection then self.code . selection = opts.selection end
2024-09-04 09:15:32 -04:00
self : render ( opts )
2024-08-17 15:14:30 +08:00
else
2024-10-14 20:22:34 -07:00
if in_visual_mode or opts.selection then
2024-08-17 22:29:05 +08:00
self : close ( )
2024-08-23 02:23:45 -04:00
self : reset ( )
2024-08-23 18:17:58 +08:00
self : initialize ( )
2024-10-14 20:22:34 -07:00
if opts.selection then self.code . selection = opts.selection end
2024-09-04 09:15:32 -04:00
self : render ( opts )
2024-08-17 22:29:05 +08:00
return self
end
2024-08-17 15:14:30 +08:00
self : focus ( )
end
2024-08-23 01:06:37 -04:00
2024-09-03 21:47:01 -04:00
if not vim.g . avante_login or vim.g . avante_login == false then
api.nvim_exec_autocmds ( " User " , { pattern = Provider.env . REQUEST_LOGIN_PATTERN } )
vim.g . avante_login = true
end
2024-09-03 05:12:07 -04:00
vim.cmd ( " wincmd = " )
2024-08-17 15:14:30 +08:00
return self
end
2024-10-14 20:22:34 -07:00
---@class SidebarCloseOptions
---@field goto_code_win? boolean
---@param opts? SidebarCloseOptions
function Sidebar : close ( opts )
opts = vim.tbl_extend ( " force " , { goto_code_win = true } , opts or { } )
2024-08-23 18:08:58 +08:00
self : delete_autocmds ( )
2024-08-21 21:28:17 +08:00
for _ , comp in pairs ( self ) do
2024-09-03 04:19:54 -04:00
if comp and type ( comp ) == " table " and comp.unmount then comp : unmount ( ) end
2024-08-18 17:54:57 +08:00
end
2024-10-14 20:22:34 -07:00
if opts.goto_code_win and self.code and self.code . winid and api.nvim_win_is_valid ( self.code . winid ) then
fn.win_gotoid ( self.code . winid )
end
2024-08-24 13:13:50 -04:00
2024-09-03 05:12:07 -04:00
vim.cmd ( " wincmd = " )
2024-08-17 15:14:30 +08:00
end
---@return boolean
function Sidebar : focus ( )
2024-08-21 21:28:17 +08:00
if self : is_open ( ) then
fn.win_gotoid ( self.result . winid )
2024-08-17 15:14:30 +08:00
return true
end
return false
end
2024-08-21 21:28:17 +08:00
function Sidebar : is_open ( )
return self.result
and self.result . bufnr
and api.nvim_buf_is_valid ( self.result . bufnr )
and self.result . winid
and api.nvim_win_is_valid ( self.result . winid )
end
2024-09-03 04:19:54 -04:00
function Sidebar : in_code_win ( ) return self.code . winid == api.nvim_get_current_win ( ) end
2024-08-17 22:29:05 +08:00
2024-09-05 02:43:31 -04:00
---@param opts AskOptions
2024-09-04 09:15:32 -04:00
function Sidebar : toggle ( opts )
2024-08-17 22:29:05 +08:00
local in_visual_mode = Utils.in_visual_mode ( ) and self : in_code_win ( )
2024-08-21 21:28:17 +08:00
if self : is_open ( ) and not in_visual_mode then
2024-08-17 15:14:30 +08:00
self : close ( )
return false
else
2024-09-04 09:15:32 -04:00
self : open ( opts )
2024-08-17 15:14:30 +08:00
return true
end
end
2024-08-30 18:53:49 +08:00
---@class AvanteReplacementResult
---@field content string
---@field is_searching boolean
---@field is_replacing boolean
---@field last_search_tag_start_line integer
---@field last_replace_tag_start_line integer
---@param original_content string
---@param result_content string
---@param code_lang string
---@return AvanteReplacementResult
local function transform_result_content ( original_content , result_content , code_lang )
2024-10-10 05:05:29 +08:00
local transformed_lines = { }
2024-08-30 18:53:49 +08:00
local original_lines = vim.split ( original_content , " \n " )
local result_lines = vim.split ( result_content , " \n " )
local is_searching = false
local is_replacing = false
local last_search_tag_start_line = 0
local last_replace_tag_start_line = 0
2024-10-10 05:05:29 +08:00
local search_start = 0
2024-08-30 18:53:49 +08:00
local i = 1
while i <= # result_lines do
2024-10-10 05:05:29 +08:00
local line_content = result_lines [ i ]
2024-11-18 02:55:44 +08:00
if line_content : match ( " <FILEPATH>.+</FILEPATH> " ) then
local filepath = line_content : match ( " <FILEPATH>(.+)</FILEPATH> " )
if filepath then
table.insert ( transformed_lines , string.format ( " Filepath: %s " , filepath ) )
goto continue
end
end
2024-10-10 05:05:29 +08:00
if line_content == " <SEARCH> " then
2024-08-30 18:53:49 +08:00
is_searching = true
2024-10-16 16:23:02 +08:00
local next_line = result_lines [ i + 1 ]
if next_line and next_line : match ( " ^%s*```%w+$ " ) then i = i + 1 end
2024-10-10 05:05:29 +08:00
search_start = i + 1
2024-08-30 18:53:49 +08:00
last_search_tag_start_line = i
2024-10-10 05:05:29 +08:00
elseif line_content == " </SEARCH> " then
is_searching = false
2024-10-16 16:23:02 +08:00
2024-10-10 05:05:29 +08:00
local search_end = i
2024-08-30 18:53:49 +08:00
2024-10-16 16:23:02 +08:00
local prev_line = result_lines [ i - 1 ]
if prev_line and prev_line : match ( " ^%s*```$ " ) then search_end = i - 1 end
2024-10-10 05:05:29 +08:00
local start_line = 0
local end_line = 0
2024-08-30 18:53:49 +08:00
for j = 1 , # original_lines - ( search_end - search_start ) + 1 do
local match = true
for k = 0 , search_end - search_start - 1 do
if
Utils.remove_indentation ( original_lines [ j + k ] ) ~= Utils.remove_indentation ( result_lines [ search_start + k ] )
then
match = false
break
end
end
if match then
start_line = j
end_line = j + ( search_end - search_start ) - 1
break
end
end
2024-10-10 05:05:29 +08:00
local search_start_tag_idx_in_transformed_lines = 0
for j = 1 , # transformed_lines do
if transformed_lines [ j ] == " <SEARCH> " then
search_start_tag_idx_in_transformed_lines = j
break
2024-08-30 18:53:49 +08:00
end
end
2024-10-10 05:05:29 +08:00
if search_start_tag_idx_in_transformed_lines > 0 then
transformed_lines = vim.list_slice ( transformed_lines , 1 , search_start_tag_idx_in_transformed_lines - 1 )
end
vim.list_extend ( transformed_lines , {
string.format ( " Replace lines: %d-%d " , start_line , end_line ) ,
string.format ( " ```%s " , code_lang ) ,
} )
goto continue
elseif line_content == " <REPLACE> " then
is_replacing = true
2024-10-16 16:23:02 +08:00
local next_line = result_lines [ i + 1 ]
if next_line and next_line : match ( " ^%s*```%w+$ " ) then i = i + 1 end
2024-10-10 05:05:29 +08:00
last_replace_tag_start_line = i
goto continue
elseif line_content == " </REPLACE> " then
2024-08-30 18:53:49 +08:00
is_replacing = false
2024-10-16 16:23:02 +08:00
local prev_line = result_lines [ i - 1 ]
if not ( prev_line and prev_line : match ( " ^%s*```$ " ) ) then table.insert ( transformed_lines , " ``` " ) end
2024-10-10 05:05:29 +08:00
goto continue
2024-08-27 15:56:38 +08:00
end
2024-10-10 05:05:29 +08:00
table.insert ( transformed_lines , line_content )
:: continue ::
i = i + 1
2024-08-27 15:56:38 +08:00
end
2024-08-30 18:53:49 +08:00
return {
2024-10-10 05:05:29 +08:00
content = table.concat ( transformed_lines , " \n " ) ,
2024-08-30 18:53:49 +08:00
is_searching = is_searching ,
is_replacing = is_replacing ,
last_search_tag_start_line = last_search_tag_start_line ,
last_replace_tag_start_line = last_replace_tag_start_line ,
}
end
2024-08-27 15:56:38 +08:00
2024-10-10 05:05:29 +08:00
local spinner_chars = {
" ⡀ " ,
" ⠄ " ,
" ⠂ " ,
" ⠁ " ,
" ⠈ " ,
" ⠐ " ,
" ⠠ " ,
" ⢀ " ,
" ⣀ " ,
" ⢄ " ,
" ⢂ " ,
" ⢁ " ,
" ⢈ " ,
" ⢐ " ,
" ⢠ " ,
" ⣠ " ,
" ⢤ " ,
" ⢢ " ,
" ⢡ " ,
" ⢨ " ,
" ⢰ " ,
" ⣰ " ,
" ⢴ " ,
" ⢲ " ,
" ⢱ " ,
" ⢸ " ,
" ⣸ " ,
" ⢼ " ,
" ⢺ " ,
" ⢹ " ,
" ⣹ " ,
" ⢽ " ,
" ⢻ " ,
" ⣻ " ,
" ⢿ " ,
" ⣿ " ,
" ⣶ " ,
" ⣤ " ,
" ⣀ " ,
}
local spinner_index = 1
local function get_searching_hint ( )
spinner_index = ( spinner_index % # spinner_chars ) + 1
local spinner = spinner_chars [ spinner_index ]
return " \n " .. spinner .. " Searching... "
end
local function get_display_content_suffix ( replacement )
if replacement.is_searching then return get_searching_hint ( ) end
return " "
end
2024-08-27 15:56:38 +08:00
2024-08-30 18:53:49 +08:00
---@param replacement AvanteReplacementResult
---@return string
local function generate_display_content ( replacement )
if replacement.is_searching then
return table.concat (
vim.list_slice ( vim.split ( replacement.content , " \n " ) , 1 , replacement.last_search_tag_start_line - 1 ) ,
" \n "
2024-10-10 05:05:29 +08:00
)
2024-08-30 18:53:49 +08:00
end
return replacement.content
2024-08-27 15:56:38 +08:00
end
2024-08-30 15:01:23 +08:00
---@class AvanteCodeSnippet
---@field range integer[]
---@field content string
---@field lang string
---@field explanation string
---@field start_line_in_response_buf integer
---@field end_line_in_response_buf integer
2024-10-14 20:15:11 +08:00
---@field filepath string
2024-08-30 15:01:23 +08:00
---@param response_content string
2024-10-14 20:15:11 +08:00
---@return table<string, AvanteCodeSnippet[]>
local function extract_code_snippets_map ( response_content )
2024-08-21 21:28:17 +08:00
local snippets = { }
local current_snippet = { }
local in_code_block = false
2024-08-30 15:01:23 +08:00
local lang , start_line , end_line , start_line_in_response_buf
2024-08-21 21:28:17 +08:00
local explanation = " "
2024-10-14 20:15:11 +08:00
local lines = vim.split ( response_content , " \n " )
for idx , line in ipairs ( lines ) do
2024-09-15 16:59:57 +02:00
local _ , start_line_str , end_line_str =
line : match ( " ^%s*(%d*)[%.%)%s]*[Aa]?n?d?%s*[Rr]eplace%s+[Ll]ines:?%s*(%d+)%-(%d+) " )
2024-08-21 21:28:17 +08:00
if start_line_str ~= nil and end_line_str ~= nil then
start_line = tonumber ( start_line_str )
end_line = tonumber ( end_line_str )
2024-09-15 16:59:57 +02:00
else
_ , start_line_str = line : match ( " ^%s*(%d*)[%.%)%s]*[Aa]?n?d?%s*[Rr]eplace%s+[Ll]ine:?%s*(%d+) " )
if start_line_str ~= nil then
start_line = tonumber ( start_line_str )
end_line = tonumber ( start_line_str )
2024-09-19 10:17:35 +08:00
else
start_line_str = line : match ( " [Aa]fter%s+[Ll]ine:?%s*(%d+) " )
if start_line_str ~= nil then
start_line = tonumber ( start_line_str ) + 1
end_line = tonumber ( start_line_str ) + 1
end
2024-09-15 16:59:57 +02:00
end
2024-08-21 21:28:17 +08:00
end
2024-09-15 16:59:57 +02:00
if line : match ( " ^%s*``` " ) then
2024-08-21 21:28:17 +08:00
if in_code_block then
if start_line ~= nil and end_line ~= nil then
2024-11-18 02:55:44 +08:00
local filepath = lines [ start_line_in_response_buf - 2 ]
if filepath : match ( " ^[Ff]ilepath: " ) then filepath = filepath : match ( " ^[Ff]ilepath:%s*(.+) " ) end
2024-08-27 15:56:38 +08:00
local snippet = {
2024-08-21 21:28:17 +08:00
range = { start_line , end_line } ,
content = table.concat ( current_snippet , " \n " ) ,
lang = lang ,
explanation = explanation ,
2024-08-30 15:01:23 +08:00
start_line_in_response_buf = start_line_in_response_buf ,
end_line_in_response_buf = idx ,
2024-11-18 02:55:44 +08:00
filepath = filepath ,
2024-08-27 15:56:38 +08:00
}
table.insert ( snippets , snippet )
2024-08-21 21:28:17 +08:00
end
current_snippet = { }
start_line , end_line = nil , nil
explanation = " "
in_code_block = false
else
2024-09-15 16:59:57 +02:00
lang = line : match ( " ^%s*```(%w+) " )
2024-09-03 04:19:54 -04:00
if not lang or lang == " " then lang = " text " end
2024-08-21 21:28:17 +08:00
in_code_block = true
2024-08-30 15:01:23 +08:00
start_line_in_response_buf = idx
2024-08-21 21:28:17 +08:00
end
elseif in_code_block then
table.insert ( current_snippet , line )
else
explanation = explanation .. line .. " \n "
end
end
2024-10-14 20:15:11 +08:00
local snippets_map = { }
for _ , snippet in ipairs ( snippets ) do
snippets_map [ snippet.filepath ] = snippets_map [ snippet.filepath ] or { }
table.insert ( snippets_map [ snippet.filepath ] , snippet )
end
return snippets_map
2024-08-21 21:28:17 +08:00
end
2024-10-14 20:15:11 +08:00
---@param snippets_map table<string, AvanteCodeSnippet[]>
---@return table<string, AvanteCodeSnippet[]>
local function ensure_snippets_no_overlap ( snippets_map )
local new_snippets_map = { }
2024-08-30 20:43:26 +08:00
2024-10-14 20:15:11 +08:00
for filepath , snippets in pairs ( snippets_map ) do
table.sort ( snippets , function ( a , b ) return a.range [ 1 ] < b.range [ 1 ] end )
2024-08-30 20:43:26 +08:00
2024-10-14 20:15:11 +08:00
local original_content = " "
if Utils.file . exists ( filepath ) then original_content = Utils.file . read_content ( filepath ) or " " end
local original_lines = vim.split ( original_content , " \n " )
local new_snippets = { }
local last_end_line = 0
for _ , snippet in ipairs ( snippets ) do
if snippet.range [ 1 ] > last_end_line then
table.insert ( new_snippets , snippet )
2024-08-30 20:43:26 +08:00
last_end_line = snippet.range [ 2 ]
else
2024-10-14 20:15:11 +08:00
local snippet_lines = vim.split ( snippet.content , " \n " )
-- Trim the overlapping part
local new_start_line = nil
for i = snippet.range [ 1 ] , math.min ( snippet.range [ 2 ] , last_end_line ) do
if
Utils.remove_indentation ( original_lines [ i ] )
== Utils.remove_indentation ( snippet_lines [ i - snippet.range [ 1 ] + 1 ] )
then
new_start_line = i + 1
else
break
end
end
if new_start_line ~= nil then
snippet.content = table.concat ( vim.list_slice ( snippet_lines , new_start_line - snippet.range [ 1 ] + 1 ) , " \n " )
snippet.range [ 1 ] = new_start_line
table.insert ( new_snippets , snippet )
last_end_line = snippet.range [ 2 ]
else
Utils.error ( " Failed to ensure snippets no overlap " , { once = true , title = " Avante " } )
end
2024-08-30 20:43:26 +08:00
end
end
2024-10-14 20:15:11 +08:00
new_snippets_map [ filepath ] = new_snippets
2024-08-30 20:43:26 +08:00
end
2024-10-14 20:15:11 +08:00
return new_snippets_map
2024-08-30 20:43:26 +08:00
end
2024-09-01 02:45:32 +08:00
local function insert_conflict_contents ( bufnr , snippets )
2024-08-21 21:28:17 +08:00
-- sort snippets by start_line
2024-09-03 04:19:54 -04:00
table.sort ( snippets , function ( a , b ) return a.range [ 1 ] < b.range [ 1 ] end )
2024-08-21 21:28:17 +08:00
2024-09-01 02:45:32 +08:00
local content = table.concat ( Utils.get_buf_lines ( 0 , - 1 , bufnr ) , " \n " )
2024-08-21 21:28:17 +08:00
local lines = vim.split ( content , " \n " )
2024-09-01 02:45:32 +08:00
local offset = 0
2024-08-21 21:28:17 +08:00
for _ , snippet in ipairs ( snippets ) do
local start_line , end_line = unpack ( snippet.range )
2024-08-30 15:40:40 +08:00
local need_prepend_indentation = false
2024-10-11 21:07:55 +08:00
local start_line_indentation = " "
2024-08-30 15:40:40 +08:00
local original_start_line_indentation = Utils.get_indentation ( lines [ start_line ] or " " )
2024-09-01 02:45:32 +08:00
local result = { }
2024-08-21 21:28:17 +08:00
table.insert ( result , " <<<<<<< HEAD " )
2024-09-23 18:52:26 +08:00
for i = start_line , end_line do
table.insert ( result , lines [ i ] )
2024-08-21 21:28:17 +08:00
end
table.insert ( result , " ======= " )
2024-09-01 02:45:32 +08:00
local snippet_lines = vim.split ( snippet.content , " \n " )
for idx , line in ipairs ( snippet_lines ) do
2024-08-30 15:40:40 +08:00
if idx == 1 then
2024-10-11 21:07:55 +08:00
start_line_indentation = Utils.get_indentation ( line )
need_prepend_indentation = start_line_indentation ~= original_start_line_indentation
end
if need_prepend_indentation then
if line : sub ( 1 , # start_line_indentation ) == start_line_indentation then
line = line : sub ( # start_line_indentation + 1 )
end
line = original_start_line_indentation .. line
2024-08-30 15:40:40 +08:00
end
2024-08-21 21:28:17 +08:00
table.insert ( result , line )
end
table.insert ( result , " >>>>>>> Snippet " )
2024-09-01 02:45:32 +08:00
api.nvim_buf_set_lines ( bufnr , offset + start_line - 1 , offset + end_line , false , result )
offset = offset + # snippet_lines + 3
2024-08-21 21:28:17 +08:00
end
end
---@param codeblocks table<integer, any>
local function is_cursor_in_codeblock ( codeblocks )
local cursor_line , _ = Utils.get_cursor_pos ( )
2024-10-11 21:07:55 +08:00
cursor_line = cursor_line - 1 -- transform to 0-indexed line number
2024-08-17 15:14:30 +08:00
2024-08-21 21:28:17 +08:00
for _ , block in ipairs ( codeblocks ) do
2024-09-03 04:19:54 -04:00
if cursor_line >= block.start_line and cursor_line <= block.end_line then return block end
2024-08-21 21:28:17 +08:00
end
return nil
end
---@class AvanteCodeblock
---@field start_line integer
---@field end_line integer
---@field lang string
---@param buf integer
---@return AvanteCodeblock[]
local function parse_codeblocks ( buf )
local codeblocks = { }
local in_codeblock = false
local start_line = nil
local lang = nil
local lines = Utils.get_buf_lines ( 0 , - 1 , buf )
for i , line in ipairs ( lines ) do
2024-09-15 16:59:57 +02:00
if line : match ( " ^%s*``` " ) then
2024-08-21 21:28:17 +08:00
-- parse language
2024-09-15 16:59:57 +02:00
local lang_ = line : match ( " ^%s*```(%w+) " )
2024-08-21 21:28:17 +08:00
if in_codeblock and not lang_ then
table.insert ( codeblocks , { start_line = start_line , end_line = i - 1 , lang = lang } )
in_codeblock = false
elseif lang_ then
lang = lang_
start_line = i - 1
in_codeblock = true
end
end
end
return codeblocks
end
2024-08-30 15:01:23 +08:00
---@param current_cursor boolean
function Sidebar : apply ( current_cursor )
local response , response_start_line = self : get_content_between_separators ( )
2024-10-14 20:15:11 +08:00
local all_snippets_map = extract_code_snippets_map ( response )
all_snippets_map = ensure_snippets_no_overlap ( all_snippets_map )
local selected_snippets_map = { }
2024-08-30 15:01:23 +08:00
if current_cursor then
if self.result and self.result . winid then
local cursor_line = Utils.get_cursor_pos ( self.result . winid )
2024-10-14 20:15:11 +08:00
for filepath , snippets in pairs ( all_snippets_map ) do
for _ , snippet in ipairs ( snippets ) do
if
cursor_line >= snippet.start_line_in_response_buf + response_start_line - 1
and cursor_line <= snippet.end_line_in_response_buf + response_start_line - 1
then
selected_snippets_map [ filepath ] = { snippet }
break
end
2024-08-30 15:01:23 +08:00
end
end
end
2024-09-01 17:26:27 +08:00
else
2024-10-14 20:15:11 +08:00
selected_snippets_map = all_snippets_map
2024-08-30 15:01:23 +08:00
end
2024-08-21 21:28:17 +08:00
vim.defer_fn ( function ( )
2024-11-18 02:55:44 +08:00
api.nvim_set_current_win ( self.code . winid )
2024-10-14 20:15:11 +08:00
for filepath , snippets in pairs ( selected_snippets_map ) do
2024-10-16 23:57:40 +08:00
local bufnr = Utils.get_or_create_buffer_with_filepath ( filepath )
2024-10-14 20:15:11 +08:00
insert_conflict_contents ( bufnr , snippets )
local winid = Utils.get_winid ( bufnr )
if not winid then goto continue end
api.nvim_set_current_win ( winid )
api.nvim_feedkeys ( api.nvim_replace_termcodes ( " <Esc> " , true , false , true ) , " n " , true )
Diff.add_visited_buffer ( bufnr )
Diff.process ( bufnr )
api.nvim_win_set_cursor ( winid , { 1 , 0 } )
vim.defer_fn ( function ( )
2024-11-02 03:33:08 -07:00
Diff.find_next ( Config.windows . ask.focus_on_apply )
2024-10-14 20:15:11 +08:00
vim.cmd ( " normal! zz " )
end , 100 )
:: continue ::
end
2024-08-21 21:28:17 +08:00
end , 10 )
end
local buf_options = {
modifiable = false ,
swapfile = false ,
buftype = " nofile " ,
}
local base_win_options = {
2024-11-15 02:33:27 +10:00
winfixbuf = true ,
2024-08-21 21:28:17 +08:00
spell = false ,
signcolumn = " no " ,
foldcolumn = " 0 " ,
number = false ,
relativenumber = false ,
winfixwidth = true ,
list = false ,
winhl = " " ,
linebreak = true ,
breakindent = true ,
wrap = false ,
cursorline = false ,
2024-08-21 22:15:31 +08:00
fillchars = " eob: " ,
2024-08-21 21:28:17 +08:00
winhighlight = " CursorLine:Normal,CursorColumn:Normal " ,
2024-08-21 22:31:56 +08:00
winbar = " " ,
statusline = " " ,
2024-08-21 21:28:17 +08:00
}
2024-08-22 03:38:22 +08:00
function Sidebar : render_header ( winid , bufnr , header_text , hl , reverse_hl )
2024-09-03 04:19:54 -04:00
if not bufnr or not api.nvim_buf_is_valid ( bufnr ) then return end
2024-08-21 23:22:34 +08:00
2024-10-20 20:42:07 +01:00
if not Config.windows . sidebar_header.enabled then return end
2024-09-03 04:19:54 -04:00
if not Config.windows . sidebar_header.rounded then header_text = " " .. header_text .. " " end
2024-08-21 23:22:34 +08:00
2024-08-27 14:28:10 +08:00
local winbar_text = " %#Normal# "
2024-08-21 11:12:10 -04:00
2024-08-21 23:31:11 +08:00
if Config.windows . sidebar_header.align == " center " then
2024-08-27 14:28:10 +08:00
winbar_text = winbar_text .. " %= "
2024-08-21 23:31:11 +08:00
elseif Config.windows . sidebar_header.align == " right " then
2024-08-27 14:28:10 +08:00
winbar_text = winbar_text .. " %= "
2024-08-21 11:12:10 -04:00
end
2024-08-27 14:28:10 +08:00
if Config.windows . sidebar_header.rounded then
winbar_text = winbar_text .. " %# " .. reverse_hl .. " # " .. " " .. " %# " .. hl .. " # "
else
winbar_text = winbar_text .. " %# " .. hl .. " # "
end
winbar_text = winbar_text .. header_text
2024-09-03 04:19:54 -04:00
if Config.windows . sidebar_header.rounded then winbar_text = winbar_text .. " %# " .. reverse_hl .. " # " end
2024-08-27 14:28:10 +08:00
winbar_text = winbar_text .. " %#Normal# "
2024-09-03 04:19:54 -04:00
if Config.windows . sidebar_header.align == " center " then winbar_text = winbar_text .. " %= " end
2024-08-27 14:28:10 +08:00
api.nvim_set_option_value ( " winbar " , winbar_text , { win = winid } )
2024-08-21 21:28:17 +08:00
end
2024-08-27 14:28:10 +08:00
function Sidebar : render_result ( )
2024-09-03 04:19:54 -04:00
if not self.result or not self.result . bufnr or not api.nvim_buf_is_valid ( self.result . bufnr ) then return end
2024-08-21 11:12:10 -04:00
local header_text = " Avante "
2024-08-27 14:28:10 +08:00
self : render_header ( self.result . winid , self.result . bufnr , header_text , Highlights.TITLE , Highlights.REVERSED_TITLE )
2024-08-21 21:28:17 +08:00
end
2024-09-05 02:43:31 -04:00
---@param ask? boolean
function Sidebar : render_input ( ask )
if ask == nil then ask = true end
2024-09-03 04:19:54 -04:00
if not self.input or not self.input . bufnr or not api.nvim_buf_is_valid ( self.input . bufnr ) then return end
2024-08-21 21:28:17 +08:00
local filetype = api.nvim_get_option_value ( " filetype " , { buf = self.code . bufnr } )
2024-08-24 22:10:05 -04:00
---@type string
local icon
2024-08-25 14:26:42 +08:00
---@diagnostic disable-next-line: undefined-field
2024-08-24 22:10:05 -04:00
if _G.MiniIcons ~= nil then
2024-08-25 14:26:42 +08:00
---@diagnostic disable-next-line: undefined-global
2024-09-01 17:04:33 +08:00
icon , _ , _ = MiniIcons.get ( " filetype " , filetype ) -- luacheck: ignore
2024-08-24 22:10:05 -04:00
else
2024-08-28 13:49:16 -03:00
local ok , devicons = pcall ( require , " nvim-web-devicons " )
if ok then
icon = devicons.get_icon_by_filetype ( filetype , { } )
else
icon = " "
end
2024-08-24 22:10:05 -04:00
end
2024-08-21 21:28:17 +08:00
local code_file_fullpath = api.nvim_buf_get_name ( self.code . bufnr )
local code_filename = fn.fnamemodify ( code_file_fullpath , " :t " )
2024-09-14 20:42:55 +02:00
local header_text = string.format (
" %s %s %s ( " .. Config.mappings . sidebar.switch_windows .. " : switch focus) " ,
ask and " Ask " or " Chat with " ,
icon ,
code_filename
)
2024-08-21 21:28:17 +08:00
if self.code . selection ~= nil then
header_text = string.format (
2024-09-05 02:43:31 -04:00
" %s %s %s(%d:%d) (<Tab>: switch focus) " ,
ask and " Ask " or " Chat with " ,
2024-08-21 21:28:17 +08:00
icon ,
code_filename ,
self.code . selection.range . start.line ,
self.code . selection.range . finish.line
)
end
2024-08-22 03:38:22 +08:00
self : render_header (
2024-08-27 14:28:10 +08:00
self.input . winid ,
self.input . bufnr ,
2024-08-21 21:28:17 +08:00
header_text ,
2024-08-24 20:15:45 -04:00
Highlights.THIRD_TITLE ,
Highlights.REVERSED_THIRD_TITLE
2024-08-21 21:28:17 +08:00
)
end
2024-08-27 14:28:10 +08:00
function Sidebar : render_selected_code ( )
if not self.selected_code or not self.selected_code . bufnr or not api.nvim_buf_is_valid ( self.selected_code . bufnr ) then
2024-08-21 21:28:17 +08:00
return
end
local selected_code_lines_count = 0
2024-08-31 23:35:35 +08:00
local selected_code_max_lines_count = 12
2024-08-21 21:28:17 +08:00
if self.code . selection ~= nil then
local selected_code_lines = vim.split ( self.code . selection.content , " \n " )
selected_code_lines_count = # selected_code_lines
end
2024-08-21 23:38:27 +08:00
local header_text = " Selected Code "
2024-08-21 21:28:17 +08:00
.. (
selected_code_lines_count > selected_code_max_lines_count
2024-08-21 23:38:27 +08:00
and " (Show only the first " .. tostring ( selected_code_max_lines_count ) .. " lines) "
or " "
2024-08-21 21:28:17 +08:00
)
2024-08-22 03:38:22 +08:00
self : render_header (
2024-08-27 14:28:10 +08:00
self.selected_code . winid ,
self.selected_code . bufnr ,
2024-08-21 21:28:17 +08:00
header_text ,
Highlights.SUBTITLE ,
Highlights.REVERSED_SUBTITLE
)
end
2024-08-17 15:14:30 +08:00
2024-09-05 02:43:31 -04:00
---@param opts AskOptions
function Sidebar : on_mount ( opts )
2024-08-21 21:28:17 +08:00
self : refresh_winids ( )
2024-08-22 01:48:40 -04:00
api.nvim_set_option_value ( " wrap " , Config.windows . wrap , { win = self.result . winid } )
2024-08-21 21:28:17 +08:00
local current_apply_extmark_id = nil
local function show_apply_button ( block )
if current_apply_extmark_id then
api.nvim_buf_del_extmark ( self.result . bufnr , CODEBLOCK_KEYBINDING_NAMESPACE , current_apply_extmark_id )
end
current_apply_extmark_id =
api.nvim_buf_set_extmark ( self.result . bufnr , CODEBLOCK_KEYBINDING_NAMESPACE , block.start_line , - 1 , {
2024-10-12 03:41:08 -07:00
virt_text = {
{
string.format (
" [<%s>: apply this, <%s>: apply all] " ,
Config.mappings . sidebar.apply_cursor ,
Config.mappings . sidebar.apply_all
) ,
" AvanteInlineHint " ,
} ,
} ,
2024-08-21 21:28:17 +08:00
virt_text_pos = " right_align " ,
2024-09-21 02:49:07 -04:00
hl_group = " AvanteInlineHint " ,
2024-08-21 21:28:17 +08:00
priority = PRIORITY ,
} )
end
local function bind_apply_key ( )
2024-09-03 04:19:54 -04:00
vim.keymap . set (
" n " ,
2024-10-12 03:41:08 -07:00
Config.mappings . sidebar.apply_cursor ,
2024-09-03 04:19:54 -04:00
function ( ) self : apply ( true ) end ,
{ buffer = self.result . bufnr , noremap = true , silent = true }
)
2024-08-21 21:28:17 +08:00
end
2024-10-12 03:41:08 -07:00
local function unbind_apply_key ( )
pcall ( vim.keymap . del , " n " , Config.mappings . sidebar.apply_cursor , { buffer = self.result . bufnr } )
end
2024-08-21 21:28:17 +08:00
---@type AvanteCodeblock[]
local codeblocks = { }
2024-08-23 02:01:14 -04:00
---@param direction "next" | "prev"
local function jump_to_codeblock ( direction )
local cursor_line = api.nvim_win_get_cursor ( self.result . winid ) [ 1 ]
---@type AvanteCodeblock
local target_block
if direction == " next " then
for _ , block in ipairs ( codeblocks ) do
if block.start_line > cursor_line then
target_block = block
break
end
end
2024-09-03 04:19:54 -04:00
if not target_block and # codeblocks > 0 then target_block = codeblocks [ 1 ] end
2024-08-23 02:01:14 -04:00
elseif direction == " prev " then
for i = # codeblocks , 1 , - 1 do
if codeblocks [ i ] . end_line < cursor_line then
target_block = codeblocks [ i ]
break
end
end
2024-09-03 04:19:54 -04:00
if not target_block and # codeblocks > 0 then target_block = codeblocks [ # codeblocks ] end
2024-08-23 02:01:14 -04:00
end
if target_block then
api.nvim_win_set_cursor ( self.result . winid , { target_block.start_line + 1 , 0 } )
2024-09-03 05:12:07 -04:00
vim.cmd ( " normal! zz " )
2024-08-23 02:01:14 -04:00
end
end
2024-10-12 03:41:08 -07:00
local function bind_sidebar_keys ( )
vim.keymap . set (
" n " ,
Config.mappings . sidebar.apply_all ,
function ( ) self : apply ( false ) end ,
{ buffer = self.result . bufnr , noremap = true , silent = true }
)
2024-09-03 04:19:54 -04:00
vim.keymap . set (
" n " ,
Config.mappings . jump.next ,
2024-09-03 05:12:07 -04:00
function ( ) jump_to_codeblock ( " next " ) end ,
2024-09-03 04:19:54 -04:00
{ buffer = self.result . bufnr , noremap = true , silent = true }
)
vim.keymap . set (
" n " ,
Config.mappings . jump.prev ,
2024-09-03 05:12:07 -04:00
function ( ) jump_to_codeblock ( " prev " ) end ,
2024-09-03 04:19:54 -04:00
{ buffer = self.result . bufnr , noremap = true , silent = true }
)
2024-08-23 02:01:14 -04:00
end
2024-10-12 03:41:08 -07:00
local function unbind_sidebar_keys ( )
2024-08-24 04:32:44 +08:00
if self.result and self.result . bufnr and api.nvim_buf_is_valid ( self.result . bufnr ) then
2024-10-12 03:41:08 -07:00
pcall ( vim.keymap . del , " n " , Config.mappings . sidebar.apply_all , { buffer = self.result . bufnr } )
2024-08-24 20:15:45 -04:00
pcall ( vim.keymap . del , " n " , Config.mappings . jump.next , { buffer = self.result . bufnr } )
pcall ( vim.keymap . del , " n " , Config.mappings . jump.prev , { buffer = self.result . bufnr } )
2024-08-24 04:32:44 +08:00
end
2024-08-23 02:01:14 -04:00
end
2024-08-21 21:28:17 +08:00
api.nvim_create_autocmd ( { " CursorMoved " , " CursorMovedI " } , {
buffer = self.result . bufnr ,
callback = function ( ev )
local block = is_cursor_in_codeblock ( codeblocks )
if block then
show_apply_button ( block )
bind_apply_key ( )
else
api.nvim_buf_clear_namespace ( ev.buf , CODEBLOCK_KEYBINDING_NAMESPACE , 0 , - 1 )
unbind_apply_key ( )
end
end ,
2024-08-17 15:14:30 +08:00
} )
2024-08-21 21:28:17 +08:00
api.nvim_create_autocmd ( { " BufEnter " , " BufWritePost " } , {
buffer = self.result . bufnr ,
callback = function ( ev )
codeblocks = parse_codeblocks ( ev.buf )
2024-10-12 03:41:08 -07:00
bind_sidebar_keys ( )
2024-08-21 21:28:17 +08:00
end ,
2024-08-17 16:04:40 -04:00
} )
2024-08-21 21:28:17 +08:00
api.nvim_create_autocmd ( " User " , {
pattern = VIEW_BUFFER_UPDATED_PATTERN ,
callback = function ( )
2024-09-03 04:19:54 -04:00
if not self.result or not self.result . bufnr or not api.nvim_buf_is_valid ( self.result . bufnr ) then return end
2024-08-21 21:28:17 +08:00
codeblocks = parse_codeblocks ( self.result . bufnr )
2024-10-12 03:41:08 -07:00
bind_sidebar_keys ( )
2024-08-23 02:01:14 -04:00
end ,
} )
api.nvim_create_autocmd ( " BufLeave " , {
buffer = self.result . bufnr ,
2024-10-12 03:41:08 -07:00
callback = function ( ) unbind_sidebar_keys ( ) end ,
2024-08-21 21:28:17 +08:00
} )
2024-08-27 14:28:10 +08:00
self : render_result ( )
2024-09-05 02:43:31 -04:00
self : render_input ( opts.ask )
2024-08-27 14:28:10 +08:00
self : render_selected_code ( )
2024-08-21 21:28:17 +08:00
local filetype = api.nvim_get_option_value ( " filetype " , { buf = self.code . bufnr } )
if self.selected_code ~= nil then
local selected_code_buf = self.selected_code . bufnr
if selected_code_buf ~= nil then
if self.code . selection ~= nil then
2024-08-21 23:20:30 +08:00
Utils.unlock_buf ( selected_code_buf )
2024-08-21 21:28:17 +08:00
local lines = vim.split ( self.code . selection.content , " \n " )
api.nvim_buf_set_lines ( selected_code_buf , 0 , - 1 , false , lines )
2024-08-21 23:20:30 +08:00
Utils.lock_buf ( selected_code_buf )
2024-08-21 21:28:17 +08:00
end
api.nvim_set_option_value ( " filetype " , filetype , { buf = selected_code_buf } )
end
end
api.nvim_create_autocmd ( " BufEnter " , {
group = self.augroup ,
buffer = self.result . bufnr ,
callback = function ( )
2024-08-25 01:59:22 -04:00
self : focus ( )
2024-08-21 21:28:17 +08:00
if self.input and self.input . winid and api.nvim_win_is_valid ( self.input . winid ) then
api.nvim_set_current_win ( self.input . winid )
2024-11-02 03:32:36 -07:00
if Config.windows . ask.start_insert then vim.cmd ( " startinsert " ) end
2024-08-21 21:28:17 +08:00
end
return true
end ,
} )
2024-08-17 15:14:30 +08:00
2024-08-21 21:28:17 +08:00
api.nvim_create_autocmd ( " WinClosed " , {
group = self.augroup ,
callback = function ( args )
local closed_winid = tonumber ( args.match )
2024-09-03 04:19:54 -04:00
if not self : is_focused_on ( closed_winid ) then return end
2024-08-21 21:28:17 +08:00
self : close ( )
end ,
} )
2024-08-17 15:52:12 +08:00
2024-08-24 00:14:20 +08:00
for _ , comp in pairs ( self ) do
if comp and type ( comp ) == " table " and comp.mount and comp.bufnr and api.nvim_buf_is_valid ( comp.bufnr ) then
Utils.mark_as_sidebar_buffer ( comp.bufnr )
end
end
2024-08-21 21:28:17 +08:00
end
function Sidebar : refresh_winids ( )
self.winids = { }
for key , comp in pairs ( self ) do
if comp and type ( comp ) == " table " and comp.winid and api.nvim_win_is_valid ( comp.winid ) then
self.winids [ key ] = comp.winid
end
end
local winids = { }
2024-09-03 04:19:54 -04:00
if self.winids . result then table.insert ( winids , self.winids . result ) end
if self.winids . selected_code then table.insert ( winids , self.winids . selected_code ) end
if self.winids . input then table.insert ( winids , self.winids . input ) end
2024-08-21 21:28:17 +08:00
2024-08-21 11:12:10 -04:00
local function switch_windows ( )
2024-08-22 03:38:22 +08:00
local current_winid = api.nvim_get_current_win ( )
local current_idx = Utils.tbl_indexof ( winids , current_winid ) or 1
2024-08-21 21:28:17 +08:00
if current_idx == # winids then
current_idx = 1
else
current_idx = current_idx + 1
2024-08-23 18:08:58 +08:00
end
2024-08-26 17:43:38 +08:00
local winid = winids [ current_idx ]
2024-09-03 04:19:54 -04:00
if winid and api.nvim_win_is_valid ( winid ) then pcall ( api.nvim_set_current_win , winid ) end
2024-08-21 21:28:17 +08:00
end
2024-08-21 11:12:10 -04:00
local function reverse_switch_windows ( )
2024-08-22 03:38:22 +08:00
local current_winid = api.nvim_get_current_win ( )
local current_idx = Utils.tbl_indexof ( winids , current_winid ) or 1
2024-08-21 21:28:17 +08:00
if current_idx == 1 then
current_idx = # winids
else
current_idx = current_idx - 1
2024-08-23 18:08:58 +08:00
end
2024-08-26 17:43:38 +08:00
local winid = winids [ current_idx ]
2024-09-03 04:19:54 -04:00
if winid and api.nvim_win_is_valid ( winid ) then api.nvim_set_current_win ( winid ) end
2024-08-21 21:28:17 +08:00
end
for _ , winid in ipairs ( winids ) do
local buf = api.nvim_win_get_buf ( winid )
2024-09-14 20:42:55 +02:00
Utils.safe_keymap_set (
2024-09-03 04:19:54 -04:00
{ " n " , " i " } ,
2024-09-14 20:42:55 +02:00
Config.mappings . sidebar.switch_windows ,
2024-09-03 04:19:54 -04:00
function ( ) switch_windows ( ) end ,
{ buffer = buf , noremap = true , silent = true }
)
2024-09-14 20:42:55 +02:00
Utils.safe_keymap_set (
2024-09-03 04:19:54 -04:00
{ " n " , " i " } ,
2024-09-14 20:42:55 +02:00
Config.mappings . sidebar.reverse_switch_windows ,
2024-09-03 04:19:54 -04:00
function ( ) reverse_switch_windows ( ) end ,
{ buffer = buf , noremap = true , silent = true }
)
2024-08-21 21:28:17 +08:00
end
end
function Sidebar : resize ( )
for _ , comp in pairs ( self ) do
if comp and type ( comp ) == " table " and comp.winid and api.nvim_win_is_valid ( comp.winid ) then
2024-09-02 12:22:48 -04:00
api.nvim_win_set_width ( comp.winid , Config.get_window_width ( ) )
2024-08-21 21:28:17 +08:00
end
end
2024-08-27 14:28:10 +08:00
self : render_result ( )
self : render_input ( )
self : render_selected_code ( )
2024-09-03 05:12:07 -04:00
vim.defer_fn ( function ( ) vim.cmd ( " AvanteRefresh " ) end , 200 )
2024-08-21 21:28:17 +08:00
end
--- Initialize the sidebar instance.
--- @return avante.Sidebar The Sidebar instance.
2024-08-23 18:17:58 +08:00
function Sidebar : initialize ( )
2024-08-21 21:28:17 +08:00
self.code . winid = api.nvim_get_current_win ( )
self.code . bufnr = api.nvim_get_current_buf ( )
self.code . selection = Utils.get_visual_selection_and_range ( )
2024-08-17 10:39:59 -04:00
return self
end
2024-08-21 21:28:17 +08:00
function Sidebar : is_focused_on_result ( )
return self : is_open ( ) and self.result and self.result . winid == api.nvim_get_current_win ( )
end
function Sidebar : is_focused_on ( winid )
for _ , stored_winid in pairs ( self.winids ) do
2024-09-03 04:19:54 -04:00
if stored_winid == winid then return true end
2024-08-21 21:28:17 +08:00
end
return false
2024-08-18 05:36:30 -04:00
end
2024-10-10 05:05:29 +08:00
local function delete_last_n_chars ( bufnr , n )
bufnr = bufnr or api.nvim_get_current_buf ( )
local line_count = api.nvim_buf_line_count ( bufnr )
while n > 0 and line_count > 0 do
local last_line = api.nvim_buf_get_lines ( bufnr , line_count - 1 , line_count , false ) [ 1 ]
local total_chars_in_line = # last_line + 1
if total_chars_in_line > n then
local chars_to_keep = total_chars_in_line - n - 1 - 1
local new_last_line = last_line : sub ( 1 , chars_to_keep )
if new_last_line == " " then
api.nvim_buf_set_lines ( bufnr , line_count - 1 , line_count , false , { } )
line_count = line_count - 1
else
api.nvim_buf_set_lines ( bufnr , line_count - 1 , line_count , false , { new_last_line } )
end
n = 0
else
n = n - total_chars_in_line
api.nvim_buf_set_lines ( bufnr , line_count - 1 , line_count , false , { } )
line_count = line_count - 1
end
end
end
2024-08-17 15:14:30 +08:00
---@param content string concatenated content of the buffer
2024-11-05 21:02:38 +08:00
---@param opts? {focus?: boolean, scroll?: boolean, backspace?: integer, ignore_history?: boolean, callback?: fun(): nil} whether to focus the result view
2024-08-18 05:36:30 -04:00
function Sidebar : update_content ( content , opts )
2024-09-03 04:19:54 -04:00
if not self.result or not self.result . bufnr then return end
2024-08-18 15:03:25 -04:00
opts = vim.tbl_deep_extend ( " force " , { focus = true , scroll = true , stream = false , callback = nil } , opts or { } )
2024-11-05 21:02:38 +08:00
if not opts.ignore_history then
local chat_history = Path.history . load ( self.code . bufnr )
2024-11-18 18:07:33 +08:00
content = self : render_history_content ( chat_history ) .. " --- \n \n " .. content
2024-11-05 21:02:38 +08:00
end
2024-08-18 15:03:25 -04:00
if opts.stream then
2024-08-20 01:47:43 +08:00
local scroll_to_bottom = function ( )
2024-08-21 21:28:17 +08:00
local last_line = api.nvim_buf_line_count ( self.result . bufnr )
2024-08-18 15:03:25 -04:00
2024-08-21 21:28:17 +08:00
local current_lines = Utils.get_buf_lines ( last_line - 1 , last_line , self.result . bufnr )
2024-08-18 15:03:25 -04:00
2024-08-20 01:47:43 +08:00
if # current_lines > 0 then
local last_line_content = current_lines [ 1 ]
local last_col = # last_line_content
2024-09-03 04:19:54 -04:00
xpcall (
function ( ) api.nvim_win_set_cursor ( self.result . winid , { last_line , last_col } ) end ,
function ( err ) return err end
)
2024-08-20 01:47:43 +08:00
end
end
2024-08-18 15:03:25 -04:00
2024-08-20 01:47:43 +08:00
vim.schedule ( function ( )
2024-09-03 04:19:54 -04:00
if not self.result or not self.result . bufnr or not api.nvim_buf_is_valid ( self.result . bufnr ) then return end
2024-08-21 23:20:30 +08:00
Utils.unlock_buf ( self.result . bufnr )
2024-10-10 05:05:29 +08:00
if opts.backspace ~= nil and opts.backspace > 0 then delete_last_n_chars ( self.result . bufnr , opts.backspace ) end
scroll_to_bottom ( )
2024-09-30 19:38:31 +08:00
local lines = vim.split ( content , " \n " )
2024-09-03 04:19:54 -04:00
api.nvim_buf_call ( self.result . bufnr , function ( ) api.nvim_put ( lines , " c " , true , true ) end )
2024-08-21 23:20:30 +08:00
Utils.lock_buf ( self.result . bufnr )
2024-08-21 21:28:17 +08:00
api.nvim_set_option_value ( " filetype " , " Avante " , { buf = self.result . bufnr } )
2024-09-03 04:19:54 -04:00
if opts.scroll then scroll_to_bottom ( ) end
if opts.callback ~= nil then opts.callback ( ) end
2024-08-18 15:03:25 -04:00
end )
else
vim.defer_fn ( function ( )
2024-09-03 04:19:54 -04:00
if not self.result or not self.result . bufnr or not api.nvim_buf_is_valid ( self.result . bufnr ) then return end
2024-08-20 01:47:43 +08:00
local lines = vim.split ( content , " \n " )
2024-08-21 23:20:30 +08:00
Utils.unlock_buf ( self.result . bufnr )
2024-10-15 12:30:20 +08:00
Utils.update_buffer_content ( self.result . bufnr , lines )
2024-08-21 23:20:30 +08:00
Utils.lock_buf ( self.result . bufnr )
2024-08-21 21:28:17 +08:00
api.nvim_set_option_value ( " filetype " , " Avante " , { buf = self.result . bufnr } )
if opts.focus and not self : is_focused_on_result ( ) then
2024-08-18 15:03:25 -04:00
xpcall ( function ( )
--- set cursor to bottom of result view
2024-08-21 21:28:17 +08:00
api.nvim_set_current_win ( self.result . winid )
2024-09-03 04:19:54 -04:00
end , function ( err ) return err end )
2024-08-18 15:03:25 -04:00
end
2024-08-18 05:36:30 -04:00
2024-09-03 04:19:54 -04:00
if opts.scroll then Utils.buf_scroll_to_end ( self.result . bufnr ) end
2024-08-20 01:47:43 +08:00
2024-09-03 04:19:54 -04:00
if opts.callback ~= nil then opts.callback ( ) end
2024-08-18 15:03:25 -04:00
end , 0 )
end
2024-08-17 15:14:30 +08:00
return self
end
2024-08-15 17:45:46 +08:00
-- Function to get current timestamp
2024-09-03 05:12:07 -04:00
local function get_timestamp ( ) return os.date ( " %Y-%m-%d %H:%M:%S " ) end
2024-08-15 17:45:46 +08:00
2024-11-18 18:07:33 +08:00
---@param timestamp string|osdate
---@param provider string
---@param model string
---@param request string
2024-11-19 06:01:50 +08:00
---@param selected_file {filepath: string}?
2024-11-18 18:07:33 +08:00
---@param selected_code {filetype: string, content: string}?
---@return string
local function render_chat_record_prefix ( timestamp , provider , model , request , selected_file , selected_code )
2024-08-19 00:05:13 +08:00
provider = provider or " unknown "
model = model or " unknown "
2024-11-18 18:07:33 +08:00
local res = " - Datetime: " .. timestamp .. " \n \n " .. " - Model: " .. provider .. " / " .. model
if selected_file ~= nil then res = res .. " \n \n - Selected file: " .. selected_file.filepath end
if selected_code ~= nil then
res = res
.. " \n \n - Selected code: "
.. " \n \n ``` "
.. selected_code.filetype
.. " \n "
.. selected_code.content
.. " \n ``` "
end
return res .. " \n \n > " .. request : gsub ( " \n " , " \n > " ) : gsub ( " ([%w-_]+)%b[] " , " `%0` " ) .. " \n \n "
2024-08-19 00:05:13 +08:00
end
2024-10-15 17:12:10 +08:00
local function calculate_config_window_position ( )
local position = Config.windows . position
if position == " smart " then
-- get editor width
local editor_width = vim.o . columns
-- get editor height
local editor_height = vim.o . lines * 3
if editor_width > editor_height then
position = " right "
else
position = " bottom "
end
end
return position
end
2024-09-01 15:52:16 +08:00
function Sidebar : get_layout ( )
2024-10-15 17:12:10 +08:00
return vim.tbl_contains ( { " left " , " right " } , calculate_config_window_position ( ) ) and " vertical " or " horizontal "
2024-09-01 15:52:16 +08:00
end
2024-11-18 18:07:33 +08:00
---@param history avante.ChatHistoryEntry[]
---@return string
function Sidebar : render_history_content ( history )
2024-08-15 17:45:46 +08:00
local content = " "
2024-08-21 21:28:17 +08:00
for idx , entry in ipairs ( history ) do
2024-11-18 18:07:33 +08:00
if entry.reset_memory then
content = content .. " ***MEMORY RESET*** \n \n "
if idx < # history then content = content .. " --- \n \n " end
goto continue
end
local prefix = render_chat_record_prefix (
entry.timestamp ,
entry.provider ,
entry.model ,
entry.request or " " ,
entry.selected_file ,
entry.selected_code
)
2024-08-19 00:05:13 +08:00
content = content .. prefix
2024-08-15 17:45:46 +08:00
content = content .. entry.response .. " \n \n "
2024-09-03 04:19:54 -04:00
if idx < # history then content = content .. " --- \n \n " end
2024-11-18 18:07:33 +08:00
:: continue ::
2024-08-15 17:45:46 +08:00
end
2024-11-05 21:02:38 +08:00
return content
end
function Sidebar : update_content_with_history ( history )
2024-11-18 18:07:33 +08:00
local content = self : render_history_content ( history )
2024-11-05 21:02:38 +08:00
self : update_content ( content , { ignore_history = true } )
2024-08-15 17:45:46 +08:00
end
2024-08-30 15:01:23 +08:00
---@return string, integer
2024-08-17 15:14:30 +08:00
function Sidebar : get_content_between_separators ( )
2024-08-15 19:04:15 +08:00
local separator = " --- "
2024-08-19 19:15:58 -04:00
local cursor_line , _ = Utils.get_cursor_pos ( )
2024-08-21 21:28:17 +08:00
local lines = Utils.get_buf_lines ( 0 , - 1 , self.result . bufnr )
2024-08-15 19:04:15 +08:00
local start_line , end_line
for i = cursor_line , 1 , - 1 do
if lines [ i ] == separator then
start_line = i + 1
break
end
end
start_line = start_line or 1
for i = cursor_line , # lines do
if lines [ i ] == separator then
end_line = i - 1
break
end
end
end_line = end_line or # lines
if lines [ cursor_line ] == separator then
if cursor_line > 1 and lines [ cursor_line - 1 ] ~= separator then
end_line = cursor_line - 1
elseif cursor_line < # lines and lines [ cursor_line + 1 ] ~= separator then
start_line = cursor_line + 1
end
end
local content = table.concat ( vim.list_slice ( lines , start_line , end_line ) , " \n " )
2024-08-30 15:01:23 +08:00
return content , start_line
2024-08-15 19:04:15 +08:00
end
2024-11-19 06:01:50 +08:00
---@alias AvanteSlashCommandType "clear" | "help" | "lines" | "reset"
---@alias AvanteSlashCommandCallback fun(args: string, cb?: fun(args: string): nil): nil
---@alias AvanteSlashCommand {description: string, command: AvanteSlashCommandType, details: string, shorthelp?: string, callback?: AvanteSlashCommandCallback}
---@return AvanteSlashCommand[]
2024-08-24 04:32:44 +08:00
function Sidebar : get_commands ( )
2024-08-25 01:59:22 -04:00
---@param items_ {command: string, description: string, shorthelp?: string}[]
---@return string
2024-08-24 04:32:44 +08:00
local function get_help_text ( items_ )
local help_text = " "
for _ , item in ipairs ( items_ ) do
2024-08-25 01:59:22 -04:00
help_text = help_text .. " - " .. item.command .. " : " .. ( item.shorthelp or item.description ) .. " \n "
2024-08-24 04:32:44 +08:00
end
return help_text
end
2024-11-19 06:01:50 +08:00
---@type AvanteSlashCommand[]
2024-08-24 04:32:44 +08:00
local items = {
2024-08-25 01:59:22 -04:00
{ description = " Show help message " , command = " help " } ,
{ description = " Clear chat history " , command = " clear " } ,
2024-11-18 18:07:33 +08:00
{ description = " Reset memory " , command = " reset " } ,
2024-08-25 01:59:22 -04:00
{
shorthelp = " Ask a question about specific lines " ,
description = " /lines <start>-<end> <question> " ,
command = " lines " ,
} ,
2024-08-24 04:32:44 +08:00
}
2024-11-19 06:01:50 +08:00
---@type {[AvanteSlashCommandType]: AvanteSlashCommandCallback}
2024-08-24 04:32:44 +08:00
local cbs = {
2024-08-25 01:59:22 -04:00
help = function ( args , cb )
local help_text = get_help_text ( items )
self : update_content ( help_text , { focus = false , scroll = false } )
2024-09-03 04:19:54 -04:00
if cb then cb ( args ) end
2024-08-25 01:59:22 -04:00
end ,
clear = function ( args , cb )
2024-09-15 16:56:28 +02:00
local chat_history = Path.history . load ( self.code . bufnr )
if next ( chat_history ) ~= nil then
chat_history = { }
Path.history . save ( self.code . bufnr , chat_history )
self : update_content ( " Chat history cleared " , { focus = false , scroll = false } )
2024-11-18 18:07:33 +08:00
if cb then cb ( args ) end
else
self : update_content ( " Chat history is already empty " , { focus = false , scroll = false } )
end
end ,
reset = function ( args , cb )
local chat_history = Path.history . load ( self.code . bufnr )
if next ( chat_history ) ~= nil then
table.insert ( chat_history , {
timestamp = get_timestamp ( ) ,
provider = Config.provider ,
model = Config.get_provider ( Config.provider ) . model ,
request = " " ,
response = " " ,
original_response = " " ,
selected_file = nil ,
selected_code = nil ,
reset_memory = true ,
} )
Path.history . save ( self.code . bufnr , chat_history )
local history_content = self : render_history_content ( chat_history )
self : update_content ( history_content , { focus = false , scroll = true } )
if cb then cb ( args ) end
2024-09-15 16:56:28 +02:00
else
self : update_content ( " Chat history is already empty " , { focus = false , scroll = false } )
end
2024-08-25 01:59:22 -04:00
end ,
lines = function ( args , cb )
2024-09-03 04:19:54 -04:00
if cb then cb ( args ) end
2024-08-25 01:59:22 -04:00
end ,
2024-08-24 04:32:44 +08:00
}
2024-08-25 01:59:22 -04:00
return vim
. iter ( items )
: map (
2024-11-19 06:01:50 +08:00
---@param item AvanteSlashCommand
2024-08-25 01:59:22 -04:00
function ( item )
return {
command = item.command ,
description = item.description ,
callback = cbs [ item.command ] ,
details = item.shorthelp and table.concat ( { item.shorthelp , item.description } , " \n " ) or item.description ,
}
end
)
: totable ( )
2024-08-24 04:32:44 +08:00
end
function Sidebar : create_selected_code ( )
if self.selected_code ~= nil then
self.selected_code : unmount ( )
self.selected_code = nil
end
local selected_code_size = self : get_selected_code_size ( )
if self.code . selection ~= nil then
2024-09-03 05:12:07 -04:00
self.selected_code = Split ( {
2024-08-24 04:32:44 +08:00
enter = false ,
relative = {
type = " win " ,
2024-08-27 14:28:10 +08:00
winid = self.input . winid ,
2024-08-24 04:32:44 +08:00
} ,
buf_options = buf_options ,
2024-09-02 12:22:48 -04:00
win_options = base_win_options ,
2024-08-27 14:28:10 +08:00
position = " top " ,
2024-08-24 04:32:44 +08:00
size = {
2024-08-24 17:34:41 +08:00
height = selected_code_size + 3 ,
2024-08-24 04:32:44 +08:00
} ,
2024-09-03 05:12:07 -04:00
} )
2024-08-24 04:32:44 +08:00
self.selected_code : mount ( )
2024-09-01 15:52:16 +08:00
if self : get_layout ( ) == " horizontal " then
2024-08-31 23:35:35 +08:00
api.nvim_win_set_height ( self.result . winid , api.nvim_win_get_height ( self.result . winid ) - selected_code_size - 3 )
end
2024-08-24 04:32:44 +08:00
end
end
2024-10-10 05:05:29 +08:00
local generating_text = " **Generating response ...** \n "
2024-08-28 15:47:29 -04:00
local hint_window = nil
2024-09-05 02:43:31 -04:00
---@param opts AskOptions
function Sidebar : create_input ( opts )
2024-09-03 04:19:54 -04:00
if self.input then self.input : unmount ( ) end
2024-08-15 19:04:15 +08:00
2024-09-03 04:19:54 -04:00
if not self.code . bufnr or not api.nvim_buf_is_valid ( self.code . bufnr ) then return end
2024-08-27 22:44:40 +08:00
2024-08-31 13:39:50 -04:00
local chat_history = Path.history . load ( self.code . bufnr )
2024-08-15 17:45:46 +08:00
2024-08-23 02:01:14 -04:00
---@param request string
2024-08-21 21:28:17 +08:00
local function handle_submit ( request )
2024-08-23 02:01:14 -04:00
local model = Config.has_provider ( Config.provider ) and Config.get_provider ( Config.provider ) . model or " default "
2024-08-15 17:45:46 +08:00
local timestamp = get_timestamp ( )
2024-08-19 00:05:13 +08:00
2024-11-18 18:07:33 +08:00
local filetype = api.nvim_get_option_value ( " filetype " , { buf = self.code . bufnr } )
local selected_file = {
2024-11-19 23:52:53 +08:00
filepath = Utils.relative_path ( api.nvim_buf_get_name ( self.code . bufnr ) ) ,
2024-11-18 18:07:33 +08:00
}
local selected_code = nil
if self.code . selection ~= nil then
selected_code = {
filetype = filetype ,
content = self.code . selection.content ,
}
end
local content_prefix =
render_chat_record_prefix ( timestamp , Config.provider , model , request , selected_file , selected_code )
2024-08-19 00:05:13 +08:00
2024-08-18 05:36:30 -04:00
--- HACK: we need to set focus to true and scroll to false to
--- prevent the cursor from jumping to the bottom of the
--- buffer at the beginning
self : update_content ( " " , { focus = true , scroll = false } )
2024-10-10 05:05:29 +08:00
self : update_content ( content_prefix .. generating_text )
2024-08-15 17:45:46 +08:00
2024-08-21 21:28:17 +08:00
local content = table.concat ( Utils.get_buf_lines ( 0 , - 1 , self.code . bufnr ) , " \n " )
2024-08-30 22:21:50 +08:00
2024-09-30 19:38:31 +08:00
local selected_code_content = nil
if self.code . selection ~= nil then selected_code_content = self.code . selection.content end
2024-08-17 22:29:05 +08:00
2024-08-23 02:01:14 -04:00
if request : sub ( 1 , 1 ) == " / " then
2024-09-03 05:12:07 -04:00
local command , args = request : match ( " ^/(%S+)%s*(.*) " )
2024-08-24 04:32:44 +08:00
if command == nil then
self : update_content ( " Invalid command " , { focus = false , scroll = false } )
2024-08-23 02:01:14 -04:00
return
2024-08-24 04:32:44 +08:00
end
local cmds = self : get_commands ( )
2024-11-19 06:01:50 +08:00
---@type AvanteSlashCommand
2024-09-03 04:19:54 -04:00
local cmd = vim.iter ( cmds ) : filter ( function ( _ ) return _.command == command end ) : totable ( ) [ 1 ]
2024-08-24 04:32:44 +08:00
if cmd then
if command == " lines " then
cmd.callback ( args , function ( args_ )
2024-09-03 05:12:07 -04:00
local start_line , end_line , question = args_ : match ( " (%d+)-(%d+)%s+(.*) " )
2024-08-23 02:01:14 -04:00
---@cast start_line integer
start_line = tonumber ( start_line )
---@cast end_line integer
end_line = tonumber ( end_line )
2024-08-23 18:33:49 +08:00
if end_line == nil then
Utils.error ( " Invalid end line number " , { once = true , title = " Avante " } )
return
end
2024-09-30 19:38:31 +08:00
selected_code_content =
table.concat ( api.nvim_buf_get_lines ( self.code . bufnr , start_line - 1 , end_line , false ) , " \n " )
2024-08-23 02:01:14 -04:00
request = question
2024-08-24 04:32:44 +08:00
end )
else
cmd.callback ( args )
return
2024-08-23 02:01:14 -04:00
end
else
self : update_content ( " Unknown command: " .. command , { focus = false , scroll = false } )
return
end
end
2024-09-30 19:38:31 +08:00
local original_response = " "
local transformed_response = " "
local displayed_response = " "
2024-08-15 17:45:46 +08:00
2024-08-24 04:32:44 +08:00
local is_first_chunk = true
2024-08-19 06:11:02 -04:00
---@type AvanteChunkParser
local on_chunk = function ( chunk )
2024-09-30 19:38:31 +08:00
original_response = original_response .. chunk
local transformed = transform_result_content ( content , transformed_response .. chunk , filetype )
transformed_response = transformed.content
local cur_displayed_response = generate_display_content ( transformed )
2024-08-24 04:32:44 +08:00
if is_first_chunk then
is_first_chunk = false
2024-11-05 21:02:38 +08:00
self : update_content ( content_prefix .. chunk , { scroll = true } )
2024-08-24 04:32:44 +08:00
return
end
2024-10-10 05:05:29 +08:00
local suffix = get_display_content_suffix ( transformed )
2024-11-05 21:02:38 +08:00
self : update_content ( content_prefix .. cur_displayed_response .. suffix , { scroll = true } )
2024-10-10 05:05:29 +08:00
vim.schedule ( function ( ) vim.cmd ( " redraw " ) end )
displayed_response = cur_displayed_response
2024-08-19 06:11:02 -04:00
end
---@type AvanteCompleteParser
local on_complete = function ( err )
2024-08-18 22:20:29 -04:00
if err ~= nil then
2024-11-05 21:02:38 +08:00
self : update_content (
content_prefix .. displayed_response .. " \n \n Error: " .. vim.inspect ( err ) ,
{ scroll = true }
)
2024-08-18 22:20:29 -04:00
return
2024-08-17 22:29:05 +08:00
end
2024-08-18 22:20:29 -04:00
-- Execute when the stream request is actually completed
2024-11-05 21:02:38 +08:00
self : update_content (
content_prefix
.. displayed_response
.. " \n \n **Generation complete!** Please review the code suggestions above. \n " ,
{
scroll = true ,
callback = function ( ) api.nvim_exec_autocmds ( " User " , { pattern = VIEW_BUFFER_UPDATED_PATTERN } ) end ,
}
)
2024-08-18 22:20:29 -04:00
2024-08-21 21:28:17 +08:00
vim.defer_fn ( function ( )
if self.result and self.result . winid and api.nvim_win_is_valid ( self.result . winid ) then
api.nvim_set_current_win ( self.result . winid )
end
2024-11-16 01:02:38 +08:00
if Config.behaviour . auto_apply_diff_after_generation then self : apply ( false ) end
2024-08-21 21:28:17 +08:00
end , 0 )
2024-08-18 22:20:29 -04:00
-- Save chat history
table.insert ( chat_history or { } , {
timestamp = timestamp ,
provider = Config.provider ,
model = model ,
request = request ,
2024-09-30 19:38:31 +08:00
response = displayed_response ,
original_response = original_response ,
2024-11-18 18:07:33 +08:00
selected_file = selected_file ,
selected_code = selected_code ,
2024-08-18 22:20:29 -04:00
} )
2024-08-31 13:39:50 -04:00
Path.history . save ( self.code . bufnr , chat_history )
2024-08-19 06:11:02 -04:00
end
2024-09-23 18:52:26 +08:00
local mentions = Utils.extract_mentions ( request )
request = mentions.new_content
local file_ext = api.nvim_buf_get_name ( self.code . bufnr ) : match ( " ^.+%.(.+)$ " )
2024-09-26 03:45:49 +08:00
local project_context = mentions.enable_project_context and RepoMap.get_repo_map ( file_ext ) or nil
2024-09-23 18:52:26 +08:00
2024-11-18 18:07:33 +08:00
local history_messages = { }
for i = # chat_history , 1 , - 1 do
local entry = chat_history [ i ]
if entry.reset_memory then break end
if
entry.request == nil
or entry.original_response == nil
or entry.request == " "
or entry.original_response == " "
then
break
end
table.insert ( history_messages , 1 , { role = " assistant " , content = entry.original_response } )
local user_content = " "
if entry.selected_file ~= nil then
user_content = user_content .. " SELECTED FILE: " .. entry.selected_file . filepath .. " \n \n "
end
if entry.selected_code ~= nil then
user_content = user_content
.. " SELECTED CODE: \n \n ``` "
.. entry.selected_code . filetype
.. " \n "
.. entry.selected_code . content
.. " \n ``` \n \n "
end
user_content = user_content .. " USER PROMPT: \n \n " .. entry.request
table.insert ( history_messages , 1 , { role = " user " , content = user_content } )
end
2024-11-04 16:20:28 +08:00
2024-09-03 05:12:07 -04:00
Llm.stream ( {
2024-09-03 04:09:13 -04:00
bufnr = self.code . bufnr ,
2024-09-05 02:43:31 -04:00
ask = opts.ask ,
2024-09-23 18:52:26 +08:00
project_context = vim.json . encode ( project_context ) ,
2024-11-04 16:20:28 +08:00
history_messages = history_messages ,
2024-09-30 19:38:31 +08:00
file_content = content ,
2024-08-30 22:21:50 +08:00
code_lang = filetype ,
2024-09-30 19:38:31 +08:00
selected_code = selected_code_content ,
2024-08-30 18:53:49 +08:00
instructions = request ,
mode = " planning " ,
on_chunk = on_chunk ,
on_complete = on_complete ,
2024-09-03 05:12:07 -04:00
} )
2024-08-15 17:45:46 +08:00
end
2024-08-31 19:21:54 +08:00
local get_position = function ( )
2024-09-03 04:19:54 -04:00
if self : get_layout ( ) == " vertical " then return " bottom " end
2024-09-01 18:51:03 -04:00
return " right "
2024-08-31 19:21:54 +08:00
end
local get_size = function ( )
2024-09-03 04:19:54 -04:00
if self : get_layout ( ) == " vertical " then return {
2024-11-03 16:48:37 +08:00
height = Config.windows . input.height ,
2024-09-03 04:19:54 -04:00
} end
2024-08-31 19:21:54 +08:00
2024-08-31 23:35:35 +08:00
local selected_code_size = self : get_selected_code_size ( )
2024-08-31 19:21:54 +08:00
return {
width = " 40% " ,
2024-08-31 23:35:35 +08:00
height = math.max ( 1 , api.nvim_win_get_height ( self.result . winid ) - selected_code_size ) ,
2024-08-31 19:21:54 +08:00
}
end
2024-09-03 05:12:07 -04:00
self.input = Split ( {
2024-08-27 14:28:10 +08:00
enter = false ,
relative = {
type = " win " ,
winid = self.result . winid ,
} ,
2024-09-02 12:22:48 -04:00
win_options = vim.tbl_deep_extend ( " force " , base_win_options , { signcolumn = " yes " } ) ,
2024-08-31 19:21:54 +08:00
position = get_position ( ) ,
size = get_size ( ) ,
2024-09-03 05:12:07 -04:00
} )
2024-08-15 17:45:46 +08:00
2024-08-24 17:34:41 +08:00
local function on_submit ( )
2024-08-27 02:12:35 -04:00
if not vim.g . avante_login then
Utils.warn ( " Sending message to fast!, API key is not yet set " , { title = " Avante " } )
return
end
2024-09-03 04:19:54 -04:00
if not self.input or not self.input . bufnr or not api.nvim_buf_is_valid ( self.input . bufnr ) then return end
2024-08-24 17:34:41 +08:00
local lines = api.nvim_buf_get_lines ( self.input . bufnr , 0 , - 1 , false )
local request = table.concat ( lines , " \n " )
2024-09-03 04:19:54 -04:00
if request == " " then return end
2024-08-24 17:34:41 +08:00
api.nvim_buf_set_lines ( self.input . bufnr , 0 , - 1 , false , { } )
handle_submit ( request )
end
2024-08-26 18:26:56 +08:00
self.input : mount ( )
2024-08-27 14:28:10 +08:00
local function place_sign_at_first_line ( bufnr )
local group = " avante_input_prompt_group "
2024-09-23 18:52:26 +08:00
fn.sign_unplace ( group , { buffer = bufnr } )
2024-08-27 14:28:10 +08:00
2024-09-23 18:52:26 +08:00
fn.sign_place ( 0 , group , " AvanteInputPromptSign " , bufnr , { lnum = 1 } )
2024-08-27 14:28:10 +08:00
end
place_sign_at_first_line ( self.input . bufnr )
if Utils.in_visual_mode ( ) then
-- Exit visual mode
api.nvim_feedkeys ( api.nvim_replace_termcodes ( " <Esc> " , true , false , true ) , " n " , true )
end
2024-08-25 00:51:59 +08:00
self.input : map ( " n " , Config.mappings . submit.normal , on_submit )
self.input : map ( " i " , Config.mappings . submit.insert , on_submit )
2024-08-24 17:34:41 +08:00
2024-08-24 04:32:44 +08:00
api.nvim_set_option_value ( " filetype " , " AvanteInput " , { buf = self.input . bufnr } )
-- Setup completion
api.nvim_create_autocmd ( " InsertEnter " , {
group = self.augroup ,
buffer = self.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
2024-11-19 06:01:50 +08:00
cmp.register_source (
" avante_commands " ,
require ( " cmp_avante.commands " ) . new ( self : get_commands ( ) , self.input . bufnr )
)
2024-09-25 17:01:10 +08:00
cmp.register_source (
" avante_mentions " ,
require ( " cmp_avante.mentions " ) . new ( Utils.get_mentions ( ) , self.input . bufnr )
)
2024-09-03 05:12:07 -04:00
cmp.setup . buffer ( {
2024-08-24 04:32:44 +08:00
enabled = true ,
sources = {
{ name = " avante_commands " } ,
2024-09-23 18:52:26 +08:00
{ name = " avante_mentions " } ,
2024-08-24 04:32:44 +08:00
} ,
2024-09-03 05:12:07 -04:00
} )
2024-08-24 04:32:44 +08:00
end
end ,
2024-08-24 20:29:46 +08:00
} )
-- Close the floating window
local function close_hint ( )
if hint_window and api.nvim_win_is_valid ( hint_window ) then
api.nvim_win_close ( hint_window , true )
hint_window = nil
end
end
2024-09-04 21:27:08 +01:00
local function get_float_window_row ( )
local win_height = vim.api . nvim_win_get_height ( self.input . winid )
local winline = Utils.winline ( self.input . winid )
if winline >= win_height - 1 then return 0 end
return winline
end
2024-08-24 20:29:46 +08:00
-- Create a floating window as a hint
local function show_hint ( )
close_hint ( ) -- Close the existing hint window
2024-08-25 00:24:48 -04:00
local hint_text = ( vim.fn . mode ( ) ~= " i " and Config.mappings . submit.normal or Config.mappings . submit.insert )
.. " : submit "
2024-08-24 20:29:46 +08:00
local buf = api.nvim_create_buf ( false , true )
api.nvim_buf_set_lines ( buf , 0 , - 1 , false , { hint_text } )
2024-09-15 10:53:33 -04:00
api.nvim_buf_add_highlight ( buf , 0 , " AvantePopupHint " , 0 , 0 , - 1 )
2024-08-24 20:29:46 +08:00
-- Get the current window size
local win_width = api.nvim_win_get_width ( self.input . winid )
2024-08-24 22:10:05 -04:00
local width = # hint_text
2024-08-24 20:29:46 +08:00
-- Set the floating window options
2024-09-05 02:43:31 -04:00
local win_opts = {
2024-08-24 20:29:46 +08:00
relative = " win " ,
win = self.input . winid ,
width = width ,
height = 1 ,
2024-09-04 21:27:08 +01:00
row = get_float_window_row ( ) ,
2024-08-27 14:28:10 +08:00
col = math.max ( win_width - width , 0 ) , -- Display in the bottom right corner
2024-08-24 20:29:46 +08:00
style = " minimal " ,
border = " none " ,
focusable = false ,
zindex = 100 ,
}
-- Create the floating window
2024-09-05 02:43:31 -04:00
hint_window = api.nvim_open_win ( buf , false , win_opts )
2024-08-24 20:29:46 +08:00
end
2024-09-04 21:27:08 +01:00
api.nvim_create_autocmd ( { " TextChanged " , " TextChangedI " , " VimResized " } , {
2024-08-27 14:28:10 +08:00
group = self.augroup ,
buffer = self.input . bufnr ,
callback = function ( )
show_hint ( )
place_sign_at_first_line ( self.input . bufnr )
end ,
} )
api.nvim_create_autocmd ( " QuitPre " , {
group = self.augroup ,
buffer = self.input . bufnr ,
2024-09-03 04:19:54 -04:00
callback = function ( ) close_hint ( ) end ,
2024-08-27 14:28:10 +08:00
} )
2024-08-24 20:29:46 +08:00
-- Show hint in insert mode
api.nvim_create_autocmd ( " ModeChanged " , {
group = self.augroup ,
pattern = " *:i " ,
callback = function ( )
local cur_buf = api.nvim_get_current_buf ( )
2024-09-03 04:19:54 -04:00
if self.input and cur_buf == self.input . bufnr then show_hint ( ) end
2024-08-24 20:29:46 +08:00
end ,
} )
-- Close hint when exiting insert mode
api.nvim_create_autocmd ( " ModeChanged " , {
group = self.augroup ,
pattern = " i:* " ,
callback = function ( )
local cur_buf = api.nvim_get_current_buf ( )
2024-09-03 04:19:54 -04:00
if self.input and cur_buf == self.input . bufnr then show_hint ( ) end
2024-08-24 20:29:46 +08:00
end ,
} )
api.nvim_create_autocmd ( " WinEnter " , {
callback = function ( )
local cur_win = api.nvim_get_current_win ( )
if self.input and cur_win == self.input . winid then
show_hint ( )
else
close_hint ( )
end
end ,
2024-08-24 04:32:44 +08:00
} )
2024-09-02 12:22:48 -04:00
api.nvim_create_autocmd ( " User " , {
2024-10-14 20:22:34 -07:00
group = self.augroup ,
2024-09-02 12:22:48 -04:00
pattern = " AvanteInputSubmitted " ,
callback = function ( ev )
2024-09-03 04:19:54 -04:00
if ev.data and ev.data . request then handle_submit ( ev.data . request ) end
2024-09-02 12:22:48 -04:00
end ,
} )
2024-09-05 02:43:31 -04:00
self : refresh_winids ( )
2024-08-21 21:28:17 +08:00
end
2024-08-17 22:29:05 +08:00
2024-08-21 21:28:17 +08:00
function Sidebar : get_selected_code_size ( )
local selected_code_max_lines_count = 10
2024-08-17 22:29:05 +08:00
2024-08-21 21:28:17 +08:00
local selected_code_size = 0
2024-08-17 22:29:05 +08:00
2024-08-21 21:28:17 +08:00
if self.code . selection ~= nil then
local selected_code_lines = vim.split ( self.code . selection.content , " \n " )
2024-09-01 17:04:33 +08:00
local selected_code_lines_count = # selected_code_lines
2024-08-24 17:34:41 +08:00
selected_code_size = math.min ( selected_code_lines_count , selected_code_max_lines_count )
2024-08-21 21:28:17 +08:00
end
2024-08-17 22:29:05 +08:00
2024-08-21 21:28:17 +08:00
return selected_code_size
end
2024-09-05 02:43:31 -04:00
---@param opts AskOptions
2024-09-04 09:15:32 -04:00
function Sidebar : render ( opts )
2024-08-31 13:39:50 -04:00
local chat_history = Path.history . load ( self.code . bufnr )
2024-08-21 21:28:17 +08:00
2024-09-04 16:34:33 +02:00
local get_position = function ( )
2024-10-15 17:12:10 +08:00
return ( opts and opts.win and opts.win . position ) and opts.win . position or calculate_config_window_position ( )
2024-09-04 16:34:33 +02:00
end
2024-08-31 19:21:54 +08:00
local get_height = function ( )
local selected_code_size = self : get_selected_code_size ( )
2024-08-31 23:35:35 +08:00
2024-09-13 07:53:06 -07:00
if self : get_layout ( ) == " horizontal " then return math.floor ( Config.windows . height / 100 * vim.o . lines ) end
2024-08-31 19:21:54 +08:00
2024-09-01 15:52:16 +08:00
return math.max ( 1 , api.nvim_win_get_height ( self.code . winid ) - selected_code_size - 3 - 8 )
2024-08-31 19:21:54 +08:00
end
local get_width = function ( )
2024-09-13 07:53:06 -07:00
if self : get_layout ( ) == " vertical " then return math.floor ( Config.windows . width / 100 * vim.o . columns ) end
2024-08-31 19:21:54 +08:00
2024-09-01 15:52:16 +08:00
return math.max ( 1 , api.nvim_win_get_width ( self.code . winid ) )
2024-08-31 19:21:54 +08:00
end
2024-08-21 21:28:17 +08:00
2024-09-03 05:12:07 -04:00
self.result = Split ( {
2024-08-27 14:28:10 +08:00
enter = false ,
2024-08-21 21:28:17 +08:00
relative = " editor " ,
2024-08-31 19:21:54 +08:00
position = get_position ( ) ,
2024-08-27 14:28:10 +08:00
buf_options = vim.tbl_deep_extend ( " force " , buf_options , {
2024-08-22 03:38:22 +08:00
modifiable = false ,
swapfile = false ,
buftype = " nofile " ,
bufhidden = " wipe " ,
filetype = " Avante " ,
2024-08-27 14:28:10 +08:00
} ) ,
2024-09-02 12:22:48 -04:00
win_options = base_win_options ,
2024-08-27 14:28:10 +08:00
size = {
2024-08-31 19:21:54 +08:00
width = get_width ( ) ,
height = get_height ( ) ,
2024-08-24 02:33:35 +08:00
} ,
2024-09-03 05:12:07 -04:00
} )
2024-08-22 03:38:22 +08:00
2024-08-26 18:26:56 +08:00
self.result : mount ( )
2024-10-14 20:22:34 -07:00
self.augroup = api.nvim_create_augroup ( " avante_sidebar_ " .. self.id .. self.result . winid , { clear = true } )
2024-08-21 21:28:17 +08:00
self.result : on ( event.BufWinEnter , function ( )
2024-09-03 04:19:54 -04:00
xpcall ( function ( ) api.nvim_buf_set_name ( self.result . bufnr , RESULT_BUF_NAME ) end , function ( _ ) end )
2024-08-21 21:28:17 +08:00
end )
self.result : map ( " n " , " q " , function ( )
2024-09-03 14:03:59 +08:00
Llm.cancel_inflight_request ( )
2024-08-21 21:28:17 +08:00
self : close ( )
end )
self.result : map ( " n " , " <Esc> " , function ( )
2024-09-03 14:03:59 +08:00
Llm.cancel_inflight_request ( )
2024-08-21 21:28:17 +08:00
self : close ( )
end )
2024-09-05 02:43:31 -04:00
self : create_input ( opts )
2024-08-21 21:28:17 +08:00
self : update_content_with_history ( chat_history )
-- reset states when buffer is closed
api.nvim_buf_attach ( self.code . bufnr , false , {
2024-09-03 04:19:54 -04:00
on_detach = function ( _ , _ ) self : reset ( ) end ,
2024-08-21 21:28:17 +08:00
} )
2024-08-24 04:32:44 +08:00
self : create_selected_code ( )
2024-08-15 17:45:46 +08:00
2024-09-05 02:43:31 -04:00
self : on_mount ( opts )
2024-08-21 21:28:17 +08:00
2024-08-17 15:14:30 +08:00
return self
2024-08-15 17:45:46 +08:00
end
2024-08-17 15:14:30 +08:00
return Sidebar