From 286d21cd0edbd9b5022d94c6f3c73b90c69d38a7 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 08:49:22 -0500 Subject: [PATCH 1/5] update config for cpp versions --- lua/cp/config.lua | 45 +++++++++++++++++++++++++++++++++++++++++---- lua/cp/init.lua | 5 +++++ templates/.clangd | 33 --------------------------------- templates/makefile | 1 - 4 files changed, 46 insertions(+), 38 deletions(-) delete mode 100644 templates/.clangd diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 3fcf4d1..5860363 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -2,15 +2,52 @@ local M = {} M.defaults = { contests = { - atcoder = { cpp_version = 23 }, - codeforces = { cpp_version = 23 }, - cses = { cpp_version = 20 }, + default = { + cpp_version = 20, + compile_flags = { "-O2", "-DLOCAL", "-Wall", "-Wextra" }, + debug_flags = { "-g3", "-fsanitize=address,undefined", "-DLOCAL" }, + timeout_ms = 2000, + }, + atcoder = { + cpp_version = 23, + }, + codeforces = { + cpp_version = 23, + }, + cses = {}, }, snippets = {}, } +local function extend_contest_config(base_config, contest_config) + local result = vim.deepcopy(base_config) + + for key, value in pairs(contest_config) do + if key == "compile_flags" or key == "debug_flags" then + vim.list_extend(result[key], value) + else + result[key] = value + end + end + + local std_flag = ("-std=c++%d"):format(result.cpp_version) + table.insert(result.compile_flags, 1, std_flag) + table.insert(result.debug_flags, 1, std_flag) + + return result +end + function M.setup(user_config) - return vim.tbl_deep_extend("force", M.defaults, user_config or {}) + local config = vim.tbl_deep_extend("force", M.defaults, user_config or {}) + + local default_contest = config.contests.default + for contest_name, contest_config in pairs(config.contests) do + if contest_name ~= "default" then + config.contests[contest_name] = extend_contest_config(default_contest, contest_config) + end + end + + return config end return M diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 98a44e6..fb73e63 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -8,6 +8,11 @@ local function log(msg, level) vim.notify(("[cp.nvim]: %s"):format(msg), level or vim.log.levels.INFO) end +if not vim.fn.has("nvim-0.10.0") then + log("cp.nvim requires Neovim 0.10.0+", vim.log.levels.ERROR) + return M +end + local function clearcol() vim.api.nvim_set_option_value("number", false, { scope = "local" }) vim.api.nvim_set_option_value("relativenumber", false, { scope = "local" }) diff --git a/templates/.clangd b/templates/.clangd deleted file mode 100644 index e5046e6..0000000 --- a/templates/.clangd +++ /dev/null @@ -1,33 +0,0 @@ -CompileFlags: - Add: - -O2 - -Wall - -Wextra - -Wpedantic - -Wshadow - -Wformat=2 - -Wfloat-equal - -Wlogical-op - -Wshift-overflow=2 - -Wnon-virtual-dtor - -Wold-style-cast - -Wcast-qual - -Wuseless-cast - -Wno-sign-promotion - -Wcast-align - -Wunused - -Woverloaded-virtual - -Wconversion - -Wsign-conversion - -Wmisleading-indentation - -Wduplicated-cond - -Wduplicated-branches - -Wlogical-op - -Wnull-dereference - -Wformat=2 - -Wformat-overflow - -Wformat-truncation - -Wdouble-promotion - -Wundef - -DLOCAL - -Wno-unknown-pragmas diff --git a/templates/makefile b/templates/makefile index 30acf70..0ae8c41 100644 --- a/templates/makefile +++ b/templates/makefile @@ -19,7 +19,6 @@ setup: test -d build || mkdir -p build test -d io || mkdir -p io test -f compile_flags.txt && echo -std=c++$(VERSION) >>compile_flags.txt - test -f .clangd && echo -e "\t\t-std=c++$(VERSION)" >>.clangd init: make setup From c4a7dc82156c5efeb5a8da57542beb1722aa916b Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 09:16:01 -0500 Subject: [PATCH 2/5] more updates --- lua/cp/config.lua | 10 +-- lua/cp/execute.lua | 111 +++++++++++++++++++++++++++++++ lua/cp/init.lua | 67 ++++++++++++------- lua/cp/scrape.lua | 68 +++++++++++++++++++ readme.md | 2 - templates/compile_flags.txt | 2 - templates/debug_flags.txt | 3 - templates/scrapers/atcoder.py | 36 +++++++--- templates/scrapers/codeforces.py | 39 +++++++---- templates/scrapers/cses.py | 46 ++++++++++--- 10 files changed, 311 insertions(+), 73 deletions(-) create mode 100644 lua/cp/execute.lua create mode 100644 lua/cp/scrape.lua delete mode 100644 templates/compile_flags.txt delete mode 100644 templates/debug_flags.txt diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 5860363..1bbda23 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -20,15 +20,7 @@ M.defaults = { } local function extend_contest_config(base_config, contest_config) - local result = vim.deepcopy(base_config) - - for key, value in pairs(contest_config) do - if key == "compile_flags" or key == "debug_flags" then - vim.list_extend(result[key], value) - else - result[key] = value - end - end + local result = vim.tbl_deep_extend("force", base_config, contest_config) local std_flag = ("-std=c++%d"):format(result.cpp_version) table.insert(result.compile_flags, 1, std_flag) diff --git a/lua/cp/execute.lua b/lua/cp/execute.lua new file mode 100644 index 0000000..f4d0d78 --- /dev/null +++ b/lua/cp/execute.lua @@ -0,0 +1,111 @@ +local M = {} + +local signal_codes = { + [128] = "SIGILL", + [130] = "SIGABRT", + [131] = "SIGBUS", + [136] = "SIGFPE", + [135] = "SIGSEGV", + [137] = "SIGPIPE", + [139] = "SIGTERM", +} + +local function get_paths(problem_id) + return { + source = ("%s.cc"):format(problem_id), + binary = ("build/%s"):format(problem_id), + input = ("io/%s.in"):format(problem_id), + output = ("io/%s.out"):format(problem_id), + expected = ("io/%s.expected"):format(problem_id), + } +end + +local function ensure_directories() + vim.system({ "mkdir", "-p", "build", "io" }):wait() +end + +local function compile_cpp(source_path, binary_path, flags) + local compile_cmd = { "g++", unpack(flags), source_path, "-o", binary_path } + return vim.system(compile_cmd, { text = true }):wait() +end + +local function execute_binary(binary_path, input_data, timeout_ms) + local start_time = vim.loop.hrtime() + + local result = vim.system({ binary_path }, { + stdin = input_data, + timeout = timeout_ms, + text = true, + }):wait() + + local end_time = vim.loop.hrtime() + local execution_time = (end_time - start_time) / 1000000 + + return { + stdout = result.stdout or "", + stderr = result.stderr or "", + code = result.code, + time_ms = execution_time, + timed_out = result.code == 124, + } +end + +local function format_output(exec_result, expected_file) + local lines = { exec_result.stdout } + + if exec_result.timed_out then + table.insert(lines, "\n[code]: 124 (TIMEOUT)") + elseif exec_result.code >= 128 then + local signal_name = signal_codes[exec_result.code] or "SIGNAL" + table.insert(lines, ("\n[code]: %d (%s)"):format(exec_result.code, signal_name)) + else + table.insert(lines, ("\n[code]: %d"):format(exec_result.code)) + end + + table.insert(lines, ("\n[time]: %.2f ms"):format(exec_result.time_ms)) + table.insert(lines, "\n[debug]: false") + + if vim.fn.filereadable(expected_file) == 1 and exec_result.code == 0 then + local expected_content = vim.fn.readfile(expected_file) + local actual_lines = vim.split(exec_result.stdout, "\n") + + local matches = #actual_lines == #expected_content + if matches then + for i, line in ipairs(actual_lines) do + if line ~= expected_content[i] then + matches = false + break + end + end + end + + table.insert(lines, ("\n[matches]: %s"):format(matches and "true" or "false")) + end + + return table.concat(lines, "") +end + +function M.run_problem(problem_id, contest_config, is_debug) + ensure_directories() + + local paths = get_paths(problem_id) + local flags = is_debug and contest_config.debug_flags or contest_config.compile_flags + + local compile_result = compile_cpp(paths.source, paths.binary, flags) + if compile_result.code ~= 0 then + vim.fn.writefile({ compile_result.stderr }, paths.output) + return + end + + local input_data = "" + if vim.fn.filereadable(paths.input) == 1 then + input_data = table.concat(vim.fn.readfile(paths.input), "\n") .. "\n" + end + + local exec_result = execute_binary(paths.binary, input_data, contest_config.timeout_ms) + local formatted_output = format_output(exec_result, paths.expected) + + vim.fn.writefile(vim.split(formatted_output, "\n"), paths.output) +end + +return M diff --git a/lua/cp/init.lua b/lua/cp/init.lua index fb73e63..09a1dc4 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -1,5 +1,7 @@ local config_module = require("cp.config") local snippets = require("cp.snippets") +local execute = require("cp.execute") +local scrape = require("cp.scrape") local M = {} local config = {} @@ -40,9 +42,9 @@ local function setup_python_env() if vim.fn.isdirectory(venv_dir) == 0 then log("setting up Python environment for scrapers...") - local result = vim.fn.system(("cd %s && uv sync"):format(vim.fn.shellescape(plugin_path))) - if vim.v.shell_error ~= 0 then - log("failed to setup Python environment: " .. result, vim.log.levels.ERROR) + local result = vim.system({ "uv", "sync" }, { cwd = plugin_path, text = true }):wait() + if result.code ~= 0 then + log("failed to setup Python environment: " .. result.stderr, vim.log.levels.ERROR) return false end log("python environment setup complete") @@ -63,8 +65,8 @@ local function setup_contest(contest_type) end vim.g.cp_contest = contest_type - vim.fn.system(("cp -fr %s/* ."):format(config.template_dir)) - vim.fn.system(("make setup VERSION=%s"):format(config.contests[contest_type].cpp_version)) + vim.fn.mkdir("build", "p") + vim.fn.mkdir("io", "p") log(("set up %s contest environment"):format(contest_type)) end @@ -89,17 +91,24 @@ local function setup_problem(problem_id, problem_letter) vim.cmd.only() - local filename, full_problem_id - if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and problem_letter then - full_problem_id = problem_id .. problem_letter - filename = full_problem_id .. ".cc" - vim.fn.system(("make scrape %s %s %s"):format(vim.g.cp_contest, problem_id, problem_letter)) + local scrape_result = scrape.scrape_problem(vim.g.cp_contest, problem_id, problem_letter) + + if not scrape_result.success then + log("scraping failed: " .. scrape_result.error, vim.log.levels.WARN) + log("you can manually add test cases to io/ directory", vim.log.levels.INFO) else - full_problem_id = problem_id - filename = problem_id .. ".cc" - vim.fn.system(("make scrape %s %s"):format(vim.g.cp_contest, problem_id)) + log(("scraped %d test case(s) for %s"):format(scrape_result.test_count, scrape_result.problem_id)) end + local full_problem_id = scrape_result.success and scrape_result.problem_id + or ( + (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") + and problem_letter + and problem_id .. problem_letter:upper() + or problem_id + ) + local filename = full_problem_id .. ".cc" + vim.cmd.e(filename) if vim.api.nvim_buf_get_lines(0, 0, -1, true)[1] == "" then @@ -150,10 +159,16 @@ local function run_problem() lsp.lsp_format({ async = true }) end - vim.system({ "make", "run", vim.fn.expand("%:t") }, {}, function() - vim.schedule(function() - vim.cmd.checktime() - end) + if not vim.g.cp_contest then + log("no contest mode set", vim.log.levels.ERROR) + return + end + + local contest_config = config.contests[vim.g.cp_contest] + + vim.schedule(function() + execute.run_problem(problem_id, contest_config, false) + vim.cmd.checktime() end) end @@ -168,10 +183,16 @@ local function debug_problem() lsp.lsp_format({ async = true }) end - vim.system({ "make", "debug", vim.fn.expand("%:t") }, {}, function() - vim.schedule(function() - vim.cmd.checktime() - end) + if not vim.g.cp_contest then + log("no contest mode set", vim.log.levels.ERROR) + return + end + + local contest_config = config.contests[vim.g.cp_contest] + + vim.schedule(function() + execute.run_problem(problem_id, contest_config, true) + vim.cmd.checktime() end) end @@ -202,7 +223,8 @@ local function diff_problem() end local temp_output = vim.fn.tempname() - vim.fn.system(("awk '/^\\[[^]]*\\]:/ {exit} {print}' %s > %s"):format(vim.fn.shellescape(output), temp_output)) + local result = vim.system({ "awk", "/^\\[[^]]*\\]:/ {exit} {print}", output }, { text = true }):wait() + vim.fn.writefile(vim.split(result.stdout, "\n"), temp_output) local session_file = vim.fn.tempname() .. ".vim" vim.cmd(("silent! mksession! %s"):format(session_file)) @@ -239,7 +261,6 @@ function M.setup(user_config) config = config_module.setup(user_config) local plugin_path = get_plugin_path() - config.template_dir = plugin_path .. "/templates" config.snippets.path = plugin_path .. "/templates/snippets" snippets.setup(config) diff --git a/lua/cp/scrape.lua b/lua/cp/scrape.lua new file mode 100644 index 0000000..dd0bb79 --- /dev/null +++ b/lua/cp/scrape.lua @@ -0,0 +1,68 @@ +local M = {} + +local function get_plugin_path() + local plugin_path = debug.getinfo(1, "S").source:sub(2) + return vim.fn.fnamemodify(plugin_path, ":h:h:h") +end + +local function ensure_io_directory() + vim.fn.mkdir("io", "p") +end + +function M.scrape_problem(contest, problem_id, problem_letter) + ensure_io_directory() + + local plugin_path = get_plugin_path() + local scraper_path = plugin_path .. "/templates/scrapers/" .. contest .. ".py" + + local args + if contest == "cses" then + args = { "uv", "run", scraper_path, problem_id } + else + args = { "uv", "run", scraper_path, problem_id, problem_letter } + end + + local result = vim.system(args, { + cwd = plugin_path, + text = true, + timeout = 30000, + }):wait() + + if result.code ~= 0 then + return { + success = false, + error = "Failed to run scraper: " .. (result.stderr or "Unknown error"), + } + end + + local ok, data = pcall(vim.json.decode, result.stdout) + if not ok then + return { + success = false, + error = "Failed to parse scraper output: " .. tostring(data), + } + end + + if not data.success then + return data + end + + local full_problem_id = data.problem_id + local input_file = "io/" .. full_problem_id .. ".in" + local expected_file = "io/" .. full_problem_id .. ".expected" + + if #data.test_cases > 0 then + local first_test = data.test_cases[1] + vim.fn.writefile(vim.split(first_test.input, "\n"), input_file) + vim.fn.writefile(vim.split(first_test.output, "\n"), expected_file) + end + + return { + success = true, + problem_id = full_problem_id, + test_count = #data.test_cases, + url = data.url, + } +end + +return M diff --git a/readme.md b/readme.md index 15405b5..cda6d7c 100644 --- a/readme.md +++ b/readme.md @@ -19,8 +19,6 @@ neovim plugin for competitive programming. - `make` - [uv](https://docs.astral.sh/uv/): problem scraping (optional) - [LuaSnip](https://github.com/L3MON4D3/LuaSnip): contest-specific snippets (optional) -- [vim-zoom](https://github.com/dhruvasagar/vim-zoom): better diff view - (optional) ## Installation diff --git a/templates/compile_flags.txt b/templates/compile_flags.txt deleted file mode 100644 index 04b1b00..0000000 --- a/templates/compile_flags.txt +++ /dev/null @@ -1,2 +0,0 @@ --O2 --DLOCAL diff --git a/templates/debug_flags.txt b/templates/debug_flags.txt deleted file mode 100644 index a2c29c5..0000000 --- a/templates/debug_flags.txt +++ /dev/null @@ -1,3 +0,0 @@ --g3 --fsanitize=address,undefined --DLOCAL diff --git a/templates/scrapers/atcoder.py b/templates/scrapers/atcoder.py index 374ffdb..788a573 100644 --- a/templates/scrapers/atcoder.py +++ b/templates/scrapers/atcoder.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import json import sys import requests @@ -57,12 +58,17 @@ def scrape(url: str) -> list[tuple[str, str]]: def main(): if len(sys.argv) != 3: - print("Usage: atcoder.py ", file=sys.stderr) - print("Example: atcoder.py abc042 a", file=sys.stderr) + result = { + "success": False, + "error": "Usage: atcoder.py ", + "problem_id": None, + } + print(json.dumps(result)) sys.exit(1) contest_id = sys.argv[1] problem_letter = sys.argv[2] + problem_id = contest_id + problem_letter url = parse_problem_url(contest_id, problem_letter) print(f"Scraping: {url}", file=sys.stderr) @@ -70,17 +76,27 @@ def main(): tests = scrape(url) if not tests: - print(f"No tests found for {contest_id} {problem_letter}", file=sys.stderr) + result = { + "success": False, + "error": f"No tests found for {contest_id} {problem_letter}", + "problem_id": problem_id, + "url": url, + } + print(json.dumps(result)) sys.exit(1) - print("---INPUT---") - print(len(tests)) + test_cases = [] for input_data, output_data in tests: - print(input_data) - print("---OUTPUT---") - for input_data, output_data in tests: - print(output_data) - print("---END---") + test_cases.append({"input": input_data, "output": output_data}) + + result = { + "success": True, + "problem_id": problem_id, + "url": url, + "test_cases": test_cases, + } + + print(json.dumps(result)) if __name__ == "__main__": diff --git a/templates/scrapers/codeforces.py b/templates/scrapers/codeforces.py index ed31990..d1c24fa 100644 --- a/templates/scrapers/codeforces.py +++ b/templates/scrapers/codeforces.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import json import sys import cloudscraper @@ -73,31 +74,43 @@ def scrape_sample_tests(url: str): def main(): if len(sys.argv) != 3: - print("Usage: codeforces.py ", file=sys.stderr) - print("Example: codeforces.py 1234 A", file=sys.stderr) + result = { + "success": False, + "error": "Usage: codeforces.py ", + "problem_id": None, + } + print(json.dumps(result)) sys.exit(1) contest_id = sys.argv[1] problem_letter = sys.argv[2] + problem_id = contest_id + problem_letter.upper() url = parse_problem_url(contest_id, problem_letter) tests = scrape_sample_tests(url) if not tests: - print(f"No tests found for {contest_id} {problem_letter}", file=sys.stderr) - print( - "Consider adding test cases manually to the io/ directory", file=sys.stderr - ) + result = { + "success": False, + "error": f"No tests found for {contest_id} {problem_letter}", + "problem_id": problem_id, + "url": url, + } + print(json.dumps(result)) sys.exit(1) - print("---INPUT---") - print(len(tests)) + test_cases = [] for input_data, output_data in tests: - print(input_data) - print("---OUTPUT---") - for input_data, output_data in tests: - print(output_data) - print("---END---") + test_cases.append({"input": input_data, "output": output_data}) + + result = { + "success": True, + "problem_id": problem_id, + "url": url, + "test_cases": test_cases, + } + + print(json.dumps(result)) if __name__ == "__main__": diff --git a/templates/scrapers/cses.py b/templates/scrapers/cses.py index 38d43aa..8da2ba6 100755 --- a/templates/scrapers/cses.py +++ b/templates/scrapers/cses.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import json import sys import requests @@ -57,31 +58,54 @@ def scrape(url: str) -> list[tuple[str, str]]: def main(): if len(sys.argv) != 2: - print("Usage: cses.py ", file=sys.stderr) + result = { + "success": False, + "error": "Usage: cses.py ", + "problem_id": None, + } + print(json.dumps(result)) sys.exit(1) problem_input = sys.argv[1] url = parse_problem_url(problem_input) if not url: - print(f"Invalid problem input: {problem_input}", file=sys.stderr) - print("Use either problem ID (e.g., 1068) or full URL", file=sys.stderr) + result = { + "success": False, + "error": f"Invalid problem input: {problem_input}. Use either problem ID (e.g., 1068) or full URL", + "problem_id": problem_input if problem_input.isdigit() else None, + } + print(json.dumps(result)) sys.exit(1) tests = scrape(url) + problem_id = ( + problem_input if problem_input.isdigit() else problem_input.split("/")[-1] + ) + if not tests: - print(f"No tests found for {problem_input}", file=sys.stderr) + result = { + "success": False, + "error": f"No tests found for {problem_input}", + "problem_id": problem_id, + "url": url, + } + print(json.dumps(result)) sys.exit(1) - print("---INPUT---") - print(len(tests)) + test_cases = [] for input_data, output_data in tests: - print(input_data) - print("---OUTPUT---") - for input_data, output_data in tests: - print(output_data) - print("---END---") + test_cases.append({"input": input_data, "output": output_data}) + + result = { + "success": True, + "problem_id": problem_id, + "url": url, + "test_cases": test_cases, + } + + print(json.dumps(result)) if __name__ == "__main__": From a6abcc3fc3f77ce743aea3be39dbf3331431082a Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 09:21:49 -0500 Subject: [PATCH 3/5] feat: window mgmt --- CLAUDE.md | 333 ++++++++++++++++++++++++++++++++++++++++++++++ lua/cp/init.lua | 47 ++----- lua/cp/window.lua | 75 +++++++++++ readme.md | 8 +- 4 files changed, 421 insertions(+), 42 deletions(-) create mode 100644 CLAUDE.md create mode 100644 lua/cp/window.lua diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3598578 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,333 @@ +# CP.nvim Analysis & Modernization Plan + +## Current Architecture Overview + +### Plugin Structure +- **Main Logic**: `lua/cp/init.lua` (297 lines) - Core plugin functionality +- **Configuration**: `lua/cp/config.lua` (16 lines) - Contest settings and defaults +- **Snippets**: `lua/cp/snippets.lua` (21 lines) - LuaSnip integration +- **Entry Point**: `plugin/cp.lua` (7 lines) - Plugin initialization +- **Vim Integration**: `after/` directory with filetype detection, syntax highlighting, and buffer settings + +### Current Build System (Make/Shell-Based) + +#### Templates Structure +``` +templates/ +├── makefile # Build orchestration +├── compile_flags.txt # Release flags: -O2, -DLOCAL +├── debug_flags.txt # Debug flags: -g3, -fsanitize=address,undefined, -DLOCAL +├── .clang-format # Code formatting rules (Google style) +├── scripts/ +│ ├── run.sh # Compilation + execution workflow +│ ├── debug.sh # Debug compilation + execution +│ ├── scrape.sh # Problem scraping orchestration +│ └── utils.sh # Core build/execution utilities +├── scrapers/ # Python scrapers for each judge +│ ├── atcoder.py # AtCoder problem scraping +│ ├── codeforces.py # Codeforces problem scraping (with cloudscraper) +│ └── cses.py # CSES problem scraping +└── io/ # Input/output files directory +``` + +#### Build Workflow Analysis + +**Compilation Process** (`utils.sh:compile_source`): +```bash +g++ @compile_flags.txt $flags "$src" -o "$bin" 2>"$output" +``` + +**Execution Process** (`utils.sh:execute_binary`): +- **Timeout**: 2-second hardcoded timeout using `timeout 2s` +- **Debug Mode**: LD_PRELOAD with AddressSanitizer when debugging +- **Output Processing**: + - Truncates to first 1000 lines + - Appends metadata: `[code]`, `[time]`, `[debug]`, `[matches]` + - Compares with `.expected` file if available +- **Signal Handling**: Maps exit codes to signal names (SIGSEGV, SIGFPE, etc.) + +### Current Neovim Integration + +#### Command System +- `:CP ` - Setup contest environment +- `:CP [letter]` - Setup problem + scrape +- `:CP run` - Compile and execute current problem +- `:CP debug` - Compile with debug flags and execute +- `:CP diff` - Toggle diff mode between output and expected + +#### Current vim.system Usage +```lua +-- Only used for async compilation in init.lua:148, 166 +vim.system({ "make", "run", vim.fn.expand("%:t") }, {}, callback) +vim.system({ "make", "debug", vim.fn.expand("%:t") }, {}, callback) +``` + +#### Window Management +- **Current Layout**: Code editor | Output window + Input window (vsplit + split) +- **Diff Mode**: + - Uses `vim.cmd.diffthis()` and `vim.cmd.diffoff()` + - Session saving with `mksession!` and restoration + - Manual window arrangement with `vim.cmd.only()`, splits, and `wincmd` + - **vim-zoom dependency**: Listed as optional for "better diff view" but not used in code + +#### File Type Integration +- **Auto-detection**: `*/io/*.in` → `cpinput`, `*/io/*.out` → `cpoutput` +- **Buffer Settings**: Disables line numbers, signs, status column for I/O files +- **Syntax Highlighting**: Custom syntax for output metadata (`[code]`, `[time]`, etc.) + +### Current Configuration System + +#### Contest-Specific Settings +```lua +defaults = { + contests = { + atcoder = { cpp_version = 23 }, + codeforces = { cpp_version = 23 }, + cses = { cpp_version = 20 }, + }, + snippets = {}, +} +``` + +#### File-Based Configuration Dependencies +- `compile_flags.txt` - Compiler flags for release builds +- `debug_flags.txt` - Debug-specific compiler flags +- `.clang-format` - Code formatting configuration +- **Missing**: `.clangd` file (referenced in makefile but doesn't exist) + +--- + +## Proposed Modernization: Full Lua Migration + +### 1. Replace Make/Shell System with vim.system + +#### Benefits of Native Lua Implementation +- **Better Error Handling**: Lua error handling vs shell exit codes +- **Timeout Control**: `vim.system` supports timeout parameter directly +- **Streaming I/O**: Native stdin/stdout handling without temp files +- **Progress Reporting**: Real-time compilation/execution feedback +- **Cross-Platform**: Remove shell script dependencies + +#### Proposed Build System +```lua +local function compile_cpp(src_path, output_path, flags, timeout_ms) + local compile_cmd = { "g++", unpack(flags), src_path, "-o", output_path } + return vim.system(compile_cmd, { timeout = timeout_ms or 10000 }) +end + +local function execute_with_timeout(binary_path, input_data, timeout_ms) + return vim.system( + { binary_path }, + { + stdin = input_data, + timeout = timeout_ms or 2000, + stdout = true, + stderr = true, + } + ) +end +``` + +### 2. Configuration Migration to Pure Lua + +#### Replace File-Based Config with Lua Tables +```lua +config = { + contests = { + atcoder = { + cpp_version = 23, + compile_flags = { "-std=c++23", "-O2", "-DLOCAL", "-Wall", "-Wextra" }, + debug_flags = { "-std=c++23", "-g3", "-fsanitize=address,undefined", "-DLOCAL" }, + timeout_ms = 2000, + }, + -- ... other contests + }, + clangd_config = { + CompileFlags = { Add = { "-std=c++23", "-DLOCAL" } }, + Diagnostics = { ClangTidy = { Add = { "readability-*" } } }, + }, + clang_format = { + BasedOnStyle = "Google", + AllowShortFunctionsOnASingleLine = false, + -- ... other formatting options + } +} +``` + +#### Dynamic File Generation +- Generate `.clangd` YAML from Lua config +- Generate `.clang-format` from Lua config +- Eliminate static template files + +### 3. Enhanced Window Management (Remove vim-zoom Dependency) + +#### Current Diff Implementation Issues +- Manual session management with temp files +- No proper view state restoration +- Hardcoded window arrangements + +#### Proposed Native Window Management +```lua +local function save_window_state() + return { + layout = vim.fn.winrestcmd(), + views = vim.tbl_map(function(win) + return { + winid = win, + view = vim.fn.winsaveview(), + bufnr = vim.api.nvim_win_get_buf(win) + } + end, vim.api.nvim_list_wins()) + } +end + +local function restore_window_state(state) + vim.cmd(state.layout) + for _, view_state in ipairs(state.views) do + if vim.api.nvim_win_is_valid(view_state.winid) then + vim.api.nvim_win_call(view_state.winid, function() + vim.fn.winrestview(view_state.view) + end) + end + end +end +``` + +#### Improved Diff Mode +- Use `vim.diff()` API for programmatic diff generation +- Better window state management with `winsaveview`/`winrestview` +- Native zoom functionality without external dependency + +### 4. Enhanced I/O and Timeout Management + +#### Current Limitations +- Hardcoded 2-second timeout +- Shell-based timeout implementation +- Limited output processing + +#### Proposed Improvements +```lua +local function execute_solution(config) + local start_time = vim.loop.hrtime() + + local result = vim.system( + { config.binary_path }, + { + stdin = config.input_data, + timeout = config.timeout_ms, + stdout = true, + stderr = true, + } + ) + + local end_time = vim.loop.hrtime() + local execution_time = (end_time - start_time) / 1000000 -- Convert to ms + + return { + stdout = result.stdout, + stderr = result.stderr, + code = result.code, + time_ms = execution_time, + timed_out = result.code == 124, + } +end +``` + +### 5. Integrated Problem Scraping + +#### Current Python Integration +- Separate uv environment management +- Shell script orchestration for scraping +- External Python dependencies (requests, beautifulsoup4, cloudscraper) + +#### Proposed Native Integration Options + +**Option A: Keep Python, Improve Integration** +```lua +local function scrape_problem(contest, problem_id, problem_letter) + local scraper_path = get_scraper_path(contest) + local args = contest == "cses" and { problem_id } or { problem_id, problem_letter } + + return vim.system( + { "uv", "run", scraper_path, unpack(args) }, + { cwd = plugin_path, timeout = 30000 } + ) +end +``` + +**Option B: Native Lua HTTP (Future)** +- Wait for Neovim native HTTP client +- Eliminate Python dependency entirely +- Pure Lua HTML parsing (challenging) + +--- + +## Implementation Challenges & Considerations + +### Technical Feasibility + +#### ✅ **Definitely Possible** +- Replace makefile with vim.system calls +- Migrate configuration to pure Lua tables +- Implement native window state management +- Add configurable timeouts +- Remove vim-zoom dependency + +#### ⚠️ **Requires Careful Implementation** +- **Signal Handling**: Shell scripts handle SIGSEGV, SIGFPE mapping - need Lua equivalent +- **AddressSanitizer Integration**: LD_PRELOAD handling in debug mode +- **Cross-Platform**: Shell scripts provide some cross-platform abstraction + +#### 🤔 **Complex/Questionable** +- **Complete Python Elimination**: Would require native HTTP client + HTML parsing +- **Output Truncation Logic**: Currently truncates to 1000 lines efficiently in shell + +### Migration Strategy + +#### Phase 1: Core Build System +1. Replace `vim.system({ "make", ... })` with direct `vim.system({ "g++", ... })` +2. Migrate compile/debug flags from txt files to Lua config +3. Implement native timeout and execution management + +#### Phase 2: Window Management +1. Implement native window state saving/restoration +2. Remove vim-zoom dependency mention +3. Enhance diff mode with better view management + +#### Phase 3: Configuration Integration +1. Generate .clangd/.clang-format from Lua config +2. Consolidate all configuration in single config table +3. Add runtime configuration validation + +#### Phase 4: Enhanced Features +1. Configurable timeouts per contest +2. Better error reporting and progress feedback +3. Enhanced output processing and metadata + +### User Experience Impact + +#### Advantages +- **Simpler Dependencies**: No external shell scripts or makefile +- **Better Error Messages**: Native Lua error handling +- **Configurable Timeouts**: Per-contest timeout settings +- **Improved Performance**: Direct system calls vs shell interpretation +- **Better Integration**: Native Neovim APIs throughout + +#### Potential Concerns +- **Compatibility**: Users relying on current makefile system +- **Feature Parity**: Ensuring all current functionality is preserved +- **Debugging**: Shell scripts are easier to debug independently + +--- + +## Recommendation + +**Proceed with modernization** - The proposed changes align well with Neovim 0.9+ capabilities and would significantly improve the plugin's maintainability and user experience. The migration is technically feasible with the main complexity being in preserving exact feature parity during the transition. + +**Priority Order**: +1. Build system migration (highest impact, lowest risk) +2. Window management improvements (removes external dependency) +3. Configuration consolidation (improves user experience) +4. Enhanced I/O and timeout management (adds new capabilities) + +The plugin's current architecture is well-designed, making this modernization an enhancement rather than a rewrite. \ No newline at end of file diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 09a1dc4..f927ca5 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -2,6 +2,7 @@ local config_module = require("cp.config") local snippets = require("cp.snippets") local execute = require("cp.execute") local scrape = require("cp.scrape") +local window = require("cp.window") local M = {} local config = {} @@ -15,13 +16,6 @@ if not vim.fn.has("nvim-0.10.0") then return M end -local function clearcol() - vim.api.nvim_set_option_value("number", false, { scope = "local" }) - vim.api.nvim_set_option_value("relativenumber", false, { scope = "local" }) - vim.api.nvim_set_option_value("statuscolumn", "", { scope = "local" }) - vim.api.nvim_set_option_value("signcolumn", "no", { scope = "local" }) - vim.api.nvim_set_option_value("equalalways", false, { scope = "global" }) -end local function get_plugin_path() local plugin_path = debug.getinfo(1, "S").source:sub(2) @@ -129,11 +123,11 @@ local function setup_problem(problem_id, problem_letter) vim.cmd.vsplit(output) vim.cmd.w() - clearcol() + window.clearcol() vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3))) vim.cmd.split(input) vim.cmd.w() - clearcol() + window.clearcol() vim.cmd.wincmd("h") log(("switched to problem %s"):format(full_problem_id)) @@ -198,13 +192,9 @@ end local function diff_problem() if vim.g.cp_diff_mode then - vim.cmd.diffoff() - if vim.g.cp_saved_session then - vim.cmd(("silent! source %s"):format(vim.g.cp_saved_session)) - vim.fn.delete(vim.g.cp_saved_session) - vim.g.cp_saved_session = nil - end + window.restore_layout(vim.g.cp_saved_layout) vim.g.cp_diff_mode = false + vim.g.cp_saved_layout = nil log("exited diff mode") else local problem_id = get_current_problem() @@ -222,31 +212,14 @@ local function diff_problem() return end - local temp_output = vim.fn.tempname() + vim.g.cp_saved_layout = window.save_layout() + local result = vim.system({ "awk", "/^\\[[^]]*\\]:/ {exit} {print}", output }, { text = true }):wait() - vim.fn.writefile(vim.split(result.stdout, "\n"), temp_output) - - local session_file = vim.fn.tempname() .. ".vim" - vim.cmd(("silent! mksession! %s"):format(session_file)) - vim.g.cp_saved_session = session_file - - vim.cmd.diffoff() - vim.cmd.only() - - vim.cmd.edit(temp_output) - vim.cmd.diffthis() - clearcol() - - vim.cmd.vsplit(expected) - vim.cmd.diffthis() - clearcol() - - vim.cmd(("botright split %s"):format(input)) - clearcol() - vim.cmd.wincmd("k") + local actual_output = result.stdout + window.setup_diff_layout(actual_output, expected, input) + vim.g.cp_diff_mode = true - vim.g.cp_temp_output = temp_output log("entered diff mode") end end diff --git a/lua/cp/window.lua b/lua/cp/window.lua new file mode 100644 index 0000000..825d449 --- /dev/null +++ b/lua/cp/window.lua @@ -0,0 +1,75 @@ +local M = {} + +function M.clearcol() + vim.api.nvim_set_option_value("number", false, { scope = "local" }) + vim.api.nvim_set_option_value("relativenumber", false, { scope = "local" }) + vim.api.nvim_set_option_value("statuscolumn", "", { scope = "local" }) + vim.api.nvim_set_option_value("signcolumn", "no", { scope = "local" }) + vim.api.nvim_set_option_value("equalalways", false, { scope = "global" }) +end + +function M.save_layout() + local windows = {} + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_is_valid(win) then + local bufnr = vim.api.nvim_win_get_buf(win) + windows[win] = { + bufnr = bufnr, + view = vim.fn.winsaveview(), + width = vim.api.nvim_win_get_width(win), + height = vim.api.nvim_win_get_height(win), + } + end + end + + return { + windows = windows, + current_win = vim.api.nvim_get_current_win(), + layout = vim.fn.winrestcmd(), + } +end + +function M.restore_layout(state) + if not state then return end + + vim.cmd.diffoff() + vim.cmd(state.layout) + + for win, win_state in pairs(state.windows) do + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_set_current_win(win) + if vim.api.nvim_get_current_buf() == win_state.bufnr then + vim.fn.winrestview(win_state.view) + end + end + end + + if vim.api.nvim_win_is_valid(state.current_win) then + vim.api.nvim_set_current_win(state.current_win) + end +end + +function M.setup_diff_layout(actual_output, expected_output, input_file) + vim.cmd.diffoff() + vim.cmd.only() + + local output_lines = vim.split(actual_output, "\n") + local output_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, output_lines) + vim.bo[output_buf].filetype = "cpoutput" + + vim.cmd.edit() + vim.api.nvim_set_current_buf(output_buf) + vim.cmd.diffthis() + M.clearcol() + + vim.cmd.vsplit(expected_output) + vim.cmd.diffthis() + M.clearcol() + + vim.cmd(("botright split %s"):format(input_file)) + M.clearcol() + vim.cmd.wincmd("k") +end + +return M \ No newline at end of file diff --git a/readme.md b/readme.md index cda6d7c..9962669 100644 --- a/readme.md +++ b/readme.md @@ -15,9 +15,8 @@ neovim plugin for competitive programming. ## Requirements -- Neovim 0.9+ -- `make` -- [uv](https://docs.astral.sh/uv/): problem scraping (optional) +- Neovim 0.10.0+ +- [uv](https://docs.astral.sh/uv/): problem scraping (optional) - [LuaSnip](https://github.com/L3MON4D3/LuaSnip): contest-specific snippets (optional) ## Installation @@ -42,8 +41,7 @@ Using [lazy.nvim](https://github.com/folke/lazy.nvim): ## TODO -- remove vim-zoom dependency -- vimdocs +- vimdocs - example video - more flexible setup (more of a question of philosophy) - USACO support From afd4cb2165d2177ee098004fab6a59151a140e1e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 09:21:59 -0500 Subject: [PATCH 4/5] fix --- CLAUDE.md | 333 ------------------------------------------------------ 1 file changed, 333 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 3598578..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,333 +0,0 @@ -# CP.nvim Analysis & Modernization Plan - -## Current Architecture Overview - -### Plugin Structure -- **Main Logic**: `lua/cp/init.lua` (297 lines) - Core plugin functionality -- **Configuration**: `lua/cp/config.lua` (16 lines) - Contest settings and defaults -- **Snippets**: `lua/cp/snippets.lua` (21 lines) - LuaSnip integration -- **Entry Point**: `plugin/cp.lua` (7 lines) - Plugin initialization -- **Vim Integration**: `after/` directory with filetype detection, syntax highlighting, and buffer settings - -### Current Build System (Make/Shell-Based) - -#### Templates Structure -``` -templates/ -├── makefile # Build orchestration -├── compile_flags.txt # Release flags: -O2, -DLOCAL -├── debug_flags.txt # Debug flags: -g3, -fsanitize=address,undefined, -DLOCAL -├── .clang-format # Code formatting rules (Google style) -├── scripts/ -│ ├── run.sh # Compilation + execution workflow -│ ├── debug.sh # Debug compilation + execution -│ ├── scrape.sh # Problem scraping orchestration -│ └── utils.sh # Core build/execution utilities -├── scrapers/ # Python scrapers for each judge -│ ├── atcoder.py # AtCoder problem scraping -│ ├── codeforces.py # Codeforces problem scraping (with cloudscraper) -│ └── cses.py # CSES problem scraping -└── io/ # Input/output files directory -``` - -#### Build Workflow Analysis - -**Compilation Process** (`utils.sh:compile_source`): -```bash -g++ @compile_flags.txt $flags "$src" -o "$bin" 2>"$output" -``` - -**Execution Process** (`utils.sh:execute_binary`): -- **Timeout**: 2-second hardcoded timeout using `timeout 2s` -- **Debug Mode**: LD_PRELOAD with AddressSanitizer when debugging -- **Output Processing**: - - Truncates to first 1000 lines - - Appends metadata: `[code]`, `[time]`, `[debug]`, `[matches]` - - Compares with `.expected` file if available -- **Signal Handling**: Maps exit codes to signal names (SIGSEGV, SIGFPE, etc.) - -### Current Neovim Integration - -#### Command System -- `:CP ` - Setup contest environment -- `:CP [letter]` - Setup problem + scrape -- `:CP run` - Compile and execute current problem -- `:CP debug` - Compile with debug flags and execute -- `:CP diff` - Toggle diff mode between output and expected - -#### Current vim.system Usage -```lua --- Only used for async compilation in init.lua:148, 166 -vim.system({ "make", "run", vim.fn.expand("%:t") }, {}, callback) -vim.system({ "make", "debug", vim.fn.expand("%:t") }, {}, callback) -``` - -#### Window Management -- **Current Layout**: Code editor | Output window + Input window (vsplit + split) -- **Diff Mode**: - - Uses `vim.cmd.diffthis()` and `vim.cmd.diffoff()` - - Session saving with `mksession!` and restoration - - Manual window arrangement with `vim.cmd.only()`, splits, and `wincmd` - - **vim-zoom dependency**: Listed as optional for "better diff view" but not used in code - -#### File Type Integration -- **Auto-detection**: `*/io/*.in` → `cpinput`, `*/io/*.out` → `cpoutput` -- **Buffer Settings**: Disables line numbers, signs, status column for I/O files -- **Syntax Highlighting**: Custom syntax for output metadata (`[code]`, `[time]`, etc.) - -### Current Configuration System - -#### Contest-Specific Settings -```lua -defaults = { - contests = { - atcoder = { cpp_version = 23 }, - codeforces = { cpp_version = 23 }, - cses = { cpp_version = 20 }, - }, - snippets = {}, -} -``` - -#### File-Based Configuration Dependencies -- `compile_flags.txt` - Compiler flags for release builds -- `debug_flags.txt` - Debug-specific compiler flags -- `.clang-format` - Code formatting configuration -- **Missing**: `.clangd` file (referenced in makefile but doesn't exist) - ---- - -## Proposed Modernization: Full Lua Migration - -### 1. Replace Make/Shell System with vim.system - -#### Benefits of Native Lua Implementation -- **Better Error Handling**: Lua error handling vs shell exit codes -- **Timeout Control**: `vim.system` supports timeout parameter directly -- **Streaming I/O**: Native stdin/stdout handling without temp files -- **Progress Reporting**: Real-time compilation/execution feedback -- **Cross-Platform**: Remove shell script dependencies - -#### Proposed Build System -```lua -local function compile_cpp(src_path, output_path, flags, timeout_ms) - local compile_cmd = { "g++", unpack(flags), src_path, "-o", output_path } - return vim.system(compile_cmd, { timeout = timeout_ms or 10000 }) -end - -local function execute_with_timeout(binary_path, input_data, timeout_ms) - return vim.system( - { binary_path }, - { - stdin = input_data, - timeout = timeout_ms or 2000, - stdout = true, - stderr = true, - } - ) -end -``` - -### 2. Configuration Migration to Pure Lua - -#### Replace File-Based Config with Lua Tables -```lua -config = { - contests = { - atcoder = { - cpp_version = 23, - compile_flags = { "-std=c++23", "-O2", "-DLOCAL", "-Wall", "-Wextra" }, - debug_flags = { "-std=c++23", "-g3", "-fsanitize=address,undefined", "-DLOCAL" }, - timeout_ms = 2000, - }, - -- ... other contests - }, - clangd_config = { - CompileFlags = { Add = { "-std=c++23", "-DLOCAL" } }, - Diagnostics = { ClangTidy = { Add = { "readability-*" } } }, - }, - clang_format = { - BasedOnStyle = "Google", - AllowShortFunctionsOnASingleLine = false, - -- ... other formatting options - } -} -``` - -#### Dynamic File Generation -- Generate `.clangd` YAML from Lua config -- Generate `.clang-format` from Lua config -- Eliminate static template files - -### 3. Enhanced Window Management (Remove vim-zoom Dependency) - -#### Current Diff Implementation Issues -- Manual session management with temp files -- No proper view state restoration -- Hardcoded window arrangements - -#### Proposed Native Window Management -```lua -local function save_window_state() - return { - layout = vim.fn.winrestcmd(), - views = vim.tbl_map(function(win) - return { - winid = win, - view = vim.fn.winsaveview(), - bufnr = vim.api.nvim_win_get_buf(win) - } - end, vim.api.nvim_list_wins()) - } -end - -local function restore_window_state(state) - vim.cmd(state.layout) - for _, view_state in ipairs(state.views) do - if vim.api.nvim_win_is_valid(view_state.winid) then - vim.api.nvim_win_call(view_state.winid, function() - vim.fn.winrestview(view_state.view) - end) - end - end -end -``` - -#### Improved Diff Mode -- Use `vim.diff()` API for programmatic diff generation -- Better window state management with `winsaveview`/`winrestview` -- Native zoom functionality without external dependency - -### 4. Enhanced I/O and Timeout Management - -#### Current Limitations -- Hardcoded 2-second timeout -- Shell-based timeout implementation -- Limited output processing - -#### Proposed Improvements -```lua -local function execute_solution(config) - local start_time = vim.loop.hrtime() - - local result = vim.system( - { config.binary_path }, - { - stdin = config.input_data, - timeout = config.timeout_ms, - stdout = true, - stderr = true, - } - ) - - local end_time = vim.loop.hrtime() - local execution_time = (end_time - start_time) / 1000000 -- Convert to ms - - return { - stdout = result.stdout, - stderr = result.stderr, - code = result.code, - time_ms = execution_time, - timed_out = result.code == 124, - } -end -``` - -### 5. Integrated Problem Scraping - -#### Current Python Integration -- Separate uv environment management -- Shell script orchestration for scraping -- External Python dependencies (requests, beautifulsoup4, cloudscraper) - -#### Proposed Native Integration Options - -**Option A: Keep Python, Improve Integration** -```lua -local function scrape_problem(contest, problem_id, problem_letter) - local scraper_path = get_scraper_path(contest) - local args = contest == "cses" and { problem_id } or { problem_id, problem_letter } - - return vim.system( - { "uv", "run", scraper_path, unpack(args) }, - { cwd = plugin_path, timeout = 30000 } - ) -end -``` - -**Option B: Native Lua HTTP (Future)** -- Wait for Neovim native HTTP client -- Eliminate Python dependency entirely -- Pure Lua HTML parsing (challenging) - ---- - -## Implementation Challenges & Considerations - -### Technical Feasibility - -#### ✅ **Definitely Possible** -- Replace makefile with vim.system calls -- Migrate configuration to pure Lua tables -- Implement native window state management -- Add configurable timeouts -- Remove vim-zoom dependency - -#### ⚠️ **Requires Careful Implementation** -- **Signal Handling**: Shell scripts handle SIGSEGV, SIGFPE mapping - need Lua equivalent -- **AddressSanitizer Integration**: LD_PRELOAD handling in debug mode -- **Cross-Platform**: Shell scripts provide some cross-platform abstraction - -#### 🤔 **Complex/Questionable** -- **Complete Python Elimination**: Would require native HTTP client + HTML parsing -- **Output Truncation Logic**: Currently truncates to 1000 lines efficiently in shell - -### Migration Strategy - -#### Phase 1: Core Build System -1. Replace `vim.system({ "make", ... })` with direct `vim.system({ "g++", ... })` -2. Migrate compile/debug flags from txt files to Lua config -3. Implement native timeout and execution management - -#### Phase 2: Window Management -1. Implement native window state saving/restoration -2. Remove vim-zoom dependency mention -3. Enhance diff mode with better view management - -#### Phase 3: Configuration Integration -1. Generate .clangd/.clang-format from Lua config -2. Consolidate all configuration in single config table -3. Add runtime configuration validation - -#### Phase 4: Enhanced Features -1. Configurable timeouts per contest -2. Better error reporting and progress feedback -3. Enhanced output processing and metadata - -### User Experience Impact - -#### Advantages -- **Simpler Dependencies**: No external shell scripts or makefile -- **Better Error Messages**: Native Lua error handling -- **Configurable Timeouts**: Per-contest timeout settings -- **Improved Performance**: Direct system calls vs shell interpretation -- **Better Integration**: Native Neovim APIs throughout - -#### Potential Concerns -- **Compatibility**: Users relying on current makefile system -- **Feature Parity**: Ensuring all current functionality is preserved -- **Debugging**: Shell scripts are easier to debug independently - ---- - -## Recommendation - -**Proceed with modernization** - The proposed changes align well with Neovim 0.9+ capabilities and would significantly improve the plugin's maintainability and user experience. The migration is technically feasible with the main complexity being in preserving exact feature parity during the transition. - -**Priority Order**: -1. Build system migration (highest impact, lowest risk) -2. Window management improvements (removes external dependency) -3. Configuration consolidation (improves user experience) -4. Enhanced I/O and timeout management (adds new capabilities) - -The plugin's current architecture is well-designed, making this modernization an enhancement rather than a rewrite. \ No newline at end of file From 2c23da2999bdd3937eb934c05d94e94dcc285943 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 12 Sep 2025 09:22:04 -0500 Subject: [PATCH 5/5] format --- lua/cp/init.lua | 3 +-- lua/cp/window.lua | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lua/cp/init.lua b/lua/cp/init.lua index f927ca5..0660712 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -16,7 +16,6 @@ if not vim.fn.has("nvim-0.10.0") then return M end - local function get_plugin_path() local plugin_path = debug.getinfo(1, "S").source:sub(2) return vim.fn.fnamemodify(plugin_path, ":h:h:h") @@ -218,7 +217,7 @@ local function diff_problem() local actual_output = result.stdout window.setup_diff_layout(actual_output, expected, input) - + vim.g.cp_diff_mode = true log("entered diff mode") end diff --git a/lua/cp/window.lua b/lua/cp/window.lua index 825d449..8c7ba11 100644 --- a/lua/cp/window.lua +++ b/lua/cp/window.lua @@ -21,7 +21,7 @@ function M.save_layout() } end end - + return { windows = windows, current_win = vim.api.nvim_get_current_win(), @@ -30,11 +30,13 @@ function M.save_layout() end function M.restore_layout(state) - if not state then return end - + if not state then + return + end + vim.cmd.diffoff() vim.cmd(state.layout) - + for win, win_state in pairs(state.windows) do if vim.api.nvim_win_is_valid(win) then vim.api.nvim_set_current_win(win) @@ -43,7 +45,7 @@ function M.restore_layout(state) end end end - + if vim.api.nvim_win_is_valid(state.current_win) then vim.api.nvim_set_current_win(state.current_win) end @@ -52,24 +54,24 @@ end function M.setup_diff_layout(actual_output, expected_output, input_file) vim.cmd.diffoff() vim.cmd.only() - + local output_lines = vim.split(actual_output, "\n") local output_buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, output_lines) vim.bo[output_buf].filetype = "cpoutput" - + vim.cmd.edit() vim.api.nvim_set_current_buf(output_buf) vim.cmd.diffthis() M.clearcol() - + vim.cmd.vsplit(expected_output) vim.cmd.diffthis() M.clearcol() - + vim.cmd(("botright split %s"):format(input_file)) M.clearcol() vim.cmd.wincmd("k") end -return M \ No newline at end of file +return M