feat(doc): update for new config

This commit is contained in:
Barrett Ruth 2025-10-04 19:04:49 -04:00
parent 2c0e808c8c
commit a76d228e3f
5 changed files with 369 additions and 297 deletions

View file

@ -32,15 +32,13 @@ COMMANDS *cp-commands*
Automatically detects platform, contest, problem, Automatically detects platform, contest, problem,
and language from cached state. Use this after and language from cached state. Use this after
switching files to restore your CP environment. switching files to restore your CP environment.
Requires previous setup with full :CP command.
Setup Commands ~ Setup Commands ~
:CP {platform} {contest_id} {problem_id} :CP {platform} {contest_id}
Full setup: set platform, load contest metadata, Full setup: set platform and load contest metadata.
and set up specific problem. Scrapes test cases Scrapes test cases and creates source file.
and creates source file.
Example: > Example: >
:CP codeforces 1933 a :CP codeforces 1933
< <
:CP {platform} {contest_id} :CP {platform} {contest_id}
Contest setup: set platform, load contest metadata, Contest setup: set platform, load contest metadata,
@ -51,10 +49,6 @@ COMMANDS *cp-commands*
Example: > Example: >
:CP atcoder abc324 :CP atcoder abc324
:CP codeforces 1951 :CP codeforces 1951
<
:CP {platform} Platform setup: set platform only.
Example: >
:CP cses
< <
Action Commands ~ Action Commands ~
:CP run [--debug] Toggle run panel for individual test case :CP run [--debug] Toggle run panel for individual test case
@ -64,12 +58,13 @@ COMMANDS *cp-commands*
Requires contest setup first. Requires contest setup first.
:CP pick Launch configured picker for interactive :CP pick Launch configured picker for interactive
platform/contest/problem selection. platform/contest selection.
Navigation Commands ~ Navigation Commands ~
:CP next Navigate to next problem in current contest. :CP next Navigate to next problem in current contest.
Stops at last problem (no wrapping). Stops at last problem (no wrapping).
Navigation Commands ~ Navigation Commands ~
:CP prev Navigate to previous problem in current contest. :CP prev Navigate to previous problem in current contest.
Stops at first problem (no wrapping). Stops at first problem (no wrapping).
@ -78,7 +73,7 @@ COMMANDS *cp-commands*
:CP cache clear [contest] :CP cache clear [contest]
Clear the cache data (contest list, problem Clear the cache data (contest list, problem
data, file states) for the specified contest, data, file states) for the specified contest,
or all contests if none specified or all contests if none specified.
:CP cache read :CP cache read
View the cache in a pretty-printed lua buffer. View the cache in a pretty-printed lua buffer.
@ -88,13 +83,16 @@ Command Flags ~
*cp-flags* *cp-flags*
Flags can be used with setup and action commands: Flags can be used with setup and action commands:
--debug Enable debug compilation with additional flags. --debug Use the debug command template.
Uses the `debug` command template instead of For compiled languages, this selects
`compile`. Typically includes debug symbols and `commands.debug` (a debug *build*) instead of
sanitizers for memory error detection. `commands.build`. For interpreted languages,
this selects `commands.debug` in place of
`commands.run`.
Example: > Example: >
:CP run --debug :CP run --debug
< <
Template Variables ~ Template Variables ~
*cp-template-vars* *cp-template-vars*
Command templates support variable substitution using `{variable}` syntax: Command templates support variable substitution using `{variable}` syntax:
@ -105,7 +103,7 @@ Template Variables ~
• {problem} Problem identifier (e.g. "a", "b") • {problem} Problem identifier (e.g. "a", "b")
Example template: > Example template: >
compile = { 'g++', '{source}', '-o', '{binary}', '-std=c++17' } build = { 'g++', '{source}', '-o', '{binary}', '-std=c++17' }
< Would expand to: > < Would expand to: >
g++ abc324a.cpp -o build/abc324a.run -std=c++17 g++ abc324a.cpp -o build/abc324a.run -std=c++17
< <
@ -116,117 +114,161 @@ CONFIGURATION *cp-config*
Here's an example configuration with lazy.nvim: >lua Here's an example configuration with lazy.nvim: >lua
{ {
'barrett-ruth/cp.nvim', 'barrett-ruth/cp.nvim',
cmd = 'CP', cmd = 'CP',
build = 'uv sync', build = 'uv sync',
opts = { opts = {
platforms = { languages = {
cses = { cpp = {
cpp = { extension = 'cc',
compile = { 'g++', '{source}', '-o', '{binary}', commands = {
'-std=c++17', '-fdiagnostic-colors=always' }, build = { 'g++', '-std=c++17', '{source}', '-o', '{binary}' },
test = { '{binary}' }, run = { '{binary}' },
debug = { 'g++', '{source}', '-o', '{binary}', debug = { 'g++', '-std=c++17', '-fsanitize=address,undefined',
'-std=c++17', '-g', '{source}', '-o', '{binary}' },
'-fdiagnostic-colors=always'
'-fsanitize=address,undefined' },
},
python = {
test = { 'python3', '{source}' },
},
},
}, },
snippets = {}, },
debug = false, python = {
run_panel = { extension = 'py',
ansi = true, commands = {
diff_mode = 'vim', run = { 'python', '{source}' },
max_output_lines = 50, debug = { 'python', '{source}' },
}, },
diff = { },
git = { },
args = { 'diff', '--no-index', '--word-diff=plain',
'--word-diff-regex=.', '--no-prefix' }, platforms = {
}, cses = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
overrides = {
cpp = { extension = 'cpp', commands = { build = { ... } } }
}, },
picker = 'telescope', },
} atcoder = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
codeforces = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
},
snippets = {},
debug = false,
ui = {
run_panel = {
ansi = true,
diff_mode = 'vim',
max_output_lines = 50,
},
diff = {
git = {
args = { 'diff', '--no-index', '--word-diff=plain',
'--word-diff-regex=.', '--no-prefix' },
},
},
picker = 'telescope',
},
}
} }
< <
By default, all contests are configured to use C++ with the g++ compiler and ISO standard By default, C++ (g++ with ISO C++17) and Python are preconfigured under
17. Python is also configured with the system executable python. `languages`. Platforms select which languages are enabled and which one is
the default; per-platform overrides can tweak `extension` or `commands`.
For example, to run CodeForces contests with Python, only the following config For example, to run CodeForces contests with Python by default:
is required:
>lua
{ {
platforms = { platforms = {
codeforces = { codeforces = {
default_language = 'python' enabled_languages = { 'cpp', 'python' },
} default_language = 'python',
} },
},
} }
<
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: run CSES problems with Rust using the single schema:
>lua
{ {
platforms = { languages = {
codeforces = { rust = {
rust = { extension = 'rs',
compile = { 'rustc', '{source}', '-o', '{binary}' }, commands = {
test = { 'binary' }, build = { 'rustc', '{source}', '-o', '{binary}' },
extension = 'rs' run = { '{binary}' },
} },
} },
} },
platforms = {
cses = {
enabled_languages = { 'cpp', 'python', 'rust' },
default_language = 'rust',
},
},
} }
<
*cp.Config* *cp.Config*
Fields: ~ Fields: ~
{platforms} (table<string,PlatformContestConfig>) Contest configurations. {languages} (table<string,|CpLanguage|>) Global language registry.
Each language provides an {extension} and {commands}.
{platforms} (table<string,|CpPlatform|>) Per-platform enablement,
default language, and optional overrides.
{hooks} (|cp.Hooks|) Hook functions called at various stages. {hooks} (|cp.Hooks|) Hook functions called at various stages.
{snippets} (table[]) LuaSnip snippet definitions. {snippets} (table[]) LuaSnip snippet definitions.
{debug} (boolean, default: false) Show info messages {debug} (boolean, default: false) Show info messages.
during operation. {scrapers} (string[]) Supported platform ids.
{run_panel} (|RunPanelConfig|) Test panel behavior configuration. {filename} (function, optional)
{diff} (|DiffConfig|) Diff backend configuration. function(contest, contest_id, problem_id, config, language): string
{picker} (string, optional) Picker integration: "telescope",
"fzf-lua", or nil to disable. When enabled, provides
:CP pick for interactive platform/contest/problem selection.
{filename} (function, optional) Custom filename generation.
function(contest, contest_id, problem_id, config, language)
Should return full filename with extension. Should return full filename with extension.
(default: concatenates contest_id and problem_id, lowercased) (default: concatenates contest_id and problem_id, lowercased)
{ui} (|CpUI|) UI settings: run panel, diff backend, picker.
*cp.PlatformConfig* *cp.PlatformConfig*
Fields: ~ Replaced by |CpPlatform|. Platforms no longer inline language tables.
{cpp} (|PlatformLanguageConfig|) C++ language configuration.
{python} (|PlatformLanguageConfig|) Python language configuration.
{default_language} (string, default: "cpp") Default language for
platform contests.
*cp.LanguageConfig* *CpPlatform*
Fields: ~ Fields: ~
{compile} (string[], optional) Compile command template with {enabled_languages} (string[]) Language ids enabled on this platform.
{source}, {binary} placeholders. {default_language} (string) One of {enabled_languages}.
{test} (string[]) Test execution command template. {overrides} (table<string,|CpPlatformOverrides|>, optional)
{debug} (string[], optional) Debug compile command template. Per-language overrides of {extension} and/or {commands}.
{executable} (string, optional) Executable name for interpreted languages.
*CpLanguage*
Fields: ~
{extension} (string) File extension without leading dot.
{commands} (|CpLangCommands|) Command templates.
*CpLangCommands*
Fields: ~
{build} (string[], optional) For compiled languages.
Must include {source} and {binary}.
{run} (string[], optional) Runtime command.
Compiled: must include {binary}.
Interpreted: must include {source}.
{debug} (string[], optional) Debug variant; same token rules
as {build} (compiled) or {run} (interpreted).
*CpUI*
Fields: ~
{run_panel} (|RunPanelConfig|) Test panel behavior configuration.
{diff} (|DiffConfig|) Diff backend configuration.
{picker} (string|nil) 'telescope', 'fzf-lua', or nil.
*cp.RunPanelConfig* *cp.RunPanelConfig*
Fields: ~ Fields: ~
{ansi} (boolean, default: true) Enable ANSI color parsing and {ansi} (boolean, default: true) Enable ANSI color parsing
highlighting. When true, compiler output and test results and highlighting.
display with colored syntax highlighting. When false, {diff_mode} (string, default: "none") Diff backend: "none",
ANSI escape codes are stripped for plain text display. "vim", or "git".
Requires vim.g.terminal_color_* to be configured for
proper color display.
{diff_mode} (string, default: "none") Diff backend: "none", "vim", or "git".
"none" displays plain buffers without highlighting,
"vim" uses built-in diff, "git" provides character-level precision.
{toggle_diff_key} (string, default: "<c-t>") Key to cycle through diff modes.
{max_output_lines} (number, default: 50) Maximum lines of test output. {max_output_lines} (number, default: 50) Maximum lines of test output.
*cp.DiffConfig* *cp.DiffConfig*
@ -247,10 +289,9 @@ run CSES problems with Rust:
Fields: ~ Fields: ~
{before_run} (function, optional) Called before test panel opens. {before_run} (function, optional) Called before test panel opens.
function(state: cp.State) function(state: cp.State)
{before_debug} (function, optional) Called before debug compilation. {before_debug} (function, optional) Called before debug build/run.
function(state: cp.State) function(state: cp.State)
{setup_code} (function, optional) Called after source file is opened. {setup_code} (function, optional) Called after source file is opened.
Good for configuring buffer settings.
function(state: cp.State) function(state: cp.State)
Hook functions receive the cp.nvim state object (cp.State). See the state Hook functions receive the cp.nvim state object (cp.State). See the state
@ -280,41 +321,21 @@ AtCoder ~
URL format: https://atcoder.jp/contests/abc123/tasks/abc123_a URL format: https://atcoder.jp/contests/abc123/tasks/abc123_a
Usage examples: > Usage examples: >
:CP atcoder abc324 a " Full setup: problem A from contest ABC324
:CP atcoder abc324 " Contest setup: load contest metadata only :CP atcoder abc324 " Contest setup: load contest metadata only
:CP next " Navigate to next problem in contest
<
Note: AtCoder template includes optimizations
for multi-test case problems commonly found
in contests.
AtCoder Heuristic Contests (AHC) are excluded
from the contest list as they don't have
standard sample test cases.
Codeforces ~ Codeforces ~
*cp-codeforces* *cp-codeforces*
URL format: https://codeforces.com/contest/1234/problem/A URL format: https://codeforces.com/contest/1234/problem/A
Usage examples: > Usage examples: >
:CP codeforces 1934 a " Full setup: problem A from contest 1934
:CP codeforces 1934 " Contest setup: load contest metadata only :CP codeforces 1934 " Contest setup: load contest metadata only
:CP prev " Navigate to previous problem in contest
<
Note: Problem IDs are automatically converted
to lowercase for consistency.
CSES ~ CSES ~
*cp-cses* *cp-cses*
URL format: https://cses.fi/problemset/task/1068 URL format: https://cses.fi/problemset/task/1068
Usage examples: > Usage examples: >
:CP cses dynamic_programming 1633 " Set up problem 1633 from DP category
:CP cses dynamic_programming " Set up ALL problems from DP category :CP cses dynamic_programming " Set up ALL problems from DP category
<
Note: Category name is always required. For bulk
setup, omit the problem ID to scrape all problems
in the category.
============================================================================== ==============================================================================

