diff --git a/AGENTS.md b/AGENTS.md index 88751a37..c3df645d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,10 +3,12 @@ ## Build, Lint, and Test - **Run all tests:** `./run_tests.sh` -- **Minimal tests:** +- **Minimal tests:** `./run_tests.sh -t minimal` `nvim --headless -u tests/minimal/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/minimal', {minimal_init = './tests/minimal/init.lua', sequential = true})"` -- **Unit tests:** +- **Unit tests:** `./run_tests.sh -t unit` `nvim --headless -u tests/minimal/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/unit', {minimal_init = './tests/minimal/init.lua'})"` +- **Replay tests:** `./run_tests.sh -t replay` + `nvim --headless -u tests/minimal/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/replay', {minimal_init = './tests/minimal/init.lua'})"` - **Run a single test:** Replace the directory in the above command with the test file path, e.g.: `nvim --headless -u tests/manual/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/unit/job_spec.lua', {minimal_init = './tests/minimal/init.lua'})"` - **Manual/Visual tests:** `./tests/manual/run_replay.sh` - Replay captured event data for visual testing @@ -27,6 +29,6 @@ - **Comments:** Only when necessary for clarity. Prefer self-explanatory code. - **Functions:** Prefer local functions. Use `M.func` for module exports. - **Config:** Centralize in `config.lua`. Use deep merge for user overrides. -- **Tests:** Place in `tests/minimal/` or `tests/unit/`. Manual/visual tests in `tests/manual/`. +- **Tests:** Place in `tests/minimal/`, `tests/unit/`, or `tests/replay/`. Manual/visual tests in `tests/manual/`. _Agentic coding agents must follow these conventions strictly for consistency and reliability._ diff --git a/README.md b/README.md index 33f366a1..6941e753 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ require('opencode').setup({ ['oq'] = { 'close' }, -- Close UI windows ['os'] = { 'select_session' }, -- Select and load a opencode session ['op'] = { 'configure_provider' }, -- Quick provider and model switch from predefined list + ['oz'] = { 'toggle_zoom' }, -- Zoom in/out on the Opencode windows ['od'] = { 'diff_open' }, -- Opens a diff tab of a modified file since the last opencode prompt ['o]'] = { 'diff_next' }, -- Navigate to next file diff ['o['] = { 'diff_prev' }, -- Navigate to previous file diff @@ -170,6 +171,7 @@ require('opencode').setup({ position = 'right', -- 'right' (default) or 'left'. Position of the UI split input_position = 'bottom', -- 'bottom' (default) or 'top'. Position of the input window window_width = 0.40, -- Width as percentage of editor width + zoom_width = 0.8, -- Zoom width as percentage of editor width input_height = 0.15, -- Input height as percentage of window height display_model = true, -- Display model name on top winbar display_context_size = true, -- Display context size in the footer diff --git a/lua/opencode/api.lua b/lua/opencode/api.lua index 8268edb3..0e5b3441 100644 --- a/lua/opencode/api.lua +++ b/lua/opencode/api.lua @@ -16,6 +16,10 @@ function M.swap_position() require('opencode.ui.ui').swap_position() end +function M.toggle_zoom() + require('opencode.ui.ui').toggle_zoom() +end + function M.open_input() core.open({ new_session = false, focus = 'input', start_insert = true }) end @@ -898,6 +902,11 @@ M.commands = { fn = M.toggle_pane, }, + toggle_zoom = { + desc = 'Toggle window zoom', + fn = M.toggle_zoom, + }, + swap = { desc = 'Swap pane position left/right', fn = M.swap_position, diff --git a/lua/opencode/config.lua b/lua/opencode/config.lua index 5694cf4f..4b5c8c8a 100644 --- a/lua/opencode/config.lua +++ b/lua/opencode/config.lua @@ -23,6 +23,7 @@ M.defaults = { ['oq'] = { 'close', desc = 'Close Opencode window' }, ['os'] = { 'select_session', desc = 'Select session' }, ['op'] = { 'configure_provider', desc = 'Configure provider' }, + ['oz'] = { 'toggle_zoom', desc = 'Toggle zoom' }, ['od'] = { 'diff_open', desc = 'Open diff view' }, ['o]'] = { 'diff_next', desc = 'Next diff' }, ['o['] = { 'diff_prev', desc = 'Previous diff' }, @@ -86,6 +87,7 @@ M.defaults = { position = 'right', input_position = 'bottom', window_width = 0.40, + zoom_width = 0.8, input_height = 0.15, picker_width = 100, display_model = true, diff --git a/lua/opencode/state.lua b/lua/opencode/state.lua index 36730b54..2263f5cc 100644 --- a/lua/opencode/state.lua +++ b/lua/opencode/state.lua @@ -35,6 +35,7 @@ ---@field opencode_server OpencodeServer|nil ---@field api_client OpencodeApiClient ---@field event_manager EventManager|nil +---@field pre_zoom_width integer|nil ---@field required_version string ---@field opencode_cli_version string|nil ---@field append fun( key:string, value:any) @@ -60,6 +61,7 @@ local _state = { display_route = nil, current_mode = nil, last_output = 0, + pre_zoom_width = nil, -- context last_sent_context = nil, current_context_config = {}, diff --git a/lua/opencode/ui/ui.lua b/lua/opencode/ui/ui.lua index 29432457..61b1ba96 100644 --- a/lua/opencode/ui/ui.lua +++ b/lua/opencode/ui/ui.lua @@ -1,4 +1,3 @@ -local M = {} local config = require('opencode.config') local state = require('opencode.state') local renderer = require('opencode.ui.renderer') @@ -7,6 +6,8 @@ local input_window = require('opencode.ui.input_window') local footer = require('opencode.ui.footer') local topbar = require('opencode.ui.topbar') +local M = {} + ---@param windows OpencodeWindowState? function M.close_windows(windows) if not windows then @@ -244,4 +245,24 @@ function M.swap_position() end) end +function M.toggle_zoom() + local windows = state.windows + if not windows or not state.windows.output_win or not state.windows.input_win then + return + end + + local width + + if state.pre_zoom_width then + width = state.pre_zoom_width + state.pre_zoom_width = nil + else + state.pre_zoom_width = vim.api.nvim_win_get_width(windows.output_win) + width = math.floor(config.ui.zoom_width * vim.o.columns) + end + + vim.api.nvim_win_set_config(windows.input_win, { width = width }) + vim.api.nvim_win_set_config(windows.output_win, { width = width }) +end + return M diff --git a/run_tests.sh b/run_tests.sh index 6823b999..e9a86902 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -18,13 +18,14 @@ print_usage() { echo "Usage: $0 [OPTIONS]" echo "Options:" echo " -f, --filter PATTERN Filter tests by pattern (matches test descriptions)" - echo " -t, --type TYPE Test type: all, minimal, unit, or specific file path" + echo " -t, --type TYPE Test type: all, minimal, unit, replay, or specific file path" echo " -h, --help Show this help message" echo "" echo "Examples:" echo " $0 # Run all tests" echo " $0 -f \"Timer\" # Run tests matching 'Timer'" echo " $0 -t unit # Run only unit tests" + echo " $0 -t replay # Run only replay tests" echo " $0 -t tests/unit/timer_spec.lua # Run specific test file" echo " $0 -f \"creates a new timer\" -t unit # Filter unit tests" } @@ -72,8 +73,10 @@ echo "------------------------------------------------" # Run tests based on type minimal_status=0 unit_status=0 +replay_status=0 minimal_output="" unit_output="" +replay_output="" if [ "$TEST_TYPE" = "all" ] || [ "$TEST_TYPE" = "minimal" ]; then # Run minimal tests @@ -103,8 +106,22 @@ if [ "$TEST_TYPE" = "all" ] || [ "$TEST_TYPE" = "unit" ]; then echo "------------------------------------------------" fi +if [ "$TEST_TYPE" = "all" ] || [ "$TEST_TYPE" = "replay" ]; then + # Run replay tests + replay_output=$(nvim --headless -u tests/minimal/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/replay', {minimal_init = './tests/minimal/init.lua'$FILTER_OPTION})" 2>&1) + replay_status=$? + clean_output "$replay_output" + + if [ $replay_status -eq 0 ]; then + echo -e "${GREEN}✓ Replay tests passed${NC}" + else + echo -e "${RED}✗ Replay tests failed${NC}" + fi + echo "------------------------------------------------" +fi + # Handle specific test file -if [ "$TEST_TYPE" != "all" ] && [ "$TEST_TYPE" != "minimal" ] && [ "$TEST_TYPE" != "unit" ]; then +if [ "$TEST_TYPE" != "all" ] && [ "$TEST_TYPE" != "minimal" ] && [ "$TEST_TYPE" != "unit" ] && [ "$TEST_TYPE" != "replay" ]; then # Assume it's a specific test file path if [ -f "$TEST_TYPE" ]; then specific_output=$(nvim --headless -u tests/minimal/init.lua -c "lua require('plenary.test_harness').test_directory('./$TEST_TYPE', {minimal_init = './tests/minimal/init.lua'$FILTER_OPTION})" 2>&1) @@ -129,9 +146,10 @@ fi # Check for any failures all_output="$minimal_output -$unit_output" +$unit_output +$replay_output" -if [ $minimal_status -ne 0 ] || [ $unit_status -ne 0 ] || echo "$all_output" | grep -q "\[31mFail.*||"; then +if [ $minimal_status -ne 0 ] || [ $unit_status -ne 0 ] || [ $replay_status -ne 0 ] || echo "$all_output" | grep -q "\[31mFail.*||"; then echo -e "\n${RED}======== TEST FAILURES SUMMARY ========${NC}" # Extract and format failures diff --git a/tests/unit/renderer_spec.lua b/tests/replay/renderer_spec.lua similarity index 100% rename from tests/unit/renderer_spec.lua rename to tests/replay/renderer_spec.lua diff --git a/tests/unit/api_spec.lua b/tests/unit/api_spec.lua index e9ad0f92..6a0d55e6 100644 --- a/tests/unit/api_spec.lua +++ b/tests/unit/api_spec.lua @@ -518,4 +518,29 @@ describe('opencode.api', function() state.current_model = original_model end) end) + + describe('toggle_zoom', function() + it('calls ui.toggle_zoom when toggle_zoom is called', function() + stub(ui, 'toggle_zoom') + + api.toggle_zoom() + + assert.stub(ui.toggle_zoom).was_called() + end) + + it('is available in the commands table', function() + local cmd = api.commands['toggle_zoom'] + assert.truthy(cmd, 'toggle_zoom command should exist') + assert.equal('Toggle window zoom', cmd.desc) + assert.is_function(cmd.fn) + end) + + it('routes through command interface', function() + stub(ui, 'toggle_zoom') + + api.commands.toggle_zoom.fn({}) + + assert.stub(ui.toggle_zoom).was_called() + end) + end) end)