feat: universal selector (#1877)
This commit is contained in:
55
README.md
55
README.md
@@ -448,60 +448,26 @@ or you can use [Kaiser-Yang/blink-cmp-avante](https://github.com/Kaiser-Yang/bli
|
|||||||
<summary>Lua</summary>
|
<summary>Lua</summary>
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
file_selector = {
|
selector = {
|
||||||
--- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil
|
--- @alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil
|
||||||
provider = "fzf",
|
provider = "fzf",
|
||||||
-- Options override for custom providers
|
-- Options override for custom providers
|
||||||
provider_opts = {},
|
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
|
```lua
|
||||||
file_selector = {
|
selector = {
|
||||||
---@param params avante.file_selector.IParams
|
---@param selector avante.ui.Selector
|
||||||
provider = function(params)
|
provider = function(selector)
|
||||||
local filepaths = params.filepaths ---@type string[]
|
local items = selector.items ---@type avante.ui.SelectorItem[]
|
||||||
local title = params.title ---@type string
|
local title = selector.title ---@type string
|
||||||
local handler = params.handler ---@type fun(selected_filepaths: string[]|nil): nil
|
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,
|
--- your customized picker logic here
|
||||||
-- 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
|
|
||||||
})
|
|
||||||
end,
|
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 | |
|
| `:AvanteShowRepoMap` | Show repo map for project's structure | |
|
||||||
| `:AvanteToggle` | Toggle the Avante sidebar | |
|
| `:AvanteToggle` | Toggle the Avante sidebar | |
|
||||||
| `:AvanteModels` | Show model list | |
|
| `:AvanteModels` | Show model list | |
|
||||||
|
| `:AvanteSwitchSelectorProvider` | Switch avante selector provider (e.g. native, telescope, fzf_lua, mini_pick, snacks) | |
|
||||||
|
|
||||||
## Highlight Groups
|
## Highlight Groups
|
||||||
|
|
||||||
|
|||||||
54
README_zh.md
54
README_zh.md
@@ -448,60 +448,26 @@ _请参见 [config.lua#L9](./lua/avante/config.lua) 以获取完整配置_
|
|||||||
<summary>Lua</summary>
|
<summary>Lua</summary>
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
file_selector = {
|
selector = {
|
||||||
--- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil
|
--- @alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil
|
||||||
provider = "fzf",
|
provider = "fzf",
|
||||||
-- 自定义提供者的选项覆盖
|
-- 自定义提供者的选项覆盖
|
||||||
provider_opts = {},
|
provider_opts = {},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
要创建自定义文件选择器,您可以指定一个自定义函数来启动选择器以选择项目,并将选定的项目传递给 `handler` 回调。
|
要创建自定义选择器,您可以指定一个自定义函数来启动选择器以选择项目,并将选定的项目传递给 `on_select` 回调。
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
file_selector = {
|
selector = {
|
||||||
---@param params avante.file_selector.IParams
|
---@param selector avante.ui.Selector
|
||||||
provider = function(params)
|
provider = function(selector)
|
||||||
local filepaths = params.filepaths ---@type string[]
|
local items = selector.items ---@type avante.ui.SelectorItem[]
|
||||||
local title = params.title ---@type string
|
local title = selector.title ---@type string
|
||||||
local handler = params.handler ---@type fun(selected_filepaths: string[]|nil): nil
|
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,
|
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
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ local PromptInput = require("avante.ui.prompt_input")
|
|||||||
---@field toggle avante.ApiToggle
|
---@field toggle avante.ApiToggle
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---@param target_provider FileSelectorProvider
|
---@param target_provider avante.SelectorProvider
|
||||||
function M.switch_file_selector_provider(target_provider)
|
function M.switch_selector_provider(target_provider)
|
||||||
require("avante.config").override({
|
require("avante.config").override({
|
||||||
file_selector = {
|
selector = {
|
||||||
provider = target_provider,
|
provider = target_provider,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -508,11 +508,15 @@ M._defaults = {
|
|||||||
},
|
},
|
||||||
--- @class AvanteFileSelectorConfig
|
--- @class AvanteFileSelectorConfig
|
||||||
file_selector = {
|
file_selector = {
|
||||||
--- @alias FileSelectorProvider "native" | "fzf" | "mini.pick" | "snacks" | "telescope" | string | fun(params: avante.file_selector.IParams|nil): nil
|
provider = nil,
|
||||||
provider = "native",
|
|
||||||
-- Options override for custom providers
|
-- Options override for custom providers
|
||||||
provider_opts = {},
|
provider_opts = {},
|
||||||
},
|
},
|
||||||
|
selector = {
|
||||||
|
---@alias avante.SelectorProvider "native" | "fzf_lua" | "mini_pick" | "snacks" | "telescope" | fun(selector: avante.ui.Selector): nil
|
||||||
|
provider = "native",
|
||||||
|
provider_opts = {},
|
||||||
|
},
|
||||||
suggestion = {
|
suggestion = {
|
||||||
debounce = 600,
|
debounce = 600,
|
||||||
throttle = 600,
|
throttle = 600,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ local Utils = require("avante.utils")
|
|||||||
local Path = require("plenary.path")
|
local Path = require("plenary.path")
|
||||||
local scan = require("plenary.scandir")
|
local scan = require("plenary.scandir")
|
||||||
local Config = require("avante.config")
|
local Config = require("avante.config")
|
||||||
|
local Selector = require("avante.ui.selector")
|
||||||
|
|
||||||
local PROMPT_TITLE = "(Avante) Add a file"
|
local PROMPT_TITLE = "(Avante) Add a file"
|
||||||
|
|
||||||
@@ -168,7 +169,7 @@ function FileSelector:off(event, callback)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function FileSelector:open() self:show_select_ui() end
|
function FileSelector:open() self:show_selector_ui() end
|
||||||
|
|
||||||
function FileSelector:get_filepaths()
|
function FileSelector:get_filepaths()
|
||||||
if type(Config.file_selector.provider_opts.get_filepaths) == "function" then
|
if type(Config.file_selector.provider_opts.get_filepaths) == "function" then
|
||||||
@@ -203,162 +204,56 @@ function FileSelector:get_filepaths()
|
|||||||
:totable()
|
:totable()
|
||||||
end
|
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", "<esc>", 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
|
---@return nil
|
||||||
function FileSelector:show_select_ui()
|
function FileSelector:show_selector_ui()
|
||||||
local function handler(selected_paths) self:handle_path_selection(selected_paths) end
|
local function handler(selected_paths) self:handle_path_selection(selected_paths) end
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
if Config.file_selector.provider == "native" then
|
if Config.file_selector.provider ~= nil then
|
||||||
self:native_ui(handler)
|
Utils.warn("config.file_selector is deprecated, please use config.selector instead!")
|
||||||
elseif Config.file_selector.provider == "fzf" then
|
if type(Config.file_selector.provider) == "function" then
|
||||||
self:fzf_ui(handler)
|
local title = string.format("%s:", PROMPT_TITLE) ---@type string
|
||||||
elseif Config.file_selector.provider == "mini.pick" then
|
local filepaths = self:get_filepaths() ---@type string[]
|
||||||
self:mini_pick_ui(handler)
|
local params = { title = title, filepaths = filepaths, handler = handler } ---@type avante.file_selector.IParams
|
||||||
elseif Config.file_selector.provider == "snacks" then
|
Config.file_selector.provider(params)
|
||||||
self:snacks_picker_ui(handler)
|
else
|
||||||
elseif Config.file_selector.provider == "telescope" then
|
local provider = "native"
|
||||||
self:telescope_ui(handler)
|
if Config.file_selector.provider == "native" then
|
||||||
elseif type(Config.file_selector.provider) == "function" then
|
provider = "native"
|
||||||
local title = string.format("%s:", PROMPT_TITLE) ---@type string
|
elseif Config.file_selector.provider == "fzf" then
|
||||||
local filepaths = self:get_filepaths() ---@type string[]
|
provider = "fzf_lua"
|
||||||
local params = { title = title, filepaths = filepaths, handler = handler } ---@type avante.file_selector.IParams
|
elseif Config.file_selector.provider == "mini.pick" then
|
||||||
Config.file_selector.provider(params)
|
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
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
local Utils = require("avante.utils")
|
local Utils = require("avante.utils")
|
||||||
local Path = require("avante.path")
|
local Path = require("avante.path")
|
||||||
|
local Config = require("avante.config")
|
||||||
|
local Selector = require("avante.ui.selector")
|
||||||
|
|
||||||
---@class avante.HistorySelector
|
---@class avante.HistorySelector
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -8,8 +10,10 @@ local M = {}
|
|||||||
---@return table?
|
---@return table?
|
||||||
local function to_selector_item(history)
|
local function to_selector_item(history)
|
||||||
local timestamp = #history.entries > 0 and history.entries[#history.entries].timestamp or history.timestamp
|
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 {
|
return {
|
||||||
name = history.title .. " - " .. timestamp .. " (" .. #history.entries .. ")",
|
name = name,
|
||||||
filename = history.filename,
|
filename = history.filename,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -30,68 +34,33 @@ function M.open(bufnr, cb)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local has_telescope, _ = pcall(require, "telescope")
|
local selector = Selector:new({
|
||||||
if has_telescope then
|
provider = Config.selector.provider,
|
||||||
local pickers = require("telescope.pickers")
|
title = "Select Avante History",
|
||||||
local finders = require("telescope.finders")
|
items = vim
|
||||||
local previewers = require("telescope.previewers")
|
.iter(selector_items)
|
||||||
local actions = require("telescope.actions")
|
:map(
|
||||||
local action_state = require("telescope.actions.state")
|
function(item)
|
||||||
local conf = require("telescope.config").values
|
return {
|
||||||
pickers
|
id = item.filename,
|
||||||
.new({}, {
|
title = item.name,
|
||||||
prompt_title = "Select Avante History",
|
}
|
||||||
finder = finders.new_table(vim.iter(selector_items):map(function(item) return item.name end):totable()),
|
end
|
||||||
sorter = conf.generic_sorter({}),
|
)
|
||||||
previewer = previewers.new_buffer_previewer({
|
:totable(),
|
||||||
title = "Preview",
|
on_select = function(item_ids)
|
||||||
define_preview = function(self, entry)
|
if not item_ids then return end
|
||||||
if not entry then return end
|
if #item_ids == 0 then return end
|
||||||
local item = vim.iter(selector_items):find(function(item) return item.name == entry.value end)
|
cb(item_ids[1])
|
||||||
if not item then return end
|
end,
|
||||||
local history = Path.history.load(vim.api.nvim_get_current_buf(), item.filename)
|
get_preview_content = function(item_id)
|
||||||
local Sidebar = require("avante.sidebar")
|
local history = Path.history.load(vim.api.nvim_get_current_buf(), item_id)
|
||||||
local content = Sidebar.render_history_content(history)
|
local Sidebar = require("avante.sidebar")
|
||||||
local lines = vim.split(content or "", "\n")
|
local content = Sidebar.render_history_content(history)
|
||||||
-- Ensure the buffer exists and is valid before setting lines
|
return content, "markdown"
|
||||||
if vim.api.nvim_buf_is_valid(self.state.bufnr) then
|
end,
|
||||||
vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines)
|
})
|
||||||
-- Set filetype after content is loaded
|
selector:open()
|
||||||
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", "<CR>", 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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local Utils = require("avante.utils")
|
local Utils = require("avante.utils")
|
||||||
local Config = require("avante.config")
|
local Config = require("avante.config")
|
||||||
|
local Selector = require("avante.ui.selector")
|
||||||
|
|
||||||
---@class avante.ModelSelector
|
---@class avante.ModelSelector
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -33,10 +34,21 @@ function M.open()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.ui.select(models, {
|
local items = vim
|
||||||
prompt = "Select Avante Model:",
|
.iter(models)
|
||||||
format_item = function(item) return item.name end,
|
:map(function(item)
|
||||||
}, function(choice)
|
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
|
if not choice then return end
|
||||||
|
|
||||||
-- Switch provider if needed
|
-- Switch provider if needed
|
||||||
@@ -52,7 +64,18 @@ function M.open()
|
|||||||
})
|
})
|
||||||
|
|
||||||
Utils.info("Switched to model: " .. choice.name)
|
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
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
55
lua/avante/ui/selector/init.lua
Normal file
55
lua/avante/ui/selector/init.lua
Normal file
@@ -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
|
||||||
49
lua/avante/ui/selector/providers/fzf_lua.lua
Normal file
49
lua/avante/ui/selector/providers/fzf_lua.lua
Normal file
@@ -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
|
||||||
37
lua/avante/ui/selector/providers/mini_pick.lua
Normal file
37
lua/avante/ui/selector/providers/mini_pick.lua
Normal file
@@ -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
|
||||||
21
lua/avante/ui/selector/providers/native.lua
Normal file
21
lua/avante/ui/selector/providers/native.lua
Normal file
@@ -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
|
||||||
58
lua/avante/ui/selector/providers/snacks.lua
Normal file
58
lua/avante/ui/selector/providers/snacks.lua
Normal file
@@ -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
|
||||||
92
lua/avante/ui/selector/providers/telescope.lua
Normal file
92
lua/avante/ui/selector/providers/telescope.lua
Normal file
@@ -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", "<esc>", 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
|
||||||
@@ -122,11 +122,11 @@ cmd("SwitchProvider", function(opts) require("avante.api").switch_provider(vim.t
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
cmd(
|
cmd(
|
||||||
"SwitchFileSelectorProvider",
|
"SwitchSelectorProvider",
|
||||||
function(opts) require("avante.api").switch_file_selector_provider(vim.trim(opts.args or "")) end,
|
function(opts) require("avante.api").switch_selector_provider(vim.trim(opts.args or "")) end,
|
||||||
{
|
{
|
||||||
nargs = 1,
|
nargs = 1,
|
||||||
desc = "avante: switch file selector provider",
|
desc = "avante: switch selector provider",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cmd("Clear", function(opts)
|
cmd("Clear", function(opts)
|
||||||
|
|||||||
Reference in New Issue
Block a user