feat: commands with cmp (#183)
This commit is contained in:
parent
f2173c9a3c
commit
3dbdba198a
@ -28,6 +28,7 @@ local Sidebar = {}
|
|||||||
|
|
||||||
---@class avante.Sidebar
|
---@class avante.Sidebar
|
||||||
---@field id integer
|
---@field id integer
|
||||||
|
---@field registered_cmp boolean
|
||||||
---@field augroup integer
|
---@field augroup integer
|
||||||
---@field code avante.CodeState
|
---@field code avante.CodeState
|
||||||
---@field winids table<string, integer> this table stores the winids of the sidebar components (result_container, result, selected_code_container, selected_code, input_container, input), even though they are destroyed.
|
---@field winids table<string, integer> this table stores the winids of the sidebar components (result_container, result, selected_code_container, selected_code, input_container, input), even though they are destroyed.
|
||||||
@ -42,6 +43,7 @@ local Sidebar = {}
|
|||||||
function Sidebar:new(id)
|
function Sidebar:new(id)
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
id = id,
|
id = id,
|
||||||
|
registered_cmp = false,
|
||||||
code = { bufnr = 0, winid = 0, selection = nil },
|
code = { bufnr = 0, winid = 0, selection = nil },
|
||||||
winids = {
|
winids = {
|
||||||
result_container = 0,
|
result_container = 0,
|
||||||
@ -67,6 +69,7 @@ end
|
|||||||
|
|
||||||
function Sidebar:reset()
|
function Sidebar:reset()
|
||||||
self:delete_autocmds()
|
self:delete_autocmds()
|
||||||
|
self.registered_cmp = false
|
||||||
self.code = { bufnr = 0, winid = 0, selection = nil }
|
self.code = { bufnr = 0, winid = 0, selection = nil }
|
||||||
self.winids = { result_container = 0, result = 0, selected_code = 0, input = 0 }
|
self.winids = { result_container = 0, result = 0, selected_code = 0, input = 0 }
|
||||||
self.result_container = nil
|
self.result_container = nil
|
||||||
@ -544,8 +547,10 @@ function Sidebar:on_mount()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function unbind_jump_keys()
|
local function unbind_jump_keys()
|
||||||
pcall(vim.keymap.del, "n", "]c", { buffer = self.result.bufnr })
|
if self.result and self.result.bufnr and api.nvim_buf_is_valid(self.result.bufnr) then
|
||||||
pcall(vim.keymap.del, "n", "[c", { buffer = self.result.bufnr })
|
pcall(vim.keymap.del, "n", "]c", { buffer = self.result.bufnr })
|
||||||
|
pcall(vim.keymap.del, "n", "[c", { buffer = self.result.bufnr })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
|
api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
|
||||||
@ -593,8 +598,6 @@ function Sidebar:on_mount()
|
|||||||
self:render_input_container()
|
self:render_input_container()
|
||||||
self:render_selected_code_container()
|
self:render_selected_code_container()
|
||||||
|
|
||||||
-- api.nvim_set_option_value("buftype", "nofile", { buf = self.input.bufnr })
|
|
||||||
|
|
||||||
self.augroup = api.nvim_create_augroup("avante_" .. self.id .. self.result.winid, { clear = true })
|
self.augroup = api.nvim_create_augroup("avante_" .. self.id .. self.result.winid, { clear = true })
|
||||||
|
|
||||||
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
||||||
@ -1105,6 +1108,111 @@ function Sidebar:get_content_between_separators()
|
|||||||
return content
|
return content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Sidebar:get_commands()
|
||||||
|
local function get_help_text(items_)
|
||||||
|
local help_text = ""
|
||||||
|
for _, item in ipairs(items_) do
|
||||||
|
help_text = help_text .. "- " .. item.name .. ": " .. item.description .. "\n"
|
||||||
|
end
|
||||||
|
return help_text
|
||||||
|
end
|
||||||
|
|
||||||
|
local items = {
|
||||||
|
{ name = "help", description = "Show this help message", command = "help" },
|
||||||
|
{ name = "clear", description = "Clear chat history", command = "clear" },
|
||||||
|
{ name = "lines <start>-<end> <question>", description = "Ask a question about specific lines", command = "lines" },
|
||||||
|
}
|
||||||
|
|
||||||
|
local cbs = {
|
||||||
|
{
|
||||||
|
command = "help",
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
callback = function(args, cb)
|
||||||
|
local help_text = get_help_text(items)
|
||||||
|
self:create_input()
|
||||||
|
self:update_content(help_text, { focus = false, scroll = false })
|
||||||
|
if cb then
|
||||||
|
cb(args)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command = "clear",
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
callback = function(args, cb)
|
||||||
|
local chat_history = {}
|
||||||
|
save_chat_history(self, chat_history)
|
||||||
|
self:create_input()
|
||||||
|
self:update_content("Chat history cleared", { focus = false, scroll = false })
|
||||||
|
vim.defer_fn(function()
|
||||||
|
self:close()
|
||||||
|
if cb then
|
||||||
|
cb(args)
|
||||||
|
end
|
||||||
|
end, 1000)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command = "lines",
|
||||||
|
callback = function(args, cb)
|
||||||
|
if cb then
|
||||||
|
cb(args)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local commands = {}
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
table.insert(commands, {
|
||||||
|
name = item.name,
|
||||||
|
command = item.command,
|
||||||
|
description = item.description,
|
||||||
|
callback = function(args, cb)
|
||||||
|
for _, cb_ in ipairs(cbs) do
|
||||||
|
if cb_.command == item.command then
|
||||||
|
cb_.callback(args, cb)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
return commands
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sidebar:create_selected_code()
|
||||||
|
if self.selected_code ~= nil then
|
||||||
|
self.selected_code:unmount()
|
||||||
|
self.selected_code = nil
|
||||||
|
end
|
||||||
|
if self.selected_code_container ~= nil then
|
||||||
|
self.selected_code_container:unmount()
|
||||||
|
self.selected_code_container = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local selected_code_size = self:get_selected_code_size()
|
||||||
|
|
||||||
|
if self.code.selection ~= nil then
|
||||||
|
self.selected_code_container = Split({
|
||||||
|
enter = false,
|
||||||
|
relative = {
|
||||||
|
type = "win",
|
||||||
|
winid = self.result_container.winid,
|
||||||
|
},
|
||||||
|
buf_options = buf_options,
|
||||||
|
win_options = get_win_options(),
|
||||||
|
position = "bottom",
|
||||||
|
size = {
|
||||||
|
height = selected_code_size,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
self.selected_code_container:mount()
|
||||||
|
self.selected_code = self:create_floating_window_for_split({ split_winid = self.selected_code_container.winid })
|
||||||
|
self.selected_code:mount()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Sidebar:create_input()
|
function Sidebar:create_input()
|
||||||
if
|
if
|
||||||
not self.input_container
|
not self.input_container
|
||||||
@ -1145,25 +1253,22 @@ function Sidebar:create_input()
|
|||||||
|
|
||||||
if request:sub(1, 1) == "/" then
|
if request:sub(1, 1) == "/" then
|
||||||
local command, args = request:match("^/(%S+)%s*(.*)")
|
local command, args = request:match("^/(%S+)%s*(.*)")
|
||||||
if command == "help" then
|
if command == nil then
|
||||||
local help_text = [[
|
self:update_content("Invalid command", { focus = false, scroll = false })
|
||||||
Available commands:
|
|
||||||
/clear - Clear chat history
|
|
||||||
/help - Show this help message
|
|
||||||
/lines <start>-<end> <question> - Ask a question about specific lines
|
|
||||||
]]
|
|
||||||
self:update_content(help_text, { focus = false, scroll = false })
|
|
||||||
return
|
return
|
||||||
elseif command == "lines" then
|
end
|
||||||
---@diagnostic disable-next-line: no-unknown
|
local cmds = self:get_commands()
|
||||||
local start_line, end_line, question = args:match("(%d+)-(%d+)%s+(.*)")
|
local cmd
|
||||||
---@cast question string
|
for _, c in ipairs(cmds) do
|
||||||
|
if c.command == command then
|
||||||
if selected_code_content_with_line_numbers ~= nil then
|
cmd = c
|
||||||
Utils.warn("/lines is mutually exclusive with visual selection on blocks.", { once = true, title = "Avante" })
|
break
|
||||||
request = question
|
end
|
||||||
else
|
end
|
||||||
if start_line and end_line and question then
|
if cmd then
|
||||||
|
if command == "lines" then
|
||||||
|
cmd.callback(args, function(args_)
|
||||||
|
local start_line, end_line, question = args_:match("(%d+)-(%d+)%s+(.*)")
|
||||||
---@cast start_line integer
|
---@cast start_line integer
|
||||||
start_line = tonumber(start_line)
|
start_line = tonumber(start_line)
|
||||||
---@cast end_line integer
|
---@cast end_line integer
|
||||||
@ -1177,24 +1282,13 @@ Available commands:
|
|||||||
start_line
|
start_line
|
||||||
)
|
)
|
||||||
request = question
|
request = question
|
||||||
else
|
end)
|
||||||
self:update_content(
|
else
|
||||||
"Invalid format. Use: /lines <start>-<end> <question>",
|
cmd.callback(args)
|
||||||
{ focus = false, scroll = false }
|
return
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif command == "clear" then
|
|
||||||
chat_history = {}
|
|
||||||
save_chat_history(self, chat_history)
|
|
||||||
self:update_content("Chat history cleared", { focus = false, scroll = false })
|
|
||||||
vim.defer_fn(function()
|
|
||||||
self:close()
|
|
||||||
end, 1000)
|
|
||||||
return
|
|
||||||
else
|
else
|
||||||
-- Unknown command
|
self:create_input()
|
||||||
self:update_content("Unknown command: " .. command, { focus = false, scroll = false })
|
self:update_content("Unknown command: " .. command, { focus = false, scroll = false })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -1204,9 +1298,16 @@ Available commands:
|
|||||||
|
|
||||||
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
|
||||||
|
|
||||||
|
local is_first_chunk = true
|
||||||
|
|
||||||
---@type AvanteChunkParser
|
---@type AvanteChunkParser
|
||||||
local on_chunk = function(chunk)
|
local on_chunk = function(chunk)
|
||||||
full_response = full_response .. chunk
|
full_response = full_response .. chunk
|
||||||
|
if is_first_chunk then
|
||||||
|
is_first_chunk = false
|
||||||
|
self:update_content(content_prefix .. chunk, { stream = false, scroll = true })
|
||||||
|
return
|
||||||
|
end
|
||||||
self:update_content(chunk, { stream = true, scroll = true })
|
self:update_content(chunk, { stream = true, scroll = true })
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
vim.cmd("redraw")
|
vim.cmd("redraw")
|
||||||
@ -1261,6 +1362,14 @@ Available commands:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
not self.input_container
|
||||||
|
or not self.input_container.winid
|
||||||
|
or not api.nvim_win_is_valid(self.input_container.winid)
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local win_width = api.nvim_win_get_width(self.input_container.winid)
|
local win_width = api.nvim_win_get_width(self.input_container.winid)
|
||||||
|
|
||||||
self.input = Input({
|
self.input = Input({
|
||||||
@ -1289,6 +1398,31 @@ Available commands:
|
|||||||
|
|
||||||
self.input:mount()
|
self.input:mount()
|
||||||
|
|
||||||
|
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
|
||||||
|
if not self.registered_cmp then
|
||||||
|
self.registered_cmp = true
|
||||||
|
cmp.register_source("avante_commands", require("cmp_avante.commands").new(self))
|
||||||
|
end
|
||||||
|
cmp.setup.buffer({
|
||||||
|
enabled = true,
|
||||||
|
sources = {
|
||||||
|
{ name = "avante_commands" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
self:refresh_winids()
|
self:refresh_winids()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1314,7 +1448,7 @@ end
|
|||||||
---@field float_opts table | nil
|
---@field float_opts table | nil
|
||||||
|
|
||||||
---@param opts CreateFloatingWindowForSplitOptions
|
---@param opts CreateFloatingWindowForSplitOptions
|
||||||
local function create_floating_window_for_split(opts)
|
function Sidebar:create_floating_window_for_split(opts)
|
||||||
local win_opts_ = vim.tbl_deep_extend("force", get_win_options(), opts.win_opts or {})
|
local win_opts_ = vim.tbl_deep_extend("force", get_win_options(), opts.win_opts or {})
|
||||||
|
|
||||||
local buf_opts_ = vim.tbl_deep_extend("force", buf_options, opts.buf_opts or {})
|
local buf_opts_ = vim.tbl_deep_extend("force", buf_options, opts.buf_opts or {})
|
||||||
@ -1346,7 +1480,7 @@ function Sidebar:render()
|
|||||||
|
|
||||||
self.result_container:mount()
|
self.result_container:mount()
|
||||||
|
|
||||||
self.result = create_floating_window_for_split({
|
self.result = self:create_floating_window_for_split({
|
||||||
split_winid = self.result_container.winid,
|
split_winid = self.result_container.winid,
|
||||||
buf_opts = {
|
buf_opts = {
|
||||||
modifiable = false,
|
modifiable = false,
|
||||||
@ -1402,24 +1536,7 @@ function Sidebar:render()
|
|||||||
|
|
||||||
self:create_input()
|
self:create_input()
|
||||||
|
|
||||||
if self.code.selection ~= nil then
|
self:create_selected_code()
|
||||||
self.selected_code_container = Split({
|
|
||||||
enter = false,
|
|
||||||
relative = {
|
|
||||||
type = "win",
|
|
||||||
winid = self.result_container.winid,
|
|
||||||
},
|
|
||||||
buf_options = buf_options,
|
|
||||||
win_options = get_win_options(),
|
|
||||||
position = "bottom",
|
|
||||||
size = {
|
|
||||||
height = selected_code_size,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
self.selected_code_container:mount()
|
|
||||||
self.selected_code = create_floating_window_for_split({ split_winid = self.selected_code_container.winid })
|
|
||||||
self.selected_code:mount()
|
|
||||||
end
|
|
||||||
|
|
||||||
self:on_mount()
|
self:on_mount()
|
||||||
|
|
||||||
|
49
lua/cmp_avante/commands.lua
Normal file
49
lua/cmp_avante/commands.lua
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---@class source
|
||||||
|
---@field sidebar avante.Sidebar
|
||||||
|
local source = {}
|
||||||
|
|
||||||
|
---@param sidebar avante.Sidebar
|
||||||
|
function source.new(sidebar)
|
||||||
|
return setmetatable({
|
||||||
|
sidebar = sidebar,
|
||||||
|
}, { __index = source })
|
||||||
|
end
|
||||||
|
|
||||||
|
function source:is_available()
|
||||||
|
return vim.bo.filetype == "AvanteInput"
|
||||||
|
end
|
||||||
|
|
||||||
|
source.get_position_encoding_kind = function()
|
||||||
|
return "utf-8"
|
||||||
|
end
|
||||||
|
|
||||||
|
function source:get_trigger_characters()
|
||||||
|
return { "/" }
|
||||||
|
end
|
||||||
|
|
||||||
|
function source:get_keyword_pattern()
|
||||||
|
return [[\%(@\|#\|/\)\k*]]
|
||||||
|
end
|
||||||
|
|
||||||
|
function source:complete(_, callback)
|
||||||
|
local kind = require("cmp").lsp.CompletionItemKind.Function
|
||||||
|
|
||||||
|
local items = {}
|
||||||
|
|
||||||
|
local commands = self.sidebar:get_commands()
|
||||||
|
|
||||||
|
for _, command in ipairs(commands) do
|
||||||
|
table.insert(items, {
|
||||||
|
label = "/" .. command.name,
|
||||||
|
kind = kind,
|
||||||
|
detail = command.description,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
callback({
|
||||||
|
items = items,
|
||||||
|
isIncomplete = false,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return source
|
Loading…
x
Reference in New Issue
Block a user