diff --git a/lua/avante/init.lua b/lua/avante/init.lua index a24ed35..544142b 100644 --- a/lua/avante/init.lua +++ b/lua/avante/init.lua @@ -64,6 +64,10 @@ H.keymaps = function() }) end +H.signs = function() + vim.fn.sign_define("AvanteInputPromptSign", { text = "> " }) +end + H.augroup = api.nvim_create_augroup("avante_autocmds", { clear = true }) H.autocmds = function() @@ -242,6 +246,7 @@ function M.setup(opts) H.autocmds() H.commands() H.keymaps() + H.signs() M.did_setup = true end diff --git a/lua/avante/sidebar.lua b/lua/avante/sidebar.lua index 77d15af..137b8dc 100644 --- a/lua/avante/sidebar.lua +++ b/lua/avante/sidebar.lua @@ -30,13 +30,10 @@ local Sidebar = {} ---@field registered_cmp boolean ---@field augroup integer ---@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 result_container NuiSplit | nil ----@field result FloatingWindow | nil ----@field selected_code_container NuiSplit | nil ----@field selected_code FloatingWindow | nil ----@field input_container NuiSplit | nil ----@field input FloatingWindow | nil +---@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 ---@param id integer the tabpage id retrieved from api.nvim_get_current_tabpage() function Sidebar:new(id) @@ -45,16 +42,12 @@ function Sidebar:new(id) registered_cmp = false, code = { bufnr = 0, winid = 0, selection = nil }, winids = { - result_container = 0, result = 0, - selected_code_container = 0, selected_code = 0, input = 0, }, result = nil, - selected_code_container = nil, selected_code = nil, - input_container = nil, input = nil, }, { __index = self }) end @@ -69,12 +62,9 @@ end function Sidebar:reset() self:delete_autocmds() self.code = { bufnr = 0, winid = 0, selection = nil } - self.winids = { result_container = 0, result = 0, selected_code = 0, input = 0 } - self.result_container = nil + self.winids = { result = 0, selected_code = 0, input = 0 } self.result = nil - self.selected_code_container = nil self.selected_code = nil - self.input_container = nil self.input = nil end @@ -336,79 +326,44 @@ function Sidebar:render_header(winid, bufnr, header_text, hl, reverse_hl) return end - local reversed_hl_off = 0 - if Config.windows.sidebar_header.rounded then - reversed_hl_off = 1 - header_text = "" .. header_text .. "" - else + if not Config.windows.sidebar_header.rounded then header_text = " " .. header_text .. " " end - local width = api.nvim_win_get_width(winid) - local header_text_length = vim.fn.strdisplaywidth(header_text) - local prefix_padding, suffix_padding = 0, 0 + local winbar_text = "%#Normal#" if Config.windows.sidebar_header.align == "center" then - prefix_padding = math.floor((width - header_text_length) / 2) - suffix_padding = width - header_text_length - prefix_padding + winbar_text = winbar_text .. "%=" elseif Config.windows.sidebar_header.align == "right" then - prefix_padding = width - header_text_length - suffix_padding = 0 - elseif Config.windows.sidebar_header.align == "left" then - suffix_padding = width - header_text_length - prefix_padding = 0 + winbar_text = winbar_text .. "%=" end - local prefix_padding_text = string.rep(" ", prefix_padding) - local suffix_padding_text = string.rep(" ", suffix_padding) - Utils.unlock_buf(bufnr) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { prefix_padding_text .. header_text .. suffix_padding_text }) - api.nvim_buf_add_highlight(bufnr, -1, "WinSeparator", 0, 0, #prefix_padding_text - reversed_hl_off) - api.nvim_buf_add_highlight(bufnr, -1, reverse_hl, 0, #prefix_padding_text, #prefix_padding_text + reversed_hl_off) - api.nvim_buf_add_highlight( - bufnr, - -1, - hl, - 0, - #prefix_padding_text + reversed_hl_off, - #prefix_padding_text + #header_text - reversed_hl_off * 3 - ) - api.nvim_buf_add_highlight( - bufnr, - -1, - reverse_hl, - 0, - #prefix_padding_text + #header_text - reversed_hl_off * 3, - #prefix_padding_text + #header_text - reversed_hl_off * 2 - ) - api.nvim_buf_add_highlight(bufnr, -1, "WinSeparator", 0, #prefix_padding_text + #header_text - reversed_hl_off, -1) - Utils.lock_buf(bufnr) + 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 + if Config.windows.sidebar_header.rounded then + winbar_text = winbar_text .. "%#" .. reverse_hl .. "#" + end + winbar_text = winbar_text .. "%#Normal#" + if Config.windows.sidebar_header.align == "center" then + winbar_text = winbar_text .. "%=" + end + api.nvim_set_option_value("winbar", winbar_text, { win = winid }) end -function Sidebar:render_result_container() - if - not self.result_container - or not self.result_container.bufnr - or not api.nvim_buf_is_valid(self.result_container.bufnr) - then +function Sidebar:render_result() + if not self.result or not self.result.bufnr or not api.nvim_buf_is_valid(self.result.bufnr) then return end local header_text = " Avante" - self:render_header( - self.result_container.winid, - self.result_container.bufnr, - header_text, - Highlights.TITLE, - Highlights.REVERSED_TITLE - ) + self:render_header(self.result.winid, self.result.bufnr, header_text, Highlights.TITLE, Highlights.REVERSED_TITLE) end -function Sidebar:render_input_container() - if - not self.input_container - or not self.input_container.bufnr - or not api.nvim_buf_is_valid(self.input_container.bufnr) - then +function Sidebar:render_input() + if not self.input or not self.input.bufnr or not api.nvim_buf_is_valid(self.input.bufnr) then return end @@ -439,20 +394,16 @@ function Sidebar:render_input_container() end self:render_header( - self.input_container.winid, - self.input_container.bufnr, + self.input.winid, + self.input.bufnr, header_text, Highlights.THIRD_TITLE, Highlights.REVERSED_THIRD_TITLE ) end -function Sidebar:render_selected_code_container() - if - not self.selected_code_container - or not self.selected_code_container.bufnr - or not api.nvim_buf_is_valid(self.selected_code_container.bufnr) - then +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 return end @@ -472,8 +423,8 @@ function Sidebar:render_selected_code_container() ) self:render_header( - self.selected_code_container.winid, - self.selected_code_container.bufnr, + self.selected_code.winid, + self.selected_code.bufnr, header_text, Highlights.SUBTITLE, Highlights.REVERSED_SUBTITLE @@ -605,9 +556,9 @@ function Sidebar:on_mount() end, }) - self:render_result_container() - self:render_input_container() - self:render_selected_code_container() + self:render_result() + self:render_input() + self:render_selected_code() self.augroup = api.nvim_create_augroup("avante_" .. self.id .. self.result.winid, { clear = true }) @@ -659,89 +610,6 @@ function Sidebar:on_mount() end, }) - local previous_winid = nil - - api.nvim_create_autocmd("WinLeave", { - group = self.augroup, - callback = function() - previous_winid = api.nvim_get_current_win() - end, - }) - - api.nvim_create_autocmd("WinEnter", { - group = self.augroup, - callback = function() - local current_win_id = api.nvim_get_current_win() - - if not self.result_container or current_win_id ~= self.result_container.winid then - return - end - - if previous_winid == self.result.winid and self.input.winid and api.nvim_win_is_valid(self.input.winid) then - api.nvim_set_current_win(self.input.winid) - return - end - - 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) - return - end - end, - }) - - api.nvim_create_autocmd("WinEnter", { - group = self.augroup, - callback = function() - local current_win_id = api.nvim_get_current_win() - - if not self.input_container or current_win_id ~= self.input_container.winid then - return - end - - if previous_winid == self.input.winid then - if self.selected_code and self.selected_code.winid and api.nvim_win_is_valid(self.selected_code.winid) then - api.nvim_set_current_win(self.selected_code.winid) - return - end - 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) - return - end - end - - 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) - return - end - end, - }) - - api.nvim_create_autocmd("WinEnter", { - group = self.augroup, - callback = function() - local current_win_id = api.nvim_get_current_win() - - if not self.selected_code_container or current_win_id ~= self.selected_code_container.winid then - return - end - - if - previous_winid == self.result.winid - and self.selected_code - and self.selected_code.winid - and api.nvim_win_is_valid(self.selected_code.winid) - then - api.nvim_set_current_win(self.selected_code.winid) - return - end - - 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) - return - end - end, - }) - 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) @@ -778,7 +646,7 @@ function Sidebar:refresh_winids() end local winid = winids[current_idx] if winid and api.nvim_win_is_valid(winid) then - api.nvim_set_current_win(winid) + pcall(api.nvim_set_current_win, winid) end end @@ -814,9 +682,9 @@ function Sidebar:resize() api.nvim_win_set_width(comp.winid, new_layout.width) end end - self:render_result_container() - self:render_input_container() - self:render_selected_code_container() + self:render_result() + self:render_input() + self:render_selected_code() vim.defer_fn(function() vim.cmd("AvanteRefresh") end, 200) @@ -1073,43 +941,31 @@ function Sidebar:create_selected_code() 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({ + self.selected_code = Split({ enter = false, relative = { type = "win", - winid = self.result_container.winid, + winid = self.input.winid, }, buf_options = buf_options, win_options = get_win_options(), - position = "bottom", + position = "top", size = { height = selected_code_size + 3, }, }) - 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() - 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 input_prompt_ns = vim.api.nvim_create_namespace("avante_input_prompt") - if self.input ~= nil then +function Sidebar:create_input() + if self.input then self.input:unmount() end @@ -1247,26 +1103,19 @@ function Sidebar:create_input() 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 - - self.input = self:create_floating_window_for_split({ - split_winid = self.input_container.winid, - buf_opts = { - modifiable = true, + self.input = Split({ + enter = false, + relative = { + type = "win", + winid = self.result.winid, + }, + win_options = vim.tbl_deep_extend("force", get_win_options(), { signcolumn = "yes" }), + position = "bottom", + size = { + height = 8, }, - keep_floating_style = true, }) - self.input:on_mount(function() - api.nvim_win_set_hl_ns(self.input.winid, Highlights.input_ns) - end) - local function on_submit() if not vim.g.avante_login then Utils.warn("Sending message to fast!, API key is not yet set", { title = "Avante" }) @@ -1286,6 +1135,22 @@ function Sidebar:create_input() self.input:mount() + local function place_sign_at_first_line(bufnr) + local group = "avante_input_prompt_group" + + vim.fn.sign_unplace(group, { buffer = bufnr }) + + vim.fn.sign_place(0, group, "AvanteInputPromptSign", bufnr, { lnum = 1 }) + end + + place_sign_at_first_line(self.input.bufnr) + api.nvim_win_set_hl_ns(self.input.winid, Highlights.input_ns) + + if Utils.in_visual_mode() then + -- Exit visual mode + api.nvim_feedkeys(api.nvim_replace_termcodes("<Esc>", true, false, true), "n", true) + end + self.input:map("n", Config.mappings.submit.normal, on_submit) self.input:map("i", Config.mappings.submit.insert, on_submit) @@ -1336,6 +1201,7 @@ function Sidebar:create_input() -- Get the current window size local win_width = api.nvim_win_get_width(self.input.winid) + local buf_height = api.nvim_buf_line_count(self.input.bufnr) local width = #hint_text -- Set the floating window options @@ -1344,8 +1210,8 @@ function Sidebar:create_input() win = self.input.winid, width = width, height = 1, - row = -1, - col = math.max(win_width - width, 0), -- Display in the top right corner + row = buf_height, + col = math.max(win_width - width, 0), -- Display in the bottom right corner style = "minimal", border = "none", focusable = false, @@ -1358,8 +1224,24 @@ function Sidebar:create_input() api.nvim_win_set_hl_ns(hint_window, Highlights.hint_ns) end - self.input:on_mount(show_hint) - self.input:on_unmount(close_hint) + show_hint() + + api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { + 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, + callback = function() + close_hint() + end, + }) -- Show hint in insert mode api.nvim_create_autocmd("ModeChanged", { @@ -1442,28 +1324,20 @@ function Sidebar:render() local sidebar_height = api.nvim_win_get_height(self.code.winid) local selected_code_size = self:get_selected_code_size() - self.result_container = Split({ + self.result = Split({ + enter = false, relative = "editor", position = "right", - buf_options = buf_options, - win_options = get_win_options(), - size = { - width = string.format("%d%%", Config.windows.width), - }, - }) - - self.result_container:mount() - - self.result = self:create_floating_window_for_split({ - split_winid = self.result_container.winid, - buf_opts = { + buf_options = vim.tbl_deep_extend("force", buf_options, { modifiable = false, swapfile = false, buftype = "nofile", bufhidden = "wipe", filetype = "Avante", - }, - float_opts = { + }), + win_options = get_win_options(), + size = { + width = string.format("%d%%", Config.windows.width), height = math.max(1, sidebar_height - selected_code_size - 3 - 8), }, }) @@ -1486,18 +1360,7 @@ function Sidebar:render() self:close() end) - self.input_container = Split({ - enter = false, - relative = { - type = "win", - winid = self.result_container.winid, - }, - buf_options = buf_options, - win_options = get_win_options(), - position = "bottom", - }) - - self.input_container:mount() + self:create_input() self:update_content_with_history(chat_history) @@ -1508,8 +1371,6 @@ function Sidebar:render() end, }) - self:create_input() - self:create_selected_code() self:on_mount()