From 82f96ff91d87433aedc9ae9061dd255a0b3ef0ef Mon Sep 17 00:00:00 2001 From: BYT0723 Date: Wed, 19 Mar 2025 13:04:33 +0800 Subject: [PATCH 1/5] feat: `folke/snacks.nvim` picker support --- lua/leetcode/config/template.lua | 2 +- lua/leetcode/picker/init.lua | 13 ++++- lua/leetcode/picker/language/snacks.lua | 63 ++++++++++++++++++++++++ lua/leetcode/picker/question/snacks.lua | 63 ++++++++++++++++++++++++ lua/leetcode/picker/tabs/snacks.lua | 65 +++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 lua/leetcode/picker/language/snacks.lua create mode 100644 lua/leetcode/picker/question/snacks.lua create mode 100644 lua/leetcode/picker/tabs/snacks.lua diff --git a/lua/leetcode/config/template.lua b/lua/leetcode/config/template.lua index 40c5779c..82be3847 100644 --- a/lua/leetcode/config/template.lua +++ b/lua/leetcode/config/template.lua @@ -38,7 +38,7 @@ ---@alias lc.storage table<"cache"|"home", string> ----@alias lc.picker { provider?: "fzf-lua" | "telescope" } +---@alias lc.picker { provider?: "fzf-lua" | "telescope" | "snacks-picker" } ---@class lc.UserConfig local M = { diff --git a/lua/leetcode/picker/init.lua b/lua/leetcode/picker/init.lua index 8fabd228..df23fa01 100644 --- a/lua/leetcode/picker/init.lua +++ b/lua/leetcode/picker/init.lua @@ -1,7 +1,7 @@ local log = require("leetcode.logger") local config = require("leetcode.config") ----@return "fzf" | "telescope" +---@return "fzf" | "telescope" | "snacks" local function resolve_provider() ---@type string local provider = config.user.picker.provider @@ -15,9 +15,18 @@ local function resolve_provider() if telescope_ok then return "telescope" end + local snacks_ok = pcall(require, "snacks.picker") + if snacks_ok then + return "snacks" + end error("no supported picker provider found") else - local provider_ok = pcall(require, provider) + local mod = provider + if provider == "snacks-picker" then + provider = "snacks" + mod = "snacks.picker" + end + local provider_ok = pcall(require, mod) assert(provider_ok, ("specified picker provider not found: `%s`"):format(provider)) return provider == "fzf-lua" and "fzf" or provider end diff --git a/lua/leetcode/picker/language/snacks.lua b/lua/leetcode/picker/language/snacks.lua new file mode 100644 index 00000000..c3241ae5 --- /dev/null +++ b/lua/leetcode/picker/language/snacks.lua @@ -0,0 +1,63 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") +local picker = require("snacks.picker") + +local language_picker = require("leetcode.picker.language") + +---@param question lc.ui.Question +return function(question, cb) + local items = language_picker.items(question.q.code_snippets) + local finder_items = {} + local completed = false + + for _, item in ipairs(items) do + local text = language_picker.ordinal(item.value) + table.insert(finder_items, { + text = text, + item = item, + }) + end + + picker.pick({ + source = "select", + items = finder_items, + format = function(item) + local ret = {} + vim.tbl_map(function(col) + if type(col) == "table" then + ret[#ret + 1] = col + else + ret[#ret + 1] = { col } + end + ret[#ret + 1] = { " " } + end, item.item.entry) + return ret + end, + title = t("Available Languages"), + layout = { + preview = false, + layout = { + height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + }, + }, + actions = { + confirm = function(p, item) + if completed then + return + end + completed = true + p:close() + vim.schedule(function() + language_picker.select(item.item.value.t, question, cb) + end) + end, + }, + on_close = function() + if completed then + return + end + completed = true + log.warn("No selection") + end, + }) +end diff --git a/lua/leetcode/picker/question/snacks.lua b/lua/leetcode/picker/question/snacks.lua new file mode 100644 index 00000000..7392598e --- /dev/null +++ b/lua/leetcode/picker/question/snacks.lua @@ -0,0 +1,63 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") +local question_picker = require("leetcode.picker.question") + +local picker = require("snacks.picker") + +---@param questions lc.cache.Question[] +return function(questions, opts) + local items = question_picker.items(questions, opts) + local finder_items = {} + local completed = false + + for _, item in ipairs(items) do + local text = question_picker.ordinal(item.value) + table.insert(finder_items, { + text = text, + item = item, + }) + end + + picker.pick({ + source = "select", + items = finder_items, + format = function(item) + local ret = {} + vim.tbl_map(function(col) + if type(col) == "table" then + ret[#ret + 1] = col + else + ret[#ret + 1] = { col } + end + ret[#ret + 1] = { " " } + end, item.item.entry) + return ret + end, + title = t("Select a Question"), + layout = { + preview = false, + layout = { + height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + }, + }, + actions = { + confirm = function(p, item) + if completed then + return + end + completed = true + p:close() + vim.schedule(function() + question_picker.select(item.value) + end) + end, + }, + on_close = function() + if completed then + return + end + completed = true + log.warn("No selection") + end, + }) +end diff --git a/lua/leetcode/picker/tabs/snacks.lua b/lua/leetcode/picker/tabs/snacks.lua new file mode 100644 index 00000000..770b0b2e --- /dev/null +++ b/lua/leetcode/picker/tabs/snacks.lua @@ -0,0 +1,65 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") +local tabs_picker = require("leetcode.picker.tabs") + +local picker = require("snacks.picker") + +return function(tabs) + local items = tabs_picker.items(tabs) + local finder_items = {} + local completed = false + local items_reflect = {} + + for _, item in ipairs(items) do + items_reflect[item.value.question.q.frontend_id] = item.value + local text = tabs_picker.ordinal(item.value.question.q) + table.insert(finder_items, { + entry = item.entry, + item = item.value.question.q.frontend_id, + text = text, + }) + end + + picker.pick({ + source = "select", + items = finder_items, + format = function(item) + local ret = {} + vim.tbl_map(function(col) + if type(col) == "table" then + ret[#ret + 1] = col + else + ret[#ret + 1] = { col } + end + ret[#ret + 1] = { " " } + end, item.entry) + return ret + end, + title = t("Select a Question"), + layout = { + preview = false, + layout = { + height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + }, + }, + actions = { + confirm = function(p, item) + if completed then + return + end + completed = true + p:close() + vim.schedule(function() + tabs_picker.select(items_reflect[item.item]) + end) + end, + }, + on_close = function() + if completed then + return + end + completed = true + log.warn("No selection") + end, + }) +end From d3e9c6a6cdde3dce2f1b2853f74589ce07d7616c Mon Sep 17 00:00:00 2001 From: BYT0723 Date: Wed, 19 Mar 2025 17:15:41 +0800 Subject: [PATCH 2/5] fix: snacks-picker question select --- lua/leetcode/picker/question/snacks.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/leetcode/picker/question/snacks.lua b/lua/leetcode/picker/question/snacks.lua index 7392598e..b168f67a 100644 --- a/lua/leetcode/picker/question/snacks.lua +++ b/lua/leetcode/picker/question/snacks.lua @@ -48,7 +48,7 @@ return function(questions, opts) completed = true p:close() vim.schedule(function() - question_picker.select(item.value) + question_picker.select(item.item.value) end) end, }, From a3956db800a477a1ccbfb170c32c4e126d06f2f1 Mon Sep 17 00:00:00 2001 From: kawre Date: Sat, 28 Jun 2025 12:36:53 +0200 Subject: [PATCH 3/5] refactor(picker): resolve provider --- lua/leetcode/picker/init.lua | 72 +++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/lua/leetcode/picker/init.lua b/lua/leetcode/picker/init.lua index df23fa01..65547ba9 100644 --- a/lua/leetcode/picker/init.lua +++ b/lua/leetcode/picker/init.lua @@ -1,35 +1,65 @@ local log = require("leetcode.logger") local config = require("leetcode.config") +local provider_order = { "snacks-picker", "fzf-lua", "telescope" } +local providers = { + ["fzf-lua"] = { + name = "fzf", + is_available = function() + return pcall(require, "fzf-lua") + end, + }, + ["snacks-picker"] = { + name = "snacks", + is_available = function() + return pcall(function() ---@diagnostic disable-next-line: undefined-field + assert(Snacks.config["picker"].enabled) + end) + end, + }, + ["telescope"] = { + name = "telescope", + is_available = function() + return pcall(require, "telescope") + end, + }, +} + +local available_pickers = table.concat( + vim.tbl_map(function(p) + return providers[p].name + end, provider_order), + ", " +) + ---@return "fzf" | "telescope" | "snacks" local function resolve_provider() ---@type string local provider = config.user.picker.provider if provider == nil then - local fzf_ok = pcall(require, "fzf-lua") - if fzf_ok then - return "fzf" - end - local telescope_ok = pcall(require, "telescope") - if telescope_ok then - return "telescope" - end - local snacks_ok = pcall(require, "snacks.picker") - if snacks_ok then - return "snacks" - end - error("no supported picker provider found") - else - local mod = provider - if provider == "snacks-picker" then - provider = "snacks" - mod = "snacks.picker" + for _, key in ipairs(provider_order) do + local picker = providers[key] + + if picker.is_available() then + return picker.name + end end - local provider_ok = pcall(require, mod) - assert(provider_ok, ("specified picker provider not found: `%s`"):format(provider)) - return provider == "fzf-lua" and "fzf" or provider + + error(("No picker is available. Please install one of the following: `%s`") -- + :format(available_pickers)) end + + local picker = providers[provider] + assert( + picker, + ("Picker `%s` is not supported. Available pickers: `%s`") -- + :format(provider, available_pickers) + ) + + local ok = picker.is_available() + assert(ok, ("Picker `%s` is not available. Make sure it is installed"):format(provider)) + return picker.name end ---@class leet.Picker From 6abdcf6240e102941937c7f5dbcc96e92a1f90b2 Mon Sep 17 00:00:00 2001 From: kawre Date: Sat, 28 Jun 2025 12:43:10 +0200 Subject: [PATCH 4/5] docs: add `snacks-picker` as picker provider --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 57ed6f9f..cff3dd79 100644 --- a/README.md +++ b/README.md @@ -279,9 +279,14 @@ injector = { ---@type table ### picker -Supported picker providers are `telescope` and `fzf-lua`. -When provider is `nil`, [leetcode.nvim] will first try to use `fzf-lua`, -if not found it will fallback to `telescope`. +Supported picker providers are: + +- `snacks-picker` +- `fzf-lua` +- `telescope` + +If `provider` is `nil`, [leetcode.nvim] will try to resolve the first +available one in the order above. ```lua ---@type lc.picker @@ -377,26 +382,22 @@ image_support = false, - `inject` re-inject code for the current question - `session` - - `create` create a new session - `change` change the current session - `update` update the current session in case it went out of sync - `desc` toggle question description - - `toggle` same as `Leet desc` - `stats` toggle description stats visibility - `cookie` - - `update` opens a prompt to enter a new cookie - `delete` sign-out - `cache` - - `update` updates cache #### Some commands can take optional arguments. To stack argument values separate them by a `,` From 45988461386adb9e3616dda816878ee1c8d87211 Mon Sep 17 00:00:00 2001 From: kawre Date: Sat, 28 Jun 2025 13:22:49 +0200 Subject: [PATCH 5/5] refactor: picker width/height --- lua/leetcode/picker/language/snacks.lua | 5 +++-- lua/leetcode/picker/question/init.lua | 2 +- lua/leetcode/picker/question/snacks.lua | 5 +++-- lua/leetcode/picker/tabs/snacks.lua | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lua/leetcode/picker/language/snacks.lua b/lua/leetcode/picker/language/snacks.lua index c3241ae5..7729ecb6 100644 --- a/lua/leetcode/picker/language/snacks.lua +++ b/lua/leetcode/picker/language/snacks.lua @@ -19,7 +19,6 @@ return function(question, cb) end picker.pick({ - source = "select", items = finder_items, format = function(item) local ret = {} @@ -36,8 +35,10 @@ return function(question, cb) title = t("Available Languages"), layout = { preview = false, + preset = "select", layout = { - height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + height = language_picker.height, + width = language_picker.width, }, }, actions = { diff --git a/lua/leetcode/picker/question/init.lua b/lua/leetcode/picker/question/init.lua index 77866f28..6967b583 100644 --- a/lua/leetcode/picker/question/init.lua +++ b/lua/leetcode/picker/question/init.lua @@ -9,7 +9,7 @@ local Picker = require("leetcode.picker") local P = {} P.width = 100 -P.height = 20 +P.height = 0.6 ---@param items lc.cache.Question[] ---@param opts table diff --git a/lua/leetcode/picker/question/snacks.lua b/lua/leetcode/picker/question/snacks.lua index b168f67a..a4e527b7 100644 --- a/lua/leetcode/picker/question/snacks.lua +++ b/lua/leetcode/picker/question/snacks.lua @@ -19,7 +19,6 @@ return function(questions, opts) end picker.pick({ - source = "select", items = finder_items, format = function(item) local ret = {} @@ -35,9 +34,11 @@ return function(questions, opts) end, title = t("Select a Question"), layout = { + preset = "select", preview = false, layout = { - height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + height = question_picker.height, + width = question_picker.width, }, }, actions = { diff --git a/lua/leetcode/picker/tabs/snacks.lua b/lua/leetcode/picker/tabs/snacks.lua index 770b0b2e..6d987f8f 100644 --- a/lua/leetcode/picker/tabs/snacks.lua +++ b/lua/leetcode/picker/tabs/snacks.lua @@ -21,7 +21,6 @@ return function(tabs) end picker.pick({ - source = "select", items = finder_items, format = function(item) local ret = {} @@ -38,8 +37,10 @@ return function(tabs) title = t("Select a Question"), layout = { preview = false, + preset = "select", layout = { - height = math.floor(math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5), + height = tabs_picker.height, + width = tabs_picker.width, }, }, actions = {