Skip to content
Open
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
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,64 @@ Please submit PRs adding new providers! 🙂

## 🚀 Usage

### 💬 Chat — `require("opencode").chat()`

Open a custom Neovim frontend for `opencode` with a floating window chat interface.

- **Pure Neovim UI** — no terminal needed
- **Real-time streaming** — see AI responses as they're generated
- **Vim keybindings** — navigate with hjkl, yank messages, and more
- **Session management** — create new sessions, interrupt responses
- **Markdown rendering** — syntax highlighting for code blocks
- **Configurable** — customize keymaps, provider, model, and window size

#### Configuration

Enable the chat frontend and configure it in your `vim.g.opencode_opts`:

```lua
vim.g.opencode_opts = {
chat = {
enabled = true, -- Enable custom chat UI instead of terminal TUI (default: false)
provider_id = "anthropic", -- AI provider (default: "anthropic")
model_id = "claude-3-5-sonnet-20241022", -- AI model (default: "claude-3-5-sonnet-20241022")
width = 0.6, -- Window width as fraction of editor width (default: 0.6)
height = 0.7, -- Window height as fraction of editor height (default: 0.7)
keymaps = {
open = "<leader>oc", -- Keymap to open chat (default: "<leader>oc")
send = { "i", "a" }, -- Keymaps to send message (default: {"i", "a"})
close = { "q", "<Esc>" }, -- Keymaps to close chat (default: {"q", "<Esc>"})
new_session = "n", -- Keymap for new session (default: "n")
interrupt = "<C-c>", -- Keymap to interrupt (default: "<C-c>")
yank = "yy", -- Keymap to yank message (default: "yy")
}
}
}
```

When `enabled = true`, the global keymap will be automatically set up. You can also manually call the chat function:

```lua
vim.keymap.set('n', '<leader>oc', function()
require('opencode').chat()
end, { desc = "Open OpenCode Chat" })
```

#### Keybindings

Default keybindings in the chat window (all configurable):

| Key | Action |
| --- | ------ |
| `<leader>oc` | Open chat window (global) |
| `i` or `a` | Send a message |
| `n` | Start a new session |
| `q` or `<Esc>` | Close chat window |
| `yy` | Yank current message to clipboard |
| `<C-c>` | Interrupt current response |
| `j`/`k` | Navigate up/down |
| `gg`/`G` | Jump to top/bottom |

### ✍️ Ask — `require("opencode").ask()`

Input a prompt for `opencode`.
Expand Down
33 changes: 33 additions & 0 deletions ftplugin/opencode_chat.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- Filetype plugin for opencode_chat buffers
-- Provides syntax highlighting for chat messages

-- Enable markdown-like syntax for code blocks
vim.bo.commentstring = "<!-- %s -->"

-- Set up basic syntax highlighting
vim.cmd([[
syntax match OpencodeHeaderUser "^### You$"
syntax match OpencodeHeaderAssistant "^### Assistant$"
syntax match OpencodeHeaderSystem "^### System$"
syntax match OpencodeSeparator "^─\+$"
syntax match OpencodeTypingIndicator "^▋$"

highlight default link OpencodeHeaderUser Title
highlight default link OpencodeHeaderAssistant Special
highlight default link OpencodeHeaderSystem Comment
highlight default link OpencodeSeparator Comment
highlight default link OpencodeTypingIndicator WarningMsg
]])

-- Enable treesitter markdown highlighting if available
local ok, ts_highlight = pcall(require, "vim.treesitter.highlighter")
if ok then
ok = pcall(vim.treesitter.start, vim.api.nvim_get_current_buf(), "markdown")
if not ok then
-- Fallback to basic markdown syntax
vim.cmd("runtime! syntax/markdown.vim")
end
else
-- Fallback to basic markdown syntax
vim.cmd("runtime! syntax/markdown.vim")
end
2 changes: 2 additions & 0 deletions lua/opencode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ M.stop = require("opencode.provider").stop

M.statusline = require("opencode.status").statusline

M.chat = require("opencode.ui.chat_init").start_chat

return M
34 changes: 34 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,31 @@ vim.g.opencode_opts = vim.g.opencode_opts
---Supports [`snacks.picker`](https://github.com/folke/snacks.nvim/blob/main/docs/picker.md).
---@field select? opencode.select.Opts
---
---Options for `chat()`.
---@field chat? opencode.chat.Opts
---
---Options for `opencode` event handling.
---@field events? opencode.events.Opts
---
---Provide an integrated `opencode` when one is not found.
---@field provider? opencode.Provider|opencode.provider.Opts

---@class opencode.chat.Opts
---@field enabled? boolean Enable custom chat UI instead of terminal TUI (default: false)
---@field provider_id? string AI provider to use (default: "anthropic")
---@field model_id? string AI model to use (default: "claude-3-5-sonnet-20241022")
---@field width? number Width of chat window as fraction of editor width (default: 0.6)
---@field height? number Height of chat window as fraction of editor height (default: 0.7)
---@field keymaps? opencode.chat.Keymaps Keymaps for chat window

---@class opencode.chat.Keymaps
---@field open? string|string[] Keymap(s) to open chat window (default: "<leader>oc")
---@field send? string|string[] Keymap(s) to send message in chat (default: {"i", "a"})
---@field close? string|string[] Keymap(s) to close chat window (default: {"q", "<Esc>"})
---@field new_session? string Keymap to start new session (default: "n")
---@field interrupt? string Keymap to interrupt response (default: "<C-c>")
---@field yank? string Keymap to yank current message (default: "yy")

---@class opencode.Prompt : opencode.api.prompt.Opts
---@field prompt string The prompt to send to `opencode`.
---@field ask? boolean Call `ask(prompt)` instead of `prompt(prompt)`. Useful for prompts that expect additional user input.
Expand Down Expand Up @@ -105,6 +124,21 @@ local defaults = {
},
},
},
chat = {
enabled = false,
provider_id = "anthropic",
model_id = "claude-3-5-sonnet-20241022",
width = 0.6,
height = 0.7,
keymaps = {
open = "<leader>oc",
send = { "i", "a" },
close = { "q", "<Esc>" },
new_session = "n",
interrupt = "<C-c>",
yank = "yy",
},
},
events = {
enabled = true,
reload = true,
Expand Down
Loading
Loading