feat(scraper): add stdin pipe to NDJSON spawn; stream submit with on_status

Problem: The NDJSON spawn path had no stdin support, so `M.submit` used
one-shot `vim.system()` with no live feedback. Status events from the
scraper were never surfaced to Neovim.

Solution: Conditionally create a `stdin_pipe` in the NDJSON path and
write `opts.stdin` after spawn. Switch `M.submit` to `ndjson=true`; route
`ev.status` events to a new `on_status` callback and `ev.success` to the
existing `callback`. A `done` flag prevents double-callback on crash.
This commit is contained in:
Barrett Ruth 2026-03-04 19:01:06 -05:00
parent 6faf9c7537
commit ecbe60cbd8
Signed by: barrett
GPG key ID: A6C96C9349D2FC81

View file

@ -73,6 +73,10 @@ local function run_scraper(platform, subcommand, args, opts)
if opts and opts.ndjson then if opts and opts.ndjson then
local uv = vim.uv local uv = vim.uv
local stdin_pipe = nil
if opts.stdin then
stdin_pipe = uv.new_pipe(false)
end
local stdout = uv.new_pipe(false) local stdout = uv.new_pipe(false)
local stderr = uv.new_pipe(false) local stderr = uv.new_pipe(false)
local buf = '' local buf = ''
@ -80,7 +84,7 @@ local function run_scraper(platform, subcommand, args, opts)
local handle local handle
handle = uv.spawn(cmd[1], { handle = uv.spawn(cmd[1], {
args = vim.list_slice(cmd, 2), args = vim.list_slice(cmd, 2),
stdio = { nil, stdout, stderr }, stdio = { stdin_pipe, stdout, stderr },
env = spawn_env_list(env), env = spawn_env_list(env),
cwd = plugin_path, cwd = plugin_path,
}, function(code, signal) }, function(code, signal)
@ -94,6 +98,9 @@ local function run_scraper(platform, subcommand, args, opts)
if opts.on_exit then if opts.on_exit then
opts.on_exit({ success = (code == 0), code = code, signal = signal }) opts.on_exit({ success = (code == 0), code = code, signal = signal })
end end
if stdin_pipe and not stdin_pipe:is_closing() then
stdin_pipe:close()
end
if not stdout:is_closing() then if not stdout:is_closing() then
stdout:close() stdout:close()
end end
@ -106,10 +113,21 @@ local function run_scraper(platform, subcommand, args, opts)
end) end)
if not handle then if not handle then
if stdin_pipe and not stdin_pipe:is_closing() then
stdin_pipe:close()
end
logger.log('Failed to start scraper process', vim.log.levels.ERROR) logger.log('Failed to start scraper process', vim.log.levels.ERROR)
return { success = false, error = 'spawn failed' } return { success = false, error = 'spawn failed' }
end end
if stdin_pipe then
uv.write(stdin_pipe, opts.stdin, function()
uv.shutdown(stdin_pipe, function()
stdin_pipe:close()
end)
end)
end
uv.read_start(stdout, function(_, data) uv.read_start(stdout, function(_, data)
if data == nil then if data == nil then
if buf ~= '' and opts.on_event then if buf ~= '' and opts.on_event then
@ -260,18 +278,23 @@ function M.scrape_all_tests(platform, contest_id, callback)
}) })
end end
function M.submit(platform, contest_id, problem_id, language, source_code, credentials, callback) function M.submit(platform, contest_id, problem_id, language, source_code, credentials, on_status, callback)
local creds_json = vim.json.encode(credentials) local done = false
run_scraper(platform, 'submit', { contest_id, problem_id, language }, { run_scraper(platform, 'submit', { contest_id, problem_id, language }, {
ndjson = true,
stdin = source_code, stdin = source_code,
env_extra = { CP_CREDENTIALS = creds_json }, env_extra = { CP_CREDENTIALS = vim.json.encode(credentials) },
on_exit = function(result) on_event = function(ev)
if type(callback) == 'function' then if ev.status ~= nil then
if result and result.success then if type(on_status) == 'function' then on_status(ev.status) end
callback(result.data or { success = true }) elseif ev.success ~= nil then
else done = true
callback({ success = false, error = result and result.error or 'unknown' }) if type(callback) == 'function' then callback(ev) end
end end
end,
on_exit = function(proc)
if not done and type(callback) == 'function' then
callback({ success = false, error = 'submit process exited (code=' .. tostring(proc.code) .. ')' })
end end
end, end,
}) })