diff options
| author | ThePrimeAgain <theprimeagain@theprimeagain.com> | 2025-12-01 08:35:01 -0700 |
|---|---|---|
| committer | ThePrimeAgain <theprimeagain@theprimeagain.com> | 2025-12-01 08:35:01 -0700 |
| commit | 0043d77b69d36cb3b7b9ba9db8f36741d27596e2 (patch) | |
| tree | 90b844d9086aa20bbc18bf3b1577c9b72b2c5e32 | |
| parent | b503987d9421c2bf29c13bf40e89c4b113134cae (diff) | |
| download | a4-0043d77b69d36cb3b7b9ba9db8f36741d27596e2.tar.xz a4-0043d77b69d36cb3b7b9ba9db8f36741d27596e2.zip | |
working through a super refactor while adding implement function
| -rw-r--r-- | lua/99/editor/init.lua | 1 | ||||
| -rw-r--r-- | lua/99/editor/location.lua | 25 | ||||
| -rw-r--r-- | lua/99/editor/treesitter.lua | 53 | ||||
| -rw-r--r-- | lua/99/init.lua | 55 | ||||
| -rw-r--r-- | lua/99/logger/logger.lua | 39 | ||||
| -rw-r--r-- | lua/99/ops/context.lua | 45 | ||||
| -rw-r--r-- | lua/99/ops/fill-in-function.lua | 41 | ||||
| -rw-r--r-- | lua/99/ops/implement-fn.lua | 38 | ||||
| -rw-r--r-- | lua/99/ops/init.lua | 2 | ||||
| -rw-r--r-- | lua/99/ops/prompts.lua | 0 | ||||
| -rw-r--r-- | lua/99/ops/system-rules.lua (renamed from lua/99/request/system-rules.lua) | 2 | ||||
| -rw-r--r-- | lua/99/prompt_settings.lua | 34 | ||||
| -rw-r--r-- | lua/99/request/init.lua | 117 | ||||
| -rw-r--r-- | queries/lua/99-identifier.scm | 7 | ||||
| -rw-r--r-- | scratch/refresh.lua | 2 |
15 files changed, 337 insertions, 124 deletions
diff --git a/lua/99/editor/init.lua b/lua/99/editor/init.lua index 70fb662..da4ecb9 100644 --- a/lua/99/editor/init.lua +++ b/lua/99/editor/init.lua @@ -1,4 +1,5 @@ return { treesitter = require("99.editor.treesitter"), lsp = require("99.editor.lsp"), + Location = require("99.editor.location"), } diff --git a/lua/99/editor/location.lua b/lua/99/editor/location.lua new file mode 100644 index 0000000..2da56b8 --- /dev/null +++ b/lua/99/editor/location.lua @@ -0,0 +1,25 @@ +local Point = require("99.geo") + +--- @class _99.Location +--- @field full_path string +--- @field range Range +--- @field buffer number +--- @field marks table<string, string> +--- @field cursor Point +local Location = {} +Location.__index = Location + +function Location.from_range(range) + local full_path = vim.api.nvim_buf_get_name(range.buffer) + local cursor = Point:from_cursor() + + return setmetatable({ + cursor = cursor, + buffer = range.buffer, + full_path = full_path, + range = range, + marks = {} + }, Location) +end + +return Location diff --git a/lua/99/editor/treesitter.lua b/lua/99/editor/treesitter.lua index 9479d2c..dff7a3d 100644 --- a/lua/99/editor/treesitter.lua +++ b/lua/99/editor/treesitter.lua @@ -13,11 +13,11 @@ local M = {} local function_query = "99-function" local imports_query = "99-imports" +local identifier_query = "99-identifier" --- @param buffer number ---@param lang string local function tree_root(buffer, lang) - -- Load the parser and the query. local ok, parser = pcall(vim.treesitter.get_parser, buffer, lang) if not ok then @@ -28,6 +28,53 @@ local function tree_root(buffer, lang) return tree:root() end +--- @param buffer number +--- @param cursor Point +--- @return TSNode | nil +function M.identifier(buffer, cursor) + local lang = vim.bo[buffer].ft + local root = tree_root(buffer, lang) + if not root then + Logger:error("unable to find treeroot, this should never happen", "buffer", buffer, "lang", lang) + return nil + end + + local ok, query = pcall(vim.treesitter.query.get, lang, identifier_query) + if not ok or query == nil then + Logger:error( + "unable to get the identifier_query", + "lang", + lang, + "buffer", + buffer, + "ok", + type(ok), + "query", + type(query) + ) + return nil + end + + --- likely something that needs to be done with treesitter#get_node + local found = nil + for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do + for _, nodes in pairs(match) do + for _, node in ipairs(nodes) do + local range = Range:from_ts_node(node, buffer) + if range:contains(cursor) then + found = node + goto end_of_loops + end + end + end + end + ::end_of_loops:: + + Logger:debug("treesitter#identifier", "found", found) + + return found +end + --- @class Scope --- @field scope TSNode[] --- @field range Range[] @@ -75,7 +122,7 @@ end --- @param buffer number? --- @return Scope | nil function M.function_scopes(cursor, buffer) - buffer = buffer or vim.api.nvim_get_current_buf() + buffer = buffer or vim.api.nvim_get_current_buf() local lang = vim.bo[buffer].ft local root = tree_root(buffer, lang) @@ -90,7 +137,7 @@ function M.function_scopes(cursor, buffer) return nil end - print("function_scopes", buffer) + print("function_scopes", buffer) local scope = Scope:new(cursor, buffer) for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do for _, nodes in pairs(match) do diff --git a/lua/99/init.lua b/lua/99/init.lua index a3c1f2c..f336f09 100644 --- a/lua/99/init.lua +++ b/lua/99/init.lua @@ -2,12 +2,8 @@ local Logger = require("99.logger.logger") local Level = require("99.logger.level") local ops = require("99.ops") ---- @class LoggerOptions ---- @field level number? ---- @field path string? - --- @class _99Options ---- @field logger LoggerOptions? +--- @field logger _99.Logger.Options? --- @field model string? --- unanswered question -- will i need to queue messages one at a time or @@ -15,42 +11,39 @@ local ops = require("99.ops") --- @class _99.State --- @field model string --- @field md_files string[] +--- @field prompts _99.Prompts --- @type _99.State local _99_state = { model = "anthropic/claude-sonnet-4-5", md_files = {}, + prompts = require("99.prompt_settings"), } --- @class _99 local _99 = { - DEBUG = Level.DEBUG, - INFO = Level.INFO, - WARN = Level.WARN, - ERROR = Level.ERROR, - FATAL = Level.FATAL, + DEBUG = Level.DEBUG, + INFO = Level.INFO, + WARN = Level.WARN, + ERROR = Level.ERROR, + FATAL = Level.FATAL, } +function _99.implement_fn() + local impl = ops.implement_fn(_99_state) + impl:start() +end + function _99.fill_in_function() local fif = ops.fill_in_function(_99_state) - fif:start() + fif:start() end --- @param opts _99Options? function _99.setup(opts) opts = opts or {} - local logger = opts.logger - if logger then - if logger.level then - Logger:set_level(logger.level) - end - if logger.path then - Logger:file_sink(logger.path) - else - Logger:print_sink() - end - end + Logger:configure(opts.logger) if opts.model then _99_state.model = opts.model @@ -61,26 +54,26 @@ end --- @return _99 function _99.add_md_file(md) table.insert(_99_state.md_files, md) - return _99 + return _99 end --- @param md string --- @return _99 function _99.rm_md_file(md) - for i, name in ipairs(_99_state.md_files) do - if name == md then - table.remove(_99_state.md_files, i) - break - end - end - return _99 + for i, name in ipairs(_99_state.md_files) do + if name == md then + table.remove(_99_state.md_files, i) + break + end + end + return _99 end --- @param model string --- @return _99 function _99.set_model(model) _99_state.model = model - return _99 + return _99 end return _99 diff --git a/lua/99/logger/logger.lua b/lua/99/logger/logger.lua index b761de6..8f03092 100644 --- a/lua/99/logger/logger.lua +++ b/lua/99/logger/logger.lua @@ -80,6 +80,7 @@ end --- @class Logger --- @field level number --- @field sink LoggerSink +--- @field print_on_error boolean local Logger = {} Logger.__index = Logger @@ -89,6 +90,7 @@ function Logger:new(level) return setmetatable({ sink = PrintSink:new(), level = level, + print_on_error = false, }, self) end @@ -112,6 +114,38 @@ function Logger:set_level(level) return self end +--- @return Logger +function Logger:on_error_print_message() + self.print_on_error = true + return self +end + +--- @class _99.Logger.Options +--- @field level number? +--- @field path string? +--- @field print_on_error? boolean + +--- @param opts _99.Logger.Options? +function Logger:configure(opts) + if not opts then + return + end + + if opts.level then + Logger:set_level(opts.level) + end + + if opts.path then + Logger:file_sink(opts.path) + else + Logger:print_sink() + end + + if opts.print_on_error then + Logger:on_error_print_message() + end +end + function Logger:_log(level, msg, ...) if self.level > level then return @@ -119,6 +153,9 @@ function Logger:_log(level, msg, ...) local args = stringifyArgs(...) local line = string.format("[%s]: %s %s", levels.levelToString(level), msg, args) + if self.print_on_error and level == levels.ERROR then + print(line) + end self.sink:write_line(line) end @@ -150,7 +187,7 @@ end --- @param ... any function Logger:fatal(msg, ...) self:_log(levels.FATAL, msg, ...) - assert(false, "fatal msg recieved") + assert(false, "fatal msg recieved: " .. msg) end local module_logger = Logger:new(levels.DEBUG) diff --git a/lua/99/ops/context.lua b/lua/99/ops/context.lua new file mode 100644 index 0000000..d88a170 --- /dev/null +++ b/lua/99/ops/context.lua @@ -0,0 +1,45 @@ +--- TODO: some people change their current working directory as they open new +--- directories. if this is still the case in neovim land, then we will need +--- to make the _99_state have the project directory. +--- @return string +local function random_file() + return string.format("%s/tmp/99-%d", vim.uv.cwd(), math.floor(math.random() * 10000)) +end + +--- @class _99.Context +--- @field md_file_names string[] +--- @field ai_context string[] +--- @field tmp_file string +local Context = {} +Context.__index = Context + +function Context.new() + return setmetatable({ + md_file_names = {}, + ai_context = {}, + tmp_file = random_file(), + }, Context) +end + +--- @param md_file_name string +--- @return self +function Context:add_md_file_name(md_file_name) + table.insert(self.md_file_names, md_file_name) + return self +end + +--- @param location _99.Location +function Context:finalize(location) + --- @ai use location's buffer's full path and walk back until we are at cwd + --- @ai and read each of the md_file_names. if it exists then add it to + --- @ai ai_context. +end + +--- @param request _99.Request +function Context:add_to_request(request) + for _, context in ipairs(self.ai_context) do + request:add_prompt_content(context) + end +end + +return Context diff --git a/lua/99/ops/fill-in-function.lua b/lua/99/ops/fill-in-function.lua index 3a8edb2..aebb444 100644 --- a/lua/99/ops/fill-in-function.lua +++ b/lua/99/ops/fill-in-function.lua @@ -1,14 +1,53 @@ +local Point = require("99.geo").Point local Logger = require("99.logger.logger") local Request = require("99.request") local system_rules = require("99.request.system-rules") local marks = require("99.ops.marks") +local editor = require("99.editor") +local ops = require("99.ops") + +--- @param request _99.Request +--- @param ok boolean +--- @param res string +local function update_file_with_changes(request, ok, res) + if not ok then + error("unable to fill in function, enable and check logger for more details") + end + local mark_pos = vim.api.nvim_buf_get_mark(request.buffer, request.mark) + local mark_point = Point:new(mark_pos[1], mark_pos[2] + 1) + + local ts = editor.treesitter + local scopes = ts.function_scopes(mark_point, request.buffer) + print("update_file_with_changes buffer", request.buffer) + + if not scopes or not scopes:has_scope() then + Logger:error("update_file_with_changes: unable to find function at mark location") + error("update_file_with_changes: funable to find function at mark location") + return + end + + local range = scopes.range[#scopes.range] + + local function_start_row, _ = range.start:to_vim() + local function_end_row, _ = range.end_:to_vim() + + local lines = vim.split(res, "\n") + vim.api.nvim_buf_set_lines(request.buffer, function_start_row, function_end_row + 1, false, lines) +end + --- @param _99 _99.State --- @return _99.Request local function fill_in_function(_99) + local ts = editor.treesitter + local cursor = Point:from_cursor() + local scopes = ts.function_scopes(cursor) + local buffer = vim.api.nvim_get_current_buf() + + local location = editor.Location.from_range(range) local request = Request.new({ model = _99.model, - md_files = _99.md_files, + on_complete = update_file_with_changes, }) if not request:has_scopes() then diff --git a/lua/99/ops/implement-fn.lua b/lua/99/ops/implement-fn.lua new file mode 100644 index 0000000..a0e6e94 --- /dev/null +++ b/lua/99/ops/implement-fn.lua @@ -0,0 +1,38 @@ +local Logger = require("99.logger.logger") +local Request = require("99.request") +local system_rules = require("99.request.system-rules") +local marks = require("99.ops.marks") +local editor = require("99.editor") +local Range = require("99.geo").Range + +local function update_code(request, ok, res) + if not ok then + error("unable to implement function. check logger for more details") + end + Logger:fatal("not implemented yet") +end + +--- @param _99 _99.State +--- @return _99.Request? +local function implement_fn(_99) + local request = Request.new({ + model = _99.model, + md_files = _99.md_files, + on_complete = update_code, + }) + local ts = editor.treesitter + local ident = ts.identifier(request.buffer, request.cursor) + + print_ident(ident) + + if not ident then + Logger:error("implement_fn was called but cursor was not on a function call", "cursor", request.cursor) + return nil + end + + Range:from_ts_node(ident, request.buffer) + + return request +end + +return implement_fn diff --git a/lua/99/ops/init.lua b/lua/99/ops/init.lua index 143ed04..8b569bc 100644 --- a/lua/99/ops/init.lua +++ b/lua/99/ops/init.lua @@ -1,4 +1,6 @@ return { fill_in_function = require("99.ops.fill-in-function"), + implement_fn = require("99.ops.implement-fn"), + context = require("99.ops.context"), } diff --git a/lua/99/ops/prompts.lua b/lua/99/ops/prompts.lua new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lua/99/ops/prompts.lua diff --git a/lua/99/request/system-rules.lua b/lua/99/ops/system-rules.lua index b1eaf7b..c71828e 100644 --- a/lua/99/request/system-rules.lua +++ b/lua/99/ops/system-rules.lua @@ -1,5 +1,4 @@ local _99_settings = { - output_file = "you must NEVER alter the file given. You must provide the desired change to TEMP_FILE. Do NOT inspec the TEMP_FILE. It is for you to write into, never read. TEMP_FILE previous contents do not matter.", fill_in_function = "fill in the function. dont change the function signature. do not edit anything outside of this function. prioritize using internal functions for work that has already been done. any NOTE's left in the function should be removed but instructions followed", } @@ -9,7 +8,6 @@ local function system_rules(tmp_file) return string.format( "<MustObey>\n%s\n%s\n</MustObey><TEMP_FILE>%s</TEMP_FILE>", _99_settings.output_file, - _99_settings.fill_in_function, tmp_file ) end diff --git a/lua/99/prompt_settings.lua b/lua/99/prompt_settings.lua new file mode 100644 index 0000000..ffccb56 --- /dev/null +++ b/lua/99/prompt_settings.lua @@ -0,0 +1,34 @@ +--- @class _99.Prompts.SpecificOperations +local prompts = { + fill_in_function = "fill in the function. dont change the function signature. do not edit anything outside of this function. prioritize using internal functions for work that has already been done. any NOTE's left in the function should be removed but instructions followed", + implement_function = "implement the function that the cursor is on. make sure you inspect the current file carefully and any imports that look related. being thorough is better than being fast. being correct is better than being speedy.", +} + +--- @class _99.Prompts +local prompt_settings = { + prompts = prompts, + + --- @param output_file string + --- @param tmp_file string + --- @return string + tmp_file_location = function(output_file, tmp_file) + return string.format("<MustObey>\n%s\n%s\n</MustObey><TEMP_FILE>%s</TEMP_FILE>", output_file, tmp_file) + end, + + ---@param location _99.Location + ---@return string + get_file_location = function(location) + return string.format( + "<Location><File>%s</File><Function>%s</Function></Location>", + location.full_path, + location.range:to_string() + ) + end, + + --- @param range Range + get_range_text = function(range) + return string.format("<FunctionText>%s</FunctionText>", range:to_text()) + end, +} + +return prompt_settings diff --git a/lua/99/request/init.lua b/lua/99/request/init.lua index dd69b6c..a758912 100644 --- a/lua/99/request/init.lua +++ b/lua/99/request/init.lua @@ -1,15 +1,4 @@ local Logger = require("99.logger.logger") -local editor = require("99.editor") -local geo = require("99.geo") -local Point = geo.Point - ---- TODO: some people change their current working directory as they open new ---- directories. if this is still the case in neovim land, then we will need ---- to make the _99_state have the project directory. ---- @return string -local function random_file() - return string.format("%s/tmp/99-%d", vim.uv.cwd(), math.floor(math.random() * 10000)) -end --- no, i am not going to use a uuid, in case of collision, call the police --- @return string @@ -17,117 +6,76 @@ local function get_id() return tostring(math.floor(math.random() * 100000000)) end +--- @param opts _99.Request.Opts +local function validate_opts(opts) + assert(opts.model, "you must provide a model for hange requests to work") + assert(type(opts.on_complete) == "function", "on_complete must be provided") + assert(opts.context, "you must provide context") +end + --- @alias _99.Request.State "ready" | "calling-model" | "parsing-result" | "updating-file" --- @class _99.Request.Opts --- @field model string ---- @field md_files string[] +--- @field on_complete fun(req: _99.Request, success: boolean, res: string): nil +--- @field context _99.Context --- @class _99.Request ---- @field query string ---- @field tmp_name string ---- @field state _99.Request.State ---- @field buffer number ---- @field model string +--- @field config _99.Request.Opts --- @field id string ---- @field mark string ---- @field scopes Scope ---- @field system_prompt string +--- @field state _99.Request.State +--- @field _content string[] local Request = {} Request.__index = Request --- @param opts _99.Request.Opts function Request.new(opts) - assert(opts.model, "you must provide a model for hange requests to work") - - local ts = editor.treesitter - local cursor = Point:from_cursor() - local scopes = ts.function_scopes(cursor) - local buffer = vim.api.nvim_get_current_buf() - + validate_opts(opts) return setmetatable({ - cursor = cursor, - scopes = scopes, - buffer = buffer, - model = opts.model, - tmp_name = random_file(), + config = opts, id = get_id(), state = "ready", - mark = "", + _content = {}, }, Request) end -function Request:has_scopes() - return self.scopes ~= nil and #self.scopes.range > 0 -end - -function Request:get_inner_scope() - assert(self:has_scopes(), "you cannot get inner scope if you dont have scopes") - return self.scopes.range[#self.scopes.range] -end - ---- @param prompt string -function Request:set_system_prompt(prompt) - self.system_prompt = prompt -end - function Request:_retrieve_response() + local tmp = self.config.context.tmp_file local success, result = pcall(function() - return vim.fn.readfile(self.tmp_name) + return vim.fn.readfile(tmp) end) if not success then - Logger:error("retrieve_results: failed to read file", "tmp_name", self.tmp_name, "error", result) + Logger:error("retrieve_results: failed to read file", "tmp_name", tmp, "error", result) return false, "" end return true, table.concat(result, "\n") end ---- @param res string -function Request:_update_file_with_changes(res) - local mark_pos = vim.api.nvim_buf_get_mark(self.buffer, self.mark) - local mark_point = Point:new(mark_pos[1], mark_pos[2] + 1) - - local ts = editor.treesitter - local scopes = ts.function_scopes(mark_point, self.buffer) - print("update_file_with_changes buffer", self.buffer) - - if not scopes or not scopes:has_scope() then - Logger:error("update_file_with_changes: unable to find function at mark location") - error("update_file_with_changes: funable to find function at mark location") - return - end - - local range = scopes.range[#scopes.range] - - local function_start_row, _ = range.start:to_vim() - local function_end_row, _ = range.end_:to_vim() - - local lines = vim.split(res, "\n") - vim.api.nvim_buf_set_lines(self.buffer, function_start_row, function_end_row + 1, false, lines) - - local stuff = { - buffer = self.buffer, - f_start = function_start_row, - f_end = function_end_row, - res = res - } - print("setting lines in buffer", vim.inspect(stuff)) +--- @param content string +--- @return self +function Request:add_prompt_content(content) + table.insert(self._content, content) + return self end function Request:start() - local query = self.system_prompt + local query = table.concat(self._content, "\n") Logger:debug("99#make_query", "id", self.id, "query", query) vim.system({ "opencode", "run", "-m", "anthropic/claude-sonnet-4-5", query }, { text = true, stdout = vim.schedule_wrap(function(err, data) Logger:debug("STDOUT#data", "id", self.id, "data", data) - Logger:debug("STDOUT#error", "id", self.id, "err", err) + if err and err ~= "" then + Logger:debug("STDOUT#error", "id", self.id, "err", err) + end end), stderr = vim.schedule_wrap(function(err, data) Logger:debug("STDERR#data", "id", self.id, "data", data) - Logger:debug("STDERR#error", "id", self.id, "err", err) + if err and err ~= "" then + Logger:debug("STDERR#error", "id", self.id, "err", err) + end end), }, function(obj) if obj.code ~= 0 then @@ -136,10 +84,7 @@ function Request:start() end vim.schedule(function() local ok, res = self:_retrieve_response() - if ok then - self:_update_file_with_changes(res) - end - print("finished") + self.config.on_complete(self, ok, res) end) end) end diff --git a/queries/lua/99-identifier.scm b/queries/lua/99-identifier.scm new file mode 100644 index 0000000..3113fa2 --- /dev/null +++ b/queries/lua/99-identifier.scm @@ -0,0 +1,7 @@ +( + (function_call + name: (identifier) @call.name + arguments: (arguments) @call.args + ) @call.node +) + diff --git a/scratch/refresh.lua b/scratch/refresh.lua index 42e7b36..db78ea5 100644 --- a/scratch/refresh.lua +++ b/scratch/refresh.lua @@ -1,2 +1,4 @@ function return_42() + local number = pick_a_number_that_is_42() + return number end |
