A Neovim plugin enabling you to solve LeetCode problems.
feat: floating console
10 files changed, +234 -20
+11-2lua/leetcode-ui/layout.lua
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | local log = require("leetcode.logger") |
| 2 | +local Line = require("nui.line") | |
| 2 | 3 | |
| 3 | 4 | ---@class lc-ui.Layout |
| 4 | 5 | ---@field contents lc-ui.Component[] |
| @@ -39,10 +40,18 @@ end | ||
| 39 | 40 | ---@param fn function |
| 40 | 41 | function layout:set_on_press(line, fn) self.on_presses[line] = fn end |
| 41 | 42 | |
| 42 | ----@param config lc-ui.Layout | |
| 43 | +---@param content lc-ui.Component | |
| 44 | +function layout:append(content) | |
| 45 | + table.insert(self.contents, content) | |
| 46 | + return self | |
| 47 | +end | |
| 48 | + | |
| 49 | +---@param config? lc-ui.Layout | |
| 43 | 50 | function layout:init(config) |
| 51 | + config = config or {} | |
| 52 | + | |
| 44 | 53 | local obj = setmetatable({ |
| 45 | - contents = config.contents, | |
| 54 | + contents = config.contents or {}, | |
| 46 | 55 | opts = config.opts or {}, |
| 47 | 56 | line_idx = 1, |
| 48 | 57 | on_presses = {}, |
+6-0lua/leetcode/api/command/init.lua
| @@ -3,6 +3,7 @@ local log = require("leetcode.logger") | ||
| 3 | 3 | local config = require("leetcode.config") |
| 4 | 4 | local question = require("leetcode.ui.components.question") |
| 5 | 5 | local Runner = require("leetcode.runner") |
| 6 | +local Console = require("leetcode.ui.components.console") | |
| 6 | 7 | |
| 7 | 8 | local ui = require("leetcode.ui") |
| 8 | 9 | -- local dashboard = require("leetcode.ui.dashboard") |
| @@ -81,4 +82,9 @@ function cmd.run() | ||
| 81 | 82 | runner:run() |
| 82 | 83 | end |
| 83 | 84 | |
| 85 | +function cmd.console() | |
| 86 | + local console = Console:init() | |
| 87 | + console:mount() | |
| 88 | +end | |
| 89 | + | |
| 84 | 90 | return cmd |
+12-0lua/leetcode/config/template.lua
| @@ -21,6 +21,18 @@ local M = { | ||
| 21 | 21 | |
| 22 | 22 | ---@type boolean |
| 23 | 23 | logging = true, |
| 24 | + | |
| 25 | + console = { | |
| 26 | + size = { | |
| 27 | + width = "75%", ---@type string | integer | |
| 28 | + height = "75%", ---@type string | integer | |
| 29 | + }, | |
| 30 | + dir = "col", ---@type "col" | "row" | |
| 31 | + }, | |
| 32 | + | |
| 33 | + description = { | |
| 34 | + width = "50%", ---@type string | integer | |
| 35 | + }, | |
| 24 | 36 | } |
| 25 | 37 | |
| 26 | 38 | return M |
+1-3lua/leetcode/logger/spinner.lua
| @@ -49,9 +49,7 @@ end | ||
| 49 | 49 | |
| 50 | 50 | function spinner:done() |
| 51 | 51 | self:stop() |
| 52 | - self:set_noti(nil, { | |
| 53 | - icon = "", | |
| 54 | - }) | |
| 52 | + self:set_noti(nil, { icon = "" }) | |
| 55 | 53 | end |
| 56 | 54 | |
| 57 | 55 | function spinner:stop() self.spinner = nil end |
+68-2lua/leetcode/ui/components/console/init.lua
| @@ -1,9 +1,75 @@ | ||
| 1 | +local Popup = require("nui.popup") | |
| 2 | +local Layout = require("leetcode-ui.layout") | |
| 3 | +local config = require("leetcode.config") | |
| 4 | +local Testcase = require("leetcode.ui.components.console.testcase") | |
| 5 | +local Text = require("leetcode-ui.component.text") | |
| 6 | +local Result = require("leetcode.ui.components.console.result") | |
| 7 | + | |
| 8 | +local NuiLine = require("nui.line") | |
| 9 | +local NuiText = require("nui.text") | |
| 10 | +local NuiLayout = require("nui.layout") | |
| 11 | + | |
| 1 | 12 | ---@class lc.Console |
| 13 | +---@field parent lc.Question | |
| 14 | +---@field layout NuiLayout | |
| 2 | 15 | local console = {} |
| 3 | 16 | console.__index = console |
| 4 | 17 | |
| 5 | -function console:init() | |
| 6 | - local obj = setmetatable({}, self) | |
| 18 | +function console:mount() | |
| 19 | + -- self.popup:mount() | |
| 20 | + -- fsadf/ | |
| 21 | + -- self.testcase.layout:draw(self.popup) | |
| 22 | + -- self.layout:append(self.testcase) | |
| 23 | + self.layout:mount() | |
| 24 | + | |
| 25 | + return self | |
| 26 | +end | |
| 27 | + | |
| 28 | +function console:open() | |
| 29 | + -- fasdf/ | |
| 30 | +end | |
| 31 | + | |
| 32 | +function console:hide() | |
| 33 | + -- fasdf/ | |
| 34 | +end | |
| 35 | + | |
| 36 | +---@private | |
| 37 | +function console.navbar() | |
| 38 | + local testcase_text = NuiText(" (t) testcase ", "MasonHeader") | |
| 39 | + local result_text = NuiText(" (r) result ", "MasonHeader") | |
| 40 | + local run_text = NuiText(" (R) run ", "MasonHeader") | |
| 41 | + local submit_text = NuiText(" (S) submit ", "MasonHeader") | |
| 42 | + | |
| 43 | + local nui_line = NuiLine({ | |
| 44 | + -- testcase_text, | |
| 45 | + -- NuiText("\t"), | |
| 46 | + -- result_text, | |
| 47 | + -- NuiText("\t"), | |
| 48 | + }) | |
| 49 | + | |
| 50 | + return Text:init({ | |
| 51 | + lines = { nui_line }, | |
| 52 | + opts = { position = "center" }, | |
| 53 | + }) | |
| 54 | +end | |
| 55 | + | |
| 56 | +---@param parent lc.Question | |
| 57 | +function console:init(parent) | |
| 58 | + local obj = setmetatable({ | |
| 59 | + parent = parent, | |
| 60 | + }, self) | |
| 61 | + | |
| 62 | + obj.layout = NuiLayout( | |
| 63 | + { | |
| 64 | + relative = "editor", | |
| 65 | + position = "50%", | |
| 66 | + size = config.user.console.size, | |
| 67 | + }, | |
| 68 | + NuiLayout.Box({ | |
| 69 | + NuiLayout.Box(Testcase:init(obj).popup, { size = "50%" }), | |
| 70 | + NuiLayout.Box(Result:init(obj).popup, { size = "50%" }), | |
| 71 | + }, { dir = config.user.console.dir }) | |
| 72 | + ) | |
| 7 | 73 | |
| 8 | 74 | return obj |
| 9 | 75 | end |
+55-0lua/leetcode/ui/components/console/result.lua
| @@ -0,0 +1,55 @@ | ||
| 1 | +local Layout = require("leetcode-ui.layout") | |
| 2 | +local Text = require("leetcode-ui.component.text") | |
| 3 | +local NuiLine = require("nui.line") | |
| 4 | +local NuiPopup = require("nui.popup") | |
| 5 | + | |
| 6 | +---@class lc.Testcase | |
| 7 | +---@field popup NuiPopup | |
| 8 | +local result = {} | |
| 9 | +result.__index = result | |
| 10 | + | |
| 11 | +---@param parent lc.Console | |
| 12 | +function result:init(parent) | |
| 13 | + local t = {} | |
| 14 | + for _, case in ipairs(parent.parent.testcases) do | |
| 15 | + for s in vim.gsplit(case, "\n", { trimempty = true }) do | |
| 16 | + table.insert(t, s) | |
| 17 | + end | |
| 18 | + end | |
| 19 | + | |
| 20 | + local popup = NuiPopup({ | |
| 21 | + border = { | |
| 22 | + padding = { | |
| 23 | + top = 1, | |
| 24 | + bottom = 1, | |
| 25 | + left = 3, | |
| 26 | + right = 3, | |
| 27 | + }, | |
| 28 | + style = "rounded", | |
| 29 | + text = { | |
| 30 | + top = " (r) Result ", | |
| 31 | + top_align = "center", | |
| 32 | + bottom = " (R) Run | (S) Submit ", | |
| 33 | + bottom_align = "center", | |
| 34 | + }, | |
| 35 | + }, | |
| 36 | + buf_options = { | |
| 37 | + modifiable = false, | |
| 38 | + readonly = true, | |
| 39 | + }, | |
| 40 | + win_options = { | |
| 41 | + -- winhighlight = "Normal:Normal,FloatBorder:FloatBorder", | |
| 42 | + winhighlight = "Normal:NormalSB,FloatBorder:FloatBorder", | |
| 43 | + }, | |
| 44 | + }) | |
| 45 | + | |
| 46 | + vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, t) | |
| 47 | + | |
| 48 | + local obj = setmetatable({ | |
| 49 | + popup = popup, | |
| 50 | + }, self) | |
| 51 | + | |
| 52 | + return obj | |
| 53 | +end | |
| 54 | + | |
| 55 | +return result |
+60-0lua/leetcode/ui/components/console/testcase.lua
| @@ -0,0 +1,60 @@ | ||
| 1 | +local Layout = require("leetcode-ui.layout") | |
| 2 | +local Text = require("leetcode-ui.component.text") | |
| 3 | +local NuiLine = require("nui.line") | |
| 4 | +local NuiPopup = require("nui.popup") | |
| 5 | + | |
| 6 | +---@class lc.Testcase | |
| 7 | +---@field popup NuiPopup | |
| 8 | +local testcase = {} | |
| 9 | +testcase.__index = testcase | |
| 10 | + | |
| 11 | +---@param parent lc.Console | |
| 12 | +function testcase:init(parent) | |
| 13 | + local t = {} | |
| 14 | + for _, case in ipairs(parent.parent.testcases) do | |
| 15 | + for s in vim.gsplit(case, "\n", { trimempty = true }) do | |
| 16 | + table.insert(t, s) | |
| 17 | + end | |
| 18 | + end | |
| 19 | + | |
| 20 | + local popup = NuiPopup({ | |
| 21 | + -- position = "50%", | |
| 22 | + -- size = config.user.console.size, | |
| 23 | + -- enter = true, | |
| 24 | + -- focusable = true, | |
| 25 | + -- zindex = 50, | |
| 26 | + -- relative = "editor", | |
| 27 | + border = { | |
| 28 | + padding = { | |
| 29 | + top = 1, | |
| 30 | + bottom = 1, | |
| 31 | + left = 3, | |
| 32 | + right = 3, | |
| 33 | + }, | |
| 34 | + style = "rounded", | |
| 35 | + text = { | |
| 36 | + top = " Console | (q) hide ", | |
| 37 | + top_align = "center", | |
| 38 | + bottom = " (t) Testcase ", | |
| 39 | + bottom_align = "center", | |
| 40 | + }, | |
| 41 | + }, | |
| 42 | + buf_options = { | |
| 43 | + modifiable = true, | |
| 44 | + readonly = false, | |
| 45 | + }, | |
| 46 | + win_options = { | |
| 47 | + winhighlight = "Normal:NormalSB,FloatBorder:FloatBorder", | |
| 48 | + }, | |
| 49 | + }) | |
| 50 | + | |
| 51 | + vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, t) | |
| 52 | + | |
| 53 | + local obj = setmetatable({ | |
| 54 | + popup = popup, | |
| 55 | + }, self) | |
| 56 | + | |
| 57 | + return obj | |
| 58 | +end | |
| 59 | + | |
| 60 | +return testcase |
+8-2lua/leetcode/ui/components/description.lua
| @@ -7,6 +7,7 @@ local Text = require("leetcode-ui.component.text") | ||
| 7 | 7 | local padding = require("leetcode-ui.component.padding") |
| 8 | 8 | local NuiText = require("nui.text") |
| 9 | 9 | local NuiLine = require("nui.line") |
| 10 | +local config = require("leetcode.config") | |
| 10 | 11 | |
| 11 | 12 | ---@class lc.Description |
| 12 | 13 | ---@field split NuiSplit |
| @@ -23,6 +24,8 @@ function description:mount() | ||
| 23 | 24 | self:populate() |
| 24 | 25 | self.split:mount() |
| 25 | 26 | self:draw() |
| 27 | + | |
| 28 | + return self | |
| 26 | 29 | end |
| 27 | 30 | |
| 28 | 31 | function description:draw() self.layout:draw(self.split) end |
| @@ -81,24 +84,27 @@ function description:init(parent) | ||
| 81 | 84 | local split = Split({ |
| 82 | 85 | relative = "win", |
| 83 | 86 | position = "left", |
| 84 | - size = "50%", | |
| 87 | + size = config.user.description.width, | |
| 85 | 88 | buf_options = { |
| 86 | 89 | modifiable = true, |
| 87 | 90 | readonly = false, |
| 88 | 91 | filetype = "leetcode.nvim", |
| 89 | 92 | swapfile = false, |
| 90 | 93 | buftype = "nofile", |
| 91 | - buflisted = false, | |
| 94 | + buflisted = true, | |
| 92 | 95 | }, |
| 93 | 96 | win_options = { |
| 94 | 97 | foldcolumn = "1", |
| 95 | 98 | wrap = true, |
| 96 | 99 | number = false, |
| 97 | 100 | signcolumn = "no", |
| 101 | + -- winhighlight = "Normal:Normal,NormalNC:NormalNC", | |
| 102 | + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder", | |
| 98 | 103 | }, |
| 99 | 104 | enter = true, |
| 100 | 105 | focusable = true, |
| 101 | 106 | }) |
| 107 | + vim.api.nvim_buf_set_name(split.bufnr, "Description") | |
| 102 | 108 | |
| 103 | 109 | local obj = setmetatable({ |
| 104 | 110 | split = split, |
+13-4lua/leetcode/ui/components/question.lua
| @@ -2,8 +2,9 @@ local Split = require("nui.split") | ||
| 2 | 2 | local path = require("plenary.path") |
| 3 | 3 | local config = require("leetcode.config") |
| 4 | 4 | local log = require("leetcode.logger") |
| 5 | -local description = require("leetcode.ui.components.description") | |
| 5 | +local Description = require("leetcode.ui.components.description") | |
| 6 | 6 | local gql = require("leetcode.graphql") |
| 7 | +local Console = require("leetcode.ui.components.console") | |
| 7 | 8 | |
| 8 | 9 | ---@class lc.Question |
| 9 | 10 | ---@field file Path |
| @@ -11,6 +12,7 @@ local gql = require("leetcode.graphql") | ||
| 11 | 12 | ---@field description lc.Description |
| 12 | 13 | ---@field testcases string[] |
| 13 | 14 | ---@field bufnr bufnr |
| 15 | +---@field console lc.Console | |
| 14 | 16 | local question = {} |
| 15 | 17 | question.__index = question |
| 16 | 18 | |
| @@ -46,12 +48,19 @@ function question:mount() | ||
| 46 | 48 | vim.cmd("edit " .. self.file:absolute()) |
| 47 | 49 | |
| 48 | 50 | self.bufnr = vim.api.nvim_get_current_buf() |
| 51 | + curr_question = self.bufnr | |
| 49 | 52 | problems[self.bufnr] = self |
| 50 | 53 | |
| 51 | - self.description = description:init(self) | |
| 52 | - self.description:mount() | |
| 54 | + self.description = Description:init(self):mount() | |
| 55 | + self.console = Console:init(self):mount() | |
| 56 | +end | |
| 53 | 57 | |
| 54 | - curr_question = self.bufnr | |
| 58 | +function question:run() | |
| 59 | + -- fasdf// | |
| 60 | +end | |
| 61 | + | |
| 62 | +function question:submit() | |
| 63 | + -- fasdf/ | |
| 55 | 64 | end |
| 56 | 65 | |
| 57 | 66 | ---@param problem lc.Problem |
+0-7lua/leetcode/ui/init.lua
| @@ -100,13 +100,6 @@ function Ui.testcase() | ||
| 100 | 100 | }, |
| 101 | 101 | }) |
| 102 | 102 | |
| 103 | - local testcases = problems[curr_question].q.testcase_list | |
| 104 | - for i, t in ipairs(testcases) do | |
| 105 | - testcases[i] = t:gsub("\n", " ") | |
| 106 | - end | |
| 107 | - | |
| 108 | - vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, testcases) | |
| 109 | - | |
| 110 | 103 | popup:mount() |
| 111 | 104 | end |
| 112 | 105 |