summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorThePrimeAgain <theprimeagain@theprimeagain.com>2025-11-26 09:21:53 -0700
committerThePrimeAgain <theprimeagain@theprimeagain.com>2025-11-26 09:21:53 -0700
commit82b05b232f6a8a2f26432a68759633783efb05ab (patch)
tree3644a2dba6be9a2832880d076199d7553eac91dd /lua
parentf901b32322e22f4cd2bf3c01896cbbcb37fecea8 (diff)
downloada4-82b05b232f6a8a2f26432a68759633783efb05ab.tar.xz
a4-82b05b232f6a8a2f26432a68759633783efb05ab.zip
fill in the function
Diffstat (limited to 'lua')
-rw-r--r--lua/99/init.lua198
-rw-r--r--lua/99/logger/logger.lua6
-rw-r--r--lua/99/ops/fill-in-function.lua27
-rw-r--r--lua/99/ops/init.lua4
-rw-r--r--lua/99/ops/marks.lua19
-rw-r--r--lua/99/request/init.lua135
-rw-r--r--lua/99/request/system-rules.lua40
7 files changed, 270 insertions, 159 deletions
diff --git a/lua/99/init.lua b/lua/99/init.lua
index b96f0bc..a3c1f2c 100644
--- a/lua/99/init.lua
+++ b/lua/99/init.lua
@@ -1,10 +1,6 @@
local Logger = require("99.logger.logger")
local Level = require("99.logger.level")
-local editor = require("99.editor")
-local geo = require("99.geo")
-local Point = geo.Point
-
-local marks_to_use = "yuiophjkl"
+local ops = require("99.ops")
--- @class LoggerOptions
--- @field level number?
@@ -14,166 +10,30 @@ local marks_to_use = "yuiophjkl"
--- @field logger LoggerOptions?
--- @field model string?
---- @alias _99ChangeRequestState "ready" | "calling-model" | "parsing-result" | "updating-file"
-
---- @class _99ChangeRequest
---- @field query string
---- @field tmp_name string
---- @field state _99ChangeRequestState
---- @field buffer number
---- @field model string
---- @field id string
---- @field mark string
---- @field range Range
-
--- unanswered question -- will i need to queue messages one at a time or
--- just send them all... So to prepare ill be sending around this state object
---- @class _99State
+--- @class _99.State
--- @field model string
---- @field mark_index number
-
-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",
-}
+--- @field md_files string[]
---- @type _99State
+--- @type _99.State
local _99_state = {
model = "anthropic/claude-sonnet-4-5",
- mark_index = 0,
+ md_files = {},
}
---- no, i am not going to use a uuid, in case of collision, call the police
---- @return string
-local function get_id()
- return tostring(math.floor(math.random() * 100000000))
-end
-
---- 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
-
---- @param tmp_file string
-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
-
---- @param buffer number
----@param range Range
----@return string
-local function get_file_location(buffer, range)
- local full_path = vim.fn.expand("%:p")
- return string.format("<Location><File>%s</File><Function>%s</Function></Location>", full_path, range:to_string())
-end
-
---- @param range Range
-local function get_range_text(range)
- return string.format("<FunctionText>%s</FunctionText>", range:to_text())
-end
-
--- @class _99
-local _99 = {}
-
---- @param change_request _99ChangeRequest
-function _99.mark_function(change_request)
- local range = change_request.range
- local start_row, start_col = range.start:to_vim()
-
- local mark = marks_to_use:sub(_99_state.mark_index + 1, _99_state.mark_index + 1)
- vim.api.nvim_buf_set_mark(change_request.buffer, mark, start_row + 1, start_col, {})
-
- _99_state.mark_index = (_99_state.mark_index + 1) % #marks_to_use
-
- change_request.mark = mark
-end
-
---- @param change_request _99ChangeRequest
-function _99.make_query(change_request)
- Logger:debug("99#make_query", "id", change_request.id, "query", change_request.query)
- vim.system({ "opencode", "run", "-m", "anthropic/claude-sonnet-4-5", change_request.query }, {
- text = true,
- stdout = vim.schedule_wrap(function(err, data)
- Logger:debug("STDOUT#data", "id", change_request.id, "data", data)
- Logger:debug("STDOUT#error", "id", change_request.id, "err", err)
- end),
- stderr = vim.schedule_wrap(function(err, data)
- Logger:debug("STDERR#data", "id", change_request.id, "data", data)
- Logger:debug("STDERR#error", "id", change_request.id, "err", err)
- end),
- }, function(obj)
- if obj.code ~= 0 then
- Logger:fatal("opencode make_query failed", "change_request", change_request, "obj from results", obj)
- return
- end
- local ok, res = _99.retrieve_results(change_request)
- if ok then
- _99.update_file_with_changes(change_request, res)
- end
- end)
-end
-
---- @param change_request _99ChangeRequest
---- @return boolean, string
-function _99.retrieve_results(change_request)
- local success, result = pcall(function()
- return vim.fn.readfile(change_request.tmp_name)
- end)
-
- if not success then
- Logger:error("retrieve_results: failed to read file", "tmp_name", change_request.tmp_name, "error", result)
- return false, ""
- end
-
- return true, table.concat(result, "\n")
-end
-
---- @param change_request _99ChangeRequest
---- @param res string
-function _99.update_file_with_changes(change_request, res)
- --- NOTE use treesitter to get the range of the function
- --- NOTE remove the previous contents of function
- --- NOTE replace with results stored in res
-end
+local _99 = {
+ DEBUG = Level.DEBUG,
+ INFO = Level.INFO,
+ WARN = Level.WARN,
+ ERROR = Level.ERROR,
+ FATAL = Level.FATAL,
+}
function _99.fill_in_function()
- local ts = editor.treesitter
- local cursor = Point:from_cursor()
- local scopes = ts.function_scopes(cursor)
- local buffer = vim.api.nvim_get_current_buf()
-
- if scopes == nil or #scopes.range == 0 then
- Logger:warn("fill_in_function: unable to find any containing function")
- error("you cannot call fill_in_function not in a function")
- end
-
- local tmp_file = random_file()
- local range = scopes.range[#scopes.range]
-
- --- @type _99ChangeRequest
- local change_request = {
- range = range,
- buffer = buffer,
- tmp_name = tmp_file,
- state = "ready",
- query = table.concat({
- system_rules(tmp_file),
- get_file_location(buffer, range),
- get_range_text(range),
- }),
- model = _99_state.model,
- id = get_id(),
- mark = "",
- }
- _99.make_query(change_request)
+ local fif = ops.fill_in_function(_99_state)
+ fif:start()
end
--- @param opts _99Options?
@@ -184,23 +44,43 @@ function _99.setup(opts)
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
+
if opts.model then
_99_state.model = opts.model
end
end
+--- @param md string
+--- @return _99
+function _99.add_md_file(md)
+ table.insert(_99_state.md_files, md)
+ 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
+end
+
--- @param model string
+--- @return _99
function _99.set_model(model)
_99_state.model = model
+ return _99
end
-Logger:set_level(Level.DEBUG)
-Logger:file_sink("/tmp/99.debug")
-
-_99.fill_in_function()
-
return _99
diff --git a/lua/99/logger/logger.lua b/lua/99/logger/logger.lua
index 738e93c..b761de6 100644
--- a/lua/99/logger/logger.lua
+++ b/lua/99/logger/logger.lua
@@ -99,6 +99,12 @@ function Logger:file_sink(path)
return self
end
+--- @return Logger
+function Logger:print_sink()
+ self.sink = PrintSink:new()
+ return self
+end
+
--- @param level number
--- @return Logger
function Logger:set_level(level)
diff --git a/lua/99/ops/fill-in-function.lua b/lua/99/ops/fill-in-function.lua
new file mode 100644
index 0000000..45a89b1
--- /dev/null
+++ b/lua/99/ops/fill-in-function.lua
@@ -0,0 +1,27 @@
+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")
+
+--- @param _99 _99.State
+--- @return _99.Request
+local function fill_in_function(_99)
+ local request = Request.new({
+ model = _99.model,
+ md_files = _99.md_files,
+ })
+
+ if !request:has_scopes() then
+ Logger:warn("fill_in_function: unable to find any containing function")
+ error("you cannot call fill_in_function not in a function")
+ end
+
+ local range = request:get_inner_scope()
+
+ request:set_system_prompt(system_rules(request))
+ request.mark = marks(request.buffer, range)
+
+ return request
+end
+
+return fill_in_function
diff --git a/lua/99/ops/init.lua b/lua/99/ops/init.lua
new file mode 100644
index 0000000..143ed04
--- /dev/null
+++ b/lua/99/ops/init.lua
@@ -0,0 +1,4 @@
+return {
+ fill_in_function = require("99.ops.fill-in-function"),
+}
+
diff --git a/lua/99/ops/marks.lua b/lua/99/ops/marks.lua
new file mode 100644
index 0000000..f56211d
--- /dev/null
+++ b/lua/99/ops/marks.lua
@@ -0,0 +1,19 @@
+local marks_to_use = "yuiophjklnm"
+local mark_index = 0
+
+--- @param buffer number
+---@param range Range
+---@return string
+local function mark_function(buffer, range)
+ local start_row, start_col = range.start:to_vim()
+ local idx = (mark_index + 1) % #marks_to_use
+ local mark = marks_to_use:sub(idx, idx)
+
+ vim.api.nvim_buf_set_mark(buffer, mark, start_row + 1, start_col, {})
+
+ mark_index = idx
+ return mark
+end
+
+return mark_function
+
diff --git a/lua/99/request/init.lua b/lua/99/request/init.lua
new file mode 100644
index 0000000..d020caa
--- /dev/null
+++ b/lua/99/request/init.lua
@@ -0,0 +1,135 @@
+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
+local function get_id()
+ return tostring(math.floor(math.random() * 100000000))
+end
+
+--- @alias _99.Request.State "ready" | "calling-model" | "parsing-result" | "updating-file"
+
+--- @class _99.Request.Opts
+--- @field model string
+--- @field md_files string[]
+
+--- @class _99.Request
+--- @field query string
+--- @field tmp_name string
+--- @field state _99.Request.State
+--- @field buffer number
+--- @field model string
+--- @field id string
+--- @field mark string
+--- @field scopes Scope
+--- @field system_prompt 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()
+
+ return setmetatable({
+ cursor = cursor,
+ scopes = scopes,
+ buffer = buffer,
+ model = opts.model,
+ tmp_name = random_file(),
+ id = get_id(),
+ state = "ready",
+ mark = "",
+ }, Request)
+end
+
+function Request:has_scopes()
+ return self.scopes ~= nil and #self.scopes > 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 success, result = pcall(function()
+ return vim.fn.readfile(self.tmp_name)
+ end)
+
+ if not success then
+ Logger:error("retrieve_results: failed to read file", "tmp_name", self.tmp_name, "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)
+
+ if not scopes or not scopes:has_scope() then
+ Logger:error("update_file_with_changes: unable 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)
+end
+
+function Request:start()
+ Logger:debug("99#make_query", "id", self.id, "query", self.query)
+ vim.system({ "opencode", "run", "-m", "anthropic/claude-sonnet-4-5", self.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)
+ 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)
+ end),
+ }, function(obj)
+ if obj.code ~= 0 then
+ Logger:fatal("opencode make_query failed", "request", self, "obj from results", obj)
+ return
+ end
+ vim.schedule(function()
+ local ok, res = self:_retrieve_response()
+ if ok then
+ self:_update_file_with_changes(res)
+ end
+ end)
+ end)
+end
+
+return Request
diff --git a/lua/99/request/system-rules.lua b/lua/99/request/system-rules.lua
new file mode 100644
index 0000000..b1eaf7b
--- /dev/null
+++ b/lua/99/request/system-rules.lua
@@ -0,0 +1,40 @@
+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",
+}
+
+--- @param tmp_file string
+--- @return string
+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
+
+--- @param buffer number
+---@param range Range
+---@return string
+local function get_file_location(buffer, range)
+ local full_path = vim.fn.expand("%:p")
+ return string.format("<Location><File>%s</File><Function>%s</Function></Location>", full_path, range:to_string())
+end
+
+--- @param range Range
+local function get_range_text(range)
+ return string.format("<FunctionText>%s</FunctionText>", range:to_text())
+end
+
+--- @param request _99.Request
+--- @return string
+return function(request)
+ local range = request:get_inner_scope()
+ local buffer = request.buffer
+ return table.concat({
+ system_rules(request.tmp_name),
+ get_file_location(buffer, range),
+ get_range_text(range),
+ })
+end