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
1 change: 1 addition & 0 deletions lua/opencode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local M = {}

M.ask = require("opencode.ui.ask").ask
M.select = require("opencode.ui.select").select
M.fzf = require("opencode.ui.fzf")

M.prompt = require("opencode.api.prompt").prompt
M.operator = require("opencode.api.operator").operator
Expand Down
21 changes: 21 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ 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 `fzf` integration.
---@field fzf? opencode.fzf.Opts
---
---Options for `opencode` event handling.
---@field events? opencode.events.Opts
---
Expand All @@ -42,6 +45,19 @@ vim.g.opencode_opts = vim.g.opencode_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.

---@class opencode.fzf.Opts
---
---Options for file search using fzf-lua.
---Passed to fzf-lua's files() function.
---@field files? table
---
---Options for buffer search using fzf-lua.
---Passed to fzf-lua's buffers() function.
---@field buffers? table
---
---Default prompt prefix when selecting files/buffers.
---@field prompt_prefix? string

---@type opencode.Opts
local defaults = {
port = nil,
Expand Down Expand Up @@ -108,6 +124,11 @@ local defaults = {
},
},
},
fzf = {
files = {},
buffers = {},
prompt_prefix = "",
},
events = {
enabled = true,
reload = true,
Expand Down
210 changes: 210 additions & 0 deletions lua/opencode/ui/fzf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
local M = {}

local config = require("opencode.config").opts.fzf or {}

local function is_buf_valid(buf)
return vim.api.nvim_get_option_value("buftype", { buf = buf }) == "" and vim.api.nvim_buf_get_name(buf) ~= ""
end

local function escape_terminal_mode()
local mode = vim.api.nvim_get_mode().mode
if mode:match("^[it]") then
vim.cmd("stopinsert")
vim.fn.feedkeys("\27", "n")
end
end

local function strip_ansi_codes(str)
local result = str
result = result:gsub("\27%[[0-9;]*m", "")
result = result:gsub("\27%[K", "")
return result
end

local function strip_file_icon(str)
local stripped = strip_ansi_codes(str)
stripped = stripped:gsub("^%s*", "")
local icon_and_space = stripped:match("^[^\32-\126]+%s+")
if icon_and_space then
stripped = stripped:sub(#icon_and_space + 1)
end
return stripped:match("^%s*(.-)%s*$")
end

local function format_file_for_opencode(filepath)
local rel_path = vim.fn.fnamemodify(filepath, ":.")
return "@" .. rel_path .. " "
end

local function send_files_to_opencode(files, prompt_prefix)
if not files or #files == 0 then
return
end

local file_contexts = {}
for _, file in ipairs(files) do
local clean_file = strip_file_icon(file)
if clean_file and clean_file ~= "" then
table.insert(file_contexts, format_file_for_opencode(clean_file))
end
end

if #file_contexts == 0 then
return
end

local prompt = table.concat(file_contexts, " ")
if prompt_prefix and #prompt_prefix > 0 then
prompt = prompt_prefix .. " " .. prompt
end

require("opencode").prompt(prompt)
end

function M.select_buffers(prompt_prefix)
escape_terminal_mode()

local ok, fzf = pcall(require, "fzf-lua")
if not ok then
vim.notify("fzf-lua not found. Please install fzf-lua to use this feature.", vim.log.levels.ERROR)
return
end

local buffers = {}
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if is_buf_valid(buf) then
local bufname = vim.api.nvim_buf_get_name(buf)
table.insert(buffers, {
path = bufname,
bufnr = buf,
ordinal = bufname,
display = bufname,
})
end
end

if #buffers == 0 then
vim.notify("No valid buffers found", vim.log.levels.WARN)
return
end

local opts = vim.tbl_deep_extend("force", {
prompt = "Select buffers> ",
previewer = false,
file_icons = false,
git_icons = false,
fzf_opts = {
["--multi"] = "",
["--header"] = "Tab to select multiple, Enter to confirm",
},
actions = {
["default"] = function(selected)
if not selected or #selected == 0 then
return
end
send_files_to_opencode(selected, prompt_prefix or config.prompt_prefix)
end,
},
}, config.buffers or {})

fzf.fzf_exec(
function(cb)
for _, buf in ipairs(buffers) do
cb(buf.display)
end
cb(nil)
end,
opts
)
end

function M.select_files(prompt_prefix, opts)
escape_terminal_mode()
opts = opts or {}

local ok, fzf = pcall(require, "fzf-lua")
if not ok then
vim.notify("fzf-lua not found. Please install fzf-lua to use this feature.", vim.log.levels.ERROR)
return
end

local files_opts = vim.tbl_deep_extend("force", {
prompt = "Select files> ",
previewer = "builtin",
file_icons = false,
color_icons = false,
git_icons = false,
fzf_opts = {
["--multi"] = "",
["--header"] = "Tab to select multiple, Enter to confirm",
},
actions = {
["default"] = function(selected)
if not selected or #selected == 0 then
return
end
send_files_to_opencode(selected, prompt_prefix or config.prompt_prefix)
end,
},
}, config.files or {})

if opts.cwd then
files_opts.cwd = opts.cwd
end

fzf.files(files_opts)
end

function M.ask_with_files(prompt_text)
escape_terminal_mode()

local ok, fzf = pcall(require, "fzf-lua")
if not ok then
vim.notify("fzf-lua not found. Please install fzf-lua to use this feature.", vim.log.levels.ERROR)
return
end

local function show_file_selector()
vim.ui.select({ "buffers", "project files" }, { prompt = "Select source: " }, function(choice)
if not choice then
return
end

if choice == "buffers" then
M.select_buffers(prompt_text or config.prompt_prefix)
elseif choice == "project files" then
M.select_files(prompt_text or config.prompt_prefix)
end
end)
end

show_file_selector()
end

function M.append_files_to_current_prompt()
escape_terminal_mode()

local ok, fzf = pcall(require, "fzf-lua")
if not ok then
vim.notify("fzf-lua not found. Please install fzf-lua to use this feature.", vim.log.levels.ERROR)
return
end

local function show_file_selector()
vim.ui.select({ "buffers", "project files" }, { prompt = "Select source: " }, function(choice)
if not choice then
return
end

if choice == "buffers" then
M.select_buffers(config.prompt_prefix)
elseif choice == "project files" then
M.select_files(config.prompt_prefix)
end
end)
end

show_file_selector()
end

return M
34 changes: 34 additions & 0 deletions plugin/fzf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
if not pcall(require, "fzf-lua") then
return
end

vim.api.nvim_create_user_command("OpencodeFzfBuffers", function(opts)
local prompt_prefix = opts.fargs[1] or ""
require("opencode").fzf.select_buffers(prompt_prefix)
end, {
nargs = "?",
desc = "Select buffers using fzf-lua and send to opencode",
})

vim.api.nvim_create_user_command("OpencodeFzfFiles", function(opts)
local prompt_prefix = opts.fargs[1] or ""
require("opencode").fzf.select_files(prompt_prefix)
end, {
nargs = "?",
desc = "Select project files using fzf-lua and send to opencode",
})

vim.api.nvim_create_user_command("OpencodeFzfAsk", function(opts)
local prompt_text = opts.fargs[1] or ""
require("opencode").fzf.ask_with_files(prompt_text)
end, {
nargs = "?",
desc = "Ask opencode with files selected via fzf-lua",
})

vim.api.nvim_create_user_command("OpencodeFzfAppend", function()
require("opencode").fzf.append_files_to_current_prompt()
end, {
nargs = 0,
desc = "Append files selected via fzf-lua to current opencode prompt",
})
Loading