fix(ci): format

This commit is contained in:
Barrett Ruth 2025-09-18 20:04:36 -04:00
parent 228b088bf7
commit bc315818e4
25 changed files with 1759 additions and 1660 deletions

3
.editorconfig Normal file
View file

@ -0,0 +1,3 @@
[*.lua]
indent_style = space
indent_size = 2

18
.github/workflows/luarocks.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: Push to Luarocks
on:
push:
tags:
- "*"
pull_request:
workflow_dispatch:
jobs:
luarocks-upload:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: LuaRocks Upload
uses: nvim-neorocks/luarocks-tag-release@v7
env:
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}

6
.stylua.toml Normal file
View file

@ -0,0 +1,6 @@
column_width = 100
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferSingle"
call_parentheses = "Always"

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Raphael
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,6 +1,6 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ""
vim.opt_local.signcolumn = "no"
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true

View file

@ -1,6 +1,6 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ""
vim.opt_local.signcolumn = "no"
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true

View file

@ -1,7 +1,7 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ""
vim.opt_local.signcolumn = "no"
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.modifiable = true

View file

@ -1,7 +1,7 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ""
vim.opt_local.signcolumn = "no"
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.foldcolumn = "0"
vim.opt_local.foldcolumn = '0'

View file

@ -1,6 +1,6 @@
vim.filetype.add({
extension = {
cpin = "cpin",
cpout = "cpout",
cpin = 'cpin',
cpout = 'cpout',
},
})

View file

@ -20,17 +20,17 @@
local M = {}
local cache_file = vim.fn.stdpath("data") .. "/cp-nvim.json"
local cache_file = vim.fn.stdpath('data') .. '/cp-nvim.json'
local cache_data = {}
---@param platform string
---@return number?
local function get_expiry_date(platform)
vim.validate({
platform = { platform, "string" },
platform = { platform, 'string' },
})
if platform == "cses" then
if platform == 'cses' then
return os.time() + (30 * 24 * 60 * 60)
end
return nil
@ -41,11 +41,11 @@ end
---@return boolean
local function is_cache_valid(contest_data, platform)
vim.validate({
contest_data = { contest_data, "table" },
platform = { platform, "string" },
contest_data = { contest_data, 'table' },
platform = { platform, 'string' },
})
if platform ~= "cses" then
if platform ~= 'cses' then
return true
end
@ -69,7 +69,7 @@ function M.load()
return
end
local ok, decoded = pcall(vim.json.decode, table.concat(content, "\n"))
local ok, decoded = pcall(vim.json.decode, table.concat(content, '\n'))
if ok then
cache_data = decoded
else
@ -78,9 +78,9 @@ function M.load()
end
function M.save()
vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ":h"), "p")
vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ':h'), 'p')
local encoded = vim.json.encode(cache_data)
vim.fn.writefile(vim.split(encoded, "\n"), cache_file)
vim.fn.writefile(vim.split(encoded, '\n'), cache_file)
end
---@param platform string
@ -88,8 +88,8 @@ end
---@return ContestData?
function M.get_contest_data(platform, contest_id)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
})
if not cache_data[platform] then
@ -113,9 +113,9 @@ end
---@param problems Problem[]
function M.set_contest_data(platform, contest_id, problems)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
problems = { problems, "table" },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
problems = { problems, 'table' },
})
if not cache_data[platform] then
@ -124,7 +124,7 @@ function M.set_contest_data(platform, contest_id, problems)
cache_data[platform][contest_id] = {
problems = problems,
scraped_at = os.date("%Y-%m-%d"),
scraped_at = os.date('%Y-%m-%d'),
expires_at = get_expiry_date(platform),
}
@ -135,8 +135,8 @@ end
---@param contest_id string
function M.clear_contest_data(platform, contest_id)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
})
if cache_data[platform] and cache_data[platform][contest_id] then
@ -151,12 +151,12 @@ end
---@return CachedTestCase[]?
function M.get_test_cases(platform, contest_id, problem_id)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
problem_id = { problem_id, { "string", "nil" }, true },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
problem_id = { problem_id, { 'string', 'nil' }, true },
})
local problem_key = problem_id and (contest_id .. "_" .. problem_id) or contest_id
local problem_key = problem_id and (contest_id .. '_' .. problem_id) or contest_id
if not cache_data[platform] or not cache_data[platform][problem_key] then
return nil
end
@ -169,13 +169,13 @@ end
---@param test_cases CachedTestCase[]
function M.set_test_cases(platform, contest_id, problem_id, test_cases)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
problem_id = { problem_id, { "string", "nil" }, true },
test_cases = { test_cases, "table" },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
problem_id = { problem_id, { 'string', 'nil' }, true },
test_cases = { test_cases, 'table' },
})
local problem_key = problem_id and (contest_id .. "_" .. problem_id) or contest_id
local problem_key = problem_id and (contest_id .. '_' .. problem_id) or contest_id
if not cache_data[platform] then
cache_data[platform] = {}
end

View file

