diff --git a/README.md b/README.md index 2895546..0207305 100644 --- a/README.md +++ b/README.md @@ -448,60 +448,26 @@ or you can use [Kaiser-Yang/blink-cmp-avante](https://github.com/Kaiser-Yang/bli Lua ```lua - file_selector = { - --- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil + selector = { + --- @alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil provider = "fzf", -- Options override for custom providers provider_opts = {}, } ``` -To create a customized file_selector, you can specify a customized function to launch a picker to select items and pass the selected items to the `handler` callback. +To create a customized selector provider, you can specify a customized function to launch a picker to select items and pass the selected items to the `on_select` callback. ```lua - file_selector = { - ---@param params avante.file_selector.IParams - provider = function(params) - local filepaths = params.filepaths ---@type string[] - local title = params.title ---@type string - local handler = params.handler ---@type fun(selected_filepaths: string[]|nil): nil + selector = { + ---@param selector avante.ui.Selector + provider = function(selector) + local items = selector.items ---@type avante.ui.SelectorItem[] + local title = selector.title ---@type string + local on_select = selector.on_select ---@type fun(selected_item_ids: string[]|nil): nil - -- Launch your customized picker with the items built from `filepaths`, then in the `on_confirm` callback, - -- pass the selected items (convert back to file paths) to the `handler` function. - - local items = __your_items_formatter__(filepaths) - __your_picker__({ - items = items, - on_cancel = function() - handler(nil) - end, - on_confirm = function(selected_items) - local selected_filepaths = {} - for _, item in ipairs(selected_items) do - table.insert(selected_filepaths, item.filepath) - end - handler(selected_filepaths) - end - }) + --- your customized picker logic here end, - ---below is optional - provider_opts = { - ---@param params avante.file_selector.opts.IGetFilepathsParams - get_filepaths = function(params) - local cwd = params.cwd ---@type string - local selected_filepaths = params.selected_filepaths ---@type string[] - local cmd = string.format("fd --base-directory '%s' --hidden", vim.fn.fnameescape(cwd)) - local output = vim.fn.system(cmd) - local filepaths = vim.split(output, "\n", { trimempty = true }) - return vim - .iter(filepaths) - :filter(function(filepath) - return not vim.tbl_contains(selected_filepaths, filepath) - end) - :totable() - end - } - end } ``` @@ -708,6 +674,7 @@ return { | `:AvanteShowRepoMap` | Show repo map for project's structure | | | `:AvanteToggle` | Toggle the Avante sidebar | | | `:AvanteModels` | Show model list | | +| `:AvanteSwitchSelectorProvider` | Switch avante selector provider (e.g. native, telescope, fzf_lua, mini_pick, snacks) | | ## Highlight Groups diff --git a/README_zh.md b/README_zh.md index 7a2e142..5b133f9 100644 --- a/README_zh.md +++ b/README_zh.md @@ -448,60 +448,26 @@ _请参见 [config.lua#L9](./lua/avante/config.lua) 以获取完整配置_ Lua ```lua - file_selector = { - --- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil + selector = { + --- @alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil provider = "fzf", -- 自定义提供者的选项覆盖 provider_opts = {}, } ``` -要创建自定义文件选择器,您可以指定一个自定义函数来启动选择器以选择项目,并将选定的项目传递给 `handler` 回调。 +要创建自定义选择器,您可以指定一个自定义函数来启动选择器以选择项目,并将选定的项目传递给 `on_select` 回调。 ```lua - file_selector = { - ---@param params avante.file_selector.IParams - provider = function(params) - local filepaths = params.filepaths ---@type string[] - local title = params.title ---@type string - local handler = params.handler ---@type fun(selected_filepaths: string[]|nil): nil + selector = { + ---@param selector avante.ui.Selector + provider = function(selector) + local items = selector.items ---@type avante.ui.SelectorItem[] + local title = selector.title ---@type string + local on_select = selector.on_select ---@type fun(selected_item_ids: string[]|nil): nil - -- 使用从 `filepaths` 构建的项目启动自定义选择器,然后在 `on_confirm` 回调中, - -- 将选定的项目(转换回文件路径)传递给 `handler` 函数。 - - local items = __your_items_formatter__(filepaths) - __your_picker__({ - items = items, - on_cancel = function() - handler(nil) - end, - on_confirm = function(selected_items) - local selected_filepaths = {} - for _, item in ipairs(selected_items) do - table.insert(selected_filepaths, item.filepath) - end - handler(selected_filepaths) - end - }) + --- 在这里添加您的自定义选择器逻辑 end, - ---以下是可选的 - provider_opts = { - ---@param params avante.file_selector.opts.IGetFilepathsParams - get_filepaths = function(params) - local cwd = params.cwd ---@type string - local selected_filepaths = params.selected_filepaths ---@type string[] - local cmd = string.format("fd --base-directory '%s' --hidden", vim.fn.fnameescape(cwd)) - local output = vim.fn.system(cmd) - local filepaths = vim.split(output, "\n", { trimempty = true }) - return vim - .iter(filepaths) - :filter(function(filepath) - return not vim.tbl_contains(selected_filepaths, filepath) - end) - :totable() - end - } - end } ``` diff --git a/lua/avante/api.lua b/lua/avante/api.lua index e4006fe..70d5089 100644 --- a/lua/avante/api.lua +++ b/lua/avante/api.lua @@ -11,10 +11,10 @@ local PromptInput = require("avante.ui.prompt_input") ---@field toggle avante.ApiToggle local M = {} ----@param target_provider FileSelectorProvider -function M.switch_file_selector_provider(target_provider) +---@param target_provider avante.SelectorProvider +function M.switch_selector_provider(target_provider) require("avante.config").override({ - file_selector = { + selector = { provider = target_provider, }, }) diff --git a/lua/avante/config.lua b/lua/avante/config.lua index 51bffc4..3768785 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -508,11 +508,15 @@ M._defaults = { }, --- @class AvanteFileSelectorConfig file_selector = { - --- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil - provider = "native", + provider = nil, -- Options override for custom providers provider_opts = {}, }, + selector = { + ---@alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil + provider = "native", + provider_opts = {}, + }, suggestion = { debounce = 600, throttle = 600, diff --git a/lua/avante/file_selector.lua b/lua/avante/file_selector.lua index c5468ab..ad18668 100644 --- a/lua/avante/file_selector.lua +++ b/lua/avante/file_selector.lua @@ -2,6 +2,7 @@ local Utils = require("avante.utils") local Path = require("plenary.path") local scan = require("plenary.scandir") local Config = require("avante.config") +local Selector = require("avante.ui.selector") local PROMPT_TITLE = "(Avante) Add a file" @@ -168,7 +169,7 @@ function FileSelector:off(event, callback) end end -function FileSelector:open() self:show_select_ui() end +function FileSelector:open() self:show_selector_ui() end function FileSelector:get_filepaths() if type(Config.file_selector.provider_opts.get_filepaths) == "function" then @@ -203,162 +204,56 @@ function FileSelector:get_filepaths() :totable() end ----@type FileSelectorHandler -function FileSelector:fzf_ui(handler) - local success, fzf_lua = pcall(require, "fzf-lua") - if not success then - Utils.error("fzf-lua is not installed. Please install fzf-lua to use it as a file selector.") - return - end - - local filepaths = self:get_filepaths() - - local function close_action() handler(nil) end - fzf_lua.fzf_exec( - filepaths, - vim.tbl_deep_extend("force", { - prompt = string.format("%s> ", PROMPT_TITLE), - fzf_opts = {}, - git_icons = false, - actions = { - ["default"] = function(selected) - if not selected or #selected == 0 then return close_action() end - ---@type string[] - local selections = {} - for _, entry in ipairs(selected) do - local file = fzf_lua.path.entry_to_file(entry) - if file and file.path then table.insert(selections, file.path) end - end - - handler(selections) - end, - ["esc"] = close_action, - ["ctrl-c"] = close_action, - }, - }, Config.file_selector.provider_opts) - ) -end - -function FileSelector:mini_pick_ui(handler) - -- luacheck: globals MiniPick - ---@diagnostic disable-next-line: undefined-field - if not _G.MiniPick then - Utils.error("mini.pick is not set up. Please install and set up mini.pick to use it as a file selector.") - return - end - local function choose(item) handler(type(item) == "string" and { item } or item) end - local function choose_marked(items_marked) handler(items_marked) end - local source = { choose = choose, choose_marked = choose_marked } - ---@diagnostic disable-next-line: undefined-global - local result = MiniPick.builtin.files(nil, { source = source }) - if result == nil then handler(nil) end -end - -function FileSelector:snacks_picker_ui(handler) - ---@diagnostic disable-next-line: undefined-field - if not _G.Snacks then - Utils.error("Snacks is not set up. Please install and set up Snacks to use it as a file selector.") - return - end - ---@diagnostic disable-next-line: undefined-global - Snacks.picker.files({ - exclude = self.selected_filepaths, - confirm = function(picker) - picker:close() - local items = picker:selected({ fallback = true }) - local files = vim.tbl_map(function(item) return item.file end, items) - handler(files) - end, - }) -end - -function FileSelector:telescope_ui(handler) - local success, _ = pcall(require, "telescope") - if not success then - Utils.error("telescope is not installed. Please install telescope to use it as a file selector.") - return - end - - local pickers = require("telescope.pickers") - local finders = require("telescope.finders") - local conf = require("telescope.config").values - local actions = require("telescope.actions") - local action_state = require("telescope.actions.state") - local action_utils = require("telescope.actions.utils") - - local files = self:get_filepaths() - - pickers - .new( - {}, - vim.tbl_extend("force", { - file_ignore_patterns = self.selected_filepaths, - prompt_title = string.format("%s> ", PROMPT_TITLE), - finder = finders.new_table(files), - sorter = conf.file_sorter(), - attach_mappings = function(prompt_bufnr, map) - map("i", "", require("telescope.actions").close) - actions.select_default:replace(function() - local picker = action_state.get_current_picker(prompt_bufnr) - - if #picker:get_multi_selection() ~= 0 then - local selections = {} - - action_utils.map_selections(prompt_bufnr, function(selection) table.insert(selections, selection[1]) end) - - handler(selections) - else - local selections = action_state.get_selected_entry() - - handler(selections) - end - - actions.close(prompt_bufnr) - end) - return true - end, - }, Config.file_selector.provider_opts) - ) - :find() -end - -function FileSelector:native_ui(handler) - local filepaths = self:get_filepaths() - - vim.ui.select(filepaths, { - prompt = string.format("%s:", PROMPT_TITLE), - format_item = function(item) return item end, - }, function(item) - if item then - handler({ item }) - else - handler(nil) - end - end) -end - ---@return nil -function FileSelector:show_select_ui() +function FileSelector:show_selector_ui() local function handler(selected_paths) self:handle_path_selection(selected_paths) end vim.schedule(function() - if Config.file_selector.provider == "native" then - self:native_ui(handler) - elseif Config.file_selector.provider == "fzf" then - self:fzf_ui(handler) - elseif Config.file_selector.provider == "mini.pick" then - self:mini_pick_ui(handler) - elseif Config.file_selector.provider == "snacks" then - self:snacks_picker_ui(handler) - elseif Config.file_selector.provider == "telescope" then - self:telescope_ui(handler) - elseif type(Config.file_selector.provider) == "function" then - local title = string.format("%s:", PROMPT_TITLE) ---@type string - local filepaths = self:get_filepaths() ---@type string[] - local params = { title = title, filepaths = filepaths, handler = handler } ---@type avante.file_selector.IParams - Config.file_selector.provider(params) + if Config.file_selector.provider ~= nil then + Utils.warn("config.file_selector is deprecated, please use config.selector instead!") + if type(Config.file_selector.provider) == "function" then + local title = string.format("%s:", PROMPT_TITLE) ---@type string + local filepaths = self:get_filepaths() ---@type string[] + local params = { title = title, filepaths = filepaths, handler = handler } ---@type avante.file_selector.IParams + Config.file_selector.provider(params) + else + local provider = "native" + if Config.file_selector.provider == "native" then + provider = "native" + elseif Config.file_selector.provider == "fzf" then + provider = "fzf_lua" + elseif Config.file_selector.provider == "mini.pick" then + provider = "mini_pick" + elseif Config.file_selector.provider == "snacks" then + provider = "snacks" + elseif Config.file_selector.provider == "telescope" then + provider = "telescope" + elseif type(Config.file_selector.provider) == "function" then + provider = Config.file_selector.provider + end + ---@cast provider avante.SelectorProvider + local selector = Selector:new({ + provider = provider, + title = PROMPT_TITLE, + items = vim.tbl_map(function(filepath) return { id = filepath, title = filepath } end, self:get_filepaths()), + default_item_id = self.selected_filepaths[1], + selected_item_ids = self.selected_filepaths, + provider_opts = Config.file_selector.provider_opts, + on_select = function(item_ids) self:handle_path_selection(item_ids) end, + }) + selector:open() + end else - Utils.error("Unknown file selector provider: " .. Config.file_selector.provider) + local selector = Selector:new({ + provider = Config.selector.provider, + title = PROMPT_TITLE, + items = vim.tbl_map(function(filepath) return { id = filepath, title = filepath } end, self:get_filepaths()), + default_item_id = self.selected_filepaths[1], + selected_item_ids = self.selected_filepaths, + provider_opts = Config.selector.provider_opts, + on_select = function(item_ids) self:handle_path_selection(item_ids) end, + }) + selector:open() end end) diff --git a/lua/avante/history_selector.lua b/lua/avante/history_selector.lua index 0211244..9272e6a 100644 --- a/lua/avante/history_selector.lua +++ b/lua/avante/history_selector.lua @@ -1,5 +1,7 @@ local Utils = require("avante.utils") local Path = require("avante.path") +local Config = require("avante.config") +local Selector = require("avante.ui.selector") ---@class avante.HistorySelector local M = {} @@ -8,8 +10,10 @@ local M = {} ---@return table? local function to_selector_item(history) local timestamp = #history.entries > 0 and history.entries[#history.entries].timestamp or history.timestamp + local name = history.title .. " - " .. timestamp .. " (" .. #history.entries .. ")" + name = name:gsub("\n", "\\n") return { - name = history.title .. " - " .. timestamp .. " (" .. #history.entries .. ")", + name = name, filename = history.filename, } end @@ -30,68 +34,33 @@ function M.open(bufnr, cb) return end - local has_telescope, _ = pcall(require, "telescope") - if has_telescope then - local pickers = require("telescope.pickers") - local finders = require("telescope.finders") - local previewers = require("telescope.previewers") - local actions = require("telescope.actions") - local action_state = require("telescope.actions.state") - local conf = require("telescope.config").values - pickers - .new({}, { - prompt_title = "Select Avante History", - finder = finders.new_table(vim.iter(selector_items):map(function(item) return item.name end):totable()), - sorter = conf.generic_sorter({}), - previewer = previewers.new_buffer_previewer({ - title = "Preview", - define_preview = function(self, entry) - if not entry then return end - local item = vim.iter(selector_items):find(function(item) return item.name == entry.value end) - if not item then return end - local history = Path.history.load(vim.api.nvim_get_current_buf(), item.filename) - local Sidebar = require("avante.sidebar") - local content = Sidebar.render_history_content(history) - local lines = vim.split(content or "", "\n") - -- Ensure the buffer exists and is valid before setting lines - if vim.api.nvim_buf_is_valid(self.state.bufnr) then - vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) - -- Set filetype after content is loaded - vim.api.nvim_set_option_value("filetype", "markdown", { buf = self.state.bufnr }) - -- Ensure cursor is within bounds - vim.schedule(function() - if vim.api.nvim_buf_is_valid(self.state.bufnr) then - local row = math.min(vim.api.nvim_buf_line_count(self.state.bufnr), 1) - pcall(vim.api.nvim_win_set_cursor, self.state.winnr, { row, 0 }) - end - end) - end - end, - }), - attach_mappings = function(prompt_bufnr, map) - map("i", "", function() - local selection = action_state.get_selected_entry() - if selection then - actions.close(prompt_bufnr) - local item = vim.iter(selector_items):find(function(item) return item.name == selection.value end) - if not item then return end - cb(item.filename) - end - end) - return true - end, - }) - :find() - return - end - - vim.ui.select(selector_items, { - prompt = "Select Avante History:", - format_item = function(item) return item.name:gsub("\n", "\\n") end, - }, function(choice) - if not choice then return end - cb(choice.filename) - end) + local selector = Selector:new({ + provider = Config.selector.provider, + title = "Select Avante History", + items = vim + .iter(selector_items) + :map( + function(item) + return { + id = item.filename, + title = item.name, + } + end + ) + :totable(), + on_select = function(item_ids) + if not item_ids then return end + if #item_ids == 0 then return end + cb(item_ids[1]) + end, + get_preview_content = function(item_id) + local history = Path.history.load(vim.api.nvim_get_current_buf(), item_id) + local Sidebar = require("avante.sidebar") + local content = Sidebar.render_history_content(history) + return content, "markdown" + end, + }) + selector:open() end return M diff --git a/lua/avante/model_selector.lua b/lua/avante/model_selector.lua index 496d32b..3a1de4f 100644 --- a/lua/avante/model_selector.lua +++ b/lua/avante/model_selector.lua @@ -1,5 +1,6 @@ local Utils = require("avante.utils") local Config = require("avante.config") +local Selector = require("avante.ui.selector") ---@class avante.ModelSelector local M = {} @@ -33,10 +34,21 @@ function M.open() return end - vim.ui.select(models, { - prompt = "Select Avante Model:", - format_item = function(item) return item.name end, - }, function(choice) + local items = vim + .iter(models) + :map(function(item) + return { + id = item.name, + title = item.name, + } + end) + :totable() + + local default_item = vim.iter(models):find(function(item) return item.provider == Config.provider end) + + local function on_select(item_ids) + if not item_ids then return end + local choice = vim.iter(models):find(function(item) return item.name == item_ids[1] end) if not choice then return end -- Switch provider if needed @@ -52,7 +64,18 @@ function M.open() }) Utils.info("Switched to model: " .. choice.name) - end) + end + + local selector = Selector:new({ + title = "Select Avante Model", + items = items, + default_item_id = default_item and default_item.name or nil, + provider = Config.selector.provider, + provider_opts = Config.selector.provider_opts, + on_select = on_select, + }) + + selector:open() end return M diff --git a/lua/avante/ui/selector/init.lua b/lua/avante/ui/selector/init.lua new file mode 100644 index 0000000..da5cc39 --- /dev/null +++ b/lua/avante/ui/selector/init.lua @@ -0,0 +1,55 @@ +local Utils = require("avante.utils") + +---@class avante.ui.SelectorItem +---@field id string +---@field title string + +---@class avante.ui.SelectorOption +---@field provider avante.SelectorProvider +---@field title string +---@field items avante.ui.SelectorItem[] +---@field default_item_id string | nil +---@field selected_item_ids string[] | nil +---@field provider_opts table | nil +---@field on_select fun(item_ids: string[] | nil) +---@field get_preview_content fun(item_id: string): (string, string) | nil + +---@class avante.ui.Selector +---@field provider avante.SelectorProvider +---@field title string +---@field items avante.ui.SelectorItem[] +---@field default_item_id string | nil +---@field provider_opts table | nil +---@field on_select fun(item_ids: string[] | nil) +---@field selected_item_ids string[] | nil +---@field get_preview_content fun(item_id: string): (string, string) | nil +local Selector = {} +Selector.__index = Selector + +---@param opts avante.ui.SelectorOption +function Selector:new(opts) + local o = {} + setmetatable(o, Selector) + o.provider = opts.provider + o.title = opts.title + o.items = opts.items + o.default_item_id = opts.default_item_id + o.provider_opts = opts.provider_opts or {} + o.on_select = opts.on_select + o.selected_item_ids = opts.selected_item_ids or {} + o.get_preview_content = opts.get_preview_content + return o +end + +function Selector:open() + if type(self.provider) == "function" then + self.provider(self) + return + end + + local ok, provider = pcall(require, "avante.ui.selector.providers." .. self.provider) + if not ok then Utils.error("Unknown file selector provider: " .. self.provider) end + provider.show(self) +end + +return Selector diff --git a/lua/avante/ui/selector/providers/fzf_lua.lua b/lua/avante/ui/selector/providers/fzf_lua.lua new file mode 100644 index 0000000..27ad0e2 --- /dev/null +++ b/lua/avante/ui/selector/providers/fzf_lua.lua @@ -0,0 +1,49 @@ +local Utils = require("avante.utils") +local M = {} + +---@param selector avante.ui.Selector +function M.show(selector) + local success, fzf_lua = pcall(require, "fzf-lua") + if not success then + Utils.error("fzf-lua is not installed. Please install fzf-lua to use it as a file selector.") + return + end + + local formated_items = vim.iter(selector.items):map(function(item) return item.title end):totable() + local title_to_id = {} + for _, item in ipairs(selector.items) do + title_to_id[item.title] = item.id + end + + local function close_action() selector.on_select(nil) end + fzf_lua.fzf_exec( + formated_items, + vim.tbl_deep_extend("force", { + prompt = selector.title, + preview = selector.get_preview_content and function(item) + local id = title_to_id[item[1]] + local content = selector.get_preview_content(id) + return content + end or nil, + fzf_opts = {}, + git_icons = false, + actions = { + ["default"] = function(selected) + if not selected or #selected == 0 then return close_action() end + ---@type string[] + local selections = {} + for _, entry in ipairs(selected) do + local id = title_to_id[entry] + if id then table.insert(selections, id) end + end + + selector.on_select(selections) + end, + ["esc"] = close_action, + ["ctrl-c"] = close_action, + }, + }, selector.provider_opts) + ) +end + +return M diff --git a/lua/avante/ui/selector/providers/mini_pick.lua b/lua/avante/ui/selector/providers/mini_pick.lua new file mode 100644 index 0000000..4fb21fb --- /dev/null +++ b/lua/avante/ui/selector/providers/mini_pick.lua @@ -0,0 +1,37 @@ +local Utils = require("avante.utils") +local M = {} + +---@param selector avante.ui.Selector +function M.show(selector) + -- luacheck: globals MiniPick + ---@diagnostic disable-next-line: undefined-field + if not _G.MiniPick then + Utils.error("mini.pick is not set up. Please install and set up mini.pick to use it as a file selector.") + return + end + local items = {} + local title_to_id = {} + for _, item in ipairs(selector.items) do + title_to_id[item.title] = item.id + if not vim.list_contains(selector.selected_item_ids, item.id) then table.insert(items, item) end + end + local function choose(item) + if not item then + selector.on_select(nil) + return + end + local item_ids = {} + ---item is not a list + for _, item_ in pairs(item) do + table.insert(item_ids, title_to_id[item_]) + end + selector.on_select(item_ids) + end + ---@diagnostic disable-next-line: undefined-global + MiniPick.ui_select(items, { + prompt = selector.title, + format_item = function(item) return item.title end, + }, choose) +end + +return M diff --git a/lua/avante/ui/selector/providers/native.lua b/lua/avante/ui/selector/providers/native.lua new file mode 100644 index 0000000..7cbb86f --- /dev/null +++ b/lua/avante/ui/selector/providers/native.lua @@ -0,0 +1,21 @@ +local M = {} + +---@param selector avante.ui.Selector +function M.show(selector) + local items = {} + for _, item in ipairs(selector.items) do + if not vim.list_contains(selector.selected_item_ids, item.id) then table.insert(items, item) end + end + vim.ui.select(items, { + prompt = selector.title, + format_item = function(item) return item.title end, + }, function(item) + if item then + selector.on_select({ item.id }) + else + selector.on_select(nil) + end + end) +end + +return M diff --git a/lua/avante/ui/selector/providers/snacks.lua b/lua/avante/ui/selector/providers/snacks.lua new file mode 100644 index 0000000..1940fe2 --- /dev/null +++ b/lua/avante/ui/selector/providers/snacks.lua @@ -0,0 +1,58 @@ +local Utils = require("avante.utils") +local M = {} + +---@param selector avante.ui.Selector +function M.show(selector) + ---@diagnostic disable-next-line: undefined-field + if not _G.Snacks then + Utils.error("Snacks is not set up. Please install and set up Snacks to use it as a file selector.") + return + end + local finder_items = {} + for i, item in ipairs(selector.items) do + if not vim.list_contains(selector.selected_item_ids, item.id) then + table.insert(finder_items, { + formatted = item.title, + text = item.title, + item = item, + idx = i, + preview = selector.get_preview_content and (function() + local content, filetype = selector.get_preview_content(item.id) + return { + text = content, + ft = filetype, + } + end)() or nil, + }) + end + end + local completed = false + ---@diagnostic disable-next-line: undefined-global + Snacks.picker.pick({ + source = "select", + items = finder_items, + ---@diagnostic disable-next-line: undefined-global + format = Snacks.picker.format.ui_select(nil, #finder_items), + title = selector.title, + preview = selector.get_preview_content and "preview" or nil, + layout = { + preset = "default", + preview = selector.get_preview_content ~= nil, + }, + confirm = function(picker) + if completed then return end + completed = true + picker:close() + local items = picker:selected({ fallback = true }) + local selected_item_ids = vim.tbl_map(function(item) return item.item.id end, items) + selector.on_select(selected_item_ids) + end, + on_close = function() + if completed then return end + completed = true + vim.schedule(function() selector.on_select(nil) end) + end, + }) +end + +return M diff --git a/lua/avante/ui/selector/providers/telescope.lua b/lua/avante/ui/selector/providers/telescope.lua new file mode 100644 index 0000000..7b7373e --- /dev/null +++ b/lua/avante/ui/selector/providers/telescope.lua @@ -0,0 +1,92 @@ +local Utils = require("avante.utils") + +local M = {} + +---@param selector avante.ui.Selector +function M.show(selector) + local success, _ = pcall(require, "telescope") + if not success then + Utils.error("telescope is not installed. Please install telescope to use it as a file selector.") + return + end + + local pickers = require("telescope.pickers") + local finders = require("telescope.finders") + local conf = require("telescope.config").values + local actions = require("telescope.actions") + local action_state = require("telescope.actions.state") + local previewers = require("telescope.previewers") + + local items = {} + for _, item in ipairs(selector.items) do + if not vim.list_contains(selector.selected_item_ids, item.id) then table.insert(items, item) end + end + + pickers + .new( + {}, + vim.tbl_extend("force", { + prompt_title = selector.title, + finder = finders.new_table({ + results = items, + entry_maker = function(entry) + return { + value = entry.id, + display = entry.title, + ordinal = entry.title, + } + end, + }), + sorter = conf.file_sorter(), + previewer = selector.get_preview_content and previewers.new_buffer_previewer({ + title = "Preview", + define_preview = function(self, entry) + if not entry then return end + local content, filetype = selector.get_preview_content(entry.value) + local lines = vim.split(content or "", "\n") + -- Ensure the buffer exists and is valid before setting lines + if vim.api.nvim_buf_is_valid(self.state.bufnr) then + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) + -- Set filetype after content is loaded + vim.api.nvim_set_option_value("filetype", filetype, { buf = self.state.bufnr }) + -- Ensure cursor is within bounds + vim.schedule(function() + if vim.api.nvim_buf_is_valid(self.state.bufnr) then + local row = math.min(vim.api.nvim_buf_line_count(self.state.bufnr), 1) + pcall(vim.api.nvim_win_set_cursor, self.state.winnr, { row, 0 }) + end + end) + end + end, + }), + attach_mappings = function(prompt_bufnr, map) + map("i", "", require("telescope.actions").close) + actions.select_default:replace(function() + local picker = action_state.get_current_picker(prompt_bufnr) + + local selections + local multi_selection = picker:get_multi_selection() + if #multi_selection ~= 0 then + selections = multi_selection + else + selections = action_state.get_selected_entry() + selections = vim.islist(selections) and selections or { selections } + end + + local selected_item_ids = vim + .iter(selections) + :map(function(selection) return selection.value end) + :totable() + + selector.on_select(selected_item_ids) + + actions.close(prompt_bufnr) + end) + return true + end, + }, selector.provider_opts) + ) + :find() +end + +return M diff --git a/plugin/avante.lua b/plugin/avante.lua index 87a7eb7..44b8ca5 100644 --- a/plugin/avante.lua +++ b/plugin/avante.lua @@ -122,11 +122,11 @@ cmd("SwitchProvider", function(opts) require("avante.api").switch_provider(vim.t end, }) cmd( - "SwitchFileSelectorProvider", - function(opts) require("avante.api").switch_file_selector_provider(vim.trim(opts.args or "")) end, + "SwitchSelectorProvider", + function(opts) require("avante.api").switch_selector_provider(vim.trim(opts.args or "")) end, { nargs = 1, - desc = "avante: switch file selector provider", + desc = "avante: switch selector provider", } ) cmd("Clear", function(opts)