This commit is contained in:
Harivansh Rathi 2026-02-22 17:14:29 -05:00
parent deee1c3835
commit ca5d1145dd
4 changed files with 303 additions and 6 deletions

View file

@ -1,6 +1,14 @@
vim.g.mapleader = ' ' vim.g.mapleader = ' '
vim.g.maplocalleader = ',' vim.g.maplocalleader = ','
local home = os.getenv('HOME') or ''
local local_bin = home .. '/.local/bin'
if not (os.getenv('PATH') or ''):find(local_bin, 1, true) then
local new_path = local_bin .. ':' .. (os.getenv('PATH') or '')
vim.env.PATH = new_path
vim.uv.os_setenv('PATH', new_path)
end
function _G.map(mode, lhs, rhs, opts) function _G.map(mode, lhs, rhs, opts)
vim.keymap.set(mode, lhs, rhs, vim.tbl_extend('keep', opts or {}, { silent = true })) vim.keymap.set(mode, lhs, rhs, vim.tbl_extend('keep', opts or {}, { silent = true }))
end end

View file

@ -1,16 +1,17 @@
{ {
"diffs.nvim": { "branch": "main", "commit": "b1abfe4f4a164ad776148ca36f852df4f1e4014e" }, "cp.nvim": { "branch": "main", "commit": "ff5ba39a592d079780819bb8226dcb35741349a4" },
"diffs.nvim": { "branch": "main", "commit": "dfebc68a1fc3e93dae5b6d49133243ec1886cb19" },
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
"fzf-lua": { "branch": "main", "commit": "b2c0603216adb92c6bba81053bc996d7ae95b77a" }, "fzf-lua": { "branch": "main", "commit": "9004cbb4c065a32b690e909c49903967b45301eb" },
"gitsigns.nvim": { "branch": "main", "commit": "9f3c6dd7868bcc116e9c1c1929ce063b978fa519" }, "gitsigns.nvim": { "branch": "main", "commit": "9f3c6dd7868bcc116e9c1c1929ce063b978fa519" },
"gruvbox.nvim": { "branch": "main", "commit": "561126520034a1dac2f78ab063db025d12555998" }, "gruvbox.nvim": { "branch": "main", "commit": "561126520034a1dac2f78ab063db025d12555998" },
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
"nonicons.nvim": { "branch": "main", "commit": "62549ecb9906e4216398c44af96719ca4cc670ef" }, "nonicons.nvim": { "branch": "main", "commit": "5426ec037f2a295ae687fa9d4def290fb044e3e8" },
"nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" }, "nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" },
"nvim-lspconfig": { "branch": "master", "commit": "44acfe887d4056f704ccc4f17513ed41c9e2b2e6" }, "nvim-lspconfig": { "branch": "master", "commit": "5a855bcfec7973767a1a472335684bbd71d2fa2b" },
"nvim-surround": { "branch": "main", "commit": "1098d7b3c34adcfa7feb3289ee434529abd4afd1" }, "nvim-surround": { "branch": "main", "commit": "1098d7b3c34adcfa7feb3289ee434529abd4afd1" },
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, "nvim-treesitter": { "branch": "main", "commit": "dc42c209f3820bdfaae0956f15de29689aa6b451" },
"nvim-treesitter-textobjects": { "branch": "main", "commit": "a0e182ae21fda68c59d1f36c9ed45600aef50311" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "a0e182ae21fda68c59d1f36c9ed45600aef50311" },
"nvim-ufo": { "branch": "main", "commit": "ab3eb124062422d276fae49e0dd63b3ad1062cfc" }, "nvim-ufo": { "branch": "main", "commit": "ab3eb124062422d276fae49e0dd63b3ad1062cfc" },
"nvim-web-devicons": { "branch": "master", "commit": "746ffbb17975ebd6c40142362eee1b0249969c5c" }, "nvim-web-devicons": { "branch": "master", "commit": "746ffbb17975ebd6c40142362eee1b0249969c5c" },

238
lua/cp/scraper.lua Normal file
View file

