diff --git a/README.md b/README.md index d5d382b6..29647609 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ The only **required** plugin dependency is [plenary.nvim](https://github.com/nvi - **[recommended]** [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim): for search and quick-switch functionality. - [Mini.Pick](https://github.com/echasnovski/mini.pick) from the mini.nvim library: an alternative to telescope for search and quick-switch functionality. - [ibhagwan/fzf-lua](https://github.com/ibhagwan/fzf-lua): another alternative to telescope for search and quick-switch functionality. +- [Snacks.Picker](https://github.com/folke/snacks.nvim/blob/main/docs/picker.md) from the snacks.nvim library: an alternative to mini and telescope for search and quick-switch functionality. **Syntax highlighting:** @@ -412,7 +413,7 @@ This is a complete list of all of the options that can be passed to `require("ob open_app_foreground = false, picker = { - -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', or 'mini.pick'. + -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', 'mini.pick' or 'snacks.pick'. name = "telescope.nvim", -- Optional, configure key mappings for the picker. These are the defaults. -- Not all pickers support all mappings. diff --git a/doc/obsidian.txt b/doc/obsidian.txt index fc053994..9e5c1006 100644 --- a/doc/obsidian.txt +++ b/doc/obsidian.txt @@ -252,6 +252,7 @@ dependencies that enhance the obsidian.nvim experience. - **[recommended]** nvim-telescope/telescope.nvim : for search and quick-switch functionality. - Mini.Pick from the mini.nvim library: an alternative to telescope for search and quick-switch functionality. - ibhagwan/fzf-lua : another alternative to telescope for search and quick-switch functionality. +- Snacks.Pick : another alternative to telescope for search and quick-switch functionality. **Syntax highlighting:** @@ -468,7 +469,7 @@ carefully and customize it to your needs: open_app_foreground = false, picker = { - -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', or 'mini.pick'. + -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', 'mini.pick' or 'snacks.pick'. name = "telescope.nvim", -- Optional, configure key mappings for the picker. These are the defaults. -- Not all pickers support all mappings. diff --git a/lua/obsidian/commands/debug.lua b/lua/obsidian/commands/debug.lua index 3013bc38..e1d855a6 100644 --- a/lua/obsidian/commands/debug.lua +++ b/lua/obsidian/commands/debug.lua @@ -51,7 +51,7 @@ return function(client, data) end log.lazy_info "Dependencies:" - for _, plugin in ipairs { "plenary.nvim", "nvim-cmp", "telescope.nvim", "fzf-lua", "mini.pick" } do + for _, plugin in ipairs { "plenary.nvim", "nvim-cmp", "telescope.nvim", "fzf-lua", "mini.pick", "snacks.pick" } do local plugin_info = util.get_plugin_info(plugin) if plugin_info ~= nil then log.lazy_info(" ✓ %s: %s", plugin, plugin_info.commit or "unknown") diff --git a/lua/obsidian/config.lua b/lua/obsidian/config.lua index 4f3ae84d..1d32f361 100644 --- a/lua/obsidian/config.lua +++ b/lua/obsidian/config.lua @@ -343,6 +343,7 @@ config.Picker = { telescope = "telescope.nvim", fzf_lua = "fzf-lua", mini = "mini.pick", + snacks = "snacks.pick", } ---@class obsidian.config.PickerOpts diff --git a/lua/obsidian/pickers/_snacks.lua b/lua/obsidian/pickers/_snacks.lua new file mode 100644 index 00000000..4bf632bf --- /dev/null +++ b/lua/obsidian/pickers/_snacks.lua @@ -0,0 +1,162 @@ +local snacks_picker = require "snacks.picker" + +local Path = require "obsidian.path" +local abc = require "obsidian.abc" +local Picker = require "obsidian.pickers.picker" + +local function debug_once(msg, ...) +-- vim.notify(msg .. vim.inspect(...)) +end + +---@param mapping table +---@return table +local function notes_mappings(mapping) + if type(mapping) == "table" then + opts = { win = { input = { keys = {} } }, actions = {} }; + for k, v in pairs(mapping) do + local name = string.gsub(v.desc, " ", "_") + opts.win.input.keys = { + [k] = { name, mode = { "n", "i" }, desc = v.desc } + } + opts.actions[name] = function(picker, item) + debug_once("mappings :", item) + picker:close() + vim.schedule(function() + v.callback(item.value or item._path) + end) + end + end + return opts + end + return {} +end + +---@class obsidian.pickers.SnacksPicker : obsidian.Picker +local SnacksPicker = abc.new_class({ + ---@diagnostic disable-next-line: unused-local + __tostring = function(self) + return "SnacksPicker()" + end, +}, Picker) + +---@param opts obsidian.PickerFindOpts|? Options. +SnacksPicker.find_files = function(self, opts) + opts = opts or {} + + ---@type obsidian.Path + local dir = opts.dir.filename and Path:new(opts.dir.filename) or self.client.dir + + local map = vim.tbl_deep_extend("force", {}, + notes_mappings(opts.selection_mappings)) + + local pick_opts = vim.tbl_extend("force", map or {}, { + source = "files", + title = opts.prompt_title, + cwd = tostring(dir), + confirm = function(picker, item, action) + picker:close() + if item then + if opts.callback then + debug_once("find files callback: ", item) + opts.callback(item._path) + else + debug_once("find files jump: ", item) + snacks_picker.actions.jump(picker, item, action) + end + end + end, + }) + local t = snacks_picker.pick(pick_opts) +end + +---@param opts obsidian.PickerGrepOpts|? Options. +SnacksPicker.grep = function(self, opts) + opts = opts or {} + + debug_once("grep opts : ", opts) + + ---@type obsidian.Path + local dir = opts.dir.filename and Path:new(opts.dir.filename) or self.client.dir + + local map = vim.tbl_deep_extend("force", {}, + notes_mappings(opts.selection_mappings)) + + local pick_opts = vim.tbl_extend("force", map or {}, { + source = "grep", + title = opts.prompt_title, + cwd = tostring(dir), + confirm = function(picker, item, action) + picker:close() + if item then + if opts.callback then + debug_once("grep callback: ", item) + opts.callback(item._path or item.filename) + else + debug_once("grep jump: ", item) + snacks_picker.actions.jump(picker, item, action) + end + end + end, + }) + snacks_picker.pick(pick_opts) +end + +---@param values string[]|obsidian.PickerEntry[] +---@param opts obsidian.PickerPickOpts|? Options. +---@diagnostic disable-next-line: unused-local +SnacksPicker.pick = function(self, values, opts) + self.calling_bufnr = vim.api.nvim_get_current_buf() + + opts = opts or {} + + debug_once("pick opts: ", opts) + + local buf = opts.buf or vim.api.nvim_get_current_buf() + + local entries = {} + for _, value in ipairs(values) do + if type(value) == "string" then + table.insert(entries, { + text = value, + value = value, + }) + elseif value.valid ~= false then + local name = self:_make_display(value) + table.insert(entries, { + text = name, + buf = buf, + filename = value.filename, + value = value.value, + pos = { value.lnum, value.col or 0 }, + }) + end + end + + local map = vim.tbl_deep_extend("force", {}, + notes_mappings(opts.selection_mappings)) + + local pick_opts = vim.tbl_extend("force", map or {}, { + tilte = opts.prompt_title, + items = entries, + layout = { + preview = false + }, + format = "text", + confirm = function(picker, item, action) + picker:close() + if item then + if opts.callback then + debug_once("pick callback: ", item) + opts.callback(item.value) + else + debug_once("pick jump: ", item) + snacks_picker.actions.jump(picker, item, action) + end + end + end, + }) + + local entry = snacks_picker.pick(pick_opts) +end + +return SnacksPicker diff --git a/lua/obsidian/pickers/init.lua b/lua/obsidian/pickers/init.lua index 7b2decd3..51d5ad88 100644 --- a/lua/obsidian/pickers/init.lua +++ b/lua/obsidian/pickers/init.lua @@ -13,7 +13,7 @@ M.get = function(client, picker_name) if picker_name then picker_name = string.lower(picker_name) else - for _, name in ipairs { PickerName.telescope, PickerName.fzf_lua, PickerName.mini } do + for _, name in ipairs { PickerName.telescope, PickerName.fzf_lua, PickerName.mini, PickerName.snacks } do local ok, res = pcall(M.get, client, name) if ok then return res @@ -28,6 +28,8 @@ M.get = function(client, picker_name) return require("obsidian.pickers._mini").new(client) elseif picker_name == string.lower(PickerName.fzf_lua) then return require("obsidian.pickers._fzf").new(client) + elseif picker_name == string.lower(PickerName.snacks) then + return require("obsidian.pickers._snacks").new(client) elseif picker_name then error("not implemented for " .. picker_name) end