Skip to content

dfmonaco/nvim-opencode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nvim-opencode

A minimal Neovim plugin to send the current buffer to a running opencode CLI.

Installation

Using lazy.nvim:

{
  "yourusername/nvim-opencode",
  opts = {
    -- Optional configuration
    keymaps = {
      send_buffer = "<leader>ob",
      prompt_command = "<leader>oc",
      insert_context = "<leader>oi",
      toggle_prompt = "<leader>ot",
      clear_prompt = "<leader>ol",
    },
    default_keymaps = true,  -- Set to false to disable default keymaps
    port_cache_ttl = 60,     -- Cache port discovery for 60 seconds
  }
}

Or with packer.nvim:

use "yourusername/nvim-opencode"

Usage

Default Keybindings

  • <leader>ob - Send current buffer to opencode
  • <leader>oc - Prompt for a command to send to opencode
  • <leader>oi - Insert context placeholder into PROMPT buffer
  • <leader>ot - Toggle PROMPT buffer visibility
  • <leader>ol - Clear PROMPT buffer content

PROMPT Buffer Features:

  • Type @ in INSERT mode to trigger file autocomplete
  • Fuzzy search through project files
  • Automatically includes referenced files in context when sent to opencode

Commands

The plugin provides the following user commands:

  • :Opencode - Send current buffer to opencode
  • :OpencodeCommand - Prompt for command and send to opencode
  • :OpencodeContext - Add context to PROMPT buffer
  • :OpencodePrompt - Toggle PROMPT buffer
  • :OpencodeClear - Clear PROMPT buffer

Backward compatibility aliases (deprecated, use the commands above):

  • :OpencodeSendBuffer:Opencode
  • :OpencodePromptCommand:OpencodeCommand
  • :OpencodeInsertContext:OpencodeContext
  • :OpencodeTogglePrompt:OpencodePrompt

Configuration

Setup Function

You can customize the plugin by calling the setup function:

require('nvim-opencode').setup({
  -- Customize keymaps
  keymaps = {
    send_buffer = "<leader>os",      -- Custom keymap for sending buffer
    prompt_command = "<leader>op",   -- Custom keymap for prompt command
    insert_context = "<leader>oi",   -- Custom keymap for inserting context
    toggle_prompt = "<leader>ot",    -- Custom keymap for toggling prompt
    clear_prompt = "<leader>ol",     -- Custom keymap for clearing prompt
  },
  
  -- Disable default keymaps (useful if you want to define your own)
  default_keymaps = false,
  
  -- Cache port discovery for N seconds (reduces latency after first use)
  port_cache_ttl = 120,  -- Cache for 2 minutes
})

Custom Context Placeholders

You can register custom context placeholders:

require('nvim-opencode').register_context(
  "@mycontext",
  function(context)
    -- Your custom logic here
    return "custom content"
  end,
  "Description of your custom context"
)

Context Registration Validation: The plugin validates context placeholders at registration time. Placeholders must:

  • Match the @word format (e.g., @mycontext)
  • Have a handler function that receives a Context object and returns a string

Statusline Integration

You can integrate the opencode connection status into your statusline:

-- For lualine
require('lualine').setup({
  sections = {
    lualine_x = { require('nvim-opencode').status }
  }
})

-- For other statuslines
vim.o.statusline = vim.o.statusline .. '%{luaeval("require(\'nvim-opencode\').status()")}'

The status function returns a short status string based on server.get_cached_port(), for example:

  • [opencode :8080] - Connected (shows port number)
  • [opencode disconnected] - Disconnected or not cached

Environment Variables

Set the OPENCODE_PORT environment variable to specify the port if auto-detection doesn't work:

export OPENCODE_PORT=8000

Requirements

  • Neovim
  • curl
  • lsof (for auto-detecting the opencode server)

Optional (for better performance):

Architecture

nvim-opencode follows a layered architecture for maintainability and extensibility:

Top-level flow

  • plugin/nvim-opencode.lua – defines user commands and default keymaps and calls into the public module.
  • lua/nvim-opencode/init.lua – public API surface that re-exports high-level operations.
  • lua/nvim-opencode/client.lua – orchestrates all user-facing flows (send buffer, prompt command, PROMPT buffer, context insertion).

