commit
1becd25cc0
27 changed files with 16906 additions and 3484 deletions
|
|
@ -3,7 +3,7 @@ name: Release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- '*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
14
.luarc.json
14
.luarc.json
|
|
@ -1,16 +1,8 @@
|
||||||
{
|
{
|
||||||
"runtime.version": "Lua 5.1",
|
"runtime.version": "Lua 5.1",
|
||||||
"runtime.path": [
|
"runtime.path": ["lua/?.lua", "lua/?/init.lua"],
|
||||||
"lua/?.lua",
|
"diagnostics.globals": ["vim"],
|
||||||
"lua/?/init.lua"
|
"workspace.library": ["$VIMRUNTIME/lua", "${3rd}/luv/library"],
|
||||||
],
|
|
||||||
"diagnostics.globals": [
|
|
||||||
"vim"
|
|
||||||
],
|
|
||||||
"workspace.library": [
|
|
||||||
"$VIMRUNTIME/lua",
|
|
||||||
"${3rd}/luv/library"
|
|
||||||
],
|
|
||||||
"workspace.checkThirdParty": false,
|
"workspace.checkThirdParty": false,
|
||||||
"completion.callSnippet": "Replace"
|
"completion.callSnippet": "Replace"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
minimum_pre_commit_version: "3.5.0"
|
minimum_pre_commit_version: '3.5.0'
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/JohnnyMorganz/StyLua
|
- repo: https://github.com/JohnnyMorganz/StyLua
|
||||||
|
|
@ -17,7 +17,7 @@ repos:
|
||||||
files: \.py$
|
files: \.py$
|
||||||
- id: ruff
|
- id: ruff
|
||||||
name: ruff (lint imports)
|
name: ruff (lint imports)
|
||||||
args: ["--fix", "--select=I"]
|
args: ['--fix', '--select=I']
|
||||||
files: \.py$
|
files: \.py$
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
|
|
@ -26,7 +26,7 @@ repos:
|
||||||
name: mypy (type check)
|
name: mypy (type check)
|
||||||
entry: uv run mypy
|
entry: uv run mypy
|
||||||
language: system
|
language: system
|
||||||
args: ["."]
|
args: ['.']
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
|
@ -35,4 +35,3 @@ repos:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
name: prettier (format markdown)
|
name: prettier (format markdown)
|
||||||
files: \.md$
|
files: \.md$
|
||||||
|
|
||||||
|
|
|
||||||
8
.prettierignore
Normal file
8
.prettierignore
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.pytest_cache/
|
||||||
|
node_modules/
|
||||||
|
.venv/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
tests/fixtures/
|
||||||
23
README.md
23
README.md
|
|
@ -2,22 +2,28 @@
|
||||||
|
|
||||||
**The definitive competitive programming environment for Neovim**
|
**The definitive competitive programming environment for Neovim**
|
||||||
|
|
||||||
Scrape problems, run tests, and debug solutions across multiple platforms with zero configuration.
|
Scrape problems, run tests, and debug solutions across multiple platforms with
|
||||||
|
zero configuration.
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/50b19481-8e6d-47b4-bebc-15e16c61a9c9
|
https://github.com/user-attachments/assets/50b19481-8e6d-47b4-bebc-15e16c61a9c9
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Multi-platform support**: AtCoder, Codeforces, CSES with consistent interface
|
- **Multi-platform support**: AtCoder, Codeforces, CSES with consistent
|
||||||
|
interface
|
||||||
- **Automatic problem setup**: Scrape test cases and metadata in seconds
|
- **Automatic problem setup**: Scrape test cases and metadata in seconds
|
||||||
- **Rich test output**: 256 color ANSI support for compiler errors and program output
|
- **Dual view modes**: Lightweight I/O view for quick feedback, full panel for
|
||||||
|
detailed analysis
|
||||||
|
- **Rich test output**: 256 color ANSI support for compiler errors and program
|
||||||
|
output
|
||||||
- **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
|
||||||
|
|
||||||
## Optional Dependencies
|
## Optional Dependencies
|
||||||
|
|
||||||
- [uv](https://docs.astral.sh/uv/) for problem scraping
|
- [uv](https://docs.astral.sh/uv/) for problem scraping
|
||||||
- GNU [time](https://www.gnu.org/software/time/) and [timeout](https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html)
|
- GNU [time](https://www.gnu.org/software/time/) and
|
||||||
|
[timeout](https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|
@ -32,10 +38,11 @@ cp.nvim follows a simple principle: **solve locally, submit remotely**.
|
||||||
:CP codeforces 1848
|
:CP codeforces 1848
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Code and test** with instant feedback and rich diffs
|
3. **Code and test** with instant feedback
|
||||||
|
|
||||||
```
|
```
|
||||||
:CP run
|
:CP run " Quick verdict summary in splits
|
||||||
|
:CP panel " Detailed analysis with diffs
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Navigate between problems**
|
4. **Navigate between problems**
|
||||||
|
|
@ -54,7 +61,9 @@ cp.nvim follows a simple principle: **solve locally, submit remotely**.
|
||||||
:help cp.nvim
|
:help cp.nvim
|
||||||
```
|
```
|
||||||
|
|
||||||
See [my config](https://github.com/barrett-ruth/dots/blob/main/nvim/lua/plugins/cp.lua) for a relatively advanced setup.
|
See
|
||||||
|
[my config](https://github.com/barrett-ruth/dots/blob/main/nvim/lua/plugins/cp.lua)
|
||||||
|
for a relatively advanced setup.
|
||||||
|
|
||||||
## Similar Projects
|
## Similar Projects
|
||||||
|
|
||||||
|
|
|
||||||
134
doc/cp.nvim.txt
134
doc/cp.nvim.txt
|
|
@ -38,14 +38,23 @@ COMMANDS *cp-commands*
|
||||||
Example: >
|
Example: >
|
||||||
:CP atcoder abc324
|
:CP atcoder abc324
|
||||||
<
|
<
|
||||||
Action Commands ~
|
View Commands ~
|
||||||
:CP run Toggle run panel for individual test cases.
|
:CP run [n] Run tests in I/O view (see |cp-io-view|).
|
||||||
Shows per-test results with redesigned
|
Lightweight split showing test verdicts.
|
||||||
layout for efficient comparison.
|
Without [n]: runs all tests, shows verdict summary
|
||||||
|
With [n]: runs test n, shows detailed output
|
||||||
:CP debug
|
Examples: >
|
||||||
Same as above but with the debug mode configured
|
:CP run " All tests, verdict list
|
||||||
settings.
|
:CP run 3 " Test 3 detail
|
||||||
|
<
|
||||||
|
:CP panel [n] Open full-screen test panel (see |cp-panel|).
|
||||||
|
Aggregate table with diff modes for detailed analysis.
|
||||||
|
Optional [n] focuses on specific test.
|
||||||
|
Example: >
|
||||||
|
:CP panel " All tests with diffs
|
||||||
|
:CP panel 2 " Focus on test 2
|
||||||
|
<
|
||||||
|
:CP debug [n] Same as :CP panel but uses debug build configuration.
|
||||||
|
|
||||||
:CP pick Launch configured picker for interactive
|
:CP pick Launch configured picker for interactive
|
||||||
platform/contest selection.
|
platform/contest selection.
|
||||||
|
|
@ -145,8 +154,8 @@ Here's an example configuration with lazy.nvim:
|
||||||
open_url = true,
|
open_url = true,
|
||||||
debug = false,
|
debug = false,
|
||||||
ui = {
|
ui = {
|
||||||
panel = {
|
|
||||||
ansi = true,
|
ansi = true,
|
||||||
|
panel = {
|
||||||
diff_mode = 'vim',
|
diff_mode = 'vim',
|
||||||
max_output_lines = 50,
|
max_output_lines = 50,
|
||||||
},
|
},
|
||||||
|
|
@ -238,14 +247,14 @@ run CSES problems with Rust using the single schema:
|
||||||
|
|
||||||
*CpUI*
|
*CpUI*
|
||||||
Fields: ~
|
Fields: ~
|
||||||
|
{ansi} (boolean, default: true) Enable ANSI color parsing
|
||||||
|
and highlighting in both I/O view and panel.
|
||||||
{panel} (|PanelConfig|) Test panel behavior configuration.
|
{panel} (|PanelConfig|) Test panel behavior configuration.
|
||||||
{diff} (|DiffConfig|) Diff backend configuration.
|
{diff} (|DiffConfig|) Diff backend configuration.
|
||||||
{picker} (string|nil) 'telescope', 'fzf-lua', or nil.
|
{picker} (string|nil) 'telescope', 'fzf-lua', or nil.
|
||||||
|
|
||||||
*cp.PanelConfig*
|
*cp.PanelConfig*
|
||||||
Fields: ~
|
Fields: ~
|
||||||
{ansi} (boolean, default: true) Enable ANSI color parsing
|
|
||||||
and highlighting.
|
|
||||||
{diff_mode} (string, default: "none") Diff backend: "none",
|
{diff_mode} (string, default: "none") Diff backend: "none",
|
||||||
"vim", or "git".
|
"vim", or "git".
|
||||||
{max_output_lines} (number, default: 50) Maximum lines of test output.
|
{max_output_lines} (number, default: 50) Maximum lines of test output.
|
||||||
|
|
@ -272,16 +281,32 @@ run CSES problems with Rust using the single schema:
|
||||||
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.
|
||||||
function(state: cp.State)
|
function(state: cp.State)
|
||||||
|
{setup_io_input} (function, optional) Called when I/O input buffer created.
|
||||||
|
function(bufnr: integer, state: cp.State)
|
||||||
|
Default: helpers.clearcol (removes line numbers/columns)
|
||||||
|
{setup_io_output} (function, optional) Called when I/O output buffer created.
|
||||||
|
function(bufnr: integer, state: cp.State)
|
||||||
|
Default: helpers.clearcol (removes line numbers/columns)
|
||||||
|
|
||||||
Hook functions receive the cp.nvim state object (cp.State). See the state
|
Hook functions receive the cp.nvim state object (|cp.State|). See
|
||||||
module documentation (lua/cp/state.lua) for available methods and fields.
|
|lua/cp/state.lua| for available methods and fields.
|
||||||
|
|
||||||
Example usage in hook:
|
The I/O buffer hooks are called once when the buffers are first created
|
||||||
|
during problem setup. Use these to customize buffer appearance (e.g.,
|
||||||
|
remove line numbers, set custom options). Access helpers via:
|
||||||
|
>lua
|
||||||
|
local helpers = require('cp').helpers
|
||||||
|
<
|
||||||
|
Example usage:
|
||||||
>lua
|
>lua
|
||||||
hooks = {
|
hooks = {
|
||||||
setup_code = function(state)
|
setup_code = function(state)
|
||||||
print("Setting up " .. state.get_base_name())
|
print("Setting up " .. state.get_base_name())
|
||||||
print("Source file: " .. state.get_source_file())
|
print("Source file: " .. state.get_source_file())
|
||||||
|
end,
|
||||||
|
setup_io_input = function(bufnr, state)
|
||||||
|
-- Custom setup for input buffer
|
||||||
|
vim.api.nvim_set_option_value('number', false, { buf = bufnr })
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
<
|
<
|
||||||
|
|
@ -333,8 +358,9 @@ Example: Setting up and solving AtCoder contest ABC324
|
||||||
|
|
||||||
3. Code your solution, then test: >
|
3. Code your solution, then test: >
|
||||||
:CP run
|
:CP run
|
||||||
< Navigate with j/k, run specific tests with <enter>
|
< View test verdicts in I/O splits. For detailed analysis:
|
||||||
Exit test panel with q or :CP run when done
|
:CP panel
|
||||||
|
< Navigate tests with <c-n>/<c-p>, exit with q
|
||||||
|
|
||||||
4. Move to next problem: >
|
4. Move to next problem: >
|
||||||
:CP next
|
:CP next
|
||||||
|
|
@ -349,6 +375,44 @@ Example: Setting up and solving AtCoder contest ABC324
|
||||||
|
|
||||||
7. Submit solutions on AtCoder website
|
7. Submit solutions on AtCoder website
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
I/O VIEW *cp-io-view*
|
||||||
|
|
||||||
|
The I/O view provides the main view aggregate view into test input and
|
||||||
|
program output. Used time/memory per test case are appended to the output.
|
||||||
|
The |cp-panel| offers more fine-grained analysis into each test case.
|
||||||
|
|
||||||
|
Access the I/O view with :CP run [n]
|
||||||
|
|
||||||
|
Layout ~
|
||||||
|
|
||||||
|
The I/O view appears as 30% width splits on the right side: >
|
||||||
|
|
||||||
|
┌──────────────────────────┬──────────────────────────┐
|
||||||
|
│ │ Output │
|
||||||
|
│ │ Test 1: AC (42ms, 8MB) │
|
||||||
|
│ │ Test 2: AC (38ms, 8MB) │
|
||||||
|
│ Solution Code │ Test 3: WA (45ms, 8MB) │
|
||||||
|
│ │ Test 4: AC (51ms, 9MB) │
|
||||||
|
│ ├──────────────────────────┤
|
||||||
|
│ │ Input │
|
||||||
|
│ │ 5 3 │
|
||||||
|
│ │ 1 2 3 4 5 │
|
||||||
|
│ │ 2 1 │
|
||||||
|
│ │ 10 20 │
|
||||||
|
└──────────────────────────┴──────────────────────────┘
|
||||||
|
<
|
||||||
|
Usage ~
|
||||||
|
|
||||||
|
:CP run Run all tests
|
||||||
|
:CP run 3 Run test 3
|
||||||
|
|
||||||
|
Buffer Customization ~
|
||||||
|
|
||||||
|
Use the setup_io_input and setup_io_output hooks (see |cp.Hooks|) to customize
|
||||||
|
buffer appearance. By default, line numbers and columns are removed via
|
||||||
|
helpers.clearcol (see |cp-helpers|).
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
PICKER INTEGRATION *cp-picker*
|
PICKER INTEGRATION *cp-picker*
|
||||||
|
|
||||||
|
|
@ -367,11 +431,13 @@ PICKER KEYMAPS *cp-picker-keys*
|
||||||
Useful when contest lists are outdated or incomplete
|
Useful when contest lists are outdated or incomplete
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
PANEL *cp-run*
|
PANEL *cp-panel*
|
||||||
|
|
||||||
The panel provides individual test case debugging. Problem time/memory
|
The panel provides full-screen test analysis with diff modes for detailed
|
||||||
limit constraints are in columns Time/Mem respectively. Used time/memory are
|
debugging. Problem time/memory limit constraints are in columns Time/Mem
|
||||||
in columns Runtime/RSS respectively.
|
respectively. Used time/memory are in columns Runtime/RSS respectively.
|
||||||
|
|
||||||
|
Access with :CP panel or :CP debug (uses debug build configuration).
|
||||||
|
|
||||||
Interface ~
|
Interface ~
|
||||||
|
|
||||||
|
|
@ -502,12 +568,33 @@ Customize highlight groups after your colorscheme loads:
|
||||||
})
|
})
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
PANEL KEYMAPS *cp-test-keys*
|
HELPERS *cp-helpers*
|
||||||
|
|
||||||
|
The helpers module provides utility functions for buffer customization.
|
||||||
|
Access via:
|
||||||
|
>lua
|
||||||
|
local helpers = require('cp').helpers
|
||||||
|
<
|
||||||
|
Functions ~
|
||||||
|
|
||||||
|
helpers.clearcol({bufnr}) *helpers.clearcol*
|
||||||
|
Remove line numbers, columns, and signs from buffer.
|
||||||
|
Sets:
|
||||||
|
• number = false
|
||||||
|
• relativenumber = false
|
||||||
|
• signcolumn = 'no'
|
||||||
|
• statuscolumn = ''
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{bufnr} (integer) Buffer handle
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
PANEL KEYMAPS *cp-panel-keys*
|
||||||
|
|
||||||
<c-n> Navigate to next test case
|
<c-n> Navigate to next test case
|
||||||
<c-p> Navigate to previous test case
|
<c-p> Navigate to previous test case
|
||||||
t Cycle through diff modes: none → git → vim
|
t Cycle through diff modes: none → git → vim
|
||||||
q Exit run panel and restore layout
|
q Exit panel and restore layout
|
||||||
<c-q> Exit interactive terminal and restore layout
|
<c-q> Exit interactive terminal and restore layout
|
||||||
|
|
||||||
Diff Modes ~
|
Diff Modes ~
|
||||||
|
|
@ -537,8 +624,7 @@ cp.nvim creates the following file structure upon problem setup: >
|
||||||
{problem_id}.run " Compiled binary
|
{problem_id}.run " Compiled binary
|
||||||
io/
|
io/
|
||||||
{problem_id}.n.cpin " nth test input
|
{problem_id}.n.cpin " nth test input
|
||||||
{problem_id}.n.cpout " nth program output
|
{problem_id}.n.cpout " nth test expected output
|
||||||
{problem_id}.expected " Expected output
|
|
||||||
<
|
<
|
||||||
==============================================================================
|
==============================================================================
|
||||||
HEALTH CHECK *cp-health*
|
HEALTH CHECK *cp-health*
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ local actions = constants.ACTIONS
|
||||||
---@field platform? string
|
---@field platform? string
|
||||||
---@field problem_id? string
|
---@field problem_id? string
|
||||||
---@field interactor_cmd? string
|
---@field interactor_cmd? string
|
||||||
|
---@field test_index? integer
|
||||||
|
|
||||||
--- Turn raw args into normalized structure to later dispatch
|
--- Turn raw args into normalized structure to later dispatch
|
||||||
---@param args string[] The raw command-line mode args
|
---@param args string[] The raw command-line mode args
|
||||||
|
|
@ -52,6 +53,23 @@ local function parse_command(args)
|
||||||
else
|
else
|
||||||
return { type = 'action', action = 'interact' }
|
return { type = 'action', action = 'interact' }
|
||||||
end
|
end
|
||||||
|
elseif first == 'run' then
|
||||||
|
local test_arg = args[2]
|
||||||
|
if test_arg then
|
||||||
|
local test_index = tonumber(test_arg)
|
||||||
|
if not test_index then
|
||||||
|
return {
|
||||||
|
type = 'error',
|
||||||
|
message = ("Test index '%s' is not a number"):format(test_index),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if test_index < 1 or test_index ~= math.floor(test_index) then
|
||||||
|
return { type = 'error', message = ("'%s' is not a valid test index"):format(test_index) }
|
||||||
|
end
|
||||||
|
return { type = 'action', action = 'run', test_index = test_index }
|
||||||
|
else
|
||||||
|
return { type = 'action', action = 'run' }
|
||||||
|
end
|
||||||
else
|
else
|
||||||
return { type = 'action', action = first }
|
return { type = 'action', action = first }
|
||||||
end
|
end
|
||||||
|
|
@ -109,6 +127,8 @@ function M.handle_command(opts)
|
||||||
if cmd.action == 'interact' then
|
if cmd.action == 'interact' then
|
||||||
ui.toggle_interactive(cmd.interactor_cmd)
|
ui.toggle_interactive(cmd.interactor_cmd)
|
||||||
elseif cmd.action == 'run' then
|
elseif cmd.action == 'run' then
|
||||||
|
ui.run_io_view(cmd.test_index)
|
||||||
|
elseif cmd.action == 'panel' then
|
||||||
ui.toggle_panel()
|
ui.toggle_panel()
|
||||||
elseif cmd.action == 'debug' then
|
elseif cmd.action == 'debug' then
|
||||||
ui.toggle_panel({ debug = true })
|
ui.toggle_panel({ debug = true })
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
---@field overrides? table<string, CpPlatformOverrides>
|
---@field overrides? table<string, CpPlatformOverrides>
|
||||||
|
|
||||||
---@class PanelConfig
|
---@class PanelConfig
|
||||||
---@field ansi boolean
|
|
||||||
---@field diff_mode "none"|"vim"|"git"
|
---@field diff_mode "none"|"vim"|"git"
|
||||||
---@field max_output_lines integer
|
---@field max_output_lines integer
|
||||||
|
|
||||||
|
|
@ -32,8 +31,11 @@
|
||||||
---@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)
|
||||||
|
---@field setup_io_input? fun(bufnr: integer, state: cp.State)
|
||||||
|
---@field setup_io_output? fun(bufnr: integer, state: cp.State)
|
||||||
|
|
||||||
---@class CpUI
|
---@class CpUI
|
||||||
|
---@field ansi boolean
|
||||||
---@field panel PanelConfig
|
---@field panel PanelConfig
|
||||||
---@field diff DiffConfig
|
---@field diff DiffConfig
|
||||||
---@field picker string|nil
|
---@field picker string|nil
|
||||||
|
|
@ -54,6 +56,7 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local constants = require('cp.constants')
|
local constants = require('cp.constants')
|
||||||
|
local helpers = require('cp.helpers')
|
||||||
local utils = require('cp.utils')
|
local utils = require('cp.utils')
|
||||||
|
|
||||||
-- defaults per the new single schema
|
-- defaults per the new single schema
|
||||||
|
|
@ -101,12 +104,19 @@ M.defaults = {
|
||||||
default_language = 'cpp',
|
default_language = 'cpp',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hooks = { before_run = nil, before_debug = nil, setup_code = nil },
|
hooks = {
|
||||||
|
before_run = nil,
|
||||||
|
before_debug = nil,
|
||||||
|
setup_code = nil,
|
||||||
|
setup_io_input = helpers.clearcol,
|
||||||
|
setup_io_output = helpers.clearcol,
|
||||||
|
},
|
||||||
debug = false,
|
debug = false,
|
||||||
scrapers = constants.PLATFORMS,
|
scrapers = constants.PLATFORMS,
|
||||||
filename = nil,
|
filename = nil,
|
||||||
ui = {
|
ui = {
|
||||||
panel = { ansi = true, diff_mode = 'none', max_output_lines = 50 },
|
ansi = true,
|
||||||
|
panel = { diff_mode = 'none', max_output_lines = 50 },
|
||||||
diff = {
|
diff = {
|
||||||
git = {
|
git = {
|
||||||
args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' },
|
args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' },
|
||||||
|
|
@ -229,10 +239,12 @@ function M.setup(user_config)
|
||||||
before_run = { cfg.hooks.before_run, { 'function', 'nil' }, true },
|
before_run = { cfg.hooks.before_run, { 'function', 'nil' }, true },
|
||||||
before_debug = { cfg.hooks.before_debug, { 'function', 'nil' }, true },
|
before_debug = { cfg.hooks.before_debug, { 'function', 'nil' }, true },
|
||||||
setup_code = { cfg.hooks.setup_code, { 'function', 'nil' }, true },
|
setup_code = { cfg.hooks.setup_code, { 'function', 'nil' }, true },
|
||||||
|
setup_io_input = { cfg.hooks.setup_io_input, { 'function', 'nil' }, true },
|
||||||
|
setup_io_output = { cfg.hooks.setup_io_output, { 'function', 'nil' }, true },
|
||||||
})
|
})
|
||||||
|
|
||||||
vim.validate({
|
vim.validate({
|
||||||
ansi = { cfg.ui.panel.ansi, 'boolean' },
|
ansi = { cfg.ui.ansi, 'boolean' },
|
||||||
diff_mode = {
|
diff_mode = {
|
||||||
cfg.ui.panel.diff_mode,
|
cfg.ui.panel.diff_mode,
|
||||||
function(v)
|
function(v)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' }
|
M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' }
|
||||||
M.ACTIONS = { 'run', 'next', 'prev', 'pick', 'cache', 'interact' }
|
M.ACTIONS = { 'run', 'panel', 'debug', 'next', 'prev', 'pick', 'cache', 'interact' }
|
||||||
|
|
||||||
M.PLATFORM_DISPLAY_NAMES = {
|
M.PLATFORM_DISPLAY_NAMES = {
|
||||||
atcoder = 'AtCoder',
|
atcoder = 'AtCoder',
|
||||||
|
|
|
||||||
13
lua/cp/helpers.lua
Normal file
13
lua/cp/helpers.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
function M.clearcol(bufnr)
|
||||||
|
for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do
|
||||||
|
vim.wo[win].signcolumn = 'no'
|
||||||
|
vim.wo[win].statuscolumn = ''
|
||||||
|
vim.wo[win].number = false
|
||||||
|
vim.wo[win].relativenumber = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local config_module = require('cp.config')
|
local config_module = require('cp.config')
|
||||||
|
local helpers = require('cp.helpers')
|
||||||
local logger = require('cp.log')
|
local logger = require('cp.log')
|
||||||
|
|
||||||
|
M.helpers = helpers
|
||||||
|
|
||||||
if vim.fn.has('nvim-0.10.0') == 0 then
|
if vim.fn.has('nvim-0.10.0') == 0 then
|
||||||
logger.log('Requires nvim-0.10.0+', vim.log.levels.ERROR)
|
logger.log('Requires nvim-0.10.0+', vim.log.levels.ERROR)
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ local function run_single_test_case(test_case)
|
||||||
local out = r.stdout or ''
|
local out = r.stdout or ''
|
||||||
local highlights = {}
|
local highlights = {}
|
||||||
if out ~= '' then
|
if out ~= '' then
|
||||||
if config.ui.panel.ansi then
|
if config.ui.ansi then
|
||||||
local parsed = ansi.parse_ansi_text(out)
|
local parsed = ansi.parse_ansi_text(out)
|
||||||
out = table.concat(parsed.lines, '\n')
|
out = table.concat(parsed.lines, '\n')
|
||||||
highlights = parsed.highlights
|
highlights = parsed.highlights
|
||||||
|
|
@ -224,14 +224,22 @@ function M.run_test_case(index)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param indices? integer[]
|
||||||
---@return RanTestCase[]
|
---@return RanTestCase[]
|
||||||
function M.run_all_test_cases()
|
function M.run_all_test_cases(indices)
|
||||||
local results = {}
|
local to_run = indices
|
||||||
|
if not to_run then
|
||||||
|
to_run = {}
|
||||||
for i = 1, #panel_state.test_cases do
|
for i = 1, #panel_state.test_cases do
|
||||||
M.run_test_case(i)
|
to_run[i] = i
|
||||||
results[i] = panel_state.test_cases[i]
|
|
||||||
end
|
end
|
||||||
return results
|
end
|
||||||
|
|
||||||
|
for _, i in ipairs(to_run) do
|
||||||
|
M.run_test_case(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
return panel_state.test_cases
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return PanelState
|
---@return PanelState
|
||||||
|
|
@ -247,7 +255,7 @@ function M.handle_compilation_failure(output)
|
||||||
local txt
|
local txt
|
||||||
local hl = {}
|
local hl = {}
|
||||||
|
|
||||||
if config.ui.panel.ansi then
|
if config.ui.ansi then
|
||||||
local p = ansi.parse_ansi_text(output or '')
|
local p = ansi.parse_ansi_text(output or '')
|
||||||
txt = table.concat(p.lines, '\n')
|
txt = table.concat(p.lines, '\n')
|
||||||
hl = p.highlights
|
hl = p.highlights
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ local M = {}
|
||||||
local cache = require('cp.cache')
|
local cache = require('cp.cache')
|
||||||
local config_module = require('cp.config')
|
local config_module = require('cp.config')
|
||||||
local constants = require('cp.constants')
|
local constants = require('cp.constants')
|
||||||
|
local helpers = require('cp.helpers')
|
||||||
local logger = require('cp.log')
|
local logger = require('cp.log')
|
||||||
local scraper = require('cp.scraper')
|
local scraper = require('cp.scraper')
|
||||||
local state = require('cp.state')
|
local state = require('cp.state')
|
||||||
|
|
@ -59,6 +60,18 @@ local function start_tests(platform, contest_id, problems)
|
||||||
ev.memory_mb or 0,
|
ev.memory_mb or 0,
|
||||||
ev.interactive
|
ev.interactive
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local io_state = state.get_io_view_state()
|
||||||
|
if io_state then
|
||||||
|
local test_cases = cache.get_test_cases(platform, contest_id, state.get_problem_id())
|
||||||
|
local input_lines = {}
|
||||||
|
for _, tc in ipairs(test_cases) do
|
||||||
|
for _, line in ipairs(vim.split(tc.input, '\n')) do
|
||||||
|
table.insert(input_lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
require('cp.utils').update_buffer_content(io_state.input_buf, input_lines, nil, nil)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -152,6 +165,7 @@ end
|
||||||
function M.setup_problem(problem_id, language)
|
function M.setup_problem(problem_id, language)
|
||||||
local platform = state.get_platform()
|
local platform = state.get_platform()
|
||||||
if not platform then
|
if not platform then
|
||||||
|
logger.log('No platform/contest/problem configured.', vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -178,6 +192,9 @@ function M.setup_problem(problem_id, language)
|
||||||
if ok then
|
if ok then
|
||||||
vim.b[prov.bufnr].cp_setup_done = true
|
vim.b[prov.bufnr].cp_setup_done = true
|
||||||
end
|
end
|
||||||
|
elseif not vim.b[prov.bufnr].cp_setup_done then
|
||||||
|
helpers.clearcol(prov.bufnr)
|
||||||
|
vim.b[prov.bufnr].cp_setup_done = true
|
||||||
end
|
end
|
||||||
cache.set_file_state(
|
cache.set_file_state(
|
||||||
vim.fn.fnamemodify(source_file, ':p'),
|
vim.fn.fnamemodify(source_file, ':p'),
|
||||||
|
|
@ -186,6 +203,7 @@ function M.setup_problem(problem_id, language)
|
||||||
state.get_problem_id() or '',
|
state.get_problem_id() or '',
|
||||||
lang
|
lang
|
||||||
)
|
)
|
||||||
|
require('cp.ui.panel').ensure_io_view()
|
||||||
end
|
end
|
||||||
state.set_provisional(nil)
|
state.set_provisional(nil)
|
||||||
return
|
return
|
||||||
|
|
@ -201,6 +219,9 @@ function M.setup_problem(problem_id, language)
|
||||||
if ok then
|
if ok then
|
||||||
vim.b[bufnr].cp_setup_done = true
|
vim.b[bufnr].cp_setup_done = true
|
||||||
end
|
end
|
||||||
|
elseif not vim.b[bufnr].cp_setup_done then
|
||||||
|
helpers.clearcol(bufnr)
|
||||||
|
vim.b[bufnr].cp_setup_done = true
|
||||||
end
|
end
|
||||||
cache.set_file_state(
|
cache.set_file_state(
|
||||||
vim.fn.expand('%:p'),
|
vim.fn.expand('%:p'),
|
||||||
|
|
@ -209,6 +230,7 @@ function M.setup_problem(problem_id, language)
|
||||||
state.get_problem_id() or '',
|
state.get_problem_id() or '',
|
||||||
lang
|
lang
|
||||||
)
|
)
|
||||||
|
require('cp.ui.panel').ensure_io_view()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -247,7 +269,11 @@ function M.navigate_problem(direction)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local active_panel = state.get_active_panel()
|
||||||
|
if active_panel == 'run' then
|
||||||
require('cp.ui.panel').disable()
|
require('cp.ui.panel').disable()
|
||||||
|
end
|
||||||
|
|
||||||
M.setup_contest(platform, contest_id, problems[new_index].id)
|
M.setup_contest(platform, contest_id, problems[new_index].id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,15 @@
|
||||||
---@field platform string
|
---@field platform string
|
||||||
---@field contest_id string
|
---@field contest_id string
|
||||||
---@field language string
|
---@field language string
|
||||||
---@field requested_problem_id string|nil
|
---@field requested_problem_id string?
|
||||||
---@field token integer
|
---@field token integer
|
||||||
|
|
||||||
|
---@class cp.IoViewState
|
||||||
|
---@field output_buf integer
|
||||||
|
---@field input_buf integer
|
||||||
|
---@field output_win integer
|
||||||
|
---@field input_win integer
|
||||||
|
|
||||||
---@class cp.State
|
---@class cp.State
|
||||||
---@field get_platform fun(): string?
|
---@field get_platform fun(): string?
|
||||||
---@field set_platform fun(platform: string)
|
---@field set_platform fun(platform: string)
|
||||||
|
|
@ -21,8 +27,12 @@
|
||||||
---@field get_input_file fun(): string?
|
---@field get_input_file fun(): string?
|
||||||
---@field get_output_file fun(): string?
|
---@field get_output_file fun(): string?
|
||||||
---@field get_expected_file fun(): string?
|
---@field get_expected_file fun(): string?
|
||||||
---@field get_provisional fun(): cp.ProvisionalState|nil
|
---@field get_provisional fun(): cp.ProvisionalState?
|
||||||
---@field set_provisional fun(p: cp.ProvisionalState|nil)
|
---@field set_provisional fun(p: cp.ProvisionalState?)
|
||||||
|
---@field get_saved_session fun(): string?
|
||||||
|
---@field set_saved_session fun(path: string?)
|
||||||
|
---@field get_io_view_state fun(): cp.IoViewState?
|
||||||
|
---@field set_io_view_state fun(s: cp.IoViewState?)
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -36,9 +46,10 @@ local state = {
|
||||||
active_panel = nil,
|
active_panel = nil,
|
||||||
provisional = nil,
|
provisional = nil,
|
||||||
solution_win = nil,
|
solution_win = nil,
|
||||||
|
io_view_state = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_platform()
|
function M.get_platform()
|
||||||
return state.platform
|
return state.platform
|
||||||
end
|
end
|
||||||
|
|
@ -48,7 +59,7 @@ function M.set_platform(platform)
|
||||||
state.platform = platform
|
state.platform = platform
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_contest_id()
|
function M.get_contest_id()
|
||||||
return state.contest_id
|
return state.contest_id
|
||||||
end
|
end
|
||||||
|
|
@ -58,7 +69,7 @@ function M.set_contest_id(contest_id)
|
||||||
state.contest_id = contest_id
|
state.contest_id = contest_id
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_problem_id()
|
function M.get_problem_id()
|
||||||
return state.problem_id
|
return state.problem_id
|
||||||
end
|
end
|
||||||
|
|
@ -68,7 +79,7 @@ function M.set_problem_id(problem_id)
|
||||||
state.problem_id = problem_id
|
state.problem_id = problem_id
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_base_name()
|
function M.get_base_name()
|
||||||
local platform, contest_id, problem_id = M.get_platform(), M.get_contest_id(), M.get_problem_id()
|
local platform, contest_id, problem_id = M.get_platform(), M.get_contest_id(), M.get_problem_id()
|
||||||
if not platform or not contest_id or not problem_id then
|
if not platform or not contest_id or not problem_id then
|
||||||
|
|
@ -86,7 +97,7 @@ function M.get_base_name()
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param language? string
|
---@param language? string
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_source_file(language)
|
function M.get_source_file(language)
|
||||||
local base_name = M.get_base_name()
|
local base_name = M.get_base_name()
|
||||||
if not base_name or not M.get_platform() then
|
if not base_name or not M.get_platform() then
|
||||||
|
|
@ -110,46 +121,46 @@ function M.get_source_file(language)
|
||||||
return base_name .. '.' .. eff.extension
|
return base_name .. '.' .. eff.extension
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_binary_file()
|
function M.get_binary_file()
|
||||||
local base_name = M.get_base_name()
|
local base_name = M.get_base_name()
|
||||||
return base_name and ('build/%s.run'):format(base_name) or nil
|
return base_name and ('build/%s.run'):format(base_name) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_input_file()
|
function M.get_input_file()
|
||||||
local base_name = M.get_base_name()
|
local base_name = M.get_base_name()
|
||||||
return base_name and ('io/%s.cpin'):format(base_name) or nil
|
return base_name and ('io/%s.cpin'):format(base_name) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_output_file()
|
function M.get_output_file()
|
||||||
local base_name = M.get_base_name()
|
local base_name = M.get_base_name()
|
||||||
return base_name and ('io/%s.cpout'):format(base_name) or nil
|
return base_name and ('io/%s.cpout'):format(base_name) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_expected_file()
|
function M.get_expected_file()
|
||||||
local base_name = M.get_base_name()
|
local base_name = M.get_base_name()
|
||||||
return base_name and ('io/%s.expected'):format(base_name) or nil
|
return base_name and ('io/%s.expected'):format(base_name) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return string|nil
|
---@return string?
|
||||||
function M.get_active_panel()
|
function M.get_active_panel()
|
||||||
return state.active_panel
|
return state.active_panel
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param panel string|nil
|
---@param panel string?
|
||||||
function M.set_active_panel(panel)
|
function M.set_active_panel(panel)
|
||||||
state.active_panel = panel
|
state.active_panel = panel
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return cp.ProvisionalState|nil
|
---@return cp.ProvisionalState?
|
||||||
function M.get_provisional()
|
function M.get_provisional()
|
||||||
return state.provisional
|
return state.provisional
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param p cp.ProvisionalState|nil
|
---@param p cp.ProvisionalState?
|
||||||
function M.set_provisional(p)
|
function M.set_provisional(p)
|
||||||
state.provisional = p
|
state.provisional = p
|
||||||
end
|
end
|
||||||
|
|
@ -167,6 +178,38 @@ function M.set_solution_win(win)
|
||||||
state.solution_win = win
|
state.solution_win = win
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return cp.IoViewState?
|
||||||
|
function M.get_io_view_state()
|
||||||
|
if not state.io_view_state then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local s = state.io_view_state
|
||||||
|
if
|
||||||
|
vim.api.nvim_buf_is_valid(s.output_buf)
|
||||||
|
and vim.api.nvim_buf_is_valid(s.input_buf)
|
||||||
|
and vim.api.nvim_win_is_valid(s.output_win)
|
||||||
|
and vim.api.nvim_win_is_valid(s.input_win)
|
||||||
|
then
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param s cp.IoViewState?
|
||||||
|
function M.set_io_view_state(s)
|
||||||
|
state.io_view_state = s
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string?
|
||||||
|
function M.get_saved_session()
|
||||||
|
return state.saved_session
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string?
|
||||||
|
function M.set_saved_session(path)
|
||||||
|
state.saved_session = path
|
||||||
|
end
|
||||||
|
|
||||||
M._state = state
|
M._state = state
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,25 @@ function M.setup_highlight_groups()
|
||||||
vim.api.nvim_set_hl(0, 'CpAnsiBold', { bold = true })
|
vim.api.nvim_set_hl(0, 'CpAnsiBold', { bold = true })
|
||||||
vim.api.nvim_set_hl(0, 'CpAnsiItalic', { italic = true })
|
vim.api.nvim_set_hl(0, 'CpAnsiItalic', { italic = true })
|
||||||
vim.api.nvim_set_hl(0, 'CpAnsiBoldItalic', { bold = true, italic = true })
|
vim.api.nvim_set_hl(0, 'CpAnsiBoldItalic', { bold = true, italic = true })
|
||||||
|
|
||||||
|
for _, combo in ipairs(combinations) do
|
||||||
|
for color_name, _ in pairs(color_map) do
|
||||||
|
local parts = { 'CpAnsi' }
|
||||||
|
if combo.bold then
|
||||||
|
table.insert(parts, 'Bold')
|
||||||
|
end
|
||||||
|
if combo.italic then
|
||||||
|
table.insert(parts, 'Italic')
|
||||||
|
end
|
||||||
|
table.insert(parts, color_name)
|
||||||
|
local hl_name = table.concat(parts)
|
||||||
|
dyn_hl_cache[hl_name] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dyn_hl_cache['CpAnsiBold'] = true
|
||||||
|
dyn_hl_cache['CpAnsiItalic'] = true
|
||||||
|
dyn_hl_cache['CpAnsiBoldItalic'] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
local helpers = require('cp.helpers')
|
||||||
local utils = require('cp.utils')
|
local utils = require('cp.utils')
|
||||||
|
|
||||||
local function create_none_diff_layout(parent_win, expected_content, actual_content)
|
local function create_none_diff_layout(parent_win, expected_content, actual_content)
|
||||||
local expected_buf = utils.create_buffer_with_options()
|
local expected_buf = utils.create_buffer_with_options()
|
||||||
local actual_buf = utils.create_buffer_with_options()
|
local actual_buf = utils.create_buffer_with_options()
|
||||||
|
helpers.clearcol(expected_buf)
|
||||||
|
helpers.clearcol(actual_buf)
|
||||||
|
|
||||||
vim.api.nvim_set_current_win(parent_win)
|
vim.api.nvim_set_current_win(parent_win)
|
||||||
vim.cmd.split()
|
vim.cmd.split()
|
||||||
|
|
@ -42,6 +45,8 @@ end
|
||||||
local function create_vim_diff_layout(parent_win, expected_content, actual_content)
|
local function create_vim_diff_layout(parent_win, expected_content, actual_content)
|
||||||
local expected_buf = utils.create_buffer_with_options()
|
local expected_buf = utils.create_buffer_with_options()
|
||||||
local actual_buf = utils.create_buffer_with_options()
|
local actual_buf = utils.create_buffer_with_options()
|
||||||
|
helpers.clearcol(expected_buf)
|
||||||
|
helpers.clearcol(actual_buf)
|
||||||
|
|
||||||
vim.api.nvim_set_current_win(parent_win)
|
vim.api.nvim_set_current_win(parent_win)
|
||||||
vim.cmd.split()
|
vim.cmd.split()
|
||||||
|
|
@ -89,6 +94,7 @@ end
|
||||||
|
|
||||||
local function create_git_diff_layout(parent_win, expected_content, actual_content)
|
local function create_git_diff_layout(parent_win, expected_content, actual_content)
|
||||||
local diff_buf = utils.create_buffer_with_options()
|
local diff_buf = utils.create_buffer_with_options()
|
||||||
|
helpers.clearcol(diff_buf)
|
||||||
|
|
||||||
vim.api.nvim_set_current_win(parent_win)
|
vim.api.nvim_set_current_win(parent_win)
|
||||||
vim.cmd.split()
|
vim.cmd.split()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ local M = {}
|
||||||
---@class PanelOpts
|
---@class PanelOpts
|
||||||
---@field debug? boolean
|
---@field debug? boolean
|
||||||
|
|
||||||
|
local cache = require('cp.cache')
|
||||||
local config_module = require('cp.config')
|
local config_module = require('cp.config')
|
||||||
local constants = require('cp.constants')
|
local helpers = require('cp.helpers')
|
||||||
local layouts = require('cp.ui.layouts')
|
local layouts = require('cp.ui.layouts')
|
||||||
local logger = require('cp.log')
|
local logger = require('cp.log')
|
||||||
local state = require('cp.state')
|
local state = require('cp.state')
|
||||||
|
|
@ -52,38 +53,24 @@ function M.toggle_interactive(interactor_cmd)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local platform, contest_id = state.get_platform(), state.get_contest_id()
|
local platform, contest_id, problem_id =
|
||||||
if not platform then
|
state.get_platform(), state.get_contest_id(), state.get_problem_id()
|
||||||
logger.log('No platform configured.', vim.log.levels.ERROR)
|
if not platform or not contest_id or not problem_id then
|
||||||
return
|
|
||||||
end
|
|
||||||
if not contest_id then
|
|
||||||
logger.log(
|
logger.log(
|
||||||
("No contest %s configured for platform '%s'."):format(
|
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
|
||||||
contest_id,
|
|
||||||
constants.PLATFORM_DISPLAY_NAMES[platform]
|
|
||||||
),
|
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local problem_id = state.get_problem_id()
|
|
||||||
if not problem_id then
|
|
||||||
logger.log('No problem is active.', vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local cache = require('cp.cache')
|
|
||||||
cache.load()
|
cache.load()
|
||||||
local contest_data = cache.get_contest_data(platform, contest_id)
|
local contest_data = cache.get_contest_data(platform, contest_id)
|
||||||
if
|
if
|
||||||
not contest_data
|
not contest_data
|
||||||
or not contest_data.index_map
|
or not contest_data.index_map
|
||||||
or not contest_data.problems[contest_data.index_map[problem_id]]
|
or contest_data.problems[contest_data.index_map[problem_id]].interactive
|
||||||
or not contest_data.problems[contest_data.index_map[problem_id]].interactive
|
|
||||||
then
|
then
|
||||||
logger.log('This problem is not interactive. Use :CP run.', vim.log.levels.ERROR)
|
logger.log('This problem is interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -92,9 +79,10 @@ function M.toggle_interactive(interactor_cmd)
|
||||||
vim.cmd('silent only')
|
vim.cmd('silent only')
|
||||||
|
|
||||||
local execute = require('cp.runner.execute')
|
local execute = require('cp.runner.execute')
|
||||||
|
local run = require('cp.runner.run')
|
||||||
local compile_result = execute.compile_problem()
|
local compile_result = execute.compile_problem()
|
||||||
if not compile_result.success then
|
if not compile_result.success then
|
||||||
require('cp.runner.run').handle_compilation_failure(compile_result.output)
|
run.handle_compilation_failure(compile_result.output)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -204,6 +192,230 @@ function M.toggle_interactive(interactor_cmd)
|
||||||
state.set_active_panel('interactive')
|
state.set_active_panel('interactive')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.ensure_io_view()
|
||||||
|
local platform, contest_id, problem_id =
|
||||||
|
state.get_platform(), state.get_contest_id(), state.get_problem_id()
|
||||||
|
if not platform or not contest_id or not problem_id then
|
||||||
|
logger.log(
|
||||||
|
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cache.load()
|
||||||
|
local contest_data = cache.get_contest_data(platform, contest_id)
|
||||||
|
if
|
||||||
|
contest_data
|
||||||
|
and contest_data.index_map
|
||||||
|
and contest_data.problems[contest_data.index_map[problem_id]].interactive
|
||||||
|
then
|
||||||
|
logger.log('No platform configured.', vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local solution_win = state.get_solution_win()
|
||||||
|
local io_state = state.get_io_view_state()
|
||||||
|
local output_buf, input_buf, output_win, input_win
|
||||||
|
|
||||||
|
if io_state then
|
||||||
|
output_buf = io_state.output_buf
|
||||||
|
input_buf = io_state.input_buf
|
||||||
|
output_win = io_state.output_win
|
||||||
|
input_win = io_state.input_win
|
||||||
|
else
|
||||||
|
vim.api.nvim_set_current_win(solution_win)
|
||||||
|
|
||||||
|
vim.cmd.vsplit()
|
||||||
|
output_win = vim.api.nvim_get_current_win()
|
||||||
|
local width = math.floor(vim.o.columns * 0.3)
|
||||||
|
vim.api.nvim_win_set_width(output_win, width)
|
||||||
|
output_buf = utils.create_buffer_with_options()
|
||||||
|
vim.api.nvim_win_set_buf(output_win, output_buf)
|
||||||
|
|
||||||
|
vim.cmd.split()
|
||||||
|
input_win = vim.api.nvim_get_current_win()
|
||||||
|
input_buf = utils.create_buffer_with_options()
|
||||||
|
vim.api.nvim_win_set_buf(input_win, input_buf)
|
||||||
|
|
||||||
|
state.set_io_view_state({
|
||||||
|
output_buf = output_buf,
|
||||||
|
input_buf = input_buf,
|
||||||
|
output_win = output_win,
|
||||||
|
input_win = input_win,
|
||||||
|
})
|
||||||
|
|
||||||
|
local config = config_module.get_config()
|
||||||
|
if config.hooks and config.hooks.setup_io_output then
|
||||||
|
pcall(config.hooks.setup_io_output, output_buf, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
if config.hooks and config.hooks.setup_io_input then
|
||||||
|
pcall(config.hooks.setup_io_input, input_buf, state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.update_buffer_content(input_buf, {})
|
||||||
|
utils.update_buffer_content(output_buf, {})
|
||||||
|
|
||||||
|
local test_cases = cache.get_test_cases(platform, contest_id, problem_id)
|
||||||
|
if test_cases and #test_cases > 0 then
|
||||||
|
local input_lines = {}
|
||||||
|
for _, tc in ipairs(test_cases) do
|
||||||
|
for _, line in ipairs(vim.split(tc.input, '\n')) do
|
||||||
|
table.insert(input_lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.update_buffer_content(input_buf, input_lines, nil, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_win(solution_win)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_io_view(test_index)
|
||||||
|
local platform, contest_id, problem_id =
|
||||||
|
state.get_platform(), state.get_contest_id(), state.get_problem_id()
|
||||||
|
if not platform or not contest_id or not problem_id then
|
||||||
|
logger.log(
|
||||||
|
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
|
||||||
|
vim.log.levels.ERROR
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cache.load()
|
||||||
|
local contest_data = cache.get_contest_data(platform, contest_id)
|
||||||
|
if
|
||||||
|
not contest_data
|
||||||
|
or not contest_data.index_map
|
||||||
|
or contest_data.problems[contest_data.index_map[problem_id]].interactive
|
||||||
|
then
|
||||||
|
logger.log('This problem is interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.ensure_io_view()
|
||||||
|
|
||||||
|
local run = require('cp.runner.run')
|
||||||
|
if not run.load_test_cases() then
|
||||||
|
logger.log('No test cases available', vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local test_state = run.get_panel_state()
|
||||||
|
local test_indices = {}
|
||||||
|
|
||||||
|
if test_index then
|
||||||
|
if test_index < 1 or test_index > #test_state.test_cases then
|
||||||
|
logger.log(
|
||||||
|
string.format(
|
||||||
|
'Test %d does not exist (only %d tests available)',
|
||||||
|
test_index,
|
||||||
|
#test_state.test_cases
|
||||||
|
),
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
test_indices = { test_index }
|
||||||
|
else
|
||||||
|
for i = 1, #test_state.test_cases do
|
||||||
|
test_indices[i] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local io_state = state.get_io_view_state()
|
||||||
|
if not io_state then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local config = config_module.get_config()
|
||||||
|
|
||||||
|
if config.ui.ansi then
|
||||||
|
require('cp.ui.ansi').setup_highlight_groups()
|
||||||
|
end
|
||||||
|
|
||||||
|
local execute = require('cp.runner.execute')
|
||||||
|
local compile_result = execute.compile_problem()
|
||||||
|
if not compile_result.success then
|
||||||
|
local ansi = require('cp.ui.ansi')
|
||||||
|
local output = compile_result.output or ''
|
||||||
|
local lines, highlights
|
||||||
|
|
||||||
|
if config.ui.ansi then
|
||||||
|
local parsed = ansi.parse_ansi_text(output)
|
||||||
|
lines = parsed.lines
|
||||||
|
highlights = parsed.highlights
|
||||||
|
else
|
||||||
|
lines = vim.split(output:gsub('\027%[[%d;]*[a-zA-Z]', ''), '\n')
|
||||||
|
highlights = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace('cp_io_view_compile_error')
|
||||||
|
utils.update_buffer_content(io_state.output_buf, lines, highlights, ns)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
run.run_all_test_cases(test_indices)
|
||||||
|
|
||||||
|
local input_lines = {}
|
||||||
|
for _, idx in ipairs(test_indices) do
|
||||||
|
local tc = test_state.test_cases[idx]
|
||||||
|
for _, line in ipairs(vim.split(tc.input, '\n')) do
|
||||||
|
table.insert(input_lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
utils.update_buffer_content(io_state.input_buf, input_lines, nil, nil)
|
||||||
|
|
||||||
|
local run_render = require('cp.runner.run_render')
|
||||||
|
run_render.setup_highlights()
|
||||||
|
|
||||||
|
if #test_indices == 1 then
|
||||||
|
local idx = test_indices[1]
|
||||||
|
local tc = test_state.test_cases[idx]
|
||||||
|
local status = run_render.get_status_info(tc)
|
||||||
|
|
||||||
|
local output_lines = {}
|
||||||
|
if tc.actual then
|
||||||
|
for _, line in ipairs(vim.split(tc.actual, '\n', { plain = true, trimempty = false })) do
|
||||||
|
table.insert(output_lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(output_lines, '')
|
||||||
|
local time = tc.time_ms and string.format('%.2fms', tc.time_ms) or '—'
|
||||||
|
local code = tc.code and tostring(tc.code) or '—'
|
||||||
|
table.insert(output_lines, string.format('--- %s: %s | Exit: %s ---', status.text, time, code))
|
||||||
|
|
||||||
|
local highlights = tc.actual_highlights or {}
|
||||||
|
local ns = vim.api.nvim_create_namespace('cp_io_view_output')
|
||||||
|
utils.update_buffer_content(io_state.output_buf, output_lines, highlights, ns)
|
||||||
|
else
|
||||||
|
local verdict_lines = {}
|
||||||
|
local verdict_highlights = {}
|
||||||
|
for _, idx in ipairs(test_indices) do
|
||||||
|
local tc = test_state.test_cases[idx]
|
||||||
|
local status = run_render.get_status_info(tc)
|
||||||
|
local time = tc.time_ms and string.format('%.2f', tc.time_ms) or '—'
|
||||||
|
local mem = tc.rss_mb and string.format('%.0f', tc.rss_mb) or '—'
|
||||||
|
local line = string.format('Test %d: %s (%sms, %sMB)', idx, status.text, time, mem)
|
||||||
|
table.insert(verdict_lines, line)
|
||||||
|
local status_pos = line:find(status.text, 1, true)
|
||||||
|
if status_pos then
|
||||||
|
table.insert(verdict_highlights, {
|
||||||
|
line = #verdict_lines - 1,
|
||||||
|
col_start = status_pos - 1,
|
||||||
|
col_end = status_pos - 1 + #status.text,
|
||||||
|
highlight_group = status.highlight_group,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local verdict_ns = vim.api.nvim_create_namespace('cp_io_view_verdict')
|
||||||
|
utils.update_buffer_content(io_state.output_buf, verdict_lines, verdict_highlights, verdict_ns)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param panel_opts? PanelOpts
|
---@param panel_opts? PanelOpts
|
||||||
function M.toggle_panel(panel_opts)
|
function M.toggle_panel(panel_opts)
|
||||||
if state.get_active_panel() == 'run' then
|
if state.get_active_panel() == 'run' then
|
||||||
|
|
@ -212,12 +424,14 @@ function M.toggle_panel(panel_opts)
|
||||||
current_diff_layout = nil
|
current_diff_layout = nil
|
||||||
current_mode = nil
|
current_mode = nil
|
||||||
end
|
end
|
||||||
if state.saved_session then
|
local saved = state.get_saved_session()
|
||||||
vim.cmd(('source %s'):format(state.saved_session))
|
if saved then
|
||||||
vim.fn.delete(state.saved_session)
|
vim.cmd(('source %s'):format(saved))
|
||||||
state.saved_session = nil
|
vim.fn.delete(saved)
|
||||||
|
state.set_saved_session(nil)
|
||||||
end
|
end
|
||||||
state.set_active_panel(nil)
|
state.set_active_panel(nil)
|
||||||
|
M.ensure_io_view()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -228,26 +442,14 @@ function M.toggle_panel(panel_opts)
|
||||||
|
|
||||||
local platform, contest_id = state.get_platform(), state.get_contest_id()
|
local platform, contest_id = state.get_platform(), state.get_contest_id()
|
||||||
|
|
||||||
if not platform then
|
if not platform or not contest_id then
|
||||||
logger.log(
|
logger.log(
|
||||||
'No platform configured. Use :CP <platform> <contest> [...] first.',
|
'No platform/contest configured. Use :CP <platform> <contest> [...] first.',
|
||||||
vim.log.levels.ERROR
|
vim.log.levels.ERROR
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not contest_id then
|
|
||||||
logger.log(
|
|
||||||
("No contest '%s' configured for platform '%s'."):format(
|
|
||||||
contest_id,
|
|
||||||
constants.PLATFORM_DISPLAY_NAMES[platform]
|
|
||||||
),
|
|
||||||
vim.log.levels.ERROR
|
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local cache = require('cp.cache')
|
|
||||||
cache.load()
|
cache.load()
|
||||||
local contest_data = cache.get_contest_data(platform, contest_id)
|
local contest_data = cache.get_contest_data(platform, contest_id)
|
||||||
if
|
if
|
||||||
|
|
@ -260,6 +462,7 @@ function M.toggle_panel(panel_opts)
|
||||||
|
|
||||||
local config = config_module.get_config()
|
local config = config_module.get_config()
|
||||||
local run = require('cp.runner.run')
|
local run = require('cp.runner.run')
|
||||||
|
local run_render = require('cp.runner.run_render')
|
||||||
local input_file = state.get_input_file()
|
local input_file = state.get_input_file()
|
||||||
logger.log(('run panel: checking test cases for %s'):format(input_file or 'none'))
|
logger.log(('run panel: checking test cases for %s'):format(input_file or 'none'))
|
||||||
|
|
||||||
|
|
@ -268,11 +471,24 @@ function M.toggle_panel(panel_opts)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
state.saved_session = vim.fn.tempname()
|
local io_state = state.get_io_view_state()
|
||||||
vim.cmd(('mksession! %s'):format(state.saved_session))
|
if io_state then
|
||||||
|
if vim.api.nvim_win_is_valid(io_state.output_win) then
|
||||||
|
vim.api.nvim_win_close(io_state.output_win, true)
|
||||||
|
end
|
||||||
|
if vim.api.nvim_win_is_valid(io_state.input_win) then
|
||||||
|
vim.api.nvim_win_close(io_state.input_win, true)
|
||||||
|
end
|
||||||
|
state.set_io_view_state(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local session_file = vim.fn.tempname()
|
||||||
|
state.set_saved_session(session_file)
|
||||||
|
vim.cmd(('mksession! %s'):format(session_file))
|
||||||
vim.cmd('silent only')
|
vim.cmd('silent only')
|
||||||
|
|
||||||
local tab_buf = utils.create_buffer_with_options()
|
local tab_buf = utils.create_buffer_with_options()
|
||||||
|
helpers.clearcol(tab_buf)
|
||||||
local main_win = vim.api.nvim_get_current_win()
|
local main_win = vim.api.nvim_get_current_win()
|
||||||
vim.api.nvim_win_set_buf(main_win, tab_buf)
|
vim.api.nvim_win_set_buf(main_win, tab_buf)
|
||||||
vim.api.nvim_set_option_value('filetype', 'cp', { buf = tab_buf })
|
vim.api.nvim_set_option_value('filetype', 'cp', { buf = tab_buf })
|
||||||
|
|
@ -298,7 +514,6 @@ function M.toggle_panel(panel_opts)
|
||||||
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
|
||||||
local run_render = require('cp.runner.run_render')
|
|
||||||
run_render.setup_highlights()
|
run_render.setup_highlights()
|
||||||
local test_state = run.get_panel_state()
|
local test_state = run.get_panel_state()
|
||||||
local tab_lines, tab_highlights = run_render.render_test_list(test_state)
|
local tab_lines, tab_highlights = run_render.render_test_list(test_state)
|
||||||
|
|
@ -368,9 +583,8 @@ function M.toggle_panel(panel_opts)
|
||||||
refresh_panel()
|
refresh_panel()
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
if config.ui.panel.ansi then
|
if config.ui.ansi then
|
||||||
local ansi = require('cp.ui.ansi')
|
require('cp.ui.ansi').setup_highlight_groups()
|
||||||
ansi.setup_highlight_groups()
|
|
||||||
end
|
end
|
||||||
if current_diff_layout then
|
if current_diff_layout then
|
||||||
update_diff_panes()
|
update_diff_panes()
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,10 @@ function M.create_buffer_with_options(filetype)
|
||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
---@param lines string[]
|
||||||
|
---@param highlights? Highlight[]
|
||||||
|
---@param namespace? integer
|
||||||
function M.update_buffer_content(bufnr, lines, highlights, namespace)
|
function M.update_buffer_content(bufnr, lines, highlights, namespace)
|
||||||
local was_readonly = vim.api.nvim_get_option_value('readonly', { buf = bufnr })
|
local was_readonly = vim.api.nvim_get_option_value('readonly', { buf = bufnr })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,24 +72,24 @@ async def fetch_text(client: httpx.AsyncClient, path: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
CATEGORY_BLOCK_RE = re.compile(
|
CATEGORY_BLOCK_RE = re.compile(
|
||||||
r'<h2>(?P<cat>[^<]+)</h2>\s*<ul class="task-list">(?P<body>.*?)</ul>',
|
r'<h2>(?P<cat>[^<]+)</h2>\s*<ul\s+class="task-list">(?P<body>.*?)</ul>',
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
TASK_LINK_RE = re.compile(
|
TASK_LINK_RE = re.compile(
|
||||||
r'<li class="task"><a href="/problemset/task/(?P<id>\d+)/?">(?P<title>[^<]+)</a>',
|
r'<li\s+class="task">\s*<a\s+href="/problemset/task/(?P<id>\d+)/?">(?P<title>[^<]+)</a\s*>',
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
|
|
||||||
TITLE_RE = re.compile(
|
TITLE_RE = re.compile(
|
||||||
r'<div class="title-block">.*?<h1>(?P<title>[^<]+)</h1>', re.DOTALL
|
r'<div\s+class="title-block">.*?<h1>(?P<title>[^<]+)</h1>', re.DOTALL
|
||||||
)
|
)
|
||||||
TIME_RE = re.compile(r"<li><b>Time limit:</b>\s*([0-9.]+)\s*s</li>")
|
TIME_RE = re.compile(r"<li>\s*<b>Time limit:</b>\s*([0-9.]+)\s*s\s*</li>")
|
||||||
MEM_RE = re.compile(r"<li><b>Memory limit:</b>\s*(\d+)\s*MB</li>")
|
MEM_RE = re.compile(r"<li>\s*<b>Memory limit:</b>\s*(\d+)\s*MB\s*</li>")
|
||||||
SIDEBAR_CAT_RE = re.compile(
|
SIDEBAR_CAT_RE = re.compile(
|
||||||
r'<div class="nav sidebar">.*?<h4>(?P<cat>[^<]+)</h4>', re.DOTALL
|
r'<div\s+class="nav sidebar">.*?<h4>(?P<cat>[^<]+)</h4>', re.DOTALL
|
||||||
)
|
)
|
||||||
|
|
||||||
MD_BLOCK_RE = re.compile(r'<div class="md">(.*?)</div>', re.DOTALL | re.IGNORECASE)
|
MD_BLOCK_RE = re.compile(r'<div\s+class="md">(.*?)</div>', re.DOTALL | re.IGNORECASE)
|
||||||
EXAMPLE_SECTION_RE = re.compile(
|
EXAMPLE_SECTION_RE = re.compile(
|
||||||
r"<h[1-6][^>]*>\s*example[s]?:?\s*</h[1-6]>\s*(?P<section>.*?)(?=<h[1-6][^>]*>|$)",
|
r"<h[1-6][^>]*>\s*example[s]?:?\s*</h[1-6]>\s*(?P<section>.*?)(?=<h[1-6][^>]*>|$)",
|
||||||
re.DOTALL | re.IGNORECASE,
|
re.DOTALL | re.IGNORECASE,
|
||||||
|
|
|
||||||
815
tests/fixtures/atcoder_task_abc100_a.html
vendored
815
tests/fixtures/atcoder_task_abc100_a.html
vendored
File diff suppressed because it is too large
Load diff
819
tests/fixtures/atcoder_task_abc100_b.html
vendored
819
tests/fixtures/atcoder_task_abc100_b.html
vendored
File diff suppressed because it is too large
Load diff
812
tests/fixtures/atcoder_task_abc100_c.html
vendored
812
tests/fixtures/atcoder_task_abc100_c.html
vendored
File diff suppressed because it is too large
Load diff
936
tests/fixtures/atcoder_task_abc100_d.html
vendored
936
tests/fixtures/atcoder_task_abc100_d.html
vendored
File diff suppressed because it is too large
Load diff
10950
tests/fixtures/codeforces_1550_problems.html
vendored
10950
tests/fixtures/codeforces_1550_problems.html
vendored
File diff suppressed because one or more lines are too long
2124
tests/fixtures/cses_contests.html
vendored
2124
tests/fixtures/cses_contests.html
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue