A Neovim plugin enabling you to solve LeetCode problems.
feat(console): group cases into separate tabs
7 files changed, +490 -318
+6-0lua/leetcode/theme/default.lua
| @@ -37,6 +37,12 @@ M.get = function() | ||
| 37 | 37 | hint = { fg = hl("DiagnosticHint").fg }, |
| 38 | 38 | error = { fg = hl("DiagnosticError").fg }, |
| 39 | 39 | |
| 40 | + case_ok = { fg = hl("DiagnosticOk").fg, bg = hl("Normal").bg, bold = true }, | |
| 41 | + case_err = { fg = hl("DiagnosticError").fg, bg = hl("Normal").bg, bold = true }, | |
| 42 | + | |
| 43 | + case_focus_ok = { bg = hl("DiagnosticOk").fg, fg = hl("NormalSB").bg, bold = true }, | |
| 44 | + case_focus_err = { bg = hl("DiagnosticError").fg, fg = hl("NormalSB").bg, bold = true }, | |
| 45 | + | |
| 40 | 46 | normal = { fg = hl("Conceal").fg }, |
| 41 | 47 | alt = { fg = hl("Comment").fg }, |
| 42 | 48 |
+76-26lua/leetcode/ui/console/components/case.lua
| @@ -1,44 +1,94 @@ | ||
| 1 | 1 | local log = require("leetcode.logger") |
| 2 | 2 | local t = require("leetcode.translator") |
| 3 | + | |
| 3 | 4 | local Pre = require("leetcode.ui.console.components.pre") |
| 5 | +local Stdout = require("leetcode.ui.console.components.stdout") | |
| 6 | +local Group = require("leetcode-ui.component.group") | |
| 4 | 7 | |
| 5 | 8 | local NuiLine = require("nui.line") |
| 6 | 9 | |
| 7 | ----@class lc.Result.Case | |
| 8 | -local case = {} | |
| 10 | +---@class lc.Result.Case : lc-ui.Group | |
| 11 | +---@field pre lc-ui.Text | |
| 12 | +---@field stdout lc-ui.Text | |
| 13 | +---@field passed boolean | |
| 14 | +---@field index integer | |
| 15 | +local Case = {} | |
| 16 | +Case.__index = Case | |
| 17 | +setmetatable(Case, Group) | |
| 18 | + | |
| 19 | +local function get_pad(key, max_len) return (" "):rep(max_len + 1 - vim.api.nvim_strwidth(key)) end | |
| 20 | + | |
| 21 | +---@private | |
| 22 | +---@param input string | |
| 23 | +---@return NuiLine | |
| 24 | +function Case:input(input) | |
| 25 | + local key = t("Input") | |
| 26 | + | |
| 27 | + local line = NuiLine() | |
| 28 | + line:append(("%s:%s"):format(key, get_pad(key, self.max_len))) | |
| 29 | + line:append(input, "leetcode_alt") | |
| 30 | + | |
| 31 | + return line | |
| 32 | +end | |
| 33 | + | |
| 34 | +---@private | |
| 35 | +---@param output string | |
| 36 | +---@param expected string | |
| 37 | +---@return NuiLine | |
| 38 | +function Case:output(output, expected) | |
| 39 | + local key = t("Output") | |
| 40 | + | |
| 41 | + local line = NuiLine() | |
| 42 | + line:append(("%s:%s"):format(key, get_pad(key, self.max_len))) | |
| 43 | + line:append(output, "leetcode_alt") | |
| 44 | + | |
| 45 | + return line | |
| 46 | +end | |
| 47 | + | |
| 48 | +---@private | |
| 49 | +---@param expected string | |
| 50 | +---@param output string | |
| 51 | +---@return NuiLine | |
| 52 | +function Case:expected(expected, output) | |
| 53 | + local key = t("Expected") | |
| 54 | + | |
| 55 | + local line = NuiLine() | |
| 56 | + line:append(("%s:%s"):format(key, get_pad(key, self.max_len))) | |
| 57 | + line:append(expected, "leetcode_alt") | |
| 58 | + | |
| 59 | + return line | |
| 60 | +end | |
| 9 | 61 | |
| 10 | ----@param index integer | |
| 11 | 62 | ---@param input string |
| 12 | 63 | ---@param output string |
| 13 | 64 | ---@param expected string |
| 14 | ----@param passed? boolean | |
| 65 | +---@param std_output string | |
| 66 | +---@param passed boolean | |
| 15 | 67 | --- |
| 16 | ----@return lc-ui.Text | |
| 17 | -function case:init(index, input, output, expected, passed) | |
| 18 | - local tbl = {} | |
| 68 | +---@return lc.Result.Case | |
| 69 | +function Case:init(input, output, expected, std_output, passed) | |
| 70 | + local group = Group:init({}, { spacing = 1 }) | |
| 71 | + self = setmetatable(group, self) | |
| 19 | 72 | |
| 20 | - local match = passed or (output == expected) | |
| 21 | - local icon = match and "" or "" | |
| 22 | - local hi = match and "leetcode_ok" or "leetcode_error" | |
| 23 | - local title_line = NuiLine() | |
| 24 | - title_line:append(icon .. " Case " .. index, hi) | |
| 73 | + self.max_len = 0 | |
| 74 | + for _, key in ipairs({ "Input", "Output", "Expected" }) do | |
| 75 | + self.max_len = math.max(self.max_len, vim.api.nvim_strwidth(t(key))) | |
| 76 | + end | |
| 77 | + | |
| 78 | + local tbl = {} | |
| 79 | + table.insert(tbl, self:input(input)) | |
| 80 | + table.insert(tbl, self:output(output, expected)) | |
| 81 | + table.insert(tbl, self:expected(expected, output)) | |
| 25 | 82 | |
| 26 | - local input_line = NuiLine() | |
| 27 | - input_line:append(("%s: "):format(t("Input"))) | |
| 28 | - input_line:append(input, "leetcode_alt") | |
| 29 | - table.insert(tbl, input_line) | |
| 83 | + local pre = Pre:init(nil, tbl) | |
| 84 | + self:append(pre) | |
| 30 | 85 | |
| 31 | - local output_line = NuiLine() | |
| 32 | - output_line:append(("%s: "):format(t("Output"))) | |
| 33 | - output_line:append(output, "leetcode_alt") | |
| 34 | - table.insert(tbl, output_line) | |
| 86 | + local stdout = Stdout:init(std_output) | |
| 87 | + if stdout then self:append(stdout) end | |
| 35 | 88 | |
| 36 | - local expected_line = NuiLine() | |
| 37 | - expected_line:append(("%s: "):format(t("Expected"))) | |
| 38 | - expected_line:append(expected, "leetcode_alt") | |
| 39 | - table.insert(tbl, expected_line) | |
| 89 | + self.passed = passed | |
| 40 | 90 | |
| 41 | - return Pre:init(title_line, tbl) | |
| 91 | + return self | |
| 42 | 92 | end |
| 43 | 93 | |
| 44 | -return case | |
| 94 | +return Case |
+88-0lua/leetcode/ui/console/components/cases.lua
| @@ -0,0 +1,88 @@ | ||
| 1 | +local log = require("leetcode.logger") | |
| 2 | + | |
| 3 | +local Group = require("leetcode-ui.component.group") | |
| 4 | +local Text = require("leetcode-ui.component.text") | |
| 5 | +local Case = require("leetcode.ui.console.components.case") | |
| 6 | + | |
| 7 | +local NuiLine = require("nui.line") | |
| 8 | + | |
| 9 | +---@class lc.Cases : lc-ui.Group | |
| 10 | +---@field nav lc-ui.Text | |
| 11 | +---@field case lc-ui.Group | |
| 12 | +---@field cases table<integer, lc.Result.Case> | |
| 13 | +---@field keymaps { mode: string, key: string } | |
| 14 | +---@field idx integer | |
| 15 | +---@field parent lc.Result | |
| 16 | +local Cases = {} | |
| 17 | +Cases.__index = Cases | |
| 18 | +setmetatable(Cases, Group) | |
| 19 | + | |
| 20 | +function Cases:clear() | |
| 21 | + for _, map in ipairs(self.keymaps) do | |
| 22 | + pcall(function() self.parent.popup:unmap(map.mode, map.key) end) | |
| 23 | + end | |
| 24 | + self.keymaps = {} | |
| 25 | + Group.clear(self) | |
| 26 | +end | |
| 27 | + | |
| 28 | +function Cases:update_nav() | |
| 29 | + local cases = NuiLine() | |
| 30 | + | |
| 31 | + for i, case in ipairs(self.cases) do | |
| 32 | + local text = NuiLine() | |
| 33 | + local hl = ("%s%s"):format(self.idx == i and "focus_" or "", case.passed and "ok" or "err") | |
| 34 | + text:append((" Case (%d) "):format(i), "leetcode_case_" .. hl) | |
| 35 | + | |
| 36 | + local keymap = { mode = "n", key = tostring(i) } | |
| 37 | + self.parent.popup:map(keymap.mode, keymap.key, function() self:change(i) end) | |
| 38 | + table.insert(self.keymaps, keymap) | |
| 39 | + | |
| 40 | + cases:append(text) | |
| 41 | + if i ~= #self.cases then cases:append(" ") end | |
| 42 | + end | |
| 43 | + | |
| 44 | + self.nav.lines = { cases } | |
| 45 | +end | |
| 46 | + | |
| 47 | +---@param idx integer | |
| 48 | +function Cases:change(idx) | |
| 49 | + if not self.cases[idx] or idx == self.idx then return end | |
| 50 | + self.case.components = { self.cases[idx] } | |
| 51 | + self.idx = idx | |
| 52 | + self:update_nav() | |
| 53 | + self.parent:draw() | |
| 54 | +end | |
| 55 | + | |
| 56 | +---@param item lc.runtime | |
| 57 | +---@param testcases string[] | |
| 58 | +---@param parent lc.Result | |
| 59 | +---@return lc.Cases | |
| 60 | +function Cases:init(item, testcases, parent) | |
| 61 | + local group = Group:init({}, { spacing = 1 }) | |
| 62 | + self = setmetatable(group, self) | |
| 63 | + | |
| 64 | + self.cases = {} | |
| 65 | + self.parent = parent | |
| 66 | + self.keymaps = {} | |
| 67 | + | |
| 68 | + for i, answer in ipairs(item.code_answer) do | |
| 69 | + self.cases[i] = Case:init( | |
| 70 | + testcases[i], | |
| 71 | + answer, | |
| 72 | + item.expected_code_answer[i], | |
| 73 | + item.std_output_list[i], | |
| 74 | + item.compare_result:sub(i, i) == "1" | |
| 75 | + ) | |
| 76 | + end | |
| 77 | + | |
| 78 | + self.nav = Text:init({}, { padding = { top = 1, bot = 1 } }) | |
| 79 | + self:append(self.nav) | |
| 80 | + | |
| 81 | + self.case = Group:init({}) | |
| 82 | + self:append(self.case) | |
| 83 | + self:change(1) | |
| 84 | + | |
| 85 | + return self ---@diagnostic disable-line | |
| 86 | +end | |
| 87 | + | |
| 88 | +return Cases |
+3-2lua/leetcode/ui/console/components/pre.lua
| @@ -9,12 +9,13 @@ local NuiLine = require("nui.line") | ||
| 9 | 9 | local pre = {} |
| 10 | 10 | pre.__index = pre |
| 11 | 11 | |
| 12 | ----@param title NuiText | NuiLine | |
| 12 | +---@param title NuiText | NuiLine | nil | |
| 13 | 13 | ---@param lines NuiLine[] |
| 14 | 14 | --- |
| 15 | 15 | ---@return lc-ui.Text |
| 16 | 16 | function pre:init(title, lines) |
| 17 | - local text = Text:init({ title, NuiLine() }) | |
| 17 | + local text = Text:init({}) | |
| 18 | + if title then text:append({ title, NuiLine() }) end | |
| 18 | 19 | |
| 19 | 20 | for _, line in ipairs(lines) do |
| 20 | 21 | local new_line = NuiLine({ NuiText("\t▎\t", "leetcode_indent") }) |
+304-0lua/leetcode/ui/console/components/result.lua
| @@ -0,0 +1,304 @@ | ||
| 1 | +local config = require("leetcode.config") | |
| 2 | +local t = require("leetcode.translator") | |
| 3 | +local log = require("leetcode.logger") | |
| 4 | +local Cases = require("leetcode.ui.console.components.cases") | |
| 5 | + | |
| 6 | +local Layout = require("leetcode-ui.layout") | |
| 7 | +local Pre = require("leetcode.ui.console.components.pre") | |
| 8 | +local Group = require("leetcode-ui.component.group") | |
| 9 | +local Text = require("leetcode-ui.component.text") | |
| 10 | +local Stdout = require("leetcode.ui.console.components.stdout") | |
| 11 | +local Case = require("leetcode.ui.console.components.case") | |
| 12 | + | |
| 13 | +local NuiLine = require("nui.line") | |
| 14 | +local NuiText = require("nui.text") | |
| 15 | + | |
| 16 | +---@class lc.ResultLayout : lc-ui.Layout | |
| 17 | +---@field parent lc.Console | |
| 18 | +---@field group lc-ui.Group | |
| 19 | +---@field cases lc.Cases | |
| 20 | +---@field case lc-ui.Group | |
| 21 | +---@field case_idx integer | |
| 22 | +local ResultLayout = {} | |
| 23 | +ResultLayout.__index = ResultLayout | |
| 24 | +setmetatable(ResultLayout, Layout) | |
| 25 | + | |
| 26 | +local function testcases_passed(item) | |
| 27 | + local correct = item.total_correct ~= vim.NIL and item.total_correct or "0" | |
| 28 | + local total = item.total_testcases ~= vim.NIL and item.total_testcases or "0" | |
| 29 | + | |
| 30 | + return ("%d/%d %s"):format(correct, total, t("testcases passed")) | |
| 31 | +end | |
| 32 | + | |
| 33 | +function ResultLayout:handle_accepted(item) | |
| 34 | + local function perc_hi(perc) return perc >= 50 and "leetcode_ok" or "leetcode_error" end | |
| 35 | + self.group:set_opts({ spacing = 2 }) | |
| 36 | + | |
| 37 | + local header = Text:init({}) | |
| 38 | + header:append(item._.title .. " 🎉", item._.hl) | |
| 39 | + self.group:append(header) | |
| 40 | + | |
| 41 | + -- runtime | |
| 42 | + local status_runtime = NuiLine() | |
| 43 | + local runtime_ms = item.display_runtime or vim.split(item.status_runtime, " ")[1] or "NIL" | |
| 44 | + status_runtime:append(runtime_ms) | |
| 45 | + status_runtime:append(" ms", "leetcode_alt") | |
| 46 | + | |
| 47 | + local perc_runtime = NuiLine() | |
| 48 | + perc_runtime:append( | |
| 49 | + ("%s %.2f%% "):format(t("Beats"), item.runtime_percentile), | |
| 50 | + perc_hi(item.runtime_percentile) | |
| 51 | + ) | |
| 52 | + if config.is_cn then | |
| 53 | + perc_runtime:append("使用 ") | |
| 54 | + perc_runtime:append(item.pretty_lang, "Structure") | |
| 55 | + perc_runtime:append(" 的用户") | |
| 56 | + else | |
| 57 | + perc_runtime:append("of users with ") | |
| 58 | + perc_runtime:append(item.pretty_lang, "Structure") | |
| 59 | + end | |
| 60 | + | |
| 61 | + local runtime = Pre:init(NuiText((" %s"):format(t("Runtime")), "leetcode_normal"), { | |
| 62 | + status_runtime, | |
| 63 | + perc_runtime, | |
| 64 | + }) | |
| 65 | + self.group:append(runtime) | |
| 66 | + | |
| 67 | + -- memory | |
| 68 | + local status_memory = NuiLine() | |
| 69 | + -- if item.status_memory == "0B" then item.status_memory = "0 MB" end | |
| 70 | + item.status_memory = item.status_memory:gsub("(%d+)%s*(%a+)", "%1 %2") | |
| 71 | + -- log.info(item.status_memory) | |
| 72 | + local s_mem = vim.split(item.status_memory, " ") | |
| 73 | + status_memory:append(s_mem[1] .. " ") | |
| 74 | + status_memory:append(s_mem[2], "leetcode_alt") | |
| 75 | + | |
| 76 | + local perc_mem = NuiLine() | |
| 77 | + perc_mem:append( | |
| 78 | + ("%s %.2f%% "):format(t("Beats"), item.memory_percentile), | |
| 79 | + perc_hi(item.memory_percentile) | |
| 80 | + ) | |
| 81 | + if config.is_cn then | |
| 82 | + perc_mem:append("使用 ") | |
| 83 | + perc_mem:append(item.pretty_lang, "Structure") | |
| 84 | + perc_mem:append(" 的用户") | |
| 85 | + else | |
| 86 | + perc_mem:append("of users with ") | |
| 87 | + perc_mem:append(item.pretty_lang, "Structure") | |
| 88 | + end | |
| 89 | + | |
| 90 | + local memory = Pre:init(NuiText((" %s"):format(t("Memory")), "leetcode_normal"), { | |
| 91 | + status_memory, | |
| 92 | + perc_mem, | |
| 93 | + }) | |
| 94 | + | |
| 95 | + self.group:append(memory) | |
| 96 | +end | |
| 97 | + | |
| 98 | +---@private | |
| 99 | +--- | |
| 100 | +---@param item lc.runtime | |
| 101 | +function ResultLayout:handle_runtime(item) -- status code = 10 | |
| 102 | + if item._.submission then return self:handle_accepted(item) end | |
| 103 | + | |
| 104 | + local header = Text:init({}) | |
| 105 | + local h = NuiLine() | |
| 106 | + h:append(item._.title, item._.hl) | |
| 107 | + h:append(" | ") | |
| 108 | + h:append(("%s: %s"):format(t("Runtime"), item.status_runtime), "leetcode_alt") | |
| 109 | + header:append(h) | |
| 110 | + self.group:append(header) | |
| 111 | + | |
| 112 | + self.cases = Cases:init(item, self.parent.testcase.testcases, self.parent.result) | |
| 113 | + self.group:append(self.cases) | |
| 114 | +end | |
| 115 | + | |
| 116 | +---@private | |
| 117 | +--- | |
| 118 | +---@param item lc.submission | |
| 119 | +function ResultLayout:handle_submission_error(item) -- status code = 11 | |
| 120 | + self.group:set_opts({ spacing = 2 }) | |
| 121 | + | |
| 122 | + local header = NuiLine() | |
| 123 | + header:append(item._.title, item._.hl) | |
| 124 | + header:append(" | ") | |
| 125 | + header:append(testcases_passed(item), "leetcode_alt") | |
| 126 | + self.group:append(Text:init({ header })) | |
| 127 | + | |
| 128 | + self.case = Case:init( | |
| 129 | + item.input_formatted, | |
| 130 | + item.code_output, | |
| 131 | + item.expected_output, | |
| 132 | + item.std_output, | |
| 133 | + false | |
| 134 | + ) | |
| 135 | + self.group:append(self.case) | |
| 136 | +end | |
| 137 | + | |
| 138 | +---@private | |
| 139 | +--- | |
| 140 | +---@param item lc.limit_exceeded_error | |
| 141 | +function ResultLayout:handle_limit_exceeded(item) -- status code = 12,13,14 | |
| 142 | + local header = NuiLine() | |
| 143 | + header:append(item._.title, item._.hl) | |
| 144 | + if item._.submission then | |
| 145 | + header:append(" | ") | |
| 146 | + header:append(testcases_passed(item), "leetcode_alt") | |
| 147 | + end | |
| 148 | + self.group:append(Text:init({ header })) | |
| 149 | + | |
| 150 | + if item._.submission then | |
| 151 | + local last_testcase = NuiLine() | |
| 152 | + last_testcase:append(item.last_testcase:gsub("\n", " "), "leetcode_indent") | |
| 153 | + | |
| 154 | + local pre_header = NuiLine() | |
| 155 | + pre_header:append((" %s"):format(t("Last Executed Input")), "leetcode_normal") | |
| 156 | + | |
| 157 | + local last_exec = Pre:init(pre_header, { last_testcase }) | |
| 158 | + self.group:append(last_exec) | |
| 159 | + | |
| 160 | + if item.std_output ~= "" then | |
| 161 | + local stdout = Stdout:init(item.std_output) | |
| 162 | + if stdout then self.group:append(stdout) end | |
| 163 | + end | |
| 164 | + elseif item.std_output_list[#item.std_output_list] ~= "" then | |
| 165 | + local stdout = Stdout:init(item.std_output_list[#item.std_output_list]) | |
| 166 | + if stdout then self.group:append(stdout) end | |
| 167 | + end | |
| 168 | +end | |
| 169 | + | |
| 170 | +---@private | |
| 171 | +--- | |
| 172 | +---@param item lc.runtime_error | |
| 173 | +function ResultLayout:handle_runtime_error(item) -- status code = 15 | |
| 174 | + local header = NuiLine() | |
| 175 | + header:append(item._.title, item._.hl) | |
| 176 | + | |
| 177 | + if item._.submission then | |
| 178 | + header:append(" | ") | |
| 179 | + header:append(testcases_passed(item), "leetcode_alt") | |
| 180 | + end | |
| 181 | + | |
| 182 | + local tbl = {} | |
| 183 | + for line in vim.gsplit(item.full_runtime_error, "\n") do | |
| 184 | + table.insert(tbl, NuiLine():append(line, "leetcode_error")) | |
| 185 | + end | |
| 186 | + group:append(Pre:init(header, tbl)) | |
| 187 | + | |
| 188 | + if item._.submission then | |
| 189 | + local pre_header = NuiLine() | |
| 190 | + pre_header:append((" %s"):format(t("Last Executed Input")), "leetcode_normal") | |
| 191 | + | |
| 192 | + local last_testcase = NuiLine() | |
| 193 | + last_testcase:append(item.last_testcase:gsub("\n", " "), "leetcode_indent") | |
| 194 | + | |
| 195 | + local last_exec = Pre:init(pre_header, { last_testcase }) | |
| 196 | + group:append(last_exec) | |
| 197 | + else | |
| 198 | + local stdout = Stdout:init(item.std_output_list[#item.std_output_list]) | |
| 199 | + if stdout then group:append(stdout) end | |
| 200 | + end | |
| 201 | +end | |
| 202 | + | |
| 203 | +function ResultLayout:handle_internal_error(item) -- status code = 16 | |
| 204 | + local header = NuiLine() | |
| 205 | + header:append(item._.title, item._.hl) | |
| 206 | + local text = Text:init({ header }) | |
| 207 | + self.group:append(text) | |
| 208 | +end | |
| 209 | + | |
| 210 | +---@private | |
| 211 | +--- | |
| 212 | +---@param item lc.compile_error | |
| 213 | +function ResultLayout:handle_compile_error(item) -- status code = 20 | |
| 214 | + local header = NuiLine() | |
| 215 | + header:append(item._.title, item._.hl) | |
| 216 | + if item._.submission then | |
| 217 | + header:append(" | ") | |
| 218 | + header:append(testcases_passed(item), "leetcode_alt") | |
| 219 | + end | |
| 220 | + | |
| 221 | + local tbl = {} | |
| 222 | + for line in vim.gsplit(item.full_compile_error, "\n") do | |
| 223 | + table.insert(tbl, NuiLine():append(line, "leetcode_error")) | |
| 224 | + end | |
| 225 | + | |
| 226 | + self.group:append(Pre:init(header, tbl)) | |
| 227 | +end | |
| 228 | + | |
| 229 | +---@param idx integer | |
| 230 | +function ResultLayout:change_case(idx) | |
| 231 | + if vim.tbl_isempty(self.cases) then return end | |
| 232 | + if not self.cases[idx] then return end | |
| 233 | + self.case.components = { self.cases[idx] } | |
| 234 | + self.parent.result:draw() | |
| 235 | +end | |
| 236 | + | |
| 237 | +---@param item lc.interpreter_response | |
| 238 | +function ResultLayout:handle_res(item) | |
| 239 | + log.info(item.status_code) | |
| 240 | + self.group = Group:init({}, { spacing = 1 }) | |
| 241 | + | |
| 242 | + local handlers = { | |
| 243 | + -- runtime | |
| 244 | + [10] = function() | |
| 245 | + self:handle_runtime(item --[[@as lc.runtime]]) | |
| 246 | + end, | |
| 247 | + [11] = function() | |
| 248 | + self:handle_submission_error(item --[[@as lc.submission]]) | |
| 249 | + end, | |
| 250 | + | |
| 251 | + -- memory limit | |
| 252 | + [12] = function() | |
| 253 | + self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 254 | + end, | |
| 255 | + -- time limit | |
| 256 | + [13] = function() | |
| 257 | + self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 258 | + end, | |
| 259 | + -- output limit | |
| 260 | + [14] = function() | |
| 261 | + self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 262 | + end, | |
| 263 | + | |
| 264 | + -- runtime error | |
| 265 | + [15] = function() | |
| 266 | + self:handle_runtime_error(item --[[@as lc.runtime_error]]) | |
| 267 | + end, | |
| 268 | + | |
| 269 | + -- internal error | |
| 270 | + [16] = function() | |
| 271 | + self:handle_internal_error(item --[[@as lc.internal_error]]) | |
| 272 | + end, | |
| 273 | + | |
| 274 | + -- compiler | |
| 275 | + [20] = function() | |
| 276 | + self:handle_compile_error(item --[[@as lc.compile_error]]) | |
| 277 | + end, | |
| 278 | + | |
| 279 | + -- unknown | |
| 280 | + ["unknown"] = function() log.error("unknown runner status code: " .. item.status_code) end, | |
| 281 | + }; | |
| 282 | + | |
| 283 | + (handlers[item.status_code] or handlers["unknown"])() | |
| 284 | + self:append(self.group) | |
| 285 | +end | |
| 286 | + | |
| 287 | +function ResultLayout:clear() | |
| 288 | + if self.cases then self.cases:clear() end | |
| 289 | + Layout.clear(self) | |
| 290 | +end | |
| 291 | + | |
| 292 | +---@param parent lc.Console | |
| 293 | +---@param opts? lc-ui.Layout.opts | |
| 294 | +function ResultLayout:init(parent, opts) | |
| 295 | + local layout = Layout:init({}, opts) | |
| 296 | + | |
| 297 | + self = setmetatable(layout, self) | |
| 298 | + | |
| 299 | + self.parent = parent | |
| 300 | + | |
| 301 | + return self | |
| 302 | +end | |
| 303 | + | |
| 304 | +return ResultLayout |
+4-4lua/leetcode/ui/console/components/stdout.lua
| @@ -10,14 +10,14 @@ local stdout = {} | ||
| 10 | 10 | --- |
| 11 | 11 | ---@return lc-ui.Text | nil |
| 12 | 12 | function stdout:init(output) |
| 13 | - output = output or "" | |
| 14 | - local output_list = vim.split(output, "\n", { trimempty = true }) | |
| 15 | - | |
| 13 | + local output_list = vim.split(output or "", "\n", { trimempty = true }) | |
| 16 | 14 | if vim.tbl_isempty(output_list) then return end |
| 17 | 15 | |
| 18 | 16 | local tbl = {} |
| 19 | 17 | for i = 1, #output_list, 1 do |
| 20 | - table.insert(tbl, NuiLine():append(output_list[i])) | |
| 18 | + local line = NuiLine() | |
| 19 | + line:append(output_list[i]) | |
| 20 | + table.insert(tbl, line) | |
| 21 | 21 | end |
| 22 | 22 | |
| 23 | 23 | return Pre:init(NuiLine():append((" %s"):format(t("Stdout")), "leetcode_alt"), tbl) |
+9-286lua/leetcode/ui/console/result.lua
| @@ -9,266 +9,27 @@ local Case = require("leetcode.ui.console.components.case") | ||
| 9 | 9 | local Group = require("leetcode-ui.component.group") |
| 10 | 10 | local Pre = require("leetcode.ui.console.components.pre") |
| 11 | 11 | local Layout = require("leetcode-ui.layout") |
| 12 | +local ResultLayout = require("leetcode.ui.console.components.result") | |
| 12 | 13 | local Text = require("leetcode-ui.component.text") |
| 13 | 14 | |
| 14 | 15 | local NuiLine = require("nui.line") |
| 15 | 16 | local NuiText = require("nui.text") |
| 16 | 17 | local NuiPopup = require("nui.popup") |
| 17 | 18 | |
| 18 | ----@class lc.Result: lc.Console.Popup | |
| 19 | ----@field layout lc-ui.Layout | |
| 19 | +---@class lc.Result : lc.Console.Popup | |
| 20 | +---@field layout lc.ResultLayout | |
| 20 | 21 | local result = {} |
| 21 | 22 | result.__index = result |
| 22 | 23 | setmetatable(result, console_popup) |
| 23 | 24 | |
| 24 | -local function testcases_passed(item) | |
| 25 | - local correct = item.total_correct ~= vim.NIL and item.total_correct or "0" | |
| 26 | - local total = item.total_testcases ~= vim.NIL and item.total_testcases or "0" | |
| 27 | - | |
| 28 | - return ("%d/%d %s"):format(correct, total, t("testcases passed")) | |
| 29 | -end | |
| 30 | - | |
| 31 | 25 | ---@param hi string |
| 32 | 26 | function result:set_popup_border_hi(hi) self.popup.border:set_highlight(hi) end |
| 33 | 27 | |
| 34 | -function result:handle_accepted(item) | |
| 35 | - local function perc_hi(perc) return perc >= 50 and "leetcode_ok" or "leetcode_error" end | |
| 36 | - local group = Group:init({}, { spacing = 2 }) | |
| 37 | - | |
| 38 | - local header = Text:init({}) | |
| 39 | - header:append(item._.title .. " 🎉", item._.hl) | |
| 40 | - group:append(header) | |
| 41 | - | |
| 42 | - -- runtime | |
| 43 | - local status_runtime = NuiLine() | |
| 44 | - local runtime_ms = item.display_runtime or vim.split(item.status_runtime, " ")[1] or "NIL" | |
| 45 | - status_runtime:append(runtime_ms) | |
| 46 | - status_runtime:append(" ms", "leetcode_alt") | |
| 47 | - | |
| 48 | - local perc_runtime = NuiLine() | |
| 49 | - perc_runtime:append( | |
| 50 | - ("%s %.2f%% "):format(t("Beats"), item.runtime_percentile), | |
| 51 | - perc_hi(item.runtime_percentile) | |
| 52 | - ) | |
| 53 | - if config.is_cn then | |
| 54 | - perc_runtime:append("使用 ") | |
| 55 | - perc_runtime:append(item.pretty_lang, "Structure") | |
| 56 | - perc_runtime:append(" 的用户") | |
| 57 | - else | |
| 58 | - perc_runtime:append("of users with ") | |
| 59 | - perc_runtime:append(item.pretty_lang, "Structure") | |
| 60 | - end | |
| 61 | - | |
| 62 | - local runtime = Pre:init(NuiText((" %s"):format(t("Runtime")), "leetcode_normal"), { | |
| 63 | - status_runtime, | |
| 64 | - perc_runtime, | |
| 65 | - }) | |
| 66 | - group:append(runtime) | |
| 67 | - | |
| 68 | - -- memory | |
| 69 | - local status_memory = NuiLine() | |
| 70 | - if item.status_memory == "0B" then item.status_memory = "0 MB" end | |
| 71 | - local s_mem = vim.split(item.status_memory, " ") | |
| 72 | - status_memory:append(s_mem[1] .. " ") | |
| 73 | - status_memory:append(s_mem[2], "leetcode_alt") | |
| 74 | - | |
| 75 | - local perc_mem = NuiLine() | |
| 76 | - perc_mem:append( | |
| 77 | - ("%s %.2f%% "):format(t("Beats"), item.memory_percentile), | |
| 78 | - perc_hi(item.memory_percentile) | |
| 79 | - ) | |
| 80 | - if config.is_cn then | |
| 81 | - perc_mem:append("使用 ") | |
| 82 | - perc_mem:append(item.pretty_lang, "Structure") | |
| 83 | - perc_mem:append(" 的用户") | |
| 84 | - else | |
| 85 | - perc_mem:append("of users with ") | |
| 86 | - perc_mem:append(item.pretty_lang, "Structure") | |
| 87 | - end | |
| 88 | - | |
| 89 | - local memory = Pre:init(NuiText((" %s"):format(t("Memory")), "leetcode_normal"), { | |
| 90 | - status_memory, | |
| 91 | - perc_mem, | |
| 92 | - }) | |
| 93 | - group:append(memory) | |
| 94 | - | |
| 95 | - self.layout:append(group) | |
| 96 | -end | |
| 97 | - | |
| 98 | ----@private | |
| 99 | ---- | |
| 100 | ----@param item lc.runtime | |
| 101 | -function result:handle_runtime(item) -- status code = 10 | |
| 102 | - if item._.submission then return self:handle_accepted(item) end | |
| 103 | - | |
| 104 | - local group = Group:init({}, { spacing = 1 }) | |
| 105 | - local header = Text:init({}) | |
| 106 | - | |
| 107 | - local h = NuiLine() | |
| 108 | - h:append(item._.title, item._.hl) | |
| 109 | - h:append(" | ") | |
| 110 | - h:append(("%s: %s"):format(t("Runtime"), item.status_runtime), "leetcode_alt") | |
| 111 | - header:append(h) | |
| 112 | - group:append(header) | |
| 113 | - | |
| 114 | - for i, answer in ipairs(item.code_answer) do | |
| 115 | - local passed = item.compare_result:sub(i, i) == "1" | |
| 116 | - | |
| 117 | - local case = Case:init( | |
| 118 | - i, | |
| 119 | - self.parent.testcase.testcases[i], | |
| 120 | - answer, | |
| 121 | - item.expected_code_answer[i], | |
| 122 | - passed | |
| 123 | - ) | |
| 124 | - group:append(case) | |
| 125 | - | |
| 126 | - local stdout = Stdout:init(item.std_output_list[i]) | |
| 127 | - if stdout then group:append(stdout) end | |
| 128 | - end | |
| 129 | - | |
| 130 | - self.layout:append(group) | |
| 131 | -end | |
| 132 | - | |
| 133 | ----@private | |
| 134 | ---- | |
| 135 | ----@param item lc.submission | |
| 136 | -function result:handle_submission_error(item) -- status code = 11 | |
| 137 | - local group = Group:init({}, { spacing = 1 }) | |
| 138 | - | |
| 139 | - local header = NuiLine() | |
| 140 | - header:append(item._.title, item._.hl) | |
| 141 | - header:append(" | ") | |
| 142 | - header:append(testcases_passed(item), "leetcode_alt") | |
| 143 | - group:append(Text:init({ header })) | |
| 144 | - | |
| 145 | - local text = Case:init( | |
| 146 | - item.total_correct + 1, | |
| 147 | - item.input_formatted, | |
| 148 | - item.code_output, | |
| 149 | - item.expected_output | |
| 150 | - ) | |
| 151 | - group:append(text) | |
| 152 | - | |
| 153 | - if item.std_output then | |
| 154 | - local stdout = Stdout:init(item.std_output) | |
| 155 | - if stdout then group:append(stdout) end | |
| 156 | - end | |
| 157 | - | |
| 158 | - self.layout:append(group) | |
| 159 | -end | |
| 160 | - | |
| 161 | ----@private | |
| 162 | ---- | |
| 163 | ----@param item lc.limit_exceeded_error | |
| 164 | -function result:handle_limit_exceeded(item) -- status code = 12,13,14 | |
| 165 | - local group = Group:init({}, { spacing = 1 }) | |
| 166 | - | |
| 167 | - local header = NuiLine() | |
| 168 | - header:append(item._.title, item._.hl) | |
| 169 | - if item._.submission then | |
| 170 | - header:append(" | ") | |
| 171 | - header:append(testcases_passed(item), "leetcode_alt") | |
| 172 | - end | |
| 173 | - group:append(Text:init({ header })) | |
| 174 | - | |
| 175 | - if item._.submission then | |
| 176 | - local last_testcase = NuiLine() | |
| 177 | - last_testcase:append(item.last_testcase:gsub("\n", " "), "leetcode_indent") | |
| 178 | - | |
| 179 | - local pre_header = NuiLine() | |
| 180 | - pre_header:append((" %s"):format(t("Last Executed Input")), "leetcode_normal") | |
| 181 | - | |
| 182 | - local last_exec = Pre:init(pre_header, { last_testcase }) | |
| 183 | - group:append(last_exec) | |
| 184 | - | |
| 185 | - if item.std_output ~= "" then | |
| 186 | - local stdout = Stdout:init(item.std_output) | |
| 187 | - if stdout then group:append(stdout) end | |
| 188 | - end | |
| 189 | - elseif item.std_output_list[1] ~= "" then | |
| 190 | - local stdout = Stdout:init(item.std_output_list[1]) | |
| 191 | - if stdout then group:append(stdout) end | |
| 192 | - end | |
| 193 | - | |
| 194 | - self.layout:append(group) | |
| 195 | -end | |
| 196 | - | |
| 197 | ----@private | |
| 198 | ---- | |
| 199 | ----@param item lc.runtime_error | |
| 200 | -function result:handle_runtime_error(item) -- status code = 15 | |
| 201 | - local group = Group:init({}, { spacing = 1 }) | |
| 202 | - | |
| 203 | - local header = NuiLine() | |
| 204 | - header:append(item._.title, item._.hl) | |
| 205 | - | |
| 206 | - if item._.submission then | |
| 207 | - header:append(" | ") | |
| 208 | - header:append(testcases_passed(item), "leetcode_alt") | |
| 209 | - end | |
| 210 | - | |
| 211 | - local tbl = {} | |
| 212 | - for line in vim.gsplit(item.full_runtime_error, "\n") do | |
| 213 | - table.insert(tbl, NuiLine():append(line, "leetcode_error")) | |
| 214 | - end | |
| 215 | - group:append(Pre:init(header, tbl)) | |
| 216 | - | |
| 217 | - if item._.submission then | |
| 218 | - local pre_header = NuiLine() | |
| 219 | - pre_header:append((" %s"):format(t("Last Executed Input")), "leetcode_normal") | |
| 220 | - | |
| 221 | - local last_testcase = NuiLine() | |
| 222 | - last_testcase:append(item.last_testcase:gsub("\n", " "), "leetcode_indent") | |
| 223 | - | |
| 224 | - local last_exec = Pre:init(pre_header, { last_testcase }) | |
| 225 | - group:append(last_exec) | |
| 226 | - else | |
| 227 | - local stdout = Stdout:init(item.std_output_list[1]) | |
| 228 | - if stdout then group:append(stdout) end | |
| 229 | - end | |
| 230 | - | |
| 231 | - self.layout:append(group) | |
| 232 | -end | |
| 233 | - | |
| 234 | 28 | function result:focus() |
| 235 | 29 | if not vim.api.nvim_win_is_valid(self.popup.winid) then return end |
| 236 | 30 | vim.api.nvim_set_current_win(self.popup.winid) |
| 237 | 31 | end |
| 238 | 32 | |
| 239 | -function result:handle_internal_error(item) -- status code = 16 | |
| 240 | - local group = Group:init({}, { spacing = 1 }) | |
| 241 | - | |
| 242 | - local header = NuiLine() | |
| 243 | - header:append(item._.title, item._.hl) | |
| 244 | - local text = Text:init({ header }) | |
| 245 | - group:append(text) | |
| 246 | - | |
| 247 | - self.layout:append(group) | |
| 248 | -end | |
| 249 | - | |
| 250 | ----@private | |
| 251 | ---- | |
| 252 | ----@param item lc.compile_error | |
| 253 | -function result:handle_compile_error(item) -- status code = 20 | |
| 254 | - local group = Group:init({}, { spacing = 1 }) | |
| 255 | - | |
| 256 | - local header = NuiLine() | |
| 257 | - header:append(item._.title, item._.hl) | |
| 258 | - if item._.submission then | |
| 259 | - header:append(" | ") | |
| 260 | - header:append(testcases_passed(item), "leetcode_alt") | |
| 261 | - end | |
| 262 | - | |
| 263 | - local tbl = {} | |
| 264 | - for line in vim.gsplit(item.full_compile_error, "\n") do | |
| 265 | - table.insert(tbl, NuiLine():append(line, "leetcode_error")) | |
| 266 | - end | |
| 267 | - | |
| 268 | - group:append(Pre:init(header, tbl)) | |
| 269 | - self.layout:append(group) | |
| 270 | -end | |
| 271 | - | |
| 272 | 33 | ---@param item lc.interpreter_response |
| 273 | 34 | --- |
| 274 | 35 | ---@return lc.interpreter_response |
| @@ -300,49 +61,7 @@ function result:handle(item) | ||
| 300 | 61 | self.layout:clear() |
| 301 | 62 | item = self:handle_item(item) |
| 302 | 63 | self:set_popup_border_hi(item._.hl) |
| 303 | - | |
| 304 | - local handlers = { | |
| 305 | - -- runtime | |
| 306 | - [10] = function() | |
| 307 | - self:handle_runtime(item --[[@as lc.runtime]]) | |
| 308 | - end, | |
| 309 | - [11] = function() | |
| 310 | - self:handle_submission_error(item --[[@as lc.submission]]) | |
| 311 | - end, | |
| 312 | - | |
| 313 | - -- memory limit | |
| 314 | - [12] = function() | |
| 315 | - self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 316 | - end, | |
| 317 | - -- time limit | |
| 318 | - [13] = function() | |
| 319 | - self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 320 | - end, | |
| 321 | - -- output limit | |
| 322 | - [14] = function() | |
| 323 | - self:handle_limit_exceeded(item --[[@as lc.limit_exceeded_error]]) | |
| 324 | - end, | |
| 325 | - | |
| 326 | - -- runtime error | |
| 327 | - [15] = function() | |
| 328 | - self:handle_runtime_error(item --[[@as lc.runtime_error]]) | |
| 329 | - end, | |
| 330 | - | |
| 331 | - -- internal error | |
| 332 | - [16] = function() | |
| 333 | - self:handle_internal_error(item --[[@as lc.internal_error]]) | |
| 334 | - end, | |
| 335 | - | |
| 336 | - -- compiler | |
| 337 | - [20] = function() | |
| 338 | - self:handle_compile_error(item --[[@as lc.compile_error]]) | |
| 339 | - end, | |
| 340 | - | |
| 341 | - -- unknown | |
| 342 | - ["unknown"] = function() log.error("unknown runner status code: " .. item.status_code) end, | |
| 343 | - }; | |
| 344 | - | |
| 345 | - (handlers[item.status_code] or handlers["unknown"])() | |
| 64 | + self.layout:handle_res(item) | |
| 346 | 65 | |
| 347 | 66 | if item._.submission then |
| 348 | 67 | local status = item.status_code == 10 and "ac" or "notac" |
| @@ -398,7 +117,11 @@ function result:init(parent) | ||
| 398 | 117 | |
| 399 | 118 | self = setmetatable({ |
| 400 | 119 | popup = popup, |
| 401 | - layout = Layout:init({}, { | |
| 120 | + -- layout = Layout:init({}, { | |
| 121 | + -- winid = popup.winid, | |
| 122 | + -- bufnr = popup.bufnr, | |
| 123 | + -- }), | |
| 124 | + layout = ResultLayout:init(parent, { | |
| 402 | 125 | winid = popup.winid, |
| 403 | 126 | bufnr = popup.bufnr, |
| 404 | 127 | }), |