irongit

A Neovim plugin enabling you to solve LeetCode problems.

feat: `ibhagwan/fzf-lua` support (#150)

* feat: `ibhagwan/fzf-lua` support

* feat(picker): close buffer

* fix: telescope lang picker

* feat: adjust picker sizes

* docs: `picker.provider` config

* feat: picker provider resolver
Marcinauthored committed by GitHub
parent 9813653commit 74c1ff56ab369c1d589ae821cd66cffbbe8c4607Browse files

17 files changed, +669 -425

+22-7README.md
@@ -26,7 +26,9 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1
2626
2727 - [Neovim] >= 0.9.0
2828
29-- [telescope.nvim]
29+- [telescope.nvim] or [fzf-lua]
30+
31+- [plenary.nvim]
3032
3133 - [nui.nvim]
3234
@@ -43,15 +45,12 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1
4345 ```lua
4446 {
4547 "kawre/leetcode.nvim",
46- build = ":TSUpdate html",
48+ build = ":TSUpdate html", -- if you have `nvim-treesitter` installed
4749 dependencies = {
4850 "nvim-telescope/telescope.nvim",
49- "nvim-lua/plenary.nvim", -- required by telescope
51+ -- "ibhagwan/fzf-lua",
52+ "nvim-lua/plenary.nvim",
5053 "MunifTanjim/nui.nvim",
51-
52- -- optional
53- "nvim-treesitter/nvim-treesitter",
54- "nvim-tree/nvim-web-devicons",
5554 },
5655 opts = {
5756 -- configuration goes here
@@ -128,6 +127,9 @@ To see full configuration types see [template.lua](./lua/leetcode/config/templat
128127 show_stats = true, ---@type boolean
129128 },
130129
130+ ---@type lc.picker
131+ picker = { provider = nil },
132+
131133 hooks = {
132134 ---@type fun()[]
133135 ["enter"] = {},
@@ -275,6 +277,17 @@ injector = { ---@type table<lc.lang, lc.inject>
275277 }
276278 ```
277279
280+### picker
281+
282+Supported picker providers are `telescope` and `fzf-lua`.
283+When provider is `nil`, [leetcode.nvim] will first try to use `fzf-lua`,
284+if not found it will fallback to `telescope`.
285+
286+```lua
287+---@type lc.picker
288+picker = { provider = nil },
289+```
290+
278291 ### hooks
279292
280293 List of functions that get executed on specified event
@@ -493,4 +506,6 @@ You can then exit [leetcode.nvim] using `:Leet exit` command
493506 [nvim-treesitter]: https://github.com/nvim-treesitter/nvim-treesitter
494507 [nvim-web-devicons]: https://github.com/nvim-tree/nvim-web-devicons
495508 [telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim
509+[fzf-lua]: https://github.com/ibhagwan/fzf-lua
496510 [tree-sitter-html]: https://github.com/tree-sitter/tree-sitter-html
511+[plenary.nvim]: https://github.com/nvim-lua/plenary.nvim
+3-2lua/leetcode-ui/question.lua
@@ -228,8 +228,9 @@ function Question:mount()
228228 local msg = ("Snippet for `%s` not found. Select a different language"):format(self.lang)
229229 log.warn(msg)
230230
231- require("leetcode.pickers.language").pick_lang(self, function(snippet)
232- self.lang = snippet.t.slug
231+ local picker = require("leetcode.picker")
232+ picker.language(self, function(slug)
233+ self.lang = slug
233234 self:handle_mount()
234235 end)
235236 end
+6-3lua/leetcode/command/init.lua
@@ -26,7 +26,8 @@ function cmd.problems(options)
2626 require("leetcode.utils").auth_guard()
2727
2828 local p = require("leetcode.cache.problemlist").get()
29- require("leetcode.pickers.question").pick(p, options)
29+ local picker = require("leetcode.picker")
30+ picker.question(p, options)
3031 end
3132
3233 ---@param cb? function
@@ -219,14 +220,16 @@ function cmd.start_user_session() --
219220 end
220221
221222 function cmd.question_tabs()
222- require("leetcode.pickers.question-tabs").pick()
223+ local picker = require("leetcode.picker")
224+ picker.tabs()
223225 end
224226
225227 function cmd.change_lang()
226228 local utils = require("leetcode.utils")
227229 local q = utils.curr_question()
228230 if q then
229- require("leetcode.pickers.language").pick(q)
231+ local picker = require("leetcode.picker")
232+ picker.language(q)
230233 end
231234 end
232235
+5-0lua/leetcode/config/template.lua
@@ -38,6 +38,8 @@
3838
3939 ---@alias lc.storage table<"cache"|"home", string>
4040
41+---@alias lc.picker { provider?: "fzf-lua" | "telescope" }
42+
4143 ---@class lc.UserConfig
4244 local M = {
4345 ---@type string
@@ -101,6 +103,9 @@ local M = {
101103 show_stats = true, ---@type boolean
102104 },
103105
106+ ---@type lc.picker
107+ picker = { provider = nil },
108+
104109 hooks = {
105110 ---@type fun()[]
106111 ["enter"] = {},
+93-0lua/leetcode/picker/init.lua
@@ -0,0 +1,93 @@
1+local log = require("leetcode.logger")
2+local config = require("leetcode.config")
3+
4+---@return "fzf" | "telescope"
5+local function resolve_provider()
6+ ---@type string
7+ local provider = config.user.picker.provider
8+
9+ if provider == nil then
10+ local fzf_ok = pcall(require, "fzf-lua")
11+ if fzf_ok then
12+ return "fzf"
13+ end
14+ local telescope_ok = pcall(require, "telescope")
15+ if telescope_ok then
16+ return "telescope"
17+ end
18+ error("no supported picker provider found")
19+ else
20+ local provider_ok = pcall(require, provider)
21+ assert(provider_ok, ("specified picker provider not found: `%s`"):format(provider))
22+ return provider == "fzf-lua" and "fzf" or provider
23+ end
24+end
25+
26+---@class leet.Picker
27+local P = {}
28+P.provider = resolve_provider()
29+
30+function P.hl_to_ansi(hl_group)
31+ local color = vim.api.nvim_get_hl(0, { name = hl_group })
32+ if color and color.fg then
33+ return string.format(
34+ "\x1b[38;2;%d;%d;%dm",
35+ bit.rshift(color.fg, 16),
36+ bit.band(bit.rshift(color.fg, 8), 0xFF),
37+ bit.band(color.fg, 0xFF)
38+ )
39+ end
40+ return ""
41+end
42+
43+function P.apply_hl(text, hl_group)
44+ if not hl_group then
45+ return text
46+ end
47+ return P.hl_to_ansi(hl_group) .. text .. "\x1b[0m"
48+end
49+
50+function P.normalize(items)
51+ return vim.tbl_map(function(item)
52+ return table.concat(
53+ vim.tbl_map(function(col)
54+ if type(col) == "table" then
55+ return P.apply_hl(col[1], col[2])
56+ else
57+ return col
58+ end
59+ end, item.entry),
60+ " "
61+ )
62+ end, items)
63+end
64+
65+function P.pick(path, ...)
66+ local rpath = table.concat({ "leetcode.picker", path, P.provider }, ".")
67+ return require(rpath)(...)
68+end
69+
70+function P.language(...)
71+ P.pick("language", ...)
72+end
73+
74+function P.question(...)
75+ P.pick("question", ...)
76+end
77+
78+function P.tabs()
79+ local utils = require("leetcode.utils")
80+ local tabs = utils.question_tabs()
81+
82+ if vim.tbl_isempty(tabs) then
83+ return log.warn("No questions opened")
84+ end
85+
86+ P.pick("tabs", tabs)
87+end
88+
89+function P.hidden_field(text, deli)
90+ return text:match(("([^%s]+)$"):format(deli))
91+end
92+
93+return P
+35-0lua/leetcode/picker/language/fzf.lua
@@ -0,0 +1,35 @@
1+local fzf = require("fzf-lua")
2+local t = require("leetcode.translator")
3+local language_picker = require("leetcode.picker.language")
4+local Picker = require("leetcode.picker")
5+
6+local deli = "\t"
7+
8+return function(question, cb)
9+ local items = language_picker.items(question.q.code_snippets)
10+
11+ for i, item in ipairs(items) do
12+ local md = vim.inspect({ slug = item.value.t.slug, lang = item.value.t.lang })
13+ :gsub("\n", "")
14+ items[i] = table.concat({ Picker.normalize({ item })[1], md }, deli)
15+ end
16+
17+ fzf.fzf_exec(items, {
18+ prompt = t("Available Languages") .. "> ",
19+ winopts = {
20+ win_height = language_picker.height,
21+ win_width = language_picker.width,
22+ },
23+ fzf_opts = {
24+ ["--delimiter"] = deli,
25+ ["--nth"] = "1",
26+ ["--with-nth"] = "1",
27+ },
28+ actions = {
29+ ["default"] = function(selected)
30+ local md = Picker.hidden_field(selected[1], deli)
31+ language_picker.select(load("return " .. md)(), question, cb)
32+ end,
33+ },
34+ })
35+end
+64-0lua/leetcode/picker/language/init.lua
@@ -0,0 +1,64 @@
1+local log = require("leetcode.logger")
2+local t = require("leetcode.translator")
3+local config = require("leetcode.config")
4+local utils = require("leetcode.utils")
5+
6+local L = {}
7+
8+L.width = 80
9+L.height = 15
10+
11+---@param snippet lc.QuestionCodeSnippet
12+local function dislay_icon(snippet)
13+ local hl = "leetcode_lang_" .. snippet.t.slug
14+ vim.api.nvim_set_hl(0, hl, { fg = snippet.t.color })
15+
16+ return { snippet.t.icon, hl }
17+end
18+
19+---@param snippet lc.QuestionCodeSnippet
20+local function display_lang(snippet)
21+ return { snippet.lang }
22+end
23+
24+function L.entry(item)
25+ return {
26+ dislay_icon(item),
27+ display_lang(item),
28+ }
29+end
30+
31+---@param item lc.QuestionCodeSnippet
32+function L.ordinal(item)
33+ return ("%s %s"):format(item.t.lang, item.t.slug)
34+end
35+
36+function L.items(content)
37+ return vim.tbl_map(function(item)
38+ ---@type lc.language
39+ item.t = utils.get_lang(item.lang_slug)
40+ if not item.t then
41+ return
42+ end
43+ return { entry = L.entry(item), value = item }
44+ end, content)
45+end
46+
47+function L.select(selection, question, cb, close)
48+ if question.lang == selection.slug then
49+ return log.warn(("%s: %s"):format(t("Language already set to"), selection.lang))
50+ end
51+
52+ config.lang = selection.slug
53+ if close then
54+ close()
55+ end
56+
57+ if cb then
58+ cb(selection.slug)
59+ else
60+ question:change_lang(selection.slug)
61+ end
62+end
63+
64+return L
+67-0lua/leetcode/picker/language/telescope.lua
@@ -0,0 +1,67 @@
1+local log = require("leetcode.logger")
2+local t = require("leetcode.translator")
3+
4+local pickers = require("telescope.pickers")
5+local finders = require("telescope.finders")
6+local conf = require("telescope.config").values
7+local config = require("leetcode.config")
8+
9+local entry_display = require("telescope.pickers.entry_display")
10+local actions = require("telescope.actions")
11+local action_state = require("telescope.actions.state")
12+local language_picker = require("leetcode.picker.language")
13+
14+local displayer = entry_display.create({
15+ separator = " ",
16+ items = {
17+ { width = 1 },
18+ { remaining = true },
19+ },
20+})
21+
22+local function entry_maker(item)
23+ return {
24+ value = item.value,
25+ display = function()
26+ return displayer(item.entry)
27+ end,
28+ ordinal = language_picker.ordinal(item.value),
29+ }
30+end
31+
32+local opts = require("telescope.themes").get_dropdown({
33+ layout_config = {
34+ width = language_picker.width,
35+ height = language_picker.height,
36+ },
37+})
38+
39+---@param question lc.ui.Question
40+return function(question, cb)
41+ local items = language_picker.items(question.q.code_snippets)
42+
43+ pickers
44+ .new(opts, {
45+ prompt_title = t("Available Languages"),
46+ finder = finders.new_table({
47+ results = items,
48+ entry_maker = entry_maker,
49+ }),
50+ sorter = conf.generic_sorter(opts),
51+ attach_mappings = function(prompt_bufnr)
52+ actions.select_default:replace(function()
53+ local selection = action_state.get_selected_entry()
54+ if not selection then
55+ log.warn("No selection")
56+ return
57+ end
58+ language_picker.select(selection.value.t, question, cb, function()
59+ actions.close(prompt_bufnr)
60+ end)
61+ end)
62+
63+ return true
64+ end,
65+ })
66+ :find()
67+end
+36-0lua/leetcode/picker/question/fzf.lua
@@ -0,0 +1,36 @@
1+local fzf = require("fzf-lua")
2+local problemlist = require("leetcode.cache.problemlist")
3+local t = require("leetcode.translator")
4+local question_picker = require("leetcode.picker.question")
5+local Picker = require("leetcode.picker")
6+
7+local deli = " "
8+
9+return function(questions, opts)
10+ local items = question_picker.items(questions, opts)
11+
12+ for i, item in ipairs(items) do
13+ items[i] = Picker.normalize({ item })[1]
14+ .. deli
15+ .. Picker.apply_hl(item.value.title_slug, "leetcode_alt")
16+ end
17+
18+ fzf.fzf_exec(items, {
19+ prompt = t("Select a Question") .. "> ",
20+ winopts = {
21+ win_height = question_picker.height,
22+ win_width = question_picker.width,
23+ },
24+ fzf_opts = {
25+ ["--delimiter"] = deli,
26+ ["--nth"] = "3..-3",
27+ },
28+ actions = {
29+ ["default"] = function(selected)
30+ local slug = Picker.hidden_field(selected[1], deli)
31+ local question = problemlist.get_by_title_slug(slug)
32+ question_picker.select(question)
33+ end,
34+ },
35+ })
36+end
+102-0lua/leetcode/picker/question/init.lua
@@ -0,0 +1,102 @@
1+local config = require("leetcode.config")
2+local log = require("leetcode.logger")
3+local ui_utils = require("leetcode-ui.utils")
4+local utils = require("leetcode.utils")
5+local Question = require("leetcode-ui.question")
6+local Picker = require("leetcode.picker")
7+
8+---@class leet.Picker.Question: leet.Picker
9+local P = {}
10+
11+P.width = 100
12+P.height = 20
13+
14+---@param items lc.cache.Question[]
15+---@param opts table<string, string[]>
16+---
17+---@return lc.cache.Question[]
18+function P.filter(items, opts)
19+ if vim.tbl_isempty(opts or {}) then
20+ return items
21+ end
22+
23+ ---@param q lc.cache.Question
24+ return vim.tbl_filter(function(q)
25+ local diff_flag = true
26+ if opts.difficulty and not vim.tbl_contains(opts.difficulty, q.difficulty:lower()) then
27+ diff_flag = false
28+ end
29+
30+ local status_flag = true
31+ if opts.status and not vim.tbl_contains(opts.status, q.status) then
32+ status_flag = false
33+ end
34+
35+ return diff_flag and status_flag
36+ end, items)
37+end
38+
39+---@param content lc.cache.Question[]
40+---@param opts table<string, string[]>
41+---
42+---@return { entry: any, value: lc.cache.Question }[]
43+function P.items(content, opts)
44+ local filtered = P.filter(content, opts)
45+ return vim.tbl_map(function(item)
46+ return { entry = P.entry(item), value = item }
47+ end, filtered)
48+end
49+
50+---@param question lc.cache.Question
51+local function display_user_status(question)
52+ if question.paid_only then
53+ return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock
54+ end
55+
56+ return config.icons.hl.status[question.status] or { " " }
57+end
58+
59+---@param question lc.cache.Question
60+local function display_difficulty(question)
61+ local hl = ui_utils.diff_to_hl(question.difficulty)
62+ return { config.icons.square, hl }
63+end
64+
65+---@param question lc.cache.Question
66+local function display_question(question)
67+ local index = { question.frontend_id .. ".", "leetcode_normal" }
68+ local title = { utils.translate(question.title, question.title_cn) }
69+ local ac_rate = { ("(%.1f%%)"):format(question.ac_rate), "leetcode_ref" }
70+
71+ return unpack({ index, title, ac_rate })
72+end
73+
74+function P.entry(item)
75+ return {
76+ display_user_status(item),
77+ display_difficulty(item),
78+ display_question(item),
79+ }
80+end
81+
82+---@param item lc.cache.Question
83+function P.ordinal(item)
84+ return ("%s. %s %s %s"):format(
85+ tostring(item.frontend_id),
86+ item.title,
87+ item.title_cn,
88+ item.title_slug
89+ )
90+end
91+
92+function P.select(selection, close)
93+ if selection.paid_only and not config.auth.is_premium then
94+ return log.warn("Question is for premium users only")
95+ end
96+ if close then
97+ close()
98+ end
99+ Question(selection):mount()
100+end
101+
102+return P
+71-0lua/leetcode/picker/question/telescope.lua
@@ -0,0 +1,71 @@
1+local log = require("leetcode.logger")
2+local t = require("leetcode.translator")
3+local question_picker = require("leetcode.picker.question")
4+
5+local Question = require("leetcode-ui.question")
6+
7+local pickers = require("telescope.pickers")
8+local finders = require("telescope.finders")
9+local conf = require("telescope.config").values
10+local config = require("leetcode.config")
11+
12+local entry_display = require("telescope.pickers.entry_display")
13+local actions = require("telescope.actions")
14+local action_state = require("telescope.actions.state")
15+
16+local displayer = entry_display.create({
17+ separator = " ",
18+ items = {
19+ { width = 1 },
20+ { width = 1 },
21+ { width = 5 },
22+ { remaining = true },
23+ { remaining = true },
24+ },
25+})
26+
27+local function entry_maker(item)
28+ return {
29+ value = item.value,
30+ display = function()
31+ return displayer(item.entry)
32+ end,
33+ ordinal = question_picker.ordinal(item.value),
34+ }
35+end
36+
37+local theme = require("telescope.themes").get_dropdown({
38+ layout_config = {
39+ width = question_picker.width,
40+ height = question_picker.height,
41+ },
42+})
43+
44+---@param questions lc.cache.Question[]
45+return function(questions, opts)
46+ local items = question_picker.items(questions, opts)
47+
48+ pickers
49+ .new(theme, {
50+ prompt_title = t("Select a Question"),
51+ finder = finders.new_table({
52+ results = items,
53+ entry_maker = entry_maker,
54+ }),
55+ sorter = conf.generic_sorter(theme),
56+ attach_mappings = function(prompt_bufnr)
57+ actions.select_default:replace(function()
58+ local selection = action_state.get_selected_entry()
59+ if not selection then
60+ log.warn("No selection")
61+ return
62+ end
63+ question_picker.select(selection.value, function()
64+ actions.close(prompt_bufnr)
65+ end)
66+ end)
67+ return true
68+ end,
69+ })
70+ :find()
71+end
+33-0lua/leetcode/picker/tabs/fzf.lua
@@ -0,0 +1,33 @@
1+local fzf = require("fzf-lua")
2+local t = require("leetcode.translator")
3+local tabs_picker = require("leetcode.picker.tabs")
4+local Picker = require("leetcode.picker")
5+
6+local deli = "\t"
7+
8+return function(tabs)
9+ local items = tabs_picker.items(tabs)
10+
11+ for i, item in ipairs(items) do
12+ items[i] = Picker.normalize({ item })[1] .. deli .. item.value.tabpage
13+ end
14+
15+ fzf.fzf_exec(items, {
16+ prompt = t("Select a Question") .. "> ",
17+ winopts = {
18+ height = tabs_picker.height,
19+ width = tabs_picker.width,
20+ },
21+ fzf_opts = {
22+ ["--delimiter"] = deli,
23+ ["--nth"] = "1",
24+ ["--with-nth"] = "1",
25+ },
26+ actions = {
27+ ["default"] = function(selected)
28+ local tabpage = Picker.hidden_field(selected[1], deli)
29+ tabs_picker.select({ tabpage = tonumber(tabpage) })
30+ end,
31+ },
32+ })
33+end
+67-0lua/leetcode/picker/tabs/init.lua
@@ -0,0 +1,67 @@
1+local log = require("leetcode.logger")
2+local utils = require("leetcode.utils")
3+local ui_utils = require("leetcode-ui.utils")
4+local t = require("leetcode.translator")
5+
6+local config = require("leetcode.config")
7+local icons = config.icons
8+
9+local T = {}
10+
11+T.width = 80
12+T.height = 15
13+
14+---@param q lc.QuestionResponse
15+function T.ordinal(q)
16+ return ("%s. %s %s"):format(q.frontend_id, q.title, q.translated_title)
17+end
18+
19+local function display_current(entry)
20+ local tabp = vim.api.nvim_get_current_tabpage()
21+ if tabp ~= entry.tabpage then
22+ return " "
23+ end
24+
25+ return { icons.caret.right, "leetcode_ref" }
26+end
27+
28+local function display_difficulty(q)
29+ local lang = utils.get_lang(q.lang)
30+ if not lang then
31+ return {}
32+ end
33+ return { lang.icon, "leetcode_lang_" .. lang.slug }
34+end
35+
36+---@param question lc.QuestionResponse
37+local function display_question(question)
38+ local hl = ui_utils.diff_to_hl(question.difficulty)
39+
40+ local index = { question.frontend_id .. ".", hl }
41+ local title = { utils.translate(question.title, question.translated_title) }
42+
43+ return unpack({ index, title })
44+end
45+
46+function T.entry(item)
47+ return {
48+ display_current(item),
49+ display_difficulty(item.question),
50+ display_question(item.question.q),
51+ }
52+end
53+
54+function T.items(content)
55+ return vim.tbl_map(function(item)
56+ return { entry = T.entry(item), value = item }
57+ end, content)
58+end
59+
60+function T.select(selection)
61+ local ok, err = pcall(vim.api.nvim_set_current_tabpage, selection.tabpage)
62+ if not ok then
63+ log.error(err)
64+ end
65+end
66+
67+return T
+65-0lua/leetcode/picker/tabs/telescope.lua
@@ -0,0 +1,65 @@
1+local log = require("leetcode.logger")
2+local t = require("leetcode.translator")
3+
4+local pickers = require("telescope.pickers")
5+local finders = require("telescope.finders")
6+local conf = require("telescope.config").values
7+
8+local entry_display = require("telescope.pickers.entry_display")
9+local actions = require("telescope.actions")
10+local tabs_picker = require("leetcode.picker.tabs")
11+local action_state = require("telescope.actions.state")
12+
13+local displayer = entry_display.create({
14+ separator = " ",
15+ items = {
16+ { width = 1 },
17+ { width = 1 },
18+ { width = 5 },
19+ { remaining = true },
20+ },
21+})
22+
23+local function entry_maker(item)
24+ return {
25+ value = item.value,
26+ display = function()
27+ return displayer(item.entry)
28+ end,
29+ ordinal = tabs_picker.ordinal(item.value.question.q),
30+ }
31+end
32+
33+local opts = require("telescope.themes").get_dropdown({
34+ layout_config = {
35+ width = tabs_picker.width,
36+ height = tabs_picker.height,
37+ },
38+})
39+
40+return function(tabs)
41+ local items = tabs_picker.items(tabs)
42+
43+ pickers
44+ .new(opts, {
45+ prompt_title = t("Select a Question"),
46+ finder = finders.new_table({
47+ results = items,
48+ entry_maker = entry_maker,
49+ }),
50+ sorter = conf.generic_sorter(opts),
51+ attach_mappings = function(prompt_bufnr)
52+ actions.select_default:replace(function()
53+ local selection = action_state.get_selected_entry()
54+ if not selection then
55+ log.warn("No selection")
56+ return
57+ end
58+ actions.close(prompt_bufnr)
59+ tabs_picker.select(selection.value)
60+ end)
61+ return true
62+ end,
63+ })
64+ :find()
65+end
+0-143lua/leetcode/pickers/language.lua
@@ -1,143 +0,0 @@
1-local log = require("leetcode.logger")
2-local Question = require("leetcode-ui.question")
3-local utils = require("leetcode.utils")
4-local t = require("leetcode.translator")
5-
6-local pickers = require("telescope.pickers")
7-local finders = require("telescope.finders")
8-local conf = require("telescope.config").values
9-local config = require("leetcode.config")
10-
11-local entry_display = require("telescope.pickers.entry_display")
12-local actions = require("telescope.actions")
13-local action_state = require("telescope.actions.state")
14-
15-local M = {}
16-
17----@param snippet lc.QuestionCodeSnippet
18----
19----@return string
20-local function lang_formatter(snippet)
21- return string.format("%s %s", snippet.t.lang, snippet.t.slug)
22-end
23-
24----@param snippet lc.QuestionCodeSnippet
25-local function dislay_icon(snippet)
26- local hl = "leetcode_lang_" .. snippet.t.slug
27- vim.api.nvim_set_hl(0, hl, { fg = snippet.t.color })
28-
29- return { snippet.t.icon, hl }
30-end
31-
32----@param snippet lc.QuestionCodeSnippet
33-local function display_lang(snippet)
34- --
35- return { snippet.lang }
36-end
37-
38-local displayer = entry_display.create({
39- separator = " ",
40- items = {
41- { width = 1 },
42- { remaining = true },
43- },
44-})
45-
46-local function make_display(entry)
47- ---@type lc.QuestionCodeSnippet
48- local snippet = entry.value
49-
50- return displayer({
51- dislay_icon(snippet),
52- display_lang(snippet),
53- })
54-end
55-
56----@param entry lc.QuestionCodeSnippet
57-local function entry_maker(entry)
58- ---@type lc.language
59- entry.t = utils.get_lang(entry.lang_slug)
60- if not entry.t then
61- return
62- end
63-
64- return {
65- value = entry,
66- display = make_display,
67- ordinal = lang_formatter(entry),
68- }
69-end
70-
71-local opts = require("telescope.themes").get_dropdown()
72-
73----@param question lc.ui.Question
74-M.pick_lang = function(question, callback)
75- pickers
76- .new(opts, {
77- prompt_title = t("Available Languages"),
78- finder = finders.new_table({
79- results = question.q.code_snippets,
80- entry_maker = entry_maker,
81- }),
82- sorter = conf.generic_sorter(opts),
83- attach_mappings = function(prompt_bufnr, map)
84- actions.select_default:replace(function()
85- local selection = action_state.get_selected_entry()
86- if not selection then
87- return
88- end
89-
90- local snippet = selection.value
91- if question.lang == snippet.t.slug then
92- return log.warn(
93- ("%s: %s"):format(t("Language already set to"), snippet.t.lang)
94- )
95- end
96-
97- config.lang = snippet.t.slug
98- actions.close(prompt_bufnr)
99- callback(snippet)
100- end)
101-
102- return true
103- end,
104- })
105- :find()
106-end
107-
108----@param question lc.ui.Question
109-M.pick = function(question)
110- pickers
111- .new(opts, {
112- prompt_title = t("Available Languages"),
113- finder = finders.new_table({
114- results = question.q.code_snippets,
115- entry_maker = entry_maker,
116- }),
117- sorter = conf.generic_sorter(opts),
118- attach_mappings = function(prompt_bufnr, map)
119- actions.select_default:replace(function()
120- local selection = action_state.get_selected_entry()
121- if not selection then
122- return
123- end
124-
125- local snippet = selection.value
126- if question.lang == snippet.t.slug then
127- return log.warn(
128- ("%s: %s"):format(t("Language already set to"), snippet.t.lang)
129- )
130- end
131-
132- config.lang = snippet.t.slug
133- actions.close(prompt_bufnr)
134- question:change_lang(snippet.t.slug)
135- end)
136-
137- return true
138- end,
139- })
140- :find()
141-end
142-
143-return M
+0-122lua/leetcode/pickers/question-tabs.lua
@@ -1,122 +0,0 @@
1-local log = require("leetcode.logger")
2-local utils = require("leetcode.utils")
3-local ui_utils = require("leetcode-ui.utils")
4-local t = require("leetcode.translator")
5-
6-local pickers = require("telescope.pickers")
7-local finders = require("telescope.finders")
8-local conf = require("telescope.config").values
9-local config = require("leetcode.config")
10-local icons = config.icons
11-
12-local entry_display = require("telescope.pickers.entry_display")
13-local actions = require("telescope.actions")
14-local action_state = require("telescope.actions.state")
15-
16----@param q lc.QuestionResponse
17----
18----@return string
19-local function question_formatter(q)
20- return string.format("%s. %s %s", q.frontend_id, q.title, q.translated_title)
21-end
22-
23-local function display_current(entry)
24- local tabp = vim.api.nvim_get_current_tabpage()
25- if tabp ~= entry.tabpage then
26- return unpack({ "", "" })
27- end
28-
29- return { icons.caret.right, "leetcode_ref" }
30-end
31-
32-local function display_difficulty(q)
33- local lang = utils.get_lang(q.lang)
34- if not lang then
35- return {}
36- end
37- return { lang.icon, "leetcode_lang_" .. lang.slug }
38-end
39-
40----@param question lc.QuestionResponse
41-local function display_question(question)
42- local hl = ui_utils.diff_to_hl(question.difficulty)
43-
44- local index = { question.frontend_id .. ".", hl }
45- local title = { utils.translate(question.title, question.translated_title) }
46-
47- return unpack({ index, title })
48-end
49-
50-local displayer = entry_display.create({
51- separator = " ",
52- items = {
53- { width = 1 },
54- { width = 1 },
55- { width = 5 },
56- { remaining = true },
57- },
58-})
59-
60-local function make_display(entry)
61- ---@type lc.cache.Question
62- local q = entry.value.question.q
63-
64- return displayer({
65- display_current(entry.value),
66- display_difficulty(entry.value.question),
67- display_question(q),
68- })
69-end
70-
71-local function entry_maker(entry)
72- return {
73- value = entry,
74- display = make_display,
75- ordinal = question_formatter(entry.question.q),
76- }
77-end
78-
79-local opts = require("telescope.themes").get_dropdown()
80-
81-return {
82- pick = function()
83- local tabs = utils.question_tabs()
84-
85- if vim.tbl_isempty(tabs) then
86- return log.warn("No questions opened")
87- end
88-
89- -- table.sort(tabs, function(q1, q2)
90- -- local fid1, fid2 =
91- -- tonumber(q1.question.q.frontend_id), tonumber(q2.question.q.frontend_id)
92- -- return (fid1 and fid2) and fid1 < fid2 or fid1 ~= nil
93- -- end)
94-
95- pickers
96- .new(opts, {
97- prompt_title = t("Select a Question"),
98- finder = finders.new_table({
99- results = tabs,
100- entry_maker = entry_maker,
101- }),
102- sorter = conf.generic_sorter(opts),
103- attach_mappings = function(prompt_bufnr)
104- actions.select_default:replace(function()
105- actions.close(prompt_bufnr)
106- local selection = action_state.get_selected_entry()
107-
108- if not selection then
109- return
110- end
111- local ok, err =
112- pcall(vim.api.nvim_set_current_tabpage, selection.value.tabpage)
113- if not ok then
114- log.error(err)
115- end
116- end)
117- return true
118- end,
119- })
120- :find()
121- end,
122-}
+0-148lua/leetcode/pickers/question.lua
@@ -1,148 +0,0 @@
1-local log = require("leetcode.logger")
2-local t = require("leetcode.translator")
3-local utils = require("leetcode.utils")
4-local ui_utils = require("leetcode-ui.utils")
5-
6-local Question = require("leetcode-ui.question")
7-
8-local pickers = require("telescope.pickers")
9-local finders = require("telescope.finders")
10-local conf = require("telescope.config").values
11-local config = require("leetcode.config")
12-
13-local entry_display = require("telescope.pickers.entry_display")
14-local actions = require("telescope.actions")
15-local action_state = require("telescope.actions.state")
16-
17----@param question lc.cache.Question
18----
19----@return string
20-local function question_formatter(question)
21- return ("%s. %s %s %s"):format(
22- tostring(question.frontend_id),
23- question.title,
24- question.title_cn,
25- question.title_slug
26- )
27-end
28-
29----@param question lc.cache.Question
30-local function display_difficulty(question)
31- local hl = ui_utils.diff_to_hl(question.difficulty)
32- return { config.icons.square, hl }
33-end
34-
35----@param question lc.cache.Question
36-local function display_user_status(question)
37- if question.paid_only then
38- return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock
39- end
40-
41- return config.icons.hl.status[question.status] or { "" }
42-end
43-
44----@param question lc.cache.Question
45-local function display_question(question)
46- local ac_rate = { ("%.1f%%"):format(question.ac_rate), "leetcode_ref" }
47- local index = { question.frontend_id .. ".", "leetcode_normal" }
48-
49- local title = { utils.translate(question.title, question.title_cn) }
50-
51- return unpack({ index, title, ac_rate })
52-end
53-
54-local displayer = entry_display.create({
55- separator = " ",
56- items = {
57- { width = 1 },
58- { width = 1 },
59- { width = 5 },
60- { width = 78 },
61- { width = 5 },
62- },
63-})
64-
65-local function make_display(entry)
66- ---@type lc.cache.Question
67- local q = entry.value
68-
69- return displayer({
70- display_user_status(q),
71- display_difficulty(q),
72- display_question(q),
73- })
74-end
75-
76-local function entry_maker(entry)
77- return {
78- value = entry,
79- display = make_display,
80- ordinal = question_formatter(entry),
81- }
82-end
83-
84-local theme = require("telescope.themes").get_dropdown({
85- layout_config = {
86- width = 100,
87- height = 20,
88- },
89-})
90-
91----@param questions lc.cache.Question[]
92----@param opts table<string, string[]>
93----
94----@return lc.cache.Question[]
95-local function filter_questions(questions, opts)
96- if vim.tbl_isempty(opts or {}) then
97- return questions
98- end
99-
100- ---@param q lc.cache.Question
101- return vim.tbl_filter(function(q)
102- local diff_flag = true
103- if opts.difficulty and not vim.tbl_contains(opts.difficulty, q.difficulty:lower()) then
104- diff_flag = false
105- end
106-
107- local status_flag = true
108- if opts.status and not vim.tbl_contains(opts.status, q.status) then
109- status_flag = false
110- end
111-
112- return diff_flag and status_flag
113- end, questions)
114-end
115-
116-return {
117- ---@param questions lc.cache.Question[]
118- pick = function(questions, opts)
119- pickers
120- .new(theme, {
121- prompt_title = t("Select a Question"),
122- finder = finders.new_table({
123- results = filter_questions(questions, opts),
124- entry_maker = entry_maker,
125- }),
126- sorter = conf.generic_sorter(theme),
127- attach_mappings = function(prompt_bufnr, map)
128- actions.select_default:replace(function()
129- local selection = action_state.get_selected_entry()
130- if not selection then
131- return
132- end
133-
134- local q = selection.value
135- if q.paid_only and not config.auth.is_premium then
136- return log.warn("Question is for premium users only")
137- end
138-
139- actions.close(prompt_bufnr)
140- Question(q):mount()
141- end)
142-
143- return true
144- end,
145- })
146- :find()
147- end,
148-}