From b6794ffb660693a4c9c9180bfc94ecf3377551c3 Mon Sep 17 00:00:00 2001 From: theprimeagain Date: Fri, 27 Feb 2026 15:14:28 -0700 Subject: in flight refactored --- lua/99/init.lua | 7 +- lua/99/prompt.lua | 4 +- lua/99/state.lua | 44 ++++++++---- lua/99/state/tracking.lua | 21 ++++++ lua/99/utils.lua | 17 +++++ lua/99/window/in-flight.lua | 114 ----------------------------- lua/99/window/status-window.lua | 156 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 230 insertions(+), 133 deletions(-) create mode 100644 lua/99/state/tracking.lua delete mode 100644 lua/99/window/in-flight.lua create mode 100644 lua/99/window/status-window.lua (limited to 'lua') diff --git a/lua/99/init.lua b/lua/99/init.lua index 82cf118..2310784 100644 --- a/lua/99/init.lua +++ b/lua/99/init.lua @@ -5,7 +5,7 @@ local Logger = require("99.logger.logger") local Level = require("99.logger.level") local ops = require("99.ops") local Window = require("99.window") -local show_in_flight_requests = require("99.window.in-flight") +local StatusWindow = require("99.window.status-window") local Prompt = require("99.prompt") local State = require("99.state") local Extensions = require("99.extensions") @@ -49,7 +49,7 @@ end --- @docs base --- @field logger? _99.Logger.Options --- @field model? string ---- @field in_flight_options? _99.InFlight.Opts +--- @field in_flight_options? _99.StatusWindow.Opts --- @field md_files? string[] --- @field provider? _99.Providers.BaseProvider --- @field display_errors? boolean @@ -496,7 +496,8 @@ function _99.setup(opts) Extensions.init(_99_state) Extensions.capture_project_root() - show_in_flight_requests(_99_state, _99_state.in_flight_options) + local sw = StatusWindow.new(_99_state, opts.in_flight_options) + sw:start() end --- @param md string diff --git a/lua/99/prompt.lua b/lua/99/prompt.lua index 429ad7b..23b2309 100644 --- a/lua/99/prompt.lua +++ b/lua/99/prompt.lua @@ -51,7 +51,7 @@ local filetype_map = { --- @field xid number TODO: we should probably get rid of this. The request pattern is not quite correct --- @field tutorial string[] ---- @class _99.Prompt +--- @class _99.Prompt.Properties --- @field md_file_names string[] --- @field model string --- @field user_prompt string @@ -65,6 +65,8 @@ local filetype_map = { --- @field marks table --- @field logger _99.Logger --- @field xid number + +--- @class _99.Prompt : _99.Prompt.Properties --- @field clean_ups (fun(): nil)[] --- @field _99 _99.State ---@diagnostic disable-next-line: undefined-doc-name diff --git a/lua/99/state.lua b/lua/99/state.lua index 2182c98..050a5c4 100644 --- a/lua/99/state.lua +++ b/lua/99/state.lua @@ -1,3 +1,4 @@ +local utils = require("99.utils") local Agents = require("99.extensions.agents") local Extensions = require("99.extensions") @@ -25,13 +26,8 @@ end --- @field prompts _99.Prompts --- @field ai_stdout_rows number --- @field display_errors 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? ---- @field auto_add_skills boolean --- @field rules _99.Agents.Rules ---- @field __view_log_idx number --- @field __request_history _99.Prompt[] --- @field __request_by_id table --- @field __active_marks _99.Mark[] @@ -44,41 +40,59 @@ local function create() return { model = "opencode/claude-sonnet-4-5", md_files = {}, - prompts = require("99.prompt-settings"), ai_stdout_rows = 3, display_errors = false, provider_override = nil, - auto_add_skills = false, - __view_log_idx = 1, __request_history = {}, __request_by_id = {}, tmp_dir = nil, } end +--- @param oos _99.Options | _99.State +local function get_tmp_dir(oos) + local tmp_dir = oos.tmp_dir and type(oos.tmp_dir) == "string" and oos.tmp_dir + or oos.__tmp_dir and oos.__tmp_dir + or "./tmp" + if tmp_dir then + tmp_dir = vim.fn.expand(tmp_dir) + end + return tmp_dir +end + +--- @param opts _99.Options +--- @return _99.StateProps | nil +local function read_state_from_tmp(opts) + local state_file = utils.named_tmp_file(get_tmp_dir(opts), "99-state") + local fd = vim.uv.fs_open(state_file, "r", 438) + if not fd then + return nil + end + return utils.read_file_json_safe(state_file) --[[@as _99.StateProps]] +end + --- @param opts _99.Options --- @return _99.State function State.new(opts) - local props = create() + local props = read_state_from_tmp(opts) or create() local _99_state = setmetatable(props, State) --[[@as _99.State]] _99_state.in_flight_options = opts.in_flight_options or { enable = true } _99_state.provider_override = opts.provider _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 {} + --- TODO: Prompt overrides would be a great thing, we just have to get there + --- for now, i am going to have this as just a hardcoded ... thing + _99_state.prompts = require("99.prompt-settings") + return _99_state end --- @return string function State:tmp_dir() - local tmp_dir = self.__tmp_dir or "./tmp" - if tmp_dir then - tmp_dir = vim.fn.expand(tmp_dir) - end - return tmp_dir + return get_tmp_dir(self) end --- TODO: This is something to understand. I bet that this is going to need diff --git a/lua/99/state/tracking.lua b/lua/99/state/tracking.lua new file mode 100644 index 0000000..afcddd0 --- /dev/null +++ b/lua/99/state/tracking.lua @@ -0,0 +1,21 @@ +-- --- @class _99.State.Tracking.Options +-- --- @field __request_history _99.Prompt[] | nil +-- --- vj +-- --- @field __request_by_id table | nil +-- --- v +-- --- @field __active_marks _99.Mark[] | nil +-- +-- --- @class _99.State.Tracking +-- --- @field __request_history _99.Prompt[] +-- --- @field __request_by_id table +-- --- @field __active_marks _99.Mark[] +-- local Tracking = {} +-- +-- --- @param previous_state +-- function Tracking.new(previous_state) +-- end +-- +-- +-- return Tracking +-- +return {} diff --git a/lua/99/utils.lua b/lua/99/utils.lua index e15e178..724b3c6 100644 --- a/lua/99/utils.lua +++ b/lua/99/utils.lua @@ -40,4 +40,21 @@ function M.named_tmp_file(dir, name) return string.format("%s/99-%s", dir, name) end +--- @param path string +--- @return table | nil +function M.read_file_json_safe(path) + local ok, fh = pcall(io.open, path, "r") + if ok and fh then + local ok2, content = pcall(fh.read, fh, "*a") + pcall(fh.close, fh) + if not ok2 then + return nil + end + local ok3, obj = pcall(vim.json, content) + if ok3 and obj then + return obj + end + end +end + return M diff --git a/lua/99/window/in-flight.lua b/lua/99/window/in-flight.lua deleted file mode 100644 index 9605688..0000000 --- a/lua/99/window/in-flight.lua +++ /dev/null @@ -1,114 +0,0 @@ -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/status-window.lua b/lua/99/window/status-window.lua new file mode 100644 index 0000000..676042b --- /dev/null +++ b/lua/99/window/status-window.lua @@ -0,0 +1,156 @@ +local Window = require("99.window") +local Consts = require("99.consts") +local Throbber = require("99.ops.throbber") + +--- @alias _99.StatusWindow.State "init" | "running" + +--- @class _99.StatusWindow.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 opts _99.StatusWindow.Opts | nil +--- @return _99.StatusWindow.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 + +--- @class _99.StatusWindow +--- @field opts _99.StatusWindow.Opts +--- @field state _99.StatusWindow.State +--- @field win _99.window.Window | nil +--- @field throbber _99.Throbber | nil +--- @field _99 _99.State +local StatusWindow = {} +StatusWindow.__index = StatusWindow + +--- @param _99 _99.State +--- @param opts _99.StatusWindow.Opts | nil +function StatusWindow.new(_99, opts) + return setmetatable({ + opts = default_opts(opts), + state = "init", + _99 = _99, + }, StatusWindow) +end + +function StatusWindow:_shutdown_status_window() + if self.throbber then + self.throbber:stop() + end + + local win = self.win + if win ~= nil then + Window.close(win) + end + self.win = nil + self.throbber = nil +end + +function StatusWindow:_run_loop() + if self.state ~= "running" then + self:_shutdown_status_window() + return + end + vim.defer_fn(function() + self:_run_loop() + end, self.opts.in_flight_interval) + + Window.refresh_active_windows() + local current_win = self.win + if current_win ~= nil and not Window.is_active_window(current_win) then + self:_shutdown_status_window() + end + + local active_window = Window.has_active_status_window() + local active_other_window = Window.has_active_windows() + local active_requests = self._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 current_win == 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 = self._99:active_request_count() + local win_valid = Window.valid(win) + + if count == 0 or not win_valid then + return self:_shutdown_status_window() + end + + --- @type string[] + local lines = { + throb .. " requests(" .. tostring(count) .. ") " .. throb, + } + + for _, c in pairs(self._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, self.opts.throbber_opts) + + self.win = win + self.throbber = throb + + throb:start() + end +end + +function StatusWindow:start() + if not self.opts.enable then + return + end + + assert( + self.state == "init", + "you cannot start an inflight request if we are not in init state: " + .. self.state + ) + + self.state = "running" + self:_run_loop() +end + +function StatusWindow:stop() + if not self.opts.enable then + return + end + assert( + self.state == "running", + "you cannot stop a running status window if its not running" + ) + self.state = "init" +end + +return StatusWindow -- cgit v1.3-3-g829e