feat(config): add language version selection with setup-time validation

Problem: users cannot choose which C++ standard (e.g. C++17 vs C++20
vs C++23) to submit with — the plugin hardcodes a single platform ID
per language with no discoverability or override mechanism.

Solution: add `LANGUAGE_VERSIONS` and `DEFAULT_VERSIONS` tables in
`constants.lua` as the single source of truth. `config.lua` gains
`version` and `submit_id` fields on `CpLanguage`/`CpPlatformOverrides`,
validated at setup time. `submit.lua` resolves the effective config to
a platform ID (priority: `submit_id` > `version` > default). Helpdocs
document the new `*cp-submit-language*` section with per-platform
available versions and the `submit_id` escape hatch. Tests cover
`LANGUAGE_IDS` completeness and `get_language_id` lookups.
This commit is contained in:
Barrett Ruth 2026-03-06 18:04:04 -05:00
parent 4e709c8470
commit b90ac67826
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
5 changed files with 153 additions and 11 deletions

View file

@ -24,15 +24,16 @@ CONTENTS *cp-contents*
16. Race .......................................................... |cp-race|
17. Credentials ............................................ |cp-credentials|
18. Submit ...................................................... |cp-submit|
19. Open ......................................................... |cp-open|
20. ANSI Colors ................................................... |cp-ansi|
21. Highlight Groups ........................................ |cp-highlights|
22. Terminal Colors .................................... |cp-terminal-colors|
23. Highlight Customization .......................... |cp-highlight-custom|
24. Helpers .................................................... |cp-helpers|
25. Statusline Integration .................................. |cp-statusline|
26. Panel Keymaps .......................................... |cp-panel-keys|
27. File Structure ................................................ |cp-files|
19. Submit Language Versions ......................... |cp-submit-language|
20. Open ......................................................... |cp-open|
21. ANSI Colors ................................................... |cp-ansi|
22. Highlight Groups ........................................ |cp-highlights|
23. Terminal Colors .................................... |cp-terminal-colors|
24. Highlight Customization .......................... |cp-highlight-custom|
25. Helpers .................................................... |cp-helpers|
26. Statusline Integration .................................. |cp-statusline|
27. Panel Keymaps .......................................... |cp-panel-keys|
28. File Structure ................................................ |cp-files|
28. Health Check ................................................ |cp-health|
==============================================================================
@ -1032,7 +1033,58 @@ Submit the current solution to the online judge.
Platform support:
AtCoder Fully implemented.
Others Not yet implemented.
Codeforces Fully implemented.
CSES Fully implemented.
Kattis Fully implemented.
USACO Fully implemented.
CodeChef Not yet implemented.
See |cp-submit-language| for configuring the language version
(e.g. C++20 instead of C++17).
==============================================================================
SUBMIT LANGUAGE VERSIONS *cp-submit-language*
When submitting, cp.nvim selects a language version for the platform.
The default is C++17 for cpp and Python 3 for python.
Configuring a version ~
Set {version} globally or per-platform:
>lua
languages = {
cpp = { version = "c++20", ... },
},
platforms = {
atcoder = {
overrides = { cpp = { version = "c++23" } },
},
},
<
Available versions per platform ~
Platform cpp python
AtCoder c++23 python3
Codeforces c++17 python3
CSES c++17 python3
Kattis c++17/20/23 python3
USACO c++17/20/23 python3
CodeChef c++17 python3
Using a raw platform ID ~
If your preferred version is not listed, you can bypass version
lookup by setting {submit_id} to the raw platform language ID:
>lua
platforms = {
codeforces = {
overrides = { cpp = { submit_id = "91" } },
},
},
<
To find the raw ID, open the platform's submit page in your browser,
inspect the language dropdown, and copy the <option value="..."> for
your desired language.
==============================================================================
OPEN *cp-open*

View file

