diff --git a/lua/leetcode-ui/layout/console.lua b/lua/leetcode-ui/layout/console.lua index b7e88c0..f1619d2 100644 --- a/lua/leetcode-ui/layout/console.lua +++ b/lua/leetcode-ui/layout/console.lua @@ -17,11 +17,13 @@ local log = require("leetcode.logger") local ConsoleLayout = Layout:extend("LeetConsoleLayout") function ConsoleLayout:unmount() -- - ConsoleLayout.super.unmount(self) - - self.testcase = Testcase(self) - self.result = Result(self) - self.popups = { self.testcase, self.result } + if self.testcase and self.testcase.unmount then + self.testcase:unmount() + end + if self.result and self.result.unmount then + self.result:unmount() + end + ConsoleLayout.super.unmount(self) end function ConsoleLayout:hide() diff --git a/lua/leetcode-ui/question.lua b/lua/leetcode-ui/question.lua index a72ccaf..22623da 100644 --- a/lua/leetcode-ui/question.lua +++ b/lua/leetcode-ui/question.lua @@ -456,6 +456,66 @@ function Question:init(problem) self.lang = config.lang end +-- start of shuffle functionality. + +function Question:shuffle() + local api_question = require("leetcode.api.question") + local problems = require("leetcode.cache.problemlist") + + -- Fetch a new random question (minimal info) + local q, err = api_question.random() + if err then + log.err(err) + return + end + + -- Get full question info by title_slug + local full_q = api_question.by_title_slug(q.title_slug) + if not full_q then + return log.err("Failed to fetch full question info for: " .. (q.title_slug or "nil")) + end + + -- Update self fields + self.q = full_q + self.cache = problems.get_by_title_slug(q.title_slug) or {} + + -- Detach LSP clients before renaming the buffer to prevent errors. + -- The clients will be re-attached later. + local clients = vim.lsp.get_active_clients({ bufnr = self.bufnr }) + if #clients > 0 then + for _, client in ipairs(clients) do + vim.lsp.stop_client(client.id) + end + end + + -- Update buffer path and name. This also writes the file if it doesn't exist. + local new_path, existed = self:path() + vim.api.nvim_buf_set_name(self.bufnr, new_path) + + -- Overwrite buffer contents with new snippet and apply settings + self:editor_reset() + self:open_buffer(existed) + + -- Unmount old UI components. The console unmount was fixed to prevent leaks. + self.description:unmount() + self.info:unmount() + self.console:unmount() + + -- Recreate UI components for the new question + self.description = Description(self):mount() + self.console = Console(self) + self.info = Info(self) + + -- Re-attach LSP clients by triggering the autocommands that lspconfig uses. + if #clients > 0 then + vim.cmd.doautocmd("BufRead") + end + + log.info("Shuffled to a new random question: " .. (full_q.title or q.title_slug or "unknown")) +end + +-- end of shuffle functionality. + ---@type fun(question: lc.cache.Question): lc.ui.Question local LeetQuestion = Question ---@diagnostic disable-line: assign-type-mismatch diff --git a/lua/leetcode/command/init.lua b/lua/leetcode/command/init.lua index 1182445..cab463e 100644 --- a/lua/leetcode/command/init.lua +++ b/lua/leetcode/command/init.lua @@ -277,6 +277,14 @@ function cmd.q_submit() end end +function cmd.q_shuffle() + local utils = require("leetcode.utils") + local q = utils.curr_question() + if q then + q:shuffle() + end +end + function cmd.ui_skills() if config.is_cn then return @@ -566,25 +574,39 @@ function cmd.rec_complete(args, options, cmds) end function cmd.exec(args) + local input_args = vim.split(args.args, "%s+", { trimempty = true }) local cmds = cmd.commands + local arg_idx = 1 + + -- Find the command by consuming parts of the input + while arg_idx <= #input_args do + local current_part = input_args[arg_idx]:lower() + if not string.find(current_part, "=") and cmds[current_part] then + cmds = cmds[current_part] + arg_idx = arg_idx + 1 + else + -- Stop when we hit an option (like status=ac) or an unknown subcommand + break + end + end + -- The rest are options local options = vim.empty_dict() - for s in vim.gsplit(args.args:lower(), "%s+", { trimempty = true }) do - local opt = vim.split(s, "=") - + for i = arg_idx, #input_args do + local opt = vim.split(input_args[i], "=") if opt[2] then - options[opt[1]] = vim.split(opt[2], ",", { trimempty = true }) - elseif cmds then - cmds = cmds[s] + options[opt[1]:lower()] = vim.split(opt[2], ",", { trimempty = true }) else - break + -- This part is not a valid option, and wasn't a valid subcommand + return log.error(("Invalid command or argument: `%s`"):format(input_args[i])) end end if cmds and type(cmds[1]) == "function" then - cmds[1](options) ---@diagnostic disable-line + cmds[1](options) else - log.error(("Invalid command: `%s %s`"):format(args.name, args.args)) + -- This happens if the command was empty or only contained unknown subcommands + return log.error(("Invalid command: `:Leet %s`"):format(args.args)) end end @@ -611,6 +633,7 @@ cmd.commands = { run = { cmd.q_run }, test = { cmd.q_run }, submit = { cmd.q_submit }, + shuffle = { cmd.q_shuffle }, daily = { cmd.qot }, yank = { cmd.yank }, open = { cmd.open },