|
| 1 | +# Buffers' High-Level Symbols Feature Spec |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This feature enhances the `recent_buffers` context in `opencode.context` by optionally including high-level symbols (e.g., functions, variables, classes) extracted via LSP's `document_symbols()` for eligible buffers. The goal is to provide a structured overview of buffer contents without sending full file text, reducing token usage (~80% savings for config files) while aiding navigation and understanding of plugin interactions (e.g., exports/requires in Lua files). |
| 6 | + |
| 7 | +- **Integration Point**: Modify `M.get_recent_buffers()` to conditionally fetch and append symbols data to each buffer entry. |
| 8 | +- **Config Location**: Add `symbols_only` (boolean, default: false) under `config.context.recent_buffers`. When true, symbols replace full content summary; when false, symbols are additional metadata. |
| 9 | +- **Output Format**: Append `symbols` array to buffer objects in `recent_buffers` context, e.g.: |
| 10 | + ```lua |
| 11 | + { |
| 12 | + bufnr = 1, |
| 13 | + name = '/path/to/file.lua', |
| 14 | + lastused = 1234567890, |
| 15 | + changed = false, |
| 16 | + symbols = { |
| 17 | + { name = 'setup', kind = 'Function', range = { start = {1,0}, ['end'] = {5,0} }, detail = '...' }, |
| 18 | + { name = 'opts', kind = 'Variable', range = { start = {10,0}, ['end'] = {15,0} }, detail = '...' }, |
| 19 | + } |
| 20 | + } |
| 21 | + ``` |
| 22 | +- **Benefits**: |
| 23 | + - Reduces noise in AI prompts by focusing on structure. |
| 24 | + - Highlights key elements (e.g., `setup()` in Neovim plugins). |
| 25 | + - Pairs with `cursor_surrounding` for deeper context on demand. |
| 26 | +- **Drawbacks & Mitigations**: |
| 27 | + - LSP dependency: Skip if no client attached. |
| 28 | + - Performance: Fetch only for recent/active buffers; cache if needed. |
| 29 | + - Parser limits: Relies on Treesitter/LSP; fallback to nil if unavailable. |
| 30 | + |
| 31 | +## Requirements |
| 32 | + |
| 33 | +1. **Configurability**: User can enable/disable via `config.context.recent_buffers.symbols_only`. |
| 34 | +2. **Data Extraction**: Use `vim.lsp.buf.document_symbols()` to get hierarchical symbols (functions, variables, etc.). |
| 35 | +3. **Context Inclusion**: Serialize symbols into JSON-friendly format for `format_message()` parts. |
| 36 | +4. **Filtering**: Only include symbols for buffers meeting all constraints (below). |
| 37 | +5. **Staleness Handling**: Prioritize buffers with `lastused > 0` (recently accessed); limit to top N (e.g., 5) for symbols fetch to avoid overhead. |
| 38 | +6. **Error Handling**: Gracefully skip symbols if LSP call fails (e.g., no client, timeout); log via `vim.notify`. |
| 39 | +7. **Test Coverage**: Unit tests for enabled/disabled cases, constraint edge cases (e.g., <100 lines, no LSP). |
| 40 | + |
| 41 | +## Constraints |
| 42 | + |
| 43 | +- **Line Count**: Fetch symbols only if buffer has >100 lines (`vim.api.nvim_buf_line_count(bufnr) > 100`). Rationale: Small files (<100 lines) are cheap to send fully; symbols add little value. |
| 44 | +- **LSP Attached**: Only if LSP client(s) attached to buffer (`#vim.lsp.get_active_clients({ bufnr = bufnr }) > 0`). Skip otherwise to avoid errors. |
| 45 | +- **Editable Buffer**: Only for editable, file-based buffers: |
| 46 | + - Not readonly (`not vim.api.nvim_buf_get_option(bufnr, 'readonly')`). |
| 47 | + - Not terminal (`not vim.api.nvim_buf_is_valid(bufnr) or vim.bo[bufnr].buftype ~= 'terminal'`). |
| 48 | + - Listed and in cwd (`buflisted = true` and `is_in_cwd(name)`). |
| 49 | + Rationale: Symbols irrelevant for non-editable (e.g., help, quickfix) or transient buffers. |
| 50 | + |
| 51 | +## Implementation Steps |
| 52 | + |
| 53 | +1. **Config Update** (`lua/opencode/config.lua`): |
| 54 | + - Add `symbols_only` to `recent_buffers` schema/defaults. |
| 55 | + - Deep merge support for user overrides. |
| 56 | + |
| 57 | +2. **Core Logic** (`lua/opencode/context.lua`): |
| 58 | + - In `M.get_recent_buffers()`: |
| 59 | + - After collecting base buffers, loop over eligible ones. |
| 60 | + - For each: Check constraints (lines >100, LSP attached, editable). |
| 61 | + - If met and `symbols_only` enabled: Call `vim.lsp.buf_request(bufnr, 'textDocument/documentSymbol', {}, handler)`. |
| 62 | + - Handler: Flatten hierarchy to array of {name, kind, range, detail}; append to buffer entry. |
| 63 | + - Async handling: Use promises or callbacks to avoid blocking; collect results post-fetch. |
| 64 | + - Update `M.load()` to call `get_recent_buffers()` as before. |
| 65 | + - In `format_context_part('recent_buffers', ...)`: Ensure symbols serialize correctly (JSON-safe). |
| 66 | + |
| 67 | +3. **Edge Cases**: |
| 68 | + - No symbols: Append empty array `symbols = {}`. |
| 69 | + - Partial fetch: If some buffers qualify, include for those only. |
| 70 | + - Limit: Cap symbols per buffer (e.g., top 20) to prevent bloat. |
| 71 | + - Fallback: If `symbols_only=true` but constraints fail, include basic buffer info without symbols. |
| 72 | + |
| 73 | +4. **Testing** (`tests/unit/context_spec.lua`): |
| 74 | + - Mock `vim.lsp.get_active_clients()` to return clients/nil. |
| 75 | + - Mock `vim.api.nvim_buf_line_count()` for line checks. |
| 76 | + - Mock `vim.lsp.buf_request()` to simulate symbol responses. |
| 77 | + - Test: Enabled with constraints met → symbols included; failed constraint → skipped; disabled → no symbols. |
| 78 | + |
| 79 | +5. **Verification**: |
| 80 | + - Run `./run_tests.sh` → All pass. |
| 81 | + - Manual: Enable in config, open large Lua file with LSP → Check context includes symbols. |
| 82 | + - Token Savings: Compare prompt sizes with/without. |
| 83 | + |
| 84 | +## Dependencies |
| 85 | + |
| 86 | +- Neovim >=0.8 (LSP APIs). |
| 87 | +- Treesitter/LSP setup for language (e.g., lua_ls for Lua). |
| 88 | +- No external libs; use built-in `vim.lsp`. |
| 89 | + |
| 90 | +## Potential Extensions |
| 91 | + |
| 92 | +- Cache symbols per buffer (invalidate on write). |
| 93 | +- Support Treesitter fallback if no LSP. |
| 94 | +- Filter symbols by kind (e.g., only functions/variables). |
| 95 | + |
| 96 | +This spec ensures elegant, constraint-driven implementation aligned with project principles: simplicity, performance, and spec-driven development. |
0 commit comments