Aaron Pham 4ad913435c
feat(templates): avanterules filetype support (closes #254) (#466)
Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
2024-09-03 04:09:13 -04:00

395 lines
10 KiB
Lua

local api = vim.api
local Utils = require("avante.utils")
local Sidebar = require("avante.sidebar")
local Selection = require("avante.selection")
local Suggestion = require("avante.suggestion")
local Config = require("avante.config")
local Diff = require("avante.diff")
---@class Avante
local M = {
---@type avante.Sidebar[] we use this to track chat command across tabs
sidebars = {},
---@type avante.Selection[]
selections = {},
---@type avante.Suggestion[]
suggestions = {},
---@type {sidebar?: avante.Sidebar, selection?: avante.Selection, suggestion?: avante.Suggestion}
current = { sidebar = nil, selection = nil, suggestion = nil },
}
M.did_setup = false
local H = {}
H.commands = function()
---@param n string
---@param c vim.api.keyset.user_command.callback
---@param o vim.api.keyset.user_command.opts
local cmd = function(n, c, o)
o = vim.tbl_extend("force", { nargs = 0 }, o or {})
api.nvim_create_user_command("Avante" .. n, c, o)
end
cmd("Ask", function(opts)
require("avante.api").ask(vim.trim(opts.args))
end, { desc = "avante: ask AI for code suggestions", nargs = "*" })
cmd("Toggle", function()
M.toggle()
end, { desc = "avante: toggle AI panel" })
cmd("Edit", function(opts)
require("avante.api").edit(vim.trim(opts.args))
end, { desc = "avante: edit selected block", nargs = "*" })
cmd("Refresh", function()
require("avante.api").refresh()
end, { desc = "avante: refresh windows" })
cmd("Build", function()
M.build()
end, { desc = "avante: build dependencies" })
cmd("SwitchProvider", function(opts)
require("avante.api").switch_provider(vim.trim(opts.args or ""))
end, {
nargs = 1,
desc = "avante: switch provider",
complete = function(_, line, _)
local prefix = line:match("AvanteSwitchProvider%s*(.*)$") or ""
---@param key string
return vim.tbl_filter(function(key)
return key:find(prefix, 1, true) == 1
end, Config.providers)
end,
})
end
H.keymaps = function()
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteAsk)", function()
require("avante.api").ask()
end, { noremap = true })
vim.keymap.set("v", "<Plug>(AvanteEdit)", function()
require("avante.api").edit()
end, { noremap = true })
vim.keymap.set("n", "<Plug>(AvanteRefresh)", function()
require("avante.api").refresh()
end, { noremap = true })
vim.keymap.set("n", "<Plug>(AvanteToggle)", function()
M.toggle()
end, { noremap = true })
vim.keymap.set("n", "<Plug>(AvanteToggleDebug)", function()
M.toggle.debug()
end)
vim.keymap.set("n", "<Plug>(AvanteToggleHint)", function()
M.toggle.hint()
end)
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteConflictOurs)", function()
Diff.choose("ours")
end)
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteConflictBoth)", function()
Diff.choose("both")
end)
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteConflictTheirs)", function()
Diff.choose("theirs")
end)
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteConflictAllTheirs)", function()
Diff.choose("all_theirs")
end)
vim.keymap.set({ "n", "v" }, "<Plug>(AvanteConflictCursor)", function()
Diff.choose("cursor")
end)
vim.keymap.set("n", "<Plug>(AvanteConflictNextConflict)", function()
Diff.find_next("ours")
end)
vim.keymap.set("n", "<Plug>(AvanteConflictPrevConflict)", function()
Diff.find_prev("ours")
end)
if Config.behaviour.auto_set_keymaps then
Utils.safe_keymap_set({ "n", "v" }, Config.mappings.ask, function()
require("avante.api").ask()
end, { desc = "avante: ask" })
Utils.safe_keymap_set("v", Config.mappings.edit, function()
require("avante.api").edit()
end, { desc = "avante: edit" })
Utils.safe_keymap_set("n", Config.mappings.refresh, function()
require("avante.api").refresh()
end, { desc = "avante: refresh" })
Utils.safe_keymap_set("n", Config.mappings.toggle.default, function()
M.toggle()
end, { desc = "avante: toggle" })
Utils.safe_keymap_set("n", Config.mappings.toggle.debug, function()
M.toggle.debug()
end, { desc = "avante: toggle debug" })
Utils.safe_keymap_set("n", Config.mappings.toggle.hint, function()
M.toggle.hint()
end, { desc = "avante: toggle hint" })
end
end
---@class ApiCaller
---@operator call(...): any
H.api = function(fun)
return setmetatable({ api = true }, {
__call = function(...)
return fun(...)
end,
}) --[[@as ApiCaller]]
end
H.signs = function()
vim.fn.sign_define("AvanteInputPromptSign", { text = Config.windows.input.prefix })
end
H.augroup = api.nvim_create_augroup("avante_autocmds", { clear = true })
H.autocmds = function()
local ok, LazyConfig = pcall(require, "lazy.core.config")
if ok then
local name = "avante.nvim"
local load_path = function()
require("avante_lib").load()
end
if LazyConfig.plugins[name] and LazyConfig.plugins[name]._.loaded then
vim.schedule(load_path)
else
api.nvim_create_autocmd("User", {
pattern = "LazyLoad",
callback = function(event)
if event.data == name then
load_path()
return true
end
end,
})
end
api.nvim_create_autocmd("User", {
pattern = "VeryLazy",
callback = load_path,
})
end
api.nvim_create_autocmd("TabEnter", {
group = H.augroup,
pattern = "*",
once = true,
callback = function(ev)
local tab = tonumber(ev.file)
M._init(tab or api.nvim_get_current_tabpage())
if Config.hints.enabled and not M.current.selection.did_setup then
M.current.selection:setup_autocmds()
end
end,
})
api.nvim_create_autocmd("VimResized", {
group = H.augroup,
callback = function()
local sidebar = M.get()
if not sidebar then
return
end
if not sidebar:is_open() then
return
end
sidebar:resize()
end,
})
api.nvim_create_autocmd("TabClosed", {
group = H.augroup,
pattern = "*",
callback = function(ev)
local tab = tonumber(ev.file)
local s = M.sidebars[tab]
local sl = M.selections[tab]
if s then
s:reset()
end
if sl then
sl:delete_autocmds()
end
if tab ~= nil then
M.sidebars[tab] = nil
end
end,
})
vim.schedule(function()
M._init(api.nvim_get_current_tabpage())
if Config.hints.enabled then
M.current.selection:setup_autocmds()
end
end)
api.nvim_create_autocmd("ColorSchemePre", {
group = H.augroup,
callback = function()
require("avante.highlights").setup()
end,
})
-- automatically setup Avante filetype to markdown
vim.treesitter.language.register("markdown", "Avante")
vim.filetype.add({
extension = {
["avanterules"] = "jinja",
},
pattern = {
["%.avanterules%.[%w_.-]+"] = "jinja",
},
})
end
---@param current boolean? false to disable setting current, otherwise use this to track across tabs.
---@return avante.Sidebar, avante.Selection, avante.Suggestion
function M.get(current)
local tab = api.nvim_get_current_tabpage()
local sidebar = M.sidebars[tab]
local selection = M.selections[tab]
local suggestion = M.suggestions[tab]
if current ~= false then
M.current.sidebar = sidebar
M.current.selection = selection
M.current.suggestion = suggestion
end
return sidebar, selection, suggestion
end
---@param id integer
function M._init(id)
local sidebar = M.sidebars[id]
local selection = M.selections[id]
local suggestion = M.suggestions[id]
if not sidebar then
sidebar = Sidebar:new(id)
M.sidebars[id] = sidebar
end
if not selection then
selection = Selection:new(id)
M.selections[id] = selection
end
if not suggestion then
suggestion = Suggestion:new(id)
M.suggestions[id] = suggestion
end
M.current = { sidebar = sidebar, selection = selection, suggestion = suggestion }
return M
end
M.toggle = { api = true }
M.toggle.debug = H.api(Utils.toggle_wrap({
name = "debug",
get = function()
return Config.debug
end,
set = function(state)
Config.override({ debug = state })
end,
}))
M.toggle.hint = H.api(Utils.toggle_wrap({
name = "hint",
get = function()
return Config.hints.enabled
end,
set = function(state)
Config.override({ hints = { enabled = state } })
end,
}))
setmetatable(M.toggle, {
__index = M.toggle,
__call = function()
local sidebar = M.get()
if not sidebar then
M._init(api.nvim_get_current_tabpage())
M.current.sidebar:open()
return true
end
return sidebar:toggle()
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
M.build = H.api(function()
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 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 -C %s", 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,
}
else
error("Unsupported operating system: " .. os_name, 2)
end
local job = vim.system(cmd, { text = true }):wait()
return vim.tbl_contains({ 0 }, job.code) and true or false
end)
---@param opts? avante.Config
function M.setup(opts)
---PERF: we can still allow running require("avante").setup() multiple times to override config if users wish to
---but most of the other functionality will only be called once from lazy.nvim
Config.setup(opts)
if M.did_setup then
return
end
require("avante.path").setup()
require("avante.highlights").setup()
require("avante.diff").setup()
require("avante.providers").setup()
require("avante.clipboard").setup()
-- setup helpers
H.autocmds()
H.commands()
H.keymaps()
H.signs()
M.did_setup = true
end
return M