feat: interactive terminal

This commit is contained in:
Barrett Ruth 2025-09-26 08:28:19 -04:00
parent 6b4dd32683
commit 7efd6404b6
6 changed files with 82 additions and 23 deletions

View file

@ -152,7 +152,7 @@ Here's an example configuration with lazy.nvim: >lua
diff_mode = 'vim', diff_mode = 'vim',
next_test_key = '<c-n>', next_test_key = '<c-n>',
prev_test_key = '<c-p>', prev_test_key = '<c-p>',
toggle_diff_key = 't', toggle_diff_key = '<c-q>',
max_output_lines = 50, max_output_lines = 50,
}, },
diff = { diff = {
@ -214,7 +214,8 @@ Here's an example configuration with lazy.nvim: >lua
"vim" uses built-in diff, "git" provides character-level precision. "vim" uses built-in diff, "git" provides character-level precision.
{next_test_key} (string, default: "<c-n>") Key to navigate to next test case. {next_test_key} (string, default: "<c-n>") Key to navigate to next test case.
{prev_test_key} (string, default: "<c-p>") Key to navigate to previous test case. {prev_test_key} (string, default: "<c-p>") Key to navigate to previous test case.
{toggle_diff_key} (string, default: "t") Key to cycle through diff modes. {toggle_diff_key} (string, default: "<c-t>") Key to cycle through diff modes.
{close_key} (string, default: "<c-q>") Close the run panel/interactive terminal
{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*
@ -531,9 +532,9 @@ RUN PANEL KEYMAPS *cp-test-keys*
run_panel.next_test_key) run_panel.next_test_key)
<c-p> Navigate to previous test case (configurable via <c-p> Navigate to previous test case (configurable via
run_panel.prev_test_key) run_panel.prev_test_key)
t Cycle through diff modes: none → git → vim (configurable <c-t> Cycle through diff modes: none → git → vim (configurable
via run_panel.toggle_diff_key) via run_panel.toggle_diff_key)
q Exit test panel and restore layout <c-q> Exit run panel/interactive terminal and restore layout
Diff Modes ~ Diff Modes ~

View file

@ -126,8 +126,10 @@ function M.handle_command(opts)
local setup = require('cp.setup') local setup = require('cp.setup')
local ui = require('cp.ui.panel') local ui = require('cp.ui.panel')
if cmd.action == 'run' then if cmd.action == 'interact' then
ui.toggle_run_panel(cmd.debug) ui.toggle_interactive()
elseif cmd.action == 'run' then
ui.close_run_panel(cmd.debug)
elseif cmd.action == 'next' then elseif cmd.action == 'next' then
setup.navigate_problem(1, cmd.language) setup.navigate_problem(1, cmd.language)
elseif cmd.action == 'prev' then elseif cmd.action == 'prev' then

View file

@ -34,6 +34,7 @@
---@field next_test_key string Key to navigate to next test case ---@field next_test_key string Key to navigate to next test case
---@field prev_test_key string Key to navigate to previous test case ---@field prev_test_key string Key to navigate to previous test case
---@field toggle_diff_key string Key to cycle through diff modes ---@field toggle_diff_key string Key to cycle through diff modes
---@field close_key string Key to close panel/interactive terminal
---@field max_output_lines number Maximum lines of test output to display ---@field max_output_lines number Maximum lines of test output to display
---@class DiffGitConfig ---@class DiffGitConfig
@ -103,7 +104,8 @@ M.defaults = {
diff_mode = 'none', diff_mode = 'none',
next_test_key = '<c-n>', next_test_key = '<c-n>',
prev_test_key = '<c-p>', prev_test_key = '<c-p>',
toggle_diff_key = 't', toggle_diff_key = '<c-t>',
close_key = '<c-q>',
max_output_lines = 50, max_output_lines = 50,
}, },
diff = { diff = {
@ -229,6 +231,13 @@ function M.setup(user_config)
end, end,
'toggle_diff_key must be a non-empty string', 'toggle_diff_key must be a non-empty string',
}, },
close_key = {
config.run_panel.close_key,
function(value)
return type(value) == 'string' and value ~= ''
end,
'close_key must be a non-empty string',
},
max_output_lines = { max_output_lines = {
config.run_panel.max_output_lines, config.run_panel.max_output_lines,
function(value) function(value)

View file

@ -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' } M.ACTIONS = { 'run', 'next', 'prev', 'pick', 'cache', 'interact' }
M.PLATFORM_DISPLAY_NAMES = { M.PLATFORM_DISPLAY_NAMES = {
atcoder = 'AtCoder', atcoder = 'AtCoder',

View file

@ -9,7 +9,54 @@ local state = require('cp.state')
local current_diff_layout = nil local current_diff_layout = nil
local current_mode = nil local current_mode = nil
function M.toggle_run_panel(is_debug) function M.toggle_interactive()
if state.is_interactive_active then
if state.saved_interactive_session then
vim.cmd(('source %s'):format(state.saved_interactive_session))
vim.fn.delete(state.saved_interactive_session)
state.saved_interactive_session = nil
end
state.is_interactive_active = false
logger.log('interactive closed')
return
end
state.saved_interactive_session = vim.fn.tempname()
vim.cmd(('mksession! %s'):format(state.saved_interactive_session))
vim.cmd('silent only')
local config = config_module.get_config()
local contest_config = config.contests[state.get_platform() or '']
local execute = require('cp.runner.execute')
local compile_result = execute.compile_problem(contest_config, false)
if not compile_result.success then
require('cp.runner.run').handle_compilation_failure(compile_result.output)
return
end
local binary = state.get_binary_file()
if not binary then
logger.log('no binary path found', vim.log.levels.ERROR)
return
end
vim.cmd('terminal')
local term_buf = vim.api.nvim_get_current_buf()
local term_win = vim.api.nvim_get_current_win()
vim.fn.chansend(vim.b.terminal_job_id, binary .. '\n')
vim.keymap.set('t', '<c-q>', function()
M.toggle_interactive()
end, { buffer = term_buf, silent = true })
state.is_interactive_active = true
state.interactive_buf = term_buf
state.interactive_win = term_win
logger.log(('interactive opened, running %s'):format(binary))
end
function M.close_run_panel(is_debug)
if state.is_run_panel_active() then if state.is_run_panel_active() then
if current_diff_layout then if current_diff_layout then
current_diff_layout.cleanup() current_diff_layout.cleanup()
@ -134,8 +181,8 @@ function M.toggle_run_panel(is_debug)
end end
setup_keybindings_for_buffer = function(buf) setup_keybindings_for_buffer = function(buf)
vim.keymap.set('n', 'q', function() vim.keymap.set('n', config.run_panel.close_key, function()
M.toggle_run_panel() M.close_run_panel()
end, { buffer = buf, silent = true }) end, { buffer = buf, silent = true })
vim.keymap.set('n', config.run_panel.toggle_diff_key, function() vim.keymap.set('n', config.run_panel.toggle_diff_key, function()
local modes = { 'none', 'git', 'vim' } local modes = { 'none', 'git', 'vim' }

View file

@ -44,7 +44,7 @@ describe('cp command parsing', function()
package.loaded['cp.state'] = mock_state package.loaded['cp.state'] = mock_state
local mock_ui_panel = { local mock_ui_panel = {
toggle_run_panel = function() end, close_run_panel = function() end,
} }
package.loaded['cp.ui.panel'] = mock_ui_panel package.loaded['cp.ui.panel'] = mock_ui_panel