Skip to content

Commit 74c1ff5

Browse files
authored
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
1 parent 9813653 commit 74c1ff5

17 files changed

+669
-425
lines changed

README.md

+22-7
Original file line numberDiff line numberDiff line change
@@ -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

lua/leetcode-ui/question.lua

+3-2
Original file line numberDiff line numberDiff line change
@@ -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

lua/leetcode/command/init.lua

+6-3
Original file line numberDiff line numberDiff line change
@@ -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

lua/leetcode/config/template.lua

+5
Original file line numberDiff line numberDiff line change
@@ -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"] = {},

lua/leetcode/picker/init.lua

+93
Original file line numberDiff line numberDiff line change
@@ -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

lua/leetcode/picker/language/fzf.lua

+35
Original file line numberDiff line numberDiff line change
@@ -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

lua/leetcode/picker/language/init.lua

+64
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)