Maintaining secondary table of window IDs is cumbersome and is prone to getting out of sync with the true state of the sidebar. In preparation for removal of winids table move all containers (sub-windows of the sidebar) into "containers" table. The change is mostly mechanical rename with following exceptions: - Sidebar:reifresh_winids() and other places where the code scanned entire Sidebar object looking for tables with specific fields, such as "winid", or "mount" needed to be adjusted for the new structure - Sidebar:new() and Sidebar:reset() have been adjusted to make better use of the new sub-table.
315 lines
9.9 KiB
Lua
315 lines
9.9 KiB
Lua
local Config = require("avante.config")
|
|
local Utils = require("avante.utils")
|
|
local PromptInput = require("avante.ui.prompt_input")
|
|
|
|
---@class avante.ApiToggle
|
|
---@operator call(): boolean
|
|
---@field debug ToggleBind.wrap
|
|
---@field hint ToggleBind.wrap
|
|
|
|
---@class avante.Api
|
|
---@field toggle avante.ApiToggle
|
|
local M = {}
|
|
|
|
---@param target_provider avante.SelectorProvider
|
|
function M.switch_selector_provider(target_provider)
|
|
require("avante.config").override({
|
|
selector = {
|
|
provider = target_provider,
|
|
},
|
|
})
|
|
end
|
|
|
|
---@param target_provider avante.InputProvider
|
|
function M.switch_input_provider(target_provider)
|
|
require("avante.config").override({
|
|
input = {
|
|
provider = target_provider,
|
|
},
|
|
})
|
|
end
|
|
|
|
---@param target avante.ProviderName
|
|
function M.switch_provider(target) require("avante.providers").refresh(target) end
|
|
|
|
---@param path string
|
|
local function to_windows_path(path)
|
|
local winpath = path:gsub("/", "\\")
|
|
|
|
if winpath:match("^%a:") then winpath = winpath:sub(1, 2):upper() .. winpath:sub(3) end
|
|
|
|
winpath = winpath:gsub("\\$", "")
|
|
|
|
return winpath
|
|
end
|
|
|
|
---@param opts? {source: boolean}
|
|
function M.build(opts)
|
|
opts = opts or { source = true }
|
|
local dirname = Utils.trim(string.sub(debug.getinfo(1).source, 2, #"/init.lua" * -1), { suffix = "/" })
|
|
local git_root = vim.fs.find(".git", { path = dirname, upward = true })[1]
|
|
local build_directory = git_root and vim.fn.fnamemodify(git_root, ":h") or (dirname .. "/../../")
|
|
|
|
if opts.source and not vim.fn.executable("cargo") then
|
|
error("Building avante.nvim requires cargo to be installed.", 2)
|
|
end
|
|
|
|
---@type string[]
|
|
local cmd
|
|
local os_name = Utils.get_os_name()
|
|
|
|
if vim.tbl_contains({ "linux", "darwin" }, os_name) then
|
|
cmd = {
|
|
"sh",
|
|
"-c",
|
|
string.format("make BUILD_FROM_SOURCE=%s -C %s", opts.source == true and "true" or "false", build_directory),
|
|
}
|
|
elseif os_name == "windows" then
|
|
build_directory = to_windows_path(build_directory)
|
|
cmd = {
|
|
"powershell",
|
|
"-ExecutionPolicy",
|
|
"Bypass",
|
|
"-File",
|
|
string.format("%s\\Build.ps1", build_directory),
|
|
"-WorkingDirectory",
|
|
build_directory,
|
|
"-BuildFromSource",
|
|
string.format("%s", opts.source == true and "true" or "false"),
|
|
}
|
|
else
|
|
error("Unsupported operating system: " .. os_name, 2)
|
|
end
|
|
|
|
---@type integer
|
|
local pid
|
|
local exit_code = { 0 }
|
|
|
|
local ok, job_or_err = pcall(vim.system, cmd, { text = true }, function(obj)
|
|
local stderr = obj.stderr and vim.split(obj.stderr, "\n") or {}
|
|
local stdout = obj.stdout and vim.split(obj.stdout, "\n") or {}
|
|
if vim.tbl_contains(exit_code, obj.code) then
|
|
local output = stdout
|
|
if #output == 0 then
|
|
table.insert(output, "")
|
|
Utils.debug("build output:", output)
|
|
else
|
|
Utils.debug("build error:", stderr)
|
|
end
|
|
end
|
|
end)
|
|
if not ok then Utils.error("Failed to build the command: " .. cmd .. "\n" .. job_or_err, { once = true }) end
|
|
pid = job_or_err.pid
|
|
return pid
|
|
end
|
|
|
|
---@class AskOptions
|
|
---@field question? string optional questions
|
|
---@field win? table<string, any> windows options similar to |nvim_open_win()|
|
|
---@field ask? boolean
|
|
---@field floating? boolean whether to open a floating input to enter the question
|
|
---@field new_chat? boolean whether to open a new chat
|
|
---@field without_selection? boolean whether to open a new chat without selection
|
|
|
|
---@param opts? AskOptions
|
|
function M.ask(opts)
|
|
opts = opts or {}
|
|
if type(opts) == "string" then
|
|
Utils.warn("passing 'ask' as string is deprecated, do {question = '...'} instead", { once = true })
|
|
opts = { question = opts }
|
|
end
|
|
|
|
local has_question = opts.question ~= nil and opts.question ~= ""
|
|
local new_chat = opts.new_chat == true
|
|
|
|
if Utils.is_sidebar_buffer(0) and not has_question and not new_chat then
|
|
require("avante").close_sidebar()
|
|
return false
|
|
end
|
|
|
|
opts = vim.tbl_extend("force", { selection = Utils.get_visual_selection_and_range() }, opts)
|
|
|
|
---@param input string | nil
|
|
local function ask(input)
|
|
if input == nil or input == "" then input = opts.question end
|
|
local sidebar = require("avante").get()
|
|
if sidebar and sidebar:is_open() and sidebar.code.bufnr ~= vim.api.nvim_get_current_buf() then
|
|
sidebar:close({ goto_code_win = false })
|
|
end
|
|
require("avante").open_sidebar(opts)
|
|
if new_chat then sidebar:new_chat() end
|
|
if opts.without_selection then
|
|
sidebar.code.selection = nil
|
|
sidebar.file_selector:reset()
|
|
if sidebar.containers.selected_files then sidebar.containers.selected_files:unmount() end
|
|
end
|
|
if input == nil or input == "" then return true end
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "AvanteInputSubmitted", data = { request = input } })
|
|
return true
|
|
end
|
|
|
|
if opts.floating == true or (Config.windows.ask.floating == true and not has_question and opts.floating == nil) then
|
|
local prompt_input = PromptInput:new({
|
|
submit_callback = function(input) ask(input) end,
|
|
close_on_submit = true,
|
|
win_opts = {
|
|
border = Config.windows.ask.border,
|
|
title = { { "Avante Ask", "FloatTitle" } },
|
|
},
|
|
start_insert = Config.windows.ask.start_insert,
|
|
default_value = opts.question,
|
|
})
|
|
prompt_input:open()
|
|
return true
|
|
end
|
|
|
|
return ask()
|
|
end
|
|
|
|
---@param request? string
|
|
---@param line1? integer
|
|
---@param line2? integer
|
|
function M.edit(request, line1, line2)
|
|
local _, selection = require("avante").get()
|
|
if not selection then return end
|
|
selection:create_editing_input(request, line1, line2)
|
|
if request ~= nil and request ~= "" then
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "AvanteEditSubmitted", data = { request = request } })
|
|
end
|
|
end
|
|
|
|
---@return avante.Suggestion | nil
|
|
function M.get_suggestion()
|
|
local _, _, suggestion = require("avante").get()
|
|
return suggestion
|
|
end
|
|
|
|
---@param opts? AskOptions
|
|
function M.refresh(opts)
|
|
opts = opts or {}
|
|
local sidebar = require("avante").get()
|
|
if not sidebar then return end
|
|
if not sidebar:is_open() then return end
|
|
local curbuf = vim.api.nvim_get_current_buf()
|
|
|
|
local focused = sidebar.containers.result.bufnr == curbuf or sidebar.containers.input.bufnr == curbuf
|
|
if focused or not sidebar:is_open() then return end
|
|
local listed = vim.api.nvim_get_option_value("buflisted", { buf = curbuf })
|
|
|
|
if Utils.is_sidebar_buffer(curbuf) or not listed then return end
|
|
|
|
local curwin = vim.api.nvim_get_current_win()
|
|
|
|
sidebar:close()
|
|
sidebar.code.winid = curwin
|
|
sidebar.code.bufnr = curbuf
|
|
sidebar:render(opts)
|
|
end
|
|
|
|
---@param opts? AskOptions
|
|
function M.focus(opts)
|
|
opts = opts or {}
|
|
local sidebar = require("avante").get()
|
|
if not sidebar then return end
|
|
|
|
local curbuf = vim.api.nvim_get_current_buf()
|
|
local curwin = vim.api.nvim_get_current_win()
|
|
|
|
if sidebar:is_open() then
|
|
if curbuf == sidebar.containers.input.bufnr then
|
|
if sidebar.code.winid and sidebar.code.winid ~= curwin then vim.api.nvim_set_current_win(sidebar.code.winid) end
|
|
elseif curbuf == sidebar.containers.result.bufnr then
|
|
if sidebar.code.winid and sidebar.code.winid ~= curwin then vim.api.nvim_set_current_win(sidebar.code.winid) end
|
|
else
|
|
if sidebar.containers.input.winid and sidebar.containers.input.winid ~= curwin then
|
|
vim.api.nvim_set_current_win(sidebar.containers.input.winid)
|
|
end
|
|
end
|
|
else
|
|
if sidebar.code.winid then vim.api.nvim_set_current_win(sidebar.code.winid) end
|
|
---@cast opts SidebarOpenOptions
|
|
sidebar:open(opts)
|
|
if sidebar.containers.input.winid then vim.api.nvim_set_current_win(sidebar.containers.input.winid) end
|
|
end
|
|
end
|
|
|
|
function M.select_model() require("avante.model_selector").open() end
|
|
|
|
function M.select_history()
|
|
local buf = vim.api.nvim_get_current_buf()
|
|
require("avante.history_selector").open(buf, function(filename)
|
|
vim.api.nvim_buf_call(buf, function()
|
|
if not require("avante").is_sidebar_open() then require("avante").open_sidebar({}) end
|
|
local Path = require("avante.path")
|
|
Path.history.save_latest_filename(buf, filename)
|
|
local sidebar = require("avante").get()
|
|
sidebar:update_content_with_history()
|
|
sidebar:create_todos_container()
|
|
sidebar:initialize_token_count()
|
|
vim.schedule(function() sidebar:focus_input() end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function M.add_buffer_files()
|
|
local sidebar = require("avante").get()
|
|
if not sidebar then
|
|
require("avante.api").ask()
|
|
sidebar = require("avante").get()
|
|
end
|
|
if not sidebar:is_open() then sidebar:open({}) end
|
|
sidebar.file_selector:add_buffer_files()
|
|
end
|
|
|
|
function M.add_selected_file(filepath)
|
|
local rel_path = Utils.uniform_path(filepath)
|
|
|
|
local sidebar = require("avante").get()
|
|
if not sidebar then
|
|
require("avante.api").ask()
|
|
sidebar = require("avante").get()
|
|
end
|
|
if not sidebar:is_open() then sidebar:open({}) end
|
|
sidebar.file_selector:add_selected_file(rel_path)
|
|
end
|
|
|
|
function M.remove_selected_file(filepath)
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local stat = vim.loop.fs_stat(filepath)
|
|
local files
|
|
if stat and stat.type == "directory" then
|
|
files = Utils.scan_directory({ directory = filepath, add_dirs = true })
|
|
else
|
|
files = { filepath }
|
|
end
|
|
|
|
local sidebar = require("avante").get()
|
|
if not sidebar then
|
|
require("avante.api").ask()
|
|
sidebar = require("avante").get()
|
|
end
|
|
if not sidebar:is_open() then sidebar:open({}) end
|
|
|
|
for _, file in ipairs(files) do
|
|
local rel_path = Utils.uniform_path(file)
|
|
sidebar.file_selector:remove_selected_file(rel_path)
|
|
end
|
|
end
|
|
|
|
function M.stop() require("avante.llm").cancel_inflight_request() end
|
|
|
|
return setmetatable(M, {
|
|
__index = function(t, k)
|
|
local module = require("avante")
|
|
---@class AvailableApi: ApiCaller
|
|
---@field api? boolean
|
|
local has = module[k]
|
|
if type(has) ~= "table" or not has.api then
|
|
Utils.warn(k .. " is not a valid avante's API method", { once = true })
|
|
return
|
|
end
|
|
t[k] = has
|
|
return t[k]
|
|
end,
|
|
}) --[[@as avante.Api]]
|