From c152e91819d6e157a8731aa4db7fa3720fdecc49 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 17:51:46 -0500 Subject: [PATCH 1/4] feat(doc): vimdoc --- doc/cp.txt | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ lua/cp/init.lua | 20 +++--- readme.md | 19 +----- 3 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 doc/cp.txt diff --git a/doc/cp.txt b/doc/cp.txt new file mode 100644 index 0000000..e21d2a6 --- /dev/null +++ b/doc/cp.txt @@ -0,0 +1,159 @@ +*cp.txt* Competitive programming plugin for Neovim + +Author: frozen +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 + +COMMANDS *cp-commands* + + *:CP* +:CP {contest} Set up contest environment for {contest}. + Creates build/ and io/ directories. + 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 (a, b, c, etc.) + +: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* + +Configure CP.nvim by calling setup() in your init.lua: > + + require('cp').setup({ + contests = { + atcoder = { + cpp_version = 23, + timeout_ms = 3000, + }, + custom = { + cpp_version = 20, + compile_flags = {"-O2", "-DLOCAL"}, + debug_flags = {"-g", "-fsanitize=address"}, + timeout_ms = 2000, + }, + }, + snippets = { + -- LuaSnip snippets for contest types + }, + hooks = { + before_run = function(problem_id) + -- Called before running problem + end, + before_debug = function(problem_id) + -- Called before debugging problem + end, + }, + }) + +Configuration options: + +contests Dictionary of contest configurations. + Each contest inherits from 'default'. + + cpp_version C++ standard version (17, 20, 23, etc.) + compile_flags List of compiler flags for release builds + debug_flags List of compiler flags for debug builds + timeout_ms Execution timeout in milliseconds + +snippets Custom 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 + +3. Write solution in abc123a.cc + +4. Test solution: > + :CP run +< + Output appears in vertical split showing execution time, output, and + whether it matches expected output. + +5. Debug if needed: > + :CP debug +< +6. Compare outputs visually: > + :CP diff +< + Enters 3-way diff mode with actual, expected, and input files. + +FILE STRUCTURE *cp-files* + +After setting up a problem, your directory structure looks like: > + + problem.cc # Source file + build/ # Compiled executables + io/ + problem.in # Test input + problem.out # Actual output + problem.expected # Expected output + +The plugin manages all files automatically. You only need to edit the +source file. + +HEALTH CHECK *cp-health* + +Run |:checkhealth| cp to verify your setup: + - Neovim version compatibility + - uv package manager availability + - Python virtual environment status + - Problem scrapers presence + - LuaSnip integration + - Directory structure + +TROUBLESHOOTING *cp-troubleshooting* + +Problem scraping fails ~ + - Ensure uv is installed: https://docs.astral.sh/uv/ + - Check internet connectivity + - Verify contest and problem IDs are correct + - Run |:checkhealth| cp for detailed diagnostics + +Compilation errors ~ + - Check C++ version compatibility with contest requirements + - Verify compiler flags in configuration + - Ensure source file syntax is correct + +Template expansion not working ~ + - Install LuaSnip plugin for automatic snippet expansion + - Check snippets configuration + - Ensure file is empty when opening new problem + +For more help, report issues at: https://github.com/frozen/cp.nvim + + vim:tw=78:ts=8:ft=help:norl: \ No newline at end of file diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 12920ce..7793247 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 From bdb0703986a6ffafc94a34fa15ce9837da15a3ca Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 18:12:05 -0500 Subject: [PATCH 2/4] feat(doc): update docs --- doc/cp.txt | 171 ++++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/doc/cp.txt b/doc/cp.txt index e21d2a6..0c5a11f 100644 --- a/doc/cp.txt +++ b/doc/cp.txt @@ -1,23 +1,28 @@ *cp.txt* Competitive programming plugin for Neovim -Author: frozen +Author: Barrett Ruth License: Same terms as Vim itself (see |license|) -INTRODUCTION *cp* *cp.nvim* +INTRODUCTION *cp* *cp.nvim* -CP.nvim is a competitive programming plugin that automates problem setup, +cp.nvim is a competitive programming plugin that automates problem setup, compilation, and testing workflow for online judges. -Supported platforms: - - AtCoder - - Codeforces - - CSES +Supported platforms: AtCoder, Codeforces, CSES -COMMANDS *cp-commands* +REQUIREMENTS *cp-requirements* - *:CP* +- 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}. - Creates build/ and io/ directories. Available contests: atcoder, codeforces, cses :CP {contest} {problem} Set up problem from {contest}. Scrapes test @@ -25,7 +30,7 @@ COMMANDS *cp-commands* :CP {contest} {problem} {letter} For AtCoder/Codeforces: set up problem with - specific letter (a, b, c, etc.) + specific letter (e.g. a, b, c) :CP {problem} Set up {problem} in current contest mode. Requires contest to be set first. @@ -39,53 +44,65 @@ COMMANDS *cp-commands* :CP diff Enter diff mode to compare actual vs expected output. Run again to exit diff mode. -CONFIGURATION *cp-config* +CONFIGURATION *cp-config* -Configure CP.nvim by calling setup() in your init.lua: > +cp.nvim is automatically lazy-loaded - no config/setup is required. - require('cp').setup({ - contests = { - atcoder = { - cpp_version = 23, - timeout_ms = 3000, - }, - custom = { - cpp_version = 20, - compile_flags = {"-O2", "-DLOCAL"}, - debug_flags = {"-g", "-fsanitize=address"}, - timeout_ms = 2000, - }, - }, - snippets = { - -- LuaSnip snippets for contest types - }, - hooks = { - before_run = function(problem_id) - -- Called before running problem - end, - before_debug = function(problem_id) - -- Called before debugging problem - end, - }, - }) +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'. +contests Dictionary of contest configurations - each contest inherits from 'default'. - cpp_version C++ standard version (17, 20, 23, etc.) - compile_flags List of compiler flags for release builds - debug_flags List of compiler flags for debug builds - timeout_ms Execution timeout in milliseconds + 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 Custom LuaSnip snippets by contest type +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* +WORKFLOW *cp-workflow* 1. Set up contest environment: > :CP atcoder @@ -94,66 +111,46 @@ WORKFLOW *cp-workflow* :CP abc123 a < This creates abc123a.cc and scrapes test cases to io/abc123a.in and - io/abc123a.expected + io/abc123a.expected. Alternatively, run :CP atcoder abc123 a -3. Write solution in abc123a.cc +3. Write, test, and view your solution output: -4. Test solution: > :CP run < Output appears in vertical split showing execution time, output, and whether it matches expected output. -5. Debug if needed: > +4. Debug if needed: + :CP debug < -6. Compare outputs visually: > +5. Compare actual vs. expected output visually: > + :CP diff < Enters 3-way diff mode with actual, expected, and input files. -FILE STRUCTURE *cp-files* +FILE STRUCTURE *cp-files* -After setting up a problem, your directory structure looks like: > +cp.nvim creates the following file structure upon setup: - problem.cc # Source file - build/ # Compiled executables + problem.cc + build/*.{run,debug} io/ - problem.in # Test input - problem.out # Actual output - problem.expected # Expected output + problem.in + problem.out + problem.expected -The plugin manages all files automatically. You only need to edit the -source file. +SNIPPETS *cp-snippets* -HEALTH CHECK *cp-health* +cp.nvim integrates with LuaSnip for automatic template expansion. When you +open a new problem file, type the contest name and press to expand. -Run |:checkhealth| cp to verify your setup: - - Neovim version compatibility - - uv package manager availability - - Python virtual environment status - - Problem scrapers presence - - LuaSnip integration - - Directory structure +Built-in snippets include basic C++ templates for each contest type. +Custom snippets can be added via configuration. -TROUBLESHOOTING *cp-troubleshooting* +HEALTH CHECK *cp-health* -Problem scraping fails ~ - - Ensure uv is installed: https://docs.astral.sh/uv/ - - Check internet connectivity - - Verify contest and problem IDs are correct - - Run |:checkhealth| cp for detailed diagnostics +Run |:checkhealth| cp to verify your setup. -Compilation errors ~ - - Check C++ version compatibility with contest requirements - - Verify compiler flags in configuration - - Ensure source file syntax is correct - -Template expansion not working ~ - - Install LuaSnip plugin for automatic snippet expansion - - Check snippets configuration - - Ensure file is empty when opening new problem - -For more help, report issues at: https://github.com/frozen/cp.nvim - - vim:tw=78:ts=8:ft=help:norl: \ No newline at end of file + vim:tw=78:ts=8:ft=help:norl: From 9b30ceed955a6b57db78a4b14b5361b4a1f77b58 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 18:15:10 -0500 Subject: [PATCH 3/4] fix(health): cp nvim health checkup --- lua/cp/health.lua | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) 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 From c13939c26890ad1b60403889d92f1c5f41fa4343 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 18:15:44 -0500 Subject: [PATCH 4/4] fix: format --- lua/cp/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 7793247..e5b683b 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -253,7 +253,7 @@ function M.handle_command(opts) 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, ", ")),