summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThePrimeAgain <theprimeagain@theprimeagain.com>2026-01-15 11:22:01 -0700
committerThePrimeAgain <theprimeagain@theprimeagain.com>2026-01-15 11:22:01 -0700
commitfc2ce13fcc6454fa12ee1015f921611cd2edc4fa (patch)
treedc2cc7817d012eff49d8a902f8d083e73e98bf59
parent4e24d093d50177e299823703759a5e276d198170 (diff)
downloada4-fc2ce13fcc6454fa12ee1015f921611cd2edc4fa.tar.xz
a4-fc2ce13fcc6454fa12ee1015f921611cd2edc4fa.zip
autocomplete appearing to work
-rw-r--r--lua/99/extensions/agents/helpers.lua (renamed from lua/99/agents/helpers.lua)4
-rw-r--r--lua/99/extensions/agents/init.lua (renamed from lua/99/agents/init.lua)19
-rw-r--r--lua/99/extensions/cmp.lua76
-rw-r--r--lua/99/extensions/init.lua38
-rw-r--r--lua/99/init.lua75
-rw-r--r--lua/99/window/init.lua19
-rw-r--r--scratch/custom_rules/back-end.md0
-rw-r--r--scratch/custom_rules/foo.md2
-rw-r--r--scratch/custom_rules/front-end.md0
-rw-r--r--scratch/custom_rules/vim.lsp.md0
-rw-r--r--scratch/custom_rules/vim.md0
-rw-r--r--scratch/custom_rules/vim.treesitter.md0
-rw-r--r--scratch/refresh.lua146
13 files changed, 173 insertions, 206 deletions
diff --git a/lua/99/agents/helpers.lua b/lua/99/extensions/agents/helpers.lua
index a5000cb..1f4ed01 100644
--- a/lua/99/agents/helpers.lua
+++ b/lua/99/extensions/agents/helpers.lua
@@ -3,8 +3,8 @@ local M = {}
--- @param dir string
--- @return _99.Agents.Rule[]
function M.ls(dir)
- local cursor_rules_dir = vim.uv.cwd() .. dir
- local files = vim.fn.glob(cursor_rules_dir .. "/*.{mdc,md}", false, true)
+ local cwd = vim.fs.joinpath(vim.uv.cwd(), dir)
+ local files = vim.fn.glob(cwd .. "/*.{mdc,md}", false, true)
local rules = {}
for _, file in ipairs(files) do
diff --git a/lua/99/agents/init.lua b/lua/99/extensions/agents/init.lua
index be43727..f594802 100644
--- a/lua/99/agents/init.lua
+++ b/lua/99/extensions/agents/init.lua
@@ -1,4 +1,4 @@
-local helpers = require("99.agents.helpers")
+local helpers = require("99.extensions.agents.helpers")
local M = {}
--- @class _99.Agents.Rule
@@ -17,7 +17,7 @@ local M = {}
function M.rules(_99)
local cursor = helpers.ls(".cursor/rules")
local custom = {}
- for _, path in ipairs(_99.custom_rules) do
+ for _, path in ipairs(_99.completion.custom_rules or {}) do
local c = helpers.ls(path)
table.insert(custom, c)
end
@@ -27,4 +27,19 @@ function M.rules(_99)
}
end
+--- @param rules _99.Agents.Rules
+--- @return _99.Agents.Rule[]
+function M.rules_to_items(rules)
+ local items = {}
+ for _, rule in ipairs(rules.cursor or {}) do
+ table.insert(items, rule)
+ end
+ for _, custom_rules in ipairs(rules.custom or {}) do
+ for _, rule in ipairs(custom_rules) do
+ table.insert(items, rule)
+ end
+ end
+ return items
+end
+
return M
diff --git a/lua/99/extensions/cmp.lua b/lua/99/extensions/cmp.lua
index e55ae36..30e5d2c 100644
--- a/lua/99/extensions/cmp.lua
+++ b/lua/99/extensions/cmp.lua
@@ -1,14 +1,24 @@
+local Agents = require("99.extensions.agents")
+local SOURCE = "99"
+
+--- @param _99 _99.State
+--- @return _99.Agents.Rule[]
+local function rules(_99)
+ return Agents.rules_to_items(Agents.rules(_99))
+end
+
--- @class CmpSource
--- @field _99 _99.State
---- @field items string[]
+--- @field items _99.Agents.Rule[]
local CmpSource = {}
CmpSource.__index = CmpSource
-local SOURCE = "99"
-
--- @param _99 _99.State
function CmpSource.new(_99)
- return setmetatable({}, CmpSource)
+ return setmetatable({
+ _99 = _99,
+ items = rules(_99),
+ }, CmpSource)
end
function CmpSource:is_available()
@@ -39,41 +49,39 @@ end
-- true: I might return more if user types more
-- false: this result set is complete
function CmpSource:complete(params, callback)
- local cmp = require("cmp")
local before = params.context.cursor_before_line or ""
- local prefix = before:match("(%w+)$") or ""
-
local items = {} --[[ @as CompletionItem[] ]]
- print("complete: context", vim.inspect(params.context))
+ if #before > 1 and before:sub(#before - 1) ~= " @" then
+ callback({
+ items = {},
+ isIncomplete = false,
+ })
+ return
+ end
+
+ for _, item in ipairs(self.items) do
+ table.insert(items, {
+ label = item.name,
+ insertText = item.path,
+ filterText = item.name,
+ kind = 17, -- file
+ -- documentation = "here is the documentation and everything associated with it",
+ -- detail = "detail: right side hint",
+ })
+ end
callback({
items = items,
- isIncomplete = true,
+ isIncomplete = false,
})
end
--- resolve(completion_item, callback) (optional)
--- Some sources return lightweight items first, then fill in heavy fields
--- only when the user selects an item.
---
--- For example:
--- - fetch docs lazily
--- - compute expensive detail text
---
--- If you don’t need it, omit it.
+--- TODO: Look into what this could be
function CmpSource:resolve(completion_item, callback)
- -- You can modify completion_item here.
callback(completion_item)
end
--- execute(completion_item, callback) (optional)
--- Called when the item is confirmed, if the item contains an "command" field
--- or if your source wants to perform side-effects.
---
--- Examples:
--- - insert import statements (usually via additionalTextEdits instead)
--- - open a snippet, run something, etc.
function CmpSource:execute(completion_item, callback)
callback(completion_item)
end
@@ -86,24 +94,32 @@ local function init_for_buffer(_99)
local cmp = require("cmp")
cmp.setup.buffer({
sources = {
- { name = "my_source" },
+ { name = SOURCE },
},
})
end
--- @param _99 _99.State
local function init(_99)
- assert(source == nil, "the source must be nil when calling init on an completer")
+ assert(
+ source == nil,
+ "the source must be nil when calling init on an completer"
+ )
local cmp = require("cmp")
source = CmpSource.new(_99)
cmp.register_source(SOURCE, source)
end
-local function refresh_state(_99) end
+--- @param _99 _99.State
+local function refresh_state(_99)
+ source.items = rules(_99)
+end
-return {
+--- @type _99.Extensions.Source
+local source_wrapper = {
init_for_buffer = init_for_buffer,
init = init,
refresh_state = refresh_state,
}
+return source_wrapper
diff --git a/lua/99/extensions/init.lua b/lua/99/extensions/init.lua
index 94190d6..ceb63a5 100644
--- a/lua/99/extensions/init.lua
+++ b/lua/99/extensions/init.lua
@@ -1,5 +1,37 @@
-local M = {}
+local cmp = require("99.extensions.cmp")
---- @param _99 _99.State
-function M.init_buffer(_99)
+--- @class _99.Extensions.Source
+--- @field init_for_buffer fun(_99: _99.State): nil
+--- @field init fun(_99: _99.State): nil
+--- @field refresh_state fun(_99: _99.State): nil
+
+--- @param completion _99.Completion | nil
+--- @return _99.Extensions.Source | nil
+local function get_source(completion)
+ if not completion or not completion.source then
+ return
+ end
+ local source = completion.source
+ if source == "cmp" then
+ return cmp
+ end
end
+
+return {
+ --- @param _99 _99.State
+ init = function(_99)
+ local source = get_source(_99.completion)
+ if not source then
+ return
+ end
+ source.init(_99)
+ end,
+
+ setup_buffer = function(_99)
+ local source = get_source(_99.completion)
+ if not source then
+ return
+ end
+ source.init_for_buffer(_99)
+ end,
+}
diff --git a/lua/99/init.lua b/lua/99/init.lua
index 3faa6e2..a677d6b 100644
--- a/lua/99/init.lua
+++ b/lua/99/init.lua
@@ -6,6 +6,7 @@ local Window = require("99.window")
local get_id = require("99.id")
local RequestContext = require("99.request-context")
local Range = require("99.geo").Range
+local Extensions = require("99.extensions")
--- @alias _99.Cleanup fun(): nil
@@ -34,6 +35,10 @@ local function create_99_state()
}
end
+--- @class _99.Completion
+--- @field source "cmp" | nil
+--- @field custom_rules string[] | nil
+
--- @class _99.Options
--- @field logger _99.Logger.Options?
--- @field model string?
@@ -41,14 +46,14 @@ end
--- @field provider _99.Provider?
--- @field debug_log_prefix string?
--- @field display_errors? boolean
---- @field custom_rules? string[]
+--- @field completion _99.Completion?
--- 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 _99.State
+--- @field completion _99.Completion
--- @field model string
--- @field md_files string[]
---- @field custom_rules string[]
--- @field prompts _99.Prompts
--- @field ai_stdout_rows number
--- @field languages string[]
@@ -146,19 +151,25 @@ end
function _99.fill_in_function_prompt()
local context = get_context("fill-in-function-with-prompt")
+
context.logger:debug("start")
- Window.capture_input(function(success, response)
- context.logger:debug(
- "capture_prompt",
- "success",
- success,
- "response",
- response
- )
- if success then
- ops.fill_in_function(context, response)
- end
- end, {})
+ Window.capture_input({
+ cb = function(success, response)
+ context.logger:debug(
+ "capture_prompt",
+ "success",
+ success,
+ "response",
+ response
+ )
+ if success then
+ ops.fill_in_function(context, response)
+ end
+ end,
+ on_load = function()
+ Extensions.setup_buffer(_99_state)
+ end,
+ })
end
function _99.fill_in_function()
@@ -168,18 +179,23 @@ end
function _99.visual_prompt()
local context = get_context("over-range-with-prompt")
context.logger:debug("start")
- Window.capture_input(function(success, response)
- context.logger:debug(
- "capture_prompt",
- "success",
- success,
- "response",
- response
- )
- if success then
- _99.visual(response)
- end
- end, {})
+ Window.capture_input({
+ cb = function(success, response)
+ context.logger:debug(
+ "capture_prompt",
+ "success",
+ success,
+ "response",
+ response
+ )
+ if success then
+ _99.visual(response)
+ end
+ end,
+ on_load = function()
+ Extensions.setup_buffer(_99_state)
+ end,
+ })
end
--- @param prompt string?
@@ -250,7 +266,11 @@ function _99.setup(opts)
opts = opts or {}
_99_state = _99_State.new()
_99_state.provider_override = opts.provider
- _99_state.custom_rules = opts.custom_rules or {}
+ _99_state.completion = opts.completion
+ or {
+ source = nil,
+ custom_rules = {},
+ }
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
@@ -275,6 +295,7 @@ function _99.setup(opts)
_99_state.display_errors = opts.display_errors or false
Languages.initialize(_99_state)
+ Extensions.init(_99_state)
end
--- @param md string
diff --git a/lua/99/window/init.lua b/lua/99/window/init.lua
index 8f45433..3475d5a 100644
--- a/lua/99/window/init.lua
+++ b/lua/99/window/init.lua
@@ -257,9 +257,12 @@ local function set_defaul_win_options(win, name)
vim.bo[win.buf_id].swapfile = false
end
---- @param cb fun(success: boolean, result: string): nil
---- @param opts {}
-function M.capture_input(cb, opts)
+--- @class _99.window.CaptureInputOpts
+--- @field cb fun(success: boolean, result: string): nil
+--- @field on_load? fun(): nil
+
+--- @param opts _99.window.CaptureInputOpts
+function M.capture_input(opts)
_ = opts
M.clear_active_popups()
@@ -299,7 +302,7 @@ function M.capture_input(cb, opts)
local lines = vim.api.nvim_buf_get_lines(win.buf_id, 0, -1, false)
local result = table.concat(lines, "\n")
M.clear_active_popups()
- cb(true, result)
+ opts.cb(true, result)
end,
})
@@ -322,14 +325,18 @@ function M.capture_input(cb, opts)
return
end
M.clear_active_popups()
- cb(false, "")
+ opts.cb(false, "")
end,
})
vim.keymap.set("n", "q", function()
M.clear_active_popups()
- cb(false, "")
+ opts.cb(false, "")
end, { buffer = win.buf_id, nowait = true })
+
+ if opts.on_load then
+ vim.schedule(opts.on_load)
+ end
end
function M.clear_active_popups()
diff --git a/scratch/custom_rules/back-end.md b/scratch/custom_rules/back-end.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scratch/custom_rules/back-end.md
diff --git a/scratch/custom_rules/foo.md b/scratch/custom_rules/foo.md
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/scratch/custom_rules/foo.md
@@ -0,0 +1,2 @@
+
+
diff --git a/scratch/custom_rules/front-end.md b/scratch/custom_rules/front-end.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scratch/custom_rules/front-end.md
diff --git a/scratch/custom_rules/vim.lsp.md b/scratch/custom_rules/vim.lsp.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scratch/custom_rules/vim.lsp.md
diff --git a/scratch/custom_rules/vim.md b/scratch/custom_rules/vim.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scratch/custom_rules/vim.md
diff --git a/scratch/custom_rules/vim.treesitter.md b/scratch/custom_rules/vim.treesitter.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/scratch/custom_rules/vim.treesitter.md
diff --git a/scratch/refresh.lua b/scratch/refresh.lua
index 0777f65..a1b6c63 100644
--- a/scratch/refresh.lua
+++ b/scratch/refresh.lua
@@ -2,140 +2,14 @@ local Window = require("99.window")
Window.clear_active_popups()
R("99")
--- Modern Neovim completion example using vim.lsp.completion and custom completions
--- This is the newer API that replaced omnifunc
-
--- Example 1: Using the built-in completion with a custom source
-local function setup_custom_completion()
- -- Set up completion options
- vim.opt.completeopt = { "menu", "menuone", "noselect" }
-
- -- Create a custom completion function
- -- This is called when user triggers completion (Ctrl-X Ctrl-U by default)
- vim.api.nvim_buf_set_option(0, 'completefunc', 'v:lua.custom_complete')
-end
-
--- Custom completion function
--- Returns completion items when called
-function _G.custom_complete(findstart, base)
- if findstart == 1 then
- -- First call: return the column where completion starts
- local line = vim.api.nvim_get_current_line()
- local col = vim.api.nvim_win_get_cursor(0)[2]
-
- -- Find start of the word
- local start = col
- while start > 0 and line:sub(start, start):match("[%w_]") do
- start = start - 1
- end
-
- return start
- else
- -- Second call: return list of completions
- -- 'base' is the text to complete
- local completions = {
- { word = "example_item", menu = "Example", kind = "Function" },
- { word = "example_variable", menu = "Example", kind = "Variable" },
- { word = "example_constant", menu = "Example", kind = "Constant" },
- }
-
- -- Filter completions based on what user typed
- local matches = {}
- for _, item in ipairs(completions) do
- if item.word:find("^" .. base) then
- table.insert(matches, item)
- end
- end
-
- return matches
- end
-end
-
--- Example 2: Using nvim-cmp style manual completion
--- This is the modern approach used by popular plugins
-local function create_completion_source()
- return {
- items = {
- { label = "Window.clear_active_popups", kind = vim.lsp.protocol.CompletionItemKind.Method },
- { label = "Window.capture_input", kind = vim.lsp.protocol.CompletionItemKind.Method },
- { label = "Window.create_popup", kind = vim.lsp.protocol.CompletionItemKind.Method },
- { label = "custom_item", kind = vim.lsp.protocol.CompletionItemKind.Variable },
- },
-
- complete = function(self, ctx)
- local line = ctx.line
- local cursor = ctx.cursor
-
- -- Return filtered items based on context
- return vim.tbl_filter(function(item)
- return item.label:find(ctx.query or "", 1, true) ~= nil
- end, self.items)
- end
- }
-end
-
--- Example 3: Set up omnifunc (older API, but still works)
--- This is triggered with Ctrl-X Ctrl-O
-function _G.my_omnifunc(findstart, base)
- if findstart == 1 then
- local line = vim.api.nvim_get_current_line()
- local col = vim.api.nvim_win_get_cursor(0)[2]
- return vim.fn.match(line:sub(1, col), [[\k*$]])
- else
- -- Return completion matches
- return {
- "Window.clear_active_popups()",
- "Window.capture_input(callback, opts)",
- "Window.create_floating_window()",
- { word = "advanced_item", abbr = "adv", menu = "Custom", info = "Detailed info here" },
+local Ext = require("99.extensions")
+local _99 = require("99")
+_99.setup({
+ completion = {
+ source = "cmp",
+ custom_rules = {
+ "scratch/custom_rules"
+ }
}
- end
-end
-
--- Set it up for the current buffer
-vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.my_omnifunc')
-
--- Usage:
--- Ctrl-X Ctrl-O to trigger omnifunc completion
--- Ctrl-X Ctrl-U to trigger completefunc completion
-
--- Example: Auto-trigger completion when typing '@' in 99-prompt buffer
-
--- Custom completion function that provides context-aware items
-function _G.prompt_at_complete(findstart, base)
- if findstart == 1 then
- -- Find the start position (right after the '@')
- local line = vim.api.nvim_get_current_line()
- local col = vim.api.nvim_win_get_cursor(0)[2]
-
- -- Find the '@' symbol
- local start = col
- while start > 0 and line:sub(start, start) ~= '@' do
- start = start - 1
- end
-
- return start
- else
- -- Return completion items based on what comes after '@'
- local completions = {
- { word = "@file", menu = "Reference a file", kind = "File" },
- { word = "@buffer", menu = "Reference a buffer", kind = "Reference" },
- { word = "@selection", menu = "Reference current selection", kind = "Reference" },
- { word = "@codebase", menu = "Search entire codebase", kind = "Keyword" },
- { word = "@web", menu = "Search the web", kind = "Keyword" },
- }
-
- -- Filter based on what user has typed
- local matches = {}
- for _, item in ipairs(completions) do
- if item.word:find("^@" .. vim.pesc(base), 1) then
- table.insert(matches, item)
- end
- end
-
- return matches
- end
-end
-
-setup_custom_completion()
-
+})
+Ext.setup_buffer(_99.__get_state())