summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThePrimeAgain <theprimeagain@theprimeagain.com>2025-12-01 08:35:01 -0700
committerThePrimeAgain <theprimeagain@theprimeagain.com>2025-12-01 08:35:01 -0700
commit0043d77b69d36cb3b7b9ba9db8f36741d27596e2 (patch)
tree90b844d9086aa20bbc18bf3b1577c9b72b2c5e32
parentb503987d9421c2bf29c13bf40e89c4b113134cae (diff)
downloada4-0043d77b69d36cb3b7b9ba9db8f36741d27596e2.tar.xz
a4-0043d77b69d36cb3b7b9ba9db8f36741d27596e2.zip
working through a super refactor while adding implement function
-rw-r--r--lua/99/editor/init.lua1
-rw-r--r--lua/99/editor/location.lua25
-rw-r--r--lua/99/editor/treesitter.lua53
-rw-r--r--lua/99/init.lua55
-rw-r--r--lua/99/logger/logger.lua39
-rw-r--r--lua/99/ops/context.lua45
-rw-r--r--lua/99/ops/fill-in-function.lua41
-rw-r--r--lua/99/ops/implement-fn.lua38
-rw-r--r--lua/99/ops/init.lua2
-rw-r--r--lua/99/ops/prompts.lua0
-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.lua34
-rw-r--r--lua/99/request/init.lua117
-rw-r--r--queries/lua/99-identifier.scm7
-rw-r--r--scratch/refresh.lua2
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