Skip to content

Commit 7daa6ef

Browse files
committed
feat: adds code reference picker to navigate file:line references in LLM responses
1 parent fcc8666 commit 7daa6ef

File tree

8 files changed

+440
-6
lines changed

8 files changed

+440
-6
lines changed

lua/opencode/api.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ function M.focus_input()
307307
ui.focus_input({ restore_position = true, start_insert = true })
308308
end
309309

310+
function M.references()
311+
require('opencode.ui.reference_picker').pick()
312+
end
313+
310314
function M.debug_output()
311315
if not config.debug.enabled then
312316
vim.notify('Debugging is not enabled in the config', vim.log.levels.WARN)
@@ -1214,6 +1218,10 @@ M.commands = {
12141218
desc = 'Paste image from clipboard and add to context',
12151219
fn = M.paste_image,
12161220
},
1221+
references = {
1222+
desc = 'Browse code references from conversation',
1223+
fn = M.references,
1224+
},
12171225
}
12181226

12191227
M.slash_commands_map = {
@@ -1231,6 +1239,7 @@ M.slash_commands_map = {
12311239
['/sessions'] = { fn = M.select_session, desc = 'Select session' },
12321240
['/share'] = { fn = M.share, desc = 'Share current session' },
12331241
['/timeline'] = { fn = M.timeline, desc = 'Open timeline picker' },
1242+
['/references'] = { fn = M.references, desc = 'Browse code references from conversation' },
12341243
['/undo'] = { fn = M.undo, desc = 'Undo last action' },
12351244
['/unshare'] = { fn = M.unshare, desc = 'Unshare current session' },
12361245
['/rename'] = { fn = M.rename_session, desc = 'Rename current session' },

lua/opencode/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ M.defaults = {
4949
['[['] = { 'prev_message' },
5050
['<tab>'] = { 'toggle_pane', mode = { 'n', 'i' } },
5151
['i'] = { 'focus_input' },
52+
['gr'] = { 'references', desc = 'Browse code references' },
5253
['<leader>oS'] = { 'select_child_session' },
5354
['<leader>oD'] = { 'debug_message' },
5455
['<leader>oO'] = { 'debug_output' },

lua/opencode/types.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,12 @@
483483
---@field messages number Number of messages reverted
484484
---@field tool_calls number Number of tool calls reverted
485485
---@field files table<string, {additions: number, deletions: number}> Summary of file changes reverted
486+
487+
---@class CodeReference
488+
---@field file_path string Relative or absolute file path
489+
---@field line number|nil Line number (1-indexed)
490+
---@field column number|nil Column number (optional)
491+
---@field context string Surrounding text for display in picker
492+
---@field message_id string ID of the message containing this reference
493+
---@field match_start number Start position of match in original text
494+
---@field match_end number End position of match in original text

lua/opencode/ui/base_picker.lua

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ local Promise = require('opencode.promise')
1717
---@field title string|fun(): string The picker title
1818
---@field width? number Optional width for the picker (defaults to config or current window width)
1919
---@field multi_selection? table<string, boolean> Actions that support multi-selection
20+
---@field preview? "file"|"none"|false Preview mode: "file" for file preview, "none" or false to disable
2021

2122
---@class TelescopeEntry
2223
---@field value any
@@ -341,15 +342,23 @@ end
341342
local function snacks_picker_ui(opts)
342343
local Snacks = require('snacks')
343344

345+
-- Determine if preview is enabled
346+
local has_preview = opts.preview == 'file'
347+
348+
-- Choose layout preset based on preview
349+
local layout_preset = has_preview and 'default' or 'select'
350+
344351
local snack_opts = {
345352
title = opts.title,
346353
layout = {
347-
preset = 'select',
354+
preset = layout_preset,
348355
config = function(layout)
349356
local width = opts.width and (opts.width + 3) or nil -- extra space for snacks UI
350-
layout.layout.width = width
351-
layout.layout.max_width = width
352-
layout.layout.min_width = width
357+
if not has_preview then
358+
layout.layout.width = width
359+
layout.layout.max_width = width
360+
layout.layout.min_width = width
361+
end
353362
return layout
354363
end,
355364
},
@@ -378,6 +387,11 @@ local function snacks_picker_ui(opts)
378387
},
379388
}
380389

390+
-- Add file preview if enabled
391+
if has_preview then
392+
snack_opts.preview = 'file'
393+
end
394+
381395
snack_opts.win = snack_opts.win or {}
382396
snack_opts.win.input = snack_opts.win.input or { keys = {} }
383397

lua/opencode/ui/formatter.lua

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,63 @@ end
426426
---@param output Output Output object to write to
427427
---@param text string
428428
function M._format_assistant_message(output, text)
429-
-- output:add_empty_line()
430-
output:add_lines(vim.split(text, '\n'))
429+
local reference_picker = require('opencode.ui.reference_picker')
430+
local references = reference_picker.get_references_for_text(text)
431+
432+
-- If no references, just add the text as-is
433+
if #references == 0 then
434+
output:add_lines(vim.split(text, '\n'))
435+
return
436+
end
437+
438+
-- Sort references by match_start position (ascending)
439+
table.sort(references, function(a, b)
440+
return a.match_start < b.match_start
441+
end)
442+
443+
-- Build a new text with icons inserted before each reference
444+
local result = ''
445+
local last_pos = 1
446+
local ref_icon = icons.get('reference')
447+
448+
for _, ref in ipairs(references) do
449+
-- Add text before this reference
450+
result = result .. text:sub(last_pos, ref.match_start - 1)
451+
-- Add the icon and the reference
452+
result = result .. ref_icon .. text:sub(ref.match_start, ref.match_end)
453+
last_pos = ref.match_end + 1
454+
end
455+
456+
-- Add any remaining text after the last reference
457+
if last_pos <= #text then
458+
result = result .. text:sub(last_pos)
459+
end
460+
461+
local lines = vim.split(result, '\n')
462+
local start_line = output:get_line_count()
463+
output:add_lines(lines)
464+
465+
-- Add highlighting for reference icons
466+
-- We need to find the icon positions in the rendered lines and add extmarks
467+
for i, line in ipairs(lines) do
468+
local line_num = start_line + i - 1
469+
local search_start = 1
470+
while true do
471+
local icon_start, icon_end = line:find(ref_icon, search_start, true)
472+
if not icon_start then
473+
break
474+
end
475+
-- Add extmark for the reference icon
476+
output:add_extmark(line_num, {
477+
virt_text = { { ref_icon, 'OpencodeReference' } },
478+
virt_text_pos = 'overlay',
479+
end_col = icon_end,
480+
hl_group = 'OpencodeReference',
481+
priority = 100,
482+
} --[[@as OutputExtmark]])
483+
search_start = icon_end + 1
484+
end
485+
end
431486
end
432487

433488
---@param output Output Output object to write to

lua/opencode/ui/highlight.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function M.setup()
3434
vim.api.nvim_set_hl(0, 'OpencodeContextSwitchOn', { link = '@label', default = true })
3535
vim.api.nvim_set_hl(0, 'OpencodePickerTime', { link = 'Comment', default = true })
3636
vim.api.nvim_set_hl(0, 'OpencodeDebugText', { link = 'Comment', default = true })
37+
vim.api.nvim_set_hl(0, 'OpencodeReference', { fg = '#1976D2', default = true })
3738
else
3839
vim.api.nvim_set_hl(0, 'OpencodeBorder', { fg = '#616161', default = true })
3940
vim.api.nvim_set_hl(0, 'OpencodeBackground', { link = 'Normal', default = true })
@@ -64,6 +65,7 @@ function M.setup()
6465
vim.api.nvim_set_hl(0, 'OpencodeContextSwitchOn', { link = '@label', default = true })
6566
vim.api.nvim_set_hl(0, 'OpencodePickerTime', { link = 'Comment', default = true })
6667
vim.api.nvim_set_hl(0, 'OpencodeDebugText', { link = 'Comment', default = true })
68+
vim.api.nvim_set_hl(0, 'OpencodeReference', { fg = '#7AA2F7', default = true })
6769
end
6870
end
6971

lua/opencode/ui/icons.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ local presets = {
2525
folder = '',
2626
attached_file = '󰌷 ',
2727
agent = '󰚩 ',
28+
reference = '',
2829
-- statuses
2930
status_on = '',
3031
status_off = '',
@@ -60,6 +61,7 @@ local presets = {
6061
folder = '[@]',
6162
attached_file = '@',
6263
agent = '@',
64+
reference = '@',
6365
-- statuses
6466
status_on = 'ON',
6567
status_off = 'OFF',

0 commit comments

Comments
 (0)