Throw code to OpenCode from Neovim — seamless AI-powered code review and editing
handball.nvim is a Neovim plugin that connects your editor to OpenCode, enabling you to send code selections to an AI agent for review, refactoring, or questions. Queue multiple requests, keep working, and get notified when changes are ready.
- 🚀 Non-blocking workflow — Queue multiple review prompts while continuing to edit
- 📦 FIFO queue — Requests execute in order, one at a time (single-flight)
- 🔄 Auto-refresh — Changed buffers reload automatically when AI work completes
- 📊 Status line — Visual indicators for request state and queue depth
- 🎯 Visual selection — Send any code block with
<leader>or - 🔗 Session linking — One-time setup per repository
- ⚡ Async HTTP — Built on Neovim 0.10+
vim.system()API
- Neovim 0.10+ (required for
vim.system()async API) - OpenCode CLI installed and in PATH
curl(for HTTP requests)
Using lazy.nvim
{
"abaayd01/handball.nvim",
config = function()
-- Plugin auto-loads with default keymaps
end,
}Using packer.nvim
use 'abaayd01/handball.nvim'git clone https://github.com/abaayd01/handball.nvim.git \
~/.config/nvim/pack/plugins/start/handball.nvim-
Start OpenCode server:
opencode serve --port 8080
-
Link your session (one-time per repo):
:OpenCodeLinkSession
Or use
<leader>os -
Send code for review:
- Visually select code (
v,V, or<C-v>) - Press
<leader>or - Enter your prompt (e.g., "refactor this for clarity")
- Visually select code (
Perfect for code reviews or refactoring sessions:
- Open a file and visually select code
- Press
<leader>orand describe what you want - Continue immediately — the request is queued and runs in the background
- Submit more prompts anytime — they queue up
- Watch the status line for progress
- Buffers auto-refresh when each request completes
How queuing works:
- First request starts immediately
- Subsequent requests queue and execute FIFO
- Context (file, line range, selected text) is captured at submission time
- Cancel anytime with
<leader>oc
| Command | Description |
|---|---|
:OpenCodeLinkSession |
Select and link to an OpenCode session |
:OpenCodeCheckSession |
Show linked session status |
:OpenCodeCancel |
Cancel the active request |
| Keymap | Mode | Description |
|---|---|---|
<leader>or |
Visual | Send selected code to OpenCode |
<leader>os |
Normal | Link/check OpenCode session |
<leader>oc |
Normal | Cancel active request |
Show OpenCode status in your status line:
-- Using lualine
require('lualine').setup {
sections = {
lualine_x = {
{ require('handball.ui').statusline, cond = require('handball').status_detail().is_active }
}
}
}Or manually:
-- In your statusline configuration
%{luaeval('require("handball.ui").statusline()')}Status indicators:
⏳ submitted- Request sent to server⚙️ processing- Server is working✓ completed- Request finished successfully✗ failed- Request failed⊘ cancelled- Request was cancelled(N)- Number of queued requests waiting
Currently uses sensible defaults:
- Server:
http://localhost:8080 - Timeout: 5 minutes
- Poll interval: 2 seconds
- Keymap leader:
<leader>
Future versions will support user configuration.
Hook into plugin lifecycle for custom behavior:
-- On request completion
vim.api.nvim_create_autocmd("User", {
pattern = "OpenCodeRequestComplete",
callback = function(args)
local data = args.data
print(string.format("✓ %d files changed", #data.files_changed))
end,
})
-- On request cancellation
vim.api.nvim_create_autocmd("User", {
pattern = "OpenCodeRequestCancelled",
callback = function(args)
print("Request cancelled")
end,
})Event data:
OpenCodeRequestComplete:{ status, files_changed[], session_id, queue_remaining }OpenCodeRequestCancelled:{ job_id }
┌─────────────────────────────────────────────────────────┐
│ User Action → Plugin Orchestrator → OpenCode │
│ (select code) (handball/init.lua) (localhost) │
└─────────────────────────────────────────────────────────┘
Modules:
api.lua— Async HTTP client usingvim.system()state.lua— Request state machine and FIFO queueinit.lua— Main orchestration and queue schedulerui.lua— Notifications and status linecontext.lua— Visual selection extractionsession.lua— Session persistence (.git/opencode-neovim-session)plugin/handball.lua— Commands and keymaps
"OpenCode server not running"
Start the server:
opencode serve --port 8080"Not in a git repository"
The plugin stores session links in .git/opencode-neovim-session. Navigate to a git repository first.
"Empty response from server"
Check:
- Server running on port 8080
- No firewall blocking localhost:8080
- At least one OpenCode session created
"No code selected"
Ensure you're in visual mode (v, V, or <C-v>) before pressing <leader>or.
MIT © abaayd01
Made with 🏐 for the OpenCode community