summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md38
-rw-r--r--TUTORIAL.md11
-rw-r--r--lua/99/consts.lua6
-rw-r--r--lua/99/init.lua79
-rw-r--r--lua/99/logger/logger.lua1
-rw-r--r--lua/99/ops/throbber.lua19
-rw-r--r--lua/99/state.lua16
-rw-r--r--lua/99/test/in_flight_spec.lua71
-rw-r--r--lua/99/test/test_utils.lua36
-rw-r--r--lua/99/window/in-flight.lua114
-rw-r--r--lua/99/window/init.lua16
11 files changed, 299 insertions, 108 deletions
diff --git a/AGENTS.md b/AGENTS.md
index f285623..38a9d38 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,3 +5,41 @@
* make lua_test
* make pr_ready
+## e2e / Integration style testing
+every testing file should have roughly the same setup.
+
+```lua
+-- In this example we are testing the visual function for 99 requests
+-- test utils has utilities to setup buffers and request context and
+-- test providers that we can control when to resolve
+-- test utils also has functions to schedule syncronously, very powerful
+local _99 = require("99")
+local test_utils = require("99.test.test_utils")
+local visual_fn = require("99.ops.over-range")
+-- ... imports that need to be tested
+
+describe("<name of test group>", function()
+
+ it("specific test condition", function()
+ -- we setup the world with test provider, context, and state
+ local p, buffer, range = setup(content, 2, 1, 2, 23)
+ local state = _99.__get_state()
+ local context = Prompt.visual(state)
+
+ -- now this test is simple, its just proving that we keep track
+ -- of inflight requests.
+ --
+ -- all tests should have simple conditions we are testing for
+ -- and the logic should attempt to be as simple as possible.
+ eq(0, state:active_request_count())
+ visual_fn(context, {
+ additional_prompt = "test prompt",
+ })
+ eq(1, state:active_request_count())
+ p:resolve("success", " return 'implemented!'")
+ test_utils.next_frame()
+ eq(0, state:active_request_count())
+
+ end)
+end)
+```
diff --git a/TUTORIAL.md b/TUTORIAL.md
deleted file mode 100644
index 212fb4f..0000000
--- a/TUTORIAL.md
+++ /dev/null
@@ -1,11 +0,0 @@
-### The features i want.
-- a prompt window
- - a way to tag docs
- - a crafted tutorial
-- links back to the docs
- - i want special remaps for this... back and forth should be amazing
-- the tutorial re-navigable / recallable
- - keep that information around for some amount of time...
- - stacked?
- - telescoped?
-
diff --git a/lua/99/consts.lua b/lua/99/consts.lua
new file mode 100644
index 0000000..4763a6a
--- /dev/null
+++ b/lua/99/consts.lua
@@ -0,0 +1,6 @@
+return {
+ show_in_flight_requests_loop_time = 1000,
+ throbber_throb_time = 1200,
+ throbber_cooldown_time = 100,
+ throbber_tick_time = 100,
+}
diff --git a/lua/99/init.lua b/lua/99/init.lua
index 4dffdfc..9fee2d3 100644
--- a/lua/99/init.lua
+++ b/lua/99/init.lua
@@ -3,12 +3,12 @@ local Level = require("99.logger.level")
local ops = require("99.ops")
local Languages = require("99.language")
local Window = require("99.window")
+local show_in_flight_requests = require("99.window.in-flight")
local Prompt = require("99.prompt")
local State = require("99.state")
local Extensions = require("99.extensions")
local Agents = require("99.extensions.agents")
local Providers = require("99.providers")
-local Throbber = require("99.ops.throbber")
---@param path_or_rule string | _99.Agents.Rule
---@return _99.Agents.Rule | string
@@ -38,20 +38,21 @@ local function process_opts(opts)
end
--- @class _99.Completion
+--- @docs included
--- @field source "cmp" | "blink" | nil
--- @field custom_rules string[]
--- @field files _99.Files.Config?
--- @class _99.Options
---- @field logger _99.Logger.Options?
---- @field model string?
---- @field show_in_flight_requests boolean?
---- @field md_files string[]?
---- @field provider _99.Providers.BaseProvider?
---- @field debug_log_prefix string?
+--- @docs base
+--- @field logger? _99.Logger.Options
+--- @field model? string
+--- @field in_flight_options? _99.InFlight.Opts
+--- @field md_files? string[]
+--- @field provider? _99.Providers.BaseProvider
--- @field display_errors? boolean
--- @field auto_add_skills? boolean
---- @field completion _99.Completion?
+--- @field completion? _99.Completion
--- @field tmp_dir? string
--- @type _99.State
@@ -282,64 +283,6 @@ function _99.__get_state()
return _99_state
end
-local function shut_down_in_flight_requests_window()
- if _99_state.show_in_flight_requests_throbber then
- _99_state.show_in_flight_requests_throbber:stop()
- end
-
- local win = _99_state.show_in_flight_requests_window
- if win ~= nil then
- Window.close(win)
- end
- _99_state.show_in_flight_requests_window = nil
- _99_state.show_in_flight_requests_throbber = nil
-end
-
-local function show_in_flight_requests()
- if _99_state.show_in_flight_requests == false then
- return
- end
- vim.defer_fn(show_in_flight_requests, 1000)
-
- Window.refresh_active_windows()
- local current_win = _99_state.show_in_flight_requests_window
- if current_win ~= nil and not Window.is_active_window(current_win) then
- shut_down_in_flight_requests_window()
- end
-
- if Window.has_active_windows() or _99_state:active_request_count() == 0 then
- return
- end
-
- if _99_state.show_in_flight_requests_window == nil then
- local win = Window.status_window()
- local throb = Throbber.new(function(throb)
- local count = _99_state:active_request_count()
- if count == 0 or not Window.valid(win) then
- return shut_down_in_flight_requests_window()
- end
-
- --- @type string[]
- local lines = {
- throb .. " requests(" .. tostring(count) .. ") " .. throb,
- }
-
- for _, c in pairs(_99_state.__request_by_id) do
- if c.state == "requesting" then
- table.insert(lines, c.operation)
- end
- end
-
- Window.resize(win, #lines[1], #lines)
- vim.api.nvim_buf_set_lines(win.buf_id, 0, 1, false, lines)
- end)
- _99_state.show_in_flight_requests_window = win
- _99_state.show_in_flight_requests_throbber = throb
-
- throb:start()
- end
-end
-
--- @param opts _99.Options?
function _99.setup(opts)
opts = opts or {}
@@ -389,9 +332,7 @@ function _99.setup(opts)
Extensions.init(_99_state)
Extensions.capture_project_root()
- if _99_state.show_in_flight_requests then
- show_in_flight_requests()
- end
+ show_in_flight_requests(_99_state, _99_state.in_flight_options)
end
--- @param md string
diff --git a/lua/99/logger/logger.lua b/lua/99/logger/logger.lua
index 611b016..003aab2 100644
--- a/lua/99/logger/logger.lua
+++ b/lua/99/logger/logger.lua
@@ -8,6 +8,7 @@ local logger_list = {}
local max_requests_in_logger_cache = MAX_REQUEST_DEFAULT
--- @class _99.Logger.Options
+--- @docs included
--- @field level number?
--- @field type? "print" | "void" | "file"
--- @field path string?
diff --git a/lua/99/ops/throbber.lua b/lua/99/ops/throbber.lua
index b08302a..4dd5867 100644
--- a/lua/99/ops/throbber.lua
+++ b/lua/99/ops/throbber.lua
@@ -1,4 +1,6 @@
local time = require("99.time")
+local Consts = require("99.consts")
+
local throb_icons = {
{ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" },
{ "◐", "◓", "◑", "◒" },
@@ -55,10 +57,6 @@ local function ease_in_ease_out_cubic(percent)
end
end
-local throb_time = 1200
-local cooldown_time = 100
-local tick_time = 100
-
--- @class _99.Throbber
--- @field start_time number
--- @field section_time number
@@ -72,16 +70,17 @@ Throbber.__index = Throbber
--- @class _99.Throbber.Opts
--- @field throb_time number
--- @field cooldown_time number
+--- @field tick_time number
--- @param cb fun(str: string): nil
--- @param opts _99.Throbber.Opts?
--- @return _99.Throbber
function Throbber.new(cb, opts)
- opts = opts
- or {
- throb_time = throb_time,
- cooldown_time = cooldown_time,
- }
+ opts = opts or {}
+ opts.throb_time = opts.throb_time or Consts.throbber_throb_time
+ opts.cooldown_time = opts.cooldown_time or Consts.throbber_cooldown_time
+ opts.tick_time = opts.tick_time or Consts.throbber_tick_time
+
return setmetatable({
state = "init",
start_time = 0,
@@ -111,7 +110,7 @@ function Throbber:_run()
self.cb(icon)
vim.defer_fn(function()
self:_run()
- end, tick_time)
+ end, self.opts.tick_time)
end
function Throbber:start()
diff --git a/lua/99/state.lua b/lua/99/state.lua
index 1762b84..d472117 100644
--- a/lua/99/state.lua
+++ b/lua/99/state.lua
@@ -1,12 +1,15 @@
local Agents = require("99.extensions.agents")
local Extensions = require("99.extensions")
+local function default_completion()
+ return { source = nil, custom_rules = {} }
+end
+
--- @class _99.StateProps
--- @field model string
--- @field md_files string[]
--- @field prompts _99.Prompts
--- @field ai_stdout_rows number
---- @field show_in_flight_requests boolean
--- @field languages string[]
--- @field display_errors boolean
--- @field auto_add_skills boolean
@@ -24,7 +27,7 @@ local Extensions = require("99.extensions")
--- @field ai_stdout_rows number
--- @field languages string[]
--- @field display_errors boolean
---- @field show_in_flight_requests boolean
+--- @field in_flight_options _99.InFlight.Opts | nil
--- @field show_in_flight_requests_window _99.window.Window | nil
--- @field show_in_flight_requests_throbber _99.Throbber | nil
--- @field provider_override _99.Providers.BaseProvider?
@@ -45,7 +48,6 @@ local function create()
md_files = {},
prompts = require("99.prompt-settings"),
ai_stdout_rows = 3,
- show_in_flight_requests = false,
languages = { "lua", "go", "java", "elixir", "cpp", "ruby" },
display_errors = false,
provider_override = nil,
@@ -63,13 +65,9 @@ function State.new(opts)
local props = create()
local _99_state = setmetatable(props, State) --[[@as _99.State]]
- _99_state.show_in_flight_requests = opts.show_in_flight_requests or false
+ _99_state.in_flight_options = opts.in_flight_options or { enable = true }
_99_state.provider_override = opts.provider
- _99_state.completion = opts.completion
- or {
- source = nil,
- custom_rules = {},
- }
+ _99_state.completion = opts.completion or default_completion()
_99_state.completion.custom_rules = _99_state.completion.custom_rules or {}
_99_state.auto_add_skills = opts.auto_add_skills or false
_99_state.completion.files = _99_state.completion.files or {}
diff --git a/lua/99/test/in_flight_spec.lua b/lua/99/test/in_flight_spec.lua
new file mode 100644
index 0000000..c9ebc6b
--- /dev/null
+++ b/lua/99/test/in_flight_spec.lua
@@ -0,0 +1,71 @@
+-- luacheck: globals describe it assert after_each
+local _99 = require("99")
+local Prompt = require("99.prompt")
+local Window = require("99.window")
+local test_utils = require("99.test.test_utils")
+local eq = assert.are.same
+
+local content = {
+ "local function foo()",
+ " return 1",
+ "end",
+}
+
+--- You have to override this or else things will crash since the ui itself
+--- does not exist. this is a headless test so i fake it by returning a very
+--- simple ui of 120x40
+local original_nvim_list_uis = vim.api.nvim_list_uis
+local function nvim_list_uis()
+ return {
+ { width = 120, height = 40 },
+ }
+end
+
+describe("in_flight window", function()
+ local WAIT_TIME = 10
+ before_each(function()
+ vim.api.nvim_list_uis = nvim_list_uis
+ end)
+ after_each(function()
+ vim.api.nvim_list_uis = original_nvim_list_uis
+ end)
+ it("shows active requests and clears when request completes", function()
+ local provider = test_utils.test_setup(content, 2, 4)
+ local state = _99.__get_state()
+ local context = Prompt.search(state)
+
+ context:start_request()
+ vim.wait(WAIT_TIME * 2, function() end)
+
+ eq(1, #Window.active_windows)
+
+ local win = Window.active_windows[1]
+ vim.api.nvim_win_close(win.win_id, true)
+
+ vim.wait(WAIT_TIME * 2, function() end)
+ local next_win = Window.active_windows[1]
+
+ eq(true, win.win_id ~= next_win.win_id)
+
+ provider:resolve("success", "results are in")
+
+ vim.wait(WAIT_TIME * 2, function() end)
+ eq(0, #Window.active_windows)
+ end)
+
+ it("enable false == do not show in flight", function()
+ local provider = test_utils.test_setup(content, 2, 4, "lua", {
+ in_flight_options = { enable = false },
+ })
+ local state = _99.__get_state()
+ local context = Prompt.search(state)
+
+ context:start_request()
+ vim.wait(WAIT_TIME * 2, function() end)
+
+ eq(0, #Window.active_windows)
+ provider:resolve("success", "results are in")
+ vim.wait(WAIT_TIME * 2, function() end)
+ eq(0, #Window.active_windows)
+ end)
+end)
diff --git a/lua/99/test/test_utils.lua b/lua/99/test/test_utils.lua
index e5484f1..28fe1e4 100644
--- a/lua/99/test/test_utils.lua
+++ b/lua/99/test/test_utils.lua
@@ -108,21 +108,39 @@ function M.create_file(contents, file_type, row, col)
return bufnr
end
+--- @param opts _99.Options | nil
+--- @param provider _99.Providers.BaseProvider
+--- @return _99.Options
+function M.get_test_setup_options(opts, provider)
+ opts = opts or {}
+ opts.provider = provider
+ opts.logger = {
+ error_cache_level = Levels.ERROR,
+ type = "print",
+ }
+ opts.in_flight_options = opts.in_flight_options
+ or {
+ throbber_opts = {
+ tick_time = 10,
+ throb_time = 1000,
+ cooldown_time = 500,
+ },
+ in_flight_interval = 10,
+ enable = true,
+ }
+ return opts
+end
+
--- @param content string[]
--- @param row number
--- @param col number
--- @param lang string?
+--- @param opts _99.Options | nil
--- @return _99.test.Provider, number
-function M.test_setup(content, row, col, lang)
- assert(lang, "lang must be provided")
+function M.test_setup(content, row, col, lang, opts)
+ lang = lang or "lua"
local provider = M.TestProvider.new()
- require("99").setup({
- provider = provider,
- logger = {
- error_cache_level = Levels.ERROR,
- type = "print",
- },
- })
+ require("99").setup(M.get_test_setup_options(opts, provider))
local buffer = M.create_file(content, lang, row, col)
return provider, buffer
diff --git a/lua/99/window/in-flight.lua b/lua/99/window/in-flight.lua
new file mode 100644
index 0000000..9605688
--- /dev/null
+++ b/lua/99/window/in-flight.lua
@@ -0,0 +1,114 @@
+local Window = require("99.window")
+local Consts = require("99.consts")
+local Throbber = require("99.ops.throbber")
+
+--- @param opts _99.InFlight.Opts | nil
+--- @return _99.InFlight.Opts
+local function default_opts(opts)
+ opts = opts or {}
+ opts.throbber_opts = opts.throbber_opts
+ or {
+ throb_time = Consts.throbber_throb_time,
+ cooldown_time = Consts.throbber_cooldown_time,
+ tick_time = Consts.throbber_tick_time,
+ }
+ opts.in_flight_interval = opts.in_flight_interval
+ or Consts.show_in_flight_requests_loop_time
+ opts.enable = opts.enable == nil and true or opts.enable
+ return opts
+end
+
+--- @param _99 _99.State
+local function shut_down_in_flight_requests_window(_99)
+ if _99.show_in_flight_requests_throbber then
+ _99.show_in_flight_requests_throbber:stop()
+ end
+
+ local win = _99.show_in_flight_requests_window
+ if win ~= nil then
+ Window.close(win)
+ end
+ _99.show_in_flight_requests_window = nil
+ _99.show_in_flight_requests_throbber = nil
+end
+
+--- @class _99.InFlight.Opts
+--- this is pure a class for testing. helps controls timings
+--- @docs include
+--- @field throbber_opts _99.Throbber.Opts | nil
+--- options for the throbber in the top left
+--- @field in_flight_interval number | nil
+--- frequency in which the in-flight interval checks to see if it should be
+--- displayed / removed
+--- @field enable boolean | nil
+--- defaults to true
+
+--- @param _99 _99.State
+--- @param opts _99.InFlight.Opts | nil
+local function show_in_flight_requests(_99, opts)
+ --- TODO: I dont like this. i dont like that i have to redo this every single
+ --- time i cycle, but its not a big deal right now. either way ill address this later
+ opts = default_opts(opts)
+ if opts.enable == false then
+ return
+ end
+ vim.defer_fn(function()
+ show_in_flight_requests(_99, opts)
+ end, opts.in_flight_interval)
+
+ Window.refresh_active_windows()
+ local current_win = _99.show_in_flight_requests_window
+ if current_win ~= nil and not Window.is_active_window(current_win) then
+ shut_down_in_flight_requests_window(_99)
+ end
+
+ local active_window = Window.has_active_status_window()
+ local active_other_window = Window.has_active_windows()
+ local active_requests = _99:active_request_count()
+ if
+ active_window == false and active_other_window
+ or active_window and active_requests > 0
+ or active_window == false and active_requests == 0
+ then
+ return
+ end
+
+ if _99.show_in_flight_requests_window == nil then
+ local ok, win = pcall(Window.status_window)
+ if not ok then
+ --- TODO: There needs to be a way to display logs for "all active requests"
+ --- this is its own activity and should not be added to any work set
+ return
+ end
+
+ local throb = Throbber.new(function(throb)
+ local count = _99:active_request_count()
+ local win_valid = Window.valid(win)
+
+ if count == 0 or not win_valid then
+ return shut_down_in_flight_requests_window(_99)
+ end
+
+ --- @type string[]
+ local lines = {
+ throb .. " requests(" .. tostring(count) .. ") " .. throb,
+ }
+
+ for _, c in pairs(_99.__request_by_id) do
+ if c.state == "requesting" then
+ table.insert(lines, c.operation)
+ end
+ end
+
+ Window.resize(win, #lines[1], #lines)
+ vim.api.nvim_buf_set_lines(win.buf_id, 0, -1, false, lines)
+ end, opts.throbber_opts)
+
+ _99.show_in_flight_requests_window = win
+ _99.show_in_flight_requests_throbber = throb
+
+ throb:start()
+ end
+end
+
+return show_in_flight_requests
diff --git a/lua/99/window/init.lua b/lua/99/window/init.lua
index 8db5b2f..f546fac 100644
--- a/lua/99/window/init.lua
+++ b/lua/99/window/init.lua
@@ -26,6 +26,7 @@ local nvim_buf_is_valid = vim.api.nvim_buf_is_valid
--- @field config _99.window.Config
--- @field win_id number
--- @field buf_id number
+--- @field type "capture_input" | "status"
--- @class _99.window.SplitWindow
--- @field win number
@@ -366,6 +367,8 @@ function M.capture_input(name, opts)
local config = create_centered_window()
local win =
create_floating_window(config, string.format(" 99 %s ", name), true)
+ win.type = "capture_input"
+
set_defaul_win_options(win, "99-prompt")
vim.api.nvim_set_current_win(win.win_id)
@@ -456,6 +459,7 @@ function M.status_window()
M.clear_active_popups()
local config = create_transparent_top_right_config(100, " 99 - Status ")
local window = create_floating_window(config, " 99 - Status ", false)
+ window.type = "status"
return window
end
@@ -477,6 +481,18 @@ function M.has_active_windows()
return #M.active_windows > 0
end
+--- @return boolean
+function M.has_active_status_window()
+ local has = false
+ for _, w in ipairs(M.active_windows) do
+ if w.type == "status" then
+ has = true
+ break
+ end
+ end
+ return has
+end
+
function M.refresh_active_windows()
--- @type _99.window.Window[]
local actives = {}