A command-driven LeetCode client for Neovim. The whole interface lives in one
central window with Description, Code, Console, and Info tabs. Statistics use
the same window. See :h leetcode-ui for the full documentation.
- Browse problems with
mini.pickorvim.ui.select(). - Read parsed descriptions, with hints hidden by default to avoid spoilers.
- Edit solutions locally; run and submit through the LeetCode API.
- Persist code drafts and custom testcases per question.
- Format code through the LeetCode API or a user-provided formatter.
- Show progress statistics and current session information.
- Show inline progress while the problem list loads.
- Render Description, Info, Statistics, and Help as Markdown when
render-markdown.nvimis loaded and enabled. - Optional browser cookie import, disabled by default (see the caution below).
- No plugin-defined keymaps: map only the commands you want.
vim.pack (Neovim 0.12+)
vim.pack.add({
{ src = "https://github.com/231tr0n/leetcode-ui.nvim" },
-- Optional, for the mini.pick picker backend:
-- { src = "https://github.com/echasnovski/mini.nvim" },
-- Optional, for Markdown rendering:
-- { src = "https://github.com/MeanderingProgrammer/render-markdown.nvim" },
})
-- require('mini.pick').setup() -- optional; enables the mini.pick backend
-- require('render-markdown').setup({}) -- optional; renders Markdown views
require("leetcode-ui").setup({})Works with any plugin manager that can clone Git repositories. mini.pick and
render-markdown.nvim are optional.
:LeetCode list
:LeetCode list Hard
:LeetCode daily
:LeetCode random Easy
:LeetCode open two-sum
:LeetCode stats
:LeetCode session
:LeetCode refresh
:LeetCode help:LeetCode and :LeetCode list both open the problem list. See
:LeetCode help or :h LeetCode for the full list. Map the
commands you use:
vim.keymap.set('n', '<leader>ll', '<cmd>LeetCode list<cr>', { desc = 'LeetCode: list' })
vim.keymap.set('n', '<leader>lr', '<cmd>LeetCode question run<cr>', { desc = 'LeetCode: run' })
vim.keymap.set('n', '<leader>ls', '<cmd>LeetCode question submit<cr>', { desc = 'LeetCode: submit' })
vim.keymap.set('n', '<leader>lx', '<cmd>LeetCode question close<cr>', { desc = 'LeetCode: close' })Question-window actions live under :LeetCode question:
:LeetCode question description
:LeetCode question code
:LeetCode question console
:LeetCode question info
:LeetCode question next
:LeetCode question prev
:LeetCode question hints show
:LeetCode question hints hide
:LeetCode question run 2
:LeetCode question submit
:LeetCode question reset
:LeetCode question format
:LeetCode question language golang
:LeetCode question case add [2,7,11,15]\n9
:LeetCode question case edit 2 [0]\n0
:LeetCode question case delete 2
:LeetCode question case next
:LeetCode question case prev
:LeetCode question case reset
:LeetCode question closeFormatting is exposed through one optional function:
require('leetcode-ui').setup({
formatter = function(text, filetype)
-- Return formatted text, or nil to leave the built-in behavior.
end,
})The same function handles every formatting path. It receives text and
filetype:
markdown— generated Markdown for Description, Info, Statistics, and Help.html— raw Description and hint HTML before conversion.- A solution filetype such as
python,go, orjavascript— code editor.
Return a formatted string, or nil:
- Markdown/HTML keep their original text.
- Code falls back to the LeetCode API. Built-in remote formatting supports
C, C++, Go, Java, Python 3, and Swift (
c,cpp,golang,java,python3,swift). Other languages require a user-provided formatter.
The plugin does not run local CLI formatters or Neovim LSP clients. Code uses
LeetCode's remote formatter for the languages it supports; other languages
require a user-provided formatter. There is no prettier option.
The only formatting command is:
:LeetCode question formatIt formats the current code editor. Read-only Markdown/HTML is formatted automatically whenever it is rendered.
Use Prettier for Markdown and HTML, and leave code to the LeetCode API:
require('leetcode-ui').setup({
formatter = function(text, filetype)
if filetype == 'markdown' or filetype == 'html' then
local file = filetype == 'html' and 'x.html' or 'x.md'
local out = vim.fn.system({ 'prettier', '--stdin-filepath', file }, text)
if vim.v.shell_error == 0 then return out end
end
return nil
end,
})Use a language-specific command for code:
require('leetcode-ui').setup({
formatter = function(text, filetype)
local command
if filetype == 'python' then
command = { 'ruff', 'format', '-' }
elseif filetype == 'javascript' or filetype == 'typescript' then
local ext = filetype == 'typescript' and 'ts' or 'js'
command = { 'prettier', '--stdin-filepath', 'x.' .. ext }
end
if command == nil then return nil end -- use the LeetCode API
local out = vim.fn.system(command, text)
if vim.v.shell_error == 0 then return out end
return nil
end,
})Use a custom Markdown formatter while leaving other filetypes to the defaults:
require('leetcode-ui').setup({
formatter = function(text, filetype)
if filetype == 'markdown' then return my_markdown_formatter(text) end
return nil
end,
})require('leetcode-ui').setup({
lang = 'python3',
picker = 'auto', -- 'auto', 'mini', or 'builtin'
notifications = true,
show_hints = false,
markdown = true, -- Markdown Description/Info/Stats/Help when render-markdown.nvim is loaded and enabled
cooldown = 1440, -- problem-list cache lifetime in minutes (one day)
-- window = {
-- config = { width = 100, height = 30, border = 'rounded' },
-- -- or: config = function() return { width = 100, height = 30 } end
-- },
-- formatter = function(text, filetype) return nil end,
-- cookies = { LEETCODE_SESSION = '...', csrftoken = '...' },
-- cookie_file = '~/.config/leetcode/cookies.txt',
-- browser = 'auto', -- opt in to browser cookie import
})See :h leetcode-ui-config for every option and the default configuration.
When cookie_file is unset, cookies resolve from cookies, then the
LEETCODE_SESSION and CSRFTOKEN environment variables, then supported
browsers when browser import is enabled. With picker = 'auto',
mini.pick is used when MiniPick is already set up; otherwise the picker is
vim.ui.select(). Set picker = 'mini' to explicitly prefer mini.pick
(with a builtin fallback and warning when unavailable).
Browser import is disabled by default. When enabled, the plugin reads LeetCode
cookies from your browser's on-disk database and may decrypt them with system
keyring tools and OpenSSL. These cookies grant access to your LeetCode
account; prefer cookie_file or environment variables when possible.
Linux is the tested Chromium decryption path; macOS uses the Keychain and
PBKDF2 and is covered by mocked unit tests, not a native macOS CI runner.
Encrypted Chromium cookies on Windows are not supported.
Development dependencies: Git, Neovim, StyLua, Luacheck, LuaLS,
Node/Prettier, curl, OpenSSL, and mini.nvim. Browser fixture tests also
require python3 with sqlite3. Common tasks are exposed through the
Makefile:
make bootstrap-mini # clone mini.nvim if missing
make deps # report missing tools
make test # run the full test suite
make test-file FILE=tests/test_commands.lua
make format # format Lua and other supported files
make check # formatting, lint, tests, and docs checks
make docs # regenerate help text and tagsRuntime:
- Neovim stable (0.12 or newer;
vim.system,vim.uv). curl— required for all LeetCode API and remote formatting requests.python3— required only when browser cookie import is enabled (browser = 'auto'or a specific browser); SQLite-backed browsers also need thesqlite3module.openssl— optional; required for supported Chromium-familyv10andv11cookie decryption.secret-toolon Linux orsecurityon macOS — optional; used for Chromium-family keyring-protected cookies (Linuxv11, macOSv10/v11).mini.nvim— optional; runrequire('mini.pick').setup()to use it, and setpicker = 'mini'to prefer it explicitly.mini.cluetriggers are enabled inside the central window when it is set up.render-markdown.nvim— optional; renders Markdown views in the central window when it is loaded and enabled. It requires themarkdownandmarkdown_inlinetreesitter parsers.- A language server such as
lua-language-server,clangd, orruff— optional; provides diagnostics and completion.
No LuaRocks or external Lua packages are required.
MIT