@ -48,7 +48,7 @@
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
local M = {}
local constants = require("cp.constants")
local constants = require('cp.constants')
---@type cp.Config
M.defaults = {
@ -68,34 +68,34 @@ M.defaults = {
---@return cp.Config
function M.setup(user_config)
vim.validate({
user_config = { user_config, { "table", "nil" }, true },
user_config = { user_config, { 'table', 'nil' }, true },
})
if user_config then
vim.validate({
contests = { user_config.contests, { "table", "nil" }, true },
snippets = { user_config.snippets, { "table", "nil" }, true },
hooks = { user_config.hooks, { "table", "nil" }, true },
debug = { user_config.debug, { "boolean", "nil" }, true },
scrapers = { user_config.scrapers, { "table", "nil" }, true },
filename = { user_config.filename, { "function", "nil" }, true },
contests = { user_config.contests, { 'table', 'nil' }, true },
snippets = { user_config.snippets, { 'table', 'nil' }, true },
hooks = { user_config.hooks, { 'table', 'nil' }, true },
debug = { user_config.debug, { 'boolean', 'nil' }, true },
scrapers = { user_config.scrapers, { 'table', 'nil' }, true },
filename = { user_config.filename, { 'function', 'nil' }, true },
})
if user_config.hooks then
vim.validate({
before_run = {
user_config.hooks.before_run,
{ "function", "nil" },
{ 'function', 'nil' },
true,
},
before_debug = {
user_config.hooks.before_debug,
{ "function", "nil" },
{ 'function', 'nil' },
true,
},
setup_code = {
user_config.hooks.setup_code,
{ "function", "nil" },
{ 'function', 'nil' },
true,
},
})
@ -104,16 +104,19 @@ function M.setup(user_config)
if user_config.contests then
for contest_name, contest_config in pairs(user_config.contests) do
for lang_name, lang_config in pairs(contest_config) do
if type(lang_config) == "table" and lang_config.extension then
if type(lang_config) == 'table' and lang_config.extension then
if
not vim.tbl_contains(vim.tbl_keys(constants.filetype_to_language), lang_config.extension)
not vim.tbl_contains(
vim.tbl_keys(constants.filetype_to_language),
lang_config.extension
)
then
error(
("Invalid extension '%s' for language '%s' in contest '%s'. Valid extensions: %s"):format(
lang_config.extension,
lang_name,
contest_name,
table.concat(vim.tbl_keys(constants.filetype_to_language), ", ")
table.concat(vim.tbl_keys(constants.filetype_to_language), ', ')
)
)
end
@ -128,18 +131,20 @@ function M.setup(user_config)
error(
("Invalid contest '%s' in scrapers config. Valid contests: %s"):format(
contest_name,
table.concat(constants.PLATFORMS, ", ")
table.concat(constants.PLATFORMS, ', ')
)
)
end
if type(enabled) ~= "boolean" then
error(("Scraper setting for '%s' must be boolean, got %s"):format(contest_name, type(enabled)))
if type(enabled) ~= 'boolean' then
error(
("Scraper setting for '%s' must be boolean, got %s"):format(contest_name, type(enabled))
)
end
end
end
end
local config = vim.tbl_deep_extend("force", M.defaults, user_config or {})
local config = vim.tbl_deep_extend('force', M.defaults, user_config or {})
return config
end
@ -148,8 +153,8 @@ end
---@return string
local function default_filename(contest_id, problem_id)
vim.validate({
contest_id = { contest_id, "string" },
problem_id = { problem_id, { "string", "nil" }, true },
contest_id = { contest_id, 'string' },
problem_id = { problem_id, { 'string', 'nil' }, true },
})
if problem_id then

View file

@ -1,10 +1,10 @@
local M = {}
M.PLATFORMS = { "atcoder", "codeforces", "cses" }
M.ACTIONS = { "test", "next", "prev" }
M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' }
M.ACTIONS = { 'test', 'next', 'prev' }
M.CPP = "cpp"
M.PYTHON = "python"
M.CPP = 'cpp'
M.PYTHON = 'python'
---@type table<string, string>
M.filetype_to_language = {
@ -17,27 +17,27 @@ M.filetype_to_language = {
---@type table<string, string>
M.canonical_filetypes = {
[M.CPP] = "cpp",
[M.PYTHON] = "python",
[M.CPP] = 'cpp',
[M.PYTHON] = 'python',
}
---@type table<number, string>
M.signal_codes = {
[128] = "SIGILL",
[130] = "SIGINT",
[131] = "SIGQUIT",
[132] = "SIGILL",
[133] = "SIGTRAP",
[134] = "SIGABRT",
[135] = "SIGBUS",
[136] = "SIGFPE",
[137] = "SIGKILL",
[138] = "SIGUSR1",
[139] = "SIGSEGV",
[140] = "SIGUSR2",
[141] = "SIGPIPE",
[142] = "SIGALRM",
[143] = "SIGTERM",
[128] = 'SIGILL',
[130] = 'SIGINT',
[131] = 'SIGQUIT',
[132] = 'SIGILL',
[133] = 'SIGTRAP',
[134] = 'SIGABRT',
[135] = 'SIGBUS',
[136] = 'SIGFPE',
[137] = 'SIGKILL',
[138] = 'SIGUSR1',
[139] = 'SIGSEGV',
[140] = 'SIGUSR2',
[141] = 'SIGPIPE',
[142] = 'SIGALRM',
[143] = 'SIGTERM',
}
return M

View file

@ -6,9 +6,9 @@
---@field timed_out boolean
local M = {}
local logger = require("cp.log")
local logger = require('cp.log')
local constants = require("cp.constants")
local constants = require('cp.constants')
local filetype_to_language = constants.filetype_to_language
---@param source_file string
@ -16,13 +16,13 @@ local filetype_to_language = constants.filetype_to_language
---@return string
local function get_language_from_file(source_file, contest_config)
vim.validate({
source_file = { source_file, "string" },
contest_config = { contest_config, "table" },
source_file = { source_file, 'string' },
contest_config = { contest_config, 'table' },
})
local extension = vim.fn.fnamemodify(source_file, ":e")
local extension = vim.fn.fnamemodify(source_file, ':e')
local language = filetype_to_language[extension] or contest_config.default_language
logger.log(("detected language: %s (extension: %s)"):format(language, extension))
logger.log(('detected language: %s (extension: %s)'):format(language, extension))
return language
end
@ -31,15 +31,15 @@ end
---@return string[]
local function substitute_template(cmd_template, substitutions)
vim.validate({
cmd_template = { cmd_template, "table" },
substitutions = { substitutions, "table" },
cmd_template = { cmd_template, 'table' },
substitutions = { substitutions, 'table' },
})
local result = {}
for _, arg in ipairs(cmd_template) do
local substituted = arg
for key, value in pairs(substitutions) do
substituted = substituted:gsub("{" .. key .. "}", value)
substituted = substituted:gsub('{' .. key .. '}', value)
end
table.insert(result, substituted)
end
@ -52,9 +52,9 @@ end
---@return string[]
local function build_command(cmd_template, executable, substitutions)
vim.validate({
cmd_template = { cmd_template, "table" },
executable = { executable, { "string", "nil" }, true },
substitutions = { substitutions, "table" },
cmd_template = { cmd_template, 'table' },
executable = { executable, { 'string', 'nil' }, true },
substitutions = { substitutions, 'table' },
})
local cmd = substitute_template(cmd_template, substitutions)
@ -65,7 +65,7 @@ local function build_command(cmd_template, executable, substitutions)
end
local function ensure_directories()
vim.system({ "mkdir", "-p", "build", "io" }):wait()
vim.system({ 'mkdir', '-p', 'build', 'io' }):wait()
end
---@param language_config table
@ -73,26 +73,29 @@ end
---@return {code: integer, stderr: string}
function M.compile_generic(language_config, substitutions)
vim.validate({
language_config = { language_config, "table" },
substitutions = { substitutions, "table" },
language_config = { language_config, 'table' },
substitutions = { substitutions, 'table' },
})
if not language_config.compile then
logger.log("no compilation step required")
return { code = 0, stderr = "" }
logger.log('no compilation step required')
return { code = 0, stderr = '' }
end
local compile_cmd = substitute_template(language_config.compile, substitutions)
logger.log(("compiling: %s"):format(table.concat(compile_cmd, " ")))
logger.log(('compiling: %s'):format(table.concat(compile_cmd, ' ')))
local start_time = vim.uv.hrtime()
local result = vim.system(compile_cmd, { text = true }):wait()
local compile_time = (vim.uv.hrtime() - start_time) / 1000000
if result.code == 0 then
logger.log(("compilation successful (%.1fms)"):format(compile_time))
logger.log(('compilation successful (%.1fms)'):format(compile_time))
else
logger.log(("compilation failed (%.1fms): %s"):format(compile_time, result.stderr), vim.log.levels.WARN)
logger.log(
('compilation failed (%.1fms): %s'):format(compile_time, result.stderr),
vim.log.levels.WARN
)
end
return result
@ -104,20 +107,22 @@ end
---@return ExecuteResult
local function execute_command(cmd, input_data, timeout_ms)
vim.validate({
cmd = { cmd, "table" },
input_data = { input_data, "string" },
timeout_ms = { timeout_ms, "number" },
cmd = { cmd, 'table' },
input_data = { input_data, 'string' },
timeout_ms = { timeout_ms, 'number' },
})
logger.log(("executing: %s"):format(table.concat(cmd, " ")))
logger.log(('executing: %s'):format(table.concat(cmd, ' ')))
local start_time = vim.uv.hrtime()
local result = vim.system(cmd, {
local result = vim
.system(cmd, {
stdin = input_data,
timeout = timeout_ms,
text = true,
}):wait()
})
:wait()
local end_time = vim.uv.hrtime()
local execution_time = (end_time - start_time) / 1000000
@ -125,16 +130,19 @@ local function execute_command(cmd, input_data, timeout_ms)
local actual_code = result.code or 0
if result.code == 124 then
logger.log(("execution timed out after %.1fms"):format(execution_time), vim.log.levels.WARN)
logger.log(('execution timed out after %.1fms'):format(execution_time), vim.log.levels.WARN)
elseif actual_code ~= 0 then
logger.log(("execution failed (exit code %d, %.1fms)"):format(actual_code, execution_time), vim.log.levels.WARN)
logger.log(
('execution failed (exit code %d, %.1fms)'):format(actual_code, execution_time),
vim.log.levels.WARN
)
else
logger.log(("execution successful (%.1fms)"):format(execution_time))
logger.log(('execution successful (%.1fms)'):format(execution_time))
end
return {
stdout = result.stdout or "",
stderr = result.stderr or "",
stdout = result.stdout or '',
stderr = result.stderr or '',
code = actual_code,
time_ms = execution_time,
timed_out = result.code == 124,
@ -147,31 +155,31 @@ end
---@return string
local function format_output(exec_result, expected_file, is_debug)
vim.validate({
exec_result = { exec_result, "table" },
expected_file = { expected_file, "string" },
is_debug = { is_debug, "boolean" },
exec_result = { exec_result, 'table' },
expected_file = { expected_file, 'string' },
is_debug = { is_debug, 'boolean' },
})
local output_lines = { exec_result.stdout }
local metadata_lines = {}
if exec_result.timed_out then
table.insert(metadata_lines, "[code]: 124 (TIMEOUT)")
table.insert(metadata_lines, '[code]: 124 (TIMEOUT)')
elseif exec_result.code >= 128 then
local signal_name = constants.signal_codes[exec_result.code] or "SIGNAL"
table.insert(metadata_lines, ("[code]: %d (%s)"):format(exec_result.code, signal_name))
local signal_name = constants.signal_codes[exec_result.code] or 'SIGNAL'
table.insert(metadata_lines, ('[code]: %d (%s)'):format(exec_result.code, signal_name))
else
table.insert(metadata_lines, ("[code]: %d"):format(exec_result.code))
table.insert(metadata_lines, ('[code]: %d'):format(exec_result.code))
end
table.insert(metadata_lines, ("[time]: %.2f ms"):format(exec_result.time_ms))
table.insert(metadata_lines, ("[debug]: %s"):format(is_debug and "true" or "false"))
table.insert(metadata_lines, ('[time]: %.2f ms'):format(exec_result.time_ms))
table.insert(metadata_lines, ('[debug]: %s'):format(is_debug and 'true' or '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 actual_lines = vim.split(exec_result.stdout, '\n')
while #actual_lines > 0 and actual_lines[#actual_lines] == "" do
while #actual_lines > 0 and actual_lines[#actual_lines] == '' do
table.remove(actual_lines)
end
@ -185,10 +193,10 @@ local function format_output(exec_result, expected_file, is_debug)
end
end
table.insert(metadata_lines, ("[ok]: %s"):format(ok and "true" or "false"))
table.insert(metadata_lines, ('[ok]: %s'):format(ok and 'true' or 'false'))
end
return table.concat(output_lines, "") .. "\n" .. table.concat(metadata_lines, "\n")
return table.concat(output_lines, '') .. '\n' .. table.concat(metadata_lines, '\n')
end
---@param ctx ProblemContext
@ -197,15 +205,15 @@ end
---@return boolean success
function M.compile_problem(ctx, contest_config, is_debug)
vim.validate({
ctx = { ctx, "table" },
contest_config = { contest_config, "table" },
ctx = { ctx, 'table' },
contest_config = { contest_config, 'table' },
})
local language = get_language_from_file(ctx.source_file, contest_config)
local language_config = contest_config[language]
if not language_config then
logger.log("No configuration for language: " .. language, vim.log.levels.ERROR)
logger.log('No configuration for language: ' .. language, vim.log.levels.ERROR)
return false
end
@ -215,15 +223,19 @@ function M.compile_problem(ctx, contest_config, is_debug)
version = tostring(language_config.version),
}
local compile_cmd = (is_debug and language_config.debug) and language_config.debug or language_config.compile
local compile_cmd = (is_debug and language_config.debug) and language_config.debug
or language_config.compile
if compile_cmd then
language_config.compile = compile_cmd
local compile_result = M.compile_generic(language_config, substitutions)
if compile_result.code ~= 0 then
logger.log("compilation failed: " .. (compile_result.stderr or "unknown error"), vim.log.levels.ERROR)
logger.log(
'compilation failed: ' .. (compile_result.stderr or 'unknown error'),
vim.log.levels.ERROR
)
return false
end
logger.log(("compilation successful (%s)"):format(is_debug and "debug mode" or "test mode"))
logger.log(('compilation successful (%s)'):format(is_debug and 'debug mode' or 'test mode'))
end
return true
@ -231,9 +243,9 @@ end
function M.run_problem(ctx, contest_config, is_debug)
vim.validate({
ctx = { ctx, "table" },
contest_config = { contest_config, "table" },
is_debug = { is_debug, "boolean" },
ctx = { ctx, 'table' },
contest_config = { contest_config, 'table' },
is_debug = { is_debug, 'boolean' },
})
ensure_directories()
@ -242,7 +254,7 @@ function M.run_problem(ctx, contest_config, is_debug)
local language_config = contest_config[language]
if not language_config then
vim.fn.writefile({ "Error: No configuration for language: " .. language }, ctx.output_file)
vim.fn.writefile({ 'Error: No configuration for language: ' .. language }, ctx.output_file)
return
end
@ -261,9 +273,9 @@ function M.run_problem(ctx, contest_config, is_debug)
end
end
local input_data = ""
local input_data = ''
if vim.fn.filereadable(ctx.input_file) == 1 then
input_data = table.concat(vim.fn.readfile(ctx.input_file), "\n") .. "\n"
input_data = table.concat(vim.fn.readfile(ctx.input_file), '\n') .. '\n'
end
local run_cmd = build_command(language_config.run, language_config.executable, substitutions)
@ -272,12 +284,12 @@ function M.run_problem(ctx, contest_config, is_debug)
local output_buf = vim.fn.bufnr(ctx.output_file)
if output_buf ~= -1 then
vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, vim.split(formatted_output, "\n"))
vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, vim.split(formatted_output, '\n'))
vim.api.nvim_buf_call(output_buf, function()
vim.cmd.write()
end)
else
vim.fn.writefile(vim.split(formatted_output, "\n"), ctx.output_file)
vim.fn.writefile(vim.split(formatted_output, '\n'), ctx.output_file)
end
end

View file

@ -1,88 +1,88 @@
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")
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+")
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")
if vim.fn.executable('uv') == 1 then
vim.health.ok('uv executable found')
local result = vim.system({ "uv", "--version" }, { text = true }):wait()
local result = vim.system({ 'uv', '--version' }, { text = true }):wait()
if result.code == 0 then
vim.health.info("uv version: " .. result.stdout:gsub("\n", ""))
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")
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")
local venv_dir = plugin_path .. "/.venv"
local plugin_path = debug.getinfo(1, 'S').source:sub(2)
plugin_path = vim.fn.fnamemodify(plugin_path, ':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)
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")
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")
local plugin_path = debug.getinfo(1, 'S').source:sub(2)
plugin_path = vim.fn.fnamemodify(plugin_path, ':h:h:h')
local scrapers = { "atcoder.py", "codeforces.py", "cses.py" }
local scrapers = { 'atcoder.py', 'codeforces.py', 'cses.py' }
for _, scraper in ipairs(scrapers) do
local scraper_path = plugin_path .. "/scrapers/" .. scraper
local scraper_path = plugin_path .. '/scrapers/' .. scraper
if vim.fn.filereadable(scraper_path) == 1 then
vim.health.ok("Scraper found: " .. scraper)
vim.health.ok('Scraper found: ' .. scraper)
else
vim.health.error("Missing scraper: " .. scraper)
vim.health.error('Missing scraper: ' .. scraper)
end
end
end
local function check_luasnip()
local has_luasnip, luasnip = pcall(require, "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)
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")
vim.health.info('LuaSnip not available - template expansion will be limited')
end
end
local function check_config()
vim.health.ok("Plugin ready")
vim.health.ok('Plugin ready')
local cp = require("cp")
local cp = require('cp')
local context = cp.get_current_context()
if context.platform then
local info = context.platform
if context.contest_id then
info = info .. " " .. context.contest_id
info = info .. ' ' .. context.contest_id
if context.problem_id then
info = info .. " " .. context.problem_id
info = info .. ' ' .. context.problem_id
end
end
vim.health.info("Current context: " .. info)
vim.health.info('Current context: ' .. info)
else
vim.health.info("No contest context set")
vim.health.info('No contest context set')
end
end
function M.check()
local version = require("cp.version")
vim.health.start("cp.nvim health check")
local version = require('cp.version')
vim.health.start('cp.nvim health check')
vim.health.info("Version: " .. version.version)
vim.health.info('Version: ' .. version.version)
check_nvim_version()
check_uv()

View file

@ -1,14 +1,14 @@
local M = {}
local config_module = require("cp.config")
local snippets = require("cp.snippets")
local scrape = require("cp.scrape")
local logger = require("cp.log")
local problem = require("cp.problem")
local cache = require("cp.cache")
local config_module = require('cp.config')
local snippets = require('cp.snippets')
local scrape = require('cp.scrape')
local logger = require('cp.log')
local problem = require('cp.problem')
local cache = require('cp.cache')
if not vim.fn.has("nvim-0.10.0") then
vim.notify("[cp.nvim]: requires nvim-0.10.0+", vim.log.levels.ERROR)
if not vim.fn.has('nvim-0.10.0') then
vim.notify('[cp.nvim]: requires nvim-0.10.0+', vim.log.levels.ERROR)
return {}
end
@ -28,19 +28,22 @@ local state = {
test_panel_active = false,
}
local constants = require("cp.constants")
local constants = require('cp.constants')
local platforms = constants.PLATFORMS
local actions = constants.ACTIONS
local function set_platform(platform)
if not vim.tbl_contains(platforms, platform) then
logger.log(("unknown platform. Available: [%s]"):format(table.concat(platforms, ", ")), vim.log.levels.ERROR)
logger.log(
('unknown platform. Available: [%s]'):format(table.concat(platforms, ', ')),
vim.log.levels.ERROR
)
return false
end
state.platform = platform
vim.fn.mkdir("build", "p")
vim.fn.mkdir("io", "p")
vim.fn.mkdir('build', 'p')
vim.fn.mkdir('io', 'p')
return true
end
@ -49,12 +52,12 @@ end
---@param language? string
local function setup_problem(contest_id, problem_id, language)
if not state.platform then
logger.log("no platform set. run :CP <platform> <contest> first", vim.log.levels.ERROR)
logger.log('no platform set. run :CP <platform> <contest> first', vim.log.levels.ERROR)
return
end
local problem_name = state.platform == "cses" and contest_id or (contest_id .. (problem_id or ""))
logger.log(("setting up problem: %s"):format(problem_name))
local problem_name = state.platform == 'cses' and contest_id or (contest_id .. (problem_id or ''))
logger.log(('setting up problem: %s'):format(problem_name))
local ctx = problem.create_context(state.platform, contest_id, problem_id, config, language)
@ -62,7 +65,7 @@ local function setup_problem(contest_id, problem_id, language)
local metadata_result = scrape.scrape_contest_metadata(state.platform, contest_id)
if not metadata_result.success then
logger.log(
"failed to load contest metadata: " .. (metadata_result.error or "unknown error"),
'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'),
vim.log.levels.WARN
)
end
@ -77,23 +80,26 @@ local function setup_problem(contest_id, problem_id, language)
local scrape_result = scrape.scrape_problem(ctx)
if not scrape_result.success then
logger.log("scraping failed: " .. (scrape_result.error or "unknown error"), vim.log.levels.ERROR)
logger.log(
'scraping failed: ' .. (scrape_result.error or 'unknown error'),
vim.log.levels.ERROR
)
return
end
local test_count = scrape_result.test_count or 0
logger.log(("scraped %d test case(s) for %s"):format(test_count, scrape_result.problem_id))
logger.log(('scraped %d test case(s) for %s'):format(test_count, scrape_result.problem_id))
state.test_cases = scrape_result.test_cases
if scrape_result.test_cases then
cache.set_test_cases(state.platform, contest_id, problem_id, scrape_result.test_cases)
end
else
logger.log(("scraping disabled for %s"):format(state.platform))
logger.log(('scraping disabled for %s'):format(state.platform))
state.test_cases = nil
end
vim.cmd("silent only")
vim.cmd('silent only')
state.contest_id = contest_id
state.problem_id = problem_id
@ -101,13 +107,13 @@ local function setup_problem(contest_id, problem_id, language)
vim.cmd.e(ctx.source_file)
local source_buf = vim.api.nvim_get_current_buf()
if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == "" then
local has_luasnip, luasnip = pcall(require, "luasnip")
if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then
local has_luasnip, luasnip = pcall(require, 'luasnip')
if has_luasnip then
local filetype = vim.api.nvim_get_option_value("filetype", { buf = source_buf })
local filetype = vim.api.nvim_get_option_value('filetype', { buf = source_buf })
local language_name = constants.filetype_to_language[filetype]
local canonical_language = constants.canonical_filetypes[language_name] or language_name
local prefixed_trigger = ("cp.nvim/%s.%s"):format(state.platform, canonical_language)
local prefixed_trigger = ('cp.nvim/%s.%s'):format(state.platform, canonical_language)
vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger })
vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger })
@ -117,13 +123,13 @@ local function setup_problem(contest_id, problem_id, language)
if luasnip.expandable() then
luasnip.expand()
else
vim.api.nvim_buf_set_lines(0, 0, 1, false, { "" })
vim.api.nvim_buf_set_lines(0, 0, 1, false, { '' })
vim.api.nvim_win_set_cursor(0, { 1, 0 })
end
vim.cmd.stopinsert()
end)
else
vim.api.nvim_input(("i%s<c-space><esc>"):format(state.platform))
vim.api.nvim_input(('i%s<c-space><esc>'):format(state.platform))
end
end
@ -131,13 +137,13 @@ local function setup_problem(contest_id, problem_id, language)
config.hooks.setup_code(ctx)
end
logger.log(("switched to problem %s"):format(ctx.problem_name))
logger.log(('switched to problem %s'):format(ctx.problem_name))
end
local function get_current_problem()
local filename = vim.fn.expand("%:t:r")
if filename == "" then
logger.log("no file open", vim.log.levels.ERROR)
local filename = vim.fn.expand('%:t:r')
if filename == '' then
logger.log('no file open', vim.log.levels.ERROR)
return nil
end
return filename
@ -146,18 +152,18 @@ end
local function toggle_test_panel(is_debug)
if state.test_panel_active then
if state.saved_session then
vim.cmd(("source %s"):format(state.saved_session))
vim.cmd(('source %s'):format(state.saved_session))
vim.fn.delete(state.saved_session)
state.saved_session = nil
end
state.test_panel_active = false
logger.log("test panel closed")
logger.log('test panel closed')
return
end
if not state.platform then
logger.log(
"No contest configured. Use :CP <platform> <contest> <problem> to set up first.",
'No contest configured. Use :CP <platform> <contest> <problem> to set up first.',
vim.log.levels.ERROR
)
return
@ -169,37 +175,37 @@ local function toggle_test_panel(is_debug)
end
local ctx = problem.create_context(state.platform, state.contest_id, state.problem_id, config)
local test_module = require("cp.test")
local test_module = require('cp.test')
if not test_module.load_test_cases(ctx, state) then
logger.log("no test cases found", vim.log.levels.WARN)
logger.log('no test cases found', vim.log.levels.WARN)
return
end
state.saved_session = vim.fn.tempname()
vim.cmd(("mksession! %s"):format(state.saved_session))
vim.cmd(('mksession! %s'):format(state.saved_session))
vim.cmd("silent only")
vim.cmd('silent only')
local tab_buf = vim.api.nvim_create_buf(false, true)
local expected_buf = vim.api.nvim_create_buf(false, true)
local actual_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = tab_buf })
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = expected_buf })
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = actual_buf })
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = tab_buf })
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = expected_buf })
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = actual_buf })
local main_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(main_win, tab_buf)
vim.api.nvim_set_option_value("filetype", "cptest", { buf = tab_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = tab_buf })
vim.cmd.split()
vim.api.nvim_win_set_buf(0, actual_buf)
vim.api.nvim_set_option_value("filetype", "cptest", { buf = actual_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = actual_buf })
vim.cmd.vsplit()
vim.api.nvim_win_set_buf(0, expected_buf)
vim.api.nvim_set_option_value("filetype", "cptest", { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = expected_buf })
local expected_win = vim.fn.bufwinid(expected_buf)
local actual_win = vim.fn.bufwinid(actual_buf)
@ -224,7 +230,7 @@ local function toggle_test_panel(is_debug)
local max_time_width = 0
for _, test_case in ipairs(test_state.test_cases) do
local status_text = test_case.status == "pending" and "" or string.upper(test_case.status)
local status_text = test_case.status == 'pending' and '' or string.upper(test_case.status)
max_status_width = math.max(max_status_width, #status_text)
if test_case.code then
@ -232,30 +238,30 @@ local function toggle_test_panel(is_debug)
end
if test_case.time_ms then
local time_text = string.format("%.0fms", test_case.time_ms)
local time_text = string.format('%.0fms', test_case.time_ms)
max_time_width = math.max(max_time_width, #time_text)
end
end
for i, test_case in ipairs(test_state.test_cases) do
local prefix = i == test_state.current_index and "> " or " "
local tab = string.format("%s%d.", prefix, i)
local prefix = i == test_state.current_index and '> ' or ' '
local tab = string.format('%s%d.', prefix, i)
if test_case.ok ~= nil then
tab = tab .. string.format(" [ok:%-5s]", tostring(test_case.ok))
tab = tab .. string.format(' [ok:%-5s]', tostring(test_case.ok))
end
if test_case.code then
tab = tab .. string.format(" [code:%-" .. max_code_width .. "s]", tostring(test_case.code))
tab = tab .. string.format(' [code:%-' .. max_code_width .. 's]', tostring(test_case.code))
end
if test_case.time_ms then
local time_text = string.format("%.0fms", test_case.time_ms)
tab = tab .. string.format(" [time:%-" .. max_time_width .. "s]", time_text)
local time_text = string.format('%.0fms', test_case.time_ms)
tab = tab .. string.format(' [time:%-' .. max_time_width .. 's]', time_text)
end
if test_case.signal then
tab = tab .. string.format(" [%s]", test_case.signal)
tab = tab .. string.format(' [%s]', test_case.signal)
end
table.insert(tab_lines, tab)
@ -263,9 +269,9 @@ local function toggle_test_panel(is_debug)
local current_test = test_state.test_cases[test_state.current_index]
if current_test then
table.insert(tab_lines, "")
table.insert(tab_lines, "Input:")
for _, line in ipairs(vim.split(current_test.input, "\n", { plain = true, trimempty = true })) do
table.insert(tab_lines, '')
table.insert(tab_lines, 'Input:')
for _, line in ipairs(vim.split(current_test.input, '\n', { plain = true, trimempty = true })) do
table.insert(tab_lines, line)
end
end
@ -282,12 +288,12 @@ local function toggle_test_panel(is_debug)
end
local expected_text = current_test.expected
local expected_lines = vim.split(expected_text, "\n", { plain = true, trimempty = true })
local expected_lines = vim.split(expected_text, '\n', { plain = true, trimempty = true })
vim.api.nvim_buf_set_lines(test_buffers.expected_buf, 0, -1, false, expected_lines)
if vim.fn.has("nvim-0.8.0") == 1 then
vim.api.nvim_set_option_value("winbar", "Expected", { win = test_windows.expected_win })
if vim.fn.has('nvim-0.8.0') == 1 then
vim.api.nvim_set_option_value('winbar', 'Expected', { win = test_windows.expected_win })
end
end
@ -303,20 +309,20 @@ local function toggle_test_panel(is_debug)
local enable_diff = false
if current_test.actual then
actual_lines = vim.split(current_test.actual, "\n", { plain = true, trimempty = true })
enable_diff = current_test.status == "fail"
actual_lines = vim.split(current_test.actual, '\n', { plain = true, trimempty = true })
enable_diff = current_test.status == 'fail'
else
actual_lines = { "(not run yet)" }
actual_lines = { '(not run yet)' }
end
vim.api.nvim_buf_set_lines(test_buffers.actual_buf, 0, -1, false, actual_lines)
if vim.fn.has("nvim-0.8.0") == 1 then
vim.api.nvim_set_option_value("winbar", "Actual", { win = test_windows.actual_win })
if vim.fn.has('nvim-0.8.0') == 1 then
vim.api.nvim_set_option_value('winbar', 'Actual', { win = test_windows.actual_win })
end
vim.api.nvim_set_option_value("diff", enable_diff, { win = test_windows.expected_win })
vim.api.nvim_set_option_value("diff", enable_diff, { win = test_windows.actual_win })
vim.api.nvim_set_option_value('diff', enable_diff, { win = test_windows.expected_win })
vim.api.nvim_set_option_value('diff', enable_diff, { win = test_windows.actual_win })
if enable_diff then
vim.api.nvim_win_call(test_windows.expected_win, function()
@ -356,15 +362,15 @@ local function toggle_test_panel(is_debug)
refresh_test_panel()
end
vim.keymap.set("n", "<c-n>", function()
vim.keymap.set('n', '<c-n>', function()
navigate_test_case(1)
end, { buffer = test_buffers.tab_buf, silent = true })
vim.keymap.set("n", "<c-p>", function()
vim.keymap.set('n', '<c-p>', function()
navigate_test_case(-1)
end, { buffer = test_buffers.tab_buf, silent = true })
for _, buf in pairs(test_buffers) do
vim.keymap.set("n", "q", function()
vim.keymap.set('n', 'q', function()
toggle_test_panel()
end, { buffer = buf, silent = true })
end
@ -373,7 +379,7 @@ local function toggle_test_panel(is_debug)
config.hooks.before_debug(ctx)
end
local execute_module = require("cp.execute")
local execute_module = require('cp.execute')
local contest_config = config.contests[state.platform]
if execute_module.compile_problem(ctx, contest_config, is_debug) then
test_module.run_all_test_cases(ctx, contest_config)
@ -387,35 +393,38 @@ local function toggle_test_panel(is_debug)
state.test_buffers = test_buffers
state.test_windows = test_windows
local test_state = test_module.get_test_panel_state()
logger.log(string.format("test panel opened (%d test cases)", #test_state.test_cases))
logger.log(string.format('test panel opened (%d test cases)', #test_state.test_cases))
end
---@param delta number 1 for next, -1 for prev
---@param language? string
local function navigate_problem(delta, language)
if not state.platform or not state.contest_id then
logger.log("no contest set. run :CP <platform> <contest> first", vim.log.levels.ERROR)
logger.log('no contest set. run :CP <platform> <contest> first', vim.log.levels.ERROR)
return
end
cache.load()
local contest_data = cache.get_contest_data(state.platform, state.contest_id)
if not contest_data or not contest_data.problems then
logger.log("no contest metadata found. set up a problem first to cache contest data", vim.log.levels.ERROR)
logger.log(
'no contest metadata found. set up a problem first to cache contest data',
vim.log.levels.ERROR
)
return
end
local problems = contest_data.problems
local current_problem_id
if state.platform == "cses" then
if state.platform == 'cses' then
current_problem_id = state.contest_id
else
current_problem_id = state.problem_id
end
if not current_problem_id then
logger.log("no current problem set", vim.log.levels.ERROR)
logger.log('no current problem set', vim.log.levels.ERROR)
return
end
@ -428,21 +437,21 @@ local function navigate_problem(delta, language)
end
if not current_index then
logger.log("current problem not found in contest", vim.log.levels.ERROR)
logger.log('current problem not found in contest', vim.log.levels.ERROR)
return
end
local new_index = current_index + delta
if new_index < 1 or new_index > #problems then
local direction = delta > 0 and "next" or "previous"
logger.log(("no %s problem available"):format(direction), vim.log.levels.INFO)
local direction = delta > 0 and 'next' or 'previous'
logger.log(('no %s problem available'):format(direction), vim.log.levels.INFO)
return
end
local new_problem = problems[new_index]
if state.platform == "cses" then
if state.platform == 'cses' then
setup_problem(new_problem.id, nil, language)
else
setup_problem(state.contest_id, new_problem.id, language)
@ -452,8 +461,8 @@ end
local function parse_command(args)
if #args == 0 then
return {
type = "error",
message = "Usage: :CP <platform> <contest> [problem] [--lang=<language>] | :CP <action> | :CP <problem>",
type = 'error',
message = 'Usage: :CP <platform> <contest> [problem] [--lang=<language>] | :CP <action> | :CP <problem>',
}
end
@ -461,48 +470,48 @@ local function parse_command(args)
local debug = false
for i, arg in ipairs(args) do
local lang_match = arg:match("^--lang=(.+)$")
local lang_match = arg:match('^--lang=(.+)$')
if lang_match then
language = lang_match
elseif arg == "--lang" then
elseif arg == '--lang' then
if i + 1 <= #args then
language = args[i + 1]
else
return { type = "error", message = "--lang requires a value" }
return { type = 'error', message = '--lang requires a value' }
end
elseif arg == "--debug" then
elseif arg == '--debug' then
debug = true
end
end
local filtered_args = vim.tbl_filter(function(arg)
return not (arg:match("^--lang") or arg == language or arg == "--debug")
return not (arg:match('^--lang') or arg == language or arg == '--debug')
end, args)
local first = filtered_args[1]
if vim.tbl_contains(actions, first) then
return { type = "action", action = first, language = language, debug = debug }
return { type = 'action', action = first, language = language, debug = debug }
end
if vim.tbl_contains(platforms, first) then
if #filtered_args == 1 then
return {
type = "platform_only",
type = 'platform_only',
platform = first,
language = language,
}
elseif #filtered_args == 2 then
if first == "cses" then
if first == 'cses' then
return {
type = "cses_problem",
type = 'cses_problem',
platform = first,
problem = filtered_args[2],
language = language,
}
else
return {
type = "contest_setup",
type = 'contest_setup',
platform = first,
contest = filtered_args[2],
language = language,
@ -510,14 +519,14 @@ local function parse_command(args)
end
elseif #filtered_args == 3 then
return {
type = "full_setup",
type = 'full_setup',
platform = first,
contest = filtered_args[2],
problem = filtered_args[3],
language = language,
}
else
return { type = "error", message = "Too many arguments" }
return { type = 'error', message = 'Too many arguments' }
end
end
@ -529,55 +538,59 @@ local function parse_command(args)
return prob.id
end, contest_data.problems)
if vim.tbl_contains(problem_ids, first) then
return { type = "problem_switch", problem = first, language = language }
return { type = 'problem_switch', problem = first, language = language }
end
end
return {
type = "error",
type = 'error',
message = ("invalid subcommand '%s'"):format(first),
}
end
return { type = "error", message = "Unknown command or no contest context" }
return { type = 'error', message = 'Unknown command or no contest context' }
end
function M.handle_command(opts)
local cmd = parse_command(opts.fargs)
if cmd.type == "error" then
if cmd.type == 'error' then
logger.log(cmd.message, vim.log.levels.ERROR)
return
end
if cmd.type == "action" then
if cmd.action == "test" then
if cmd.type == 'action' then
if cmd.action == 'test' then
toggle_test_panel(cmd.debug)
elseif cmd.action == "next" then
elseif cmd.action == 'next' then
navigate_problem(1, cmd.language)
elseif cmd.action == "prev" then
elseif cmd.action == 'prev' then
navigate_problem(-1, cmd.language)
end
return
end
if cmd.type == "platform_only" then
if cmd.type == 'platform_only' then
set_platform(cmd.platform)
return
end
if cmd.type == "contest_setup" then
if cmd.type == 'contest_setup' then
if set_platform(cmd.platform) then
state.contest_id = cmd.contest
if vim.tbl_contains(config.scrapers, cmd.platform) then
local metadata_result = scrape.scrape_contest_metadata(cmd.platform, cmd.contest)
if not metadata_result.success then
logger.log(
"failed to load contest metadata: " .. (metadata_result.error or "unknown error"),
'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'),
vim.log.levels.WARN
)
else
logger.log(
("loaded %d problems for %s %s"):format(#metadata_result.problems, cmd.platform, cmd.contest)
('loaded %d problems for %s %s'):format(
#metadata_result.problems,
cmd.platform,
cmd.contest
)
)
end
end
@ -585,7 +598,7 @@ function M.handle_command(opts)
return
end
if cmd.type == "full_setup" then
if cmd.type == 'full_setup' then
if set_platform(cmd.platform) then
state.contest_id = cmd.contest
local problem_ids = {}
@ -595,14 +608,18 @@ function M.handle_command(opts)
local metadata_result = scrape.scrape_contest_metadata(cmd.platform, cmd.contest)
if not metadata_result.success then
logger.log(
"failed to load contest metadata: " .. (metadata_result.error or "unknown error"),
'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'),
vim.log.levels.ERROR
)
return
end
logger.log(
("loaded %d problems for %s %s"):format(#metadata_result.problems, cmd.platform, cmd.contest)
('loaded %d problems for %s %s'):format(
#metadata_result.problems,
cmd.platform,
cmd.contest
)
)
problem_ids = vim.tbl_map(function(prob)
return prob.id
@ -632,13 +649,13 @@ function M.handle_command(opts)
return
end
if cmd.type == "cses_problem" then
if cmd.type == 'cses_problem' then
if set_platform(cmd.platform) then
if vim.tbl_contains(config.scrapers, cmd.platform) then
local metadata_result = scrape.scrape_contest_metadata(cmd.platform, "")
local metadata_result = scrape.scrape_contest_metadata(cmd.platform, '')
if not metadata_result.success then
logger.log(
"failed to load contest metadata: " .. (metadata_result.error or "unknown error"),
'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'),
vim.log.levels.WARN
)
end
@ -648,8 +665,8 @@ function M.handle_command(opts)
return
end
if cmd.type == "problem_switch" then
if state.platform == "cses" then
if cmd.type == 'problem_switch' then
if state.platform == 'cses' then
setup_problem(cmd.problem, nil, cmd.language)
else
setup_problem(state.contest_id, cmd.problem, cmd.language)

View file

@ -9,7 +9,7 @@ end
function M.log(msg, level)
level = level or vim.log.levels.INFO
if not config or config.debug or level >= vim.log.levels.WARN then
vim.notify(("[cp.nvim]: %s"):format(msg), level)
vim.notify(('[cp.nvim]: %s'):format(msg), level)
end
end

View file

@ -19,11 +19,11 @@ local M = {}
---@return ProblemContext
function M.create_context(contest, contest_id, problem_id, config, language)
vim.validate({
contest = { contest, "string" },
contest_id = { contest_id, "string" },
problem_id = { problem_id, { "string", "nil" }, true },
config = { config, "table" },
language = { language, { "string", "nil" }, true },
contest = { contest, 'string' },
contest_id = { contest_id, 'string' },
problem_id = { problem_id, { 'string', 'nil' }, true },
config = { config, 'table' },
language = { language, { 'string', 'nil' }, true },
})
local contest_config = config.contests[contest]
@ -37,29 +37,31 @@ function M.create_context(contest, contest_id, problem_id, config, language)
error(("No language config found for '%s' in contest '%s'"):format(target_language, contest))
end
if not language_config.extension then
error(("No extension configured for language '%s' in contest '%s'"):format(target_language, contest))
error(
("No extension configured for language '%s' in contest '%s'"):format(target_language, contest)
)
end
local base_name
if config.filename then
local source_file = config.filename(contest, contest_id, problem_id, config, language)
base_name = vim.fn.fnamemodify(source_file, ":t:r")
base_name = vim.fn.fnamemodify(source_file, ':t:r')
else
local default_filename = require("cp.config").default_filename
local default_filename = require('cp.config').default_filename
base_name = default_filename(contest_id, problem_id)
end
local source_file = base_name .. "." .. language_config.extension
local source_file = base_name .. '.' .. language_config.extension
return {
contest = contest,
contest_id = contest_id,
problem_id = problem_id,
source_file = source_file,
binary_file = ("build/%s.run"):format(base_name),
input_file = ("io/%s.cpin"):format(base_name),
output_file = ("io/%s.cpout"):format(base_name),
expected_file = ("io/%s.expected"):format(base_name),
binary_file = ('build/%s.run'):format(base_name),
input_file = ('io/%s.cpin'):format(base_name),
output_file = ('io/%s.cpout'):format(base_name),
expected_file = ('io/%s.expected'):format(base_name),
problem_name = base_name,
}
end

View file

@ -10,43 +10,43 @@
---@field error? string
local M = {}
local logger = require("cp.log")
local cache = require("cp.cache")
local logger = require('cp.log')
local cache = require('cp.cache')
local function get_plugin_path()
local plugin_path = debug.getinfo(1, "S").source:sub(2)
return vim.fn.fnamemodify(plugin_path, ":h:h:h")
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")
vim.fn.mkdir('io', 'p')
end
local function check_internet_connectivity()
local result = vim.system({ "ping", "-c", "1", "-W", "3", "8.8.8.8" }, { text = true }):wait()
local result = vim.system({ 'ping', '-c', '1', '-W', '3', '8.8.8.8' }, { text = true }):wait()
return result.code == 0
end
local function setup_python_env()
local plugin_path = get_plugin_path()
local venv_dir = plugin_path .. "/.venv"
local venv_dir = plugin_path .. '/.venv'
if vim.fn.executable("uv") == 0 then
if vim.fn.executable('uv') == 0 then
logger.log(
"uv is not installed. Install it to enable problem scraping: https://docs.astral.sh/uv/",
'uv is not installed. Install it to enable problem scraping: https://docs.astral.sh/uv/',
vim.log.levels.WARN
)
return false
end
if vim.fn.isdirectory(venv_dir) == 0 then
logger.log("setting up Python environment for scrapers...")
local result = vim.system({ "uv", "sync" }, { cwd = plugin_path, text = true }):wait()
logger.log('setting up Python environment for scrapers...')
local result = vim.system({ 'uv', 'sync' }, { cwd = plugin_path, text = true }):wait()
if result.code ~= 0 then
logger.log("failed to setup Python environment: " .. result.stderr, vim.log.levels.ERROR)
logger.log('failed to setup Python environment: ' .. result.stderr, vim.log.levels.ERROR)
return false
end
logger.log("python environment setup complete")
logger.log('python environment setup complete')
end
return true
@ -57,8 +57,8 @@ end
---@return {success: boolean, problems?: table[], error?: string}
function M.scrape_contest_metadata(platform, contest_id)
vim.validate({
platform = { platform, "string" },
contest_id = { contest_id, "string" },
platform = { platform, 'string' },
contest_id = { contest_id, 'string' },
})
cache.load()
@ -74,52 +74,54 @@ function M.scrape_contest_metadata(platform, contest_id)
if not check_internet_connectivity() then
return {
success = false,
error = "No internet connection available",
error = 'No internet connection available',
}
end
if not setup_python_env() then
return {
success = false,
error = "Python environment setup failed",
error = 'Python environment setup failed',
}
end
local plugin_path = get_plugin_path()
local scraper_path = plugin_path .. "/scrapers/" .. platform .. ".py"
local scraper_path = plugin_path .. '/scrapers/' .. platform .. '.py'
local args
if platform == "cses" then
if platform == 'cses' then
args = {
"uv",
"run",
"--directory",
'uv',
'run',
'--directory',
plugin_path,
scraper_path,
"metadata",
'metadata',
}
else
args = {
"uv",
"run",
"--directory",
'uv',
'run',
'--directory',
plugin_path,
scraper_path,
"metadata",
'metadata',
contest_id,
}
end
local result = vim.system(args, {
local result = vim
.system(args, {
cwd = plugin_path,
text = true,
timeout = 30000,
}):wait()
})
:wait()
if result.code ~= 0 then
return {
success = false,
error = "Failed to run metadata scraper: " .. (result.stderr or "Unknown error"),
error = 'Failed to run metadata scraper: ' .. (result.stderr or 'Unknown error'),
}
end
@ -127,7 +129,7 @@ function M.scrape_contest_metadata(platform, contest_id)
if not ok then
return {
success = false,
error = "Failed to parse metadata scraper output: " .. tostring(data),
error = 'Failed to parse metadata scraper output: ' .. tostring(data),
}
end
@ -136,8 +138,8 @@ function M.scrape_contest_metadata(platform, contest_id)
end
local problems_list
if platform == "cses" then
problems_list = data.categories and data.categories["CSES Problem Set"] or {}
if platform == 'cses' then
problems_list = data.categories and data.categories['CSES Problem Set'] or {}
else
problems_list = data.problems or {}
end
@ -153,23 +155,23 @@ end
---@return {success: boolean, problem_id: string, test_count?: number, test_cases?: ScraperTestCase[], url?: string, error?: string}
function M.scrape_problem(ctx)
vim.validate({
ctx = { ctx, "table" },
ctx = { ctx, 'table' },
})
ensure_io_directory()
if vim.fn.filereadable(ctx.input_file) == 1 and vim.fn.filereadable(ctx.expected_file) == 1 then
local base_name = vim.fn.fnamemodify(ctx.input_file, ":r")
local base_name = vim.fn.fnamemodify(ctx.input_file, ':r')
local test_cases = {}
local i = 1
while true do
local input_file = base_name .. "." .. i .. ".cpin"
local expected_file = base_name .. "." .. i .. ".cpout"
local input_file = base_name .. '.' .. i .. '.cpin'
local expected_file = base_name .. '.' .. i .. '.cpout'
if vim.fn.filereadable(input_file) == 1 and vim.fn.filereadable(expected_file) == 1 then
local input_content = table.concat(vim.fn.readfile(input_file), "\n")
local expected_content = table.concat(vim.fn.readfile(expected_file), "\n")
local input_content = table.concat(vim.fn.readfile(input_file), '\n')
local expected_content = table.concat(vim.fn.readfile(expected_file), '\n')
table.insert(test_cases, {
index = i,
@ -194,7 +196,7 @@ function M.scrape_problem(ctx)
return {
success = false,
problem_id = ctx.problem_name,
error = "No internet connection available",
error = 'No internet connection available',
}
end
@ -202,48 +204,50 @@ function M.scrape_problem(ctx)
return {
success = false,
problem_id = ctx.problem_name,
error = "Python environment setup failed",
error = 'Python environment setup failed',
}
end
local plugin_path = get_plugin_path()
local scraper_path = plugin_path .. "/scrapers/" .. ctx.contest .. ".py"
local scraper_path = plugin_path .. '/scrapers/' .. ctx.contest .. '.py'
local args
if ctx.contest == "cses" then
if ctx.contest == 'cses' then
args = {
"uv",
"run",
"--directory",
'uv',
'run',
'--directory',
plugin_path,
scraper_path,
"tests",
'tests',
ctx.contest_id,
}
else
args = {
"uv",
"run",
"--directory",
'uv',
'run',
'--directory',
plugin_path,
scraper_path,
"tests",
'tests',
ctx.contest_id,
ctx.problem_id,
}
end
local result = vim.system(args, {
local result = vim
.system(args, {
cwd = plugin_path,
text = true,
timeout = 30000,
}):wait()
})
:wait()
if result.code ~= 0 then
return {
success = false,
problem_id = ctx.problem_name,
error = "Failed to run tests scraper: " .. (result.stderr or "Unknown error"),
error = 'Failed to run tests scraper: ' .. (result.stderr or 'Unknown error'),
}
end
@ -252,7 +256,7 @@ function M.scrape_problem(ctx)
return {
success = false,
problem_id = ctx.problem_name,
error = "Failed to parse tests scraper output: " .. tostring(data),
error = 'Failed to parse tests scraper output: ' .. tostring(data),
}
end
@ -261,17 +265,17 @@ function M.scrape_problem(ctx)
end
if data.tests and #data.tests > 0 then
local base_name = vim.fn.fnamemodify(ctx.input_file, ":r")
local base_name = vim.fn.fnamemodify(ctx.input_file, ':r')
for i, test_case in ipairs(data.tests) do
local input_file = base_name .. "." .. i .. ".cpin"
local expected_file = base_name .. "." .. i .. ".cpout"
local input_file = base_name .. '.' .. i .. '.cpin'
local expected_file = base_name .. '.' .. i .. '.cpout'
local input_content = test_case.input:gsub("\r", "")
local expected_content = test_case.expected:gsub("\r", "")
local input_content = test_case.input:gsub('\r', '')
local expected_content = test_case.expected:gsub('\r', '')
vim.fn.writefile(vim.split(input_content, "\n", true), input_file)
vim.fn.writefile(vim.split(expected_content, "\n", true), expected_file)
vim.fn.writefile(vim.split(input_content, '\n', true), input_file)
vim.fn.writefile(vim.split(expected_content, '\n', true), expected_file)
end
end

View file

@ -1,16 +1,16 @@
local M = {}
local logger = require("cp.log")
local logger = require('cp.log')
function M.setup(config)
local ok, ls = pcall(require, "luasnip")
local ok, ls = pcall(require, 'luasnip')
if not ok then
logger.log("LuaSnip not available - snippets disabled", vim.log.levels.INFO)
logger.log('LuaSnip not available - snippets disabled', vim.log.levels.INFO)
return
end
local s, i, fmt = ls.snippet, ls.insert_node, require("luasnip.extras.fmt").fmt
local s, i, fmt = ls.snippet, ls.insert_node, require('luasnip.extras.fmt').fmt
local constants = require("cp.constants")
local constants = require('cp.constants')
local filetype_to_language = constants.filetype_to_language
local language_to_filetype = {}
@ -110,14 +110,14 @@ if __name__ == "__main__":
local filetype = constants.canonical_filetypes[language]
for contest, template in pairs(template_set) do
local prefixed_trigger = ("cp.nvim/%s.%s"):format(contest, language)
local prefixed_trigger = ('cp.nvim/%s.%s'):format(contest, language)
if not user_overrides[prefixed_trigger] then
table.insert(snippets, s(prefixed_trigger, fmt(template, { i(1) })))
end
end
for trigger, snippet in pairs(user_overrides) do
local prefix_match = trigger:match("^cp%.nvim/[^.]+%.(.+)$")
local prefix_match = trigger:match('^cp%.nvim/[^.]+%.(.+)$')
if prefix_match == language then
table.insert(snippets, snippet)
end

View file

@ -21,8 +21,8 @@
---@field saved_layout table?
local M = {}
local logger = require("cp.log")
local constants = require("cp.constants")
local logger = require('cp.log')
local constants = require('cp.constants')
---@type TestPanelState
local test_panel_state = {
@ -43,7 +43,7 @@ local function create_test_case(index, input, expected)
index = index,
input = input,
expected = expected,
status = "pending",
status = 'pending',
actual = nil,
time_ms = nil,
error = nil,
@ -56,7 +56,7 @@ end
---@param problem_id string?
---@return TestCase[]
local function parse_test_cases_from_cache(platform, contest_id, problem_id)
local cache = require("cp.cache")
local cache = require('cp.cache')
cache.load()
local cached_test_cases = cache.get_test_cases(platform, contest_id, problem_id)
@ -68,7 +68,7 @@ local function parse_test_cases_from_cache(platform, contest_id, problem_id)
for i, test_case in ipairs(cached_test_cases) do
local index = test_case.index or i
local expected = test_case.expected or test_case.output or ""
local expected = test_case.expected or test_case.output or ''
table.insert(test_cases, create_test_case(index, test_case.input, expected))
end
@ -83,17 +83,20 @@ local function parse_test_cases_from_files(input_file, expected_file)
return {}
end
local base_name = vim.fn.fnamemodify(input_file, ":r")
local base_name = vim.fn.fnamemodify(input_file, ':r')
local test_cases = {}
local i = 1
while true do
local individual_input_file = base_name .. "." .. i .. ".cpin"
local individual_expected_file = base_name .. "." .. i .. ".cpout"
local individual_input_file = base_name .. '.' .. i .. '.cpin'
local individual_expected_file = base_name .. '.' .. i .. '.cpout'
if vim.fn.filereadable(individual_input_file) == 1 and vim.fn.filereadable(individual_expected_file) == 1 then
local input_content = table.concat(vim.fn.readfile(individual_input_file), "\n")
local expected_content = table.concat(vim.fn.readfile(individual_expected_file), "\n")
if
vim.fn.filereadable(individual_input_file) == 1
and vim.fn.filereadable(individual_expected_file) == 1
then
local input_content = table.concat(vim.fn.readfile(individual_input_file), '\n')
local expected_content = table.concat(vim.fn.readfile(individual_expected_file), '\n')
table.insert(test_cases, create_test_case(i, input_content, expected_content))
i = i + 1
@ -103,8 +106,8 @@ local function parse_test_cases_from_files(input_file, expected_file)
end
if #test_cases == 0 then
local input_content = table.concat(vim.fn.readfile(input_file), "\n")
local expected_content = table.concat(vim.fn.readfile(expected_file), "\n")
local input_content = table.concat(vim.fn.readfile(input_file), '\n')
local expected_content = table.concat(vim.fn.readfile(expected_file), '\n')
return { create_test_case(1, input_content, expected_content) }
end
@ -116,15 +119,15 @@ end
---@param test_case TestCase
---@return table
local function run_single_test_case(ctx, contest_config, test_case)
local language = vim.fn.fnamemodify(ctx.source_file, ":e")
local language = vim.fn.fnamemodify(ctx.source_file, ':e')
local language_name = constants.filetype_to_language[language] or contest_config.default_language
local language_config = contest_config[language_name]
if not language_config then
return {
status = "fail",
actual = "",
error = "No language configuration",
status = 'fail',
actual = '',
error = 'No language configuration',
time_ms = 0,
}
end
@ -134,7 +137,7 @@ local function run_single_test_case(ctx, contest_config, test_case)
for _, arg in ipairs(cmd_template) do
local substituted = arg
for key, value in pairs(substitutions) do
substituted = substituted:gsub("{" .. key .. "}", value)
substituted = substituted:gsub('{' .. key .. '}', value)
end
table.insert(result, substituted)
end
@ -152,18 +155,18 @@ local function run_single_test_case(ctx, contest_config, test_case)
local substitutions = {
source = ctx.source_file,
binary = ctx.binary_file,
version = tostring(language_config.version or ""),
version = tostring(language_config.version or ''),
}
if language_config.compile and vim.fn.filereadable(ctx.binary_file) == 0 then
logger.log("binary not found, compiling first...")
logger.log('binary not found, compiling first...')
local compile_cmd = substitute_template(language_config.compile, substitutions)
local compile_result = vim.system(compile_cmd, { text = true }):wait()
if compile_result.code ~= 0 then
return {
status = "fail",
actual = "",
error = "Compilation failed: " .. (compile_result.stderr or "Unknown error"),
status = 'fail',
actual = '',
error = 'Compilation failed: ' .. (compile_result.stderr or 'Unknown error'),
time_ms = 0,
}
end
@ -171,28 +174,30 @@ local function run_single_test_case(ctx, contest_config, test_case)
local run_cmd = build_command(language_config.run, language_config.executable, substitutions)
local stdin_content = test_case.input .. "\n"
local stdin_content = test_case.input .. '\n'
local start_time = vim.uv.hrtime()
local result = vim.system(run_cmd, {
local result = vim
.system(run_cmd, {
stdin = stdin_content,
timeout = contest_config.timeout_ms or 2000,
text = true,
}):wait()
})
:wait()
local execution_time = (vim.uv.hrtime() - start_time) / 1000000
local actual_output = (result.stdout or ""):gsub("\n$", "")
local expected_output = test_case.expected:gsub("\n$", "")
local actual_output = (result.stdout or ''):gsub('\n$', '')
local expected_output = test_case.expected:gsub('\n$', '')
local ok = actual_output == expected_output
local status
local timed_out = result.code == 143 or result.code == 124
if timed_out then
status = "timeout"
status = 'timeout'
elseif result.code == 0 and ok then
status = "pass"
status = 'pass'
else
status = "fail"
status = 'fail'
end
local signal = nil
@ -225,7 +230,7 @@ function M.load_test_cases(ctx, state)
test_panel_state.test_cases = test_cases
test_panel_state.current_index = 1
logger.log(("loaded %d test case(s)"):format(#test_cases))
logger.log(('loaded %d test case(s)'):format(#test_cases))
return #test_cases > 0
end
@ -239,8 +244,8 @@ function M.run_test_case(ctx, contest_config, index)
return false
end
logger.log(("running test case %d"):format(index))
test_case.status = "running"
logger.log(('running test case %d'):format(index))
test_case.status = 'running'
local result = run_single_test_case(ctx, contest_config, test_case)

View file

@ -1,25 +1,27 @@
local M = {}
local function get_git_version()
local plugin_path = debug.getinfo(1, "S").source:sub(2)
local plugin_root = vim.fn.fnamemodify(plugin_path, ":h:h:h")
local plugin_path = debug.getinfo(1, 'S').source:sub(2)
local plugin_root = vim.fn.fnamemodify(plugin_path, ':h:h:h')
local result = vim.system({ "git", "describe", "--tags", "--always", "--dirty" }, {
local result = vim
.system({ 'git', 'describe', '--tags', '--always', '--dirty' }, {
cwd = plugin_root,
text = true,
}):wait()
})
:wait()
if result.code == 0 then
return result.stdout:gsub("\n", "")
return result.stdout:gsub('\n', '')
else
return "unknown"
return 'unknown'
end
end
local function parse_semver(version_string)
local semver = version_string:match("^v?(%d+%.%d+%.%d+)")
local semver = version_string:match('^v?(%d+%.%d+%.%d+)')
if semver then
local major, minor, patch = semver:match("(%d+)%.(%d+)%.(%d+)")
local major, minor, patch = semver:match('(%d+)%.(%d+)%.(%d+)')
return {
full = semver,
major = tonumber(major),

View file

@ -10,7 +10,7 @@
---@field height integer
local M = {}
local constants = require("cp.constants")
local constants = require('cp.constants')
---@return WindowState
function M.save_layout()
@ -38,8 +38,8 @@ end
---@param tile_fn? fun(source_buf: integer, input_buf: integer, output_buf: integer)
function M.restore_layout(state, tile_fn)
vim.validate({
state = { state, { "table", "nil" }, true },
tile_fn = { tile_fn, { "function", "nil" }, true },
state = { state, { 'table', 'nil' }, true },
tile_fn = { tile_fn, { 'function', 'nil' }, true },
})
if not state then
@ -48,32 +48,36 @@ function M.restore_layout(state, tile_fn)
vim.cmd.diffoff()
local problem_id = vim.fn.expand("%:t:r")
if problem_id == "" then
local problem_id = vim.fn.expand('%:t:r')
if problem_id == '' then
for win, win_state in pairs(state.windows) do
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_buf_is_valid(win_state.bufnr) then
local bufname = vim.api.nvim_buf_get_name(win_state.bufnr)
if not bufname:match("%.in$") and not bufname:match("%.out$") and not bufname:match("%.expected$") then
problem_id = vim.fn.fnamemodify(bufname, ":t:r")
if
not bufname:match('%.in$')
and not bufname:match('%.out$')
and not bufname:match('%.expected$')
then
problem_id = vim.fn.fnamemodify(bufname, ':t:r')
break
end
end
end
end
if problem_id ~= "" then
vim.cmd("silent only")
if problem_id ~= '' then
vim.cmd('silent only')
local base_fp = vim.fn.getcwd()
local input_file = ("%s/io/%s.in"):format(base_fp, problem_id)
local output_file = ("%s/io/%s.out"):format(base_fp, problem_id)
local source_files = vim.fn.glob(problem_id .. ".*")
local input_file = ('%s/io/%s.in'):format(base_fp, problem_id)
local output_file = ('%s/io/%s.out'):format(base_fp, problem_id)
local source_files = vim.fn.glob(problem_id .. '.*')
local source_file
if source_files ~= "" then
local files = vim.split(source_files, "\n")
if source_files ~= '' then
local files = vim.split(source_files, '\n')
local valid_extensions = vim.tbl_keys(constants.filetype_to_language)
for _, file in ipairs(files) do
local ext = vim.fn.fnamemodify(file, ":e")
local ext = vim.fn.fnamemodify(file, ':e')
if vim.tbl_contains(valid_extensions, ext) then
source_file = file
break
@ -119,20 +123,20 @@ end
---@param output_buf integer
local function default_tile(source_buf, input_buf, output_buf)
vim.validate({
source_buf = { source_buf, "number" },
input_buf = { input_buf, "number" },
output_buf = { output_buf, "number" },
source_buf = { source_buf, 'number' },
input_buf = { input_buf, 'number' },
output_buf = { output_buf, 'number' },
})
vim.api.nvim_set_current_buf(source_buf)
vim.cmd.vsplit()
vim.api.nvim_set_current_buf(output_buf)
vim.bo.filetype = "cp"
vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3)))
vim.bo.filetype = 'cp'
vim.cmd(('vertical resize %d'):format(math.floor(vim.o.columns * 0.3)))
vim.cmd.split()
vim.api.nvim_set_current_buf(input_buf)
vim.bo.filetype = "cp"
vim.cmd.wincmd("h")
vim.bo.filetype = 'cp'
vim.cmd.wincmd('h')
end
M.default_tile = default_tile

View file

@ -3,30 +3,30 @@ if vim.g.loaded_cp then
end
vim.g.loaded_cp = 1
local constants = require("cp.constants")
local constants = require('cp.constants')
local platforms = constants.PLATFORMS
local actions = constants.ACTIONS
vim.api.nvim_create_user_command("CP", function(opts)
local cp = require("cp")
vim.api.nvim_create_user_command('CP', function(opts)
local cp = require('cp')
cp.handle_command(opts)
end, {
nargs = "*",
desc = "Competitive programming helper",
nargs = '*',
desc = 'Competitive programming helper',
complete = function(ArgLead, CmdLine, _)
local args = vim.split(vim.trim(CmdLine), "%s+")
local args = vim.split(vim.trim(CmdLine), '%s+')
local num_args = #args
if CmdLine:sub(-1) == " " then
if CmdLine:sub(-1) == ' ' then
num_args = num_args + 1
end
if num_args == 2 then
local candidates = {}
local cp = require("cp")
local cp = require('cp')
local context = cp.get_current_context()
if context.platform and context.contest_id then
vim.list_extend(candidates, actions)
local cache = require("cp.cache")
local cache = require('cp.cache')
cache.load()
local contest_data = cache.get_contest_data(context.platform, context.contest_id)
if contest_data and contest_data.problems then
@ -42,7 +42,7 @@ end, {
end, candidates)
elseif num_args == 4 then
if vim.tbl_contains(platforms, args[2]) then
local cache = require("cp.cache")
local cache = require('cp.cache')
cache.load()
local contest_data = cache.get_contest_data(args[2], args[3])
if contest_data and contest_data.problems then

View file

@ -2,6 +2,6 @@ local cp = require('cp')
describe('neovim plugin', function()
it('work as expect', function()
cp.setup
cp.setup()
end)
end)