aboutsummaryrefslogtreecommitdiff
path: root/lua/hobs.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/hobs.lua')
-rw-r--r--lua/hobs.lua147
1 files changed, 46 insertions, 101 deletions
diff --git a/lua/hobs.lua b/lua/hobs.lua
index 85acec7..885004d 100644
--- a/lua/hobs.lua
+++ b/lua/hobs.lua
@@ -1,11 +1,19 @@
local M = {}
+local source = debug.getinfo(1, "S").source
+local module_file = assert(source:match("^@(.+)$"))
+local plugin_dir = vim.fs.dirname(vim.fs.dirname(module_file))
+local opencode_config_dir = vim.fs.joinpath(plugin_dir, "opencode")
+
+local log = require("hobs.log")
+local prompt = require("hobs.prompt")
+
local ns = vim.api.nvim_create_namespace("hobs.nvim")
local next_hob_id = 0
-local log_buf = nil
local config = {
opencode = { "opencode", "run" },
+ opencode_env = { OPENCODE_CONFIG_DIR = opencode_config_dir },
}
local function trim(s)
@@ -24,43 +32,6 @@ local function strip_markdown_fence(s)
return s
end
-local function get_log_buf()
- if log_buf and vim.api.nvim_buf_is_valid(log_buf) then
- return log_buf
- end
-
- log_buf = vim.api.nvim_create_buf(false, true)
- vim.api.nvim_buf_set_name(log_buf, "hobs://log")
- vim.bo[log_buf].buftype = "nofile"
- vim.bo[log_buf].bufhidden = "hide"
- vim.bo[log_buf].swapfile = false
- vim.bo[log_buf].filetype = "sed"
- vim.bo[log_buf].modifiable = false
- return log_buf
-end
-
-local function append_log(lines)
- local buf = get_log_buf()
- vim.bo[buf].modifiable = true
- vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines)
- vim.bo[buf].modifiable = false
-end
-
-local function log_sed(state, script)
- local filename = vim.api.nvim_buf_get_name(state.buf)
- local lines = {
- string.format("===== Hob %d | %s | %s =====", state.id, os.date("%Y-%m-%d %H:%M:%S"), filename),
- }
- vim.list_extend(lines, vim.split(script, "\n", { plain = true }))
- table.insert(lines, "")
- append_log(lines)
-end
-
-local function open_log()
- local buf = get_log_buf()
- vim.cmd("botright split")
- vim.api.nvim_win_set_buf(0, buf)
-end
local function get_visual_selection(buf)
local mode = vim.fn.visualmode()
@@ -190,6 +161,7 @@ local function set_status(state, text)
width = math.max(16, vim.fn.strdisplaywidth(line)),
height = 1,
})
+ vim.api.nvim_buf_add_highlight(state.status_buf, ns, "HobStatus", 0, 0, -1)
end
end
@@ -235,7 +207,7 @@ local function create_status(buf, source_win, selection)
win = source_win,
bufpos = pos,
row = 0,
- col = 0,
+ col = anchor_col + 3,
width = 22,
height = 1,
style = "minimal",
@@ -244,9 +216,8 @@ local function create_status(buf, source_win, selection)
zindex = 50,
})
- vim.api.nvim_set_option_value("winhighlight", "WinBg:HobStatusBg", { win = state.float_win })
vim.api.nvim_win_set_hl_ns(state.float_win, ns)
- vim.api.nvim_set_hl(ns, "HobStatusBg", { bg = "darkgrey" })
+ vim.api.nvim_set_hl(ns, "HobStatus", { fg = "#8b5cf6" })
state.augroup = vim.api.nvim_create_augroup("hobs.nvim." .. state.id, { clear = true })
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "InsertLeave" }, {
@@ -263,42 +234,6 @@ local function create_status(buf, source_win, selection)
return state
end
-local function build_prompt(filename, selection, user_prompt)
- return table.concat({
- "You are a coding agent working concurrently with a human in Neovim.",
- "",
- "The human selected this code and gave you a task.",
- "The buffer may continue changing while you work.",
- "",
- "File: " .. filename,
- string.format(
- "Initial selection: %d:%d-%d:%d",
- selection.display_start_line,
- selection.display_start_col,
- selection.display_end_line,
- selection.display_end_col
- ),
- "",
- "Selected text:",
- "<selection>",
- selection.text,
- "</selection>",
- "",
- "Task:",
- user_prompt,
- "",
- "IMPORTANT OUTPUT CONTRACT:",
- "- Do not edit any files yourself.",
- "- You may inspect the project as needed, but your final response must be ONLY a GNU sed script.",
- "- The sed script will be applied to the CURRENT live contents of this Neovim buffer after you finish.",
- "- Do not use absolute line-number addresses; the human may have inserted or removed lines while you were working.",
- "- Address edits by distinctive source text/patterns and keep substitutions narrowly scoped.",
- "- Prefer scripts that fail to change anything rather than matching unrelated code.",
- "- Do not include Markdown fences, prose, explanations, or shell commands.",
- "- Output only the contents of a sed -f program.",
- }, "\n")
-end
-
local function current_buffer_text(buf)
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
return table.concat(lines, "\n") .. "\n"
@@ -314,7 +249,8 @@ local function apply_sed(state, script)
error("OpenCode returned an empty sed script")
end
- log_sed(state, script)
+ log.log_hob_sed(state.id, script)
+ log.log_hob_finished(state.id)
local sedfile = vim.fn.tempname() .. ".sed"
vim.fn.writefile(vim.split(script, "\n", { plain = true }), sedfile, "b")
@@ -353,6 +289,7 @@ function M.hob(user_prompt)
local ok, selection = pcall(get_visual_selection, buf)
if not ok then
+ log.log_error("hob: " .. selection)
vim.notify("hobs.nvim: " .. selection, vim.log.levels.ERROR)
return
end
@@ -363,24 +300,26 @@ function M.hob(user_prompt)
end
local state = create_status(buf, source_win, selection)
- local prompt = build_prompt(filename, selection, user_prompt)
+ local built_prompt = prompt.build(filename, selection, user_prompt)
+
+ log.log_hob_started(state.id, filename, user_prompt)
local argv = vim.deepcopy(config.opencode)
- table.insert(argv, prompt)
+ table.insert(argv, built_prompt)
local cwd = vim.fn.getcwd()
vim.system(argv, {
cwd = cwd,
text = true,
+ env = config.opencode_env,
}, function(result)
vim.schedule(function()
if result.code ~= 0 then
+ local error_msg = trim(result.stderr or result.stdout or "")
set_status(state, "Hob failed")
- vim.notify(
- "hobs.nvim: opencode failed:\n" .. trim(result.stderr or result.stdout or ""),
- vim.log.levels.ERROR
- )
+ log.log_hob_failed(state.id, error_msg)
+ vim.notify("hobs.nvim: opencode failed:\n" .. error_msg, vim.log.levels.ERROR)
vim.defer_fn(function()
close_status(state)
end, 2500)
@@ -390,6 +329,7 @@ function M.hob(user_prompt)
local applied, apply_err = pcall(apply_sed, state, result.stdout or "")
if not applied then
set_status(state, "Hob edit failed")
+ log.log_hob_edit_failed(state.id, apply_err)
vim.notify("hobs.nvim: " .. apply_err, vim.log.levels.ERROR)
vim.defer_fn(function()
close_status(state)
@@ -405,7 +345,7 @@ function M.hob(user_prompt)
end)
end
-_G.hob_todos_operator = function ()
+local function hob_todos_operator()
local sel = vim.fn.getpos(".")
local line_start = sel[2] + 1
@@ -440,9 +380,11 @@ _G.hob_todos_operator = function ()
}
local filename = vim.api.nvim_buf_get_name(buf) or "[unnamed buffer]"
- local prompt = build_prompt(filename, selection, "address the TODOs in this" ..
- "selection. Only address the TODOs in the selected text, leave other TODOs" ..
- "alone.")
+ local built_prompt = prompt.build(filename, selection, "address the TODOs in this" ..
+ "selection. Only address the TODOs in the selected text, leave other TODOs" ..
+ "alone.")
+
+ log.log_hob_started(state.id, filename, "address the TODOs")
local state = {
id = next_hob_id,
@@ -484,7 +426,7 @@ _G.hob_todos_operator = function ()
win = source_win,
bufpos = pos,
row = 0,
- col = 0,
+ col = anchor_col + 3,
width = 22,
height = 1,
style = "minimal",
@@ -493,9 +435,8 @@ _G.hob_todos_operator = function ()
zindex = 50,
})
- vim.api.nvim_set_option_value("winhighlight", "WinBg:HobStatusBg", { win = state.float_win })
vim.api.nvim_win_set_hl_ns(state.float_win, ns)
- vim.api.nvim_set_hl(ns, "HobStatusBg", { bg = "darkgrey" })
+ vim.api.nvim_set_hl(ns, "HobStatus", { fg = "#8b5cf6" })
state.augroup = vim.api.nvim_create_augroup("hobs.nvim." .. state.id, { clear = true })
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "InsertLeave" }, {
@@ -511,21 +452,21 @@ _G.hob_todos_operator = function ()
set_status(state, "Hob is working…")
local argv = vim.deepcopy(config.opencode)
- table.insert(argv, prompt)
+ table.insert(argv, built_prompt)
local cwd = vim.fn.getcwd()
vim.system(argv, {
cwd = cwd,
text = true,
+ env = config.opencode_env,
}, function(result)
vim.schedule(function()
if result.code ~= 0 then
+ local error_msg = trim(result.stderr or result.stdout or "")
set_status(state, "Hob failed")
- vim.notify(
- "hobs.nvim: opencode failed:\n" .. trim(result.stderr or result.stdout or ""),
- vim.log.levels.ERROR
- )
+ log.log_hob_failed(state.id, error_msg)
+ vim.notify("hobs.nvim: opencode failed:\n" .. error_msg, vim.log.levels.ERROR)
vim.defer_fn(function()
close_status(state)
end, 2500)
@@ -535,6 +476,7 @@ _G.hob_todos_operator = function ()
local applied, apply_err = pcall(apply_sed, state, result.stdout or "")
if not applied then
set_status(state, "Hob edit failed")
+ log.log_hob_edit_failed(state.id, apply_err)
vim.notify("hobs.nvim: " .. apply_err, vim.log.levels.ERROR)
vim.defer_fn(function()
close_status(state)
@@ -553,6 +495,8 @@ end
function M.setup(opts)
config = vim.tbl_deep_extend("force", config, opts or {})
+ log.init(ns)
+
pcall(vim.api.nvim_del_user_command, "Hob")
vim.api.nvim_create_user_command("Hob", function(opts_)
M.hob(opts_.args)
@@ -563,15 +507,16 @@ function M.setup(opts)
})
pcall(vim.api.nvim_del_user_command, "HobLog")
- vim.api.nvim_create_user_command("HobLog", open_log, {
- desc = "Open the hobs.nvim sed log",
+ vim.api.nvim_create_user_command("HobLog", log.open, {
+ desc = "Open the hobs.nvim log",
})
pcall(vim.api.nvim_del_keymap, "n", "<leader>hd")
pcall(vim.api.nvim_del_keymap, "x", "<leader>hd")
- vim.api.nvim_set_keymap("n", "<leader>hd", "<cmd>set operatorfunc=v:lua.hob_todos_operator<CR>g@", { desc = "Hob: address TODOs (motion)" })
- vim.api.nvim_set_keymap("x", "<leader>hd", "<cmd>set operatorfunc=v:lua.hob_todos_operator<CR>g@", { desc = "Hob: address TODOs (motion)" })
+ vim.api.nvim_set_keymap("n", "<leader>hd", "<cmd>set operatorfunc=v:lua.require('hobs').hob_todos_operator<CR>g@",
+ { desc = "Hob: address TODOs (motion)" })
+ vim.api.nvim_set_keymap("x", "<leader>hd", "<cmd>set operatorfunc=v:lua.require('hobs').hob_todos_operator<CR>g@",
+ { desc = "Hob: address TODOs (motion)" })
end
return M
-