feat: modernize the plugin
This commit is contained in:
parent
03807e46e0
commit
94f5828a0a
4 changed files with 227 additions and 48 deletions
|
|
@ -1,5 +1,21 @@
|
|||
---@class cp.ContestConfig
|
||||
---@field cpp_version number
|
||||
---@field compile_flags string[]
|
||||
---@field debug_flags string[]
|
||||
---@field timeout_ms number
|
||||
|
||||
---@class cp.HooksConfig
|
||||
---@field before_run? function
|
||||
---@field before_debug? function
|
||||
|
||||
---@class cp.Config
|
||||
---@field contests table<string, cp.ContestConfig>
|
||||
---@field snippets table<string, any>
|
||||
---@field hooks cp.HooksConfig
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type cp.Config
|
||||
M.defaults = {
|
||||
contests = {
|
||||
default = {
|
||||
|
|
@ -25,6 +41,9 @@ M.defaults = {
|
|||
},
|
||||
}
|
||||
|
||||
---@param base_config cp.ContestConfig
|
||||
---@param contest_config cp.ContestConfig
|
||||
---@return cp.ContestConfig
|
||||
local function extend_contest_config(base_config, contest_config)
|
||||
local result = vim.tbl_deep_extend("force", base_config, contest_config)
|
||||
|
||||
|
|
@ -35,23 +54,69 @@ local function extend_contest_config(base_config, contest_config)
|
|||
return result
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@param tbl table
|
||||
---@return boolean is_valid
|
||||
---@return string|nil error_message
|
||||
local function validate_path(path, tbl)
|
||||
local ok, err = pcall(vim.validate, tbl)
|
||||
return ok, err and path .. "." .. err
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@param contest_config table|nil
|
||||
---@return boolean is_valid
|
||||
---@return string|nil error_message
|
||||
local function validate_contest_config(path, contest_config)
|
||||
if not contest_config then
|
||||
return true, nil
|
||||
end
|
||||
|
||||
return validate_path(path, {
|
||||
cpp_version = { contest_config.cpp_version, "number", true },
|
||||
compile_flags = { contest_config.compile_flags, "table", true },
|
||||
debug_flags = { contest_config.debug_flags, "table", true },
|
||||
timeout_ms = { contest_config.timeout_ms, "number", true },
|
||||
})
|
||||
end
|
||||
|
||||
---@param user_config cp.Config|nil
|
||||
---@return cp.Config
|
||||
function M.setup(user_config)
|
||||
vim.validate({
|
||||
local ok, err = validate_path("config", {
|
||||
user_config = { user_config, { "table", "nil" }, true },
|
||||
})
|
||||
if not ok then
|
||||
error(err)
|
||||
end
|
||||
|
||||
if user_config then
|
||||
vim.validate({
|
||||
ok, err = validate_path("config", {
|
||||
contests = { user_config.contests, { "table", "nil" }, true },
|
||||
snippets = { user_config.snippets, { "table", "nil" }, true },
|
||||
hooks = { user_config.hooks, { "table", "nil" }, true },
|
||||
})
|
||||
if not ok then
|
||||
error(err)
|
||||
end
|
||||
|
||||
if user_config.hooks then
|
||||
vim.validate({
|
||||
ok, err = validate_path("config.hooks", {
|
||||
before_run = { user_config.hooks.before_run, { "function", "nil" }, true },
|
||||
before_debug = { user_config.hooks.before_debug, { "function", "nil" }, true },
|
||||
})
|
||||
if not ok then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
if user_config.contests then
|
||||
for contest_name, contest_config in pairs(user_config.contests) do
|
||||
ok, err = validate_contest_config("config.contests." .. contest_name, contest_config)
|
||||
if not ok then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
107
lua/cp/health.lua
Normal file
107
lua/cp/health.lua
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
local M = {}
|
||||
|
||||
local function check_nvim_version()
|
||||
if vim.fn.has("nvim-0.10.0") == 1 then
|
||||
vim.health.ok("Neovim 0.10.0+ detected")
|
||||
else
|
||||
vim.health.error("cp.nvim requires Neovim 0.10.0+")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_uv()
|
||||
if vim.fn.executable("uv") == 1 then
|
||||
vim.health.ok("uv executable found")
|
||||
|
||||
local result = vim.system({ "uv", "--version" }, { text = true }):wait()
|
||||
if result.code == 0 then
|
||||
vim.health.info("uv version: " .. result.stdout:gsub("\n", ""))
|
||||
end
|
||||
else
|
||||
vim.health.warn("uv not found - install from https://docs.astral.sh/uv/ for problem scraping")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_python_env()
|
||||
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||
plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h")
|
||||
local venv_dir = plugin_path .. "/.venv"
|
||||
|
||||
if vim.fn.isdirectory(venv_dir) == 1 then
|
||||
vim.health.ok("Python virtual environment found at " .. venv_dir)
|
||||
else
|
||||
vim.health.warn("Python virtual environment not set up - run :CP command to initialize")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_scrapers()
|
||||
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||
plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h")
|
||||
|
||||
local scrapers = { "atcoder.py", "codeforces.py", "cses.py" }
|
||||
for _, scraper in ipairs(scrapers) do
|
||||
local scraper_path = plugin_path .. "/scrapers/" .. scraper
|
||||
if vim.fn.filereadable(scraper_path) == 1 then
|
||||
vim.health.ok("Scraper found: " .. scraper)
|
||||
else
|
||||
vim.health.error("Missing scraper: " .. scraper)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function check_luasnip()
|
||||
local has_luasnip, luasnip = pcall(require, "luasnip")
|
||||
if has_luasnip then
|
||||
vim.health.ok("LuaSnip integration available")
|
||||
local snippet_count = #luasnip.get_snippets("all")
|
||||
vim.health.info("LuaSnip snippets loaded: " .. snippet_count)
|
||||
else
|
||||
vim.health.info("LuaSnip not available - template expansion will be limited")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_directories()
|
||||
local cwd = vim.fn.getcwd()
|
||||
local build_dir = cwd .. "/build"
|
||||
local io_dir = cwd .. "/io"
|
||||
|
||||
if vim.fn.isdirectory(build_dir) == 1 then
|
||||
vim.health.ok("Build directory exists: " .. build_dir)
|
||||
else
|
||||
vim.health.info("Build directory will be created when needed")
|
||||
end
|
||||
|
||||
if vim.fn.isdirectory(io_dir) == 1 then
|
||||
vim.health.ok("IO directory exists: " .. io_dir)
|
||||
else
|
||||
vim.health.info("IO directory will be created when needed")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_config()
|
||||
local cp = require("cp")
|
||||
if cp.is_initialized() then
|
||||
vim.health.ok("Plugin initialized")
|
||||
|
||||
if vim.g.cp_contest then
|
||||
vim.health.info("Current contest: " .. vim.g.cp_contest)
|
||||
else
|
||||
vim.health.info("No contest mode set")
|
||||
end
|
||||
else
|
||||
vim.health.warn("Plugin not initialized - configuration may be incomplete")
|
||||
end
|
||||
end
|
||||
|
||||
function M.check()
|
||||
vim.health.start("cp.nvim health check")
|
||||
|
||||
check_nvim_version()
|
||||
check_uv()
|
||||
check_python_env()
|
||||
check_scrapers()
|
||||
check_luasnip()
|
||||
check_directories()
|
||||
check_config()
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -214,66 +214,57 @@ end
|
|||
|
||||
local initialized = false
|
||||
|
||||
function M.is_initialized()
|
||||
return initialized
|
||||
end
|
||||
|
||||
function M.setup(user_config)
|
||||
if initialized and not user_config then
|
||||
return
|
||||
end
|
||||
|
||||
config = config_module.setup(user_config)
|
||||
|
||||
snippets.setup(config)
|
||||
initialized = true
|
||||
end
|
||||
|
||||
if initialized then
|
||||
function M.handle_command(opts)
|
||||
local args = opts.fargs
|
||||
if #args == 0 then
|
||||
log("Usage: :CP <contest|problem_id|run|debug|diff>", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
initialized = true
|
||||
|
||||
vim.api.nvim_create_user_command("CP", function(opts)
|
||||
local args = opts.fargs
|
||||
if #args == 0 then
|
||||
log("Usage: :CP <contest|problem_id|run|debug|diff>", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
local cmd = args[1]
|
||||
|
||||
local cmd = args[1]
|
||||
|
||||
if vim.tbl_contains(competition_types, cmd) then
|
||||
if args[2] then
|
||||
setup_contest(cmd)
|
||||
if (cmd == "atcoder" or cmd == "codeforces") and args[3] then
|
||||
setup_problem(args[2], args[3])
|
||||
else
|
||||
setup_problem(args[2])
|
||||
end
|
||||
if vim.tbl_contains(competition_types, cmd) then
|
||||
if args[2] then
|
||||
setup_contest(cmd)
|
||||
if (cmd == "atcoder" or cmd == "codeforces") and args[3] then
|
||||
setup_problem(args[2], args[3])
|
||||
else
|
||||
setup_contest(cmd)
|
||||
setup_problem(args[2])
|
||||
end
|
||||
elseif cmd == "run" then
|
||||
run_problem()
|
||||
elseif cmd == "debug" then
|
||||
debug_problem()
|
||||
elseif cmd == "diff" then
|
||||
diff_problem()
|
||||
else
|
||||
if vim.g.cp_contest then
|
||||
if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and args[2] then
|
||||
setup_problem(cmd, args[2])
|
||||
else
|
||||
setup_problem(cmd)
|
||||
end
|
||||
else
|
||||
log("no contest mode set. run :CP <contest> first or use full command", vim.log.levels.ERROR)
|
||||
end
|
||||
setup_contest(cmd)
|
||||
end
|
||||
end, {
|
||||
nargs = "*",
|
||||
complete = function(ArgLead, _, _)
|
||||
local commands = vim.list_extend(vim.deepcopy(competition_types), { "run", "debug", "diff" })
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, commands)
|
||||
end,
|
||||
})
|
||||
elseif cmd == "run" then
|
||||
run_problem()
|
||||
elseif cmd == "debug" then
|
||||
debug_problem()
|
||||
elseif cmd == "diff" then
|
||||
diff_problem()
|
||||
else
|
||||
if vim.g.cp_contest then
|
||||
if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and args[2] then
|
||||
setup_problem(cmd, args[2])
|
||||
else
|
||||
setup_problem(cmd)
|
||||
end
|
||||
else
|
||||
log("no contest mode set. run :CP <contest> first or use full command", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue