Skip to content

Commit 7923dc3

Browse files
committed
feat: better permission actions
1 parent feda1b5 commit 7923dc3

7 files changed

Lines changed: 30 additions & 6684 deletions

lua/opencode/api.lua

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,11 @@ end
927927

928928
---@param answer? 'once'|'always'|'reject'
929929
---@param permission? OpencodePermission
930-
function M.respond_to_permission(answer, permission_id)
930+
function M.respond_to_permission(answer, permission)
931931
answer = answer or 'once'
932932

933-
-- Try to get current permission from permission window first, fallback to state
934933
local permission_window = require('opencode.ui.permission_window')
935-
local current_permission = permission_window.get_current_permission()
934+
local current_permission = permission or permission_window.get_current_permission()
936935

937936
if not current_permission then
938937
vim.notify('No permission request to accept', vim.log.levels.WARN)
@@ -1270,12 +1269,24 @@ M.commands = {
12701269
completions = { 'accept', 'accept_all', 'deny' },
12711270
fn = function(args)
12721271
local subcmd = args[1]
1272+
local index = tonumber(args[2])
1273+
local permission = nil
1274+
if index then
1275+
local permission_window = require('opencode.ui.permission_window')
1276+
local permissions = permission_window.get_all_permissions()
1277+
if not permissions or not permissions[index] then
1278+
vim.notify('Invalid permission index: ' .. tostring(index), vim.log.levels.ERROR)
1279+
return
1280+
end
1281+
permission = permissions[index]
1282+
end
1283+
12731284
if subcmd == 'accept' then
1274-
M.permission_accept()
1285+
M.permission_accept(permission)
12751286
elseif subcmd == 'accept_all' then
1276-
M.permission_accept_all()
1287+
M.permission_accept_all(permission)
12771288
elseif subcmd == 'deny' then
1278-
M.permission_deny()
1289+
M.permission_deny(permission)
12791290
else
12801291
local valid_subcmds = table.concat(M.commands.permission.completions or {}, ', ')
12811292
vim.notify('Invalid permission subcommand. Use: ' .. valid_subcmds, vim.log.levels.ERROR)

lua/opencode/ui/permission_window.lua

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,37 +161,26 @@ function M.clear_keymaps()
161161

162162
local action_order = { 'accept', 'deny', 'accept_all' }
163163

164+
local function safe_del(keymap, opts)
165+
pcall(vim.keymap.del, 'n', keymap, opts)
166+
end
167+
164168
for i, permission in ipairs(M._permission_queue) do
165169
for _, action in ipairs(action_order) do
166-
-- Clear OpenCode-focused keys (buffer-specific)
167170
local opencode_key = opencode_keys[action]
168171
if opencode_key then
169-
local function safe_del(keymap, opts)
170-
pcall(vim.keymap.del, 'n', keymap, opts)
171-
end
172-
173172
for _, buf in ipairs(buffers) do
174173
if buf then
175-
if #M._permission_queue > 1 then
176-
safe_del(opencode_key .. tostring(i), { buffer = buf })
177-
else
178-
safe_del(opencode_key, { buffer = buf })
179-
end
174+
safe_del(opencode_key .. tostring(i), { buffer = buf })
175+
safe_del(opencode_key, { buffer = buf })
180176
end
181177
end
182178
end
183179

184180
local editor_key = editor_keys[action]
185181
if editor_key then
186-
local function safe_del_global(keymap)
187-
pcall(vim.keymap.del, 'n', keymap)
188-
end
189-
190-
if #M._permission_queue > 1 then
191-
safe_del_global(editor_key .. tostring(i))
192-
else
193-
safe_del_global(editor_key)
194-
end
182+
safe_del(editor_key .. tostring(i))
183+
safe_del(editor_key)
195184
end
196185
end
197186
end
@@ -200,6 +189,7 @@ end
200189
---Setup keymaps for all permission actions
201190
function M.setup_keymaps()
202191
M.clear_keymaps()
192+
203193
if #M._permission_queue == 0 then
204194
return
205195
end
@@ -227,7 +217,7 @@ function M.setup_keymaps()
227217
if api_func then
228218
local function execute_action()
229219
M._selected_index = i
230-
api_func()
220+
api_func(permission)
231221
M.remove_permission(permission.id)
232222
end
233223

tests/data/permission-denied.expected.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/data/permission-denied.json

Lines changed: 0 additions & 6646 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"actions":[],"extmarks":[[1,1,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["PLAN","OpencodeMessageRoleAssistant"],[" claude-sonnet-4.5","OpencodeHint"],[" (2025-10-16 04:27:36)","OpencodeHint"],[" [msg_9eb45fbe60020xE560OGH3Vdoo]","OpencodeHint"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-3,"virt_text_hide":false}],[2,5,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[3,6,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[4,7,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[5,8,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[6,9,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[7,10,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[8,11,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[9,12,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[10,13,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[11,14,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}],[12,15,0,{"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text":[["▌","OpencodeToolBorder"]],"right_gravity":true,"ns_id":3,"virt_text_win_col":-1,"virt_text_hide":false}]],"timestamp":1766432126,"lines":["----","","","Perfect! Now I understand how it works. The message headers have extmarks with `virt_text` where the first element contains the icon (either `header_user` or `header_assistant`). Let me check the output_window module to understand the extmark namespace:","","** run** `Find extmark namespace usage`","","`````bash","> rg \"nvim_buf_get_extmarks|ns_id\" /Users/cam/Dev/neovim-dev/opencode.nvim/lua/opencode/ui/output_window.lua -B 2 -A 2","","`````","","> [!WARNING] Permission required to run this tool.",">","> Accept `a` Always `A` Deny `d`","",""]}
1+
{"actions":[],"extmarks":[[1,1,0,{"virt_text_hide":false,"virt_text":[[" ","OpencodeMessageRoleAssistant"],[" "],["PLAN","OpencodeMessageRoleAssistant"],[" claude-sonnet-4.5","OpencodeHint"],[" (2025-10-16 04:27:36)","OpencodeHint"],[" [msg_9eb45fbe60020xE560OGH3Vdoo]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"virt_text_win_col":-3,"ns_id":3,"right_gravity":true}],[2,5,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[3,6,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[4,7,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[5,8,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[6,9,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[7,10,0,{"virt_text_hide":false,"virt_text":[["▌","OpencodeToolBorder"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":true,"priority":4096,"virt_text_win_col":-1,"ns_id":3,"right_gravity":true}],[8,13,0,{"virt_text_hide":false,"virt_text":[[" ","OpencodeMessageRoleSystem"],[" "],["SYSTEM","OpencodeMessageRoleSystem"],["","OpencodeHint"],["","OpencodeHint"],[" [permission-display-message]","OpencodeHint"]],"virt_text_pos":"win_col","virt_text_repeat_linebreak":false,"priority":10,"virt_text_win_col":-3,"ns_id":3,"right_gravity":true}]],"timestamp":1767982258,"lines":["----","","","Perfect! Now I understand how it works. The message headers have extmarks with `virt_text` where the first element contains the icon (either `header_user` or `header_assistant`). Let me check the output_window module to understand the extmark namespace:","","** run** `Find extmark namespace usage`","","`````bash","> rg \"nvim_buf_get_extmarks|ns_id\" /Users/cam/Dev/neovim-dev/opencode.nvim/lua/opencode/ui/output_window.lua -B 2 -A 2","","`````","","----","","","> [!WARNING] Permission Required",">","> `rg \"nvim_buf_get_extmarks|ns_id\" /Users/cam/Dev/neovim-dev/opencode.nvim/lua/opencode/ui/output_window.lua -B 2 -A 2`",">","> Accept `a` Deny `d` Always `A`","",""]}

tests/data/shifting-and-multiple-perms.expected.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/unit/keymap_spec.lua

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,7 @@ describe('opencode.keymap', function()
5858
package.loaded['opencode.state'] = mock_state
5959

6060
-- Mock the config module
61-
local mock_config = {
62-
keymap = {
63-
permission = {
64-
accept = 'a',
65-
accept_all = 'A',
66-
deny = 'd',
67-
},
68-
},
69-
}
61+
local mock_config = {}
7062
package.loaded['opencode.config'] = mock_config
7163

7264
-- Now require the keymap module

0 commit comments

Comments
 (0)