Compare commits

...
Sign in to create a new pull request.

32 commits

Author SHA1 Message Date
dc635d5167 chore: add issue templates 2026-02-03 21:07:01 -05:00
Barrett Ruth
81ddd1ea87
Merge pull request #231 from barrettruth/fix/config
Some checks are pending
luarocks / ci (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
use `vim.g` for setup
2026-02-03 16:14:19 -05:00
Barrett Ruth
7444a99b22
Merge branch 'main' into fix/config 2026-02-03 16:13:35 -05:00
ec487aa489 feat: config update to viom.g 2026-02-03 16:12:47 -05:00
Barrett Ruth
c4af9bf604
Merge pull request #228 from barrettruth/fix/doc
via, not main
2026-02-03 01:51:38 -05:00
Barrett Ruth
a4437bc1c6
Merge branch 'main' into fix/doc 2026-02-03 01:50:46 -05:00
1a7e9517ba force 2026-02-03 01:50:22 -05:00
11b8365aac via, not main 2026-02-03 01:49:47 -05:00
Barrett Ruth
585ebf0daf
Merge pull request #227 from barrettruth/fix/doc
Some checks are pending
luarocks / ci (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
update installation method
2026-02-03 01:43:56 -05:00
08fb654d23 format yml too in pre-commit 2026-02-03 01:43:13 -05:00
01efc7c344 fix(ci): prettier format 2026-02-03 01:41:35 -05:00
f9f993db0c fix: pre-commit syntax error 2026-02-03 01:39:26 -05:00
f184a7874a feat: update docs 2026-02-03 01:38:13 -05:00
Barrett Ruth
89e3c0e21d
Merge pull request #226 from barrettruth/feat/dir-bug
misc bugfixes
2026-02-02 13:16:46 -05:00
Barrett Ruth
a9ce31a291
Merge branch 'main' into feat/dir-bug 2026-02-02 13:13:41 -05:00
c8f735617a misc bugfixes 2026-02-02 13:13:08 -05:00
Barrett Ruth
a14f543371
Merge pull request #225 from barrettruth/fix/rockspec
fix username docs
2026-02-01 17:13:00 -05:00
Barrett Ruth
56ec178cdd
Merge branch 'main' into fix/rockspec 2026-02-01 17:12:38 -05:00
5cd6f75419 fix username too 2026-02-01 17:11:51 -05:00
Barrett Ruth
99d907aa7a
Merge pull request #224 from barrettruth/fix/rockspec
fix rockspec url for new username
2026-02-01 17:02:22 -05:00
c06d819597 fix(ci): fix rockspec url 2026-02-01 17:01:29 -05:00
Barrett Ruth
682b267019
Merge pull request #223 from barrettruth/fix/ci
fix ci
2026-01-27 17:20:43 -06:00
Barrett Ruth
8a2871ec1b
Merge branch 'main' into fix/ci 2026-01-27 17:20:25 -06:00
de1295d361 fix ci
Some checks are pending
luarocks / ci (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
2026-01-27 18:19:49 -05:00
Barrett Ruth
32f449850b
Merge pull request #222 from barrettruth/fix/ci
feat: misc tests
2026-01-27 17:16:16 -06:00
6966e8e101 feat: misc tests 2026-01-27 18:14:54 -05:00
Barrett Ruth
a5e094d44a
Merge pull request #221 from barrettruth/fix/ci
fix(ci): only run on tag push
2026-01-27 17:12:10 -06:00
Barrett Ruth
5de6fb2fee
Merge branch 'main' into fix/ci 2026-01-27 17:10:47 -06:00
bd25f1db0b fix(ci): only run on tag push 2026-01-27 18:09:57 -05:00
Barrett Ruth
9daa4e4ec4
Merge pull request #220 from barrettruth/fix/ci
run luarocks build on successful ci
2026-01-27 17:06:12 -06:00
0b5c0f0c40 fix(ci): only run luarocks build on successful ci 2026-01-27 18:04:56 -05:00
Barrett Ruth
af559b0fa3
Merge pull request #219 from barrettruth/fix/misc
Some checks are pending
Release / Publish to LuaRocks (push) Waiting to run
improve config validation
2026-01-27 16:38:03 -06:00
17 changed files with 352 additions and 83 deletions

78
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View file

@ -0,0 +1,78 @@
name: Bug Report
description: Report a bug
title: 'bug: '
labels: [bug]
body:
- type: checkboxes
attributes:
label: Prerequisites
options:
- label:
I have searched [existing
issues](https://github.com/barrettruth/cp.nvim/issues)
required: true
- label: I have updated to the latest version
required: true
- type: textarea
attributes:
label: 'Neovim version'
description: 'Output of `nvim --version`'
render: text
validations:
required: true
- type: input
attributes:
label: 'Operating system'
placeholder: 'e.g. Arch Linux, macOS 15, Ubuntu 24.04'
validations:
required: true
- type: textarea
attributes:
label: Description
description: What happened? What did you expect?
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Minimal steps to trigger the bug
value: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: 'Health check'
description: 'Output of `:checkhealth cp`'
render: text
- type: textarea
attributes:
label: Minimal reproduction
description: |
Save the script below as `repro.lua`, edit if needed, and run:
```
nvim -u repro.lua
```
Confirm the bug reproduces with this config before submitting.
render: lua
value: |
vim.env.LAZY_STDPATH = '.repro'
load(vim.fn.system('curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua'))()
require('lazy.nvim').setup({
spec = {
{
'barrett-ruth/cp.nvim',
opts = {},
},
},
})
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yaml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions
url: https://github.com/barrettruth/cp.nvim/discussions
about: Ask questions and discuss ideas

View file

@ -0,0 +1,30 @@
name: Feature Request
description: Suggest a feature
title: 'feat: '
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Prerequisites
options:
- label:
I have searched [existing
issues](https://github.com/barrettruth/cp.nvim/issues)
required: true
- type: textarea
attributes:
label: Problem
description: What problem does this solve?
validations:
required: true
- type: textarea
attributes:
label: Proposed solution
validations:
required: true
- type: textarea
attributes:
label: Alternatives considered

112
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,112 @@
name: ci
on:
workflow_call:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
lua: ${{ steps.changes.outputs.lua }}
python: ${{ steps.changes.outputs.python }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
lua:
- 'lua/**'
- 'spec/**'
- 'plugin/**'
- 'after/**'
- 'ftdetect/**'
- '*.lua'
- '.luarc.json'
- 'stylua.toml'
- 'selene.toml'
python:
- 'scripts/**'
- 'scrapers/**'
- 'tests/**'
- 'pyproject.toml'
- 'uv.lock'
lua-format:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: 2.1.0
args: --check .
lua-lint:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: NTBBloodbath/selene-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --display-style quiet .
lua-typecheck:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: mrcjkb/lua-typecheck-action@v0
with:
checklevel: Warning
directories: lua
configpath: .luarc.json
python-format:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.python == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv tool install ruff
- run: ruff format --check .
python-lint:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.python == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv tool install ruff
- run: ruff check .
python-typecheck:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.python == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uvx ty check .
python-test:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.python == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- run: uv sync --dev
- run: uv run camoufox fetch
- run: uv run pytest tests/ -v

View file

@ -1,18 +1,21 @@
name: Release name: luarocks
on: on:
push: push:
tags: tags:
- '*' - 'v*'
workflow_dispatch:
jobs: jobs:
publish-luarocks: ci:
name: Publish to LuaRocks uses: ./.github/workflows/ci.yaml
publish:
needs: ci
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Publish to LuaRocks
uses: nvim-neorocks/luarocks-tag-release@v7 - uses: nvim-neorocks/luarocks-tag-release@v7
env: env:
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}

View file

@ -1,4 +1,4 @@
name: Code Quality name: quality
on: on:
pull_request: pull_request:

View file

@ -1,4 +1,4 @@
name: Tests name: tests
on: on:
pull_request: pull_request:

View file

@ -25,7 +25,7 @@ repos:
hooks: hooks:
- id: prettier - id: prettier
name: prettier name: prettier
files: \.(md|,toml,yaml,sh)$ files: \.(md|toml|ya?ml|sh)$
- repo: local - repo: local
hooks: hooks:

View file

@ -19,6 +19,15 @@ https://github.com/user-attachments/assets/e81d8dfb-578f-4a79-9989-210164fc0148
- **Language agnostic**: Works with any language - **Language agnostic**: Works with any language
- **Diff viewer**: Compare expected vs actual output with 3 diff modes - **Diff viewer**: Compare expected vs actual output with 3 diff modes
## Installation
Install using your package manager of choice or via
[luarocks](https://luarocks.org/modules/barrettruth/cp.nvim):
```
luarocks install cp.nvim
```
## Optional Dependencies ## Optional Dependencies
- [uv](https://docs.astral.sh/uv/) for problem scraping - [uv](https://docs.astral.sh/uv/) for problem scraping

View file

@ -2,7 +2,7 @@ rockspec_format = '3.0'
package = 'cp.nvim' package = 'cp.nvim'
version = 'scm-1' version = 'scm-1'
source = { url = 'git://github.com/barrett-ruth/cp.nvim' } source = { url = 'git://github.com/barrettruth/cp.nvim' }
build = { type = 'builtin' } build = { type = 'builtin' }
test_dependencies = { test_dependencies = {

View file

@ -205,71 +205,66 @@ Debug Builds ~
============================================================================== ==============================================================================
CONFIGURATION *cp-config* CONFIGURATION *cp-config*
Here's an example configuration with lazy.nvim: Configuration is done via `vim.g.cp_config`. Set this before using the plugin:
>lua >lua
{ vim.g.cp_config = {
'barrett-ruth/cp.nvim', languages = {
cmd = 'CP', cpp = {
build = 'uv sync', extension = 'cc',
opts = { commands = {
languages = { build = { 'g++', '-std=c++17', '{source}', '-o', '{binary}',
cpp = { '-fdiagnostics-color=always' },
extension = 'cc', run = { '{binary}' },
commands = { debug = { 'g++', '-std=c++17', '-fsanitize=address,undefined',
build = { 'g++', '-std=c++17', '{source}', '-o', '{binary}', '{source}', '-o', '{binary}' },
'-fdiagnostics-color=always' },
run = { '{binary}' },
debug = { 'g++', '-std=c++17', '-fsanitize=address,undefined',
'{source}', '-o', '{binary}' },
},
},
python = {
extension = 'py',
commands = {
run = { 'python', '{source}' },
debug = { 'python', '{source}' },
},
}, },
}, },
platforms = { python = {
cses = { extension = 'py',
enabled_languages = { 'cpp', 'python' }, commands = {
default_language = 'cpp', run = { 'python', '{source}' },
overrides = { debug = { 'python', '{source}' },
cpp = { extension = 'cpp', commands = { build = { ... } } }
},
},
atcoder = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
codeforces = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
}, },
}, },
open_url = true, },
debug = false, platforms = {
ui = { cses = {
ansi = true, enabled_languages = { 'cpp', 'python' },
run = { default_language = 'cpp',
width = 0.3, overrides = {
next_test_key = '<c-n>', -- or nil to disable cpp = { extension = 'cpp', commands = { build = { ... } } }
prev_test_key = '<c-p>', -- or nil to disable
}, },
panel = {
diff_modes = { 'side-by-side', 'git', 'vim' },
max_output_lines = 50,
},
diff = {
git = {
args = { 'diff', '--no-index', '--word-diff=plain',
'--word-diff-regex=.', '--no-prefix' },
},
},
picker = 'telescope',
}, },
} atcoder = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
codeforces = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
},
open_url = true,
debug = false,
ui = {
ansi = true,
run = {
width = 0.3,
next_test_key = '<c-n>', -- or nil to disable
prev_test_key = '<c-p>', -- or nil to disable
},
panel = {
diff_modes = { 'side-by-side', 'git', 'vim' },
max_output_lines = 50,
},
diff = {
git = {
args = { 'diff', '--no-index', '--word-diff=plain',
'--word-diff-regex=.', '--no-prefix' },
},
},
picker = 'telescope',
},
} }
< <
@ -279,7 +274,7 @@ the default; per-platform overrides can tweak 'extension' or 'commands'.
For example, to run CodeForces contests with Python by default: For example, to run CodeForces contests with Python by default:
>lua >lua
{ vim.g.cp_config = {
platforms = { platforms = {
codeforces = { codeforces = {
default_language = 'python', default_language = 'python',
@ -290,7 +285,7 @@ For example, to run CodeForces contests with Python by default:
Any language is supported provided the proper configuration. For example, to Any language is supported provided the proper configuration. For example, to
run CSES problems with Rust using the single schema: run CSES problems with Rust using the single schema:
>lua >lua
{ vim.g.cp_config = {
languages = { languages = {
rust = { rust = {
extension = 'rs', extension = 'rs',

View file

@ -11,25 +11,25 @@ if vim.fn.has('nvim-0.10.0') == 0 then
return {} return {}
end end
local user_config = {}
local config = nil
local initialized = false local initialized = false
local function ensure_initialized()
if initialized then
return
end
local user_config = vim.g.cp_config or {}
local config = config_module.setup(user_config)
config_module.set_current_config(config)
initialized = true
end
---@return nil ---@return nil
function M.handle_command(opts) function M.handle_command(opts)
ensure_initialized()
local commands = require('cp.commands') local commands = require('cp.commands')
commands.handle_command(opts) commands.handle_command(opts)
end end
function M.setup(opts)
opts = opts or {}
user_config = opts
config = config_module.setup(user_config)
config_module.set_current_config(config)
initialized = true
end
function M.is_initialized() function M.is_initialized()
return initialized return initialized
end end

View file

@ -194,6 +194,8 @@ function M.compile_problem(debug, on_complete)
return return
end end
require('cp.utils').ensure_dirs()
local binary = debug and state.get_debug_file() or state.get_binary_file() local binary = debug and state.get_debug_file() or state.get_binary_file()
local substitutions = { source = state.get_source_file(), binary = binary } local substitutions = { source = state.get_source_file(), binary = binary }

View file

@ -186,7 +186,7 @@ function M.scrape_all_tests(platform, contest_id, callback)
return return
end end
vim.schedule(function() vim.schedule(function()
vim.system({ 'mkdir', '-p', 'build', 'io' }):wait() require('cp.utils').ensure_dirs()
local config = require('cp.config') local config = require('cp.config')
local base_name = config.default_filename(contest_id, ev.problem_id) local base_name = config.default_filename(contest_id, ev.problem_id)
for i, t in ipairs(ev.tests) do for i, t in ipairs(ev.tests) do

View file

@ -13,6 +13,7 @@ local utils = require('cp.utils')
local current_diff_layout = nil local current_diff_layout = nil
local current_mode = nil local current_mode = nil
local io_view_running = false
function M.disable() function M.disable()
local active_panel = state.get_active_panel() local active_panel = state.get_active_panel()
@ -390,6 +391,8 @@ function M.ensure_io_view()
return return
end end
require('cp.utils').ensure_dirs()
local source_file = state.get_source_file() local source_file = state.get_source_file()
if source_file then if source_file then
local source_file_abs = vim.fn.fnamemodify(source_file, ':p') local source_file_abs = vim.fn.fnamemodify(source_file, ':p')
@ -622,6 +625,12 @@ local function render_io_view_results(io_state, test_indices, mode, combined_res
end end
function M.run_io_view(test_indices_arg, debug, mode) function M.run_io_view(test_indices_arg, debug, mode)
if io_view_running then
logger.log('Tests already running', vim.log.levels.WARN)
return
end
io_view_running = true
logger.log(('%s tests...'):format(debug and 'Debugging' or 'Running'), vim.log.levels.INFO, true) logger.log(('%s tests...'):format(debug and 'Debugging' or 'Running'), vim.log.levels.INFO, true)
mode = mode or 'combined' mode = mode or 'combined'
@ -633,6 +642,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.', 'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
vim.log.levels.ERROR vim.log.levels.ERROR
) )
io_view_running = false
return return
end end
@ -640,6 +650,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
local contest_data = cache.get_contest_data(platform, contest_id) local contest_data = cache.get_contest_data(platform, contest_id)
if not contest_data or not contest_data.index_map then if not contest_data or not contest_data.index_map then
logger.log('No test cases available.', vim.log.levels.ERROR) logger.log('No test cases available.', vim.log.levels.ERROR)
io_view_running = false
return return
end end
@ -656,11 +667,13 @@ function M.run_io_view(test_indices_arg, debug, mode)
local combined = cache.get_combined_test(platform, contest_id, problem_id) local combined = cache.get_combined_test(platform, contest_id, problem_id)
if not combined then if not combined then
logger.log('No combined test available', vim.log.levels.ERROR) logger.log('No combined test available', vim.log.levels.ERROR)
io_view_running = false
return return
end end
else else
if not run.load_test_cases() then if not run.load_test_cases() then
logger.log('No test cases available', vim.log.levels.ERROR) logger.log('No test cases available', vim.log.levels.ERROR)
io_view_running = false
return return
end end
end end
@ -681,6 +694,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
), ),
vim.log.levels.WARN vim.log.levels.WARN
) )
io_view_running = false
return return
end end
end end
@ -698,6 +712,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
local io_state = state.get_io_view_state() local io_state = state.get_io_view_state()
if not io_state then if not io_state then
io_view_running = false
return return
end end
@ -711,6 +726,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
execute.compile_problem(debug, function(compile_result) execute.compile_problem(debug, function(compile_result)
if not vim.api.nvim_buf_is_valid(io_state.output_buf) then if not vim.api.nvim_buf_is_valid(io_state.output_buf) then
io_view_running = false
return return
end end
@ -730,6 +746,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
local ns = vim.api.nvim_create_namespace('cp_io_view_compile_error') local ns = vim.api.nvim_create_namespace('cp_io_view_compile_error')
utils.update_buffer_content(io_state.output_buf, lines, highlights, ns) utils.update_buffer_content(io_state.output_buf, lines, highlights, ns)
io_view_running = false
return return
end end
@ -737,6 +754,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
local combined = cache.get_combined_test(platform, contest_id, problem_id) local combined = cache.get_combined_test(platform, contest_id, problem_id)
if not combined then if not combined then
logger.log('No combined test found', vim.log.levels.ERROR) logger.log('No combined test found', vim.log.levels.ERROR)
io_view_running = false
return return
end end
@ -745,18 +763,21 @@ function M.run_io_view(test_indices_arg, debug, mode)
run.run_combined_test(debug, function(result) run.run_combined_test(debug, function(result)
if not result then if not result then
logger.log('Failed to run combined test', vim.log.levels.ERROR) logger.log('Failed to run combined test', vim.log.levels.ERROR)
io_view_running = false
return return
end end
if vim.api.nvim_buf_is_valid(io_state.output_buf) then if vim.api.nvim_buf_is_valid(io_state.output_buf) then
render_io_view_results(io_state, test_indices, mode, result, combined.input) render_io_view_results(io_state, test_indices, mode, result, combined.input)
end end
io_view_running = false
end) end)
else else
run.run_all_test_cases(test_indices, debug, nil, function() run.run_all_test_cases(test_indices, debug, nil, function()
if vim.api.nvim_buf_is_valid(io_state.output_buf) then if vim.api.nvim_buf_is_valid(io_state.output_buf) then
render_io_view_results(io_state, test_indices, mode, nil, nil) render_io_view_results(io_state, test_indices, mode, nil, nil)
end end
io_view_running = false
end) end)
end end
end) end)
@ -859,6 +880,9 @@ function M.toggle_panel(panel_opts)
end end
local function refresh_panel() local function refresh_panel()
if state.get_active_panel() ~= 'run' then
return
end
if not test_buffers.tab_buf or not vim.api.nvim_buf_is_valid(test_buffers.tab_buf) then if not test_buffers.tab_buf or not vim.api.nvim_buf_is_valid(test_buffers.tab_buf) then
return return
end end
@ -884,6 +908,10 @@ function M.toggle_panel(panel_opts)
vim.cmd.normal({ 'zz', bang = true }) vim.cmd.normal({ 'zz', bang = true })
end) end)
end end
if test_windows.tab_win and vim.api.nvim_win_is_valid(test_windows.tab_win) then
vim.api.nvim_set_current_win(test_windows.tab_win)
end
end end
local function navigate_test_case(delta) local function navigate_test_case(delta)
@ -942,6 +970,9 @@ function M.toggle_panel(panel_opts)
local function finalize_panel() local function finalize_panel()
vim.schedule(function() vim.schedule(function()
if state.get_active_panel() ~= 'run' then
return
end
if config.ui.ansi then if config.ui.ansi then
require('cp.ui.ansi').setup_highlight_groups() require('cp.ui.ansi').setup_highlight_groups()
end end

View file

@ -262,4 +262,8 @@ function M.cwd_executables()
return out return out
end end
function M.ensure_dirs()
vim.system({ 'mkdir', '-p', 'build', 'io' }):wait()
end
return M return M

0
new Normal file
View file