Skip to content

Add in-place question shuffle feature (no more buffer clutter!) #188

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
12 changes: 7 additions & 5 deletions lua/leetcode-ui/layout/console.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
60 changes: 60 additions & 0 deletions lua/leetcode-ui/question.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 32 additions & 9 deletions lua/leetcode/command/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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 },
Expand Down