diff --git a/README.md b/README.md index ece23e2..57ed6f9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1 - [Neovim] >= 0.9.0 -- [telescope.nvim] +- [telescope.nvim] or [fzf-lua] + +- [plenary.nvim] - [nui.nvim] @@ -43,15 +45,12 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1 ```lua { "kawre/leetcode.nvim", - build = ":TSUpdate html", + build = ":TSUpdate html", -- if you have `nvim-treesitter` installed dependencies = { "nvim-telescope/telescope.nvim", - "nvim-lua/plenary.nvim", -- required by telescope + -- "ibhagwan/fzf-lua", + "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim", - - -- optional - "nvim-treesitter/nvim-treesitter", - "nvim-tree/nvim-web-devicons", }, opts = { -- configuration goes here @@ -128,6 +127,9 @@ To see full configuration types see [template.lua](./lua/leetcode/config/templat show_stats = true, ---@type boolean }, + ---@type lc.picker + picker = { provider = nil }, + hooks = { ---@type fun()[] ["enter"] = {}, @@ -275,6 +277,17 @@ 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`. + +```lua +---@type lc.picker +picker = { provider = nil }, +``` + ### hooks List of functions that get executed on specified event @@ -493,4 +506,6 @@ You can then exit [leetcode.nvim] using `:Leet exit` command [nvim-treesitter]: https://github.com/nvim-treesitter/nvim-treesitter [nvim-web-devicons]: https://github.com/nvim-tree/nvim-web-devicons [telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim +[fzf-lua]: https://github.com/ibhagwan/fzf-lua [tree-sitter-html]: https://github.com/tree-sitter/tree-sitter-html +[plenary.nvim]: https://github.com/nvim-lua/plenary.nvim diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua index df35892..e820474 100644 --- a/lua/leetcode-ui/question.lua +++ b/lua/leetcode-ui/question.lua @@ -228,8 +228,9 @@ function Question:mount() local msg = ("Snippet for `%s` not found. Select a different language"):format(self.lang) log.warn(msg) - require("leetcode.pickers.language").pick_lang(self, function(snippet) - self.lang = snippet.t.slug + local picker = require("leetcode.picker") + picker.language(self, function(slug) + self.lang = slug self:handle_mount() end) end diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index 8cfaae0..ae41554 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -26,7 +26,8 @@ function cmd.problems(options) require("leetcode.utils").auth_guard() local p = require("leetcode.cache.problemlist").get() - require("leetcode.pickers.question").pick(p, options) + local picker = require("leetcode.picker") + picker.question(p, options) end ---@param cb? function @@ -219,14 +220,16 @@ function cmd.start_user_session() -- end function cmd.question_tabs() - require("leetcode.pickers.question-tabs").pick() + local picker = require("leetcode.picker") + picker.tabs() end function cmd.change_lang() local utils = require("leetcode.utils") local q = utils.curr_question() if q then - require("leetcode.pickers.language").pick(q) + local picker = require("leetcode.picker") + picker.language(q) end end diff --git a/lua/leetcode/config/template.lua b/lua/leetcode/config/template.lua index 27590e0..40c5779 100644 --- a/lua/leetcode/config/template.lua +++ b/lua/leetcode/config/template.lua @@ -38,6 +38,8 @@ ---@alias lc.storage table<"cache"|"home", string> +---@alias lc.picker { provider?: "fzf-lua" | "telescope" } + ---@class lc.UserConfig local M = { ---@type string @@ -101,6 +103,9 @@ local M = { show_stats = true, ---@type boolean }, + ---@type lc.picker + picker = { provider = nil }, + hooks = { ---@type fun()[] ["enter"] = {}, diff --git a/lua/leetcode/picker/init.lua b/lua/leetcode/picker/init.lua new file mode 100644 index 0000000..8fabd22 --- /dev/null +++ b/lua/leetcode/picker/init.lua @@ -0,0 +1,93 @@ +local log = require("leetcode.logger") +local config = require("leetcode.config") + +---@return "fzf" | "telescope" +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 + error("no supported picker provider found") + else + local provider_ok = pcall(require, provider) + assert(provider_ok, ("specified picker provider not found: `%s`"):format(provider)) + return provider == "fzf-lua" and "fzf" or provider + end +end + +---@class leet.Picker +local P = {} +P.provider = resolve_provider() + +function P.hl_to_ansi(hl_group) + local color = vim.api.nvim_get_hl(0, { name = hl_group }) + if color and color.fg then + return string.format( + "\x1b[38;2;%d;%d;%dm", + bit.rshift(color.fg, 16), + bit.band(bit.rshift(color.fg, 8), 0xFF), + bit.band(color.fg, 0xFF) + ) + end + return "" +end + +function P.apply_hl(text, hl_group) + if not hl_group then + return text + end + return P.hl_to_ansi(hl_group) .. text .. "\x1b[0m" +end + +function P.normalize(items) + return vim.tbl_map(function(item) + return table.concat( + vim.tbl_map(function(col) + if type(col) == "table" then + return P.apply_hl(col[1], col[2]) + else + return col + end + end, item.entry), + " " + ) + end, items) +end + +function P.pick(path, ...) + local rpath = table.concat({ "leetcode.picker", path, P.provider }, ".") + return require(rpath)(...) +end + +function P.language(...) + P.pick("language", ...) +end + +function P.question(...) + P.pick("question", ...) +end + +function P.tabs() + local utils = require("leetcode.utils") + local tabs = utils.question_tabs() + + if vim.tbl_isempty(tabs) then + return log.warn("No questions opened") + end + + P.pick("tabs", tabs) +end + +function P.hidden_field(text, deli) + return text:match(("([^%s]+)$"):format(deli)) +end + +return P diff --git a/lua/leetcode/picker/language/fzf.lua b/lua/leetcode/picker/language/fzf.lua new file mode 100644 index 0000000..013b28f --- /dev/null +++ b/lua/leetcode/picker/language/fzf.lua @@ -0,0 +1,35 @@ +local fzf = require("fzf-lua") +local t = require("leetcode.translator") +local language_picker = require("leetcode.picker.language") +local Picker = require("leetcode.picker") + +local deli = "\t" + +return function(question, cb) + local items = language_picker.items(question.q.code_snippets) + + for i, item in ipairs(items) do + local md = vim.inspect({ slug = item.value.t.slug, lang = item.value.t.lang }) + :gsub("\n", "") + items[i] = table.concat({ Picker.normalize({ item })[1], md }, deli) + end + + fzf.fzf_exec(items, { + prompt = t("Available Languages") .. "> ", + winopts = { + win_height = language_picker.height, + win_width = language_picker.width, + }, + fzf_opts = { + ["--delimiter"] = deli, + ["--nth"] = "1", + ["--with-nth"] = "1", + }, + actions = { + ["default"] = function(selected) + local md = Picker.hidden_field(selected[1], deli) + language_picker.select(load("return " .. md)(), question, cb) + end, + }, + }) +end diff --git a/lua/leetcode/picker/language/init.lua b/lua/leetcode/picker/language/init.lua new file mode 100644 index 0000000..d7b7615 --- /dev/null +++ b/lua/leetcode/picker/language/init.lua @@ -0,0 +1,64 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") +local config = require("leetcode.config") +local utils = require("leetcode.utils") + +local L = {} + +L.width = 80 +L.height = 15 + +---@param snippet lc.QuestionCodeSnippet +local function dislay_icon(snippet) + local hl = "leetcode_lang_" .. snippet.t.slug + vim.api.nvim_set_hl(0, hl, { fg = snippet.t.color }) + + return { snippet.t.icon, hl } +end + +---@param snippet lc.QuestionCodeSnippet +local function display_lang(snippet) + return { snippet.lang } +end + +function L.entry(item) + return { + dislay_icon(item), + display_lang(item), + } +end + +---@param item lc.QuestionCodeSnippet +function L.ordinal(item) + return ("%s %s"):format(item.t.lang, item.t.slug) +end + +function L.items(content) + return vim.tbl_map(function(item) + ---@type lc.language + item.t = utils.get_lang(item.lang_slug) + if not item.t then + return + end + return { entry = L.entry(item), value = item } + end, content) +end + +function L.select(selection, question, cb, close) + if question.lang == selection.slug then + return log.warn(("%s: %s"):format(t("Language already set to"), selection.lang)) + end + + config.lang = selection.slug + if close then + close() + end + + if cb then + cb(selection.slug) + else + question:change_lang(selection.slug) + end +end + +return L diff --git a/lua/leetcode/picker/language/telescope.lua b/lua/leetcode/picker/language/telescope.lua new file mode 100644 index 0000000..c91061b --- /dev/null +++ b/lua/leetcode/picker/language/telescope.lua @@ -0,0 +1,67 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") + +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values +local config = require("leetcode.config") + +local entry_display = require("telescope.pickers.entry_display") +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") +local language_picker = require("leetcode.picker.language") + +local displayer = entry_display.create({ + separator = " ", + items = { + { width = 1 }, + { remaining = true }, + }, +}) + +local function entry_maker(item) + return { + value = item.value, + display = function() + return displayer(item.entry) + end, + ordinal = language_picker.ordinal(item.value), + } +end + +local opts = require("telescope.themes").get_dropdown({ + layout_config = { + width = language_picker.width, + height = language_picker.height, + }, +}) + +---@param question lc.ui.Question +return function(question, cb) + local items = language_picker.items(question.q.code_snippets) + + pickers + .new(opts, { + prompt_title = t("Available Languages"), + finder = finders.new_table({ + results = items, + entry_maker = entry_maker, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + if not selection then + log.warn("No selection") + return + end + language_picker.select(selection.value.t, question, cb, function() + actions.close(prompt_bufnr) + end) + end) + + return true + end, + }) + :find() +end diff --git a/lua/leetcode/picker/question/fzf.lua b/lua/leetcode/picker/question/fzf.lua new file mode 100644 index 0000000..1ffdaec --- /dev/null +++ b/lua/leetcode/picker/question/fzf.lua @@ -0,0 +1,36 @@ +local fzf = require("fzf-lua") +local problemlist = require("leetcode.cache.problemlist") +local t = require("leetcode.translator") +local question_picker = require("leetcode.picker.question") +local Picker = require("leetcode.picker") + +local deli = " " + +return function(questions, opts) + local items = question_picker.items(questions, opts) + + for i, item in ipairs(items) do + items[i] = Picker.normalize({ item })[1] + .. deli + .. Picker.apply_hl(item.value.title_slug, "leetcode_alt") + end + + fzf.fzf_exec(items, { + prompt = t("Select a Question") .. "> ", + winopts = { + win_height = question_picker.height, + win_width = question_picker.width, + }, + fzf_opts = { + ["--delimiter"] = deli, + ["--nth"] = "3..-3", + }, + actions = { + ["default"] = function(selected) + local slug = Picker.hidden_field(selected[1], deli) + local question = problemlist.get_by_title_slug(slug) + question_picker.select(question) + end, + }, + }) +end diff --git a/lua/leetcode/picker/question/init.lua b/lua/leetcode/picker/question/init.lua new file mode 100644 index 0000000..77866f2 --- /dev/null +++ b/lua/leetcode/picker/question/init.lua @@ -0,0 +1,102 @@ +local config = require("leetcode.config") +local log = require("leetcode.logger") +local ui_utils = require("leetcode-ui.utils") +local utils = require("leetcode.utils") +local Question = require("leetcode-ui.question") +local Picker = require("leetcode.picker") + +---@class leet.Picker.Question: leet.Picker +local P = {} + +P.width = 100 +P.height = 20 + +---@param items lc.cache.Question[] +---@param opts table +--- +---@return lc.cache.Question[] +function P.filter(items, opts) + if vim.tbl_isempty(opts or {}) then + return items + end + + ---@param q lc.cache.Question + return vim.tbl_filter(function(q) + local diff_flag = true + if opts.difficulty and not vim.tbl_contains(opts.difficulty, q.difficulty:lower()) then + diff_flag = false + end + + local status_flag = true + if opts.status and not vim.tbl_contains(opts.status, q.status) then + status_flag = false + end + + return diff_flag and status_flag + end, items) +end + +---@param content lc.cache.Question[] +---@param opts table +--- +---@return { entry: any, value: lc.cache.Question }[] +function P.items(content, opts) + local filtered = P.filter(content, opts) + return vim.tbl_map(function(item) + return { entry = P.entry(item), value = item } + end, filtered) +end + +---@param question lc.cache.Question +local function display_user_status(question) + if question.paid_only then + return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock + end + + return config.icons.hl.status[question.status] or { " " } +end + +---@param question lc.cache.Question +local function display_difficulty(question) + local hl = ui_utils.diff_to_hl(question.difficulty) + return { config.icons.square, hl } +end + +---@param question lc.cache.Question +local function display_question(question) + local index = { question.frontend_id .. ".", "leetcode_normal" } + local title = { utils.translate(question.title, question.title_cn) } + local ac_rate = { ("(%.1f%%)"):format(question.ac_rate), "leetcode_ref" } + + return unpack({ index, title, ac_rate }) +end + +function P.entry(item) + return { + display_user_status(item), + display_difficulty(item), + display_question(item), + } +end + +---@param item lc.cache.Question +function P.ordinal(item) + return ("%s. %s %s %s"):format( + tostring(item.frontend_id), + item.title, + item.title_cn, + item.title_slug + ) +end + +function P.select(selection, close) + if selection.paid_only and not config.auth.is_premium then + return log.warn("Question is for premium users only") + end + if close then + close() + end + Question(selection):mount() +end + +return P diff --git a/lua/leetcode/picker/question/telescope.lua b/lua/leetcode/picker/question/telescope.lua new file mode 100644 index 0000000..b30137d --- /dev/null +++ b/lua/leetcode/picker/question/telescope.lua @@ -0,0 +1,71 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") +local question_picker = require("leetcode.picker.question") + +local Question = require("leetcode-ui.question") + +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values +local config = require("leetcode.config") + +local entry_display = require("telescope.pickers.entry_display") +local actions = require("telescope.actions") +local action_state = require("telescope.actions.state") + +local displayer = entry_display.create({ + separator = " ", + items = { + { width = 1 }, + { width = 1 }, + { width = 5 }, + { remaining = true }, + { remaining = true }, + }, +}) + +local function entry_maker(item) + return { + value = item.value, + display = function() + return displayer(item.entry) + end, + ordinal = question_picker.ordinal(item.value), + } +end + +local theme = require("telescope.themes").get_dropdown({ + layout_config = { + width = question_picker.width, + height = question_picker.height, + }, +}) + +---@param questions lc.cache.Question[] +return function(questions, opts) + local items = question_picker.items(questions, opts) + + pickers + .new(theme, { + prompt_title = t("Select a Question"), + finder = finders.new_table({ + results = items, + entry_maker = entry_maker, + }), + sorter = conf.generic_sorter(theme), + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + if not selection then + log.warn("No selection") + return + end + question_picker.select(selection.value, function() + actions.close(prompt_bufnr) + end) + end) + return true + end, + }) + :find() +end diff --git a/lua/leetcode/picker/tabs/fzf.lua b/lua/leetcode/picker/tabs/fzf.lua new file mode 100644 index 0000000..57e5dbd --- /dev/null +++ b/lua/leetcode/picker/tabs/fzf.lua @@ -0,0 +1,33 @@ +local fzf = require("fzf-lua") +local t = require("leetcode.translator") +local tabs_picker = require("leetcode.picker.tabs") +local Picker = require("leetcode.picker") + +local deli = "\t" + +return function(tabs) + local items = tabs_picker.items(tabs) + + for i, item in ipairs(items) do + items[i] = Picker.normalize({ item })[1] .. deli .. item.value.tabpage + end + + fzf.fzf_exec(items, { + prompt = t("Select a Question") .. "> ", + winopts = { + height = tabs_picker.height, + width = tabs_picker.width, + }, + fzf_opts = { + ["--delimiter"] = deli, + ["--nth"] = "1", + ["--with-nth"] = "1", + }, + actions = { + ["default"] = function(selected) + local tabpage = Picker.hidden_field(selected[1], deli) + tabs_picker.select({ tabpage = tonumber(tabpage) }) + end, + }, + }) +end diff --git a/lua/leetcode/picker/tabs/init.lua b/lua/leetcode/picker/tabs/init.lua new file mode 100644 index 0000000..8ea7049 --- /dev/null +++ b/lua/leetcode/picker/tabs/init.lua @@ -0,0 +1,67 @@ +local log = require("leetcode.logger") +local utils = require("leetcode.utils") +local ui_utils = require("leetcode-ui.utils") +local t = require("leetcode.translator") + +local config = require("leetcode.config") +local icons = config.icons + +local T = {} + +T.width = 80 +T.height = 15 + +---@param q lc.QuestionResponse +function T.ordinal(q) + return ("%s. %s %s"):format(q.frontend_id, q.title, q.translated_title) +end + +local function display_current(entry) + local tabp = vim.api.nvim_get_current_tabpage() + if tabp ~= entry.tabpage then + return " " + end + + return { icons.caret.right, "leetcode_ref" } +end + +local function display_difficulty(q) + local lang = utils.get_lang(q.lang) + if not lang then + return {} + end + return { lang.icon, "leetcode_lang_" .. lang.slug } +end + +---@param question lc.QuestionResponse +local function display_question(question) + local hl = ui_utils.diff_to_hl(question.difficulty) + + local index = { question.frontend_id .. ".", hl } + local title = { utils.translate(question.title, question.translated_title) } + + return unpack({ index, title }) +end + +function T.entry(item) + return { + display_current(item), + display_difficulty(item.question), + display_question(item.question.q), + } +end + +function T.items(content) + return vim.tbl_map(function(item) + return { entry = T.entry(item), value = item } + end, content) +end + +function T.select(selection) + local ok, err = pcall(vim.api.nvim_set_current_tabpage, selection.tabpage) + if not ok then + log.error(err) + end +end + +return T diff --git a/lua/leetcode/picker/tabs/telescope.lua b/lua/leetcode/picker/tabs/telescope.lua new file mode 100644 index 0000000..93998e0 --- /dev/null +++ b/lua/leetcode/picker/tabs/telescope.lua @@ -0,0 +1,65 @@ +local log = require("leetcode.logger") +local t = require("leetcode.translator") + +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local conf = require("telescope.config").values + +local entry_display = require("telescope.pickers.entry_display") +local actions = require("telescope.actions") +local tabs_picker = require("leetcode.picker.tabs") +local action_state = require("telescope.actions.state") + +local displayer = entry_display.create({ + separator = " ", + items = { + { width = 1 }, + { width = 1 }, + { width = 5 }, + { remaining = true }, + }, +}) + +local function entry_maker(item) + return { + value = item.value, + display = function() + return displayer(item.entry) + end, + ordinal = tabs_picker.ordinal(item.value.question.q), + } +end + +local opts = require("telescope.themes").get_dropdown({ + layout_config = { + width = tabs_picker.width, + height = tabs_picker.height, + }, +}) + +return function(tabs) + local items = tabs_picker.items(tabs) + + pickers + .new(opts, { + prompt_title = t("Select a Question"), + finder = finders.new_table({ + results = items, + entry_maker = entry_maker, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + if not selection then + log.warn("No selection") + return + end + actions.close(prompt_bufnr) + tabs_picker.select(selection.value) + end) + return true + end, + }) + :find() +end diff --git a/lua/leetcode/pickers/language.lua b/lua/leetcode/pickers/language.lua deleted file mode 100644 index 7e9f346..0000000 --- a/lua/leetcode/pickers/language.lua +++ /dev/null @@ -1,143 +0,0 @@ -local log = require("leetcode.logger") -local Question = require("leetcode-ui.question") -local utils = require("leetcode.utils") -local t = require("leetcode.translator") - -local pickers = require("telescope.pickers") -local finders = require("telescope.finders") -local conf = require("telescope.config").values -local config = require("leetcode.config") - -local entry_display = require("telescope.pickers.entry_display") -local actions = require("telescope.actions") -local action_state = require("telescope.actions.state") - -local M = {} - ----@param snippet lc.QuestionCodeSnippet ---- ----@return string -local function lang_formatter(snippet) - return string.format("%s %s", snippet.t.lang, snippet.t.slug) -end - ----@param snippet lc.QuestionCodeSnippet -local function dislay_icon(snippet) - local hl = "leetcode_lang_" .. snippet.t.slug - vim.api.nvim_set_hl(0, hl, { fg = snippet.t.color }) - - return { snippet.t.icon, hl } -end - ----@param snippet lc.QuestionCodeSnippet -local function display_lang(snippet) - -- - return { snippet.lang } -end - -local displayer = entry_display.create({ - separator = " ", - items = { - { width = 1 }, - { remaining = true }, - }, -}) - -local function make_display(entry) - ---@type lc.QuestionCodeSnippet - local snippet = entry.value - - return displayer({ - dislay_icon(snippet), - display_lang(snippet), - }) -end - ----@param entry lc.QuestionCodeSnippet -local function entry_maker(entry) - ---@type lc.language - entry.t = utils.get_lang(entry.lang_slug) - if not entry.t then - return - end - - return { - value = entry, - display = make_display, - ordinal = lang_formatter(entry), - } -end - -local opts = require("telescope.themes").get_dropdown() - ----@param question lc.ui.Question -M.pick_lang = function(question, callback) - pickers - .new(opts, { - prompt_title = t("Available Languages"), - finder = finders.new_table({ - results = question.q.code_snippets, - entry_maker = entry_maker, - }), - sorter = conf.generic_sorter(opts), - attach_mappings = function(prompt_bufnr, map) - actions.select_default:replace(function() - local selection = action_state.get_selected_entry() - if not selection then - return - end - - local snippet = selection.value - if question.lang == snippet.t.slug then - return log.warn( - ("%s: %s"):format(t("Language already set to"), snippet.t.lang) - ) - end - - config.lang = snippet.t.slug - actions.close(prompt_bufnr) - callback(snippet) - end) - - return true - end, - }) - :find() -end - ----@param question lc.ui.Question -M.pick = function(question) - pickers - .new(opts, { - prompt_title = t("Available Languages"), - finder = finders.new_table({ - results = question.q.code_snippets, - entry_maker = entry_maker, - }), - sorter = conf.generic_sorter(opts), - attach_mappings = function(prompt_bufnr, map) - actions.select_default:replace(function() - local selection = action_state.get_selected_entry() - if not selection then - return - end - - local snippet = selection.value - if question.lang == snippet.t.slug then - return log.warn( - ("%s: %s"):format(t("Language already set to"), snippet.t.lang) - ) - end - - config.lang = snippet.t.slug - actions.close(prompt_bufnr) - question:change_lang(snippet.t.slug) - end) - - return true - end, - }) - :find() -end - -return M diff --git a/lua/leetcode/pickers/question-tabs.lua b/lua/leetcode/pickers/question-tabs.lua deleted file mode 100644 index e22426d..0000000 --- a/lua/leetcode/pickers/question-tabs.lua +++ /dev/null @@ -1,122 +0,0 @@ -local log = require("leetcode.logger") -local utils = require("leetcode.utils") -local ui_utils = require("leetcode-ui.utils") -local t = require("leetcode.translator") - -local pickers = require("telescope.pickers") -local finders = require("telescope.finders") -local conf = require("telescope.config").values -local config = require("leetcode.config") -local icons = config.icons - -local entry_display = require("telescope.pickers.entry_display") -local actions = require("telescope.actions") -local action_state = require("telescope.actions.state") - ----@param q lc.QuestionResponse ---- ----@return string -local function question_formatter(q) - return string.format("%s. %s %s", q.frontend_id, q.title, q.translated_title) -end - -local function display_current(entry) - local tabp = vim.api.nvim_get_current_tabpage() - if tabp ~= entry.tabpage then - return unpack({ "", "" }) - end - - return { icons.caret.right, "leetcode_ref" } -end - -local function display_difficulty(q) - local lang = utils.get_lang(q.lang) - if not lang then - return {} - end - return { lang.icon, "leetcode_lang_" .. lang.slug } -end - ----@param question lc.QuestionResponse -local function display_question(question) - local hl = ui_utils.diff_to_hl(question.difficulty) - - local index = { question.frontend_id .. ".", hl } - local title = { utils.translate(question.title, question.translated_title) } - - return unpack({ index, title }) -end - -local displayer = entry_display.create({ - separator = " ", - items = { - { width = 1 }, - { width = 1 }, - { width = 5 }, - { remaining = true }, - }, -}) - -local function make_display(entry) - ---@type lc.cache.Question - local q = entry.value.question.q - - return displayer({ - display_current(entry.value), - display_difficulty(entry.value.question), - display_question(q), - }) -end - -local function entry_maker(entry) - return { - value = entry, - display = make_display, - ordinal = question_formatter(entry.question.q), - } -end - -local opts = require("telescope.themes").get_dropdown() - -return { - pick = function() - local tabs = utils.question_tabs() - - if vim.tbl_isempty(tabs) then - return log.warn("No questions opened") - end - - -- table.sort(tabs, function(q1, q2) - -- local fid1, fid2 = - -- tonumber(q1.question.q.frontend_id), tonumber(q2.question.q.frontend_id) - -- return (fid1 and fid2) and fid1 < fid2 or fid1 ~= nil - -- end) - - pickers - .new(opts, { - prompt_title = t("Select a Question"), - finder = finders.new_table({ - results = tabs, - entry_maker = entry_maker, - }), - sorter = conf.generic_sorter(opts), - attach_mappings = function(prompt_bufnr) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - - if not selection then - return - end - local ok, err = - pcall(vim.api.nvim_set_current_tabpage, selection.value.tabpage) - if not ok then - log.error(err) - end - end) - return true - end, - }) - :find() - end, -} diff --git a/lua/leetcode/pickers/question.lua b/lua/leetcode/pickers/question.lua deleted file mode 100644 index d8fa117..0000000 --- a/lua/leetcode/pickers/question.lua +++ /dev/null @@ -1,148 +0,0 @@ -local log = require("leetcode.logger") -local t = require("leetcode.translator") -local utils = require("leetcode.utils") -local ui_utils = require("leetcode-ui.utils") - -local Question = require("leetcode-ui.question") - -local pickers = require("telescope.pickers") -local finders = require("telescope.finders") -local conf = require("telescope.config").values -local config = require("leetcode.config") - -local entry_display = require("telescope.pickers.entry_display") -local actions = require("telescope.actions") -local action_state = require("telescope.actions.state") - ----@param question lc.cache.Question ---- ----@return string -local function question_formatter(question) - return ("%s. %s %s %s"):format( - tostring(question.frontend_id), - question.title, - question.title_cn, - question.title_slug - ) -end - ----@param question lc.cache.Question -local function display_difficulty(question) - local hl = ui_utils.diff_to_hl(question.difficulty) - return { config.icons.square, hl } -end - ----@param question lc.cache.Question -local function display_user_status(question) - if question.paid_only then - return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock - end - - return config.icons.hl.status[question.status] or { "" } -end - ----@param question lc.cache.Question -local function display_question(question) - local ac_rate = { ("%.1f%%"):format(question.ac_rate), "leetcode_ref" } - local index = { question.frontend_id .. ".", "leetcode_normal" } - - local title = { utils.translate(question.title, question.title_cn) } - - return unpack({ index, title, ac_rate }) -end - -local displayer = entry_display.create({ - separator = " ", - items = { - { width = 1 }, - { width = 1 }, - { width = 5 }, - { width = 78 }, - { width = 5 }, - }, -}) - -local function make_display(entry) - ---@type lc.cache.Question - local q = entry.value - - return displayer({ - display_user_status(q), - display_difficulty(q), - display_question(q), - }) -end - -local function entry_maker(entry) - return { - value = entry, - display = make_display, - ordinal = question_formatter(entry), - } -end - -local theme = require("telescope.themes").get_dropdown({ - layout_config = { - width = 100, - height = 20, - }, -}) - ----@param questions lc.cache.Question[] ----@param opts table ---- ----@return lc.cache.Question[] -local function filter_questions(questions, opts) - if vim.tbl_isempty(opts or {}) then - return questions - end - - ---@param q lc.cache.Question - return vim.tbl_filter(function(q) - local diff_flag = true - if opts.difficulty and not vim.tbl_contains(opts.difficulty, q.difficulty:lower()) then - diff_flag = false - end - - local status_flag = true - if opts.status and not vim.tbl_contains(opts.status, q.status) then - status_flag = false - end - - return diff_flag and status_flag - end, questions) -end - -return { - ---@param questions lc.cache.Question[] - pick = function(questions, opts) - pickers - .new(theme, { - prompt_title = t("Select a Question"), - finder = finders.new_table({ - results = filter_questions(questions, opts), - entry_maker = entry_maker, - }), - sorter = conf.generic_sorter(theme), - attach_mappings = function(prompt_bufnr, map) - actions.select_default:replace(function() - local selection = action_state.get_selected_entry() - if not selection then - return - end - - local q = selection.value - if q.paid_only and not config.auth.is_premium then - return log.warn("Question is for premium users only") - end - - actions.close(prompt_bufnr) - Question(q):mount() - end) - - return true - end, - }) - :find() - end, -}