A Neovim plugin enabling you to solve LeetCode problems.
feat: neoconf.nvim
17 files changed, +355 -89
+1-0.gitignore
| @@ -0,0 +1 @@ | ||
| 1 | +/.luarc.json |
+3-13.luarc.json
| @@ -1,20 +1,10 @@ | ||
| 1 | 1 | { |
| 2 | - "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", | |
| 3 | - "diagnostics": { | |
| 4 | - "globals": [ | |
| 5 | - "vim" | |
| 6 | - ] | |
| 7 | - }, | |
| 8 | - "format": { | |
| 9 | - "enable": false | |
| 10 | - }, | |
| 11 | - "runtime": { | |
| 12 | - "version": "LuaJIT" | |
| 13 | - }, | |
| 14 | 2 | "workspace.library": [ |
| 15 | 3 | "/home/kawre/.local/share/nvim/lazy/neodev.nvim/types/stable", |
| 16 | 4 | "/usr/share/nvim/runtime/lua", |
| 17 | 5 | "/home/kawre/projects/leetcode.nvim/lua", |
| 6 | + "/home/kawre/.local/share/nvim/lazy/neoconf.nvim/types", | |
| 7 | + "/home/kawre/.local/share/nvim/lazy/neoconf.nvim/types/lua", | |
| 18 | 8 | "${3rd}/luv/library" |
| 19 | 9 | ] |
| 20 | -} | |
| 10 | +} |
+25-0.neoconf.json
| @@ -0,0 +1,25 @@ | ||
| 1 | +{ | |
| 2 | + "neodev": { | |
| 3 | + "library": { | |
| 4 | + "enabled": true, | |
| 5 | + "plugins": [ | |
| 6 | + "telescope", | |
| 7 | + "notify", | |
| 8 | + "plenary", | |
| 9 | + "nui" | |
| 10 | + ] | |
| 11 | + } | |
| 12 | + }, | |
| 13 | + "neoconf": { | |
| 14 | + "plugins": { | |
| 15 | + "lua_ls": { | |
| 16 | + "enabled": true | |
| 17 | + } | |
| 18 | + } | |
| 19 | + }, | |
| 20 | + "lspconfig": { | |
| 21 | + "lua_ls": { | |
| 22 | + "Lua.completion.callSnippet": "Replace" | |
| 23 | + } | |
| 24 | + } | |
| 25 | +} |
+4-0lua/leetcode.lua
| @@ -7,6 +7,10 @@ local M = {} | ||
| 7 | 7 | function M.setup(cfg) |
| 8 | 8 | config.apply(cfg or {}) |
| 9 | 9 | |
| 10 | + vim.cmd([[highlight LcProblemEasy guifg=##00b8a3]]) | |
| 11 | + vim.cmd([[highlight LcProblemMedium guifg=#ffb800]]) | |
| 12 | + vim.cmd([[highlight LcProblemHard guifg=#ef4743]]) | |
| 13 | + | |
| 10 | 14 | vim.api.nvim_create_autocmd("VimEnter", { |
| 11 | 15 | pattern = "*", |
| 12 | 16 | nested = true, |
+31-5lua/leetcode/api/command/init.lua
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | local cache = require("leetcode.cache") |
| 2 | -local logger = require("leetcode.logger") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | 3 | local config = require("leetcode.config") |
| 4 | 4 | |
| 5 | 5 | local ui = require("leetcode.ui") |
| @@ -29,8 +29,8 @@ function M.lc_problems() | ||
| 29 | 29 | |
| 30 | 30 | if not file:exists() then |
| 31 | 31 | local editor_data = gql.question.editor_data(problem.title_slug) |
| 32 | - local code | |
| 33 | 32 | |
| 33 | + local code | |
| 34 | 34 | if editor_data.code_snippets ~= vim.NIL then |
| 35 | 35 | for _, v in ipairs(editor_data.code_snippets) do |
| 36 | 36 | if v.langSlug == config.user.lang or v.langSlug == config.user.sql then |
| @@ -39,20 +39,46 @@ function M.lc_problems() | ||
| 39 | 39 | end |
| 40 | 40 | end |
| 41 | 41 | end |
| 42 | - assert(code, "Failed to fetch code snippet") | |
| 43 | 42 | |
| 44 | - file:write(code, "w") | |
| 43 | + if not code then | |
| 44 | + log.error("failed to fetch code snippet") | |
| 45 | + else | |
| 46 | + file:write(code, "w") | |
| 47 | + end | |
| 45 | 48 | end |
| 46 | 49 | |
| 47 | 50 | vim.cmd("cd " .. dir) |
| 48 | 51 | vim.cmd("e " .. file:absolute()) |
| 52 | + | |
| 53 | + ui.create_leetcode_win(problem) | |
| 49 | 54 | end |
| 50 | 55 | ) |
| 51 | 56 | end |
| 52 | 57 | |
| 53 | 58 | function M.leetcode() |
| 59 | + M.authenticate() | |
| 54 | 60 | dashboard.setup() |
| 55 | - vim.cmd("Alpha | bd #") | |
| 61 | + vim.cmd("Alpha | bd#") | |
| 56 | 62 | end |
| 57 | 63 | |
| 64 | +function M.cookie_prompt() | |
| 65 | + local cookie = require("leetcode.cache.cookie") | |
| 66 | + | |
| 67 | + ui.input( | |
| 68 | + "Enter cookie", | |
| 69 | + ---@param cookie_str string | |
| 70 | + function(cookie_str) | |
| 71 | + if not cookie_str then return end | |
| 72 | + | |
| 73 | + cookie.new(cookie_str) | |
| 74 | + require("leetcode.ui.dashboard").update() | |
| 75 | + end | |
| 76 | + ) | |
| 77 | +end | |
| 78 | + | |
| 79 | +---Merge configurations into default configurations and set it as user configurations. | |
| 80 | +--- | |
| 81 | +---@return lc.UserStatus | nil | |
| 82 | +function M.authenticate() gql.auth.user_status() end | |
| 83 | + | |
| 58 | 84 | return M |
+14-1lua/leetcode/config/init.lua
| @@ -16,9 +16,22 @@ Config.default = template | ||
| 16 | 16 | ---@type lc.Config |
| 17 | 17 | Config.user = Config.default |
| 18 | 18 | |
| 19 | +---@type lc.UserStatus|nil | |
| 20 | +Config.auth = nil | |
| 21 | + | |
| 19 | 22 | ---Merge configurations into default configurations and set it as user configurations. |
| 20 | 23 | --- |
| 21 | 24 | ---@param cfg lc.Config Configurations to be merged. |
| 22 | -function Config.apply(cfg) Config.user = vim.tbl_deep_extend("force", template, cfg) end | |
| 25 | +function Config.apply(cfg) | |
| 26 | + Config.user = vim.tbl_deep_extend("force", template, cfg) | |
| 27 | + | |
| 28 | + local ok, notify = pcall(require, "notify") | |
| 29 | + if ok then vim.notify = notify end | |
| 30 | +end | |
| 31 | + | |
| 32 | +---Merge configurations into default configurations and set it as user configurations. | |
| 33 | +--- | |
| 34 | +---@param status lc.UserStatus | nil | |
| 35 | +function Config.authenticate(status) Config.auth = status end | |
| 23 | 36 | |
| 24 | 37 | return Config |
+4-0lua/leetcode/config/template.lua
| @@ -16,7 +16,11 @@ local M = { | ||
| 16 | 16 | ---| "oraclesql" |
| 17 | 17 | sql = "mysql", |
| 18 | 18 | |
| 19 | + ---@type string | |
| 19 | 20 | directory = vim.fn.stdpath("data") .. "/leetcode", |
| 21 | + | |
| 22 | + ---@type boolean | |
| 23 | + logging = true, | |
| 20 | 24 | } |
| 21 | 25 | |
| 22 | 26 | return M |
+13-5lua/leetcode/graphql/auth.lua
| @@ -1,22 +1,27 @@ | ||
| 1 | 1 | local gql = require("leetcode.graphql.utils") |
| 2 | +local config = require("leetcode.config") | |
| 2 | 3 | |
| 3 | 4 | ---@class lc.AuthApi |
| 4 | 5 | local M = {} |
| 5 | 6 | |
| 6 | ----@return table | nil | |
| 7 | +---@class lc.UserStatus | |
| 8 | +---@field username string | |
| 9 | +---@field is_signed_in boolean | |
| 10 | +---@field is_premium boolean | |
| 11 | +---@field user_id integer | |
| 12 | + | |
| 13 | +---@return lc.UserStatus | nil | |
| 7 | 14 | function M.user_status() |
| 8 | 15 | local query = [[ |
| 9 | 16 | query globalData { |
| 10 | 17 | userStatus { |
| 11 | 18 | userId |
| 12 | - isSignedIn | |
| 19 | + is_signed_in: isSignedIn | |
| 13 | 20 | isMockUser |
| 14 | - isPremium | |
| 21 | + is_premium: isPremium | |
| 15 | 22 | isVerified |
| 16 | 23 | username |
| 17 | - isTranslator | |
| 18 | 24 | activeSessionId |
| 19 | - checkedInToday | |
| 20 | 25 | } |
| 21 | 26 | } |
| 22 | 27 | ]] |
| @@ -24,6 +29,9 @@ function M.user_status() | ||
| 24 | 29 | local ok, res = pcall(gql.query, query) |
| 25 | 30 | if not ok then return end |
| 26 | 31 | |
| 32 | + local status = res["userStatus"] | |
| 33 | + config.authenticate(status) | |
| 34 | + | |
| 27 | 35 | return res["userStatus"] |
| 28 | 36 | end |
| 29 | 37 |
+33-5lua/leetcode/graphql/question.lua
| @@ -1,10 +1,15 @@ | ||
| 1 | 1 | local gql = require("leetcode.graphql.utils") |
| 2 | -local logger = require("leetcode.logger") | |
| 2 | +local log = require("leetcode.logger") | |
| 3 | 3 | |
| 4 | 4 | local M = {} |
| 5 | 5 | |
| 6 | +---@class lc.Question.Content | |
| 7 | +---@field content string | |
| 8 | + | |
| 6 | 9 | ---@param title_slug string |
| 7 | -function M.get_by_title_slug(title_slug) | |
| 10 | +--- | |
| 11 | +---@return lc.Question.Content | |
| 12 | +function M.content(title_slug) | |
| 8 | 13 | local variables = { |
| 9 | 14 | titleSlug = title_slug, |
| 10 | 15 | } |
| @@ -22,9 +27,7 @@ function M.get_by_title_slug(title_slug) | ||
| 22 | 27 | local ok, res = pcall(gql.query, query, variables) |
| 23 | 28 | assert(ok) |
| 24 | 29 | |
| 25 | - logger.inspect(res) | |
| 26 | - | |
| 27 | - return res["questionContent"] | |
| 30 | + return res["question"] | |
| 28 | 31 | end |
| 29 | 32 | |
| 30 | 33 | ---@param title_slug string |
| @@ -57,4 +60,29 @@ function M.editor_data(title_slug) | ||
| 57 | 60 | return res["question"] |
| 58 | 61 | end |
| 59 | 62 | |
| 63 | +---@param title_slug string | |
| 64 | +function M.title(title_slug) | |
| 65 | + local variables = { | |
| 66 | + titleSlug = title_slug, | |
| 67 | + } | |
| 68 | + | |
| 69 | + local query = [[ | |
| 70 | + query questionTitle($titleSlug: String!) { | |
| 71 | + question(titleSlug: $titleSlug) { | |
| 72 | + questionId | |
| 73 | + questionFrontendId | |
| 74 | + title | |
| 75 | + titleSlug | |
| 76 | + isPaidOnly | |
| 77 | + difficulty | |
| 78 | + likes | |
| 79 | + dislikes | |
| 80 | + categoryTitle | |
| 81 | + } | |
| 82 | + } | |
| 83 | + ]] | |
| 84 | + | |
| 85 | + return gql.query(query, variables)["question"] | |
| 86 | +end | |
| 87 | + | |
| 60 | 88 | return M |
+1-1lua/leetcode/graphql/utils.lua
| @@ -22,7 +22,7 @@ function M.query(query, variables) | ||
| 22 | 22 | }) |
| 23 | 23 | |
| 24 | 24 | local ok, data = pcall(vim.json.decode, response["body"]) |
| 25 | - assert(ok and data and data["data"]) | |
| 25 | + assert(ok and data and data["data"], "Failed to query data") | |
| 26 | 26 | |
| 27 | 27 | return data["data"] |
| 28 | 28 | end |
+14-7lua/leetcode/logger/init.lua
| @@ -1,25 +1,32 @@ | ||
| 1 | 1 | local config = require("leetcode.config") |
| 2 | +local levels = vim.log.levels | |
| 2 | 3 | |
| 3 | 4 | ---@class lc.Logger |
| 4 | 5 | local Logger = {} |
| 5 | 6 | |
| 6 | 7 | ---@param msg string |
| 7 | ----@param lvl? string | |
| 8 | -Logger.log = function(msg, lvl) require("notify")(config.name .. ": " .. msg, lvl or "info") end | |
| 8 | +---@param lvl? integer | |
| 9 | +Logger.log = function(msg, lvl) | |
| 10 | + if not config.user.logging then return end | |
| 11 | + vim.notify(msg, lvl or levels.INFO, { title = config.name }) | |
| 12 | +end | |
| 9 | 13 | |
| 10 | 14 | ---@param msg string |
| 11 | 15 | Logger.info = function(msg) Logger.log(msg) end |
| 12 | 16 | |
| 13 | 17 | ---@param msg string |
| 14 | -Logger.warn = function(msg) Logger.log(msg, "warn") end | |
| 15 | - | |
| 16 | ----@param msg string | |
| 17 | -Logger.debug = function(msg) Logger.log(msg, "debug") end | |
| 18 | +Logger.warn = function(msg) Logger.log(msg, levels.WARN) end | |
| 18 | 19 | |
| 19 | 20 | ---@param msg table |
| 20 | 21 | Logger.inspect = function(msg) Logger.log(vim.inspect(msg)) end |
| 21 | 22 | |
| 22 | 23 | ---@param msg string |
| 23 | -Logger.error = function(msg) Logger.log(msg, "error") end | |
| 24 | +Logger.error = function(msg) Logger.log(msg, levels.ERROR) end | |
| 25 | + | |
| 26 | +---@param msg string | |
| 27 | +Logger.debug = function(msg) | |
| 28 | + if not config.debug then return end | |
| 29 | + Logger.log(msg, levels.DEBUG) | |
| 30 | +end | |
| 24 | 31 | |
| 25 | 32 | return Logger |
+29-0lua/leetcode/theme/default.lua
| @@ -0,0 +1,29 @@ | ||
| 1 | +---Get highlight group value by name. | |
| 2 | +--- | |
| 3 | +---@param name string String for looking up highlight group. | |
| 4 | +---@return table | |
| 5 | +local function hl(name) | |
| 6 | + local highlight = vim.api.nvim_get_hl_by_name(name, true) | |
| 7 | + setmetatable(highlight, { | |
| 8 | + __index = function(self, key) | |
| 9 | + if key == "bg" then return self.background end | |
| 10 | + if key == "fg" then return self.foreground end | |
| 11 | + | |
| 12 | + return nil | |
| 13 | + end, | |
| 14 | + }) | |
| 15 | + | |
| 16 | + return highlight | |
| 17 | +end | |
| 18 | + | |
| 19 | +---@alias lc.Theme table<string, table> | |
| 20 | +---@type lc.Theme | |
| 21 | +local M = { | |
| 22 | + problem = { | |
| 23 | + easy = { fg = hl("LcProblemEasy").fg }, | |
| 24 | + medium = { fg = hl("LeetCodeProblemMedium").fg }, | |
| 25 | + hard = { fg = hl("LeetCodeProblemHard").fg }, | |
| 26 | + }, | |
| 27 | +} | |
| 28 | + | |
| 29 | +return M |
+6-0lua/leetcode/ui/components/init.lua
| @@ -0,0 +1,6 @@ | ||
| 1 | +---@class lc.Ui.Components | |
| 2 | +local Components = { | |
| 3 | + problem = require("leetcode.ui.components.problem"), | |
| 4 | +} | |
| 5 | + | |
| 6 | +return Components |
+108-0lua/leetcode/ui/components/problem.lua
| @@ -0,0 +1,108 @@ | ||
| 1 | +local log = require("leetcode.logger") | |
| 2 | + | |
| 3 | +local Text = require("nui.text") | |
| 4 | +local Line = require("nui.line") | |
| 5 | +local Layout = require("nui.layout") | |
| 6 | + | |
| 7 | +local utils = require("leetcode.utils") | |
| 8 | + | |
| 9 | +---@class lc.Ui.Components.Problem | |
| 10 | +local M = {} | |
| 11 | + | |
| 12 | +---@param title any | |
| 13 | +function M.title(title) | |
| 14 | + local line = Line() | |
| 15 | + | |
| 16 | + line:append(title.questionFrontendId .. ". " .. title.title) | |
| 17 | + | |
| 18 | + return line | |
| 19 | +end | |
| 20 | + | |
| 21 | +---@param title_slug string | |
| 22 | +function M.link(title_slug) | |
| 23 | + local line = Line() | |
| 24 | + | |
| 25 | + line:append("https://leetcode.com/problems/" .. title_slug .. "/", "Comment") | |
| 26 | + line:append("") | |
| 27 | + | |
| 28 | + return line | |
| 29 | +end | |
| 30 | + | |
| 31 | +---@param title any | |
| 32 | +function M.stats(title) | |
| 33 | + local line = Line() | |
| 34 | + | |
| 35 | + line:append( | |
| 36 | + title.difficulty, | |
| 37 | + title.difficulty == "Easy" and "DiagnosticOk" | |
| 38 | + or title.difficulty == "Medium" and "DiagnosticWarn" | |
| 39 | + or "DiagnosticError" | |
| 40 | + ) | |
| 41 | + line:append(" | ") | |
| 42 | + line:append(title.likes .. " ", "Comment") | |
| 43 | + line:append(title.dislikes .. " ", "Comment") | |
| 44 | + | |
| 45 | + return line | |
| 46 | +end | |
| 47 | + | |
| 48 | +---@param content any | |
| 49 | +function M.content(content) | |
| 50 | + local line = Line() | |
| 51 | + | |
| 52 | + local s = vim.split(content.content, "\n\n<p> </p>\n", { trimempty = true }) | |
| 53 | + | |
| 54 | + local desc = M.description(s[1]) | |
| 55 | + local exam = M.examples(s[2]) | |
| 56 | + local cons = M.constrains(s[3]) | |
| 57 | + local foll = M.follow_up(s[4]) | |
| 58 | + | |
| 59 | + line:append({ desc, exam, cons, foll }) | |
| 60 | + | |
| 61 | + return line | |
| 62 | +end | |
| 63 | + | |
| 64 | +---@param p string | |
| 65 | +local parse_paragraph = function(p) | |
| 66 | + -- for s in vim.gsplit(p, " ", { trimempty = true }) do | |
| 67 | + -- log.info(s) | |
| 68 | + -- end | |
| 69 | + | |
| 70 | + local ts = vim.treesitter | |
| 71 | + local parser = ts.get_string_parser(p, "html") | |
| 72 | + | |
| 73 | + local tree = parser:parse()[1] | |
| 74 | + local function get_text(node) | |
| 75 | + local text = {} | |
| 76 | + for _, child in ipairs(node) do | |
| 77 | + if child:type() == "text" then | |
| 78 | + table.insert(text, child:subtree_text()) | |
| 79 | + elseif child:named() then | |
| 80 | + table.insert(text, get_text(child)) | |
| 81 | + end | |
| 82 | + end | |
| 83 | + return table.concat(text) | |
| 84 | + end | |
| 85 | + local text = get_text(tree:root()) | |
| 86 | + | |
| 87 | + log.info(text) | |
| 88 | +end | |
| 89 | + | |
| 90 | +---@param description string | |
| 91 | +function M.description(description) | |
| 92 | + local lines = { "Description:" } | |
| 93 | + | |
| 94 | + for v in vim.gsplit(description, "\n\n", { trimempty = true }) do | |
| 95 | + parse_paragraph(utils.strip_html_tag(v, "p")) | |
| 96 | + end | |
| 97 | +end | |
| 98 | + | |
| 99 | +---@param examples string | |
| 100 | +function M.examples(examples) end | |
| 101 | + | |
| 102 | +---@param constrains string | |
| 103 | +function M.constrains(constrains) end | |
| 104 | + | |
| 105 | +---@param follow_up string | |
| 106 | +function M.follow_up(follow_up) end | |
| 107 | + | |
| 108 | +return M |
+10-8lua/leetcode/ui/dashboard.lua
| @@ -1,5 +1,7 @@ | ||
| 1 | 1 | local utils = require("leetcode.utils") |
| 2 | 2 | local gql = require("leetcode.graphql") |
| 3 | +local config = require("leetcode.config") | |
| 4 | +local log = require("leetcode.logger") | |
| 3 | 5 | |
| 4 | 6 | local M = {} |
| 5 | 7 | |
| @@ -48,8 +50,8 @@ local footer = { | ||
| 48 | 50 | } |
| 49 | 51 | |
| 50 | 52 | function M.setup() |
| 51 | - local user = gql.auth.user_status() | |
| 52 | - local is_signed_in = (user ~= nil and user.isSignedIn) | |
| 53 | + local auth = config.auth | |
| 54 | + local is_signed_in = auth and auth.is_signed_in | |
| 53 | 55 | |
| 54 | 56 | local alpha = require("alpha") |
| 55 | 57 | local dashboard = require("alpha.themes.dashboard") |
| @@ -72,7 +74,7 @@ function M.setup() | ||
| 72 | 74 | dashboard.button( |
| 73 | 75 | "c", |
| 74 | 76 | " " .. " Update cookie", |
| 75 | - "<cmd>lua require('leetcode.utils').prompt_for_cookie()<cr>" | |
| 77 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 76 | 78 | ), |
| 77 | 79 | dashboard.button( |
| 78 | 80 | "s", |
| @@ -85,12 +87,12 @@ function M.setup() | ||
| 85 | 87 | dashboard.button( |
| 86 | 88 | "s", |
| 87 | 89 | " " .. " Sign in (By Cookie)", |
| 88 | - "<cmd>lua require('leetcode.utils').prompt_for_cookie()<cr>" | |
| 90 | + "<cmd>lua require('leetcode.api').cmd.cookie_prompt()<cr>" | |
| 89 | 91 | ), |
| 90 | 92 | dashboard.button("q", " " .. " Exit LeetCode", "<cmd>qa!<CR>"), |
| 91 | 93 | } |
| 92 | 94 | |
| 93 | - footer.val = is_signed_in and "Signed in as: " .. user.username or "" | |
| 95 | + footer.val = is_signed_in and "Signed in as: " .. auth.username or "" | |
| 94 | 96 | |
| 95 | 97 | local section = { |
| 96 | 98 | header = header, |
| @@ -99,10 +101,10 @@ function M.setup() | ||
| 99 | 101 | footer = footer, |
| 100 | 102 | } |
| 101 | 103 | |
| 102 | - local config = { | |
| 104 | + local cfg = { | |
| 103 | 105 | layout = { |
| 104 | 106 | -- header |
| 105 | - { type = "padding", val = 4 }, | |
| 107 | + { type = "padding", val = 2 }, | |
| 106 | 108 | section.header, |
| 107 | 109 | |
| 108 | 110 | -- notifications |
| @@ -121,7 +123,7 @@ function M.setup() | ||
| 121 | 123 | }, |
| 122 | 124 | } |
| 123 | 125 | |
| 124 | - alpha.setup(config) | |
| 126 | + alpha.setup(cfg) | |
| 125 | 127 | end |
| 126 | 128 | |
| 127 | 129 | function M.update() |
+41-27lua/leetcode/ui/init.lua
| @@ -1,3 +1,7 @@ | ||
| 1 | +local logger = require("leetcode.logger") | |
| 2 | +local components = require("leetcode.ui.components") | |
| 3 | +local gql = require("leetcode.graphql") | |
| 4 | + | |
| 1 | 5 | ---@class lc.UI |
| 2 | 6 | local Ui = {} |
| 3 | 7 | |
| @@ -15,40 +19,50 @@ end | ||
| 15 | 19 | |
| 16 | 20 | function Ui.input(opts, callback) vim.ui.input(opts, callback) end |
| 17 | 21 | |
| 18 | -function Ui.oldfiles() Ui.create_win() end | |
| 22 | +---@param problem lc.Problem | |
| 23 | +function Ui.create_leetcode_win(problem) | |
| 24 | + local start_win = vim.api.nvim_get_current_win() | |
| 19 | 25 | |
| 20 | -function Ui.create_win() | |
| 21 | - -- We save handle to window from which we open the navigation | |
| 22 | - start_win = vim.api.nvim_get_current_win() | |
| 26 | + local Split = require("nui.split") | |
| 27 | + local split = Split({ | |
| 28 | + relative = "win", | |
| 29 | + position = "left", | |
| 30 | + size = "30%", | |
| 31 | + }) | |
| 32 | + split:mount() | |
| 23 | 33 | |
| 24 | - vim.api.nvim_command("topleft vnew") -- We open a new vertical window at the far right | |
| 25 | - win = vim.api.nvim_get_current_win() -- We save our navigation window handle... | |
| 26 | - buf = vim.api.nvim_get_current_buf() -- ...and it's buffer handle. | |
| 34 | + local title = gql.question.title(problem.title_slug) | |
| 35 | + local content = gql.question.content(problem.title_slug) | |
| 27 | 36 | |
| 28 | - -- We should name our buffer. All buffers in vim must have unique names. | |
| 29 | - -- The easiest solution will be adding buffer handle to it | |
| 30 | - -- because it is already unique and it's just a number. | |
| 31 | - vim.api.nvim_buf_set_name(buf, "Oldfiles #" .. buf) | |
| 37 | + local win = vim.api.nvim_get_current_win() -- We save our navigation window handle... | |
| 38 | + local buf = vim.api.nvim_get_current_buf() -- ...and it's buffer handle. | |
| 39 | + | |
| 40 | + vim.api.nvim_buf_set_name(buf, "LeetCode") | |
| 32 | 41 | |
| 33 | - -- Now we set some options for our buffer. | |
| 34 | - -- nofile prevent mark buffer as modified so we never get warnings about not saved changes. | |
| 35 | - -- Also some plugins treat nofile buffers different. | |
| 36 | - -- For example coc.nvim don't triggers aoutcompletation for these. | |
| 37 | - vim.api.nvim_buf_set_option(buf, "buftype", "nofile") | |
| 38 | - -- We do not need swapfile for this buffer. | |
| 39 | - vim.api.nvim_buf_set_option(buf, "swapfile", false) | |
| 40 | - -- And we would rather prefer that this buffer will be destroyed when hide. | |
| 41 | - vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") | |
| 42 | - -- It's not necessary but it is good practice to set custom filetype. | |
| 43 | - -- This allows users to create their own autocommand or colorschemes on filetype. | |
| 44 | - -- and prevent collisions with other plugins. | |
| 45 | 42 | vim.api.nvim_buf_set_option(buf, "filetype", "leetcode.nvim") |
| 43 | + vim.api.nvim_buf_set_option(buf, "swapfile", false) | |
| 44 | + vim.api.nvim_buf_set_option(buf, "buftype", "nofile") | |
| 45 | + vim.api.nvim_buf_set_option(buf, "buflisted", false) | |
| 46 | + -- vim.api.nvim_buf_set_option(buf, "modifiable", true) | |
| 47 | + | |
| 48 | + vim.api.nvim_win_set_option(win, "wrap", true) | |
| 49 | + vim.api.nvim_win_set_option(win, "number", false) | |
| 50 | + vim.api.nvim_win_set_option(win, "signcolumn", "no") | |
| 51 | + | |
| 52 | + vim.api.nvim_buf_set_keymap(buf, "n", "<esc>", "<cmd>hide<CR>", { noremap = true }) | |
| 53 | + vim.api.nvim_buf_set_keymap(buf, "n", "q", "<cmd>hide<CR>", { noremap = true }) | |
| 54 | + | |
| 55 | + local line = require("nui.line")() | |
| 56 | + line:append("") | |
| 57 | + | |
| 58 | + components.problem.link(problem.title_slug):render(buf, -1, 1) | |
| 59 | + line:render(buf, -1, 2) | |
| 46 | 60 | |
| 47 | - -- For better UX we will turn off line wrap and turn on current line highlight. | |
| 48 | - vim.api.nvim_win_set_option(win, "wrap", false) | |
| 49 | - vim.api.nvim_win_set_option(win, "cursorline", true) | |
| 61 | + components.problem.title(title):render(buf, -1, 3) | |
| 62 | + components.problem.stats(title):render(buf, -1, 4) | |
| 63 | + line:render(buf, -1, 5) | |
| 50 | 64 | |
| 51 | - set_mappings() -- At end we will set mappings for our navigation. | |
| 65 | + components.problem.content(content):render(buf, -1, 6) | |
| 52 | 66 | end |
| 53 | 67 | |
| 54 | 68 | return Ui |
+18-17lua/leetcode/utils.lua
| @@ -1,23 +1,6 @@ | ||
| 1 | -local ui = require("leetcode.ui") | |
| 2 | - | |
| 3 | 1 | ---@class lc.Utils |
| 4 | 2 | local Utils = {} |
| 5 | 3 | |
| 6 | -function Utils.prompt_for_cookie() | |
| 7 | - local cookie = require("leetcode.cache.cookie") | |
| 8 | - | |
| 9 | - ui.input( | |
| 10 | - "Enter cookie", | |
| 11 | - ---@param cookie_str string | |
| 12 | - function(cookie_str) | |
| 13 | - if not cookie_str then return end | |
| 14 | - | |
| 15 | - cookie.new(cookie_str) | |
| 16 | - require("leetcode.ui.dashboard").update() | |
| 17 | - end | |
| 18 | - ) | |
| 19 | -end | |
| 20 | - | |
| 21 | 4 | function Utils.remove_cookie() |
| 22 | 5 | require("leetcode.cache.cookie").delete() |
| 23 | 6 | require("leetcode.ui.dashboard").update() |
| @@ -29,4 +12,22 @@ function Utils.alpha_move_cursor_top() | ||
| 29 | 12 | require("alpha").move_cursor(curr_win) |
| 30 | 13 | end |
| 31 | 14 | |
| 15 | +---Extracts an HTML tag from a string. | |
| 16 | +--- | |
| 17 | +---@param str string The input string containing HTML tags. | |
| 18 | +---@return string | nil The extracted HTML tag, or nil if no tag is found. | |
| 19 | +function Utils.get_html_tag(str) return str:match("^<(.-)>") end | |
| 20 | + | |
| 21 | +---@param str string | |
| 22 | +---@param tag string | |
| 23 | +--- | |
| 24 | +---@return string | |
| 25 | +function Utils.strip_html_tag(str, tag) | |
| 26 | + local regex = string.format("^<%s>(.*)</%s>$", tag, tag) | |
| 27 | + assert(str:match(regex)) | |
| 28 | + | |
| 29 | + local offset = 3 + tag:len() | |
| 30 | + return str:sub(offset, str:len() - offset) | |
| 31 | +end | |
| 32 | + | |
| 32 | 33 | return Utils |