View file

@ -222,8 +222,7 @@ end
---@param platform string ---@param platform string
---@param contest_id string ---@param contest_id string
---@param problem_id? string ---@param problem_id? string
---@param language? string function M.set_file_state(file_path, platform, contest_id, problem_id)
function M.set_file_state(file_path, platform, contest_id, problem_id, language)
if not cache_data.file_states then if not cache_data.file_states then
cache_data.file_states = {} cache_data.file_states = {}
end end
@ -232,7 +231,6 @@ function M.set_file_state(file_path, platform, contest_id, problem_id, language)
platform = platform, platform = platform,
contest_id = contest_id, contest_id = contest_id,
problem_id = problem_id, problem_id = problem_id,
language = language,
} }
M.save() M.save()

View file

@ -1,201 +1,279 @@
---@class PlatformLanguageConfig -- lua/cp/config.lua
---@field compile? string[] Compile command template ---@class CpLangCommands
---@field test string[] Test execution command template ---@field build? string[]
---@field debug? string[] Debug command template ---@field run? string[]
---@field executable? string Executable name ---@field debug? string[]
---@field extension? string File extension
---@class PlatformConfig ---@class CpLanguage
---@field [string] PlatformLanguageConfig ---@field extension string
---@field default_language? string ---@field commands CpLangCommands
---@class CpPlatformOverrides
---@field extension? string
---@field commands? CpLangCommands
---@class CpPlatform
---@field enabled_languages string[]
---@field default_language string
---@field overrides? table<string, CpPlatformOverrides>
---@class RunPanelConfig
---@field ansi boolean
---@field diff_mode "none"|"vim"|"git"
---@field max_output_lines integer
---@class DiffGitConfig
---@field args string[]
---@class DiffConfig
---@field git DiffGitConfig
---@class Hooks ---@class Hooks
---@field before_run? fun(state: cp.State) ---@field before_run? fun(state: cp.State)
---@field before_debug? fun(state: cp.State) ---@field before_debug? fun(state: cp.State)
---@field setup_code? fun(state: cp.State) ---@field setup_code? fun(state: cp.State)
---@class RunPanelConfig ---@class CpUI
---@field ansi boolean Enable ANSI color parsing and highlighting
---@field diff_mode "none"|"vim"|"git" Diff backend to use
---@field max_output_lines number Maximum lines of test output to display
---@class DiffGitConfig
---@field args string[] Git diff arguments
---@class DiffConfig
---@field git DiffGitConfig
---@class cp.Config
---@field platforms table<string, PlatformConfig>
---@field snippets any[]
---@field hooks Hooks
---@field debug boolean
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
---@field run_panel RunPanelConfig ---@field run_panel RunPanelConfig
---@field diff DiffConfig ---@field diff DiffConfig
---@field picker string|nil ---@field picker string|nil
---@class cp.PartialConfig ---@class cp.Config
---@field platforms? table<string, PlatformConfig> ---@field languages table<string, CpLanguage>
---@field snippets? any[] ---@field platforms table<string, CpPlatform>
---@field hooks? Hooks ---@field hooks Hooks
---@field debug? boolean ---@field snippets any[]
---@field debug boolean
---@field scrapers string[]
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
---@field run_panel? RunPanelConfig ---@field ui CpUI
---@field diff? DiffConfig ---@field runtime { effective: table<string, table<string, CpLanguage>> } -- computed
---@field picker? string|nil
---@class cp.PartialConfig: cp.Config
local M = {} local M = {}
local constants = require('cp.constants') local constants = require('cp.constants')
local utils = require('cp.utils') local utils = require('cp.utils')
local default_contest_config = { -- defaults per the new single schema
cpp = {
compile = { 'g++', '-std=c++17', '{source}', '-o', '{binary}' },
debug = { 'g++', '-std=c++17', '-fsanitize=address,undefined', '{source}', '-o', '{binary}' },
test = { '{binary}' },
},
python = {
test = { '{source}' },
debug = { '{source}' },
executable = 'python',
},
default_language = 'cpp',
}
---@type cp.Config ---@type cp.Config
M.defaults = { M.defaults = {
languages = {
cpp = {
extension = 'cc',
commands = {
build = { 'g++', '-std=c++17', '{source}', '-o', '{binary}' },
run = { '{binary}' },
debug = {
'g++',
'-std=c++17',
'-fsanitize=address,undefined',
'{source}',
'-o',
'{binary}',
},
},
},
python = {
extension = 'py',
commands = {
run = { 'python', '{source}' },
debug = { 'python', '{source}' },
},
},
},
platforms = { platforms = {
codeforces = default_contest_config, codeforces = {
atcoder = default_contest_config, enabled_languages = { 'cpp', 'python' },
cses = default_contest_config, default_language = 'cpp',
overrides = {
-- example override, safe to keep empty initially
},
},
atcoder = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
cses = {
enabled_languages = { 'cpp', 'python' },
default_language = 'cpp',
},
}, },
snippets = {}, snippets = {},
hooks = { hooks = { before_run = nil, before_debug = nil, setup_code = nil },
before_run = nil,
before_debug = nil,
setup_code = nil,
},
debug = false, debug = false,
scrapers = constants.PLATFORMS, scrapers = constants.PLATFORMS,
filename = nil, filename = nil,
run_panel = { ui = {
ansi = true, run_panel = { ansi = true, diff_mode = 'none', max_output_lines = 50 },
diff_mode = 'none', diff = {
max_output_lines = 50, git = {
}, args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' },
diff = { },
git = {
args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' },
}, },
picker = nil,
}, },
picker = nil, runtime = { effective = {} },
} }
local function is_string_list(t)
if type(t) ~= 'table' then
return false
end
for i, v in ipairs(t) do
if type(v) ~= 'string' then
return false
end
end
return true
end
local function has_tokens(cmd, required)
if type(cmd) ~= 'table' then
return false
end
local s = table.concat(cmd, ' ')
for _, tok in ipairs(required) do
if not s:find(vim.pesc(tok), 1, true) then
return false
end
end
return true
end
local function validate_language(id, lang)
vim.validate({
extension = { lang.extension, 'string' },
commands = { lang.commands, { 'table' } },
})
if lang.commands.build ~= nil then
vim.validate({ build = { lang.commands.build, { 'table' } } })
if not has_tokens(lang.commands.build, { '{source}', '{binary}' }) then
error(('[cp.nvim] languages.%s.commands.build must include {source} and {binary}'):format(id))
end
for _, k in ipairs({ 'run', 'debug' }) do
if lang.commands[k] then
if not has_tokens(lang.commands[k], { '{binary}' }) then
error(('[cp.nvim] languages.%s.commands.%s must include {binary}'):format(id, k))
end
end
end
else
for _, k in ipairs({ 'run', 'debug' }) do
if lang.commands[k] then
if not has_tokens(lang.commands[k], { '{source}' }) then
error(('[cp.nvim] languages.%s.commands.%s must include {source}'):format(id, k))
end
end
end
end
end
local function merge_lang(base, ov)
if not ov then
return base
end
local out = vim.deepcopy(base)
if ov.extension then
out.extension = ov.extension
end
if ov.commands then
out.commands = vim.tbl_deep_extend('force', out.commands or {}, ov.commands or {})
end
return out
end
---@param cfg cp.Config
local function build_runtime(cfg)
cfg.runtime = cfg.runtime or { effective = {} }
for plat, p in pairs(cfg.platforms) do
vim.validate({
enabled_languages = { p.enabled_languages, is_string_list, 'string[]' },
default_language = { p.default_language, 'string' },
})
for _, lid in ipairs(p.enabled_languages) do
if not cfg.languages[lid] then
error(("[cp.nvim] platform %s references unknown language '%s'"):format(plat, lid))
end
end
if not vim.tbl_contains(p.enabled_languages, p.default_language) then
error(
("[cp.nvim] platform %s default_language '%s' not in enabled_languages"):format(
plat,
p.default_language
)
)
end
cfg.runtime.effective[plat] = {}
for _, lid in ipairs(p.enabled_languages) do
local base = cfg.languages[lid]
validate_language(lid, base)
local eff = merge_lang(base, p.overrides and p.overrides[lid] or nil)
validate_language(lid, eff)
cfg.runtime.effective[plat][lid] = eff
end
end
end
---@param user_config cp.PartialConfig|nil ---@param user_config cp.PartialConfig|nil
---@return cp.Config ---@return cp.Config
function M.setup(user_config) function M.setup(user_config)
vim.validate({ vim.validate({ user_config = { user_config, { 'table', 'nil' }, true } })
user_config = { user_config, { 'table', 'nil' }, true }, local cfg = vim.tbl_deep_extend('force', vim.deepcopy(M.defaults), user_config or {})
})
if user_config then
vim.validate({
platforms = { user_config.platforms, { 'table', 'nil' }, true },
snippets = { user_config.snippets, { 'table', 'nil' }, true },
hooks = { user_config.hooks, { 'table', 'nil' }, true },
debug = { user_config.debug, { 'boolean', 'nil' }, true },
filename = { user_config.filename, { 'function', 'nil' }, true },
run_panel = { user_config.run_panel, { 'table', 'nil' }, true },
diff = { user_config.diff, { 'table', 'nil' }, true },
picker = { user_config.picker, { 'string', 'nil' }, true },
})
if user_config.platforms then
for platform_name, platform_config in pairs(user_config.platforms) do
vim.validate({
[platform_name] = {
platform_config,
function(config)
if type(config) ~= 'table' then
return false
end
return true
end,
'contest configuration',
},
})
end
end
if user_config.picker then
if not vim.tbl_contains({ 'telescope', 'fzf-lua' }, user_config.picker) then
error(("Invalid picker '%s'. Must be 'telescope' or 'fzf-lua'"):format(user_config.picker))
end
end
end
local config = vim.tbl_deep_extend('force', M.defaults, user_config or {})
vim.validate({ vim.validate({
before_run = { hooks = { cfg.hooks, { 'table' } },
config.hooks.before_run, ui = { cfg.ui, { 'table' } },
{ 'function', 'nil' },
true,
},
before_debug = {
config.hooks.before_debug,
{ 'function', 'nil' },
true,
},
setup_code = {
config.hooks.setup_code,
{ 'function', 'nil' },
true,
},
}) })
vim.validate({ vim.validate({
ansi = { before_run = { cfg.hooks.before_run, { 'function', 'nil' }, true },
config.run_panel.ansi, before_debug = { cfg.hooks.before_debug, { 'function', 'nil' }, true },
'boolean', setup_code = { cfg.hooks.setup_code, { 'function', 'nil' }, true },
'ansi color parsing must be enabled xor disabled', })
},
vim.validate({
ansi = { cfg.ui.run_panel.ansi, 'boolean' },
diff_mode = { diff_mode = {
config.run_panel.diff_mode, cfg.ui.run_panel.diff_mode,
function(value) function(v)
return vim.tbl_contains({ 'none', 'vim', 'git' }, value) return vim.tbl_contains({ 'none', 'vim', 'git' }, v)
end, end,
"diff_mode must be 'none', 'vim', or 'git'", "diff_mode must be 'none', 'vim', or 'git'",
}, },
max_output_lines = { max_output_lines = {
config.run_panel.max_output_lines, cfg.ui.run_panel.max_output_lines,
function(value) function(v)
return type(value) == 'number' and value > 0 and value == math.floor(value) return type(v) == 'number' and v > 0 and v == math.floor(v)
end, end,
'max_output_lines must be a positive integer', 'positive integer',
}, },
git = { cfg.ui.diff.git, { 'table' } },
}) })
vim.validate({ for id, lang in pairs(cfg.languages) do
git = { config.diff.git, { 'table', 'nil' }, true }, validate_language(id, lang)
})
for _, platform_config in pairs(config.platforms) do
if not platform_config.default_language then
-- arbitrarily choose a language
platform_config.default_language = vim.tbl_keys(platform_config)[1]
end
end end
build_runtime(cfg)
local ok, err = utils.check_required_runtime() local ok, err = utils.check_required_runtime()
if not ok then if not ok then
error('[cp.nvim] ' .. err) error('[cp.nvim] ' .. err)
end end
return config current_config = cfg
return cfg
end
local current_config = nil
function M.set_current_config(config)
current_config = config
end
function M.get_config()
return current_config or M.defaults
end end
---@param contest_id string ---@param contest_id string
@ -204,25 +282,9 @@ end
local function default_filename(contest_id, problem_id) local function default_filename(contest_id, problem_id)
if problem_id then if problem_id then
return (contest_id .. problem_id):lower() return (contest_id .. problem_id):lower()
else
return contest_id:lower()
end end
return contest_id:lower()
end end
M.default_filename = default_filename M.default_filename = default_filename
local current_config = nil
--- Set the config
---@return nil
function M.set_current_config(config)
current_config = config
end
--- Get the config
---@return cp.Config
function M.get_config()
return current_config or M.defaults
end
return M return M

