Merge pull request #4 from barrett-ruth/feat/config-revamp
Feat/config revamp
This commit is contained in:
commit
80ed2f2df7
13 changed files with 433 additions and 140 deletions
|
|
@ -2,15 +2,44 @@ local M = {}
|
||||||
|
|
||||||
M.defaults = {
|
M.defaults = {
|
||||||
contests = {
|
contests = {
|
||||||
atcoder = { cpp_version = 23 },
|
default = {
|
||||||
codeforces = { cpp_version = 23 },
|
cpp_version = 20,
|
||||||
cses = { cpp_version = 20 },
|
compile_flags = { "-O2", "-DLOCAL", "-Wall", "-Wextra" },
|
||||||
|
debug_flags = { "-g3", "-fsanitize=address,undefined", "-DLOCAL" },
|
||||||
|
timeout_ms = 2000,
|
||||||
|
},
|
||||||
|
atcoder = {
|
||||||
|
cpp_version = 23,
|
||||||
|
},
|
||||||
|
codeforces = {
|
||||||
|
cpp_version = 23,
|
||||||
|
},
|
||||||
|
cses = {},
|
||||||
},
|
},
|
||||||
snippets = {},
|
snippets = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function extend_contest_config(base_config, contest_config)
|
||||||
|
local result = vim.tbl_deep_extend("force", base_config, contest_config)
|
||||||
|
|
||||||
|
local std_flag = ("-std=c++%d"):format(result.cpp_version)
|
||||||
|
table.insert(result.compile_flags, 1, std_flag)
|
||||||
|
table.insert(result.debug_flags, 1, std_flag)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup(user_config)
|
function M.setup(user_config)
|
||||||
return vim.tbl_deep_extend("force", M.defaults, user_config or {})
|
local config = vim.tbl_deep_extend("force", M.defaults, user_config or {})
|
||||||
|
|
||||||
|
local default_contest = config.contests.default
|
||||||
|
for contest_name, contest_config in pairs(config.contests) do
|
||||||
|
if contest_name ~= "default" then
|
||||||
|
config.contests[contest_name] = extend_contest_config(default_contest, contest_config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
111
lua/cp/execute.lua
Normal file
111
lua/cp/execute.lua
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local signal_codes = {
|
||||||
|
[128] = "SIGILL",
|
||||||
|
[130] = "SIGABRT",
|
||||||
|
[131] = "SIGBUS",
|
||||||
|
[136] = "SIGFPE",
|
||||||
|
[135] = "SIGSEGV",
|
||||||
|
[137] = "SIGPIPE",
|
||||||
|
[139] = "SIGTERM",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function get_paths(problem_id)
|
||||||
|
return {
|
||||||
|
source = ("%s.cc"):format(problem_id),
|
||||||
|
binary = ("build/%s"):format(problem_id),
|
||||||
|
input = ("io/%s.in"):format(problem_id),
|
||||||
|
output = ("io/%s.out"):format(problem_id),
|
||||||
|
expected = ("io/%s.expected"):format(problem_id),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_directories()
|
||||||
|
vim.system({ "mkdir", "-p", "build", "io" }):wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compile_cpp(source_path, binary_path, flags)
|
||||||
|
local compile_cmd = { "g++", unpack(flags), source_path, "-o", binary_path }
|
||||||
|
return vim.system(compile_cmd, { text = true }):wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function execute_binary(binary_path, input_data, timeout_ms)
|
||||||
|
local start_time = vim.loop.hrtime()
|
||||||
|
|
||||||
|
local result = vim.system({ binary_path }, {
|
||||||
|
stdin = input_data,
|
||||||
|
timeout = timeout_ms,
|
||||||
|
text = true,
|
||||||
|
}):wait()
|
||||||
|
|
||||||
|
local end_time = vim.loop.hrtime()
|
||||||
|
local execution_time = (end_time - start_time) / 1000000
|
||||||
|
|
||||||
|
return {
|
||||||
|
stdout = result.stdout or "",
|
||||||
|
stderr = result.stderr or "",
|
||||||
|
code = result.code,
|
||||||
|
time_ms = execution_time,
|
||||||
|
timed_out = result.code == 124,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function format_output(exec_result, expected_file)
|
||||||
|
local lines = { exec_result.stdout }
|
||||||
|
|
||||||
|
if exec_result.timed_out then
|
||||||
|
table.insert(lines, "\n[code]: 124 (TIMEOUT)")
|
||||||
|
elseif exec_result.code >= 128 then
|
||||||
|
local signal_name = signal_codes[exec_result.code] or "SIGNAL"
|
||||||
|
table.insert(lines, ("\n[code]: %d (%s)"):format(exec_result.code, signal_name))
|
||||||
|
else
|
||||||
|
table.insert(lines, ("\n[code]: %d"):format(exec_result.code))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(lines, ("\n[time]: %.2f ms"):format(exec_result.time_ms))
|
||||||
|
table.insert(lines, "\n[debug]: false")
|
||||||
|
|
||||||
|
if vim.fn.filereadable(expected_file) == 1 and exec_result.code == 0 then
|
||||||
|
local expected_content = vim.fn.readfile(expected_file)
|
||||||
|
local actual_lines = vim.split(exec_result.stdout, "\n")
|
||||||
|
|
||||||
|
local matches = #actual_lines == #expected_content
|
||||||
|
if matches then
|
||||||
|
for i, line in ipairs(actual_lines) do
|
||||||
|
if line ~= expected_content[i] then
|
||||||
|
matches = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(lines, ("\n[matches]: %s"):format(matches and "true" or "false"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(lines, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_problem(problem_id, contest_config, is_debug)
|
||||||
|
ensure_directories()
|
||||||
|
|
||||||
|
local paths = get_paths(problem_id)
|
||||||
|
local flags = is_debug and contest_config.debug_flags or contest_config.compile_flags
|
||||||
|
|
||||||
|
local compile_result = compile_cpp(paths.source, paths.binary, flags)
|
||||||
|
if compile_result.code ~= 0 then
|
||||||
|
vim.fn.writefile({ compile_result.stderr }, paths.output)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local input_data = ""
|
||||||
|
if vim.fn.filereadable(paths.input) == 1 then
|
||||||
|
input_data = table.concat(vim.fn.readfile(paths.input), "\n") .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
local exec_result = execute_binary(paths.binary, input_data, contest_config.timeout_ms)
|
||||||
|
local formatted_output = format_output(exec_result, paths.expected)
|
||||||
|
|
||||||
|
vim.fn.writefile(vim.split(formatted_output, "\n"), paths.output)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
110
lua/cp/init.lua
110
lua/cp/init.lua
|
|
@ -1,5 +1,8 @@
|
||||||
local config_module = require("cp.config")
|
local config_module = require("cp.config")
|
||||||
local snippets = require("cp.snippets")
|
local snippets = require("cp.snippets")
|
||||||
|
local execute = require("cp.execute")
|
||||||
|
local scrape = require("cp.scrape")
|
||||||
|
local window = require("cp.window")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
@ -8,12 +11,9 @@ local function log(msg, level)
|
||||||
vim.notify(("[cp.nvim]: %s"):format(msg), level or vim.log.levels.INFO)
|
vim.notify(("[cp.nvim]: %s"):format(msg), level or vim.log.levels.INFO)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clearcol()
|
if not vim.fn.has("nvim-0.10.0") then
|
||||||
vim.api.nvim_set_option_value("number", false, { scope = "local" })
|
log("cp.nvim requires Neovim 0.10.0+", vim.log.levels.ERROR)
|
||||||
vim.api.nvim_set_option_value("relativenumber", false, { scope = "local" })
|
return M
|
||||||
vim.api.nvim_set_option_value("statuscolumn", "", { scope = "local" })
|
|
||||||
vim.api.nvim_set_option_value("signcolumn", "no", { scope = "local" })
|
|
||||||
vim.api.nvim_set_option_value("equalalways", false, { scope = "global" })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_plugin_path()
|
local function get_plugin_path()
|
||||||
|
|
@ -35,9 +35,9 @@ local function setup_python_env()
|
||||||
|
|
||||||
if vim.fn.isdirectory(venv_dir) == 0 then
|
if vim.fn.isdirectory(venv_dir) == 0 then
|
||||||
log("setting up Python environment for scrapers...")
|
log("setting up Python environment for scrapers...")
|
||||||
local result = vim.fn.system(("cd %s && uv sync"):format(vim.fn.shellescape(plugin_path)))
|
local result = vim.system({ "uv", "sync" }, { cwd = plugin_path, text = true }):wait()
|
||||||
if vim.v.shell_error ~= 0 then
|
if result.code ~= 0 then
|
||||||
log("failed to setup Python environment: " .. result, vim.log.levels.ERROR)
|
log("failed to setup Python environment: " .. result.stderr, vim.log.levels.ERROR)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
log("python environment setup complete")
|
log("python environment setup complete")
|
||||||
|
|
@ -58,8 +58,8 @@ local function setup_contest(contest_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.g.cp_contest = contest_type
|
vim.g.cp_contest = contest_type
|
||||||
vim.fn.system(("cp -fr %s/* ."):format(config.template_dir))
|
vim.fn.mkdir("build", "p")
|
||||||
vim.fn.system(("make setup VERSION=%s"):format(config.contests[contest_type].cpp_version))
|
vim.fn.mkdir("io", "p")
|
||||||
log(("set up %s contest environment"):format(contest_type))
|
log(("set up %s contest environment"):format(contest_type))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -84,17 +84,24 @@ local function setup_problem(problem_id, problem_letter)
|
||||||
|
|
||||||
vim.cmd.only()
|
vim.cmd.only()
|
||||||
|
|
||||||
local filename, full_problem_id
|
local scrape_result = scrape.scrape_problem(vim.g.cp_contest, problem_id, problem_letter)
|
||||||
if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and problem_letter then
|
|
||||||
full_problem_id = problem_id .. problem_letter
|
if not scrape_result.success then
|
||||||
filename = full_problem_id .. ".cc"
|
log("scraping failed: " .. scrape_result.error, vim.log.levels.WARN)
|
||||||
vim.fn.system(("make scrape %s %s %s"):format(vim.g.cp_contest, problem_id, problem_letter))
|
log("you can manually add test cases to io/ directory", vim.log.levels.INFO)
|
||||||
else
|
else
|
||||||
full_problem_id = problem_id
|
log(("scraped %d test case(s) for %s"):format(scrape_result.test_count, scrape_result.problem_id))
|
||||||
filename = problem_id .. ".cc"
|
|
||||||
vim.fn.system(("make scrape %s %s"):format(vim.g.cp_contest, problem_id))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local full_problem_id = scrape_result.success and scrape_result.problem_id
|
||||||
|
or (
|
||||||
|
(vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces")
|
||||||
|
and problem_letter
|
||||||
|
and problem_id .. problem_letter:upper()
|
||||||
|
or problem_id
|
||||||
|
)
|
||||||
|
local filename = full_problem_id .. ".cc"
|
||||||
|
|
||||||
vim.cmd.e(filename)
|
vim.cmd.e(filename)
|
||||||
|
|
||||||
if vim.api.nvim_buf_get_lines(0, 0, -1, true)[1] == "" then
|
if vim.api.nvim_buf_get_lines(0, 0, -1, true)[1] == "" then
|
||||||
|
|
@ -115,11 +122,11 @@ local function setup_problem(problem_id, problem_letter)
|
||||||
|
|
||||||
vim.cmd.vsplit(output)
|
vim.cmd.vsplit(output)
|
||||||
vim.cmd.w()
|
vim.cmd.w()
|
||||||
clearcol()
|
window.clearcol()
|
||||||
vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3)))
|
vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3)))
|
||||||
vim.cmd.split(input)
|
vim.cmd.split(input)
|
||||||
vim.cmd.w()
|
vim.cmd.w()
|
||||||
clearcol()
|
window.clearcol()
|
||||||
vim.cmd.wincmd("h")
|
vim.cmd.wincmd("h")
|
||||||
|
|
||||||
log(("switched to problem %s"):format(full_problem_id))
|
log(("switched to problem %s"):format(full_problem_id))
|
||||||
|
|
@ -145,10 +152,16 @@ local function run_problem()
|
||||||
lsp.lsp_format({ async = true })
|
lsp.lsp_format({ async = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.system({ "make", "run", vim.fn.expand("%:t") }, {}, function()
|
if not vim.g.cp_contest then
|
||||||
vim.schedule(function()
|
log("no contest mode set", vim.log.levels.ERROR)
|
||||||
vim.cmd.checktime()
|
return
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
local contest_config = config.contests[vim.g.cp_contest]
|
||||||
|
|
||||||
|
vim.schedule(function()
|
||||||
|
execute.run_problem(problem_id, contest_config, false)
|
||||||
|
vim.cmd.checktime()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -163,22 +176,24 @@ local function debug_problem()
|
||||||
lsp.lsp_format({ async = true })
|
lsp.lsp_format({ async = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.system({ "make", "debug", vim.fn.expand("%:t") }, {}, function()
|
if not vim.g.cp_contest then
|
||||||
vim.schedule(function()
|
log("no contest mode set", vim.log.levels.ERROR)
|
||||||
vim.cmd.checktime()
|
return
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
local contest_config = config.contests[vim.g.cp_contest]
|
||||||
|
|
||||||
|
vim.schedule(function()
|
||||||
|
execute.run_problem(problem_id, contest_config, true)
|
||||||
|
vim.cmd.checktime()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function diff_problem()
|
local function diff_problem()
|
||||||
if vim.g.cp_diff_mode then
|
if vim.g.cp_diff_mode then
|
||||||
vim.cmd.diffoff()
|
window.restore_layout(vim.g.cp_saved_layout)
|
||||||
if vim.g.cp_saved_session then
|
|
||||||
vim.cmd(("silent! source %s"):format(vim.g.cp_saved_session))
|
|
||||||
vim.fn.delete(vim.g.cp_saved_session)
|
|
||||||
vim.g.cp_saved_session = nil
|
|
||||||
end
|
|
||||||
vim.g.cp_diff_mode = false
|
vim.g.cp_diff_mode = false
|
||||||
|
vim.g.cp_saved_layout = nil
|
||||||
log("exited diff mode")
|
log("exited diff mode")
|
||||||
else
|
else
|
||||||
local problem_id = get_current_problem()
|
local problem_id = get_current_problem()
|
||||||
|
|
@ -196,30 +211,14 @@ local function diff_problem()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local temp_output = vim.fn.tempname()
|
vim.g.cp_saved_layout = window.save_layout()
|
||||||
vim.fn.system(("awk '/^\\[[^]]*\\]:/ {exit} {print}' %s > %s"):format(vim.fn.shellescape(output), temp_output))
|
|
||||||
|
|
||||||
local session_file = vim.fn.tempname() .. ".vim"
|
local result = vim.system({ "awk", "/^\\[[^]]*\\]:/ {exit} {print}", output }, { text = true }):wait()
|
||||||
vim.cmd(("silent! mksession! %s"):format(session_file))
|
local actual_output = result.stdout
|
||||||
vim.g.cp_saved_session = session_file
|
|
||||||
|
|
||||||
vim.cmd.diffoff()
|
window.setup_diff_layout(actual_output, expected, input)
|
||||||
vim.cmd.only()
|
|
||||||
|
|
||||||
vim.cmd.edit(temp_output)
|
|
||||||
vim.cmd.diffthis()
|
|
||||||
clearcol()
|
|
||||||
|
|
||||||
vim.cmd.vsplit(expected)
|
|
||||||
vim.cmd.diffthis()
|
|
||||||
clearcol()
|
|
||||||
|
|
||||||
vim.cmd(("botright split %s"):format(input))
|
|
||||||
clearcol()
|
|
||||||
vim.cmd.wincmd("k")
|
|
||||||
|
|
||||||
vim.g.cp_diff_mode = true
|
vim.g.cp_diff_mode = true
|
||||||
vim.g.cp_temp_output = temp_output
|
|
||||||
log("entered diff mode")
|
log("entered diff mode")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -234,7 +233,6 @@ function M.setup(user_config)
|
||||||
config = config_module.setup(user_config)
|
config = config_module.setup(user_config)
|
||||||
|
|
||||||
local plugin_path = get_plugin_path()
|
local plugin_path = get_plugin_path()
|
||||||
config.template_dir = plugin_path .. "/templates"
|
|
||||||
config.snippets.path = plugin_path .. "/templates/snippets"
|
config.snippets.path = plugin_path .. "/templates/snippets"
|
||||||
|
|
||||||
snippets.setup(config)
|
snippets.setup(config)
|
||||||
|
|
|
||||||
68
lua/cp/scrape.lua
Normal file
68
lua/cp/scrape.lua
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function get_plugin_path()
|
||||||
|
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||||
|
return vim.fn.fnamemodify(plugin_path, ":h:h:h")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_io_directory()
|
||||||
|
vim.fn.mkdir("io", "p")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.scrape_problem(contest, problem_id, problem_letter)
|
||||||
|
ensure_io_directory()
|
||||||
|
|
||||||
|
local plugin_path = get_plugin_path()
|
||||||
|
local scraper_path = plugin_path .. "/templates/scrapers/" .. contest .. ".py"
|
||||||
|
|
||||||
|
local args
|
||||||
|
if contest == "cses" then
|
||||||
|
args = { "uv", "run", scraper_path, problem_id }
|
||||||
|
else
|
||||||
|
args = { "uv", "run", scraper_path, problem_id, problem_letter }
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = vim.system(args, {
|
||||||
|
cwd = plugin_path,
|
||||||
|
text = true,
|
||||||
|
timeout = 30000,
|
||||||
|
}):wait()
|
||||||
|
|
||||||
|
if result.code ~= 0 then
|
||||||
|
return {
|
||||||
|
success = false,
|
||||||
|
error = "Failed to run scraper: " .. (result.stderr or "Unknown error"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, data = pcall(vim.json.decode, result.stdout)
|
||||||
|
if not ok then
|
||||||
|
return {
|
||||||
|
success = false,
|
||||||
|
error = "Failed to parse scraper output: " .. tostring(data),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not data.success then
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local full_problem_id = data.problem_id
|
||||||
|
local input_file = "io/" .. full_problem_id .. ".in"
|
||||||
|
local expected_file = "io/" .. full_problem_id .. ".expected"
|
||||||
|
|
||||||
|
if #data.test_cases > 0 then
|
||||||
|
local first_test = data.test_cases[1]
|
||||||
|
vim.fn.writefile(vim.split(first_test.input, "\n"), input_file)
|
||||||
|
vim.fn.writefile(vim.split(first_test.output, "\n"), expected_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
success = true,
|
||||||
|
problem_id = full_problem_id,
|
||||||
|
test_count = #data.test_cases,
|
||||||
|
url = data.url,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
77
lua/cp/window.lua
Normal file
77
lua/cp/window.lua
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.clearcol()
|
||||||
|
vim.api.nvim_set_option_value("number", false, { scope = "local" })
|
||||||
|
vim.api.nvim_set_option_value("relativenumber", false, { scope = "local" })
|
||||||
|
vim.api.nvim_set_option_value("statuscolumn", "", { scope = "local" })
|
||||||
|
vim.api.nvim_set_option_value("signcolumn", "no", { scope = "local" })
|
||||||
|
vim.api.nvim_set_option_value("equalalways", false, { scope = "global" })
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.save_layout()
|
||||||
|
local windows = {}
|
||||||
|
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||||
|
if vim.api.nvim_win_is_valid(win) then
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(win)
|
||||||
|
windows[win] = {
|
||||||
|
bufnr = bufnr,
|
||||||
|
view = vim.fn.winsaveview(),
|
||||||
|
width = vim.api.nvim_win_get_width(win),
|
||||||
|
height = vim.api.nvim_win_get_height(win),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
windows = windows,
|
||||||
|
current_win = vim.api.nvim_get_current_win(),
|
||||||
|
layout = vim.fn.winrestcmd(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.restore_layout(state)
|
||||||
|
if not state then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.cmd.diffoff()
|
||||||
|
vim.cmd(state.layout)
|
||||||
|
|
||||||
|
for win, win_state in pairs(state.windows) do
|
||||||
|
if vim.api.nvim_win_is_valid(win) then
|
||||||
|
vim.api.nvim_set_current_win(win)
|
||||||
|
if vim.api.nvim_get_current_buf() == win_state.bufnr then
|
||||||
|
vim.fn.winrestview(win_state.view)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.api.nvim_win_is_valid(state.current_win) then
|
||||||
|
vim.api.nvim_set_current_win(state.current_win)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup_diff_layout(actual_output, expected_output, input_file)
|
||||||
|
vim.cmd.diffoff()
|
||||||
|
vim.cmd.only()
|
||||||
|
|
||||||
|
local output_lines = vim.split(actual_output, "\n")
|
||||||
|
local output_buf = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, output_lines)
|
||||||
|
vim.bo[output_buf].filetype = "cpoutput"
|
||||||
|
|
||||||
|
vim.cmd.edit()
|
||||||
|
vim.api.nvim_set_current_buf(output_buf)
|
||||||
|
vim.cmd.diffthis()
|
||||||
|
M.clearcol()
|
||||||
|
|
||||||
|
vim.cmd.vsplit(expected_output)
|
||||||
|
vim.cmd.diffthis()
|
||||||
|
M.clearcol()
|
||||||
|
|
||||||
|
vim.cmd(("botright split %s"):format(input_file))
|
||||||
|
M.clearcol()
|
||||||
|
vim.cmd.wincmd("k")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
10
readme.md
10
readme.md
|
|
@ -15,12 +15,9 @@ neovim plugin for competitive programming.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Neovim 0.9+
|
- Neovim 0.10.0+
|
||||||
- `make`
|
- [uv](https://docs.astral.sh/uv/): problem scraping (optional)
|
||||||
- [uv](https://docs.astral.sh/uv/): problem scraping (optional)
|
|
||||||
- [LuaSnip](https://github.com/L3MON4D3/LuaSnip): contest-specific snippets (optional)
|
- [LuaSnip](https://github.com/L3MON4D3/LuaSnip): contest-specific snippets (optional)
|
||||||
- [vim-zoom](https://github.com/dhruvasagar/vim-zoom): better diff view
|
|
||||||
(optional)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -44,8 +41,7 @@ Using [lazy.nvim](https://github.com/folke/lazy.nvim):
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- remove vim-zoom dependency
|
- vimdocs
|
||||||
- vimdocs
|
|
||||||
- example video
|
- example video
|
||||||
- more flexible setup (more of a question of philosophy)
|
- more flexible setup (more of a question of philosophy)
|
||||||
- USACO support
|
- USACO support
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
CompileFlags:
|
|
||||||
Add:
|
|
||||||
-O2
|
|
||||||
-Wall
|
|
||||||
-Wextra
|
|
||||||
-Wpedantic
|
|
||||||
-Wshadow
|
|
||||||
-Wformat=2
|
|
||||||
-Wfloat-equal
|
|
||||||
-Wlogical-op
|
|
||||||
-Wshift-overflow=2
|
|
||||||
-Wnon-virtual-dtor
|
|
||||||
-Wold-style-cast
|
|
||||||
-Wcast-qual
|
|
||||||
-Wuseless-cast
|
|
||||||
-Wno-sign-promotion
|
|
||||||
-Wcast-align
|
|
||||||
-Wunused
|
|
||||||
-Woverloaded-virtual
|
|
||||||
-Wconversion
|
|
||||||
-Wsign-conversion
|
|
||||||
-Wmisleading-indentation
|
|
||||||
-Wduplicated-cond
|
|
||||||
-Wduplicated-branches
|
|
||||||
-Wlogical-op
|
|
||||||
-Wnull-dereference
|
|
||||||
-Wformat=2
|
|
||||||
-Wformat-overflow
|
|
||||||
-Wformat-truncation
|
|
||||||
-Wdouble-promotion
|
|
||||||
-Wundef
|
|
||||||
-DLOCAL
|
|
||||||
-Wno-unknown-pragmas
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-O2
|
|
||||||
-DLOCAL
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-g3
|
|
||||||
-fsanitize=address,undefined
|
|
||||||
-DLOCAL
|
|
||||||
|
|
@ -19,7 +19,6 @@ setup:
|
||||||
test -d build || mkdir -p build
|
test -d build || mkdir -p build
|
||||||
test -d io || mkdir -p io
|
test -d io || mkdir -p io
|
||||||
test -f compile_flags.txt && echo -std=c++$(VERSION) >>compile_flags.txt
|
test -f compile_flags.txt && echo -std=c++$(VERSION) >>compile_flags.txt
|
||||||
test -f .clangd && echo -e "\t\t-std=c++$(VERSION)" >>.clangd
|
|
||||||
|
|
||||||
init:
|
init:
|
||||||
make setup
|
make setup
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -57,12 +58,17 @@ def scrape(url: str) -> list[tuple[str, str]]:
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
print("Usage: atcoder.py <contest_id> <problem_letter>", file=sys.stderr)
|
result = {
|
||||||
print("Example: atcoder.py abc042 a", file=sys.stderr)
|
"success": False,
|
||||||
|
"error": "Usage: atcoder.py <contest_id> <problem_letter>",
|
||||||
|
"problem_id": None,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
contest_id = sys.argv[1]
|
contest_id = sys.argv[1]
|
||||||
problem_letter = sys.argv[2]
|
problem_letter = sys.argv[2]
|
||||||
|
problem_id = contest_id + problem_letter
|
||||||
|
|
||||||
url = parse_problem_url(contest_id, problem_letter)
|
url = parse_problem_url(contest_id, problem_letter)
|
||||||
print(f"Scraping: {url}", file=sys.stderr)
|
print(f"Scraping: {url}", file=sys.stderr)
|
||||||
|
|
@ -70,17 +76,27 @@ def main():
|
||||||
tests = scrape(url)
|
tests = scrape(url)
|
||||||
|
|
||||||
if not tests:
|
if not tests:
|
||||||
print(f"No tests found for {contest_id} {problem_letter}", file=sys.stderr)
|
result = {
|
||||||
|
"success": False,
|
||||||
|
"error": f"No tests found for {contest_id} {problem_letter}",
|
||||||
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("---INPUT---")
|
test_cases = []
|
||||||
print(len(tests))
|
|
||||||
for input_data, output_data in tests:
|
for input_data, output_data in tests:
|
||||||
print(input_data)
|
test_cases.append({"input": input_data, "output": output_data})
|
||||||
print("---OUTPUT---")
|
|
||||||
for input_data, output_data in tests:
|
result = {
|
||||||
print(output_data)
|
"success": True,
|
||||||
print("---END---")
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
"test_cases": test_cases,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import cloudscraper
|
import cloudscraper
|
||||||
|
|
@ -73,31 +74,43 @@ def scrape_sample_tests(url: str):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
print("Usage: codeforces.py <contest_id> <problem_letter>", file=sys.stderr)
|
result = {
|
||||||
print("Example: codeforces.py 1234 A", file=sys.stderr)
|
"success": False,
|
||||||
|
"error": "Usage: codeforces.py <contest_id> <problem_letter>",
|
||||||
|
"problem_id": None,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
contest_id = sys.argv[1]
|
contest_id = sys.argv[1]
|
||||||
problem_letter = sys.argv[2]
|
problem_letter = sys.argv[2]
|
||||||
|
problem_id = contest_id + problem_letter.upper()
|
||||||
|
|
||||||
url = parse_problem_url(contest_id, problem_letter)
|
url = parse_problem_url(contest_id, problem_letter)
|
||||||
tests = scrape_sample_tests(url)
|
tests = scrape_sample_tests(url)
|
||||||
|
|
||||||
if not tests:
|
if not tests:
|
||||||
print(f"No tests found for {contest_id} {problem_letter}", file=sys.stderr)
|
result = {
|
||||||
print(
|
"success": False,
|
||||||
"Consider adding test cases manually to the io/ directory", file=sys.stderr
|
"error": f"No tests found for {contest_id} {problem_letter}",
|
||||||
)
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("---INPUT---")
|
test_cases = []
|
||||||
print(len(tests))
|
|
||||||
for input_data, output_data in tests:
|
for input_data, output_data in tests:
|
||||||
print(input_data)
|
test_cases.append({"input": input_data, "output": output_data})
|
||||||
print("---OUTPUT---")
|
|
||||||
for input_data, output_data in tests:
|
result = {
|
||||||
print(output_data)
|
"success": True,
|
||||||
print("---END---")
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
"test_cases": test_cases,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -57,31 +58,54 @@ def scrape(url: str) -> list[tuple[str, str]]:
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: cses.py <problem_id_or_url>", file=sys.stderr)
|
result = {
|
||||||
|
"success": False,
|
||||||
|
"error": "Usage: cses.py <problem_id_or_url>",
|
||||||
|
"problem_id": None,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
problem_input = sys.argv[1]
|
problem_input = sys.argv[1]
|
||||||
url = parse_problem_url(problem_input)
|
url = parse_problem_url(problem_input)
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
print(f"Invalid problem input: {problem_input}", file=sys.stderr)
|
result = {
|
||||||
print("Use either problem ID (e.g., 1068) or full URL", file=sys.stderr)
|
"success": False,
|
||||||
|
"error": f"Invalid problem input: {problem_input}. Use either problem ID (e.g., 1068) or full URL",
|
||||||
|
"problem_id": problem_input if problem_input.isdigit() else None,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
tests = scrape(url)
|
tests = scrape(url)
|
||||||
|
|
||||||
|
problem_id = (
|
||||||
|
problem_input if problem_input.isdigit() else problem_input.split("/")[-1]
|
||||||
|
)
|
||||||
|
|
||||||
if not tests:
|
if not tests:
|
||||||
print(f"No tests found for {problem_input}", file=sys.stderr)
|
result = {
|
||||||
|
"success": False,
|
||||||
|
"error": f"No tests found for {problem_input}",
|
||||||
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
print(json.dumps(result))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("---INPUT---")
|
test_cases = []
|
||||||
print(len(tests))
|
|
||||||
for input_data, output_data in tests:
|
for input_data, output_data in tests:
|
||||||
print(input_data)
|
test_cases.append({"input": input_data, "output": output_data})
|
||||||
print("---OUTPUT---")
|
|
||||||
for input_data, output_data in tests:
|
result = {
|
||||||
print(output_data)
|
"success": True,
|
||||||
print("---END---")
|
"problem_id": problem_id,
|
||||||
|
"url": url,
|
||||||
|
"test_cases": test_cases,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue