2024-09-03 05:12:07 -04:00
local Utils = require ( " avante.utils " )
local Config = require ( " avante.config " )
local Llm = require ( " avante.llm " )
2024-09-03 21:47:01 -04:00
local Provider = require ( " avante.providers " )
2024-09-26 03:45:49 +08:00
local RepoMap = require ( " avante.repo_map " )
2024-10-14 20:22:34 -07:00
local PromptInput = require ( " avante.prompt_input " )
2024-08-17 22:29:05 +08:00
local api = vim.api
local fn = vim.fn
2024-09-03 05:12:07 -04:00
local NAMESPACE = api.nvim_create_namespace ( " avante_selection " )
local SELECTED_CODE_NAMESPACE = api.nvim_create_namespace ( " avante_selected_code " )
2024-08-17 22:29:05 +08:00
local PRIORITY = vim.highlight . priorities.user
2024-08-18 05:36:30 -04:00
---@class avante.Selection
2024-08-27 22:44:40 +08:00
---@field selection avante.SelectionResult | nil
---@field cursor_pos table | nil
---@field shortcuts_extmark_id integer | nil
2024-08-27 23:47:15 +08:00
---@field selected_code_extmark_id integer | nil
2024-08-27 22:44:40 +08:00
---@field augroup integer | nil
---@field code_winid integer | nil
2024-10-14 20:22:34 -07:00
---@field prompt_input PromptInput | nil
2024-08-17 22:29:05 +08:00
local Selection = { }
2024-11-23 20:23:05 +08:00
Selection.__index = Selection
2024-08-17 22:29:05 +08:00
2024-08-18 05:36:30 -04:00
Selection.did_setup = false
2024-08-22 14:46:08 +08:00
---@param id integer the tabpage id retrieved from api.nvim_get_current_tabpage()
2024-08-18 05:36:30 -04:00
function Selection : new ( id )
2024-08-17 22:29:05 +08:00
return setmetatable ( {
2024-08-27 22:44:40 +08:00
shortcuts_extmark_id = nil ,
2024-08-27 23:47:15 +08:00
selected_code_extmark_id = nil ,
2024-08-18 05:36:30 -04:00
augroup = api.nvim_create_augroup ( " avante_selection_ " .. id , { clear = true } ) ,
2024-08-27 22:44:40 +08:00
selection = nil ,
cursor_pos = nil ,
code_winid = nil ,
2024-10-14 20:22:34 -07:00
prompt_input = nil ,
2024-11-23 20:23:05 +08:00
} , Selection )
2024-08-17 22:29:05 +08:00
end
function Selection : get_virt_text_line ( )
2024-09-03 05:12:07 -04:00
local current_pos = fn.getpos ( " . " )
2024-08-17 22:29:05 +08:00
-- Get the current and start position line numbers
local current_line = current_pos [ 2 ] - 1 -- 0-indexed
-- Ensure line numbers are not negative and don't exceed buffer range
local total_lines = api.nvim_buf_line_count ( 0 )
2024-09-03 04:19:54 -04:00
if current_line < 0 then current_line = 0 end
if current_line >= total_lines then current_line = total_lines - 1 end
2024-08-17 22:29:05 +08:00
-- Take the first line of the selection to ensure virt_text is always in the top right corner
return current_line
end
2024-08-27 22:44:40 +08:00
function Selection : show_shortcuts_hints_popup ( )
self : close_shortcuts_hints_popup ( )
2024-08-17 22:29:05 +08:00
2024-08-27 13:13:38 -04:00
local hint_text = string.format ( " [%s: ask, %s: edit] " , Config.mappings . ask , Config.mappings . edit )
2024-08-17 22:29:05 +08:00
local virt_text_line = self : get_virt_text_line ( )
2024-08-27 22:44:40 +08:00
self.shortcuts_extmark_id = api.nvim_buf_set_extmark ( 0 , NAMESPACE , virt_text_line , - 1 , {
2024-09-15 10:53:33 -04:00
virt_text = { { hint_text , " AvanteInlineHint " } } ,
2024-08-17 22:29:05 +08:00
virt_text_pos = " eol " ,
priority = PRIORITY ,
} )
end
2024-08-27 22:44:40 +08:00
function Selection : close_shortcuts_hints_popup ( )
if self.shortcuts_extmark_id then
api.nvim_buf_del_extmark ( 0 , NAMESPACE , self.shortcuts_extmark_id )
self.shortcuts_extmark_id = nil
end
end
function Selection : close_editing_input ( )
2024-10-14 20:22:34 -07:00
if self.prompt_input then
self.prompt_input : close ( )
self.prompt_input = nil
2024-08-27 22:44:40 +08:00
end
2024-10-14 20:22:34 -07:00
Llm.cancel_inflight_request ( )
2024-08-27 23:47:15 +08:00
if self.code_winid and api.nvim_win_is_valid ( self.code_winid ) then
local code_bufnr = api.nvim_win_get_buf ( self.code_winid )
api.nvim_buf_clear_namespace ( code_bufnr , SELECTED_CODE_NAMESPACE , 0 , - 1 )
if self.selected_code_extmark_id then
api.nvim_buf_del_extmark ( code_bufnr , SELECTED_CODE_NAMESPACE , self.selected_code_extmark_id )
self.selected_code_extmark_id = nil
end
end
2024-08-27 22:44:40 +08:00
if self.cursor_pos and self.code_winid then
vim.schedule ( function ( )
2024-08-31 12:30:22 +08:00
local bufnr = api.nvim_win_get_buf ( self.code_winid )
local line_count = api.nvim_buf_line_count ( bufnr )
local row = math.min ( self.cursor_pos [ 1 ] , line_count )
local line = api.nvim_buf_get_lines ( bufnr , row - 1 , row , true ) [ 1 ] or " "
local col = math.min ( self.cursor_pos [ 2 ] , # line )
api.nvim_win_set_cursor ( self.code_winid , { row , col } )
2024-08-27 22:44:40 +08:00
end )
end
end
function Selection : create_editing_input ( )
2024-08-27 23:47:15 +08:00
self : close_editing_input ( )
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-08-27 22:44:40 +08:00
local code_bufnr = api.nvim_get_current_buf ( )
2024-10-14 20:22:34 -07:00
local code_winid = api.nvim_get_current_win ( )
self.cursor_pos = api.nvim_win_get_cursor ( code_winid )
self.code_winid = code_winid
2024-08-27 22:44:40 +08:00
local code_lines = api.nvim_buf_get_lines ( code_bufnr , 0 , - 1 , false )
local code_content = table.concat ( code_lines , " \n " )
self.selection = Utils.get_visual_selection_and_range ( )
2024-10-08 16:29:18 +08:00
if self.selection == nil then
Utils.error ( " No visual selection found " , { once = true , title = " Avante " } )
return
end
2024-08-28 08:46:26 -07:00
local start_row
local start_col
local end_row
local end_col
if vim.fn . mode ( ) == " V " then
2024-11-23 21:49:33 +08:00
start_row = self.selection . range.start . lnum - 1
2024-08-28 08:46:26 -07:00
start_col = 0
2024-11-23 21:49:33 +08:00
end_row = self.selection . range.finish . lnum - 1
end_col = # code_lines [ self.selection . range.finish . lnum ]
2024-08-28 08:46:26 -07:00
else
2024-11-23 21:49:33 +08:00
start_row = self.selection . range.start . lnum - 1
2024-08-28 08:46:26 -07:00
start_col = self.selection . range.start . col - 1
2024-11-23 21:49:33 +08:00
end_row = self.selection . range.finish . lnum - 1
end_col = math.min ( self.selection . range.finish . col , # code_lines [ self.selection . range.finish . lnum ] )
2024-08-28 08:46:26 -07:00
end
self.selected_code_extmark_id = api.nvim_buf_set_extmark ( code_bufnr , SELECTED_CODE_NAMESPACE , start_row , start_col , {
hl_group = " Visual " ,
hl_mode = " combine " ,
end_row = end_row ,
end_col = end_col ,
priority = PRIORITY ,
} )
2024-08-27 22:44:40 +08:00
2024-10-14 20:22:34 -07:00
local submit_input = function ( input )
2024-08-27 22:44:40 +08:00
local full_response = " "
2024-11-23 21:49:33 +08:00
local start_line = self.selection . range.start . lnum
local finish_line = self.selection . range.finish . lnum
2024-08-27 22:44:40 +08:00
2024-11-23 21:49:33 +08:00
local original_first_line_indentation = Utils.get_indentation ( code_lines [ self.selection . range.start . lnum ] )
2024-08-27 22:44:40 +08:00
2024-08-31 13:29:28 +08:00
local need_prepend_indentation = false
2024-10-14 20:22:34 -07:00
self.prompt_input : start_spinner ( )
2024-08-27 22:44:40 +08:00
---@type AvanteChunkParser
local on_chunk = function ( chunk )
full_response = full_response .. chunk
2024-09-30 19:38:31 +08:00
local response_lines_ = vim.split ( full_response , " \n " )
local response_lines = { }
for i , line in ipairs ( response_lines_ ) do
if not ( string.match ( line , " ^``` " ) and ( i == 1 or i == # response_lines_ ) ) then
table.insert ( response_lines , line )
end
end
2024-08-31 13:29:28 +08:00
if # response_lines == 1 then
2024-08-28 22:17:00 +08:00
local first_line = response_lines [ 1 ]
local first_line_indentation = Utils.get_indentation ( first_line )
need_prepend_indentation = first_line_indentation ~= original_first_line_indentation
end
if need_prepend_indentation then
for i , line in ipairs ( response_lines ) do
response_lines [ i ] = original_first_line_indentation .. line
end
2024-08-27 22:44:40 +08:00
end
api.nvim_buf_set_lines ( code_bufnr , start_line - 1 , finish_line , true , response_lines )
finish_line = start_line + # response_lines - 1
end
---@type AvanteCompleteParser
local on_complete = function ( err )
if err then
2024-11-25 04:26:23 -08:00
-- NOTE: in Ubuntu 22.04+ you will see this ignorable error from ~/.local/share/nvim/lazy/avante.nvim/lua/avante/llm.lua `on_error = function(err)`, check to avoid showing this error.
if type ( err ) == " table " and err.exit == nil and err.stderr == " {} " then return end
2024-08-27 22:44:40 +08:00
Utils.error (
" Error occurred while processing the response: " .. vim.inspect ( err ) ,
{ once = true , title = " Avante " }
)
return
end
2024-10-14 20:22:34 -07:00
self.prompt_input : stop_spinner ( )
2024-09-03 04:19:54 -04:00
vim.defer_fn ( function ( ) self : close_editing_input ( ) end , 0 )
2024-08-27 22:44:40 +08:00
end
2024-08-30 22:21:50 +08:00
local filetype = api.nvim_get_option_value ( " filetype " , { buf = code_bufnr } )
2024-09-23 18:52:26 +08:00
local file_ext = api.nvim_buf_get_name ( code_bufnr ) : match ( " ^.+%.(.+)$ " )
local mentions = Utils.extract_mentions ( input )
input = mentions.new_content
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-08-30 22:21:50 +08:00
2024-11-23 23:08:10 +08:00
local diagnostics = Utils.get_current_selection_diagnostics ( code_bufnr , self.selection )
2024-11-23 21:49:33 +08:00
2024-09-03 05:12:07 -04:00
Llm.stream ( {
2024-09-05 02:43:31 -04:00
ask = true ,
2024-09-23 18:52:26 +08:00
project_context = vim.json . encode ( project_context ) ,
2024-11-23 21:49:33 +08:00
diagnostics = vim.json . encode ( diagnostics ) ,
2024-12-12 03:29:10 +10:00
selected_files = { { content = code_content , file_type = filetype , path = " " } } ,
2024-08-30 22:21:50 +08:00
code_lang = filetype ,
2024-08-30 18:53:49 +08:00
selected_code = self.selection . content ,
instructions = input ,
mode = " editing " ,
on_chunk = on_chunk ,
on_complete = on_complete ,
2024-09-03 05:12:07 -04:00
} )
2024-08-27 22:44:40 +08:00
end
2024-10-14 20:22:34 -07:00
local prompt_input = PromptInput : new ( {
submit_callback = submit_input ,
cancel_callback = function ( ) self : close_editing_input ( ) end ,
win_opts = {
border = Config.windows . edit.border ,
title = { { " edit selected block " , " FloatTitle " } } ,
} ,
start_insert = Config.windows . edit.start_insert ,
2024-08-27 22:44:40 +08:00
} )
2024-10-14 20:22:34 -07:00
self.prompt_input = prompt_input
prompt_input : open ( )
2024-09-02 12:22:48 -04:00
2024-09-23 18:52:26 +08:00
api.nvim_create_autocmd ( " InsertEnter " , {
group = self.augroup ,
2024-10-14 20:22:34 -07:00
buffer = prompt_input.bufnr ,
2024-09-23 18:52:26 +08:00
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-10-14 20:22:34 -07:00
cmp.register_source (
" avante_mentions " ,
2024-11-23 20:23:05 +08:00
require ( " cmp_avante.mentions " ) : new ( Utils.get_mentions ( ) , prompt_input.bufnr )
2024-10-14 20:22:34 -07:00
)
2024-09-23 18:52:26 +08:00
cmp.setup . buffer ( {
enabled = true ,
sources = {
{ name = " avante_mentions " } ,
} ,
} )
end
end ,
} )
2024-08-17 22:29:05 +08:00
end
2024-08-18 05:36:30 -04:00
function Selection : setup_autocmds ( )
Selection.did_setup = true
api.nvim_create_autocmd ( { " ModeChanged " } , {
2024-08-17 22:29:05 +08:00
group = self.augroup ,
pattern = { " n:v " , " n:V " , " n: " } , -- Entering Visual mode from Normal mode
2024-08-23 02:23:45 -04:00
callback = function ( ev )
2024-09-03 04:19:54 -04:00
if not Utils.is_sidebar_buffer ( ev.buf ) then self : show_shortcuts_hints_popup ( ) end
2024-08-17 22:29:05 +08:00
end ,
} )
api.nvim_create_autocmd ( { " CursorMoved " , " CursorMovedI " } , {
group = self.augroup ,
2024-08-23 02:23:45 -04:00
callback = function ( ev )
2024-08-24 00:14:20 +08:00
if not Utils.is_sidebar_buffer ( ev.buf ) then
if Utils.in_visual_mode ( ) then
2024-08-27 22:44:40 +08:00
self : show_shortcuts_hints_popup ( )
2024-08-23 02:23:45 -04:00
else
2024-08-27 22:44:40 +08:00
self : close_shortcuts_hints_popup ( )
2024-08-23 02:23:45 -04:00
end
2024-08-17 22:29:05 +08:00
end
end ,
} )
api.nvim_create_autocmd ( { " ModeChanged " } , {
group = self.augroup ,
pattern = { " v:n " , " v:i " , " v:c " } , -- Switching from visual mode back to normal, insert, or other modes
2024-08-23 02:23:45 -04:00
callback = function ( ev )
2024-09-03 04:19:54 -04:00
if not Utils.is_sidebar_buffer ( ev.buf ) then self : close_shortcuts_hints_popup ( ) end
2024-08-17 22:29:05 +08:00
end ,
} )
2024-08-24 00:21:00 +08:00
api.nvim_create_autocmd ( { " BufLeave " } , {
group = self.augroup ,
callback = function ( ev )
2024-09-03 04:19:54 -04:00
if not Utils.is_sidebar_buffer ( ev.buf ) then self : close_shortcuts_hints_popup ( ) end
2024-08-24 00:21:00 +08:00
end ,
} )
2024-08-17 14:14:02 -04:00
return self
2024-08-17 22:29:05 +08:00
end
function Selection : 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 22:29:05 +08:00
self.augroup = nil
2024-08-18 05:36:30 -04:00
Selection.did_setup = false
2024-08-17 22:29:05 +08:00
end
return Selection