From 2e6a26240e028bea01daf9a38a8108c8e2f88aaf Mon Sep 17 00:00:00 2001 From: Aaron Pham Date: Wed, 28 Aug 2024 23:56:00 -0400 Subject: [PATCH] feat(mapping): plug and expose API (#346) Signed-off-by: Aaron Pham --- README.md | 17 +++--- lua/avante/api.lua | 27 +++++++++ lua/avante/config.lua | 9 +-- lua/avante/init.lua | 116 +++++++++++++++++++++++++++----------- lua/avante/utils/init.lua | 89 +++++++++++++++++++++++++---- 5 files changed, 201 insertions(+), 57 deletions(-) create mode 100644 lua/avante/api.lua diff --git a/README.md b/README.md index 1734986..f3d5148 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ Install `avante.nvim` using [lazy.nvim](https://github.com/folke/lazy.nvim): opts = { -- add any opts here }, + keys = { -- See https://github.com/yetone/avante.nvim/wiki#keymaps for more info + { "aa", function() require("avante.api").ask() end, desc = "avante: ask", mode = { "n", "v" } }, + { "ar", function() require("avante.api").refresh() end, desc = "avante: refresh", mode = "v" }, + { "ae", function() require("avante.api").edit() end, desc = "avante: edit", mode = { "n", "v" } }, + }, dependencies = { "stevearc/dressing.nvim", "nvim-lua/plenary.nvim", @@ -94,9 +99,6 @@ _See [config.lua#L9](./lua/avante/config.lua) for the full config_ max_tokens = 4096, }, mappings = { - ask = "aa", - edit = "ae", - refresh = "ar", --- @class AvanteConflictMappings diff = { ours = "co", @@ -113,10 +115,6 @@ _See [config.lua#L9](./lua/avante/config.lua) for the full config_ normal = "", insert = "", }, - toggle = { - debug = "ad", - hint = "ah", - }, }, hints = { enabled = true }, windows = { @@ -208,6 +206,11 @@ The following key bindings are available for use with `avante.nvim`: | [[ | jump to previous codeblocks (results window) | | ]] | jump to next codeblocks (results windows) | +> [!NOTE] +> +> If you are using `lazy.nvim`, then all keymap here will be safely set, meaning if `aa` is already binded, then avante.nvim won't bind this mapping. +> In this case, user will be responsible for setting up their own. See [notes on keymaps](https://github.com/yetone/avante.nvim/wiki#keymaps) for more details. + ## Highlight Groups diff --git a/lua/avante/api.lua b/lua/avante/api.lua new file mode 100644 index 0000000..f0e6bf6 --- /dev/null +++ b/lua/avante/api.lua @@ -0,0 +1,27 @@ +local Utils = require("avante.utils") + +---@class avante.ApiToggle +---@operator call(): boolean +---@field debug ToggleBind.wrap +---@field hint ToggleBind.wrap +--- +---@class avante.Api +---@field ask fun(): boolean +---@field edit fun(): nil +---@field refresh fun(): nil +---@field toggle avante.ApiToggle + +return setmetatable({}, { + __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]] diff --git a/lua/avante/config.lua b/lua/avante/config.lua index e60f576..c14a85d 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -88,10 +88,7 @@ M.defaults = { }, }, mappings = { - ask = "aa", - edit = "ae", - refresh = "ar", - --- @class AvanteConflictMappings + ---@class AvanteConflictMappings diff = { ours = "co", theirs = "ct", @@ -108,6 +105,10 @@ M.defaults = { normal = "", insert = "", }, + -- NOTE: The following will be safely set by avante.nvim + ask = "aa", + edit = "ae", + refresh = "ar", toggle = { debug = "ad", hint = "ah", diff --git a/lua/avante/init.lua b/lua/avante/init.lua index 27c91cb..6738d92 100644 --- a/lua/avante/init.lua +++ b/lua/avante/init.lua @@ -26,7 +26,7 @@ H.commands = function() end cmd("Ask", function() - M.toggle() + M.ask() end, { desc = "avante: ask AI for code suggestions" }) cmd("Close", function() local sidebar, _ = M._get() @@ -35,34 +35,53 @@ H.commands = function() end sidebar:close() end, { desc = "avante: close chat window" }) + cmd("Edit", function() + M.edit() + end, { desc = "avante: edit selected block" }) cmd("Refresh", function() M.refresh() end, { desc = "avante: refresh windows" }) end H.keymaps = function() - vim.keymap.set({ "n", "v" }, Config.mappings.ask, M.toggle, { noremap = true, desc = "avante: Ask" }) - vim.keymap.set("v", Config.mappings.edit, M.edit, { noremap = true, desc = "avante: Edit" }) - vim.keymap.set("n", Config.mappings.refresh, M.refresh, { noremap = true, desc = "avante: Refresh" }) + vim.keymap.set({ "n", "v" }, "(AvanteAsk)", function() + M.ask() + end, { noremap = true }) + vim.keymap.set("v", "(AvanteEdit)", function() + M.edit() + end, { noremap = true }) + vim.keymap.set("n", "(AvanteRefresh)", function() + M.refresh() + end, { noremap = true }) + --- the following is kinda considered as internal mappings. + vim.keymap.set("n", "(AvanteToggleDebug)", function() + M.toggle.debug() + end) + vim.keymap.set("n", "(AvanteToggleHint)", function() + M.toggle.hint() + end) - Utils.toggle_map("n", Config.mappings.toggle.debug, { - name = "debug", - get = function() - return Config.debug + Utils.safe_keymap_set({ "n", "v" }, Config.mappings.ask, "(AvanteAsk)", { desc = "avante: ask" }) + Utils.safe_keymap_set("v", Config.mappings.edit, "(AvanteEdit)", { desc = "avante: edit" }) + Utils.safe_keymap_set("n", Config.mappings.refresh, "(AvanteRefresh)", { desc = "avante: refresh" }) + Utils.safe_keymap_set( + "n", + Config.mappings.toggle.debug, + "(AvanteToggleDebug)", + { desc = "avante: toggle debug" } + ) + Utils.safe_keymap_set("n", Config.mappings.toggle.hint, "(AvanteToggleHint)", { desc = "avante: toggle hint" }) +end + +---@class ApiCaller +---@operator call(...): any + +H.api = function(fun) + return setmetatable({ api = true }, { + __call = function(...) + return fun(...) end, - set = function(state) - Config.override({ debug = state }) - end, - }) - Utils.toggle_map("n", Config.mappings.toggle.hint, { - name = "hint", - get = function() - return Config.hints.enabled - end, - set = function(state) - Config.override({ hints = { enabled = state } }) - end, - }) + }) --[[@as ApiCaller]] end H.signs = function() @@ -166,26 +185,55 @@ function M._init(id) return M end -M.toggle = function() - local sidebar, _ = M._get() - if not sidebar then - M._init(api.nvim_get_current_tabpage()) - M.current.sidebar:open() - return true - end +M.toggle = { api = true } - return sidebar:toggle() -end +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.edit = function() +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, +}) + +M.ask = H.api(function() + M.toggle() +end) + +M.edit = H.api(function() local _, selection = M._get() if not selection then return end selection:create_editing_input() -end +end) -M.refresh = function() +M.refresh = H.api(function() local sidebar, _ = M._get() if not sidebar then return @@ -213,7 +261,7 @@ M.refresh = function() sidebar.code.winid = curwin sidebar.code.bufnr = curbuf sidebar:render() -end +end) ---@param opts? avante.Config function M.setup(opts) diff --git a/lua/avante/utils/init.lua b/lua/avante/utils/init.lua index a646257..f666798 100644 --- a/lua/avante/utils/init.lua +++ b/lua/avante/utils/init.lua @@ -69,23 +69,88 @@ M.shell_run = function(input_cmd) return { stdout = output, code = code } end +---@see https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/util/toggle.lua +--- ---@alias _ToggleSet fun(state: boolean): nil ---@alias _ToggleGet fun(): boolean --- +---@class ToggleBind +---@field name string +---@field set _ToggleSet +---@field get _ToggleGet +--- +---@class ToggleBind.wrap: ToggleBind +---@operator call:boolean + +---@param toggle ToggleBind +M.toggle_wrap = function(toggle) + return setmetatable(toggle, { + __call = function() + toggle.set(not toggle.get()) + local state = toggle.get() + if state then + M.info("enabled: " .. toggle.name) + else + M.warn("disabled: " .. toggle.name) + end + return state + end, + }) --[[@as ToggleBind.wrap]] +end + ---@param lhs string ----@param toggle {name: string, set: _ToggleSet, get: _ToggleGet} ----@param mode? string | string[] -M.toggle_map = function(mode, lhs, toggle) - vim.keymap.set(mode or { "n" }, lhs, function() - toggle.set(not toggle.get()) - local state = toggle.get() - if state then - M.info("enabled: " .. toggle.name, { title = "Avante" }) - else - M.warn("disabled: " .. toggle.name, { title = "Avante" }) +---@param toggle ToggleBind +M.toggle_map = function(lhs, toggle) + M.safe_keymap_set("n", lhs, M.toggle_wrap(toggle), { desc = "toggle(avante): " .. toggle.name }) +end + +-- Wrapper around vim.keymap.set that will +-- not create a keymap if a lazy key handler exists. +-- It will also set `silent` to true by default. +-- +---@param mode string|string[] Mode short-name, see |nvim_set_keymap()|. +--- Can also be list of modes to create mapping on multiple modes. +---@param lhs string Left-hand side |{lhs}| of the mapping. +---@param rhs string|function Right-hand side |{rhs}| of the mapping, can be a Lua function. +--- +---@param opts? vim.keymap.set.Opts +---@see |nvim_set_keymap()| +---@see |maparg()| +---@see |mapcheck()| +---@see |mapset()| +M.safe_keymap_set = function(mode, lhs, rhs, opts) + ---@type boolean + local ok + ---@module "lazy.core.handler" + local H + + ok, H = pcall(require, "lazy.core.handler") + if not ok then + M.warn("lazy.nvim is not available. Avante will use vim.keymap.set", { once = true }) + vim.keymap.set(mode, lhs, rhs, opts) + return + end + + local Keys = H.handlers.keys + ---@cast Keys LazyKeysHandler + local modes = type(mode) == "string" and { mode } or mode + ---@cast modes -string + + ---@param m string + modes = vim.tbl_filter(function(m) + return not (Keys.have and Keys:have(lhs, m)) + end, modes) + + -- don't create keymap if a lazy keys handler exists + if #modes > 0 then + opts = opts or {} + opts.silent = opts.silent ~= false + if opts.remap and not vim.g.vscode then + ---@diagnostic disable-next-line: no-unknown + opts.remap = nil end - return state - end, { desc = "toggle(avante): " .. toggle.name }) + vim.keymap.set(mode, lhs, rhs, opts) + end end ---@param str string