View file

@ -242,7 +242,6 @@ end
---@return nil ---@return nil
function M.handle_compilation_failure(output) function M.handle_compilation_failure(output)
local ansi = require('cp.ui.ansi') local ansi = require('cp.ui.ansi')
local config = require('cp.config').setup()
local txt local txt
local hl = {} local hl = {}

View file

@ -1,3 +1,4 @@
-- lua/cp/setup.lua
local M = {} local M = {}
local cache = require('cp.cache') local cache = require('cp.cache')
@ -17,9 +18,7 @@ function M.set_platform(platform)
) )
return false return false
end end
state.set_platform(platform) state.set_platform(platform)
return true return true
end end
@ -112,15 +111,10 @@ function M.setup_problem(problem_id)
if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then
local has_luasnip, luasnip = pcall(require, 'luasnip') local has_luasnip, luasnip = pcall(require, 'luasnip')
if has_luasnip then if has_luasnip then
local filetype = vim.api.nvim_get_option_value('filetype', { buf = source_buf }) local prefixed_trigger = ('cp.nvim/%s.%s'):format(platform, language)
local language_name = constants.filetype_to_language[filetype]
local canonical_language = constants.canonical_filetypes[language_name] or language_name
local prefixed_trigger = ('cp.nvim/%s.%s'):format(platform, canonical_language)
vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger }) vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger })
vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger }) vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger })
vim.cmd.startinsert({ bang = true }) vim.cmd.startinsert({ bang = true })
vim.schedule(function() vim.schedule(function()
if luasnip.expandable() then if luasnip.expandable() then
luasnip.expand() luasnip.expand()
@ -141,8 +135,7 @@ function M.setup_problem(problem_id)
vim.fn.expand('%:p'), vim.fn.expand('%:p'),
platform, platform,
state.get_contest_id() or '', state.get_contest_id() or '',
state.get_problem_id(), state.get_problem_id()
language
) )
end) end)
end end
@ -177,7 +170,6 @@ function M.navigate_problem(direction)
local problems = contest_data.problems local problems = contest_data.problems
local index = contest_data.index_map[current_problem_id] local index = contest_data.index_map[current_problem_id]
local new_index = index + direction local new_index = index + direction
if new_index < 1 or new_index > #problems then if new_index < 1 or new_index > #problems then
return return