2024-08-25 03:12:53 -04:00
|
|
|
local fn, api = vim.fn, vim.api
|
2024-09-03 05:12:07 -04:00
|
|
|
local Utils = require("avante.utils")
|
|
|
|
local Path = require("plenary.path")
|
|
|
|
local Scan = require("plenary.scandir")
|
|
|
|
local Config = require("avante.config")
|
2024-08-25 03:12:53 -04:00
|
|
|
|
2024-08-31 13:39:50 -04:00
|
|
|
---@class avante.Path
|
|
|
|
---@field history_path Path
|
|
|
|
---@field cache_path Path
|
|
|
|
local P = {}
|
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
-- Helpers
|
2024-08-25 03:12:53 -04:00
|
|
|
local H = {}
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
-- Get a chat history file name given a buffer
|
2024-08-25 03:12:53 -04:00
|
|
|
---@param bufnr integer
|
|
|
|
---@return string
|
|
|
|
H.filename = function(bufnr)
|
|
|
|
local code_buf_name = api.nvim_buf_get_name(bufnr)
|
|
|
|
-- Replace path separators with double underscores
|
|
|
|
local path_with_separators = fn.substitute(code_buf_name, "/", "__", "g")
|
|
|
|
-- Replace other non-alphanumeric characters with single underscores
|
|
|
|
return fn.substitute(path_with_separators, "[^A-Za-z0-9._]", "_", "g") .. ".json"
|
|
|
|
end
|
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
-- Given a mode, return the file name for the custom prompt.
|
|
|
|
---@param mode LlmMode
|
2024-09-03 04:19:54 -04:00
|
|
|
H.get_mode_file = function(mode) return string.format("custom.%s.avanterules", mode) end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
-- History path
|
2024-09-23 18:52:26 +08:00
|
|
|
local History = {}
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
-- Returns the Path to the chat history file for the given buffer.
|
2024-08-25 03:12:53 -04:00
|
|
|
---@param bufnr integer
|
|
|
|
---@return Path
|
2024-09-23 18:52:26 +08:00
|
|
|
History.get = function(bufnr) return Path:new(Config.history.storage_path):joinpath(H.filename(bufnr)) end
|
2024-08-25 03:12:53 -04:00
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
-- Loads the chat history for the given buffer.
|
2024-08-25 03:12:53 -04:00
|
|
|
---@param bufnr integer
|
2024-09-23 18:52:26 +08:00
|
|
|
History.load = function(bufnr)
|
|
|
|
local history_file = History.get(bufnr)
|
2024-08-25 03:12:53 -04:00
|
|
|
if history_file:exists() then
|
|
|
|
local content = history_file:read()
|
|
|
|
return content ~= nil and vim.json.decode(content) or {}
|
|
|
|
end
|
|
|
|
return {}
|
|
|
|
end
|
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
-- Saves the chat history for the given buffer.
|
2024-08-25 03:12:53 -04:00
|
|
|
---@param bufnr integer
|
|
|
|
---@param history table
|
2024-10-10 14:04:37 +02:00
|
|
|
History.save = vim.schedule_wrap(function(bufnr, history)
|
2024-09-23 18:52:26 +08:00
|
|
|
local history_file = History.get(bufnr)
|
2024-08-25 03:12:53 -04:00
|
|
|
history_file:write(vim.json.encode(history), "w")
|
2024-10-10 14:04:37 +02:00
|
|
|
end)
|
2024-08-25 03:12:53 -04:00
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
P.history = History
|
2024-08-31 13:39:50 -04:00
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
-- Prompt path
|
2024-09-23 18:52:26 +08:00
|
|
|
local Prompt = {}
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
---@class AvanteTemplates
|
|
|
|
---@field initialize fun(directory: string): nil
|
|
|
|
---@field render fun(template: string, context: TemplateOptions): string
|
|
|
|
local templates = nil
|
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.templates = { planning = nil, editing = nil, suggesting = nil }
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
-- Creates a directory in the cache path for the given buffer and copies the custom prompts to it.
|
|
|
|
-- We need to do this beacuse the prompt template engine requires a given directory to load all required files.
|
|
|
|
-- PERF: Hmm instead of copy to cache, we can also load in globals context, but it requires some work on bindings. (eh maybe?)
|
|
|
|
---@param bufnr number
|
|
|
|
---@return string the resulted cache_directory to be loaded with avante_templates
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.get = function(bufnr)
|
2024-09-03 04:19:54 -04:00
|
|
|
if not P.available() then error("Make sure to build avante (missing avante_templates)", 2) end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
-- get root directory of given bufnr
|
2024-09-03 05:12:07 -04:00
|
|
|
local directory = Path:new(Utils.root.get({ buf = bufnr }))
|
2024-09-04 18:34:53 +08:00
|
|
|
if Utils.get_os_name() == "windows" then directory = Path:new(directory:absolute():gsub("^%a:", "")[1]) end
|
2024-09-03 04:09:13 -04:00
|
|
|
---@cast directory Path
|
|
|
|
---@type Path
|
|
|
|
local cache_prompt_dir = P.cache_path:joinpath(directory)
|
2024-09-03 05:12:07 -04:00
|
|
|
if not cache_prompt_dir:exists() then cache_prompt_dir:mkdir({ parents = true }) end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
local scanner = Scan.scan_dir(directory:absolute(), { depth = 1, add_dirs = true })
|
|
|
|
for _, entry in ipairs(scanner) do
|
|
|
|
local file = Path:new(entry)
|
2024-11-04 13:28:29 -08:00
|
|
|
if file:is_file() then
|
|
|
|
if entry:find("planning") and Prompt.templates.planning == nil then
|
|
|
|
Prompt.templates.planning = file:read()
|
|
|
|
elseif entry:find("editing") and Prompt.templates.editing == nil then
|
|
|
|
Prompt.templates.editing = file:read()
|
|
|
|
elseif entry:find("suggesting") and Prompt.templates.suggesting == nil then
|
|
|
|
Prompt.templates.suggesting = file:read()
|
|
|
|
end
|
2024-09-03 04:09:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Path:new(debug.getinfo(1).source:match("@?(.*/)"):gsub("/lua/avante/path.lua$", "") .. "templates")
|
2024-09-03 05:12:07 -04:00
|
|
|
:copy({ destination = cache_prompt_dir, recursive = true })
|
2024-09-03 04:19:54 -04:00
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
vim.iter(Prompt.templates):filter(function(_, v) return v ~= nil end):each(function(k, v)
|
2024-09-03 04:19:54 -04:00
|
|
|
local f = cache_prompt_dir:joinpath(H.get_mode_file(k))
|
|
|
|
f:write(v, "w")
|
|
|
|
end)
|
2024-09-03 04:09:13 -04:00
|
|
|
|
|
|
|
return cache_prompt_dir:absolute()
|
|
|
|
end
|
|
|
|
|
|
|
|
---@param mode LlmMode
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.get_file = function(mode)
|
|
|
|
if Prompt.templates[mode] ~= nil then return H.get_mode_file(mode) end
|
2024-09-03 04:09:13 -04:00
|
|
|
return string.format("%s.avanterules", mode)
|
|
|
|
end
|
|
|
|
|
2024-09-04 03:19:33 -04:00
|
|
|
---@param path string
|
|
|
|
---@param opts TemplateOptions
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.render_file = function(path, opts) return templates.render(path, opts) end
|
2024-09-04 03:19:33 -04:00
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
---@param mode LlmMode
|
|
|
|
---@param opts TemplateOptions
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.render_mode = function(mode, opts) return templates.render(Prompt.get_file(mode), opts) end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
Prompt.initialize = function(directory) templates.initialize(directory) end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
P.prompts = Prompt
|
|
|
|
|
|
|
|
local RepoMap = {}
|
|
|
|
|
|
|
|
-- Get a chat history file name given a buffer
|
|
|
|
---@param project_root string
|
|
|
|
---@param ext string
|
|
|
|
---@return string
|
|
|
|
RepoMap.filename = function(project_root, ext)
|
|
|
|
-- Replace path separators with double underscores
|
|
|
|
local path_with_separators = fn.substitute(project_root, "/", "__", "g")
|
|
|
|
-- Replace other non-alphanumeric characters with single underscores
|
|
|
|
return fn.substitute(path_with_separators, "[^A-Za-z0-9._]", "_", "g") .. "." .. ext .. ".repo_map.json"
|
|
|
|
end
|
|
|
|
|
|
|
|
RepoMap.get = function(project_root, ext) return Path:new(P.data_path):joinpath(RepoMap.filename(project_root, ext)) end
|
|
|
|
|
|
|
|
RepoMap.save = function(project_root, ext, data)
|
|
|
|
local file = RepoMap.get(project_root, ext)
|
|
|
|
file:write(vim.json.encode(data), "w")
|
|
|
|
end
|
|
|
|
|
|
|
|
RepoMap.load = function(project_root, ext)
|
|
|
|
local file = RepoMap.get(project_root, ext)
|
|
|
|
if file:exists() then
|
|
|
|
local content = file:read()
|
|
|
|
return content ~= nil and vim.json.decode(content) or {}
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
P.repo_map = RepoMap
|
2024-09-03 04:09:13 -04:00
|
|
|
|
2024-08-31 13:39:50 -04:00
|
|
|
P.setup = function()
|
|
|
|
local history_path = Path:new(Config.history.storage_path)
|
2024-09-03 05:12:07 -04:00
|
|
|
if not history_path:exists() then history_path:mkdir({ parents = true }) end
|
2024-08-31 13:39:50 -04:00
|
|
|
P.history_path = history_path
|
|
|
|
|
2024-09-03 05:12:07 -04:00
|
|
|
local cache_path = Path:new(vim.fn.stdpath("cache") .. "/avante")
|
|
|
|
if not cache_path:exists() then cache_path:mkdir({ parents = true }) end
|
2024-08-31 13:39:50 -04:00
|
|
|
P.cache_path = cache_path
|
2024-09-03 04:09:13 -04:00
|
|
|
|
2024-09-23 18:52:26 +08:00
|
|
|
local data_path = Path:new(vim.fn.stdpath("data") .. "/avante")
|
|
|
|
if not data_path:exists() then data_path:mkdir({ parents = true }) end
|
|
|
|
P.data_path = data_path
|
|
|
|
|
2024-09-03 04:09:13 -04:00
|
|
|
vim.defer_fn(function()
|
|
|
|
local ok, module = pcall(require, "avante_templates")
|
|
|
|
---@cast module AvanteTemplates
|
|
|
|
---@cast ok boolean
|
2024-09-03 04:19:54 -04:00
|
|
|
if not ok then return end
|
|
|
|
if templates == nil then templates = module end
|
2024-09-03 04:09:13 -04:00
|
|
|
end, 1000)
|
|
|
|
end
|
|
|
|
|
2024-09-03 04:19:54 -04:00
|
|
|
P.available = function() return templates ~= nil end
|
2024-09-03 04:09:13 -04:00
|
|
|
|
2024-09-04 19:38:59 +08:00
|
|
|
P.clear = function()
|
|
|
|
P.cache_path:rm({ recursive = true })
|
|
|
|
P.history_path:rm({ recursive = true })
|
2024-10-05 11:49:15 +02:00
|
|
|
|
|
|
|
if not P.cache_path:exists() then P.cache_path:mkdir({ parents = true }) end
|
|
|
|
if not P.history_path:exists() then P.history_path:mkdir({ parents = true }) end
|
2024-09-04 19:38:59 +08:00
|
|
|
end
|
2024-08-25 03:12:53 -04:00
|
|
|
|
2024-08-31 13:39:50 -04:00
|
|
|
return P
|