From 4329ed79f404f4f4c24e2dac3d33d263e970a9c1 Mon Sep 17 00:00:00 2001 From: doodleEsc Date: Wed, 2 Jul 2025 14:43:17 +0800 Subject: [PATCH] feat: enable override prompt templates directory (#2387) --- README.md | 28 +++++++++++++++++++++++-- README_zh.md | 26 ++++++++++++++++++++++- lua/avante/config.lua | 4 +++- lua/avante/path.lua | 48 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 334c6a0..98ed1ba 100644 --- a/README.md +++ b/README.md @@ -1116,7 +1116,9 @@ By default, `avante.nvim` provides three different modes to interact with: `plan - `suggesting`: Used with `require("avante").get_suggestion():suggest()` on Tab flow. - `cursor-planning`: Used with `require("avante").toggle()` on Tab flow, but only when cursor planning mode is enabled. -Users can customize the system prompts via `Config.system_prompt`. We recommend calling this in a custom Autocmds depending on your need: +Users can customize the system prompts via `Config.system_prompt` or `Config.override_prompt_dir`. + +`Config.system_prompt` allows you to set a global system prompt. We recommend calling this in a custom Autocmds depending on your need: ```lua vim.api.nvim_create_autocmd("User", { @@ -1127,7 +1129,29 @@ vim.api.nvim_create_autocmd("User", { vim.keymap.set("n", "am", function() vim.api.nvim_exec_autocmds("User", { pattern = "ToggleMyPrompt" }) end, { desc = "avante: toggle my prompt" }) ``` -If one wish to custom prompts for each mode, `avante.nvim` will check for project root based on the given buffer whether it contains +`Config.override_prompt_dir` allows you to specify a directory containing your own custom prompt templates, which will override the built-in templates. This is useful if you want to maintain a set of custom prompts outside of your Neovim configuration. It can be a string representing the directory path, or a function that returns a string representing the directory path. + +```lua +-- Example: Override with prompts from a specific directory +require("avante").setup({ + override_prompt_dir = vim.fn.expand("~/.config/nvim/avante_prompts"), +}) + +-- Example: Override with prompts from a function (dynamic directory) +require("avante").setup({ + override_prompt_dir = function() + -- Your logic to determine the prompt directory + return vim.fn.expand("~/.config/nvim/my_dynamic_prompts") + end, +}) +``` + +> [!WARNING] +> +> If you customize `base.avanterules`, please ensure that `{% block custom_prompt %}{% endblock %}` and `{% block extra_prompt %}{% endblock %}` exist, otherwise the entire plugin may become unusable. +> If you are unsure about the specific reasons or what you are doing, please do not override the built-in prompts. The built-in prompts work very well. + +If you wish to custom prompts for each mode, `avante.nvim` will check for project root based on the given buffer whether it contains the following patterns: `*.{mode}.avanterules`. The rules for root hierarchy: diff --git a/README_zh.md b/README_zh.md index e63a451..3295d08 100644 --- a/README_zh.md +++ b/README_zh.md @@ -940,7 +940,9 @@ Avante 利用 [Claude 文本编辑器工具](https://docs.anthropic.com/en/docs/ - `suggesting`:与 Tab 流上的 `require("avante").get_suggestion():suggest()` 一起使用。 - `cursor-planning`:与 Tab 流上的 `require("avante").toggle()` 一起使用,但仅在启用 cursor 规划模式时。 -用户可以通过 `Config.system_prompt` 自定义系统提示。我们建议根据您的需要在自定义 Autocmds 中调用此方法: +用户可以通过 `Config.system_prompt` 或 `Config.override_prompt_dir` 自定义系统提示。 + +`Config.system_prompt` 允许您设置全局系统提示。我们建议根据您的需要在自定义 Autocmds 中调用此方法: ```lua vim.api.nvim_create_autocmd("User", { @@ -951,6 +953,28 @@ vim.api.nvim_create_autocmd("User", { vim.keymap.set("n", "am", function() vim.api.nvim_exec_autocmds("User", { pattern = "ToggleMyPrompt" }) end, { desc = "avante: toggle my prompt" }) ``` +`Config.override_prompt_dir` 允许您指定一个目录,其中包含您自己的自定义提示模板,这将覆盖内置模板。如果您想在 Neovim 配置之外维护一组自定义提示,这将非常有用。它可以是一个表示目录路径的字符串,也可以是一个返回表示目录路径的字符串的函数。 + +```lua +-- 示例:使用特定目录中的提示进行覆盖 +require("avante").setup({ + override_prompt_dir = vim.fn.expand("~/.config/nvim/avante_prompts"), +}) + +-- 示例:使用函数(动态目录)中的提示进行覆盖 +require("avante").setup({ + override_prompt_dir = function() + -- 确定提示目录的逻辑 + return vim.fn.expand("~/.config/nvim/my_dynamic_prompts") + end, +}) +``` + +> [!WARNING] +> +> 如果您自定 `base.avanterules`,请一定要确保 `{% block custom_prompt %}{% endblock %}` 和 `{% block extra_prompt %}{% endblock %}` 存在,否则可能会导致整个插件无法使用。 +> 如果您不清楚具体原因或者您不知道自己在干什么,请不要覆盖内置 prompt。内置 prompt 工作得非常好。 + 如果希望为每种模式自定义提示,`avante.nvim` 将根据给定缓冲区的项目根目录检查是否包含以下模式:`*.{mode}.avanterules`。 根目录层次结构的规则: diff --git a/lua/avante/config.lua b/lua/avante/config.lua index 5f937f9..d989e11 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -38,8 +38,10 @@ M._defaults = { -- For most providers that we support we will determine this automatically. -- If you wish to use a given implementation, then you can override it here. tokenizer = "tiktoken", - ---@type string | (fun(): string) | nil + ---@type string | fun(): string | nil system_prompt = nil, + ---@type string | fun(): string | nil + override_prompt_dir = nil, rules = { project_dir = nil, ---@type string | nil (could be relative dirpath) global_dir = nil, ---@type string | nil (absolute dirpath) diff --git a/lua/avante/path.lua b/lua/avante/path.lua index dff650e..5321b43 100644 --- a/lua/avante/path.lua +++ b/lua/avante/path.lua @@ -7,6 +7,7 @@ local Config = require("avante.config") ---@class avante.Path ---@field history_path Path ---@field cache_path Path +---@field data_path Path local P = {} ---@param bufnr integer | nil @@ -243,6 +244,45 @@ function Prompt.get_templates_dir(project_root) end end + -- Check for override prompt + local override_prompt_dir = Config.override_prompt_dir + if override_prompt_dir then + -- Handle the case where override_prompt_dir is a function + if type(override_prompt_dir) == "function" then + local ok, result = pcall(override_prompt_dir) + if ok and result then override_prompt_dir = result end + end + + if override_prompt_dir then + local user_template_path = Path:new(override_prompt_dir) + if user_template_path:exists() then + local user_scanner = Scan.scan_dir(user_template_path:absolute(), { depth = 1, add_dirs = false }) + for _, entry in ipairs(user_scanner) do + local file = Path:new(entry) + if file:is_file() then + local pieces = vim.split(entry, "/") + local piece = pieces[#pieces] + + if piece == "base.avanterules" then + local content = file:read() + + if not content:match("{%% block extra_prompt %%}[%s,\\n]*{%% endblock %%}") then + file:write("{% block extra_prompt %}\n", "a") + file:write("{% endblock %}\n", "a") + end + + if not content:match("{%% block custom_prompt %%}[%s,\\n]*{%% endblock %%}") then + file:write("{% block custom_prompt %}\n", "a") + file:write("{% endblock %}", "a") + end + end + file:copy({ destination = cache_prompt_dir:joinpath(piece) }) + end + end + end + end + end + if Config.rules.project_dir then local project_rules_path = Path:new(Config.rules.project_dir) if not project_rules_path:is_absolute() then project_rules_path = directory:joinpath(project_rules_path) end @@ -251,8 +291,12 @@ function Prompt.get_templates_dir(project_root) find_rules(Config.rules.global_dir) find_rules(directory:absolute()) - Path:new(debug.getinfo(1).source:match("@?(.*/)"):gsub("/lua/avante/path.lua$", "") .. "templates") - :copy({ destination = cache_prompt_dir, recursive = true }) + -- Copy built-in templates to cache directory (only if not overridden by user templates) + Path:new(debug.getinfo(1).source:match("@?(.*/)"):gsub("/lua/avante/path.lua$", "") .. "templates"):copy({ + destination = cache_prompt_dir, + recursive = true, + override = false, + }) vim.iter(Prompt.custom_prompts_contents):filter(function(_, v) return v ~= nil end):each(function(k, v) local orig_file = cache_prompt_dir:joinpath(Prompt.get_builtin_prompts_filepath(k))