Skip to content
Draft
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,12 @@ Example keymap for silent add:
['<leader>oY'] = { 'add_visual_selection', { open_input = false }, mode = {'v'} }
```

Example keymap for toggling a specific context using a parameterized API action:

```lua
['<leader>ocf'] = { 'toggle_context#current_file', desc = 'Toggle current file context' }
```

### Run opts

You can pass additional options when running a prompt via command or API:
Expand Down
6 changes: 6 additions & 0 deletions lua/opencode/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ function M.quick_chat(message, range)
quick_chat.quick_chat(prompt, { context_config = ctx }, range)
end

---@param context_key OpencodeToggleableContextKey
---@return boolean|nil enabled
function M.toggle_context(context_key)
return require('opencode.context').toggle_context(context_key)
end

function M.toggle_pane()
ui.toggle_pane()
end
Expand Down
12 changes: 9 additions & 3 deletions lua/opencode/keymap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ local function process_keymap_entry(keymap_config, default_modes, base_opts, def
-- Skip keymap if explicitly set to false (disabled)
elseif config_entry then
local func_name = config_entry[1]
local func_args = config_entry[2]
local raw_callback = type(func_name) == 'function' and func_name or api[func_name]
local resolved_func_name = func_name
local inline_arg = nil
if type(func_name) == 'string' then
resolved_func_name, inline_arg = func_name:match('^([^#]+)#(.+)$')
resolved_func_name = resolved_func_name or func_name
end
local func_args = config_entry[2] or inline_arg
local raw_callback = type(func_name) == 'function' and func_name or api[resolved_func_name]
local callback = raw_callback

if raw_callback and func_args then
Expand All @@ -38,7 +44,7 @@ local function process_keymap_entry(keymap_config, default_modes, base_opts, def

local modes = config_entry.mode or default_modes
local opts = vim.tbl_deep_extend('force', {}, base_opts)
opts.desc = config_entry.desc or cmds[func_name] and cmds[func_name].desc
opts.desc = config_entry.desc or cmds[resolved_func_name] and cmds[resolved_func_name].desc

if callback then
if defer_to_completion then
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
---@field share? SessionShareInfo

---@class OpencodeKeymapEntry
---@field [1] string # Function name
---@field [1] string # Function name, optionally suffixed with #<arg> to pass one string arg
---@field mode? string|string[] # Mode(s) for the keymap
---@field desc? string # Keymap description

Expand Down
7 changes: 7 additions & 0 deletions tests/unit/api_spec.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local api = require('opencode.api')
local core = require('opencode.core')
local ui = require('opencode.ui.ui')
local context = require('opencode.context')
local state = require('opencode.state')
local stub = require('luassert.stub')
local assert = require('luassert')
Expand Down Expand Up @@ -104,6 +105,12 @@ describe('opencode.api', function()
new_session = true,
focus = 'output',
})

assert.is_function(api.toggle_context, 'Should export toggle_context')
stub(context, 'toggle_context').returns(true)
local enabled = api.toggle_context('current_file')
assert.is_true(enabled)
assert.stub(context.toggle_context).was_called_with('current_file')
end)
end)

Expand Down
23 changes: 23 additions & 0 deletions tests/unit/keymap_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ describe('opencode.keymap', function()
mock_api = {
open_input = function() end,
toggle = function() end,
toggle_context = function() end,
submit_input_prompt = function() end,
permission_accept = function() end,
permission_accept_all = function() end,
permission_deny = function() end,
commands = {
open_input = { desc = 'Open input window' },
toggle = { desc = 'Toggle opencode windows' },
toggle_context = { desc = 'Toggle prompt context' },
submit_input_prompt = { desc = 'Submit input prompt' },
},
}
Expand Down Expand Up @@ -159,6 +161,27 @@ describe('opencode.keymap', function()
assert.is_not_nil(keymap_entry.opts.desc, 'Should have a description from API fallback')
assert.equal('Toggle opencode windows', keymap_entry.opts.desc)
end)

it('supports parameterized function names with #suffix', function()
local called_with = nil
mock_api.toggle_context = function(context_key)
called_with = context_key
end

local test_keymap = {
editor = {
['<leader>tcf'] = { 'toggle_context#current_file' },
},
}

keymap.setup(test_keymap)

assert.equal(1, #set_keymaps, 'Should set up 1 keymap')
assert.equal('Toggle prompt context', set_keymaps[1].opts.desc)

set_keymaps[1].callback()
assert.equal('current_file', called_with)
end)
end)

describe('setup_window_keymaps', function()
Expand Down