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
4 changes: 3 additions & 1 deletion lua/opencode/commands/handlers/workflow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ M.actions.submit_input_prompt = Promise.async(function()
return
end

require('opencode.services.messaging').send_message(input_content)
require('opencode.services.messaging').send_message(input_content):catch(function(err)
require('opencode.log').notify('Error sending message: ' .. vim.inspect(err), vim.log.levels.ERROR)
end)
if config.ui.input.auto_hide and not input_window.is_hidden() then
input_window._hide()
end
Expand Down
12 changes: 7 additions & 5 deletions lua/opencode/context/base_context.lua
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,14 @@ M.get_git_diff = Promise.async(function(context_config)
return nil
end

return Promise.system({ 'git', 'diff', '--cached', '--minimal' }):and_then(function(output)
if output == '' then
return nil
end
return output.stdout
-- Optional context must not prevent sending from non-repositories or after Git failures.
local ok, output = pcall(function()
return Promise.system({ 'git', 'diff', '--cached', '--minimal' }):await()
end)
if not ok or output.stdout == '' then
return nil
end
return output.stdout
end)

---@param file table
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/api_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ describe('opencode.api', function()
local after_run_stub = stub(messaging, 'after_run')
local send_message_stub = stub(messaging, 'send_message').invokes(function(prompt)
require('opencode.services.messaging').after_run(prompt)
return true
return resolved(true)
end)
local take_input_stub = stub(input_window, 'take_input').returns('hello')
local is_hidden_stub = stub(input_window, 'is_hidden').returns(true)
Expand Down
65 changes: 65 additions & 0 deletions tests/unit/base_context_git_diff_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
local assert = require('luassert')
local stub = require('luassert.stub')
local base_context = require('opencode.context.base_context')
local Promise = require('opencode.promise')

describe('Git diff context', function()
local system
local enabled = { git_diff = { enabled = true } }

before_each(function()
system = stub(Promise, 'system')
end)

after_each(function()
system:revert()
end)

it('does not run Git when disabled', function()
assert.is_nil(base_context.get_git_diff({ git_diff = { enabled = false } }):wait())
assert.stub(system).was_not_called()
end)

it('returns staged changes', function()
system.returns(Promise.new():resolve({ stdout = 'staged diff' }))
assert.equals('staged diff', base_context.get_git_diff(enabled):wait())
assert.stub(system).was_called_with({ 'git', 'diff', '--cached', '--minimal' })
end)

it('returns nil for an empty diff', function()
system.returns(Promise.new():resolve({ stdout = '' }))
assert.is_nil(base_context.get_git_diff(enabled):wait())
end)

it('returns nil when Git rejects', function()
system.returns(Promise.new():reject({ code = 129, stderr = 'not a git repository' }))
assert.is_nil(base_context.get_git_diff(enabled):wait())
end)

it('returns nil when Git cannot start', function()
system.invokes(function()
error('Git executable not found')
end)
assert.is_nil(base_context.get_git_diff(enabled):wait())
end)
end)

describe('Git diff context outside a repository', function()
local cwd, directory

before_each(function()
cwd = vim.fn.getcwd()
directory = '/tmp/opencode/git-diff-' .. vim.uv.hrtime()
vim.fn.mkdir(directory, 'p')
vim.fn.chdir(directory)
end)

after_each(function()
vim.fn.chdir(cwd)
vim.fn.delete(directory, 'd')
end)

it('resolves nil instead of rejecting', function()
assert.is_nil(base_context.get_git_diff({ git_diff = { enabled = true } }):wait())
end)
end)
18 changes: 17 additions & 1 deletion tests/unit/commands_handlers_workflow_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('opencode.commands.handlers.workflow', function()
state.store.set_raw('windows', { input_buf = buf, input_win = vim.api.nvim_get_current_win() })
state.store.set_raw('display_route', nil)
config.ui.input.auto_hide = true
send_message = stub(require('opencode.services.messaging'), 'send_message').returns(false)
send_message = stub(require('opencode.services.messaging'), 'send_message').returns(Promise.new():resolve(false))
hide = stub(input_window, '_hide')
hidden = stub(input_window, 'is_hidden').returns(false)
get_key = stub(config, 'get_key_for_function').returns('/')
Expand Down Expand Up @@ -76,6 +76,22 @@ describe('opencode.commands.handlers.workflow', function()
assert.stub(hide).was_called(1)
end)

it('reports asynchronous send failures without waiting to auto-hide', function()
local pending = Promise.new()
send_message.returns(pending)
submit({ 'hello' })
assert.stub(hide).was_called(1)
assert.stub(notify).was_not_called()

pending:reject('context preparation failed')
assert.is_true(vim.wait(1000, function()
return #notify.calls > 0
end))
assert
.stub(notify)
.was_called_with('[opencode.nvim] Error sending message: ' .. vim.inspect('context preparation failed'), vim.log.levels.ERROR)
end)

it('clears empty input without sending or hiding', function()
submit({ '' })
assert.stub(send_message).was_not_called()
Expand Down
Loading