From d7829c069b09d2115daa1aa33a729dd9ffee301e Mon Sep 17 00:00:00 2001 From: Josh Rahm Date: Tue, 11 Aug 2026 11:19:05 -0600 Subject: [refactor] break up some of the mega lua file. --- lua/hobs.lua | 147 +++++++++++++++++++---------------------------------------- 1 file changed, 46 insertions(+), 101 deletions(-) (limited to 'lua/hobs.lua') 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.text, - "", - "", - "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", "hd") pcall(vim.api.nvim_del_keymap, "x", "hd") - vim.api.nvim_set_keymap("n", "hd", "set operatorfunc=v:lua.hob_todos_operatorg@", { desc = "Hob: address TODOs (motion)" }) - vim.api.nvim_set_keymap("x", "hd", "set operatorfunc=v:lua.hob_todos_operatorg@", { desc = "Hob: address TODOs (motion)" }) + vim.api.nvim_set_keymap("n", "hd", "set operatorfunc=v:lua.require('hobs').hob_todos_operatorg@", + { desc = "Hob: address TODOs (motion)" }) + vim.api.nvim_set_keymap("x", "hd", "set operatorfunc=v:lua.require('hobs').hob_todos_operatorg@", + { desc = "Hob: address TODOs (motion)" }) end return M - -- cgit