Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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._
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ require('opencode').setup({
['<leader>oq'] = { 'close' }, -- Close UI windows
['<leader>os'] = { 'select_session' }, -- Select and load a opencode session
['<leader>op'] = { 'configure_provider' }, -- Quick provider and model switch from predefined list
['<leader>oz'] = { 'toggle_zoom' }, -- Zoom in/out on the Opencode windows
['<leader>od'] = { 'diff_open' }, -- Opens a diff tab of a modified file since the last opencode prompt
['<leader>o]'] = { 'diff_next' }, -- Navigate to next file diff
['<leader>o['] = { 'diff_prev' }, -- Navigate to previous file diff
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions lua/opencode/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ M.defaults = {
['<leader>oq'] = { 'close', desc = 'Close Opencode window' },
['<leader>os'] = { 'select_session', desc = 'Select session' },
['<leader>op'] = { 'configure_provider', desc = 'Configure provider' },
['<leader>oz'] = { 'toggle_zoom', desc = 'Toggle zoom' },
['<leader>od'] = { 'diff_open', desc = 'Open diff view' },
['<leader>o]'] = { 'diff_next', desc = 'Next diff' },
['<leader>o['] = { 'diff_prev', desc = 'Previous diff' },
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lua/opencode/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 = {},
Expand Down
23 changes: 22 additions & 1 deletion lua/opencode/ui/ui.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local M = {}
local config = require('opencode.config')
local state = require('opencode.state')
local renderer = require('opencode.ui.renderer')
Expand All @@ -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
Expand Down Expand Up @@ -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
26 changes: 22 additions & 4 deletions run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
File renamed without changes.
25 changes: 25 additions & 0 deletions tests/unit/api_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)