Christopher Brewin 78dd9b0a6d
feat(context): add a ui for selecting and adding files to the sidebar as context (#912)
* feat(sidebar): supports select files

chore (context) update add type annotations to context functions

chore (sidebar) remove unused notify function call

refactor (sidebar) remove setting search file to file path

chore (sidebar) remove nvim_notify debugging api call

* feat (files) allow selecting a file by string via cmp suggestion menu

* chore (context) refactor to allow context using @file with a context view

* refactor (context) refactor seletected file types as an array of path and content

* refactor (config) remove unused configuration options

* refactor (sidebar) remove unused unbild key

* refactor (context) remove unused imports

* refactor (mentions) update mentions to support items with callback functions and removal of the underlying selection.

* fix (sidebar) add file context as a window that is visitable via the tab key

* refactor (file_content) remove file content as an input to llm

* feat (sidebar) support suggesting and applying code in all languages that are in the context

* feat (sidebar) configurable mapping for removing a file from the context.

* feat (context_view) configure hints for the context view for adding and deleting a file.

* feat (context) add hints for the context view.

* fix (sidebar) type when scrolling the results buffer.

* refactor (selected files) refactor llm stream to accept an array of selected file metadata

* refactor: context => selected_files

---------

Co-authored-by: yetone <yetoneful@gmail.com>
2024-12-12 01:29:10 +08:00

218 lines
6.9 KiB
Lua

local Config = require("avante.config")
local Utils = require("avante.utils")
local PromptInput = require("avante.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
M.switch_provider = function(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}
M.build = function(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
---@param opts? AskOptions
M.ask = function(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 ~= ""
if Utils.is_sidebar_buffer(0) and not has_question then
require("avante").close_sidebar()
return false
end
opts = vim.tbl_extend("force", { selection = Utils.get_visual_selection_and_range() }, opts)
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 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.options.windows.ask.border,
title = { { "ask", "FloatTitle" } },
},
start_insert = Config.options.windows.ask.start_insert,
})
prompt_input:open()
return true
end
return ask()
end
---@param question? string
M.edit = function(question)
local _, selection = require("avante").get()
if not selection then return end
selection:create_editing_input()
if question ~= nil or question ~= "" then
vim.api.nvim_exec_autocmds("User", { pattern = "AvanteEditSubmitted", data = { request = question } })
end
end
---@return avante.Suggestion | nil
M.get_suggestion = function()
local _, _, suggestion = require("avante").get()
return suggestion
end
---@param opts? AskOptions
M.refresh = function(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.result_container.bufnr == curbuf or sidebar.input_container.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
M.focus = function(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.input_container.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.result_container.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.input_container.winid and sidebar.input_container.winid ~= curwin then
vim.api.nvim_set_current_win(sidebar.input_container.winid)
end
end
else
if sidebar.code.winid then vim.api.nvim_set_current_win(sidebar.code.winid) end
sidebar:open(opts)
if sidebar.input_container.winid then vim.api.nvim_set_current_win(sidebar.input_container.winid) end
end
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]]