feat(ui): add envvar popup with warnings (#54)
* feat: add support for input popup of envvar Signed-off-by: Aaron Pham <contact@aarnphm.xyz> * chore: update README and ignore certain filetypes for popup Signed-off-by: Aaron Pham <contact@aarnphm.xyz> * fix: readme doesn't support nested callout Signed-off-by: Aaron Pham <contact@aarnphm.xyz> --------- Signed-off-by: Aaron Pham <contact@aarnphm.xyz>
This commit is contained in:
parent
f4ab995515
commit
af1f51455e
81
README.md
81
README.md
@ -3,9 +3,9 @@
|
||||
**avante.nvim** is a Neovim plugin designed to emulate the behavior of the [Cursor](https://www.cursor.com) AI IDE, providing users with AI-driven code suggestions and the ability to apply these recommendations directly to their source files with minimal effort.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
>
|
||||
> ⚠️ This plugin is still in a very early stage of development, so please be aware that the current code is very messy and unstable, and problems are likely to occur.
|
||||
>
|
||||
>
|
||||
> 🥰 This project is undergoing rapid iterations, and many exciting features will be added successively. Stay tuned!
|
||||
|
||||
https://github.com/user-attachments/assets/510e6270-b6cf-459d-9a2f-15b397d1fe53
|
||||
@ -69,8 +69,11 @@ Install `avante.nvim` using [lazy.nvim](https://github.com/folke/lazy.nvim):
|
||||
|
||||
Default setup configuration:
|
||||
|
||||
_See [config.lua#L9](./lua/avante/config.lua) for the full config_
|
||||
|
||||
```lua
|
||||
{
|
||||
---@alias Provider "openai" | "claude" | "azure"
|
||||
provider = "claude", -- "claude" or "openai" or "azure"
|
||||
openai = {
|
||||
endpoint = "https://api.openai.com",
|
||||
@ -79,7 +82,7 @@ Default setup configuration:
|
||||
max_tokens = 4096,
|
||||
},
|
||||
azure = {
|
||||
endpoint = "", -- Example: "https://<your-resource-name>.openai.azure.com"
|
||||
endpoint = "", -- example: "https://<your-resource-name>.openai.azure.com"
|
||||
deployment = "", -- Azure deployment name (e.g., "gpt-4o", "my-gpt-4o-deployment")
|
||||
api_version = "2024-06-01",
|
||||
temperature = 0,
|
||||
@ -92,13 +95,17 @@ Default setup configuration:
|
||||
max_tokens = 4096,
|
||||
},
|
||||
highlights = {
|
||||
---@type AvanteConflictHighlights
|
||||
diff = {
|
||||
current = "DiffText", -- need have background color
|
||||
incoming = "DiffAdd", -- need have background color
|
||||
current = "DiffText",
|
||||
incoming = "DiffAdd",
|
||||
},
|
||||
},
|
||||
mappings = {
|
||||
ask = "<leader>aa",
|
||||
edit = "<leader>ae",
|
||||
refresh = "<leader>ar",
|
||||
--- @class AvanteConflictMappings
|
||||
diff = {
|
||||
ours = "co",
|
||||
theirs = "ct",
|
||||
@ -107,6 +114,20 @@ Default setup configuration:
|
||||
next = "]x",
|
||||
prev = "[x",
|
||||
},
|
||||
jump = {
|
||||
next = "]]",
|
||||
prev = "[[",
|
||||
},
|
||||
},
|
||||
windows = {
|
||||
width = 30, -- default % based on available width
|
||||
},
|
||||
--- @class AvanteConflictUserConfig
|
||||
diff = {
|
||||
debug = false,
|
||||
autojump = true,
|
||||
---@type string | fun(): any
|
||||
list_opener = "copen",
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -115,30 +136,33 @@ Default setup configuration:
|
||||
|
||||
Given its early stage, `avante.nvim` currently supports the following basic functionalities:
|
||||
|
||||
1. Set the appropriate API key as an environment variable:
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> For most consistency between neovim session, it is recommended to set the environment variables in your shell file.
|
||||
> By default, `Avante` will prompt you at startup to input the API key for the provider you have selected.
|
||||
>
|
||||
> For Claude:
|
||||
>
|
||||
> ```sh
|
||||
> export ANTHROPIC_API_KEY=your-api-key
|
||||
> ```
|
||||
>
|
||||
> For OpenAI:
|
||||
>
|
||||
> ```sh
|
||||
> export OPENAI_API_KEY=your-api-key
|
||||
> ```
|
||||
>
|
||||
> For Azure OpenAI:
|
||||
>
|
||||
> ```sh
|
||||
> export AZURE_OPENAI_API_KEY=your-api-key
|
||||
> ```
|
||||
|
||||
For Claude:
|
||||
|
||||
```sh
|
||||
export ANTHROPIC_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
For OpenAI:
|
||||
|
||||
```sh
|
||||
export OPENAI_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
For Azure OpenAI:
|
||||
|
||||
```sh
|
||||
export AZURE_OPENAI_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
2. Open a code file in Neovim.
|
||||
3. Use the `:AvanteAsk` command to query the AI about the code.
|
||||
4. Review the AI's suggestions.
|
||||
5. Apply the recommended changes directly to your code with a simple command or key binding.
|
||||
1. Open a code file in Neovim.
|
||||
2. Use the `:AvanteAsk` command to query the AI about the code.
|
||||
3. Review the AI's suggestions.
|
||||
4. Apply the recommended changes directly to your code with a simple command or key binding.
|
||||
|
||||
**Note**: The plugin is still under active development, and both its functionality and interface are subject to significant changes. Expect some rough edges and instability as the project evolves.
|
||||
|
||||
@ -147,6 +171,7 @@ Given its early stage, `avante.nvim` currently supports the following basic func
|
||||
The following key bindings are available for use with `avante.nvim`:
|
||||
|
||||
- <kbd>Leader</kbd><kbd>a</kbd><kbd>a</kbd> — show sidebar
|
||||
- <kbd>Leader</kbd><kbd>a</kbd><kbd>r</kbd> — show sidebar
|
||||
- <kbd>c</kbd><kbd>o</kbd> — choose ours
|
||||
- <kbd>c</kbd><kbd>t</kbd> — choose theirs
|
||||
- <kbd>c</kbd><kbd>b</kbd> — choose both
|
||||
|
@ -1,6 +1,9 @@
|
||||
local fn = vim.fn
|
||||
local api = vim.api
|
||||
|
||||
local curl = require("plenary.curl")
|
||||
local Input = require("nui.input")
|
||||
local Event = require("nui.utils.autocmd").event
|
||||
|
||||
local Utils = require("avante.utils")
|
||||
local Config = require("avante.config")
|
||||
@ -9,6 +12,153 @@ local Tiktoken = require("avante.tiktoken")
|
||||
---@class avante.AiBot
|
||||
local M = {}
|
||||
|
||||
---@class Environment: table<[string], any>
|
||||
---@field [string] string the environment variable name
|
||||
---@field fallback? string Optional fallback API key environment variable name
|
||||
|
||||
---@class EnvironmentHandler: table<[Provider], string>
|
||||
local E = {
|
||||
---@type table<Provider, Environment | string>
|
||||
env = {
|
||||
openai = "OPENAI_API_KEY",
|
||||
claude = "ANTHROPIC_API_KEY",
|
||||
azure = { "AZURE_OPENAI_API_KEY", fallback = "OPENAI_API_KEY" },
|
||||
},
|
||||
_once = false,
|
||||
}
|
||||
|
||||
E = setmetatable(E, {
|
||||
---@param k Provider
|
||||
__index = function(_, k)
|
||||
local envvar = E.env[k]
|
||||
if type(envvar) == "string" then
|
||||
local value = os.getenv(envvar)
|
||||
return value and true or false
|
||||
elseif type(envvar) == "table" then
|
||||
local main_key = envvar[1]
|
||||
local value = os.getenv(main_key)
|
||||
if value then
|
||||
return true
|
||||
elseif envvar.fallback then
|
||||
return os.getenv(envvar.fallback) and true or false
|
||||
end
|
||||
end
|
||||
return false
|
||||
end,
|
||||
})
|
||||
|
||||
-- courtesy of https://github.com/MunifTanjim/nui.nvim/wiki/nui.input
|
||||
local SecretInput = Input:extend("SecretInput")
|
||||
|
||||
function SecretInput:init(popup_options, options)
|
||||
assert(
|
||||
not options.conceal_char or vim.api.nvim_strwidth(options.conceal_char) == 1,
|
||||
"conceal_char must be a single char"
|
||||
)
|
||||
|
||||
popup_options.win_options = vim.tbl_deep_extend("force", popup_options.win_options or {}, {
|
||||
conceallevel = 2,
|
||||
concealcursor = "nvi",
|
||||
})
|
||||
|
||||
SecretInput.super.init(self, popup_options, options)
|
||||
|
||||
self._.conceal_char = type(options.conceal_char) == "nil" and "*" or options.conceal_char
|
||||
end
|
||||
|
||||
function SecretInput:mount()
|
||||
SecretInput.super.mount(self)
|
||||
|
||||
local conceal_char = self._.conceal_char
|
||||
local prompt_length = vim.api.nvim_strwidth(vim.fn.prompt_getprompt(self.bufnr))
|
||||
|
||||
vim.api.nvim_buf_call(self.bufnr, function()
|
||||
vim.cmd(string.format(
|
||||
[[
|
||||
syn region SecretValue start=/^/ms=s+%s end=/$/ contains=SecretChar
|
||||
syn match SecretChar /./ contained conceal %s
|
||||
]],
|
||||
prompt_length,
|
||||
conceal_char and "cchar=" .. (conceal_char or "*") or ""
|
||||
))
|
||||
end)
|
||||
end
|
||||
|
||||
--- return the environment variable name for the given provider
|
||||
---@param provider? Provider
|
||||
---@return string the envvar key
|
||||
E.key = function(provider)
|
||||
provider = provider or Config.provider
|
||||
local var = E.env[provider]
|
||||
return type(var) == "table" and var[1] ---@cast var string
|
||||
or var
|
||||
end
|
||||
|
||||
E.setup = function(var)
|
||||
if E._once then
|
||||
return
|
||||
end
|
||||
|
||||
local input = SecretInput({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 40,
|
||||
},
|
||||
border = {
|
||||
style = "single",
|
||||
text = {
|
||||
top = "Enter " .. var,
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:Normal",
|
||||
},
|
||||
}, {
|
||||
prompt = "> ",
|
||||
default_value = "",
|
||||
on_submit = function(value)
|
||||
vim.fn.setenv(var, value)
|
||||
end,
|
||||
on_close = function()
|
||||
if not E[Config.provider] then
|
||||
vim.notify_once("Failed to set " .. var .. ". Avante won't work as expected", vim.log.levels.WARN)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
if E._once then
|
||||
return
|
||||
end
|
||||
|
||||
vim.defer_fn(function()
|
||||
-- only mount if given buffer is not of buftype ministarter, dashboard, alpha, qf
|
||||
local exclude_buftypes = { "dashboard", "alpha", "qf", "nofile" }
|
||||
local exclude_filetypes =
|
||||
{ "NvimTree", "Outline", "help", "dashboard", "alpha", "qf", "ministarter", "TelescopePrompt", "gitcommit" }
|
||||
if
|
||||
not vim.tbl_contains(exclude_buftypes, vim.bo.buftype)
|
||||
and not vim.tbl_contains(exclude_filetypes, vim.bo.filetype)
|
||||
then
|
||||
E._once = true
|
||||
input:mount()
|
||||
end
|
||||
end, 200)
|
||||
end,
|
||||
})
|
||||
|
||||
input:map("n", "<Esc>", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:on(Event.BufLeave, function()
|
||||
input:unmount()
|
||||
end)
|
||||
end
|
||||
|
||||
local system_prompt = [[
|
||||
You are an excellent programming expert.
|
||||
]]
|
||||
@ -57,10 +207,7 @@ Remember: Accurate line numbers are CRITICAL. The range start_line to end_line m
|
||||
]]
|
||||
|
||||
local function call_claude_api_stream(question, code_lang, code_content, selected_code_content, on_chunk, on_complete)
|
||||
local api_key = os.getenv("ANTHROPIC_API_KEY")
|
||||
if not api_key then
|
||||
error("ANTHROPIC_API_KEY environment variable is not set")
|
||||
end
|
||||
local api_key = os.getenv(E.key("azure"))
|
||||
|
||||
local tokens = Config.claude.max_tokens
|
||||
local headers = {
|
||||
@ -174,11 +321,7 @@ local function call_claude_api_stream(question, code_lang, code_content, selecte
|
||||
end
|
||||
|
||||
local function call_openai_api_stream(question, code_lang, code_content, selected_code_content, on_chunk, on_complete)
|
||||
local api_key = os.getenv("OPENAI_API_KEY")
|
||||
if not api_key and Config.provider == "openai" then
|
||||
error("OPENAI_API_KEY environment variable is not set")
|
||||
end
|
||||
|
||||
local api_key = os.getenv(E.key("openai"))
|
||||
local user_prompt = base_user_prompt
|
||||
.. "\n\nCODE:\n"
|
||||
.. "```"
|
||||
@ -209,10 +352,7 @@ local function call_openai_api_stream(question, code_lang, code_content, selecte
|
||||
|
||||
local url, headers, body
|
||||
if Config.provider == "azure" then
|
||||
api_key = os.getenv("AZURE_OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY")
|
||||
if not api_key then
|
||||
error("Azure OpenAI API key is not set. Please set AZURE_OPENAI_API_KEY or OPENAI_API_KEY environment variable.")
|
||||
end
|
||||
api_key = os.getenv(E.key("azure"))
|
||||
url = Config.azure.endpoint
|
||||
.. "/openai/deployments/"
|
||||
.. Config.azure.deployment
|
||||
@ -306,4 +446,11 @@ function M.call_ai_api_stream(question, code_lang, code_content, selected_conten
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
local has = E[Config.provider]
|
||||
if not has then
|
||||
E.setup(E.key())
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
@ -6,6 +6,7 @@ local M = {}
|
||||
|
||||
---@class avante.Config
|
||||
M.defaults = {
|
||||
---@alias Provider "openai" | "claude" | "azure"
|
||||
provider = "claude", -- "claude" or "openai" or "azure"
|
||||
openai = {
|
||||
endpoint = "https://api.openai.com",
|
||||
|
@ -331,9 +331,10 @@ local function register_cursor_move_events(bufnr)
|
||||
end
|
||||
|
||||
local hint = string.format(
|
||||
" [Press <%s> for OURS, <%s> for THEIRS, <%s> for PREV, <%s> for NEXT] ",
|
||||
" [Press <%s> for OURS, <%s> for THEIRS, <%s> for BOTH, <%s> for PREV, <%s> for NEXT] ",
|
||||
Config.diff.mappings.ours,
|
||||
Config.diff.mappings.theirs,
|
||||
Config.diff.mappings.both,
|
||||
Config.diff.mappings.prev,
|
||||
Config.diff.mappings.next
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ local Tiktoken = require("avante.tiktoken")
|
||||
local Sidebar = require("avante.sidebar")
|
||||
local Config = require("avante.config")
|
||||
local Diff = require("avante.diff")
|
||||
local AiBot = require("avante.ai_bot")
|
||||
local Selection = require("avante.selection")
|
||||
|
||||
---@class Avante
|
||||
@ -164,6 +165,7 @@ function M.setup(opts)
|
||||
end
|
||||
|
||||
Diff.setup()
|
||||
AiBot.setup()
|
||||
M.selection = Selection:new():setup()
|
||||
|
||||
-- setup helpers
|
||||
|
@ -132,6 +132,16 @@ function Sidebar:intialize()
|
||||
relative = { type = "win", winid = fn.bufwinid(self.view.buf) },
|
||||
})
|
||||
|
||||
self.renderer:add_mappings({
|
||||
{
|
||||
mode = { "n" },
|
||||
key = "q",
|
||||
handler = function()
|
||||
self.renderer:close()
|
||||
end,
|
||||
},
|
||||
})
|
||||
|
||||
self.renderer:on_mount(function()
|
||||
self.winid.result = self.renderer:get_component_by_id("result").winid
|
||||
self.winid.input = self.renderer:get_component_by_id("input").winid
|
||||
|
Loading…
x
Reference in New Issue
Block a user