@ -8,6 +8,8 @@
---@field extension string
---@field commands CpLangCommands
---@field template? string
---@field version? string
---@field submit_id? string
---@class CpTemplatesConfig
---@field cursor_marker? string
@ -16,6 +18,8 @@
---@field extension? string
---@field commands? CpLangCommands
---@field template? string
---@field version? string
---@field submit_id? string
---@class CpPlatform
---@field enabled_languages string[]
@ -293,6 +297,12 @@ local function merge_lang(base, ov)
if ov.template then
out.template = ov.template
end
if ov.version then
out.version = ov.version
end
if ov.submit_id then
out.submit_id = ov.submit_id
end
return out
end
@ -323,6 +333,23 @@ local function build_runtime(cfg)
validate_language(lid, base)
local eff = merge_lang(base, p.overrides and p.overrides[lid] or nil)
validate_language(lid, eff)
if eff.version then
local normalized = eff.version:lower():gsub('%s+', '')
local versions = (constants.LANGUAGE_VERSIONS[plat] or {})[lid]
if not versions or not versions[normalized] then
local avail = versions and vim.tbl_keys(versions) or {}
table.sort(avail)
error(
("[cp.nvim] Unknown version '%s' for %s on %s. Available: [%s]. See :help cp-submit-language"):format(
eff.version,
lid,
plat,
table.concat(avail, ', ')
)
)
end
eff.version = normalized
end
cfg.runtime.effective[plat][lid] = eff
end
end

View file

@ -74,4 +74,21 @@ M.signal_codes = {
[143] = 'SIGTERM',
}
M.LANGUAGE_VERSIONS = {
atcoder = { cpp = { ['c++23'] = '6017' }, python = { python3 = '6082' } },
codeforces = { cpp = { ['c++17'] = '89' }, python = { python3 = '70' } },
cses = { cpp = { ['c++17'] = 'C++17' }, python = { python3 = 'Python3' } },
kattis = {
cpp = { ['c++17'] = 'C++', ['c++20'] = 'C++', ['c++23'] = 'C++' },
python = { python3 = 'Python 3' },
},
usaco = {
cpp = { ['c++17'] = 'cpp', ['c++20'] = 'cpp', ['c++23'] = 'cpp' },
python = { python3 = 'python' },
},
codechef = { cpp = { ['c++17'] = 'C++ 17' }, python = { python3 = 'Python 3' } },
}
M.DEFAULT_VERSIONS = { cpp = 'c++17', python = 'python3' }
return M

View file

@ -1,6 +1,8 @@
local M = {}
local cache = require('cp.cache')
local config = require('cp.config')
local constants = require('cp.constants')
local logger = require('cp.log')
local state = require('cp.state')
@ -56,6 +58,24 @@ function M.submit(opts)
end
source_file = vim.fn.fnamemodify(source_file, ':p')
local submit_language = language
local cfg = config.get_config()
local plat_effective = cfg.runtime and cfg.runtime.effective and cfg.runtime.effective[platform]
local eff = plat_effective and plat_effective[language]
if eff then
if eff.submit_id then
submit_language = eff.submit_id
else
local ver = eff.version or constants.DEFAULT_VERSIONS[language]
if ver then
local versions = (constants.LANGUAGE_VERSIONS[platform] or {})[language]
if versions and versions[ver] then
submit_language = versions[ver]
end
end
end
end
prompt_credentials(platform, function(creds)
vim.cmd.update()
vim.notify('[cp.nvim] Submitting...', vim.log.levels.INFO)
@ -64,7 +84,7 @@ function M.submit(opts)
platform,
contest_id,
problem_id,
language,
submit_language,
source_file,
creds,
function(ev)

View file

@ -1,5 +1,6 @@
import pytest
from scrapers.language_ids import LANGUAGE_IDS, get_language_id
from scrapers.models import (
ContestListResult,
MetadataResult,
@ -137,3 +138,28 @@ def test_scraper_metadata_error(run_scraper_offline, scraper, contest_id):
assert objs
assert objs[-1].get("success") is False
assert objs[-1].get("error")
EXPECTED_PLATFORMS = {"atcoder", "codeforces", "cses", "usaco", "kattis", "codechef"}
EXPECTED_LANGUAGES = {"cpp", "python"}
def test_language_ids_coverage():
assert set(LANGUAGE_IDS.keys()) == EXPECTED_PLATFORMS
for platform, langs in LANGUAGE_IDS.items():
assert set(langs.keys()) == EXPECTED_LANGUAGES, f"{platform} missing languages"
for lang, lid in langs.items():
assert isinstance(lid, str) and lid, f"{platform}/{lang} empty ID"
@pytest.mark.parametrize("platform", EXPECTED_PLATFORMS)
@pytest.mark.parametrize("language", EXPECTED_LANGUAGES)
def test_get_language_id(platform, language):
result = get_language_id(platform, language)
assert result is not None, f"No ID for {platform}/{language}"
assert result == LANGUAGE_IDS[platform][language]
def test_get_language_id_unknown():
assert get_language_id("nonexistent", "cpp") is None
assert get_language_id("codeforces", "rust") is None