From 7a41cd9b08b580982918056f2a56af8d8ad50cd0 Mon Sep 17 00:00:00 2001 From: kawre Date: Tue, 9 Jan 2024 02:25:54 +0100 Subject: [PATCH 001/115] feat: `Leet yank` command --- lua/leetcode-ui/question.lua | 14 ++++++++++---- lua/leetcode/command/init.lua | 10 ++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua index 7b3fb555..f5cc8ae6 100644 --- a/lua/leetcode-ui/question.lua +++ b/lua/leetcode-ui/question.lua @@ -136,19 +136,25 @@ function Question:mount() return self end ----@return string -function Question:lines() +---@return integer, integer, string[] +function Question:range() local lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) local start_i, end_i = 1, #lines for i, line in ipairs(lines) do if line:match("@leet start") then start_i = i + 1 - else - if line:match("@leet end") then end_i = i - 1 end + elseif line:match("@leet end") then + end_i = i - 1 end end + return start_i, end_i, lines +end + +---@return string +function Question:lines() + local start_i, end_i, lines = self:range() return table.concat(lines, "\n", start_i, end_i) end diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index a4c7b387..3776b81d 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -155,6 +155,15 @@ function cmd.menu() end end +function cmd.yank() + local utils = require("leetcode.utils") + local q = utils.curr_question() + if not q then return end + + local start_i, end_i = q:range() + vim.cmd(("%d,%dyank"):format(start_i, end_i)) +end + ---@param page lc-menu.page function cmd.menu_layout(page) _Lc_Menu:set_page(page) end @@ -346,6 +355,7 @@ cmd.commands = { submit = { cmd.q_submit }, daily = { cmd.qot }, fix = { cmd.fix }, + yank = { cmd.yank }, list = { cmd.problems, From b6876dbeeb13e900ff2297aa90769e86a7890202 Mon Sep 17 00:00:00 2001 From: kawre Date: Tue, 9 Jan 2024 20:43:43 +0100 Subject: [PATCH 002/115] fix(yank): focus question window on `Leet yank` --- lua/leetcode/command/init.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index e9a6a645..28dc6241 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -167,8 +167,11 @@ function cmd.yank() local q = utils.curr_question() if not q then return end - local start_i, end_i = q:range() - vim.cmd(("%d,%dyank"):format(start_i, end_i)) + if vim.api.nvim_win_is_valid(q.winid) then + vim.api.nvim_set_current_win(q.winid) + local start_i, end_i = q:range() + vim.cmd(("%d,%dyank"):format(start_i, end_i)) + end end ---@param page lc-menu.page From 709aaee5d529e68fd3d1d707809f515ea1716c91 Mon Sep 17 00:00:00 2001 From: kawre Date: Wed, 10 Jan 2024 01:30:41 +0100 Subject: [PATCH 003/115] feat(cache): separate `home` and `cache` dirs --- lua/leetcode.lua | 3 +++ lua/leetcode/cache/cookie.lua | 2 +- lua/leetcode/cache/problemlist.lua | 2 +- lua/leetcode/cache/solutions.lua | 0 lua/leetcode/config/init.lua | 1 + lua/leetcode/runner/init.lua | 2 +- 6 files changed, 7 insertions(+), 3 deletions(-) delete mode 100644 lua/leetcode/cache/solutions.lua diff --git a/lua/leetcode.lua b/lua/leetcode.lua index d1f66a8d..622d60b3 100644 --- a/lua/leetcode.lua +++ b/lua/leetcode.lua @@ -57,6 +57,9 @@ function leetcode.start() config.home = path:new(config.user.directory) ---@diagnostic disable-line config.home:mkdir() + config.cache_dir = path:new(vim.fn.stdpath("cache") .. "/leetcode/") ---@diagnostic disable-line + config.cache_dir:mkdir() + vim.api.nvim_set_current_dir(config.home:absolute()) leetcode.setup_cmds() diff --git a/lua/leetcode/cache/cookie.lua b/lua/leetcode/cache/cookie.lua index e6b2d667..ccdc10b9 100644 --- a/lua/leetcode/cache/cookie.lua +++ b/lua/leetcode/cache/cookie.lua @@ -3,7 +3,7 @@ local log = require("leetcode.logger") local config = require("leetcode.config") ---@type Path -local file = config.home:joinpath((".cookie%s"):format(config.is_cn and "_cn" or "")) +local file = config.cache_dir:joinpath((".cookie%s"):format(config.is_cn and "_cn" or "")) local hist = {} diff --git a/lua/leetcode/cache/problemlist.lua b/lua/leetcode/cache/problemlist.lua index a01ad384..10656422 100644 --- a/lua/leetcode/cache/problemlist.lua +++ b/lua/leetcode/cache/problemlist.lua @@ -6,7 +6,7 @@ local config = require("leetcode.config") local interval = config.user.cache.update_interval ---@type Path -local file = config.home:joinpath((".problemlist%s"):format(config.is_cn and "_cn" or "")) +local file = config.cache_dir:joinpath((".problemlist%s"):format(config.is_cn and "_cn" or "")) ---@type { at: integer, payload: lc.cache.payload } local hist = nil diff --git a/lua/leetcode/cache/solutions.lua b/lua/leetcode/cache/solutions.lua deleted file mode 100644 index e69de29b..00000000 diff --git a/lua/leetcode/config/init.lua b/lua/leetcode/config/init.lua index 9ba43a72..b98d25b8 100644 --- a/lua/leetcode/config/init.lua +++ b/lua/leetcode/config/init.lua @@ -17,6 +17,7 @@ local config = { debug = false, lang = "cpp", home = {}, ---@type Path + cache_dir = {}, ---@type Path version = "1.0.1", translator = false, diff --git a/lua/leetcode/runner/init.lua b/lua/leetcode/runner/init.lua index e3a5ff8c..d1698940 100644 --- a/lua/leetcode/runner/init.lua +++ b/lua/leetcode/runner/init.lua @@ -3,7 +3,7 @@ local interpreter = require("leetcode.api.interpreter") local config = require("leetcode.config") ---@type Path -local leetbody = config.home:joinpath(".leetbody") +local leetbody = config.cache_dir:joinpath(".leetbody") leetbody:touch() ---@class lc.Runner From 2bef48e5c37c42d1ba591c324bb9cc8a78ba1598 Mon Sep 17 00:00:00 2001 From: kawre Date: Wed, 10 Jan 2024 13:21:08 +0100 Subject: [PATCH 004/115] feat: exclude `lang` from filename --- lua/leetcode-ui/question.lua | 3 +- lua/leetcode/cache/cookie.lua | 2 +- lua/leetcode/cache/problemlist.lua | 2 +- lua/leetcode/config/langs.lua | 60 +----------------------------- lua/leetcode/runner/init.lua | 2 +- 5 files changed, 7 insertions(+), 62 deletions(-) diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua index f5cc8ae6..1b5eb169 100644 --- a/lua/leetcode-ui/question.lua +++ b/lua/leetcode-ui/question.lua @@ -34,7 +34,8 @@ end ---@private function Question:create_file() local lang = utils.get_lang(self.lang) - local fn = ("%s.%s-%s.%s"):format(self.q.frontend_id, self.q.title_slug, lang.slug, lang.ft) + local alt = lang.alt and ("-" .. lang.alt) or "" + local fn = ("%s.%s%s.%s"):format(self.q.frontend_id, self.q.title_slug, alt, lang.ft) self.file = config.home:joinpath(fn) if not self.file:exists() then self.file:write(self:get_snippet(), "w") end diff --git a/lua/leetcode/cache/cookie.lua b/lua/leetcode/cache/cookie.lua index ccdc10b9..55057e86 100644 --- a/lua/leetcode/cache/cookie.lua +++ b/lua/leetcode/cache/cookie.lua @@ -3,7 +3,7 @@ local log = require("leetcode.logger") local config = require("leetcode.config") ---@type Path -local file = config.cache_dir:joinpath((".cookie%s"):format(config.is_cn and "_cn" or "")) +local file = config.cache_dir:joinpath(("cookie%s"):format(config.is_cn and "_cn" or "")) local hist = {} diff --git a/lua/leetcode/cache/problemlist.lua b/lua/leetcode/cache/problemlist.lua index 10656422..27b2c90a 100644 --- a/lua/leetcode/cache/problemlist.lua +++ b/lua/leetcode/cache/problemlist.lua @@ -6,7 +6,7 @@ local config = require("leetcode.config") local interval = config.user.cache.update_interval ---@type Path -local file = config.cache_dir:joinpath((".problemlist%s"):format(config.is_cn and "_cn" or "")) +local file = config.cache_dir:joinpath(("problemlist%s"):format(config.is_cn and "_cn" or "")) ---@type { at: integer, payload: lc.cache.payload } local hist = nil diff --git a/lua/leetcode/config/langs.lua b/lua/leetcode/config/langs.lua index 052b13e9..f55a63cd 100644 --- a/lua/leetcode/config/langs.lua +++ b/lua/leetcode/config/langs.lua @@ -3,11 +3,10 @@ ---@field slug string ---@field icon string ---@field color string ----@field short string ---@field hl? string ---@field comment string ----@field sql boolean|nil ---@field ft string +---@field alt? string ---@type lc.language[] return { @@ -16,7 +15,6 @@ return { slug = "cpp", icon = "", color = "#00599C", - short = "cpp", ft = "cpp", comment = "//", }, @@ -25,7 +23,6 @@ return { slug = "java", icon = "", color = "#E76F00", - short = "java", ft = "java", comment = "//", }, @@ -34,16 +31,15 @@ return { slug = "python", icon = "", color = "#306998", - short = "pythn", ft = "py", comment = "#", + alt = "py2", }, { lang = "Python3", slug = "python3", icon = "", color = "#306998", - short = "pyth3", ft = "py", comment = "#", }, @@ -52,7 +48,6 @@ return { slug = "c", icon = "", color = "#555555", - short = "clang", ft = "c", comment = "//", }, @@ -61,7 +56,6 @@ return { slug = "csharp", icon = "󰌛", color = "#68217A", - short = "cshrp", ft = "cs", comment = "//", }, @@ -70,7 +64,6 @@ return { slug = "javascript", icon = "", color = "#F0DB4F", - short = "js", ft = "js", comment = "//", }, @@ -79,7 +72,6 @@ return { slug = "typescript", icon = "", color = "#3178C6", - short = "ts", ft = "ts", comment = "//", }, @@ -88,7 +80,6 @@ return { slug = "php", icon = "", color = "#777BB4", - short = "php", ft = "php", comment = "//", }, @@ -97,7 +88,6 @@ return { slug = "swift", icon = "", color = "#FFAC45", - short = "swift", ft = "swift", comment = "//", }, @@ -106,7 +96,6 @@ return { slug = "kotlin", icon = "", color = "#7F52FF", - short = "ktlin", ft = "kt", comment = "//", }, @@ -115,7 +104,6 @@ return { slug = "dart", icon = "", color = "#1057A7", - short = "dart", ft = "dart", comment = "//", }, @@ -124,7 +112,6 @@ return { slug = "golang", icon = "", color = "#00ADD8", - short = "golng", ft = "go", comment = "//", }, @@ -133,7 +120,6 @@ return { slug = "ruby", icon = "", color = "#CC342D", - short = "ruby", ft = "rb", comment = "#", }, @@ -142,7 +128,6 @@ return { slug = "scala", icon = "", color = "#DC322F", - short = "scala", ft = "scala", comment = "//", }, @@ -151,7 +136,6 @@ return { slug = "rust", icon = "", color = "#DEA584", - short = "rust", ft = "rs", comment = "//", }, @@ -160,7 +144,6 @@ return { slug = "racket", icon = "󰰟", color = "#22228F", - short = "rcket", ft = "rkt", comment = ";;", }, @@ -169,7 +152,6 @@ return { slug = "erlang", icon = "", color = "#A90533", - short = "erlng", ft = "erl", comment = "%", }, @@ -178,7 +160,6 @@ return { slug = "elixir", icon = "", color = "#6E4A7E", - short = "elixr", ft = "ex", comment = "#", }, @@ -187,44 +168,7 @@ return { slug = "bash", icon = "󱆃", color = "#000000", - short = "bash", ft = "sh", comment = "#", }, - -- { - -- lang = "HTML", - -- slug = "html", - -- icon = "", - -- color = "#E44D26", - -- short = "html", - -- ft = "html", - -- comment = " https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-123cb32b7563 -> [!CAUTION] -> This plugin has been primarily tested with `Java`. -> If you encounter any errors while using other languages, -> please open an issue to report them. - ## ✨ Features - 📌 an intuitive dashboard for effortless navigation within [leetcode.nvim] diff --git a/README.zh.md b/README.zh.md deleted file mode 100644 index 194228cf..00000000 --- a/README.zh.md +++ /dev/null @@ -1,497 +0,0 @@ -
- -# leetcode.nvim - -🔥 在 [Neovim] 中解决 [LeetCode] 问题 🔥 - -🇺🇸 English, 🇨🇳 简体中文 - -
- -https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-123cb32b7563 - -> [!CAUTION] -> 此插件仅与`Java`进行了专门测试。 -> 如果您在使用其他语言时遇到任何错误,请打开一个问题报告它们。 - -## ✨ 特性 - -- 📌 直观的仪表板,轻松导航 [leetcode.nvim] 内 - -- 😍 更好的可读性的问题描述格式 - -- 📈 在 [Neovim] 中显示 [LeetCode] 个人统计信息 - -- 🔀 支持每日和随机问题 - -- 💾 缓存以优化性能 - -## 📬 环境要求 - -- [Neovim] >= 0.9.0 - -- [telescope.nvim] - -- [nui.nvim] - -- [nvim-treesitter] _**(可选,但强烈推荐)**_ - 用于格式化问题描述。 - 确保安装 `html` 解析器。 - -- [nvim-notify] _**(可选)**_ - -- [Nerd Font][nerd-font] & [nvim-web-devicons] _**(可选)**_ - -## 📦 安装 - -- [lazy.nvim] - -```lua -{ - "kawre/leetcode.nvim", - build = ":TSUpdate html", - dependencies = { - "nvim-telescope/telescope.nvim", - "nvim-lua/plenary.nvim", -- telescope 所需 - "MunifTanjim/nui.nvim", - - -- 可选 - "nvim-treesitter/nvim-treesitter", - "rcarriga/nvim-notify", - "nvim-tree/nvim-web-devicons", - }, - opts = { - -- 配置放在这里 - cn = { - enabled = true, - }, - }, -} -``` - -## 🛠️ 配置 - -要查看完整的配置类型,请参见 [template.lua](./lua/leetcode/config/template.lua) - -### ⚙️ 默认配置 - -```lua -{ - ---@type string - arg = "leetcode.nvim", - - ---@type lc.lang - lang = "cpp", - - cn = { -- leetcode.cn - enabled = false, ---@type boolean - translator = true, ---@type boolean - translate_problems = true, ---@type boolean - }, - - ---@type lc.storage - storage = { - home = vim.fn.stdpath("data") .. "/leetcode", - cache = vim.fn.stdpath("cache") .. "/leetcode", - }, - - ---@type table - plugins = { - non_standalone = false, - }, - - ---@type boolean - logging = true, - - injector = {}, ---@type table - - cache = { - update_interval = 60 * 60 * 24 * 7, ---@type integer 7 days - }, - - console = { - open_on_runcode = true, ---@type boolean - - dir = "row", ---@type lc.direction - - size = { ---@type lc.size - width = "90%", - height = "75%", - }, - - result = { - size = "60%", ---@type lc.size - }, - - testcase = { - virt_text = true, ---@type boolean - - size = "40%", ---@type lc.size - }, - }, - - description = { - position = "left", ---@type lc.position - - width = "40%", ---@type lc.size - - show_stats = true, ---@type boolean - }, - - hooks = { - ---@type fun()[] - ["enter"] = {}, - - ---@type fun(question: lc.ui.Question)[] - ["question_enter"] = {}, - - ---@type fun()[] - ["leave"] = {}, - }, - - keys = { - toggle = { "q" }, ---@type string|string[] - confirm = { "" }, ---@type string|string[] - - reset_testcases = "r", ---@type string - use_testcase = "U", ---@type string - focus_testcases = "H", ---@type string - focus_result = "L", ---@type string - }, - - ---@type lc.highlights - theme = {}, - - ---@type boolean - image_support = false, -} -``` - -### arg - -[Neovim] 的参数 - -```lua ----@type string -arg = "leetcode.nvim" -``` - -有关更多信息,请参见 [usage](#-usage) - -### lang - -会话开始时使用的语言 - -```lua ----@type lc.lang -lang = "cpp" -``` - -
- 可用编程语言 - -| Language | lang | -| ---------- | ---------- | -| C++ | cpp | -| Java | java | -| Python | python | -| Python3 | python3 | -| C | c | -| C# | csharp | -| JavaScript | javascript | -| TypeScript | typescript | -| PHP | php | -| Swift | swift | -| Kotlin | kotlin | -| Dart | dart | -| Go | golang | -| Ruby | ruby | -| Scala | scala | -| Rust | rust | -| Racket | racket | -| Erlang | erlang | -| Elixir | elixir | -| Bash | bash | - -
- -### cn - -将 [leetcode.com][leetcode] 替换为 [leetcode.cn] - -```lua -cn = { -- leetcode.cn - enabled = false, ---@type boolean - translator = true, ---@type boolean - translate_problems = true, ---@type boolean -}, -``` - -### storage - -存储目录 - -```lua ----@type lc.storage -storage = { - home = vim.fn.stdpath("data") .. "/leetcode", - cache = vim.fn.stdpath("cache") .. "/leetcode", -}, -``` - -### plugins - -[插件列表](#-plugins) - -```lua ----@type table -plugins = { - non_standalone = false, -}, -``` - -### logging - -是否记录 [leetcode.nvim] 状态通知 - -```lua ----@type boolean -logging = true -``` - -### injector - -在你的答案前后注入额外代码,注入的代码不会被提交或测试。 - -#### 默认导入 - -您还可以传递 `before = true` 以注入语言的默认导入。 -支持的语言为 `python`、`python3`、`java` - -通过 `require("leetcode.config.imports")` 访问默认导入 - -```lua -injector = { ---@type table - ["cpp"] = { - before = { "#include ", "using namespace std;" }, - after = "int main() {}", - }, - ["java"] = { - before = "import java.util.*;", - }, -} -``` - -### hooks - -在指定事件上执行的函数列表 - -```lua -hooks = { - ---@type fun()[] - ["enter"] = {}, - - ---@type fun(question: lc.ui.Question)[] - ["question_enter"] = {}, - - ---@type fun()[] - ["leave"] = {}, -}, -``` - -### theme - -覆盖[默认主题](./lua/leetcode/theme/default.lua) - -每个值都与 `:help nvim_set_hl` 中的val参数相同类型 - -```lua ----@type lc.highlights -theme = { - ["alt"] = { - bg = "#FFFFFF", - }, - ["normal"] = { - fg = "#EA4AAA", - }, -}, -``` - -### image support - -是否使用 [image.nvim] 渲染问题描述中的图片 - -> [!WARNING] -> 启用此选项将禁用问题描述的换行,因为 -> https://github.com/3rd/image.nvim/issues/62#issuecomment-1778082534 - -```lua ----@type boolean -image_support = false, -- 将此设置为 `true` 将禁用问题描述的换行 -``` - -## 📋 命令 - -### `Leet` 打开菜单仪表板 - -- `menu` 与 `Leet` 相同 - -- `exit` 关闭 [leetcode.nvim] - -- `console` 打开当前打开问题的控制台弹出窗口 - -- `info` 打开包含当前打开问题信息的弹出窗口 - -- `tabs` 打开所有当前打开问题选项卡的选择器 - -- `yank` 复制当前问题的解决方案 - -- `lang` 打开更改当前问题语言的选择器 - -- `run` 运行当前打开的问题 - -- `test` 与 `Leet run` 相同 - -- `submit` 提交当前打开的问题 - -- `random` 打开一个随机问题 - -- `daily` 打开今天的问题 - -- `list` 打开问题列表选择器 - -- `open` 在默认浏览器中打开当前问题 - -- `reset` 还原到默认的代码模版 - -- `last_submit` 检索上次提交的代码,用于当前问题 - -- `restore` 尝试恢复默认问题布局 - -- `inject` 重新注入当前问题的代码 - -- `session` - - - `create` 创建一个新的会话 - - - `change` 更改当前会话 - - - `update` 更新当前会话,以防它失去同步 - -- `desc` 切换问题描述 - - - `toggle` 与 `Leet desc` 相同 - - - `stats` 切换描述统计可见性 - -- `cookie` - - - `update` 打开提示输入新 cookie - - - `delete` 注销 - -- `cache` - - - `update` 更新缓存 - -#### 可以带有可选参数。要堆叠参数值,请使用 `,` 将它们分隔开 - -- `Leet list` - - ``` - Leet list status= difficulty= - ``` - -- `Leet random` - - ``` - Leet random status= difficulty= tags= - ``` - -## 🚀 使用方法 - -该插件可以通过两种方式启动: - -- 要启动 [leetcode.nvim],只需将 [`arg`](#arg) - 作为 第一个且唯一 [Neovim] 参数传递 - - ``` - nvim leetcode.nvim - ``` - -- _**(实验性)**_ 另外,您可以使用 `:Leet` 命令在您喜欢的仪表板插件中打开 - [leetcode.nvim]。唯一的要求是 [Neovim] 不能有任何列出的缓冲区打开。 - -### 切换问题 - -要在问题之间切换,请使用 `Leet tabs`。 - -### 登录 - -使用 [leetcode.nvim] 必须 **登录** - -https://github.com/kawre/leetcode.nvim/assets/69250723/b7be8b95-5e2c-4153-8845-4ad3abeda5c3 - -## 🍴 示例 - -### 💤 使用 [lazy.nvim] 进行延迟加载 - -> [!WARNING] -> 选择其中任一选项将由于延迟加载而使另一启动方法不可用。 - -- 使用 [`arg`](#arg) - - ```lua - local leet_arg = "leetcode.nvim" - ``` - - ```lua - { - "kawre/leetcode.nvim", - lazy = leet_arg ~= vim.fn.argv()[1], - opts = { arg = leet_arg }, - } - ``` - -- 使用 `:Leet` - - ```lua - { - "kawre/leetcode.nvim", - cmd = "Leet", - } - ``` - -## 🧩 Plugins - -### Non-Standalone mode - -要在非独立模式下运行 [leetcode.nvim](即不带参数或在空的 Neovim 会话中运行), -请在您的配置中启用 `non_standalone` 插件: - -```lua -plugins = { - non_standalone = true, -} -``` - -你可以使用 `:Leet exit` 命令退出 [leetcode.nvim] - -## 🙌 鸣谢 - -- [Leetbuddy.nvim](https://github.com/Dhanus3133/Leetbuddy.nvim) - -- [alpha-nvim](https://github.com/goolord/alpha-nvim) - -[image.nvim]: https://github.com/3rd/image.nvim -[lazy.nvim]: https://github.com/folke/lazy.nvim -[leetcode]: https://leetcode.com -[leetcode.cn]: https://leetcode.cn -[leetcode.nvim]: https://github.com/kawre/leetcode.nvim -[neovim]: https://github.com/neovim/neovim -[nerd-font]: https://www.nerdfonts.com -[nui.nvim]: https://github.com/MunifTanjim/nui.nvim -[nvim-notify]: https://github.com/rcarriga/nvim-notify -[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 From dc02f1424756d759ed46a4ad75224a4717065e34 Mon Sep 17 00:00:00 2001 From: kawre Date: Sat, 26 Oct 2024 22:38:22 +0200 Subject: [PATCH 103/115] chore(README): add Windows recipe --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a90f4257..ea2ac4bb 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,11 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/b7be8b95-5e2c-4153-8845-4 } ``` +### 🪟 Windows + +If you are using Windows, +it is recommended to use [Cygwin](https://www.cygwin.com/) for a more consistent and Unix-like experience. + ## 🧩 Plugins ### Non-Standalone mode From 94c16d8be8d09c0b543782b0323ce6eb1fbc95c5 Mon Sep 17 00:00:00 2001 From: Tarek Dakhran Date: Sun, 27 Oct 2024 02:06:10 +0200 Subject: [PATCH 104/115] fix: filter question by `todo` status (#127) * Fix todo status handling For `todo` status api return vim.NIL, normalize it into "todo". Fixes #105. * refactor: remove `todo` icon --------- Co-authored-by: kawre --- lua/leetcode/api/utils.lua | 2 +- lua/leetcode/config/icons.lua | 2 +- lua/leetcode/pickers/question.lua | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lua/leetcode/api/utils.lua b/lua/leetcode/api/utils.lua index a8ba89ea..74c9d5b1 100644 --- a/lua/leetcode/api/utils.lua +++ b/lua/leetcode/api/utils.lua @@ -212,7 +212,7 @@ function utils.normalize_problems(problems) return vim.tbl_map(function(p) return { - status = p.status, + status = p.status == vim.NIL and "todo" or p.status, -- api returns nil for todo id = p.stat.question_id, frontend_id = p.stat.frontend_question_id, title = p.stat.question__title, diff --git a/lua/leetcode/config/icons.lua b/lua/leetcode/config/icons.lua index b8df2022..ac912bd8 100644 --- a/lua/leetcode/config/icons.lua +++ b/lua/leetcode/config/icons.lua @@ -19,7 +19,7 @@ icons.hl = { status = { ac = { icons.status.ac, "leetcode_easy" }, notac = { icons.status.notac, "leetcode_medium" }, - todo = { icons.status.todo, "leetcode_alt" }, + -- todo = { icons.status.todo, "leetcode_alt" }, }, lock = { icons.lock, "leetcode_medium" }, unlock = { icons.unlock, "leetcode_medium" }, diff --git a/lua/leetcode/pickers/question.lua b/lua/leetcode/pickers/question.lua index cd586cb9..d8fa1177 100644 --- a/lua/leetcode/pickers/question.lua +++ b/lua/leetcode/pickers/question.lua @@ -38,9 +38,6 @@ local function display_user_status(question) return config.auth.is_premium and config.icons.hl.unlock or config.icons.hl.lock end - if question.status == vim.NIL then - return { "" } - end return config.icons.hl.status[question.status] or { "" } end From e2e43b38085faf51c0b62d239f29eeac463161ba Mon Sep 17 00:00:00 2001 From: Marcin <69250723+kawre@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:04:43 +0100 Subject: [PATCH 105/115] feat: `winfixbuf` support (#137) * feat: add `winfixbuf` support * feat(utils): `with_version` * refactor: `cmd.menu()` * feat(ui-utils): new utility * fix(yank): check for `0.10` --- lua/leetcode-ui/popup/info.lua | 4 ++-- lua/leetcode-ui/question.lua | 6 +++-- lua/leetcode-ui/renderer/init.lua | 6 ++--- lua/leetcode-ui/renderer/menu.lua | 10 ++++---- lua/leetcode-ui/split/description.lua | 6 +++-- lua/leetcode-ui/utils.lua | 33 +++++++++++++++++++++++++-- lua/leetcode/command/init.lua | 12 ++++++---- lua/leetcode/utils.lua | 6 +++++ 8 files changed, 64 insertions(+), 19 deletions(-) diff --git a/lua/leetcode-ui/popup/info.lua b/lua/leetcode-ui/popup/info.lua index e62f1ba8..82bc68f2 100644 --- a/lua/leetcode-ui/popup/info.lua +++ b/lua/leetcode-ui/popup/info.lua @@ -134,12 +134,12 @@ function Info:mount() local ui_utils = require("leetcode-ui.utils") local winhighlight = "Normal:NormalSB,FloatBorder:FloatBorder" - ui_utils.set_win_opts(self.winid, { + ui_utils.win_set_opts(self.winid, { winhighlight = winhighlight, wrap = true, }) - ui_utils.set_win_opts(self.border.winid, { + ui_utils.win_set_opts(self.border.winid, { winhighlight = winhighlight, }) diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua index d9093734..df35892b 100644 --- a/lua/leetcode-ui/question.lua +++ b/lua/leetcode-ui/question.lua @@ -5,6 +5,7 @@ local Object = require("nui.object") local api_question = require("leetcode.api.question") local utils = require("leetcode.utils") +local ui_utils = require("leetcode-ui.utils") local config = require("leetcode.config") local log = require("leetcode.logger") @@ -88,14 +89,15 @@ function Question:create_buffer() vim.cmd("$tabe " .. path) self.bufnr = vim.api.nvim_get_current_buf() self.winid = vim.api.nvim_get_current_win() + ui_utils.win_set_winfixbuf(self.winid) self:open_buffer(existed) end ---@param existed boolean function Question:open_buffer(existed) - vim.api.nvim_win_set_buf(self.winid, self.bufnr) - vim.api.nvim_set_option_value("buflisted", true, { buf = self.bufnr }) + ui_utils.buf_set_opts(self.bufnr, { buflisted = true }) + ui_utils.win_set_buf(self.winid, self.bufnr, true) local i = self:fold_range() if i then diff --git a/lua/leetcode-ui/renderer/init.lua b/lua/leetcode-ui/renderer/init.lua index f0275bb3..c1c201ac 100644 --- a/lua/leetcode-ui/renderer/init.lua +++ b/lua/leetcode-ui/renderer/init.lua @@ -37,7 +37,7 @@ function Renderer:draw(component) self._.line_idx = 1 local c_ok, c = pcall(vim.api.nvim_win_get_cursor, self.winid) - self:modifiable(function() + self:with_modifiable(function() vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {}) vim.api.nvim_buf_clear_namespace(self.bufnr, -1, 0, -1) @@ -51,7 +51,7 @@ end ---@private --- ---@param fn function -function Renderer:modifiable(fn) +function Renderer:with_modifiable(fn) local bufnr = self.bufnr if not (bufnr and vim.api.nvim_buf_is_valid(bufnr)) then return @@ -112,7 +112,7 @@ function Renderer:clear() self._.line_idx = 1 self._.buttons = {} self:clear_keymaps() - self:modifiable(function() + self:with_modifiable(function() vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {}) end) end diff --git a/lua/leetcode-ui/renderer/menu.lua b/lua/leetcode-ui/renderer/menu.lua index b642dde0..d1fb2e51 100644 --- a/lua/leetcode-ui/renderer/menu.lua +++ b/lua/leetcode-ui/renderer/menu.lua @@ -1,7 +1,8 @@ local log = require("leetcode.logger") local cookie = require("leetcode.cache.cookie") local config = require("leetcode.config") -local utils = require("leetcode-ui.utils") +local ui_utils = require("leetcode-ui.utils") +local utils = require("leetcode.utils") local Renderer = require("leetcode-ui.renderer") local api = vim.api @@ -123,7 +124,7 @@ end function Menu:apply_options() api.nvim_buf_set_name(self.bufnr, "") - utils.set_buf_opts(self.bufnr, { + ui_utils.buf_set_opts(self.bufnr, { modifiable = false, buflisted = false, matchpairs = "", @@ -132,7 +133,7 @@ function Menu:apply_options() filetype = config.name, synmaxcol = 0, }) - utils.set_win_opts(self.winid, { + ui_utils.win_set_opts(self.winid, { wrap = false, colorcolumn = "", foldlevel = 999, @@ -145,6 +146,7 @@ function Menu:apply_options() spell = false, signcolumn = "no", }) + ui_utils.win_set_winfixbuf(self.winid) end function Menu:unmount() @@ -173,7 +175,7 @@ function Menu:remount() api.nvim_buf_delete(self.bufnr, { force = true }) end - vim.cmd("$tabnew") + vim.cmd("0tabnew") self.bufnr = api.nvim_get_current_buf() self.winid = api.nvim_get_current_win() diff --git a/lua/leetcode-ui/split/description.lua b/lua/leetcode-ui/split/description.lua index bf5eeb7b..a5c16c05 100644 --- a/lua/leetcode-ui/split/description.lua +++ b/lua/leetcode-ui/split/description.lua @@ -34,7 +34,7 @@ function Description:mount() self:populate() local ui_utils = require("leetcode-ui.utils") - ui_utils.set_buf_opts(self.bufnr, { + ui_utils.buf_set_opts(self.bufnr, { modifiable = false, buflisted = false, matchpairs = "", @@ -43,7 +43,7 @@ function Description:mount() filetype = config.name, synmaxcol = 0, }) - ui_utils.set_win_opts(self.winid, { + ui_utils.win_set_opts(self.winid, { winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder", wrap = not img_sup, colorcolumn = "", @@ -58,6 +58,8 @@ function Description:mount() signcolumn = "no", linebreak = true, }) + ui_utils.win_set_winfixbuf(self.winid) + if not img_ok and config.user.image_support then log.error("image.nvim not found but `image_support` is enabled") end diff --git a/lua/leetcode-ui/utils.lua b/lua/leetcode-ui/utils.lua index a387bb4e..b6d2aa4e 100644 --- a/lua/leetcode-ui/utils.lua +++ b/lua/leetcode-ui/utils.lua @@ -103,7 +103,7 @@ function utils.get_padding(lines, layout) return padding end -function utils.set_buf_opts(bufnr, options) +function utils.buf_set_opts(bufnr, options) if not vim.api.nvim_buf_is_valid(bufnr) then return end @@ -116,7 +116,7 @@ function utils.set_buf_opts(bufnr, options) end end -function utils.set_win_opts(winid, options) +function utils.win_set_opts(winid, options) if not vim.api.nvim_win_is_valid(winid) then return end @@ -130,6 +130,35 @@ function utils.set_win_opts(winid, options) end end +---@param winid number +function utils.win_set_winfixbuf(winid) + local u = require("leetcode.utils") + u.with_version("0.10.0", function() + utils.win_set_opts(winid, { winfixbuf = true }) + end) +end + +---@param winid number +---@param bufnr number +---@param force? boolean +function utils.win_set_buf(winid, bufnr, force) + local u = require("leetcode.utils") + + u.with_version("0.10.0", function() + local wfb = vim.api.nvim_win_get_option(winid, "winfixbuf") + + if not wfb then + vim.api.nvim_win_set_buf(winid, bufnr) + elseif force then + utils.win_set_opts(winid, { winfixbuf = false }) + vim.api.nvim_win_set_buf(winid, bufnr) + utils.win_set_opts(winid, { winfixbuf = true }) + end + end, function() + vim.api.nvim_win_set_buf(winid, bufnr) + end) +end + function utils.is_instance(instance, class) return type(instance) == "table" and O.is_instance(instance, class) end diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index fd56ae71..6d421990 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -177,10 +177,11 @@ end function cmd.menu() local winid, bufnr = _Lc_state.menu.winid, _Lc_state.menu.bufnr local ok, tabp = pcall(api.nvim_win_get_tabpage, winid) + local ui = require("leetcode-ui.utils") if ok then api.nvim_set_current_tabpage(tabp) - api.nvim_win_set_buf(winid, bufnr) + ui.win_set_buf(winid, bufnr) else _Lc_state.menu:remount() end @@ -198,7 +199,9 @@ function cmd.yank() and (q.winid and api.nvim_win_is_valid(q.winid)) then api.nvim_set_current_win(q.winid) - api.nvim_set_current_buf(q.bufnr) + utils.with_version("0.10.0", nil, function() + api.nvim_set_current_buf(q.bufnr) + end) local start_i, end_i, lines = q:range() vim.cmd(("%d,%dyank"):format(start_i or 1, end_i or #lines)) @@ -358,6 +361,7 @@ end function cmd.restore() local utils = require("leetcode.utils") + local ui = require("leetcode-ui.utils") local q = utils.curr_question() if not q then return @@ -367,7 +371,7 @@ function cmd.restore() (q.winid and api.nvim_win_is_valid(q.winid)) and (q.bufnr and api.nvim_buf_is_valid(q.bufnr)) then - api.nvim_win_set_buf(q.winid, q.bufnr) + ui.win_set_buf(q.winid, q.bufnr) end q.description:show() @@ -377,7 +381,7 @@ function cmd.restore() (winid and api.nvim_win_is_valid(winid)) -- and (bufnr and api.nvim_buf_is_valid(bufnr)) then - api.nvim_win_set_buf(winid, bufnr) + ui.win_set_buf(q.winid, q.bufnr) end end diff --git a/lua/leetcode/utils.lua b/lua/leetcode/utils.lua index ec58e783..25c49cd8 100644 --- a/lua/leetcode/utils.lua +++ b/lua/leetcode/utils.lua @@ -138,4 +138,10 @@ function utils.norm_ins(str) return ins:sub(2, #ins - 1) end +function utils.with_version(v, with, without) + with = with or function() end + without = without or function() end + return (vim.fn.has("nvim-" .. v) == 1 and with or without)() +end + return utils From 98a398cc120265725cfb9489a9355cb9f2a93f57 Mon Sep 17 00:00:00 2001 From: Marcin Date: Sun, 27 Oct 2024 22:41:54 +0100 Subject: [PATCH 106/115] fix: temporarily disable `Leet session` (#139) * #138 * fix: temporarily disable `Leet session` --- lua/leetcode/api/headers.lua | 2 +- lua/leetcode/command/init.lua | 22 +++++++++++----------- lua/leetcode/config/stats.lua | 14 +++++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lua/leetcode/api/headers.lua b/lua/leetcode/api/headers.lua index a95ee9f9..a476a12f 100644 --- a/lua/leetcode/api/headers.lua +++ b/lua/leetcode/api/headers.lua @@ -13,7 +13,7 @@ function headers.get() ["Content-Type"] = "application/json", ["Accept"] = "application/json", ["Host"] = ("leetcode.%s"):format(config.domain), - ["X-Requested-With"] = "XMLHttpRequest", + -- ["X-Requested-With"] = "XMLHttpRequest", }, cookie and { ["Cookie"] = cookie.str, ["x-csrftoken"] = cookie.csrftoken, diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index 6d421990..0b589fe9 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -624,17 +624,17 @@ cmd.commands = { last_submit = { cmd.last_submit }, restore = { cmd.restore }, inject = { cmd.inject }, - session = { - change = { - cmd.change_session, - _args = arguments.session_change, - }, - create = { - cmd.create_session, - _args = arguments.session_create, - }, - update = { cmd.update_sessions }, - }, + -- session = { + -- change = { + -- cmd.change_session, + -- _args = arguments.session_change, + -- }, + -- create = { + -- cmd.create_session, + -- _args = arguments.session_create, + -- }, + -- update = { cmd.update_sessions }, + -- }, list = { cmd.problems, _args = arguments.list, diff --git a/lua/leetcode/config/stats.lua b/lua/leetcode/config/stats.lua index 4f58e01f..ba968579 100644 --- a/lua/leetcode/config/stats.lua +++ b/lua/leetcode/config/stats.lua @@ -26,13 +26,13 @@ function Stats.update_sessions() Stats.progress = {} - stats_api.sessions(function(_, err) - if err then - return log.err(err) - end - - _Lc_state.menu:draw() - end) + -- stats_api.sessions(function(_, err) + -- if err then + -- return log.err(err) + -- end + -- + -- _Lc_state.menu:draw() + -- end) stats_api.session_progress(function(res, err) if err then From 6a2e54ff13027fb3ce46b61a0e721eccc020ec80 Mon Sep 17 00:00:00 2001 From: kawre Date: Mon, 28 Oct 2024 07:59:11 +0100 Subject: [PATCH 107/115] fix: menu duplication on launch (#141) --- lua/leetcode-ui/renderer/menu.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/leetcode-ui/renderer/menu.lua b/lua/leetcode-ui/renderer/menu.lua index d1fb2e51..4b0d9eac 100644 --- a/lua/leetcode-ui/renderer/menu.lua +++ b/lua/leetcode-ui/renderer/menu.lua @@ -146,7 +146,9 @@ function Menu:apply_options() spell = false, signcolumn = "no", }) - ui_utils.win_set_winfixbuf(self.winid) + vim.schedule(function() + ui_utils.win_set_winfixbuf(self.winid) + end) end function Menu:unmount() From 9197cf8f09b8b50056388a6792e058af818468fe Mon Sep 17 00:00:00 2001 From: kawre Date: Thu, 14 Nov 2024 11:55:59 +0100 Subject: [PATCH 108/115] feat(`Leet open`): use `vim.ui.open` --- lua/leetcode/command/init.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index 0b589fe9..8cfaae07 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -304,19 +304,23 @@ function cmd.open() local q = utils.curr_question() if q then - local command - local os_name = vim.loop.os_uname().sysname - - if os_name == "Linux" then - command = string.format("xdg-open '%s'", q.cache.link) - elseif os_name == "Darwin" then - command = string.format("open '%s'", q.cache.link) + if vim.ui.open then + vim.ui.open(q.cache.link) else - -- Fallback to Windows if uname is not available or does not match Linux/Darwin. - command = string.format("start \"\" \"%s\"", q.cache.link) - end + local command + local os_name = vim.loop.os_uname().sysname + + if os_name == "Linux" then + command = string.format("xdg-open '%s'", q.cache.link) + elseif os_name == "Darwin" then + command = string.format("open '%s'", q.cache.link) + else + -- Fallback to Windows if uname is not available or does not match Linux/Darwin. + command = string.format("start \"\" \"%s\"", q.cache.link) + end - vim.fn.jobstart(command, { detach = true }) + vim.fn.jobstart(command, { detach = true }) + end end end From 5d8b30f554f6a36055fde0fe3752c3c9eaf57974 Mon Sep 17 00:00:00 2001 From: kawre Date: Mon, 2 Dec 2024 18:04:03 +0100 Subject: [PATCH 109/115] fix: add support for `snacks.nvim` (#148) --- README.md | 4 ---- lua/leetcode/logger/init.lua | 5 ----- lua/leetcode/logger/spinner/init.lua | 5 ++++- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ea2ac4bb..e008ec02 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,6 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1 used for formatting the question description. Can be installed with [nvim-treesitter]. -- [nvim-notify] _**(optional)**_ - - [Nerd Font][nerd-font] & [nvim-web-devicons] _**(optional)**_ ## 📦 Installation @@ -53,7 +51,6 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/aee6584c-e099-4409-b114-1 -- optional "nvim-treesitter/nvim-treesitter", - "rcarriga/nvim-notify", "nvim-tree/nvim-web-devicons", }, opts = { @@ -493,7 +490,6 @@ You can then exit [leetcode.nvim] using `:Leet exit` command [neovim]: https://github.com/neovim/neovim [nerd-font]: https://www.nerdfonts.com [nui.nvim]: https://github.com/MunifTanjim/nui.nvim -[nvim-notify]: https://github.com/rcarriga/nvim-notify [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 diff --git a/lua/leetcode/logger/init.lua b/lua/leetcode/logger/init.lua index a10c8332..8908c32f 100644 --- a/lua/leetcode/logger/init.lua +++ b/lua/leetcode/logger/init.lua @@ -1,8 +1,3 @@ -local n_ok, notify = pcall(require, "notify") -if n_ok then - vim.notify = notify -end - local config = require("leetcode.config") local t = require("leetcode.translator") local lvls = vim.log.levels diff --git a/lua/leetcode/logger/spinner/init.lua b/lua/leetcode/logger/spinner/init.lua index ae19c391..569c353c 100644 --- a/lua/leetcode/logger/spinner/init.lua +++ b/lua/leetcode/logger/spinner/init.lua @@ -73,11 +73,14 @@ function spinner:set(msg, lvl, opts) end lvl = lvl or vim.log.levels.INFO + local id = self.noti and (self.noti.id or self.noti) + opts = vim.tbl_deep_extend("force", { hide_from_history = true, title = config.name, timeout = false, - replace = self.noti, + replace = id, + id = id, }, opts or {}) self.noti = vim.notify(self.msg, lvl, opts) From 98136532e61f6eb6a51b8130a8bbf73ccdee3d51 Mon Sep 17 00:00:00 2001 From: kawre Date: Mon, 2 Dec 2024 22:15:54 +0100 Subject: [PATCH 110/115] refactor: `leetcode.open` --- README.md | 2 +- lua/leetcode-plugins/non_standalone/init.lua | 2 +- .../non_standalone/leetcode.lua | 42 ++--------- lua/leetcode.lua | 74 +++++++++++++++---- lua/leetcode/config/init.lua | 3 + 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index e008ec02..ece23e23 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,7 @@ https://github.com/kawre/leetcode.nvim/assets/69250723/b7be8b95-5e2c-4153-8845-4 ```lua { "kawre/leetcode.nvim", - lazy = leet_arg ~= vim.fn.argv()[1], + lazy = leet_arg ~= vim.fn.argv(0, -1), opts = { arg = leet_arg }, } ``` diff --git a/lua/leetcode-plugins/non_standalone/init.lua b/lua/leetcode-plugins/non_standalone/init.lua index 709b7c80..de095f4b 100644 --- a/lua/leetcode-plugins/non_standalone/init.lua +++ b/lua/leetcode-plugins/non_standalone/init.lua @@ -5,7 +5,7 @@ non_standalone.opts = { lazy = false, } -function non_standalone.load() -- +function non_standalone.load() require("leetcode-plugins.non_standalone.leetcode") end diff --git a/lua/leetcode-plugins/non_standalone/leetcode.lua b/lua/leetcode-plugins/non_standalone/leetcode.lua index 6d4b9588..9cb95d61 100644 --- a/lua/leetcode-plugins/non_standalone/leetcode.lua +++ b/lua/leetcode-plugins/non_standalone/leetcode.lua @@ -1,42 +1,14 @@ +---@diagnostic disable: invisible, duplicate-set-field + local leetcode = require("leetcode") local config = require("leetcode.config") -local standalone = true +local is_standalone = true local prev_cwd = nil ----@param on_vimenter boolean -leetcode.should_skip = function(on_vimenter) - if on_vimenter then - if vim.fn.argc() ~= 1 then - return true - end - - local usr_arg, arg = vim.fn.argv()[1], config.user.arg - if usr_arg ~= arg then - return true - end - - local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true) - if #lines > 1 or (#lines == 1 and lines[1]:len() > 0) then - local log = require("leetcode.logger") - log.warn(("Failed to initialize: `%s` is not an empty buffer"):format(usr_arg)) - return true - end - else - for _, buf_id in pairs(vim.api.nvim_list_bufs()) do - local bufinfo = vim.fn.getbufinfo(buf_id)[1] - if bufinfo and (bufinfo.listed == 1 and #bufinfo.windows > 0) then - return false, true - end - end - end - - return false -end - ---@param on_vimenter boolean leetcode.start = function(on_vimenter) - local skip, buflisted = leetcode.should_skip(on_vimenter) + local skip, standalone = leetcode.should_skip(on_vimenter) if skip then return false end @@ -49,14 +21,14 @@ leetcode.start = function(on_vimenter) theme.setup() if not on_vimenter then - if buflisted then + if not standalone then prev_cwd = vim.fn.getcwd() vim.cmd.tabe() else vim.cmd.enew() end - standalone = not buflisted + is_standalone = standalone ---@diagnostic disable-line: cast-local-type end vim.api.nvim_set_current_dir(config.storage.home:absolute()) @@ -71,7 +43,7 @@ leetcode.start = function(on_vimenter) end leetcode.stop = vim.schedule_wrap(function() - if standalone then + if is_standalone then return vim.cmd("qa!") end diff --git a/lua/leetcode.lua b/lua/leetcode.lua index 5e4f5430..4d55f27e 100644 --- a/lua/leetcode.lua +++ b/lua/leetcode.lua @@ -3,38 +3,81 @@ local config = require("leetcode.config") ---@class lc.LeetCode local leetcode = {} +---@private +local function log_failed_to_init() + local log = require("leetcode.logger") + log.warn("Failed to initialize: `neovim` contains listed buffers") +end + +local function log_buf_not_empty(bufname) + local log = require("leetcode.logger") + if bufname and bufname ~= "" then + log.warn(("Failed to initialize: `%s` is not an empty buffer"):format(bufname)) + else + log.warn("Failed to initialize: not an empty buffer") + end +end + +local function buf_is_empty(buf) + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true) + return not (#lines > 1 or (#lines == 1 and lines[1]:len() > 0)) +end + ---@param on_vimenter boolean --- ----@return boolean +---@return boolean, boolean? (skip, standalone) function leetcode.should_skip(on_vimenter) if on_vimenter then - if vim.fn.argc() ~= 1 then + if vim.fn.argc(-1) ~= 1 then return true end - local usr_arg, arg = config.user.arg, vim.fn.argv()[1] + local usr_arg, arg = config.user.arg, vim.fn.argv(0, -1) if usr_arg ~= arg then return true end - local lines = vim.api.nvim_buf_get_lines(0, 0, -1, true) - if #lines > 1 or (#lines == 1 and lines[1]:len() > 0) then - local log = require("leetcode.logger") - log.warn(("Failed to initialize: `%s` is not an empty buffer"):format(usr_arg)) + if not buf_is_empty(0) then + log_buf_not_empty(usr_arg) return true end + + return false, true else - for _, buf_id in pairs(vim.api.nvim_list_bufs()) do - local bufinfo = vim.fn.getbufinfo(buf_id)[1] - if bufinfo and (bufinfo.listed == 1 and #bufinfo.windows > 0) then - local log = require("leetcode.logger") - log.warn("Failed to initialize: `neovim` contains listed buffers") + local listed_bufs = vim.tbl_filter(function(info) + return info.listed == 1 + end, vim.fn.getbufinfo()) + + if #listed_bufs == 0 then + return false, true + elseif vim.fn.argc(-1) == 0 and #listed_bufs == 1 then + local buf = listed_bufs[1] + + if vim.api.nvim_get_current_buf() ~= buf.bufnr then + if config.plugins.non_standalone then + return false, false + else + log_failed_to_init() + return true + end + end + + vim.schedule(function() + if buf.changed == 1 then + vim.api.nvim_buf_delete(buf.bufnr, { force = true }) + end + end) + + return false, true + elseif #listed_bufs >= 1 then + if config.plugins.non_standalone then + return false, false + else + log_failed_to_init() return true end end end - - return false end function leetcode.setup_cmds() @@ -43,7 +86,8 @@ end ---@param on_vimenter boolean function leetcode.start(on_vimenter) - if leetcode.should_skip(on_vimenter) then + local skip = leetcode.should_skip(on_vimenter) + if skip then return false end diff --git a/lua/leetcode/config/init.lua b/lua/leetcode/config/init.lua index 93d6e3f7..0dfad2a6 100644 --- a/lua/leetcode/config/init.lua +++ b/lua/leetcode/config/init.lua @@ -21,6 +21,7 @@ local config = { version = "1.0.1", storage = {}, ---@type table theme = {}, ---@type lc.highlights + plugins = {}, translator = false, @@ -98,6 +99,7 @@ function config.validate() end function config.load_plugins() + config.plugins = {} local plugins = {} if config.user.cn.enabled then @@ -119,6 +121,7 @@ function config.load_plugins() else table.insert(lazy_plugs, plug.load) end + config.plugins[plugin] = true else table.insert(lazy_plugs, function() local log = require("leetcode.logger") From 74c1ff56ab369c1d589ae821cd66cffbbe8c4607 Mon Sep 17 00:00:00 2001 From: Marcin Date: Thu, 12 Dec 2024 20:25:49 +0100 Subject: [PATCH 111/115] 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 --- README.md | 29 +++- lua/leetcode-ui/question.lua | 5 +- lua/leetcode/command/init.lua | 9 +- lua/leetcode/config/template.lua | 5 + lua/leetcode/picker/init.lua | 93 +++++++++++++ lua/leetcode/picker/language/fzf.lua | 35 +++++ lua/leetcode/picker/language/init.lua | 64 +++++++++ lua/leetcode/picker/language/telescope.lua | 67 ++++++++++ lua/leetcode/picker/question/fzf.lua | 36 +++++ lua/leetcode/picker/question/init.lua | 102 ++++++++++++++ lua/leetcode/picker/question/telescope.lua | 71 ++++++++++ lua/leetcode/picker/tabs/fzf.lua | 33 +++++ lua/leetcode/picker/tabs/init.lua | 67 ++++++++++ lua/leetcode/picker/tabs/telescope.lua | 65 +++++++++ lua/leetcode/pickers/language.lua | 143 -------------------- lua/leetcode/pickers/question-tabs.lua | 122 ----------------- lua/leetcode/pickers/question.lua | 148 --------------------- 17 files changed, 669 insertions(+), 425 deletions(-) create mode 100644 lua/leetcode/picker/init.lua create mode 100644 lua/leetcode/picker/language/fzf.lua create mode 100644 lua/leetcode/picker/language/init.lua create mode 100644 lua/leetcode/picker/language/telescope.lua create mode 100644 lua/leetcode/picker/question/fzf.lua create mode 100644 lua/leetcode/picker/question/init.lua create mode 100644 lua/leetcode/picker/question/telescope.lua create mode 100644 lua/leetcode/picker/tabs/fzf.lua create mode 100644 lua/leetcode/picker/tabs/init.lua create mode 100644 lua/leetcode/picker/tabs/telescope.lua delete mode 100644 lua/leetcode/pickers/language.lua delete mode 100644 lua/leetcode/pickers/question-tabs.lua delete mode 100644 lua/leetcode/pickers/question.lua diff --git a/README.md b/README.md index ece23e23..57ed6f9f 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 df35892b..e8204746 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 8cfaae07..ae41554d 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 27590e0b..40c5779c 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 00000000..8fabd228 --- /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 00000000..013b28f4 --- /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 00000000..d7b7615b --- /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 00000000..c91061b8 --- /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 00000000..1ffdaecd --- /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 00000000..77866f28 --- /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 00000000..b30137d6 --- /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 00000000..57e5dbd4 --- /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 00000000..8ea70497 --- /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 00000000..93998e00 --- /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 7e9f3464..00000000 --- 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 e22426d4..00000000 --- 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 d8fa1177..00000000 --- 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, -} From febe2b5c8678241799fc07bcb3399af4691cdae0 Mon Sep 17 00:00:00 2001 From: 9c <125933011+New9c@users.noreply.github.com> Date: Fri, 13 Dec 2024 04:08:14 +0800 Subject: [PATCH 112/115] fix: Remove s when it's 1 problem solved (#149) * fix: try to remove s when it's 1 problem solved * fix: Restore chinese version --- lua/leetcode-ui/popup/languages.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/leetcode-ui/popup/languages.lua b/lua/leetcode-ui/popup/languages.lua index a72c6b92..81886d1b 100644 --- a/lua/leetcode-ui/popup/languages.lua +++ b/lua/leetcode-ui/popup/languages.lua @@ -32,7 +32,11 @@ function Languages:handle(res) lines:append(" " .. res.problems_solved) else lines:append("" .. res.problems_solved) - lines:append(" problems solved", "leetcode_alt") + if res.problems_solved == 1 then + lines:append(" problem solved", "leetcode_alt") + else + lines:append(" problems solved", "leetcode_alt") + end end return lines From eac055e57571dee310a5a157f23946a6495e016b Mon Sep 17 00:00:00 2001 From: phanium <91544758+phanen@users.noreply.github.com> Date: Sat, 14 Dec 2024 03:57:07 +0800 Subject: [PATCH 113/115] fix(`Leet inject`): update index after buffer changed (#151) --- lua/leetcode/command/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index ae41554d..a04d1725 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -409,6 +409,7 @@ function cmd.inject() local before = q:inject(true) if before then api.nvim_buf_set_lines(q.bufnr, 0, start_i - 1, false, vim.split(before, "\n")) + _, end_i = q:range(true) end end @@ -417,7 +418,7 @@ function cmd.inject() else local after = q:inject(false) if after then - api.nvim_buf_set_lines(q.bufnr, end_i + 1, -1, false, vim.split(after, "\n")) + api.nvim_buf_set_lines(q.bufnr, end_i, -1, false, vim.split(after, "\n")) end end From 221ee9ecccd7b4ce7b41417a92a9f07011edbfab Mon Sep 17 00:00:00 2001 From: kawre Date: Wed, 18 Dec 2024 01:05:26 +0100 Subject: [PATCH 114/115] fix(fzf): `winopts.win_*` deprecated --- lua/leetcode/logger/spinner/init.lua | 1 + lua/leetcode/picker/language/fzf.lua | 4 ++-- lua/leetcode/picker/question/fzf.lua | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/leetcode/logger/spinner/init.lua b/lua/leetcode/logger/spinner/init.lua index 569c353c..c14b8511 100644 --- a/lua/leetcode/logger/spinner/init.lua +++ b/lua/leetcode/logger/spinner/init.lua @@ -77,6 +77,7 @@ function spinner:set(msg, lvl, opts) opts = vim.tbl_deep_extend("force", { hide_from_history = true, + history = false, title = config.name, timeout = false, replace = id, diff --git a/lua/leetcode/picker/language/fzf.lua b/lua/leetcode/picker/language/fzf.lua index 013b28f4..45469929 100644 --- a/lua/leetcode/picker/language/fzf.lua +++ b/lua/leetcode/picker/language/fzf.lua @@ -17,8 +17,8 @@ return function(question, cb) fzf.fzf_exec(items, { prompt = t("Available Languages") .. "> ", winopts = { - win_height = language_picker.height, - win_width = language_picker.width, + height = language_picker.height, + width = language_picker.width, }, fzf_opts = { ["--delimiter"] = deli, diff --git a/lua/leetcode/picker/question/fzf.lua b/lua/leetcode/picker/question/fzf.lua index 1ffdaecd..3b23e5b4 100644 --- a/lua/leetcode/picker/question/fzf.lua +++ b/lua/leetcode/picker/question/fzf.lua @@ -18,8 +18,8 @@ return function(questions, opts) fzf.fzf_exec(items, { prompt = t("Select a Question") .. "> ", winopts = { - win_height = question_picker.height, - win_width = question_picker.width, + height = question_picker.height, + width = question_picker.width, }, fzf_opts = { ["--delimiter"] = deli, From db7e1cd6b9191b34b4c1f2f96e4e3949cde9f951 Mon Sep 17 00:00:00 2001 From: kawre Date: Wed, 18 Dec 2024 17:18:59 +0100 Subject: [PATCH 115/115] fix(spinner): check if `notif` is table (#153) --- lua/leetcode/logger/spinner/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/leetcode/logger/spinner/init.lua b/lua/leetcode/logger/spinner/init.lua index c14b8511..b328c642 100644 --- a/lua/leetcode/logger/spinner/init.lua +++ b/lua/leetcode/logger/spinner/init.lua @@ -73,7 +73,7 @@ function spinner:set(msg, lvl, opts) end lvl = lvl or vim.log.levels.INFO - local id = self.noti and (self.noti.id or self.noti) + local id = type(self.noti) == "table" and self.noti.id or self.noti opts = vim.tbl_deep_extend("force", { hide_from_history = true,