A Neovim plugin enabling you to solve LeetCode problems.
feat: ui components
28 files changed, +1009 -265
+2-1.neoconf.json
| @@ -6,7 +6,8 @@ | ||
| 6 | 6 | "nvim-notify", |
| 7 | 7 | "telescope.nvim", |
| 8 | 8 | "plenary.nvim", |
| 9 | - "nui.nvim" | |
| 9 | + "nui.nvim", | |
| 10 | + "alpha-nvim" | |
| 10 | 11 | ] |
| 11 | 12 | } |
| 12 | 13 | } |
+42-0lua/leetcode-ui/component/button.lua
| @@ -0,0 +1,42 @@ | ||
| 1 | +local component = require("leetcode-ui.component") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | +local Line = require("nui.line") | |
| 4 | + | |
| 5 | +---@class lc.db.Button: lc.db.Component | |
| 6 | +local button = {} | |
| 7 | +button.__index = button | |
| 8 | +setmetatable(button, component) | |
| 9 | + | |
| 10 | +--- @param sc string | |
| 11 | +--- @param txt string | |
| 12 | +--- @param icon string | |
| 13 | +--- @param expandable boolean? optional | |
| 14 | +--- @param keybind string? optional | |
| 15 | +--- @param keybind_opts table? optional | |
| 16 | +function button:init(sc, txt, icon, expandable, keybind, keybind_opts) | |
| 17 | + -- local sc_ = sc:gsub("%s", ""):gsub(leader, "<leader>") | |
| 18 | + | |
| 19 | + local opts = { | |
| 20 | + position = "center", | |
| 21 | + } | |
| 22 | + local width = 40 | |
| 23 | + | |
| 24 | + local line = Line() | |
| 25 | + local padding = string.rep(" ", width - txt:len() - sc:len() - 3) | |
| 26 | + -- line:append(string.format("%s ", icon)) | |
| 27 | + line:append("x " .. txt .. (expandable and "_" or " ") .. padding .. sc) | |
| 28 | + -- txt = string.format("%s%s%s", icon, " " .. txt, expandable and "…" or "") | |
| 29 | + -- line:append(txt .. padding .. sc) | |
| 30 | + | |
| 31 | + local o = setmetatable({ | |
| 32 | + opts = opts, | |
| 33 | + lines = { line }, | |
| 34 | + }, button) | |
| 35 | + | |
| 36 | + self.lines = o.lines | |
| 37 | + self.opts = o.opts | |
| 38 | + | |
| 39 | + return o | |
| 40 | +end | |
| 41 | + | |
| 42 | +return button |
+40-0lua/leetcode-ui/component/group.lua
| @@ -0,0 +1,40 @@ | ||
| 1 | +local component = require("leetcode-ui.component") | |
| 2 | +local utils = require("leetcode-ui.utils") | |
| 3 | +local padding = require("leetcode-ui.component.padding") | |
| 4 | +---@class lc.db.Group: lc.db.Component | |
| 5 | +local group = {} | |
| 6 | +group.__index = group | |
| 7 | +setmetatable(group, component) | |
| 8 | + | |
| 9 | +---@param config lc.db.Group.config | |
| 10 | +--- | |
| 11 | +---@return lc.db.Group | |
| 12 | +function group:init(config) | |
| 13 | + local o = setmetatable(config, self) | |
| 14 | + self.__index = self | |
| 15 | + | |
| 16 | + -- self:append(config.components or {}) | |
| 17 | + self.components = config.components or {} | |
| 18 | + self.opts = config.opts or {} | |
| 19 | + | |
| 20 | + return o | |
| 21 | +end | |
| 22 | + | |
| 23 | +---@param cmp lc.db.Component | lc.db.Component[] | |
| 24 | +--- | |
| 25 | +---@return lc.db.Group | |
| 26 | +function group:append(cmp) | |
| 27 | + table.insert(self.components, cmp) | |
| 28 | + | |
| 29 | + return self | |
| 30 | +end | |
| 31 | + | |
| 32 | +function group:draw(split) | |
| 33 | + for i, cmp in pairs(self.components) do | |
| 34 | + local opts = self.opts | |
| 35 | + if opts.spacing and i ~= 1 then padding:init(opts.spacing):draw(split) end | |
| 36 | + cmp:draw(split) | |
| 37 | + end | |
| 38 | +end | |
| 39 | + | |
| 40 | +return group |
+44-0lua/leetcode-ui/component/init.lua
| @@ -0,0 +1,44 @@ | ||
| 1 | +local log = require("leetcode.logger") | |
| 2 | +local Line = require("nui.line") | |
| 3 | +local utils = require("leetcode-ui.utils") | |
| 4 | + | |
| 5 | +---@class lc.db.Component | |
| 6 | +---@field lines NuiLine[] | |
| 7 | +---@field opts lc.db.Component.config.opts | |
| 8 | +---@field type string | |
| 9 | +local component = {} ---@diagnostic disable-line | |
| 10 | +component.__index = component | |
| 11 | + | |
| 12 | +-------------------------------------------------------- | |
| 13 | +--- Methods | |
| 14 | +-------------------------------------------------------- | |
| 15 | + | |
| 16 | +---@param content NuiLine | string | |
| 17 | +---@param highlight? string | |
| 18 | +function component:append(content, highlight) | |
| 19 | + if type(content) == "table" then | |
| 20 | + table.insert(self.lines, content) | |
| 21 | + else | |
| 22 | + local line = Line():append(content, highlight) | |
| 23 | + table.insert(self.lines, line) | |
| 24 | + end | |
| 25 | + | |
| 26 | + return self | |
| 27 | +end | |
| 28 | + | |
| 29 | +---@param split NuiSplit | |
| 30 | +function component:draw(split) | |
| 31 | + local padding = utils.get_padding(self.lines, self.opts.position, split) | |
| 32 | + | |
| 33 | + for _, line in pairs(self.lines) do | |
| 34 | + local new_line = Line() | |
| 35 | + new_line:append(padding) | |
| 36 | + new_line:append(line) | |
| 37 | + | |
| 38 | + new_line:render(split.bufnr, -1, line_idx, line_idx) | |
| 39 | + | |
| 40 | + line_idx = line_idx + 1 | |
| 41 | + end | |
| 42 | +end | |
| 43 | + | |
| 44 | +return component |
+35-0lua/leetcode-ui/component/padding.lua
| @@ -0,0 +1,35 @@ | ||
| 1 | +local component = require("leetcode-ui.component") | |
| 2 | +local utils = require("leetcode-ui.utils") | |
| 3 | +local Line = require("nui.line") | |
| 4 | + | |
| 5 | +---@class lc.db.Padding: lc.db.Component | |
| 6 | +local padding = {} | |
| 7 | +padding.__index = padding | |
| 8 | +setmetatable(padding, component) | |
| 9 | + | |
| 10 | +-------------------------------------------------------- | |
| 11 | +--- Methods | |
| 12 | +-------------------------------------------------------- | |
| 13 | + | |
| 14 | +-------------------------------------------------------- | |
| 15 | +--- Constructor | |
| 16 | +-------------------------------------------------------- | |
| 17 | + | |
| 18 | +---@param int integer | |
| 19 | +--- | |
| 20 | +---@return lc.db.Component | |
| 21 | +function padding:init(int) | |
| 22 | + local t = {} | |
| 23 | + for _ = 1, int, 1 do | |
| 24 | + table.insert(t, Line():append("")) | |
| 25 | + end | |
| 26 | + | |
| 27 | + local o = setmetatable({ lines = t, opts = {} }, self) | |
| 28 | + self.__index = self | |
| 29 | + self.lines = t | |
| 30 | + self.opts = {} | |
| 31 | + | |
| 32 | + return o | |
| 33 | +end | |
| 34 | + | |
| 35 | +return padding |
+33-0lua/leetcode-ui/component/text.lua
| @@ -0,0 +1,33 @@ | ||
| 1 | +local component = require("leetcode-ui.component") | |
| 2 | +local utils = require("leetcode-ui.utils") | |
| 3 | + | |
| 4 | +---@class lc.db.Text: lc.db.Component | |
| 5 | +local text = {} | |
| 6 | +text.__index = text | |
| 7 | +setmetatable(text, component) | |
| 8 | + | |
| 9 | +-------------------------------------------------------- | |
| 10 | +--- Methods | |
| 11 | +-------------------------------------------------------- | |
| 12 | + | |
| 13 | +-------------------------------------------------------- | |
| 14 | +--- Constructor | |
| 15 | +-------------------------------------------------------- | |
| 16 | + | |
| 17 | +---@param config lc.db.Component.config | |
| 18 | +--- | |
| 19 | +---@return lc.db.Text | |
| 20 | +function text:init(config) | |
| 21 | + local parsed_lines = utils.parse_lines(config) | |
| 22 | + config.lines = parsed_lines | |
| 23 | + | |
| 24 | + local o = setmetatable(config, self) | |
| 25 | + self.__index = self | |
| 26 | + | |
| 27 | + self.lines = parsed_lines | |
| 28 | + self.opts = config.opts or {} | |
| 29 | + | |
| 30 | + return o | |
| 31 | +end | |
| 32 | + | |
| 33 | +return text |
+73-0lua/leetcode-ui/init.lua
| @@ -0,0 +1,73 @@ | ||
| 1 | +local component = require("leetcode-ui.component") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | + | |
| 4 | +local Split = require("nui.split") | |
| 5 | +local Text = require("nui.text") | |
| 6 | +local Line = require("nui.line") | |
| 7 | + | |
| 8 | +local dashboard = {} | |
| 9 | + | |
| 10 | +---@type NuiSplit | |
| 11 | +local split | |
| 12 | + | |
| 13 | +line_idx = 1 | |
| 14 | + | |
| 15 | +---@type lc.db.Component[] | |
| 16 | +local state = {} | |
| 17 | + | |
| 18 | +function dashboard.redraw() end | |
| 19 | + | |
| 20 | +function dashboard.clear() | |
| 21 | + vim.api.nvim_buf_set_lines(split.bufnr, 0, -1, false, {}) | |
| 22 | + line_idx = 1 | |
| 23 | +end | |
| 24 | + | |
| 25 | +function dashboard.draw() | |
| 26 | + dashboard.clear() | |
| 27 | + | |
| 28 | + -- local menu = require("leetcode-ui.theme.menu") | |
| 29 | + local problems = require("leetcode-ui.theme.problems") | |
| 30 | + -- local menu = require("leetcode-ui.theme.menu") | |
| 31 | + -- local menu = require("leetcode-ui.theme.menu") | |
| 32 | + problems:draw(split) | |
| 33 | +end | |
| 34 | + | |
| 35 | +function dashboard.setup() | |
| 36 | + split = Split({ | |
| 37 | + relative = "win", | |
| 38 | + size = "100%", | |
| 39 | + enter = true, | |
| 40 | + focusable = true, | |
| 41 | + buf_options = { | |
| 42 | + -- modifiable = false, | |
| 43 | + readonly = false, | |
| 44 | + filetype = "leetcode.nvim", | |
| 45 | + swapfile = false, | |
| 46 | + buftype = "nofile", | |
| 47 | + buflisted = false, | |
| 48 | + }, | |
| 49 | + win_options = { | |
| 50 | + -- winblend = 10, | |
| 51 | + -- winhighlight = "Normal:Normal,FloatBorder:FloatBorder", | |
| 52 | + foldcolumn = "1", | |
| 53 | + wrap = true, | |
| 54 | + number = false, | |
| 55 | + signcolumn = "no", | |
| 56 | + cursorline = false, | |
| 57 | + }, | |
| 58 | + }) | |
| 59 | + split:mount() | |
| 60 | + | |
| 61 | + dashboard.split = split | |
| 62 | + | |
| 63 | + vim.cmd("bd#") | |
| 64 | + | |
| 65 | + vim.api.nvim_create_autocmd("WinResized", { | |
| 66 | + -- group = group_id, | |
| 67 | + callback = function() dashboard.draw() end, | |
| 68 | + }) | |
| 69 | + | |
| 70 | + dashboard.draw() | |
| 71 | +end | |
| 72 | + | |
| 73 | +return dashboard |
+26-0lua/leetcode-ui/layout.lua
| @@ -0,0 +1,26 @@ | ||
| 1 | +local log = require("leetcode.logger") | |
| 2 | + | |
| 3 | +---@class lc.db.Layout | |
| 4 | +---@field contents lc.db.Component[] | |
| 5 | +---@field opts lc.db.Layout.opts | |
| 6 | +local layout = {} | |
| 7 | +layout.__index = layout | |
| 8 | + | |
| 9 | +---@param split NuiSplit | |
| 10 | +function layout:draw(split) | |
| 11 | + for _, component in ipairs(self.contents) do | |
| 12 | + component:draw(split) | |
| 13 | + end | |
| 14 | +end | |
| 15 | + | |
| 16 | +---@param config lc.db.Layout.config | |
| 17 | +function layout:init(config) | |
| 18 | + local o = setmetatable(config, self) | |
| 19 | + | |
| 20 | + self.contents = config.contents | |
| 21 | + self.opts = config.opts | |
| 22 | + | |
| 23 | + return o | |
| 24 | +end | |
| 25 | + | |
| 26 | +return layout |
+48-0lua/leetcode-ui/theme/cache.lua
| @@ -0,0 +1,48 @@ | ||
| 1 | +local config = require("leetcode.config") | |
| 2 | + | |
| 3 | +local template = require("leetcode.ui.dashboard.template") | |
| 4 | +local section = template.section | |
| 5 | +local auth = config.auth | |
| 6 | + | |
| 7 | +section.title.val = "Cache" | |
| 8 | + | |
| 9 | +section.buttons.val = { | |
| 10 | + template.button( | |
| 11 | + "c", | |
| 12 | + " " .. " Update", | |
| 13 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 14 | + ), | |
| 15 | + template.button( | |
| 16 | + "s", | |
| 17 | + " " .. " Sign out", | |
| 18 | + "<cmd>lua require('leetcode.utils').remove_cookie()<cr>" | |
| 19 | + ), | |
| 20 | + template.button("q", " " .. " Exit LeetCode", "<cmd>qa<CR>"), | |
| 21 | +} | |
| 22 | + | |
| 23 | +section.footer.val = "Signed in as: " .. auth.username | |
| 24 | + | |
| 25 | +return { | |
| 26 | + layout = { | |
| 27 | + -- header | |
| 28 | + { type = "padding", val = 2 }, | |
| 29 | + section.header, | |
| 30 | + | |
| 31 | + -- notifications | |
| 32 | + -- { type = "padding", val = 2 }, | |
| 33 | + -- section.notifications, | |
| 34 | + { type = "padding", val = 2 }, | |
| 35 | + | |
| 36 | + -- title | |
| 37 | + section.title, | |
| 38 | + | |
| 39 | + -- buttons | |
| 40 | + section.buttons, | |
| 41 | + | |
| 42 | + --footer | |
| 43 | + section.footer, | |
| 44 | + }, | |
| 45 | + opts = { | |
| 46 | + margin = 5, | |
| 47 | + }, | |
| 48 | +} |
+50-0lua/leetcode-ui/theme/cookie.lua
| @@ -0,0 +1,50 @@ | ||
| 1 | +local config = require("leetcode.config") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | + | |
| 4 | +local template = require("leetcode.ui.dashboard.template") | |
| 5 | +local section = template.section | |
| 6 | + | |
| 7 | +section.title.val = "Cookie" | |
| 8 | + | |
| 9 | +section.buttons.val = { | |
| 10 | + template.button( | |
| 11 | + "s", | |
| 12 | + " " .. " Sign out / Delete cookie ", | |
| 13 | + "<cmd>lua require('leetcode.utils').remove_cookie()<cr>" | |
| 14 | + ), | |
| 15 | + template.button( | |
| 16 | + "s", | |
| 17 | + " " .. " Update ", | |
| 18 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 19 | + ), | |
| 20 | + template.button( | |
| 21 | + "q", | |
| 22 | + " " .. " Back", | |
| 23 | + "<cmd>lua require('leetcode.api').cmd.dashboard('menu')<cr>" | |
| 24 | + ), | |
| 25 | +} | |
| 26 | + | |
| 27 | +return { | |
| 28 | + layout = { | |
| 29 | + -- header | |
| 30 | + { type = "padding", val = 2 }, | |
| 31 | + section.header, | |
| 32 | + | |
| 33 | + -- notifications | |
| 34 | + { type = "padding", val = 2 }, | |
| 35 | + section.notifications, | |
| 36 | + { type = "padding", val = 2 }, | |
| 37 | + | |
| 38 | + -- title | |
| 39 | + section.title, | |
| 40 | + | |
| 41 | + -- buttons | |
| 42 | + section.buttons, | |
| 43 | + | |
| 44 | + --footer | |
| 45 | + section.footer, | |
| 46 | + }, | |
| 47 | + opts = { | |
| 48 | + margin = 5, | |
| 49 | + }, | |
| 50 | +} |
+39-0lua/leetcode-ui/theme/default.lua
| @@ -0,0 +1,39 @@ | ||
| 1 | +local config = require("leetcode.config") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | +local component = require("leetcode-ui.component") | |
| 4 | + | |
| 5 | +local template = require("leetcode.ui.dashboard.template") | |
| 6 | +local section = template.section | |
| 7 | + | |
| 8 | +section.notifications.val = { " Sign in to use LeetCode.nvim" } | |
| 9 | + | |
| 10 | +section.buttons.val = { | |
| 11 | + template.button( | |
| 12 | + "s", | |
| 13 | + " " .. " Sign in (By Cookie)", | |
| 14 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 15 | + ), | |
| 16 | + template.button("q", " " .. " Exit LeetCode", "<cmd>qa!<CR>"), | |
| 17 | +} | |
| 18 | + | |
| 19 | +return { | |
| 20 | + layout = { | |
| 21 | + -- header | |
| 22 | + { type = "padding", val = 2 }, | |
| 23 | + section.header, | |
| 24 | + | |
| 25 | + -- notifications | |
| 26 | + { type = "padding", val = 2 }, | |
| 27 | + section.notifications, | |
| 28 | + { type = "padding", val = 2 }, | |
| 29 | + | |
| 30 | + -- buttons | |
| 31 | + section.buttons, | |
| 32 | + | |
| 33 | + --footer | |
| 34 | + section.footer, | |
| 35 | + }, | |
| 36 | + opts = { | |
| 37 | + margin = 5, | |
| 38 | + }, | |
| 39 | +} |
+30-0lua/leetcode-ui/theme/init.lua
| @@ -0,0 +1,30 @@ | ||
| 1 | +local utils = require("leetcode.utils") | |
| 2 | +local gql = require("leetcode.graphql") | |
| 3 | +local config = require("leetcode.config") | |
| 4 | +local log = require("leetcode.logger") | |
| 5 | + | |
| 6 | +local M = {} | |
| 7 | + | |
| 8 | +---@alias lc.db.Theme | |
| 9 | +---| "cache" | |
| 10 | +---| "cookie" | |
| 11 | +---| "default" | |
| 12 | +---| "menu" | |
| 13 | +---| "problems" | |
| 14 | +---| "stats" | |
| 15 | + | |
| 16 | +---@param theme lc.db.Theme | |
| 17 | +function M.apply(theme) | |
| 18 | + local alpha = require("alpha") | |
| 19 | + utils.alpha_move_cursor_top() | |
| 20 | + | |
| 21 | + local ok, cfg = pcall(require, "leetcode.ui.dashboard." .. theme) | |
| 22 | + if not ok then log.error("Failed to load dashboard theme: " .. theme) end | |
| 23 | + alpha.setup(cfg) | |
| 24 | + | |
| 25 | + pcall(vim.cmd, [[AlphaRedraw]]) | |
| 26 | +end | |
| 27 | + | |
| 28 | +-- function M.update() M.apply() end | |
| 29 | + | |
| 30 | +return M |
+42-0lua/leetcode-ui/theme/problems.lua
| @@ -0,0 +1,42 @@ | ||
| 1 | +local log = require("leetcode.logger") | |
| 2 | +local group = require("leetcode-ui.component.group") | |
| 3 | +local button = require("leetcode-ui.component.button") | |
| 4 | +local layout = require("leetcode-ui.layout") | |
| 5 | + | |
| 6 | +local template = require("leetcode-ui.theme.template") | |
| 7 | +local section = template.section | |
| 8 | + | |
| 9 | +section.title:append("Problems") | |
| 10 | + | |
| 11 | +local buttons = group:init({ | |
| 12 | + components = { | |
| 13 | + button:init("p", "List", ""), | |
| 14 | + button:init("t", "Question of Today", " "), | |
| 15 | + button:init("t", "Random", " "), | |
| 16 | + button:init("r", "Recent", " "), | |
| 17 | + button:init("q", "Back", "… "), | |
| 18 | + }, | |
| 19 | + opts = { spacing = 1 }, | |
| 20 | +}) | |
| 21 | + | |
| 22 | +section.footer:append("Signed in as: ") | |
| 23 | + | |
| 24 | +return layout:init({ | |
| 25 | + contents = { | |
| 26 | + -- header | |
| 27 | + section.header, | |
| 28 | + | |
| 29 | + -- notifications | |
| 30 | + section.notifications, | |
| 31 | + | |
| 32 | + -- title | |
| 33 | + section.title, | |
| 34 | + | |
| 35 | + -- buttons | |
| 36 | + buttons, | |
| 37 | + | |
| 38 | + --footer | |
| 39 | + section.footer, | |
| 40 | + }, | |
| 41 | + opts = {}, | |
| 42 | +}) |
+53-0lua/leetcode-ui/theme/stats.lua
| @@ -0,0 +1,53 @@ | ||
| 1 | +local config = require("leetcode.config") | |
| 2 | + | |
| 3 | +local template = require("leetcode.ui.dashboard.template") | |
| 4 | +local section = template.section | |
| 5 | +local auth = config.auth | |
| 6 | + | |
| 7 | +section.title.val = "Statistics" | |
| 8 | + | |
| 9 | +section.buttons.val = { | |
| 10 | + template.button( | |
| 11 | + "p", | |
| 12 | + " " .. " Problems ", | |
| 13 | + "<cmd>lua require('leetcode.api').cmd.lc_problems()<CR>" | |
| 14 | + ), | |
| 15 | + template.button( | |
| 16 | + "c", | |
| 17 | + " " .. " Cookie ", | |
| 18 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 19 | + ), | |
| 20 | + template.button( | |
| 21 | + "c", | |
| 22 | + " " .. " Cache ", | |
| 23 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 24 | + ), | |
| 25 | + template.button("q", " " .. " Exit LeetCode", "<cmd>qa<CR>"), | |
| 26 | +} | |
| 27 | + | |
| 28 | +section.footer.val = "Signed in as: " .. auth.username | |
| 29 | + | |
| 30 | +return { | |
| 31 | + layout = { | |
| 32 | + -- header | |
| 33 | + { type = "padding", val = 2 }, | |
| 34 | + section.header, | |
| 35 | + | |
| 36 | + -- notifications | |
| 37 | + -- { type = "padding", val = 2 }, | |
| 38 | + -- section.notifications, | |
| 39 | + { type = "padding", val = 2 }, | |
| 40 | + | |
| 41 | + -- title | |
| 42 | + section.title, | |
| 43 | + | |
| 44 | + -- buttons | |
| 45 | + section.buttons, | |
| 46 | + | |
| 47 | + --footer | |
| 48 | + section.footer, | |
| 49 | + }, | |
| 50 | + opts = { | |
| 51 | + margin = 5, | |
| 52 | + }, | |
| 53 | +} |
+61-0lua/leetcode-ui/theme/template.lua
| @@ -0,0 +1,61 @@ | ||
| 1 | +local text = require("leetcode-ui.component.text") | |
| 2 | +local group = require("leetcode-ui.component.group") | |
| 3 | + | |
| 4 | +local header = text:init({ | |
| 5 | + lines = { | |
| 6 | + [[ /$$ /$$ /$$$$$$ /$$ ]], | |
| 7 | + [[| $$ | $$ /$$__ $$ | $$ ]], | |
| 8 | + [[| $$ /$$$$$$ /$$$$$$ /$$$$$$ | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ ]], | |
| 9 | + [[| $$ /$$__ $$/$$__ $|_ $$_/ | $$ /$$__ $$/$$__ $$/$$__ $$]], | |
| 10 | + [[| $$ | $$$$$$$| $$$$$$$$ | $$ | $$ | $$ \ $| $$ | $| $$$$$$$$]], | |
| 11 | + [[| $$ | $$_____| $$_____/ | $$ /$| $$ $| $$ | $| $$ | $| $$_____/]], | |
| 12 | + [[| $$$$$$$| $$$$$$| $$$$$$$ | $$$$| $$$$$$| $$$$$$| $$$$$$| $$$$$$$]], | |
| 13 | + [[|________/\_______/\_______/ \___/ \______/ \______/ \_______/\_______/]], | |
| 14 | + }, | |
| 15 | + opts = { | |
| 16 | + position = "center", | |
| 17 | + hl = "Keyword", | |
| 18 | + padding = { | |
| 19 | + bot = 2, | |
| 20 | + }, | |
| 21 | + }, | |
| 22 | +}) | |
| 23 | + | |
| 24 | +local notifications = text:init({ | |
| 25 | + opts = { | |
| 26 | + position = "center", | |
| 27 | + hl = "DiagnosticInfo", | |
| 28 | + }, | |
| 29 | +}) | |
| 30 | + | |
| 31 | +local title = text:init({ | |
| 32 | + opts = { | |
| 33 | + position = "center", | |
| 34 | + hl = "Comment", | |
| 35 | + }, | |
| 36 | +}) | |
| 37 | + | |
| 38 | +local footer = text:init({ | |
| 39 | + opts = { | |
| 40 | + position = "center", | |
| 41 | + hl = "Keyword", | |
| 42 | + }, | |
| 43 | +}) | |
| 44 | + | |
| 45 | +---@alias section table<string, lc.db.Component> | |
| 46 | + | |
| 47 | +---@type section | |
| 48 | +local section = { | |
| 49 | + header = header, | |
| 50 | + notifications = notifications, | |
| 51 | + title = title, | |
| 52 | + footer = footer, | |
| 53 | +} | |
| 54 | + | |
| 55 | +---@class lc.db.Template | |
| 56 | +---@field section section | |
| 57 | +---@field button function | |
| 58 | +return { | |
| 59 | + section = section, | |
| 60 | + button = require("alpha.themes.dashboard").button, | |
| 61 | +} |
+34-0lua/leetcode-ui/types.lua
| @@ -0,0 +1,34 @@ | ||
| 1 | +---@alias position | |
| 2 | +---| "left" | |
| 3 | +---| "center" | |
| 4 | +---| "right" | |
| 5 | + | |
| 6 | +---@class lc.db.Layout.padding | |
| 7 | +---@field top | |
| 8 | +---@field right | |
| 9 | +---@field bot | |
| 10 | +---@field left | |
| 11 | + | |
| 12 | +---@class lc.db.Component.config.opts | |
| 13 | +---@field position position | |
| 14 | +---@field hl string | |
| 15 | + | |
| 16 | +---@class lc.db.Group.config.opts | |
| 17 | +---@field spacing integer | |
| 18 | +--@field position position | |
| 19 | + | |
| 20 | +---@class lc.db.Component.config | |
| 21 | +---@field lines? NuiLine[] | string[] | |
| 22 | +---@field opts? lc.db.Component.config.opts | |
| 23 | + | |
| 24 | +---@class lc.db.Group.config | |
| 25 | +---@field components? lc.db.Component[] | |
| 26 | +---@field opts? lc.db.Group.config.opts | |
| 27 | + | |
| 28 | +---@class lc.db.Layout.opts | |
| 29 | +---@field margin? integer | |
| 30 | +---@field padding? lc.db.Layout.padding | integer | |
| 31 | + | |
| 32 | +---@class lc.db.Layout.config | |
| 33 | +---@field opts lc.db.Layout.opts | |
| 34 | +---@field contents lc.db.Component[] |
+70-0lua/leetcode-ui/utils.lua
| @@ -0,0 +1,70 @@ | ||
| 1 | +local Line = require("nui.line") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | + | |
| 4 | +local utils = {} | |
| 5 | + | |
| 6 | +---@param lines NuiLine[] | |
| 7 | +function utils.longest_line(lines) | |
| 8 | + local width = 0 | |
| 9 | + | |
| 10 | + for _, line in pairs(lines) do | |
| 11 | + local text = line:content() | |
| 12 | + width = math.max(text:len(), width) | |
| 13 | + end | |
| 14 | + | |
| 15 | + return width | |
| 16 | +end | |
| 17 | + | |
| 18 | +---@param split NuiSplit | |
| 19 | +function utils.win_width(split) return vim.api.nvim_win_get_width(split.winid) end | |
| 20 | + | |
| 21 | +---@param config lc.db.Component.config | |
| 22 | +--- | |
| 23 | +---@return NuiLine[] | |
| 24 | +function utils.parse_lines(config) | |
| 25 | + local lines = config.lines | |
| 26 | + local t = {} | |
| 27 | + | |
| 28 | + if type(lines) == "table" then | |
| 29 | + for _, line in pairs(lines) do | |
| 30 | + if type(line) == "string" then | |
| 31 | + local nui_line = Line() | |
| 32 | + nui_line:append(line, config.opts and config.opts.hl or nil) | |
| 33 | + table.insert(t, nui_line) | |
| 34 | + else | |
| 35 | + table.insert(t, line) | |
| 36 | + end | |
| 37 | + end | |
| 38 | + end | |
| 39 | + | |
| 40 | + return t | |
| 41 | +end | |
| 42 | + | |
| 43 | +---@param lines NuiLine[] | |
| 44 | +---@param position position | |
| 45 | +---@param split NuiSplit | |
| 46 | +--- | |
| 47 | +---@return string | |
| 48 | +function utils.get_padding(lines, position, split) | |
| 49 | + local padding = "" | |
| 50 | + | |
| 51 | + if position ~= "left" then | |
| 52 | + local longest_line = utils.longest_line(lines) | |
| 53 | + | |
| 54 | + if position == "center" then | |
| 55 | + local width = utils.win_width(split) | |
| 56 | + local mid = (width - longest_line) / 2 | |
| 57 | + local spaces = string.rep(" ", mid) | |
| 58 | + padding = spaces | |
| 59 | + elseif position == "right" then | |
| 60 | + local width = utils.win_width(split) | |
| 61 | + local mid = width - longest_line - 1 | |
| 62 | + local spaces = string.rep(" ", mid) | |
| 63 | + padding = spaces | |
| 64 | + end | |
| 65 | + end | |
| 66 | + | |
| 67 | + return padding | |
| 68 | +end | |
| 69 | + | |
| 70 | +return utils |
+2-1lua/leetcode.lua
| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | local config = require("leetcode.config") |
| 2 | 2 | local api = require("leetcode.api") |
| 3 | +local dashboard = require("leetcode-ui") | |
| 3 | 4 | |
| 4 | 5 | local M = {} |
| 5 | 6 | |
| @@ -15,7 +16,7 @@ function M.setup(cfg) | ||
| 15 | 16 | pattern = "*", |
| 16 | 17 | nested = true, |
| 17 | 18 | callback = function() |
| 18 | - if config.user.invoke_name == vim.fn.expand("%") then api.cmd.leetcode() end | |
| 19 | + if config.user.invoke_name == vim.fn.expand("%") then dashboard.setup() end | |
| 19 | 20 | end, |
| 20 | 21 | }) |
| 21 | 22 | end |
+17-5lua/leetcode/api/command/init.lua
| @@ -3,7 +3,7 @@ local log = require("leetcode.logger") | ||
| 3 | 3 | local config = require("leetcode.config") |
| 4 | 4 | |
| 5 | 5 | local ui = require("leetcode.ui") |
| 6 | -local dashboard = require("leetcode.ui.dashboard") | |
| 6 | +-- local dashboard = require("leetcode.ui.dashboard") | |
| 7 | 7 | local gql = require("leetcode.graphql") |
| 8 | 8 | |
| 9 | 9 | local path = require("plenary.path") |
| @@ -11,7 +11,7 @@ local path = require("plenary.path") | ||
| 11 | 11 | ---@class lc.Commands |
| 12 | 12 | local M = {} |
| 13 | 13 | |
| 14 | -function M.lc_problems() | |
| 14 | +function M.problems() | |
| 15 | 15 | local _, res = assert(pcall(cache.problems.read)) |
| 16 | 16 | |
| 17 | 17 | ui.pick_one( |
| @@ -57,8 +57,10 @@ end | ||
| 57 | 57 | |
| 58 | 58 | function M.leetcode() |
| 59 | 59 | M.authenticate() |
| 60 | - dashboard.setup() | |
| 61 | - vim.cmd("Alpha | bd#") | |
| 60 | + -- M.dashboard(config.auth.is_signed_in and "menu" or "default") | |
| 61 | + | |
| 62 | + local ok, _ = pcall(vim.cmd, "Alpha | bd#") | |
| 63 | + if not ok then log.error("Failed to launch Alpha") end | |
| 62 | 64 | end |
| 63 | 65 | |
| 64 | 66 | function M.cookie_prompt() |
| @@ -71,7 +73,7 @@ function M.cookie_prompt() | ||
| 71 | 73 | if not cookie_str then return end |
| 72 | 74 | |
| 73 | 75 | cookie.new(cookie_str) |
| 74 | - require("leetcode.ui.dashboard").update() | |
| 76 | + M.dashboard("menu") | |
| 75 | 77 | end |
| 76 | 78 | ) |
| 77 | 79 | end |
| @@ -81,4 +83,14 @@ end | ||
| 81 | 83 | ---@return lc.UserStatus | nil |
| 82 | 84 | function M.authenticate() gql.auth.user_status() end |
| 83 | 85 | |
| 86 | +---Merge configurations into default configurations and set it as user configurations. | |
| 87 | +--- | |
| 88 | +---@param theme lc.db.Theme | |
| 89 | +function M.dashboard(theme) dashboard.apply(theme) end | |
| 90 | + | |
| 91 | +---Merge configurations into default configurations and set it as user configurations. | |
| 92 | +--- | |
| 93 | +---@param theme lc.db.Theme | |
| 94 | +function M.qot(theme) ui.open_qot() end | |
| 95 | + | |
| 84 | 96 | return M |
+1-1lua/leetcode/config/init.lua
| @@ -31,7 +31,7 @@ function Config.apply(cfg) | ||
| 31 | 31 | vim.api.nvim_set_hl(0, "LeetCodePTag", { link = "Comment" }) |
| 32 | 32 | vim.api.nvim_set_hl(0, "LeetCodeEmTag", { italic = true }) |
| 33 | 33 | vim.api.nvim_set_hl(0, "LeetCodeStrongTag", { bold = true }) |
| 34 | - vim.api.nvim_set_hl(0, "LeetCodeCodeTag", { link = "MatchParen" }) | |
| 34 | + vim.api.nvim_set_hl(0, "LeetCodeCodeTag", { link = "DiagnosticHint" }) | |
| 35 | 35 | vim.api.nvim_set_hl(0, "LeetCodeSupTag", { link = "MatchParen" }) |
| 36 | 36 | vim.api.nvim_set_hl(0, "LeetCodePreTag", { link = "@text" }) |
| 37 | 37 | end |
+32-32lua/leetcode/graphql/problems.lua
| @@ -29,22 +29,22 @@ function M.all() | ||
| 29 | 29 | } |
| 30 | 30 | |
| 31 | 31 | local query = [[ |
| 32 | - query problemsetQuestionList($limit: Int) { | |
| 33 | - problemsetQuestionList: questionList( | |
| 34 | - categorySlug: "" | |
| 35 | - limit: $limit | |
| 36 | - filters: {} | |
| 37 | - ) { | |
| 38 | - questions: data { | |
| 39 | - index: questionFrontendId | |
| 40 | - title | |
| 41 | - title_slug: titleSlug | |
| 42 | - ac_rate: acRate | |
| 43 | - difficulty | |
| 32 | + query problemsetQuestionList($limit: Int) { | |
| 33 | + problemsetQuestionList: questionList( | |
| 34 | + categorySlug: "" | |
| 35 | + limit: $limit | |
| 36 | + filters: {} | |
| 37 | + ) { | |
| 38 | + questions: data { | |
| 39 | + index: questionFrontendId | |
| 40 | + title | |
| 41 | + title_slug: titleSlug | |
| 42 | + ac_rate: acRate | |
| 43 | + difficulty | |
| 44 | + } | |
| 45 | + } | |
| 44 | 46 | } |
| 45 | - } | |
| 46 | - } | |
| 47 | - ]] | |
| 47 | + ]] | |
| 48 | 48 | |
| 49 | 49 | local ok, res = pcall(gql.query, query, variables) |
| 50 | 50 | assert(ok) |
| @@ -54,24 +54,24 @@ end | ||
| 54 | 54 | |
| 55 | 55 | function M.question_of_today() |
| 56 | 56 | local query = [[ |
| 57 | - query questionOfToday { | |
| 58 | - activeDailyCodingChallengeQuestion { | |
| 59 | - userStatus | |
| 60 | - link | |
| 61 | - question { | |
| 62 | - acRate | |
| 63 | - difficulty | |
| 64 | - freqBar | |
| 65 | - index: questionFrontendId | |
| 66 | - isFavor | |
| 67 | - paidOnly: isPaidOnly | |
| 68 | - status | |
| 69 | - title | |
| 70 | - titleSlug | |
| 57 | + query questionOfToday { | |
| 58 | + activeDailyCodingChallengeQuestion { | |
| 59 | + userStatus | |
| 60 | + link | |
| 61 | + question { | |
| 62 | + acRate | |
| 63 | + difficulty | |
| 64 | + freqBar | |
| 65 | + index: questionFrontendId | |
| 66 | + isFavor | |
| 67 | + paidOnly: isPaidOnly | |
| 68 | + status | |
| 69 | + title | |
| 70 | + titleSlug | |
| 71 | + } | |
| 72 | + } | |
| 71 | 73 | } |
| 72 | - } | |
| 73 | - } | |
| 74 | - ]] | |
| 74 | + ]] | |
| 75 | 75 | |
| 76 | 76 | local ok, res = pcall(gql.query, query) |
| 77 | 77 | assert(ok) |
+18-14lua/leetcode/logger/init.lua
| @@ -2,31 +2,35 @@ local config = require("leetcode.config") | ||
| 2 | 2 | local levels = vim.log.levels |
| 3 | 3 | |
| 4 | 4 | ---@class lc.Logger |
| 5 | -local Logger = {} | |
| 5 | +local logger = {} | |
| 6 | 6 | |
| 7 | ----@param msg string | |
| 7 | +---@alias msg string | number | |
| 8 | + | |
| 9 | +---@param msg msg | |
| 8 | 10 | ---@param lvl? integer |
| 9 | -Logger.log = function(msg, lvl) | |
| 11 | +logger.log = function(msg, lvl) | |
| 10 | 12 | if not config.user.logging then return end |
| 13 | + | |
| 14 | + if type(msg) == "number" then msg = tostring(msg) end | |
| 11 | 15 | vim.notify(msg, lvl or levels.INFO, { title = config.name }) |
| 12 | 16 | end |
| 13 | 17 | |
| 14 | ----@param msg string | |
| 15 | -Logger.info = function(msg) Logger.log(msg) end | |
| 18 | +---@param msg msg | |
| 19 | +logger.info = function(msg) logger.log(msg) end | |
| 16 | 20 | |
| 17 | ----@param msg string | |
| 18 | -Logger.warn = function(msg) Logger.log(msg, levels.WARN) end | |
| 21 | +---@param msg msg | |
| 22 | +logger.warn = function(msg) logger.log(msg, levels.WARN) end | |
| 19 | 23 | |
| 20 | 24 | ---@param msg table |
| 21 | -Logger.inspect = function(msg) Logger.log(vim.inspect(msg)) end | |
| 25 | +logger.inspect = function(msg) logger.log(vim.inspect(msg)) end | |
| 22 | 26 | |
| 23 | ----@param msg string | |
| 24 | -Logger.error = function(msg) Logger.log(msg, levels.ERROR) end | |
| 27 | +---@param msg msg | |
| 28 | +logger.error = function(msg) logger.log(msg, levels.ERROR) end | |
| 25 | 29 | |
| 26 | ----@param msg string | |
| 27 | -Logger.debug = function(msg) | |
| 30 | +---@param msg msg | |
| 31 | +logger.debug = function(msg) | |
| 28 | 32 | if not config.debug then return end |
| 29 | - Logger.log(msg, levels.DEBUG) | |
| 33 | + logger.log(msg, levels.DEBUG) | |
| 30 | 34 | end |
| 31 | 35 | |
| 32 | -return Logger | |
| 36 | +return logger |
+18-11lua/leetcode/parser/init.lua
| @@ -1,11 +1,12 @@ | ||
| 1 | 1 | local log = require("leetcode.logger") |
| 2 | 2 | local Text = require("nui.text") |
| 3 | +local Line = require("nui.line") | |
| 3 | 4 | |
| 4 | 5 | ---@class lc.Parser |
| 5 | 6 | local M = {} |
| 6 | 7 | |
| 7 | ----@type TreesitterModule, string, NuiText[] | |
| 8 | -local ts, html, nui_texts | |
| 8 | +---@type TreesitterModule, string, NuiLine | |
| 9 | +local ts, html, line | |
| 9 | 10 | |
| 10 | 11 | local theme = { |
| 11 | 12 | ["p"] = "LeetCodePTag", |
| @@ -20,6 +21,7 @@ local entities = { | ||
| 20 | 21 | ["<"] = "<", |
| 21 | 22 | [">"] = ">", |
| 22 | 23 | [" "] = " ", |
| 24 | + ["""] = "\"", | |
| 23 | 25 | } |
| 24 | 26 | |
| 25 | 27 | ---@param node TSNode |
| @@ -76,26 +78,30 @@ end | ||
| 76 | 78 | ---@param node TSNode |
| 77 | 79 | ---@param tag_data lc.Parser.Tag |
| 78 | 80 | --- |
| 79 | ----@return NuiText | |
| 81 | +---@return nil | |
| 80 | 82 | local function highlight_node(node, tag_data) |
| 81 | - if not tag_data then return Text("") end | |
| 83 | + local text = get_text(node) | |
| 84 | + if not tag_data then | |
| 85 | + text = text:gsub(" ", " ") | |
| 86 | + return line:append(text) | |
| 87 | + end | |
| 82 | 88 | |
| 83 | 89 | local data = tag_data |
| 84 | - local text = get_text(node) | |
| 85 | 90 | local tag = data.tag |
| 86 | 91 | |
| 87 | 92 | if node:type() == "entity" then text = entities[text] or text end |
| 88 | 93 | if tag == "sup" then text = "^" .. text end |
| 94 | + if tag == "sub" then text = "_" .. text end | |
| 89 | 95 | |
| 90 | 96 | local nui_text = Text(text, theme[tag] or "Normal") |
| 91 | 97 | |
| 92 | 98 | for _, attr in ipairs(data.attrs) do |
| 93 | - if attr.name == "class" and attr.value == "example" then | |
| 94 | - nui_text = Text(text, "DiagnosticInfo") | |
| 99 | + if attr.name == "class" then | |
| 100 | + if attr.value == "example" then nui_text = Text(text, "DiagnosticInfo") end | |
| 95 | 101 | end |
| 96 | 102 | end |
| 97 | 103 | |
| 98 | - table.insert(nui_texts, nui_text) | |
| 104 | + line:append(nui_text) | |
| 99 | 105 | end |
| 100 | 106 | |
| 101 | 107 | ---@param node TSNode |
| @@ -118,16 +124,17 @@ end | ||
| 118 | 124 | |
| 119 | 125 | ---@param to_parse string |
| 120 | 126 | --- |
| 121 | ----@return NuiText[] | |
| 127 | +---@return NuiLine | |
| 122 | 128 | function M.parse(to_parse) |
| 123 | - ts, html, nui_texts = vim.treesitter, to_parse, {} | |
| 129 | + to_parse = to_parse:gsub(" ", " ") | |
| 130 | + ts, html, line = vim.treesitter, to_parse, Line() | |
| 124 | 131 | |
| 125 | 132 | local parser = ts.get_string_parser(html, "html") |
| 126 | 133 | local tree = parser:parse()[1] |
| 127 | 134 | |
| 128 | 135 | parse_nodes(tree:root()) |
| 129 | 136 | |
| 130 | - return nui_texts | |
| 137 | + return line | |
| 131 | 138 | end |
| 132 | 139 | |
| 133 | 140 | return M |
+78-16lua/leetcode/ui/components/question.lua
| @@ -1,15 +1,21 @@ | ||
| 1 | 1 | local log = require("leetcode.logger") |
| 2 | +local gql = require("leetcode.graphql") | |
| 2 | 3 | |
| 3 | -local Text = require("nui.text") | |
| 4 | 4 | local Line = require("nui.line") |
| 5 | -local Layout = require("nui.layout") | |
| 5 | +local Split = require("nui.split") | |
| 6 | 6 | local parser = require("leetcode.parser") |
| 7 | 7 | |
| 8 | -local utils = require("leetcode.utils") | |
| 9 | - | |
| 10 | 8 | ---@class lc.Ui.Components.Problem |
| 11 | 9 | local M = {} |
| 12 | 10 | |
| 11 | +---@type integer, NuiSplit | |
| 12 | +local curr_line, split | |
| 13 | + | |
| 14 | +---Increment current line | |
| 15 | +--- | |
| 16 | +---@return nil | |
| 17 | +local function inc_line() curr_line = curr_line + 1 end | |
| 18 | + | |
| 13 | 19 | ---@param title any |
| 14 | 20 | function M.title(title) |
| 15 | 21 | local line = Line() |
| @@ -46,19 +52,16 @@ function M.stats(title) | ||
| 46 | 52 | return line |
| 47 | 53 | end |
| 48 | 54 | |
| 49 | ----@param question any | |
| 55 | +---@param content string | |
| 50 | 56 | --- |
| 51 | ----@return NuiLine[] | |
| 52 | -function M.content(question) | |
| 53 | - local s = vim.split(question.content, "\n\n<p> </p>\n") | |
| 54 | - local lines = {} | |
| 57 | +---@return nil | |
| 58 | +function M.content(content) | |
| 59 | + local s = vim.gsplit(content, "\n", {}) | |
| 55 | 60 | |
| 56 | - vim.list_extend(lines, M.description(s[1])) | |
| 57 | - vim.list_extend(lines, M.examples(s[2])) | |
| 58 | - -- local cons = parser.parse(s[3]) | |
| 59 | - -- local foll = parser.parse(s[4]) | |
| 60 | - | |
| 61 | - return lines | |
| 61 | + for l in s do | |
| 62 | + parser.parse(l):render(split.bufnr, -1, curr_line) | |
| 63 | + inc_line() | |
| 64 | + end | |
| 62 | 65 | end |
| 63 | 66 | |
| 64 | 67 | ---@param html string |
| @@ -90,11 +93,70 @@ end | ||
| 90 | 93 | ---@param html string |
| 91 | 94 | --- |
| 92 | 95 | ---@return NuiLine[] |
| 93 | -function M.constrains(html) end | |
| 96 | +function M.constrains(html) | |
| 97 | + local lines = {} | |
| 98 | + | |
| 99 | + for s in vim.gsplit(html, "\n\n") do | |
| 100 | + log.info(s) | |
| 101 | + table.insert(lines, Line(parser.parse(s:gsub("s(?![^<>]*>)", " ")))) | |
| 102 | + end | |
| 103 | + | |
| 104 | + return lines | |
| 105 | +end | |
| 94 | 106 | |
| 95 | 107 | ---@param html string |
| 96 | 108 | --- |
| 97 | 109 | ---@return NuiLine[] |
| 98 | 110 | function M.follow_up(html) end |
| 99 | 111 | |
| 112 | +---Render question split | |
| 113 | +--- | |
| 114 | +---@param question lc.Problem | |
| 115 | +--- | |
| 116 | +---@return nil | |
| 117 | +function M.open(question) | |
| 118 | + curr_line = 1 | |
| 119 | + | |
| 120 | + split = Split({ | |
| 121 | + relative = "editor", | |
| 122 | + position = "left", | |
| 123 | + size = "40%", | |
| 124 | + buf_options = { | |
| 125 | + modifiable = true, | |
| 126 | + readonly = false, | |
| 127 | + filetype = "leetcode.nvim", | |
| 128 | + swapfile = false, | |
| 129 | + buftype = "nofile", | |
| 130 | + buflisted = false, | |
| 131 | + }, | |
| 132 | + win_options = { | |
| 133 | + -- winblend = 10, | |
| 134 | + -- winhighlight = "Normal:Normal,FloatBorder:FloatBorder", | |
| 135 | + foldcolumn = "1", | |
| 136 | + wrap = true, | |
| 137 | + number = false, | |
| 138 | + signcolumn = "no", | |
| 139 | + }, | |
| 140 | + enter = true, | |
| 141 | + focusable = true, | |
| 142 | + }) | |
| 143 | + split:mount() | |
| 144 | + | |
| 145 | + vim.api.nvim_buf_set_name(split.bufnr, "LeetCode") | |
| 146 | + | |
| 147 | + local title = gql.question.title(question.title_slug) | |
| 148 | + local content = gql.question.content(question.title_slug).content | |
| 149 | + | |
| 150 | + M.link(question.title_slug):render(split.bufnr, -1, curr_line) | |
| 151 | + inc_line() | |
| 152 | + | |
| 153 | + M.title(title):render(split.bufnr, -1, curr_line) | |
| 154 | + inc_line() | |
| 155 | + | |
| 156 | + M.stats(title):render(split.bufnr, -1, curr_line) | |
| 157 | + inc_line() | |
| 158 | + | |
| 159 | + M.content(content) | |
| 160 | +end | |
| 161 | + | |
| 100 | 162 | return M |
+0-135lua/leetcode/ui/dashboard.lua
| @@ -1,135 +0,0 @@ | ||
| 1 | -local utils = require("leetcode.utils") | |
| 2 | -local gql = require("leetcode.graphql") | |
| 3 | -local config = require("leetcode.config") | |
| 4 | -local log = require("leetcode.logger") | |
| 5 | - | |
| 6 | -local M = {} | |
| 7 | - | |
| 8 | -local header = { | |
| 9 | - type = "text", | |
| 10 | - val = { | |
| 11 | - [[ /$$ /$$ /$$$$$$ /$$ ]], | |
| 12 | - [[| $$ | $$ /$$__ $$ | $$ ]], | |
| 13 | - [[| $$ /$$$$$$ /$$$$$$ /$$$$$$ | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ ]], | |
| 14 | - [[| $$ /$$__ $$/$$__ $|_ $$_/ | $$ /$$__ $$/$$__ $$/$$__ $$]], | |
| 15 | - [[| $$ | $$$$$$$| $$$$$$$$ | $$ | $$ | $$ \ $| $$ | $| $$$$$$$$]], | |
| 16 | - [[| $$ | $$_____| $$_____/ | $$ /$| $$ $| $$ | $| $$ | $| $$_____/]], | |
| 17 | - [[| $$$$$$$| $$$$$$| $$$$$$$ | $$$$| $$$$$$| $$$$$$| $$$$$$| $$$$$$$]], | |
| 18 | - [[|________/\_______/\_______/ \___/ \______/ \______/ \_______/\_______/]], | |
| 19 | - }, | |
| 20 | - opts = { | |
| 21 | - position = "center", | |
| 22 | - hl = "Keyword", | |
| 23 | - }, | |
| 24 | -} | |
| 25 | - | |
| 26 | -local notifications = { | |
| 27 | - type = "text", | |
| 28 | - val = {}, | |
| 29 | - opts = { | |
| 30 | - position = "center", | |
| 31 | - hl = "DiagnosticInfo", | |
| 32 | - }, | |
| 33 | -} | |
| 34 | - | |
| 35 | -local buttons = { | |
| 36 | - type = "group", | |
| 37 | - val = {}, | |
| 38 | - opts = { | |
| 39 | - spacing = 1, | |
| 40 | - }, | |
| 41 | -} | |
| 42 | - | |
| 43 | -local footer = { | |
| 44 | - type = "text", | |
| 45 | - val = {}, | |
| 46 | - opts = { | |
| 47 | - position = "center", | |
| 48 | - hl = "Number", | |
| 49 | - }, | |
| 50 | -} | |
| 51 | - | |
| 52 | -function M.setup() | |
| 53 | - local auth = config.auth | |
| 54 | - local is_signed_in = auth and auth.is_signed_in | |
| 55 | - | |
| 56 | - local alpha = require("alpha") | |
| 57 | - local dashboard = require("alpha.themes.dashboard") | |
| 58 | - | |
| 59 | - notifications.val = not is_signed_in and { " Sign in to use LeetCode.nvim" } or {} | |
| 60 | - | |
| 61 | - buttons.val = is_signed_in | |
| 62 | - and { | |
| 63 | - dashboard.button( | |
| 64 | - "p", | |
| 65 | - " " .. " All Problems", | |
| 66 | - "<cmd>lua require('leetcode.api').cmd.lc_problems()<CR>" | |
| 67 | - ), | |
| 68 | - dashboard.button( | |
| 69 | - "t", | |
| 70 | - " " .. " Question of today", | |
| 71 | - "<cmd>lua require('leetcode.api').gql.problems.question_of_today()<CR>" | |
| 72 | - ), | |
| 73 | - dashboard.button("r", " " .. " Recent problems", ""), | |
| 74 | - dashboard.button( | |
| 75 | - "c", | |
| 76 | - " " .. " Update cookie", | |
| 77 | - "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 78 | - ), | |
| 79 | - dashboard.button( | |
| 80 | - "s", | |
| 81 | - " " .. " Sign out", | |
| 82 | - "<cmd>lua require('leetcode.utils').remove_cookie()<cr>" | |
| 83 | - ), | |
| 84 | - dashboard.button("q", " " .. " Exit LeetCode", "<cmd>qa<CR>"), | |
| 85 | - } | |
| 86 | - or { | |
| 87 | - dashboard.button( | |
| 88 | - "s", | |
| 89 | - " " .. " Sign in (By Cookie)", | |
| 90 | - "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 91 | - ), | |
| 92 | - dashboard.button("q", " " .. " Exit LeetCode", "<cmd>qa!<CR>"), | |
| 93 | - } | |
| 94 | - | |
| 95 | - footer.val = is_signed_in and "Signed in as: " .. auth.username or "" | |
| 96 | - | |
| 97 | - local section = { | |
| 98 | - header = header, | |
| 99 | - notifications = notifications, | |
| 100 | - buttons = buttons, | |
| 101 | - footer = footer, | |
| 102 | - } | |
| 103 | - | |
| 104 | - local cfg = { | |
| 105 | - layout = { | |
| 106 | - -- header | |
| 107 | - { type = "padding", val = 2 }, | |
| 108 | - section.header, | |
| 109 | - | |
| 110 | - -- notifications | |
| 111 | - { type = "padding", val = 2 }, | |
| 112 | - section.notifications, | |
| 113 | - { type = "padding", val = 2 }, | |
| 114 | - | |
| 115 | - -- buttons | |
| 116 | - section.buttons, | |
| 117 | - | |
| 118 | - --footer | |
| 119 | - section.footer, | |
| 120 | - }, | |
| 121 | - opts = { | |
| 122 | - margin = 5, | |
| 123 | - }, | |
| 124 | - } | |
| 125 | - | |
| 126 | - alpha.setup(cfg) | |
| 127 | -end | |
| 128 | - | |
| 129 | -function M.update() | |
| 130 | - utils.alpha_move_cursor_top() | |
| 131 | - M.setup() | |
| 132 | - pcall(vim.cmd, "AlphaRedraw") | |
| 133 | -end | |
| 134 | - | |
| 135 | -return M |
+42-48lua/leetcode/ui/init.lua
| @@ -1,7 +1,8 @@ | ||
| 1 | -local log = require("leetcode.logger") | |
| 2 | 1 | local components = require("leetcode.ui.components") |
| 2 | + | |
| 3 | +local Split = require("nui.split") | |
| 3 | 4 | local gql = require("leetcode.graphql") |
| 4 | -local Line = require("nui.line") | |
| 5 | +local Popup = require("nui.popup") | |
| 5 | 6 | |
| 6 | 7 | ---@class lc.UI |
| 7 | 8 | local Ui = {} |
| @@ -20,54 +21,47 @@ end | ||
| 20 | 21 | |
| 21 | 22 | function Ui.input(opts, callback) vim.ui.input(opts, callback) end |
| 22 | 23 | |
| 23 | ----@param problem lc.Problem | |
| 24 | -function Ui.create_leetcode_win(problem) | |
| 25 | - local start_win = vim.api.nvim_get_current_win() | |
| 26 | - | |
| 27 | - local Split = require("nui.split") | |
| 28 | - local split = Split({ | |
| 29 | - relative = "win", | |
| 30 | - position = "left", | |
| 31 | - size = "30%", | |
| 32 | - }) | |
| 33 | - split:mount() | |
| 34 | - | |
| 35 | - local title = gql.question.title(problem.title_slug) | |
| 36 | - local content = gql.question.content(problem.title_slug) | |
| 37 | - | |
| 38 | - local win = vim.api.nvim_get_current_win() -- We save our navigation window handle... | |
| 39 | - local buf = vim.api.nvim_get_current_buf() -- ...and it's buffer handle. | |
| 40 | - | |
| 41 | - vim.api.nvim_buf_set_name(buf, "LeetCode") | |
| 24 | +---@param question lc.Problem | |
| 25 | +function Ui.create_leetcode_win(question) components.question.open(question) end | |
| 42 | 26 | |
| 43 | - vim.api.nvim_buf_set_option(buf, "filetype", "leetcode.nvim") | |
| 44 | - vim.api.nvim_buf_set_option(buf, "swapfile", false) | |
| 45 | - vim.api.nvim_buf_set_option(buf, "buftype", "nofile") | |
| 46 | - vim.api.nvim_buf_set_option(buf, "buflisted", false) | |
| 47 | - -- vim.api.nvim_buf_set_option(buf, "modifiable", true) | |
| 27 | +---@param question lc.Problem | |
| 28 | +function Ui.open_qot() | |
| 29 | + local qot = gql.problems.question_of_today() | |
| 48 | 30 | |
| 49 | - vim.api.nvim_win_set_option(win, "wrap", true) | |
| 50 | - vim.api.nvim_win_set_option(win, "number", false) | |
| 51 | - vim.api.nvim_win_set_option(win, "signcolumn", "no") | |
| 52 | - | |
| 53 | - -- vim.api.nvim_buf_set_keymap(buf, "n", "<esc>", "<cmd>hide<CR>", { noremap = true }) | |
| 54 | - -- vim.api.nvim_buf_set_keymap(buf, "n", "q", "<cmd>hide<CR>", { noremap = true }) | |
| 55 | - local sep = Line() | |
| 56 | - sep:append("") | |
| 57 | - | |
| 58 | - components.question.link(problem.title_slug):render(buf, -1, 1) | |
| 59 | - sep:render(buf, -1, 2) | |
| 60 | - | |
| 61 | - components.question.title(title):render(buf, -1, 3) | |
| 62 | - components.question.stats(title):render(buf, -1, 4) | |
| 63 | - sep:render(buf, -1, 5) | |
| 64 | - | |
| 65 | - local line_index = 6 | |
| 66 | - for _, line in ipairs(components.question.content(content)) do | |
| 67 | - line:render(buf, -1, line_index) | |
| 68 | - sep:render(buf, -1, line_index + 1) | |
| 69 | - line_index = line_index + 2 | |
| 70 | - end | |
| 31 | + local popup = Popup({ | |
| 32 | + position = "50%", | |
| 33 | + size = { | |
| 34 | + width = 80, | |
| 35 | + height = 40, | |
| 36 | + }, | |
| 37 | + enter = true, | |
| 38 | + focusable = true, | |
| 39 | + zindex = 50, | |
| 40 | + relative = "editor", | |
| 41 | + border = { | |
| 42 | + padding = { | |
| 43 | + top = 2, | |
| 44 | + bottom = 2, | |
| 45 | + left = 3, | |
| 46 | + right = 3, | |
| 47 | + }, | |
| 48 | + style = "rounded", | |
| 49 | + text = { | |
| 50 | + top = " I am top title ", | |
| 51 | + top_align = "center", | |
| 52 | + bottom = "I am bottom title", | |
| 53 | + bottom_align = "left", | |
| 54 | + }, | |
| 55 | + }, | |
| 56 | + buf_options = { | |
| 57 | + modifiable = true, | |
| 58 | + readonly = false, | |
| 59 | + }, | |
| 60 | + win_options = { | |
| 61 | + winblend = 10, | |
| 62 | + winhighlight = "Normal:Normal,FloatBorder:FloatBorder", | |
| 63 | + }, | |
| 64 | + }) | |
| 71 | 65 | end |
| 72 | 66 | |
| 73 | 67 | return Ui |
+1-1lua/leetcode/utils.lua
| @@ -3,7 +3,7 @@ local Utils = {} | ||
| 3 | 3 | |
| 4 | 4 | function Utils.remove_cookie() |
| 5 | 5 | require("leetcode.cache.cookie").delete() |
| 6 | - require("leetcode.ui.dashboard").update() | |
| 6 | + -- require("leetcode.ui.dashboard").apply("default") | |
| 7 | 7 | end |
| 8 | 8 | |
| 9 | 9 | function Utils.alpha_move_cursor_top() |