diff --git a/doc/cp.txt b/doc/cp.txt new file mode 100644 index 0000000..0c5a11f --- /dev/null +++ b/doc/cp.txt @@ -0,0 +1,156 @@ +*cp.txt* Competitive programming plugin for Neovim + +Author: Barrett Ruth +License: Same terms as Vim itself (see |license|) + +INTRODUCTION *cp* *cp.nvim* + +cp.nvim is a competitive programming plugin that automates problem setup, +compilation, and testing workflow for online judges. + +Supported platforms: AtCoder, Codeforces, CSES + +REQUIREMENTS *cp-requirements* + +- Neovim 0.10.0+ +- uv package manager (https://docs.astral.sh/uv/) +- C++ compiler (g++/clang++) + +Optional: +- LuaSnip for template expansion (https://github.com/L3MON4D3/LuaSnip) + +COMMANDS *cp-commands* + + *:CP* +:CP {contest} Set up contest environment for {contest}. + Available contests: atcoder, codeforces, cses + +:CP {contest} {problem} Set up problem from {contest}. Scrapes test + cases and creates source file. + +:CP {contest} {problem} {letter} + For AtCoder/Codeforces: set up problem with + specific letter (e.g. a, b, c) + +:CP {problem} Set up {problem} in current contest mode. + Requires contest to be set first. + +:CP run Compile and run current problem with test input. + Shows execution time and output comparison. + +:CP debug Compile with debug flags and run current problem. + Includes sanitizers and debug symbols. + +:CP diff Enter diff mode to compare actual vs expected + output. Run again to exit diff mode. + +CONFIGURATION *cp-config* + +cp.nvim is automatically lazy-loaded - no config/setup is required. + +Provide extra options via a setup() function with your package manager. For +example, with lazy.nvim (https://github.com/folke/lazy.nvim): + + { + 'barrett-ruth/cp.nvim', + config = function() + local ls = require('luasnip') + local s = ls.snippet + + require('cp').setup({ + contests = { + default = { + cpp_version = 20, + compile_flags = { "-O2", "-DLOCAL", "-Wall", "-Wextra" }, + debug_flags = { "-g3", "-fsanitize=address,undefined", "-DLOCAL" }, + timeout_ms = 2000, + }, + atcoder = { + cpp_version = 23, + }, + }, + snippets = { + cses = { + s("cses", "#include \nusing namespace std;\n\nint main() {\n\t$0\n}") + }, + }, + hooks = { + before_run = function(problem_id) + vim.cmd.w() + vim.lsp.buf.format() + end, + before_debug = function(problem_id) + ... + end + }, + }) + end + } + +Configuration options: + +contests Dictionary of contest configurations - each contest inherits from 'default'. + + cpp_version c++ standard version (e.g. 20, 23) + compile_flags compiler flags for run builds + debug_flags compiler flags for debug builds + timeout_ms duration (ms) to run/debug before timeout + +snippets LuaSnip snippets by contest type + +hooks Functions called at specific events + before_run Called before :CP run + before_debug Called before :CP debug + +WORKFLOW *cp-workflow* + +1. Set up contest environment: > + :CP atcoder +< +2. Set up specific problem: > + :CP abc123 a +< + This creates abc123a.cc and scrapes test cases to io/abc123a.in and + io/abc123a.expected. Alternatively, run :CP atcoder abc123 a + +3. Write, test, and view your solution output: + + :CP run +< + Output appears in vertical split showing execution time, output, and + whether it matches expected output. + +4. Debug if needed: + + :CP debug +< +5. Compare actual vs. expected output visually: > + + :CP diff +< + Enters 3-way diff mode with actual, expected, and input files. + +FILE STRUCTURE *cp-files* + +cp.nvim creates the following file structure upon setup: + + problem.cc + build/*.{run,debug} + io/ + problem.in + problem.out + problem.expected + +SNIPPETS *cp-snippets* + +cp.nvim integrates with LuaSnip for automatic template expansion. When you +open a new problem file, type the contest name and press to expand. + +Built-in snippets include basic C++ templates for each contest type. +Custom snippets can be added via configuration. + +HEALTH CHECK *cp-health* + +Run |:checkhealth| cp to verify your setup. + + vim:tw=78:ts=8:ft=help:norl: diff --git a/lua/cp/health.lua b/lua/cp/health.lua index 150700f..464ee8d 100644 --- a/lua/cp/health.lua +++ b/lua/cp/health.lua @@ -23,7 +23,7 @@ end local function check_python_env() local plugin_path = debug.getinfo(1, "S").source:sub(2) - plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h") + plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h") local venv_dir = plugin_path .. "/.venv" if vim.fn.isdirectory(venv_dir) == 1 then @@ -35,7 +35,7 @@ end local function check_scrapers() local plugin_path = debug.getinfo(1, "S").source:sub(2) - plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h") + plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h") local scrapers = { "atcoder.py", "codeforces.py", "cses.py" } for _, scraper in ipairs(scrapers) do @@ -59,24 +59,6 @@ local function check_luasnip() end end -local function check_directories() - local cwd = vim.fn.getcwd() - local build_dir = cwd .. "/build" - local io_dir = cwd .. "/io" - - if vim.fn.isdirectory(build_dir) == 1 then - vim.health.ok("Build directory exists: " .. build_dir) - else - vim.health.info("Build directory will be created when needed") - end - - if vim.fn.isdirectory(io_dir) == 1 then - vim.health.ok("IO directory exists: " .. io_dir) - else - vim.health.info("IO directory will be created when needed") - end -end - local function check_config() local cp = require("cp") if cp.is_initialized() then @@ -100,7 +82,6 @@ function M.check() check_python_env() check_scrapers() check_luasnip() - check_directories() check_config() end diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 12920ce..e5b683b 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -250,14 +250,20 @@ function M.handle_command(opts) elseif cmd == "diff" then diff_problem() else - if vim.g.cp_contest then - if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and args[2] then - setup_problem(cmd, args[2]) - else - setup_problem(cmd) - end + local similar_contests = vim.tbl_filter(function(contest) + return contest:find(cmd:sub(1, 3), 1, true) == 1 + end, competition_types) + + if #similar_contests > 0 then + log( + ("unknown contest type '%s'. Did you mean: %s"):format(cmd, table.concat(similar_contests, ", ")), + vim.log.levels.ERROR + ) else - log("no contest mode set. run :CP first or use full command", vim.log.levels.ERROR) + log( + ("unknown contest type '%s'. Available: [%s]"):format(cmd, table.concat(competition_types, ", ")), + vim.log.levels.ERROR + ) end end end diff --git a/readme.md b/readme.md index 6985571..c45bba3 100644 --- a/readme.md +++ b/readme.md @@ -2,12 +2,11 @@ neovim plugin for competitive programming. -> NOTE: sample test data from [codeforces](https://codeforces.com) is scraped via [cloudscraper](https://github.com/VeNoMouS/cloudscraper). -> Use at your own risk. +> Sample test data from [codeforces](https://codeforces.com) is scraped via [cloudscraper](https://github.com/VeNoMouS/cloudscraper). Use at your own risk. ## Features -- Support for multiple online judges (AtCoder, Codeforces, CSES) +- Support for multiple online judges ([AtCoder](https://atcoder.jp/), [Codeforces](https://codeforces.com/), [CSES](https://cses.fi)) - Automatic problem scraping and test case management - Integrated build, run, and debug commands - Diff mode for comparing output with expected results @@ -19,20 +18,6 @@ neovim plugin for competitive programming. - [uv](https://docs.astral.sh/uv/): problem scraping (optional) - [LuaSnip](https://github.com/L3MON4D3/LuaSnip): contest-specific snippets (optional) -## Installation - -Using [lazy.nvim](https://github.com/folke/lazy.nvim): - -```lua -{ - "barrett-ruth/cp.nvim", - cmd = "CP", - dependencies = { - "L3MON4D3/LuaSnip", - } -} -``` - ## Documentation ```vim