From 295ad84b6351d4922410d83a21066045aed2e37a Mon Sep 17 00:00:00 2001 From: Anurag Kumar Singh Date: Wed, 9 Sep 2026 10:20:22 +0530 Subject: [PATCH 1/6] Added support for snack picker --- lua/platformio/init.lua | 22 ++++-- lua/platformio/pickers/init.lua | 4 ++ lua/platformio/pickers/snacks.lua | 114 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 lua/platformio/pickers/snacks.lua diff --git a/lua/platformio/init.lua b/lua/platformio/init.lua index 8f0e5bd..7e6e41f 100644 --- a/lua/platformio/init.lua +++ b/lua/platformio/init.lua @@ -189,14 +189,22 @@ function M.setup(user_config) user_config.clangd_source = M.config.clangd_source end end - if - user_config.picker_backend - and user_config.picker_backend ~= 'auto' - and user_config.picker_backend ~= 'telescope' - and user_config.picker_backend ~= 'ui_select' - then + local valid_picker_backends = { + auto = true, + telescope = true, + snacks = true, + ui_select = true, + } + if user_config.picker_backend and not valid_picker_backends[user_config.picker_backend] then vim.api.nvim_echo( - { { 'Invalid picker backend {allowed "auto", "telescope" or "ui_select"} (default "' .. M.config.picker_backend .. '" will be used)', 'ErrorMsg' } }, + { + { + 'Invalid picker backend {allowed "auto", "telescope", "snacks", "mini_pick" or "ui_select"} (default "' + .. M.config.picker_backend + .. '" will be used)', + 'ErrorMsg', + }, + }, true, {} ) diff --git a/lua/platformio/pickers/init.lua b/lua/platformio/pickers/init.lua index b9e6163..668c78e 100644 --- a/lua/platformio/pickers/init.lua +++ b/lua/platformio/pickers/init.lua @@ -17,6 +17,10 @@ local function get_backend() return load_backend('telescope') or load_backend('ui_select') end + if backend_name == 'snacks' then + return load_backend('snacks') or load_backend('ui_select') + end + if backend_name == 'ui_select' then return load_backend('ui_select') end diff --git a/lua/platformio/pickers/snacks.lua b/lua/platformio/pickers/snacks.lua new file mode 100644 index 0000000..8bd4c8b --- /dev/null +++ b/lua/platformio/pickers/snacks.lua @@ -0,0 +1,114 @@ +local Snacks = require('snacks') +local align = Snacks.picker.util.align + +local M = {} + +local SEP = ' ▏' + +local function to_items(list, text_fn) + local items = {} + for i, entry in ipairs(list) do + items[i] = { idx = i, text = text_fn(entry), data = entry } + end + return items +end + +local function plain_format(item) + return { { item.text } } +end + +local function framework_text(framework) + return framework +end + +local function board_text(board) + return string.format('%s %s %s', board.name or '', board.vendor or '', board.platform or '') +end + +local function board_format(item) + local board = item.data + return { + { align(board.name or '', 35, { truncate = true }), 'SnacksPickerLabel' }, + { SEP, 'SnacksPickerDelim' }, + { align(board.vendor or '', 20, { truncate = true }), 'SnacksPickerComment' }, + { SEP, 'SnacksPickerDelim' }, + { board.platform or '' }, + } +end + +local function library_text(lib) + local owner = (lib.owner and lib.owner.username) or '' + return string.format('%s %s %s', lib.name or '', owner, lib.description or '') +end + +local function library_format(item) + local lib = item.data + local owner = (lib.owner and lib.owner.username) or '' + return { + { align(lib.name or '', 20, { truncate = true }), 'SnacksPickerLabel' }, + { SEP, 'SnacksPickerDelim' }, + { align(owner, 20, { truncate = true }), 'SnacksPickerComment' }, + { SEP, 'SnacksPickerDelim' }, + { lib.description or '' }, + } +end + +local function terminal_text(entry) + local is_hidden = vim.api.nvim_buf_is_loaded(entry.term.bufnr) and (vim.fn.bufwinid(entry.term.bufnr) == -1) + return string.format('%d:%s (hidden: %s)', entry.term.id, entry.termtype, tostring(is_hidden)) +end + +local function terminal_format(item) + local entry = item.data + local is_hidden = vim.api.nvim_buf_is_loaded(entry.term.bufnr) and (vim.fn.bufwinid(entry.term.bufnr) == -1) + return { + { align(tostring(entry.term.id), 3, { align = 'right' }), 'SnacksPickerLabel' }, + { SEP, 'SnacksPickerDelim' }, + { align(entry.termtype, 8), 'SnacksPickerComment' }, + { SEP, 'SnacksPickerDelim' }, + { 'hidden: ' .. tostring(is_hidden) }, + } +end + +local function data_preview(ctx) + vim.bo[ctx.buf].filetype = 'lua' + vim.bo[ctx.buf].modifiable = true + vim.api.nvim_buf_set_lines(ctx.buf, 0, -1, false, vim.split(vim.inspect(ctx.item.data), '\n')) + vim.bo[ctx.buf].modifiable = false + return true +end + +local function open_picker(items, title, on_select, format_fn, preview_fn, layout) + Snacks.picker.pick({ + title = title, + items = items, + format = format_fn or plain_format, + preview = preview_fn, + layout = layout, + confirm = function(picker, item) + picker:close() + if item then + on_select(item.data) + end + end, + }) +end + +function M.pick_framework(frameworks, on_select) + -- a bare framework name has nothing worth previewing, so skip the preview pane entirely + open_picker(to_items(frameworks, framework_text), 'Frameworks', on_select, nil, nil, { preview = false }) +end + +function M.pick_board(boards, on_select) + open_picker(to_items(boards, board_text), 'Boards', on_select, board_format, data_preview) +end + +function M.pick_library(libraries, on_select) + open_picker(to_items(libraries, library_text), 'Libraries', on_select, library_format, data_preview) +end + +function M.pick_terminal(terminals, on_select) + open_picker(to_items(terminals, terminal_text), 'PIO terminals', on_select, terminal_format, data_preview) +end + +return M From bbbbbbc1aa2c7bfde11e9ae2a91a183a40267a2c Mon Sep 17 00:00:00 2001 From: Anurag Kumar Singh Date: Wed, 9 Sep 2026 10:36:13 +0530 Subject: [PATCH 2/6] Added support for mini.pick picker --- lua/platformio/init.lua | 1 + lua/platformio/pickers/init.lua | 4 + lua/platformio/pickers/mini_pick.lua | 109 +++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 lua/platformio/pickers/mini_pick.lua diff --git a/lua/platformio/init.lua b/lua/platformio/init.lua index 7e6e41f..41e09a5 100644 --- a/lua/platformio/init.lua +++ b/lua/platformio/init.lua @@ -193,6 +193,7 @@ function M.setup(user_config) auto = true, telescope = true, snacks = true, + mini_pick = true, ui_select = true, } if user_config.picker_backend and not valid_picker_backends[user_config.picker_backend] then diff --git a/lua/platformio/pickers/init.lua b/lua/platformio/pickers/init.lua index 668c78e..75dd41c 100644 --- a/lua/platformio/pickers/init.lua +++ b/lua/platformio/pickers/init.lua @@ -21,6 +21,10 @@ local function get_backend() return load_backend('snacks') or load_backend('ui_select') end + if backend_name == 'mini_pick' then + return load_backend('mini_pick') or load_backend('ui_select') + end + if backend_name == 'ui_select' then return load_backend('ui_select') end diff --git a/lua/platformio/pickers/mini_pick.lua b/lua/platformio/pickers/mini_pick.lua new file mode 100644 index 0000000..e9f47c1 --- /dev/null +++ b/lua/platformio/pickers/mini_pick.lua @@ -0,0 +1,109 @@ +local MiniPick = require('mini.pick') + +local M = {} + +local SEP = ' | ' +local COLUMN_NS = vim.api.nvim_create_namespace('platformio_pick_columns') + +local function to_items(list, label_fn) + local items = {} + for i, entry in ipairs(list) do + items[i] = { text = label_fn(entry), data = entry } + end + return items +end + +local function pad(text, width) + text = text or '' + if #text >= width then + return text:sub(1, width) + end + return text .. string.rep(' ', width - #text) +end + +local function framework_label(framework) + return framework +end + +local BOARD_NAME_W, BOARD_VENDOR_W = 35, 20 + +local function board_label(board) + return pad(board.name or '', BOARD_NAME_W) .. SEP .. pad(board.vendor or '', BOARD_VENDOR_W) .. SEP .. (board.platform or '') +end + +local LIB_NAME_W, LIB_OWNER_W = 20, 20 + +local function library_label(lib) + local owner = (lib.owner and lib.owner.username) or '' + return pad(lib.name or '', LIB_NAME_W) .. SEP .. pad(owner, LIB_OWNER_W) .. SEP .. (lib.description or '') +end + +local function terminal_label(entry) + local is_hidden = vim.api.nvim_buf_is_loaded(entry.term.bufnr) and (vim.fn.bufwinid(entry.term.bufnr) == -1) + return string.format('%d:%s (hidden: %s)', entry.term.id, entry.termtype, tostring(is_hidden)) +end + +local function data_preview(buf_id, item) + vim.bo[buf_id].filetype = 'lua' + vim.bo[buf_id].modifiable = true + vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, vim.split(vim.inspect(item.data), '\n')) + vim.bo[buf_id].modifiable = false +end + +-- Colors the two padded columns and the separators on top of mini.pick's default +-- rendering (which still handles line text + query match highlighting). +local function columns_show(col1_w, col2_w, hl1, hl2) + return function(buf_id, items, query) + MiniPick.default_show(buf_id, items, query) + vim.api.nvim_buf_clear_namespace(buf_id, COLUMN_NS, 0, -1) + local sep_len = #SEP + local col2_start = col1_w + sep_len + local col2_end = col2_start + col2_w + local opts = { hl_mode = 'combine', priority = 100 } + for i = 1, #items do + local row = i - 1 + pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, 0, vim.tbl_extend('force', opts, { end_col = col1_w, hl_group = hl1 })) + pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col1_w, vim.tbl_extend('force', opts, { end_col = col2_start, hl_group = 'Delimiter' })) + pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col2_start, vim.tbl_extend('force', opts, { end_col = col2_end, hl_group = hl2 })) + pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col2_end, vim.tbl_extend('force', opts, { end_col = col2_end + sep_len, hl_group = 'Delimiter' })) + end + end +end + +local board_show = columns_show(BOARD_NAME_W, BOARD_VENDOR_W, 'Title', 'Comment') +local library_show = columns_show(LIB_NAME_W, LIB_OWNER_W, 'Title', 'Comment') + +local function open_picker(items, name, on_select, preview_fn, show_fn) + MiniPick.start({ + source = { + items = items, + name = name, + preview = preview_fn, + show = show_fn, + choose = function(item) + if item then + on_select(item.data) + end + return false + end, + }, + }) +end + +function M.pick_framework(frameworks, on_select) + open_picker(to_items(frameworks, framework_label), 'Frameworks', on_select) +end + +function M.pick_board(boards, on_select) + open_picker(to_items(boards, board_label), 'Boards', on_select, data_preview, board_show) +end + +function M.pick_library(libraries, on_select) + open_picker(to_items(libraries, library_label), 'Libraries', on_select, data_preview, library_show) +end + +function M.pick_terminal(terminals, on_select) + open_picker(to_items(terminals, terminal_label), 'PIO terminals', on_select) +end + +return M From 7a76ef03fbc90be1c71de798ebb8cc062b0b0db5 Mon Sep 17 00:00:00 2001 From: Anurag Kumar Singh Date: Wed, 9 Sep 2026 11:03:57 +0530 Subject: [PATCH 3/6] New picker_backed config and order to incorporate snacks and mini_pick --- lua/platformio/pickers/init.lua | 55 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/lua/platformio/pickers/init.lua b/lua/platformio/pickers/init.lua index 75dd41c..508dd0a 100644 --- a/lua/platformio/pickers/init.lua +++ b/lua/platformio/pickers/init.lua @@ -2,6 +2,8 @@ local M = {} local config = require('platformio').config +local AUTO_ORDER = { 'telescope', 'snacks', 'mini_pick', 'ui_select' } + local function load_backend(name) local ok, backend = pcall(require, 'platformio.pickers.' .. name) if ok then @@ -10,62 +12,55 @@ local function load_backend(name) return nil end +-- 'auto' tries backends in order and silently falls back; an explicit backend +-- name that isn't installed reports why and does not fall back to another one. local function get_backend() local backend_name = config.picker_backend or 'auto' - if backend_name == 'telescope' then - return load_backend('telescope') or load_backend('ui_select') - end - - if backend_name == 'snacks' then - return load_backend('snacks') or load_backend('ui_select') - end - - if backend_name == 'mini_pick' then - return load_backend('mini_pick') or load_backend('ui_select') + if backend_name == 'auto' then + for _, name in ipairs(AUTO_ORDER) do + local backend = load_backend(name) + if backend then + return backend + end + end + vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) + return nil end - if backend_name == 'ui_select' then - return load_backend('ui_select') + local backend = load_backend(backend_name) + if not backend then + vim.notify(string.format('PlatformIO: picker backend "%s" is not installed.', backend_name), vim.log.levels.ERROR) end - - return load_backend('telescope') or load_backend('ui_select') + return backend end function M.pick_board(boards, on_select) local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return + if backend then + backend.pick_board(boards, on_select) end - backend.pick_board(boards, on_select) end function M.pick_framework(frameworks, on_select) local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return + if backend then + backend.pick_framework(frameworks, on_select) end - backend.pick_framework(frameworks, on_select) end function M.pick_library(libraries, on_select) local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return + if backend then + backend.pick_library(libraries, on_select) end - backend.pick_library(libraries, on_select) end function M.pick_terminal(terminals, on_select) local backend = get_backend() - if not backend then - vim.notify('No picker backend available for PlatformIO.', vim.log.levels.ERROR) - return + if backend then + backend.pick_terminal(terminals, on_select) end - backend.pick_terminal(terminals, on_select) end return M From e037e2212061b83dffd40a8306be8cbfa0cb6365 Mon Sep 17 00:00:00 2001 From: Anurag Kumar Singh Date: Wed, 9 Sep 2026 11:10:48 +0530 Subject: [PATCH 4/6] Docs for new pickers --- doc/platformio.txt | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/doc/platformio.txt b/doc/platformio.txt index 278d369..58fb83c 100644 --- a/doc/platformio.txt +++ b/doc/platformio.txt @@ -130,16 +130,30 @@ picker_backend *platformio-picker* Allowed values: - `auto` - Default. Tries Telescope backend first. If Telescope is unavailable, - falls back to `vim.ui.select` backend. + Default. Tries backends in this order, using the first one that is + installed: `telescope`, then `snacks`, then `mini_pick`, then + `ui_select`. `ui_select` is always available since it only needs + Neovim's built-in `vim.ui.select`, so `auto` never fails to find + a backend. - `telescope` - Force Telescope picker backend. If Telescope backend cannot be loaded, - plugin falls back to `vim.ui.select` as a safety fallback. + Force the Telescope picker backend (requires telescope.nvim). + + - `snacks` + Force the Snacks picker backend (requires folke/snacks.nvim with + its `picker` module enabled). + + - `mini_pick` + Force the mini.pick backend (requires nvim-mini/mini.pick). - `ui_select` Force `vim.ui.select` backend. This works with Neovim built-in `vim.ui.select` or any plugin that overrides it (for example, - telescope-ui-select, dressing.nvim, snacks picker adapters, etc.). + telescope-ui-select, dressing.nvim, snacks' `ui_select` option, + mini.pick's `MiniPick.ui_select`, etc.). + + Unlike `auto`, explicitly requesting `telescope`, `snacks`, `mini_pick`, + or `ui_select` does NOT fall back to another backend if the requested + one isn't installed—it reports an error and the picker does not open. vim:tw=78:sw=4:ts=8:ft=help:norl:noet: From 50fc44c204e7ac5b38d28a602743fbe61a60be7b Mon Sep 17 00:00:00 2001 From: Anurag Kumar Singh Date: Wed, 9 Sep 2026 11:16:18 +0530 Subject: [PATCH 5/6] updated README --- README.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c6dda0b..8547a98 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,25 @@ return { -- Dependencies are lazy-loaded by default unless specified otherwise. dependencies = { - { 'nvim-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, + { 'akinsho/toggleterm.nvim' }, { 'nvim-lua/plenary.nvim' }, { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' } + { 'nvim-treesitter/nvim-treesitter' }, - -- install picker based on your likeing - { 'akinsho/toggleterm.nvim' }, + + -- install picker based on your likeing, remove or ignore rest + + -- telescope + { 'nvim-telescope/telescope.nvim' }, + { 'nvim-telescope/telescope-ui-select.nvim' }, + + -- Snacks picker + {'folke/snacks.nvim', opts = { + picker = {enabled = true, ui_select = true } + }}, + + -- Mini.Pick + {'nvim-mini/mini.pick'}, }, } ``` @@ -55,7 +66,8 @@ return { vim.g.pioConfig ={ lsp = 'clangd', -- value: clangd | ccls clangd_source = 'ccls', -- value: ccls | compiledb, For detailed explation check :help platformio-clangd_source - picker_backend = 'auto', -- value: auto | telescope | ui_select, default: auto, check :help platformio-picker + picker_backend = 'auto', -- value: auto | telescope | snacks | mini_pick | ui_select + -- default: auto, check :help platformio-picker menu_key = '\\', -- replace this menu key to your convenience debug = false -- enable debug messages } @@ -111,11 +123,23 @@ return { -- Dependencies are lazy-loaded by default unless specified otherwise. dependencies = { { 'akinsho/toggleterm.nvim' }, - { 'nvim-telescope/telescope.nvim' }, - { 'nvim-telescope/telescope-ui-select.nvim' }, { 'nvim-lua/plenary.nvim' }, { 'folke/which-key.nvim' }, - { 'nvim-treesitter/nvim-treesitter' } + { 'nvim-treesitter/nvim-treesitter' }, + + -- install picker based on your likeing, remove or ignore rest + + -- telescope + { 'nvim-telescope/telescope.nvim' }, + { 'nvim-telescope/telescope-ui-select.nvim' }, + + -- Snacks picker + {'folke/snacks.nvim', opts = { + picker = {enabled = true, ui_select = true } + }}, + + -- Mini.Pick + {'nvim-mini/mini.pick'}, }, } ``` From ad5cf6fc0107c69ca04ec963e46bcf54c699df3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Sep 2026 06:21:51 +0000 Subject: [PATCH 6/6] style: auto format lua --- lua/platformio/init.lua | 16 ++++++---------- lua/platformio/pickers/mini_pick.lua | 9 ++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lua/platformio/init.lua b/lua/platformio/init.lua index 41e09a5..6ed6840 100644 --- a/lua/platformio/init.lua +++ b/lua/platformio/init.lua @@ -197,18 +197,14 @@ function M.setup(user_config) ui_select = true, } if user_config.picker_backend and not valid_picker_backends[user_config.picker_backend] then - vim.api.nvim_echo( + vim.api.nvim_echo({ { - { - 'Invalid picker backend {allowed "auto", "telescope", "snacks", "mini_pick" or "ui_select"} (default "' - .. M.config.picker_backend - .. '" will be used)', - 'ErrorMsg', - }, + 'Invalid picker backend {allowed "auto", "telescope", "snacks", "mini_pick" or "ui_select"} (default "' + .. M.config.picker_backend + .. '" will be used)', + 'ErrorMsg', }, - true, - {} - ) + }, true, {}) user_config.picker_backend = M.config.picker_backend end diff --git a/lua/platformio/pickers/mini_pick.lua b/lua/platformio/pickers/mini_pick.lua index e9f47c1..5ecdc93 100644 --- a/lua/platformio/pickers/mini_pick.lua +++ b/lua/platformio/pickers/mini_pick.lua @@ -65,7 +65,14 @@ local function columns_show(col1_w, col2_w, hl1, hl2) pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, 0, vim.tbl_extend('force', opts, { end_col = col1_w, hl_group = hl1 })) pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col1_w, vim.tbl_extend('force', opts, { end_col = col2_start, hl_group = 'Delimiter' })) pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col2_start, vim.tbl_extend('force', opts, { end_col = col2_end, hl_group = hl2 })) - pcall(vim.api.nvim_buf_set_extmark, buf_id, COLUMN_NS, row, col2_end, vim.tbl_extend('force', opts, { end_col = col2_end + sep_len, hl_group = 'Delimiter' })) + pcall( + vim.api.nvim_buf_set_extmark, + buf_id, + COLUMN_NS, + row, + col2_end, + vim.tbl_extend('force', opts, { end_col = col2_end + sep_len, hl_group = 'Delimiter' }) + ) end end end