@ -0,0 +1,238 @@
local M = {}
local constants = require('cp.constants')
local logger = require('cp.log')
local utils = require('cp.utils')
local function syshandle(result)
if result.code ~= 0 then
local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error')
return { success = false, error = msg }
end
local ok, data = pcall(vim.json.decode, result.stdout)
if not ok then
local msg = 'Failed to parse scraper output: ' .. tostring(data)
logger.log(msg, vim.log.levels.ERROR)
return { success = false, error = msg }
end
return { success = true, data = data }
end
local function spawn_env_list(env_map)
local out = {}
for key, value in pairs(env_map) do
out[#out + 1] = tostring(key) .. '=' .. tostring(value)
end
table.sort(out)
return out
end
---@param platform string
---@param subcommand string
---@param args string[]
---@param opts { sync?: boolean, ndjson?: boolean, on_event?: fun(ev: table), on_exit?: fun(result: table) }
local function run_scraper(platform, subcommand, args, opts)
if not utils.setup_python_env() then
local msg = 'no Python environment available (install uv or nix)'
logger.log(msg, vim.log.levels.ERROR)
if opts and opts.on_exit then
opts.on_exit({ success = false, error = msg })
end
return { success = false, error = msg }
end
local plugin_path = utils.get_plugin_path()
local cmd = utils.get_python_cmd(platform, plugin_path)
vim.list_extend(cmd, { subcommand })
vim.list_extend(cmd, args)
logger.log('scraper cmd: ' .. table.concat(cmd, ' '))
local env = vim.fn.environ()
env.VIRTUAL_ENV = ''
env.PYTHONPATH = ''
env.CONDA_PREFIX = ''
if opts and opts.ndjson then
local uv = vim.loop
local stdout = uv.new_pipe(false)
local stderr = uv.new_pipe(false)
local buf = ''
local handle
handle = uv.spawn(cmd[1], {
args = vim.list_slice(cmd, 2),
stdio = { nil, stdout, stderr },
env = spawn_env_list(env),
cwd = plugin_path,
}, function(code, signal)
if buf ~= '' and opts.on_event then
local ok_tail, ev_tail = pcall(vim.json.decode, buf)
if ok_tail then
opts.on_event(ev_tail)
end
buf = ''
end
if opts.on_exit then
opts.on_exit({ success = (code == 0), code = code, signal = signal })
end
if not stdout:is_closing() then
stdout:close()
end
if not stderr:is_closing() then
stderr:close()
end
if handle and not handle:is_closing() then
handle:close()
end
end)
if not handle then
logger.log('Failed to start scraper process', vim.log.levels.ERROR)
return { success = false, error = 'spawn failed' }
end
uv.read_start(stdout, function(_, data)
if data == nil then
if buf ~= '' and opts.on_event then
local ok_tail, ev_tail = pcall(vim.json.decode, buf)
if ok_tail then
opts.on_event(ev_tail)
end
buf = ''
end
return
end
buf = buf .. data
while true do
local s, e = buf:find('\n', 1, true)
if not s then
break
end
local line = buf:sub(1, s - 1)
buf = buf:sub(e + 1)
local ok, ev = pcall(vim.json.decode, line)
if ok and opts.on_event then
opts.on_event(ev)
end
end
end)
uv.read_start(stderr, function(_, _) end)
return
end
local sysopts = { text = true, timeout = 30000, env = env, cwd = plugin_path }
if opts and opts.sync then
local result = vim.system(cmd, sysopts):wait()
return syshandle(result)
else
vim.system(cmd, sysopts, function(result)
if opts and opts.on_exit then
return opts.on_exit(syshandle(result))
end
end)
end
end
function M.scrape_contest_metadata(platform, contest_id, callback)
run_scraper(platform, 'metadata', { contest_id }, {
on_exit = function(result)
if not result or not result.success then
logger.log(
("Failed to scrape metadata for %s contest '%s'."):format(
constants.PLATFORM_DISPLAY_NAMES[platform],
contest_id
),
vim.log.levels.ERROR
)
return
end
local data = result.data or {}
if not data.problems or #data.problems == 0 then
logger.log(
("No problems returned for %s contest '%s'."):format(
constants.PLATFORM_DISPLAY_NAMES[platform],
contest_id
),
vim.log.levels.ERROR
)
return
end
if type(callback) == 'function' then
callback(data)
end
end,
})
end
function M.scrape_contest_list(platform)
local result = run_scraper(platform, 'contests', {}, { sync = true })
if not result or not result.success or not (result.data and result.data.contests) then
logger.log(
('Could not scrape contests list for platform %s: %s'):format(
platform,
(result and result.error) or 'unknown'
),
vim.log.levels.ERROR
)
return {}
end
return result.data.contests
end
---@param platform string
---@param contest_id string
---@param callback fun(data: table)|nil
function M.scrape_all_tests(platform, contest_id, callback)
run_scraper(platform, 'tests', { contest_id }, {
ndjson = true,
on_event = function(ev)
if ev.done then
return
end
if ev.error and ev.problem_id then
logger.log(
("Failed to load tests for problem '%s' in contest '%s': %s"):format(
ev.problem_id,
contest_id,
ev.error
),
vim.log.levels.WARN
)
return
end
if not ev.problem_id or not ev.tests then
return
end
vim.schedule(function()
require('cp.utils').ensure_dirs()
local config = require('cp.config')
local base_name = config.default_filename(contest_id, ev.problem_id)
for i, t in ipairs(ev.tests) do
local input_file = 'io/' .. base_name .. '.' .. i .. '.cpin'
local expected_file = 'io/' .. base_name .. '.' .. i .. '.cpout'
local input_content = t.input:gsub('\r', '')
local expected_content = t.expected:gsub('\r', '')
vim.fn.writefile(vim.split(input_content, '\n'), input_file)
vim.fn.writefile(vim.split(expected_content, '\n'), expected_file)
end
if type(callback) == 'function' then
callback({
combined = ev.combined,
tests = ev.tests,
timeout_ms = ev.timeout_ms or 0,
memory_mb = ev.memory_mb or 0,
interactive = ev.interactive or false,
multi_test = ev.multi_test or false,
problem_id = ev.problem_id,
})
end
end)
end,
})
end
return M

View file

@ -35,6 +35,56 @@ return {
map('n', 'zM', require('ufo').closeAllFolds) map('n', 'zM', require('ufo').closeAllFolds)
end, end,
}, },
{
'barrettruth/cp.nvim',
dependencies = { 'ibhagwan/fzf-lua' },
init = function()
-- Keep uv cache in-project so cp.nvim scraping works in restricted environments.
if vim.env.UV_CACHE_DIR == nil or vim.env.UV_CACHE_DIR == '' then
local uv_cache_dir = vim.fn.getcwd() .. '/.uv-cache'
vim.fn.mkdir(uv_cache_dir, 'p')
vim.env.UV_CACHE_DIR = uv_cache_dir
end
vim.g.cp = {
languages = {
python = {
extension = 'py',
commands = {
run = { 'python3', '{source}' },
debug = { 'python3', '{source}' },
},
},
},
platforms = {
codeforces = {
enabled_languages = { 'python' },
default_language = 'python',
},
atcoder = {
enabled_languages = { 'python' },
default_language = 'python',
},
cses = {
enabled_languages = { 'python' },
default_language = 'python',
},
},
ui = {
picker = 'fzf-lua',
},
}
end,
config = function()
map('n', '<leader>cr', '<cmd>CP run<cr>', { desc = 'CP run' })
map('n', '<leader>cp', '<cmd>CP panel<cr>', { desc = 'CP panel' })
map('n', '<leader>ce', '<cmd>CP edit<cr>', { desc = 'CP edit tests' })
map('n', '<leader>cn', '<cmd>CP next<cr>', { desc = 'CP next problem' })
map('n', '<leader>cN', '<cmd>CP prev<cr>', { desc = 'CP previous problem' })
map('n', '<leader>cc', '<cmd>CP pick<cr>', { desc = 'CP contest picker' })
map('n', '<leader>ci', '<cmd>CP interact<cr>', { desc = 'CP interact' })
end,
},
{ {
'supermaven-inc/supermaven-nvim', 'supermaven-inc/supermaven-nvim',
opts = { opts = {