Layers

  • HTTP Layer (http.lua): Handles all HTTP communication with opencode server
  • API Layer (api/*.lua): Business logic for messages and commands
  • UI Layer (ui/*.lua): User interface components (command selector, context picker, file selector, PROMPT buffer)
  • Context Layer (context.lua): Context construction and interpolation
  • Server Layer (server.lua): Server discovery and port resolution with caching
  • Config Layer (config.lua): Configuration management and context registry accessors
  • Utilities (utils.lua, notify.lua, prompt_buffer.lua): Shared helpers, file discovery, and PROMPT buffer management with @ trigger

This design ensures:

  • Single responsibility per module
  • Easy testing through clear boundaries
  • Simple extension points for new features

Context System

The context system lets you embed location-aware placeholders in your prompts and expand them into rich strings before sending to opencode.

Core pieces:

  • context.lua – Defines the Context object, which captures buffer, window, cursor position, visual selection, diagnostics, quickfix list, and git diff.
  • config.lua – Registers built-in placeholders, validates custom ones, and exposes helpers like get_contexts() / get_context_descriptions().
  • ui/context_picker.lua – UI picker that lets you choose a context placeholder to append into the PROMPT buffer.
  • ui/file_context_selector.lua – File autocomplete UI for @ trigger in PROMPT buffer.
  • prompt_buffer.lua – Manages the PROMPT buffer where interpolated context content and user input live, with @ trigger for file references.
  • utils.lua – File discovery (using ripgrep or pure-Lua) and fuzzy filtering for file autocomplete.

Built-in placeholders:

Placeholder Description Example Output
@this Visual selection or cursor position @file.lua L10:C5 or @file.lua L10:C5-L15:C20
@buffer Current buffer path @myfile.lua
@buffers All open buffers @file1.lua @file2.lua @file3.lua
@diagnostics Current buffer diagnostics 2 diagnostics in @file.lua\n- L21:C10 (lua_ls): error
@quickfix Quickfix list entries @file.lua L10:C5 @file.lua L20:C10
@diff Git diff output Raw git diff content

For a deeper dive into placeholder formats, buffer-aware behavior, and interpolation details, see:

  • docs/architecture/context-interpolation.md
  • docs/architecture/buffer-aware-contexts.md
  • docs/features/context-system.md

You can register your own placeholders using:

require('nvim-opencode').register_context('@mycontext', function(ctx)
  -- ctx is a Context instance
  return 'custom content based on ctx'
end, 'My custom context')

These placeholders are expanded by Context:interpolate(text), which scans the text for @placeholder patterns (with optional @file and range markers) and replaces them using the appropriate handler.

For a deeper dive into the overall architecture and context system internals, see docs/architecture/architecture.md.

Events

nvim-opencode automatically subscribes to opencode's server-sent event (SSE) stream and forwards all events as Neovim User autocmds. This enables you to react to opencode activity programmatically.

Listening to Events

All opencode events are triggered as User autocmds with the pattern OpencodeEvent. You can listen to them using standard Neovim autocommand patterns:

vim.api.nvim_create_autocmd("User", {
  pattern = "OpencodeEvent",
  callback = function(args)
    local event = args.data.event  -- The event object from opencode
    local port = args.data.port    -- The port of the opencode server
    
    -- React to specific event types
    if event.type == "session.idle" then
      vim.notify("opencode finished responding")
    end
  end,
})

Event Data Structure

The autocmd data contains:

  • event: The decoded JSON event object from opencode
    • type: Event type string (e.g., "session.idle", "file.edited")
    • properties: Table with event-specific properties (optional)
  • port: The port number of the opencode server that emitted the event

Common Event Types

Based on opencode's event stream, common event types include:

  • server.connected - opencode server started
  • session.idle - opencode finished processing and is waiting
  • session.error - An error occurred in the session
  • message.updated - opencode is updating a message (streaming response)
  • message.part.updated - A message part was updated
  • file.edited - opencode edited a file (properties include file path)
  • permission.updated - opencode requesting permission
  • permission.replied - Permission request was answered

Example Use Cases

Display status notifications:

vim.api.nvim_create_autocmd("User", {
  pattern = "OpencodeEvent",
  callback = function(args)
    local event = args.data.event
    if event.type == "session.idle" then
      vim.notify("opencode is ready", vim.log.levels.INFO)
    elseif event.type == "session.error" then
      vim.notify("opencode error occurred", vim.log.levels.ERROR)
    end
  end,
})

Auto-reload edited files:

vim.api.nvim_create_autocmd("User", {
  pattern = "OpencodeEvent",
  callback = function(args)
    local event = args.data.event
    if event.type == "file.edited" and event.properties then
      local filepath = event.properties.path
      -- Reload the buffer if it's open
      for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        if vim.api.nvim_buf_is_loaded(buf) then
          local bufname = vim.api.nvim_buf_get_name(buf)
          if bufname == filepath then
            vim.api.nvim_buf_call(buf, function()
              vim.cmd("checktime")
            end)
            break
          end
        end
      end
    end
  end,
})

Filter specific events:

vim.api.nvim_create_autocmd("User", {
  pattern = "OpencodeEvent",
  callback = function(args)
    local event = args.data.event
    -- Only react to file edits
    if event.type == "file.edited" then
      print(string.format("opencode edited: %s", event.properties.path))
    end
  end,
})

Connection Management:

The SSE connection is automatically managed:

  • Connects when opencode port is discovered
  • Automatically reconnects if opencode restarts on a different port
  • Handles connection errors gracefully
  • No user intervention required

Development

For running tests, install plenary.nvim as a dependency:

Using lazy.nvim:

{
  "yourusername/nvim-opencode",
  dependencies = {
    "nvim-lua/plenary.nvim", -- for testing
  },
}

Run tests with: nvim --headless -c "lua require('plenary.test_harness').test_directory('spec')"

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages