diff options
| author | theprimeagain <the.primeagen@gmail.com> | 2026-02-23 19:50:47 -0700 |
|---|---|---|
| committer | theprimeagain <the.primeagen@gmail.com> | 2026-02-23 19:50:47 -0700 |
| commit | 6d80dccf18b0bb66415d355857cb5c11d777d297 (patch) | |
| tree | d5ab4ce9c663d7209099301afdcb293093ca1652 /lua | |
| parent | 6a64e0b2f4c7f1e3911db1f8318e5d7c68cb8dff (diff) | |
| download | a4-6d80dccf18b0bb66415d355857cb5c11d777d297.tar.xz a4-6d80dccf18b0bb66415d355857cb5c11d777d297.zip | |
beginning the vibe work
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/99/extensions/work/worker.lua | 26 | ||||
| -rw-r--r-- | lua/99/init.lua | 25 | ||||
| -rw-r--r-- | lua/99/ops/init.lua | 1 | ||||
| -rw-r--r-- | lua/99/ops/qfix-helpers.lua | 43 | ||||
| -rw-r--r-- | lua/99/ops/search.lua | 46 | ||||
| -rw-r--r-- | lua/99/ops/vibe.lua | 52 | ||||
| -rw-r--r-- | lua/99/prompt.lua | 41 | ||||
| -rw-r--r-- | lua/99/providers.lua | 1 | ||||
| -rw-r--r-- | lua/99/test/qfix_helpers_spec.lua | 86 |
9 files changed, 265 insertions, 56 deletions
diff --git a/lua/99/extensions/work/worker.lua b/lua/99/extensions/work/worker.lua index 780b2f2..be38aca 100644 --- a/lua/99/extensions/work/worker.lua +++ b/lua/99/extensions/work/worker.lua @@ -1,10 +1,26 @@ local Window = require("99.window") local utils = require("99.utils") ---- @class _99.Extension.Worker +--- @class _99.Extensions.Worker +--- A persistent way to keep track of work. +--- +--- this will likely be where the most change and focus goes into. I would like +--- to take this into worktree territory and be able to swap between stuff super +--- slick. +--- +--- Until then, it is going to be a single bit of work that you can provide +--- the description and then use search to find what is left that needs to be done. +--- @docs base +--- @field set_work fun(opts?: _99.WorkOpts): nil +--- will set the work for the project. If opts provide a description then no +--- input capture of work description will be required +--- @field work fun(): nil +--- will use _99.search to find what is left to be done for this work item to be +--- considered done local M = {} --- @class _99.WorkOpts +--- @docs included --- @field description string | nil --- @return string @@ -49,7 +65,7 @@ local function set_work_item_cb(success, result) end end -function M.updated_work() +function M.update_work() local work = M.current_work_item or "Put in the description of the work you want to complete" Window.capture_input(" Work ", { @@ -67,7 +83,7 @@ function M.set_work(opts) else Window.capture_input(" Work ", { cb = set_work_item_cb, - content = { "Put in the description of the work you want to complete" }, + content = { M.current_work_item or "Put in the description of the work you want to complete" }, }) end @@ -76,7 +92,7 @@ function M.set_work(opts) end --- craft_prompt can be overridden so you can create your own prompt ---- @param worker _99.Extension.Worker +--- @param worker _99.Extensions.Worker --- @return string function M.craft_prompt(worker) return string.format( @@ -136,7 +152,7 @@ function M.last_search_results() return end - require("99").qfix_search_results(M.last_work_search) + require("99").qfix(M.last_work_search) end return M diff --git a/lua/99/init.lua b/lua/99/init.lua index 6e7e1e4..f0c7132 100644 --- a/lua/99/init.lua +++ b/lua/99/init.lua @@ -197,6 +197,8 @@ local _99_state --- be killed (OpenCode) and any result will be discared --- @field clear_previous_requests fun(): nil --- clears all previous search and visual operations +--- @field Extensions _99.Extensions +--- clears all previous search and visual operations local _99 = { DEBUG = Level.DEBUG, INFO = Level.INFO, @@ -311,6 +313,19 @@ function _99:rule_from_path(path) return Agents.get_rule_by_path(_99_state.rules, path) end +--- @param opts? _99.ops.Opts +--- @return _99.TraceID +function _99.vibe(opts) + local o = process_opts(opts) + local context = Prompt.vibe(_99_state) + if o.additional_prompt then + ops.vibe(context, o) + else + capture_prompt(ops.vibe, "Vibe", context, o) + end + return context.xid +end + --- @param opts? _99.ops.SearchOpts --- @return _99.TraceID function _99.search(opts) @@ -402,14 +417,13 @@ function _99.clear_all_marks() end --- @param xid number | nil -function _99.qfix_search_results(xid) +function _99.qfix(xid) --- @type _99.Prompt local entry = _99_state.__request_by_id[xid] assert(entry, "qfix_search_results could not find id: " .. xid) - local data = entry:search_data() - local items = data.qfix_items - vim.fn.setqflist({}, "r", { title = "99 Search Results", items = items }) + local items = entry:qfix_data() + vim.fn.setqflist({}, "r", { title = "99 Results", items = items }) vim.cmd("copen") end @@ -529,6 +543,9 @@ function _99.__debug() end _99.Providers = Providers + +--- @class _99.Extensions +--- @field Worker _99.Extensions.Worker _99.Extensions = { Worker = require("99.extensions.work.worker"), } diff --git a/lua/99/ops/init.lua b/lua/99/ops/init.lua index e4596c2..d45b1a5 100644 --- a/lua/99/ops/init.lua +++ b/lua/99/ops/init.lua @@ -35,4 +35,5 @@ return { search = require("99.ops.search"), tutorial = require("99.ops.tutorial"), over_range = require("99.ops.over-range"), + vibe = require("99.ops.vibe") } diff --git a/lua/99/ops/qfix-helpers.lua b/lua/99/ops/qfix-helpers.lua new file mode 100644 index 0000000..8d7ee63 --- /dev/null +++ b/lua/99/ops/qfix-helpers.lua @@ -0,0 +1,43 @@ +local M = {} + +--- @return _99.Search.Result | nil +function M.parse_line(line) + local parts = vim.split(line, ":", { plain = true }) + if #parts ~= 3 then + return nil + end + + local filepath = parts[1] + local lnum = parts[2] + local comma_parts = vim.split(parts[3], ",", { plain = true }) + local col = comma_parts[1] + local notes = nil + + if #comma_parts >= 2 then + notes = table.concat(comma_parts, ",", 2) + end + + return { + filename = filepath, + lnum = tonumber(lnum) or 1, + col = tonumber(col) or 1, + text = notes or "", + } +end + +--- @param response string +--- @return _99.Search.Result[] +function M.create_qfix_entries(response) + local lines = vim.split(response, "\n") + local qf_list = {} --[[ @as _99.Search.Result[] ]] + + for _, line in ipairs(lines) do + local res = M.parse_line(line) + if res then + table.insert(qf_list, res) + end + end + return qf_list +end + +return M diff --git a/lua/99/ops/search.lua b/lua/99/ops/search.lua index dda70d1..0619f47 100644 --- a/lua/99/ops/search.lua +++ b/lua/99/ops/search.lua @@ -1,59 +1,23 @@ local make_prompt = require("99.ops.make-prompt") local CleanUp = require("99.ops.clean-up") +local QFixHelpers = require("99.ops.qfix-helpers") local make_clean_up = CleanUp.make_clean_up local make_observer = CleanUp.make_observer ---- @class _99.Search.Result ---- @field filename string ---- @field lnum number ---- @field col number ---- @field text string - ---- @return _99.Search.Result | nil -local function parse_line(line) - local parts = vim.split(line, ":", { plain = true }) - if #parts ~= 3 then - return nil - end - - local filepath = parts[1] - local lnum = parts[2] - local comma_parts = vim.split(parts[3], ",", { plain = true }) - local col = comma_parts[1] - local notes = nil - - if #comma_parts >= 2 then - notes = table.concat(comma_parts, ",", 2) - end - - return { - filename = filepath, - lnum = tonumber(lnum) or 1, - col = tonumber(col) or 1, - text = notes or "", - } -end - --- @param context _99.Prompt --- @param response string local function create_search_locations(context, response) - local lines = vim.split(response, "\n") - local qf_list = {} - - for _, line in ipairs(lines) do - local res = parse_line(line) - if res then - table.insert(qf_list, res) - end - end + local qf_list = QFixHelpers.create_qfix_entries(response) + context.logger:set_area("search"):debug("qf_list created", "qf_list", qf_list) context.data = { type = "search", qfix_items = qf_list, + response = response, } if #qf_list > 0 then - require("99").qfix_search_results(context.xid) + require("99").qfix(context.xid) else vim.notify("No search results found", vim.log.levels.INFO) end diff --git a/lua/99/ops/vibe.lua b/lua/99/ops/vibe.lua new file mode 100644 index 0000000..291fc45 --- /dev/null +++ b/lua/99/ops/vibe.lua @@ -0,0 +1,52 @@ +local make_prompt = require("99.ops.make-prompt") +local CleanUp = require("99.ops.clean-up") + +local make_clean_up = CleanUp.make_clean_up +local make_observer = CleanUp.make_observer + +--- @class _99.Search.Result +--- @field filename string +--- @field lnum number +--- @field col number +--- @field text string + +--- @param context _99.Prompt +---@param opts _99.ops.SearchOpts +local function search(context, opts) + opts = opts or {} + + local logger = context.logger:set_area("search") + logger:debug("search", "with opts", opts.additional_prompt) + + local clean_up = make_clean_up(function() + context:stop() + end) + + local prompt, refs = + make_prompt(context, context._99.prompts.prompts.semantic_search(), opts) + + context:add_prompt_content(prompt) + context:add_references(refs) + context:add_clean_up(clean_up) + + --- TODO: part of the context request clean up there needs to be a refactoring of + --- make observer... it really should just be within the context observer creation. + --- same with cleanup.. that should just be clean_ups from context, instead of a + --- once cleanup function wrapper. + --- + --- i think an interface, CleanUpI could be something that is worth it :) + context:start_request(make_observer(clean_up, function(status, response) + if status == "cancelled" then + logger:debug("request cancelled for search") + elseif status == "failed" then + logger:error( + "request failed for search", + "error response", + response or "no response provided" + ) + elseif status == "success" then + create_search_locations(context, response) + end + end)) +end +return search diff --git a/lua/99/prompt.lua b/lua/99/prompt.lua index ec1d0ae..840b5ee 100644 --- a/lua/99/prompt.lua +++ b/lua/99/prompt.lua @@ -21,8 +21,8 @@ local filetype_map = { } -- luacheck: ignore ---- @alias _99.Prompt.Data _99.Prompt.Data.Search | _99.Prompt.Data.Tutorial | _99.Prompt.Data.Visual ---- @alias _99.Prompt.Operation "visual" | "tutorial" | "search" +--- @alias _99.Prompt.Data _99.Prompt.Data.Search | _99.Prompt.Data.Tutorial | _99.Prompt.Data.Visual | _99.Prompt.Data.Vibe +--- @alias _99.Prompt.Operation "visual" | "tutorial" | "search" | "vibe" --- @alias _99.Prompt.EndingState "failed" | "success" | "cancelled" --- @alias _99.Prompt.State "ready" | "requesting" | _99.Prompt.EndingState --- @alias _99.Prompt.Cleanup fun(): nil @@ -30,6 +30,12 @@ local filetype_map = { --- @class _99.Prompt.Data.Search --- @field type "search" --- @field qfix_items _99.Search.Result[] +--- @field response string + +--- @class _99.Prompt.Data.Vibe +--- @field type "vibe" +--- @field response string +--- @field xfix_items _99.Search.Result[] --- @class _99.Prompt.Data.Visual --- @field type "visual" @@ -96,10 +102,23 @@ function Prompt.todo(_99) assert(false, "not implemented") end -function Prompt.vibe(_99, opts) - _ = _99 - _ = opts - assert(false, "not implemented") +--- @param _99 _99.State +--- @return _99.Prompt +function Prompt.vibe(_99) + _99:refresh_rules() + + --- @type _99.Prompt + local context = setmetatable({}, Prompt) + set_defaults(context, _99) + context.operation = "vibe" + context.data = { + type = "vibe", + response = "", + qfix_items = {}, + } + context.logger:debug("99 Request", "method", "vibe") + + return context end --- @param _99 _99.State @@ -289,6 +308,16 @@ function Prompt:search_data() return self.data --[[@as _99.Prompt.Data.Search]] end +--- @return _99.Search.Result[] +function Prompt:qfix_data() + assert( + self.data.type == "search" or self.data.type == "vibe", + "data type is not search or vibe: " .. self.data.type + ) + return self.data.xfix_items +end + + function Prompt:stop() self:cancel() for _, cb in ipairs(self.clean_ups) do diff --git a/lua/99/providers.lua b/lua/99/providers.lua index 24c3d28..5eab070 100644 --- a/lua/99/providers.lua +++ b/lua/99/providers.lua @@ -20,6 +20,7 @@ end --- @class _99.Providers.BaseProvider --- @field _build_command fun(self: _99.Providers.BaseProvider, query: string, context: _99.Prompt): string[] --- @field _get_provider_name fun(self: _99.Providers.BaseProvider): string +--- @field _get_default_model fun(): string local BaseProvider = {} --- @param callback fun(models: string[]|nil, err: string|nil): nil diff --git a/lua/99/test/qfix_helpers_spec.lua b/lua/99/test/qfix_helpers_spec.lua new file mode 100644 index 0000000..ad9e5ab --- /dev/null +++ b/lua/99/test/qfix_helpers_spec.lua @@ -0,0 +1,86 @@ +-- luacheck: globals describe it assert +local QFixHelpers = require("99.ops.qfix-helpers") +local eq = assert.are.same +local create_entries = QFixHelpers.create_qfix_entries + +describe("qfix helpers", function() + it("parse_line parses filename, line, column, and notes", function() + local parsed = + QFixHelpers.parse_line("lua/99/ops/search.lua:42:7,found semantic search") + + eq({ + filename = "lua/99/ops/search.lua", + lnum = 42, + col = 7, + text = "found semantic search", + }, parsed) + end) + + it("parse_line keeps commas in notes text", function() + local parsed = QFixHelpers.parse_line("file.lua:10:3,note,with,commas") + + eq("file.lua", parsed.filename) + eq(10, parsed.lnum) + eq(3, parsed.col) + eq("note,with,commas", parsed.text) + end) + + it("parse_line returns nil for malformed lines", function() + eq(nil, QFixHelpers.parse_line("file.lua:10")) + eq(nil, QFixHelpers.parse_line("file.lua:10:3:extra")) + end) + + it( + "parse_line uses defaults for invalid numbers and missing notes", + function() + local parsed = QFixHelpers.parse_line("file.lua:notnum:notcol") + + eq({ + filename = "file.lua", + lnum = 1, + col = 1, + text = "", + }, parsed) + end + ) + + it( + "create_qfix_entries parses valid lines and skips malformed ones", + function() + local response = table.concat({ + "a.lua:1:2,first hit", + "not a valid line", + "b.lua:3:4", + "c.lua:notnum:notcol,fallback values", + "", + }, "\n") + + local locations = create_entries(response) + + eq({ + { + filename = "a.lua", + lnum = 1, + col = 2, + text = "first hit", + }, + { + filename = "b.lua", + lnum = 3, + col = 4, + text = "", + }, + { + filename = "c.lua", + lnum = 1, + col = 1, + text = "fallback values", + }, + }, locations) + end + ) + + it("create_qfix_entries returns empty list for empty response", function() + eq({}, create_entries("")) + end) +end) |
