fix(ui): set selection per buffer and remove spinner (closes #32) (#62)

Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
This commit is contained in:
Aaron Pham 2024-08-18 05:36:30 -04:00 committed by GitHub
parent c19ea9a48a
commit d885bd9680
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 85 deletions

View File

@ -282,8 +282,6 @@ local function call_claude_api_stream(question, code_lang, code_content, selecte
local url = Utils.trim_suffix(Config.claude.endpoint, "/") .. "/v1/messages" local url = Utils.trim_suffix(Config.claude.endpoint, "/") .. "/v1/messages"
-- print("Sending request to Claude API...")
curl.post(url, { curl.post(url, {
---@diagnostic disable-next-line: unused-local ---@diagnostic disable-next-line: unused-local
stream = function(err, data, job) stream = function(err, data, job)
@ -390,8 +388,6 @@ local function call_openai_api_stream(question, code_lang, code_content, selecte
} }
end end
-- print("Sending request to " .. (config.get().provider == "azure" and "Azure OpenAI" or "OpenAI") .. " API...")
curl.post(url, { curl.post(url, {
---@diagnostic disable-next-line: unused-local ---@diagnostic disable-next-line: unused-local
stream = function(err, data, job) stream = function(err, data, job)

View File

@ -1,22 +1,21 @@
local api = vim.api local api = vim.api
local Tiktoken = require("avante.tiktoken")
local Sidebar = require("avante.sidebar") local Sidebar = require("avante.sidebar")
local Config = require("avante.config")
local Diff = require("avante.diff")
local AiBot = require("avante.ai_bot")
local Selection = require("avante.selection") local Selection = require("avante.selection")
local Config = require("avante.config")
---@class Avante ---@class Avante
local M = { local M = {
---@type avante.Sidebar[] we use this to track chat command across tabs ---@type avante.Sidebar[] we use this to track chat command across tabs
sidebars = {}, sidebars = {},
---@type avante.Sidebar ---@type avante.Selection[]
current = nil, selections = {},
selection = nil, ---@type {sidebar?: avante.Sidebar, selection?: avante.Selection}
_once = false, current = { sidebar = nil, selection = nil },
} }
M.did_setup = false
local H = {} local H = {}
H.commands = function() H.commands = function()
@ -52,7 +51,7 @@ H.autocmds = function()
local name = "avante.nvim" local name = "avante.nvim"
local load_path = function() local load_path = function()
require("tiktoken_lib").load() require("tiktoken_lib").load()
Tiktoken.setup("gpt-4o") require("avante.tiktoken").setup("gpt-4o")
end end
if LazyConfig.plugins[name] and LazyConfig.plugins[name]._.loaded then if LazyConfig.plugins[name] and LazyConfig.plugins[name]._.loaded then
@ -80,15 +79,24 @@ H.autocmds = function()
callback = function(ev) callback = function(ev)
local tab = tonumber(ev.file) local tab = tonumber(ev.file)
local s = M.sidebars[tab] local s = M.sidebars[tab]
local sl = M.selections[tab]
if s then if s then
s:destroy() s:destroy()
end end
if sl then
sl:delete_autocmds()
end
if tab ~= nil then if tab ~= nil then
M.sidebars[tab] = nil M.sidebars[tab] = nil
end end
end, end,
}) })
vim.schedule(function()
M._init(api.nvim_get_current_tabpage())
M.current.selection:setup_autocmds()
end)
-- automatically setup Avante filetype to markdown -- automatically setup Avante filetype to markdown
vim.treesitter.language.register("markdown", "Avante") vim.treesitter.language.register("markdown", "Avante")
end end
@ -98,24 +106,33 @@ end
function M._get(current) function M._get(current)
local tab = api.nvim_get_current_tabpage() local tab = api.nvim_get_current_tabpage()
local sidebar = M.sidebars[tab] local sidebar = M.sidebars[tab]
local selection = M.selections[tab]
if current ~= false then if current ~= false then
M.current = sidebar M.current.sidebar = sidebar
M.current.selection = selection
end end
return sidebar return sidebar
end end
M.open = function() ---@param id integer
local tab = api.nvim_get_current_tabpage() function M._init(id)
local sidebar = M.sidebars[tab] local sidebar = M.sidebars[id]
local selection = M.selections[id]
if not sidebar then if not sidebar then
sidebar = Sidebar:new(tab) sidebar = Sidebar:new(id)
M.sidebars[tab] = sidebar M.sidebars[id] = sidebar
end
if not selection then
selection = Selection:new(id)
M.selections[id] = selection
end
M.current = { sidebar = sidebar, selection = selection }
return M
end end
M.current = sidebar M.open = function()
M._init(api.nvim_get_current_tabpage())._get(false):open()
return sidebar:open()
end end
M.toggle = function() M.toggle = function()
@ -160,20 +177,19 @@ function M.setup(opts)
---but most of the other functionality will only be called once from lazy.nvim ---but most of the other functionality will only be called once from lazy.nvim
Config.setup(opts) Config.setup(opts)
if M._once then if M.did_setup then
return return
end end
Diff.setup() require("avante.diff").setup()
AiBot.setup() require("avante.ai_bot").setup()
M.selection = Selection:new():setup()
-- setup helpers -- setup helpers
H.autocmds() H.autocmds()
H.commands() H.commands()
H.keymaps() H.keymaps()
M._once = true M.did_setup = true
end end
return M return M

View File

@ -6,13 +6,17 @@ local fn = vim.fn
local NAMESPACE = api.nvim_create_namespace("avante_selection") local NAMESPACE = api.nvim_create_namespace("avante_selection")
local PRIORITY = vim.highlight.priorities.user local PRIORITY = vim.highlight.priorities.user
---@class avante.Selection
local Selection = {} local Selection = {}
function Selection:new() Selection.did_setup = false
---@param id integer the tabpage id retrieved from vim.api.nvim_get_current_tabpage()
function Selection:new(id)
return setmetatable({ return setmetatable({
hints_popup_extmark_id = nil, hints_popup_extmark_id = nil,
edit_popup_renderer = nil, edit_popup_renderer = nil,
augroup = api.nvim_create_augroup("avante_selection", { clear = true }), augroup = api.nvim_create_augroup("avante_selection_" .. id, { clear = true }),
}, { __index = self }) }, { __index = self })
end end
@ -56,8 +60,9 @@ function Selection:close_hints_popup()
end end
end end
function Selection:setup() function Selection:setup_autocmds()
vim.api.nvim_create_autocmd({ "ModeChanged" }, { Selection.did_setup = true
api.nvim_create_autocmd({ "ModeChanged" }, {
group = self.augroup, group = self.augroup,
pattern = { "n:v", "n:V", "n:" }, -- Entering Visual mode from Normal mode pattern = { "n:v", "n:V", "n:" }, -- Entering Visual mode from Normal mode
callback = function() callback = function()
@ -91,6 +96,7 @@ function Selection:delete_autocmds()
vim.api.nvim_del_augroup_by_id(self.augroup) vim.api.nvim_del_augroup_by_id(self.augroup)
end end
self.augroup = nil self.augroup = nil
Selection.did_setup = false
end end
return Selection return Selection

View File

@ -219,28 +219,34 @@ function Sidebar:intialize()
return self return self
end end
function Sidebar:is_focused()
return self.view:is_open() and self.code.win == api.nvim_get_current_win()
end
---@param content string concatenated content of the buffer ---@param content string concatenated content of the buffer
---@param focus? boolean whether to focus the result view ---@param opts? {focus?: boolean, scroll?: boolean, callback?: fun(): nil} whether to focus the result view
function Sidebar:update_content(content, focus, callback) function Sidebar:update_content(content, opts)
focus = focus or false opts = vim.tbl_deep_extend("force", { focus = true, scroll = true, callback = nil }, opts or {})
vim.defer_fn(function() vim.defer_fn(function()
api.nvim_set_option_value("modifiable", true, { buf = self.view.buf }) api.nvim_set_option_value("modifiable", true, { buf = self.view.buf })
api.nvim_buf_set_lines(self.view.buf, 0, -1, false, vim.split(content, "\n")) api.nvim_buf_set_lines(self.view.buf, 0, -1, false, vim.split(content, "\n"))
api.nvim_set_option_value("modifiable", false, { buf = self.view.buf }) api.nvim_set_option_value("modifiable", false, { buf = self.view.buf })
api.nvim_set_option_value("filetype", "Avante", { buf = self.view.buf }) api.nvim_set_option_value("filetype", "Avante", { buf = self.view.buf })
if callback ~= nil then if opts.callback ~= nil then
callback() opts.callback()
end end
if focus then if opts.focus and not self:is_focused() then
xpcall(function() xpcall(function()
--- set cursor to bottom of result view --- set cursor to bottom of result view
api.nvim_set_current_win(self.winid.result) api.nvim_set_current_win(self.winid.result)
end, function(err) end, function(err)
-- XXX: omit error for now, but should fix me why it can't jump here.
return err return err
end) end)
end
if opts.scroll then
xpcall(function() xpcall(function()
api.nvim_win_set_cursor(self.winid.result, { api.nvim_buf_line_count(self.view.buf), 0 }) api.nvim_win_set_cursor(self.winid.result, { api.nvim_buf_line_count(self.bufnr.result), 0 })
end, function(err) end, function(err)
return err return err
end) end)
@ -411,7 +417,7 @@ function Sidebar:update_content_with_history(history)
content = content .. entry.response .. "\n\n" content = content .. entry.response .. "\n\n"
content = content .. "---\n\n" content = content .. "---\n\n"
end end
self:update_content(content, true) self:update_content(content)
end end
local function get_conflict_content(content, snippets) local function get_conflict_content(content, snippets)
@ -582,10 +588,15 @@ function Sidebar:render()
self:update_content_with_history(chat_history) self:update_content_with_history(chat_history)
local function handle_submit() local function handle_submit()
signal.is_loading = true
local state = signal:get_value() local state = signal:get_value()
local user_input = state.text local user_input = state.text
local timestamp = get_timestamp() local timestamp = get_timestamp()
--- 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 })
self:update_content( self:update_content(
"## " "## "
.. timestamp .. timestamp
@ -607,8 +618,6 @@ function Sidebar:render()
local full_response = "" local full_response = ""
signal.is_loading = true
local filetype = api.nvim_get_option_value("filetype", { buf = self.code.buf }) local filetype = api.nvim_get_option_value("filetype", { buf = self.code.buf })
AiBot.call_ai_api_stream( AiBot.call_ai_api_stream(
@ -617,11 +626,9 @@ function Sidebar:render()
content_with_line_numbers, content_with_line_numbers,
selected_code_content_with_line_numbers, selected_code_content_with_line_numbers,
function(chunk) function(chunk)
signal.is_loading = true
full_response = full_response .. chunk full_response = full_response .. chunk
self:update_content( self:update_content("## " .. timestamp .. "\n\n> " .. user_input:gsub("\n", "\n> ") .. "\n\n" .. full_response)
"## " .. timestamp .. "\n\n> " .. user_input:gsub("\n", "\n> ") .. "\n\n" .. full_response,
true
)
vim.schedule(function() vim.schedule(function()
vim.cmd("redraw") vim.cmd("redraw")
end) end)
@ -638,8 +645,7 @@ function Sidebar:render()
.. "\n\n" .. "\n\n"
.. full_response .. full_response
.. "\n\n🚨 Error: " .. "\n\n🚨 Error: "
.. vim.inspect(err), .. vim.inspect(err)
true
) )
return return
end end
@ -653,10 +659,11 @@ function Sidebar:render()
.. "\n\n" .. "\n\n"
.. full_response .. full_response
.. "\n\n🎉🎉🎉 **Generation complete!** Please review the code suggestions above.\n\n\n\n", .. "\n\n🎉🎉🎉 **Generation complete!** Please review the code suggestions above.\n\n\n\n",
true, {
function() callback = function()
api.nvim_exec_autocmds("User", { pattern = VIEW_BUFFER_UPDATED_PATTERN }) api.nvim_exec_autocmds("User", { pattern = VIEW_BUFFER_UPDATED_PATTERN })
end end,
}
) )
-- Save chat history -- Save chat history
@ -676,11 +683,11 @@ function Sidebar:render()
local code_file_fullpath = api.nvim_buf_get_name(self.code.buf) local code_file_fullpath = api.nvim_buf_get_name(self.code.buf)
local code_filename = fn.fnamemodify(code_file_fullpath, ":t") local code_filename = fn.fnamemodify(code_file_fullpath, ":t")
local input_label = string.format(" 🙋 with %s %s (<Tab> switch focus): ", icon, code_filename) local input_label = string.format(" 🙋 with %s %s (<Tab>: switch focus): ", icon, code_filename)
if self.code.selection ~= nil then if self.code.selection ~= nil then
input_label = string.format( input_label = string.format(
" 🙋 with selected code in %s %s(%d:%d) (<Tab> switch focus): ", " 🙋 with %s %s(%d:%d) (<Tab>: switch focus): ",
icon, icon,
code_filename, code_filename,
self.code.selection.range.start.line, self.code.selection.range.start.line,
@ -747,8 +754,7 @@ function Sidebar:render()
right = 1, right = 1,
}, },
}), }),
N.columns( N.gap(1),
{ flex = 0 },
N.text_input({ N.text_input({
id = "input", id = "input",
border_label = { border_label = {
@ -770,15 +776,8 @@ function Sidebar:render()
end end
end, end,
padding = { left = 1, right = 1 }, padding = { left = 1, right = 1 },
}),
N.gap(1),
N.spinner({
is_loading = signal.is_loading,
padding = { top = 1, right = 1 },
hidden = signal.is_loading:negate(),
}) })
) )
)
end end
self.renderer:render(body) self.renderer:render(body)