From 75de3198cdadeadc7ddf0450d8402e7d45ff662a Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Tue, 24 Mar 2026 22:31:49 -0400 Subject: [PATCH] Adding the migration to pure files only --- lua/codetyper/adapters/nvim/autocmds.lua | 35 +--- .../adapters/nvim/ui/context_modal.lua | 14 -- .../adapters/nvim/ui/context_modal/open.lua | 4 +- .../adapters/nvim/ui/diff_review.lua | 24 --- lua/codetyper/adapters/nvim/ui/logs.lua | 34 ---- lua/codetyper/adapters/nvim/ui/logs_panel.lua | 12 -- .../nvim/ui/logs_panel/add_log_entry.lua | 4 +- .../adapters/nvim/ui/logs_panel/close.lua | 4 +- .../adapters/nvim/ui/logs_panel/open.lua | 10 +- .../nvim/ui/logs_panel/update_title.lua | 7 +- lua/codetyper/adapters/nvim/ui/thinking.lua | 179 ------------------ .../nvim/ui/thinking/active_count.lua | 9 + .../nvim/ui/thinking/check_and_hide.lua | 12 ++ .../adapters/nvim/ui/thinking/close.lua | 10 + .../nvim/ui/thinking/close_window.lua | 28 +++ .../nvim/ui/thinking/ensure_shown.lua | 47 +++++ .../adapters/nvim/ui/thinking/is_shown.lua | 9 + .../adapters/nvim/ui/thinking/setup.lua | 15 ++ .../nvim/ui/thinking/status_window_config.lua | 21 ++ .../nvim/ui/thinking/update_display.lua | 27 +++ .../nvim/ui/thinking/update_stage.lua | 9 + lua/codetyper/adapters/nvim/ui/throbber.lua | 87 --------- .../adapters/nvim/ui/throbber/class.lua | 13 ++ .../adapters/nvim/ui/throbber/new.lua | 28 +++ .../adapters/nvim/ui/throbber/run.lua | 28 +++ .../adapters/nvim/ui/throbber/start.lua | 12 ++ .../adapters/nvim/ui/throbber/stop.lua | 7 + lua/codetyper/constants/config.lua | 14 ++ lua/codetyper/constants/constants.lua | 23 +++ .../{config => constants}/defaults.lua | 0 lua/codetyper/constants/prices.lua | 88 +++++++++ lua/codetyper/core/cost/init.lua | 111 +---------- lua/codetyper/core/diff/conflict.lua | 8 +- lua/codetyper/core/diff/patch.lua | 58 +++--- lua/codetyper/core/events/queue.lua | 4 +- lua/codetyper/core/llm/selector.lua | 4 +- lua/codetyper/core/scheduler/executor.lua | 37 ++-- lua/codetyper/core/scheduler/loop.lua | 4 +- lua/codetyper/core/scheduler/scheduler.lua | 70 ++++--- lua/codetyper/core/scheduler/worker.lua | 32 ++-- lua/codetyper/core/thinking_placeholder.lua | 4 +- .../features/completion/suggestion.lua | 44 +---- lua/codetyper/init.lua | 2 +- lua/codetyper/state/state.lua | 22 +++ lua/codetyper/utils/get_config.lua | 10 +- lua/codetyper/utils/get_now.lua | 5 + 46 files changed, 576 insertions(+), 653 deletions(-) delete mode 100644 lua/codetyper/adapters/nvim/ui/context_modal.lua delete mode 100644 lua/codetyper/adapters/nvim/ui/diff_review.lua delete mode 100644 lua/codetyper/adapters/nvim/ui/logs.lua delete mode 100644 lua/codetyper/adapters/nvim/ui/logs_panel.lua delete mode 100644 lua/codetyper/adapters/nvim/ui/thinking.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/active_count.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/close.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/close_window.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/setup.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/status_window_config.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/update_display.lua create mode 100644 lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua delete mode 100644 lua/codetyper/adapters/nvim/ui/throbber.lua create mode 100644 lua/codetyper/adapters/nvim/ui/throbber/class.lua create mode 100644 lua/codetyper/adapters/nvim/ui/throbber/new.lua create mode 100644 lua/codetyper/adapters/nvim/ui/throbber/run.lua create mode 100644 lua/codetyper/adapters/nvim/ui/throbber/start.lua create mode 100644 lua/codetyper/adapters/nvim/ui/throbber/stop.lua create mode 100644 lua/codetyper/constants/config.lua create mode 100644 lua/codetyper/constants/constants.lua rename lua/codetyper/{config => constants}/defaults.lua (100%) create mode 100644 lua/codetyper/constants/prices.lua create mode 100644 lua/codetyper/utils/get_now.lua diff --git a/lua/codetyper/adapters/nvim/autocmds.lua b/lua/codetyper/adapters/nvim/autocmds.lua index 94f94f8..7e8dc94 100644 --- a/lua/codetyper/adapters/nvim/autocmds.lua +++ b/lua/codetyper/adapters/nvim/autocmds.lua @@ -4,29 +4,14 @@ local M = {} local utils = require("codetyper.support.utils") ---- Autocommand group name -local AUGROUP = "Codetyper" - ---- Debounce timer for tree updates -local tree_update_timer = nil -local TREE_UPDATE_DEBOUNCE_MS = 1000 -- 1 second debounce - ---- Track processed prompts to avoid re-processing ----@type table -local processed_prompts = {} - ---- Track if we're currently asking for preferences -local asking_preference = false - ---- Track if we're currently processing prompts (busy flag) -local is_processing = false - ---- Track the previous mode for visual mode detection -local previous_mode = "n" - ---- Debounce timer for prompt processing -local prompt_process_timer = nil -local PROMPT_PROCESS_DEBOUNCE_MS = 200 -- Wait 200ms after mode change before processing +local AUGROUP = require("codetyper.constants.constants").AUGROUP +local tree_update_timer = require("codetyper.constants.constants").tree_update_timer +local TREE_UPDATE_DEBOUNCE_MS = require("codetyper.constants.constants").TREE_UPDATE_DEBOUNCE_MS +local processed_prompts = require("codetyper.constants.constants").processed_prompts +local is_processing = require("codetyper.constants.constants").is_processing +local previous_mode = require("codetyper.constants.constants").previous_mode +local prompt_process_timer = require("codetyper.constants.constants").prompt_process_timer +local PROMPT_PROCESS_DEBOUNCE_MS = require("codetyper.constants.constants").PROMPT_PROCESS_DEBOUNCE_MS --- Generate a unique key for a prompt ---@param bufnr number Buffer number @@ -265,8 +250,8 @@ function M.setup() }) -- Thinking indicator (throbber) cleanup on exit - local thinking = require("codetyper.adapters.nvim.ui.thinking") - thinking.setup() + local thinking_setup = require("codetyper.adapters.nvim.ui.thinking.setup") + thinking_setup() end --- Create extmarks for injection range so position survives user edits (99-style). diff --git a/lua/codetyper/adapters/nvim/ui/context_modal.lua b/lua/codetyper/adapters/nvim/ui/context_modal.lua deleted file mode 100644 index fe9789b..0000000 --- a/lua/codetyper/adapters/nvim/ui/context_modal.lua +++ /dev/null @@ -1,14 +0,0 @@ ----@mod codetyper.agent.context_modal Modal for additional context input ----@brief [[ ---- Opens a floating window for user to provide additional context ---- when the LLM requests more information. ----@brief ]] - -local M = {} - -M.close = require("codetyper.adapters.nvim.ui.context_modal.close") -M.is_open = require("codetyper.adapters.nvim.ui.context_modal.is_open") -M.setup = require("codetyper.adapters.nvim.ui.context_modal.setup") -M.open = require("codetyper.adapters.nvim.ui.context_modal.open") - -return M diff --git a/lua/codetyper/adapters/nvim/ui/context_modal/open.lua b/lua/codetyper/adapters/nvim/ui/context_modal/open.lua index 366a886..ef93500 100644 --- a/lua/codetyper/adapters/nvim/ui/context_modal/open.lua +++ b/lua/codetyper/adapters/nvim/ui/context_modal/open.lua @@ -109,8 +109,8 @@ function M.open(original_event, llm_response, callback, suggested_commands) vim.cmd("startinsert") pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = "Context modal opened - waiting for user input", }) diff --git a/lua/codetyper/adapters/nvim/ui/diff_review.lua b/lua/codetyper/adapters/nvim/ui/diff_review.lua deleted file mode 100644 index 050325b..0000000 --- a/lua/codetyper/adapters/nvim/ui/diff_review.lua +++ /dev/null @@ -1,24 +0,0 @@ ----@mod codetyper.agent.diff_review Diff review UI for agent changes ---- ---- Provides a lazygit-style window interface for reviewing all changes ---- made during an agent session. - -local M = {} - -local get_config_utils = require("codetyper.utils.get_config") - -M.clear = get_config_utils.clear_diff_entries -M.add = get_config_utils.add_diff_entry -M.get_entries = get_config_utils.get_diff_entries -M.count = get_config_utils.count_diff_entries -M.next = require("codetyper.adapters.nvim.ui.diff_review.navigate_next") -M.prev = require("codetyper.adapters.nvim.ui.diff_review.navigate_prev") -M.approve_current = require("codetyper.adapters.nvim.ui.diff_review.approve_current") -M.reject_current = require("codetyper.adapters.nvim.ui.diff_review.reject_current") -M.approve_all = require("codetyper.adapters.nvim.ui.diff_review.approve_all") -M.apply_approved = require("codetyper.adapters.nvim.ui.diff_review.apply_approved") -M.close = require("codetyper.adapters.nvim.ui.diff_review.close") -M.is_open = require("codetyper.adapters.nvim.ui.diff_review.is_open") -M.open = require("codetyper.adapters.nvim.ui.diff_review.open") - -return M diff --git a/lua/codetyper/adapters/nvim/ui/logs.lua b/lua/codetyper/adapters/nvim/ui/logs.lua deleted file mode 100644 index d6edb5f..0000000 --- a/lua/codetyper/adapters/nvim/ui/logs.lua +++ /dev/null @@ -1,34 +0,0 @@ ----@mod codetyper.agent.logs Real-time logging for agent operations ---- ---- Captures and displays the agent's thinking process, token usage, and LLM info. - -local M = {} - -M.log = require("codetyper.adapters.nvim.ui.logs.log") -M.clear = require("codetyper.adapters.nvim.ui.logs.clear") -M.info = require("codetyper.adapters.nvim.ui.logs.info") -M.debug = require("codetyper.adapters.nvim.ui.logs.debug") -M.error = require("codetyper.adapters.nvim.ui.logs.error") -M.warning = require("codetyper.adapters.nvim.ui.logs.warning") -M.thinking = require("codetyper.adapters.nvim.ui.logs.thinking") -M.reason = require("codetyper.adapters.nvim.ui.logs.reason") -M.request = require("codetyper.adapters.nvim.ui.logs.request") -M.response = require("codetyper.adapters.nvim.ui.logs.response") -M.tool = require("codetyper.adapters.nvim.ui.logs.tool") -M.add = require("codetyper.adapters.nvim.ui.logs.add") -M.read = require("codetyper.adapters.nvim.ui.logs.read") -M.explore = require("codetyper.adapters.nvim.ui.logs.explore") -M.explore_done = require("codetyper.adapters.nvim.ui.logs.explore_done") -M.update = require("codetyper.adapters.nvim.ui.logs.update") -M.task = require("codetyper.adapters.nvim.ui.logs.task") -M.task_done = require("codetyper.adapters.nvim.ui.logs.task_done") -M.add_listener = require("codetyper.adapters.nvim.ui.logs.add_listener") -M.remove_listener = require("codetyper.adapters.nvim.ui.logs.remove_listener") -M.get_entries = require("codetyper.adapters.nvim.ui.logs.get_entries") -M.get_token_totals = require("codetyper.adapters.nvim.ui.logs.get_token_totals") -M.get_provider_info = require("codetyper.adapters.nvim.ui.logs.get_provider_info") -M.format_entry = require("codetyper.adapters.nvim.ui.logs.format_entry") -M.format_for_chat = require("codetyper.adapters.nvim.ui.logs.format_for_chat") -M.estimate_tokens = require("codetyper.utils.estimate_tokens") - -return M diff --git a/lua/codetyper/adapters/nvim/ui/logs_panel.lua b/lua/codetyper/adapters/nvim/ui/logs_panel.lua deleted file mode 100644 index 8dcb6b8..0000000 --- a/lua/codetyper/adapters/nvim/ui/logs_panel.lua +++ /dev/null @@ -1,12 +0,0 @@ ----@mod codetyper.logs_panel Standalone logs panel for code generation ---- -local M = {} - -M.open = require("codetyper.adapters.nvim.ui.logs_panel.open") -M.close = require("codetyper.adapters.nvim.ui.logs_panel.close") -M.toggle = require("codetyper.adapters.nvim.ui.logs_panel.toggle") -M.is_open = require("codetyper.adapters.nvim.ui.logs_panel.is_open") -M.ensure_open = require("codetyper.adapters.nvim.ui.logs_panel.ensure_open") -M.setup = require("codetyper.adapters.nvim.ui.logs_panel.setup") - -return M diff --git a/lua/codetyper/adapters/nvim/ui/logs_panel/add_log_entry.lua b/lua/codetyper/adapters/nvim/ui/logs_panel/add_log_entry.lua index e88b557..4440dbd 100644 --- a/lua/codetyper/adapters/nvim/ui/logs_panel/add_log_entry.lua +++ b/lua/codetyper/adapters/nvim/ui/logs_panel/add_log_entry.lua @@ -1,5 +1,5 @@ local state = require("codetyper.state.state") -local logs = require("codetyper.adapters.nvim.ui.logs") +local logs_format_entry = require("codetyper.adapters.nvim.ui.logs.format_entry") local constants = require("codetyper.adapters.nvim.ui.logs_panel.constants") --- Add a log entry to the panel buffer with highlighting @@ -27,7 +27,7 @@ local function add_log_entry(entry) vim.bo[state.buf].modifiable = true - local formatted = logs.format_entry(entry) + local formatted = logs_format_entry(entry) local formatted_lines = vim.split(formatted, "\n", { plain = true }) local line_count = vim.api.nvim_buf_line_count(state.buf) diff --git a/lua/codetyper/adapters/nvim/ui/logs_panel/close.lua b/lua/codetyper/adapters/nvim/ui/logs_panel/close.lua index 52b36e3..2b050dc 100644 --- a/lua/codetyper/adapters/nvim/ui/logs_panel/close.lua +++ b/lua/codetyper/adapters/nvim/ui/logs_panel/close.lua @@ -1,5 +1,5 @@ local state = require("codetyper.state.state") -local logs = require("codetyper.adapters.nvim.ui.logs") +local logs_remove_listener = require("codetyper.adapters.nvim.ui.logs.remove_listener") local queue = require("codetyper.core.events.queue") --- Close the logs panel and clean up listeners, windows, buffers @@ -10,7 +10,7 @@ local function close(force) end if state.listener_id then - pcall(logs.remove_listener, state.listener_id) + pcall(logs_remove_listener, state.listener_id) state.listener_id = nil end diff --git a/lua/codetyper/adapters/nvim/ui/logs_panel/open.lua b/lua/codetyper/adapters/nvim/ui/logs_panel/open.lua index c4393f0..4ede9b6 100644 --- a/lua/codetyper/adapters/nvim/ui/logs_panel/open.lua +++ b/lua/codetyper/adapters/nvim/ui/logs_panel/open.lua @@ -1,5 +1,7 @@ local state = require("codetyper.state.state") -local logs = require("codetyper.adapters.nvim.ui.logs") +local logs_clear = require("codetyper.adapters.nvim.ui.logs.clear") +local logs_add_listener = require("codetyper.adapters.nvim.ui.logs.add_listener") +local logs_info = require("codetyper.adapters.nvim.ui.logs.info") local queue = require("codetyper.core.events.queue") local constants = require("codetyper.adapters.nvim.ui.logs_panel.constants") local add_log_entry = require("codetyper.adapters.nvim.ui.logs_panel.add_log_entry") @@ -13,7 +15,7 @@ local function open() return end - logs.clear() + logs_clear() state.buf = vim.api.nvim_create_buf(false, true) vim.bo[state.buf].buftype = "nofile" @@ -67,7 +69,7 @@ local function open() vim.keymap.set("n", "q", close, queue_keymap_opts) vim.keymap.set("n", "", close, queue_keymap_opts) - state.listener_id = logs.add_listener(function(entry) + state.listener_id = logs_add_listener(function(entry) add_log_entry(entry) if entry.level == "response" then vim.schedule(update_title) @@ -84,7 +86,7 @@ local function open() vim.cmd("wincmd p") - logs.info("Logs panel opened") + logs_info("Logs panel opened") end return open diff --git a/lua/codetyper/adapters/nvim/ui/logs_panel/update_title.lua b/lua/codetyper/adapters/nvim/ui/logs_panel/update_title.lua index 222cf4e..52aeb64 100644 --- a/lua/codetyper/adapters/nvim/ui/logs_panel/update_title.lua +++ b/lua/codetyper/adapters/nvim/ui/logs_panel/update_title.lua @@ -1,5 +1,6 @@ local state = require("codetyper.state.state") -local logs = require("codetyper.adapters.nvim.ui.logs") +local logs_get_token_totals = require("codetyper.adapters.nvim.ui.logs.get_token_totals") +local logs_get_provider_info = require("codetyper.adapters.nvim.ui.logs.get_provider_info") --- Update the panel title with token counts and provider info local function update_title() @@ -7,8 +8,8 @@ local function update_title() return end - local prompt_tokens, response_tokens = logs.get_token_totals() - local provider, _ = logs.get_provider_info() + local prompt_tokens, response_tokens = logs_get_token_totals() + local provider, _ = logs_get_provider_info() if provider and state.buf and vim.api.nvim_buf_is_valid(state.buf) then vim.bo[state.buf].modifiable = true diff --git a/lua/codetyper/adapters/nvim/ui/thinking.lua b/lua/codetyper/adapters/nvim/ui/thinking.lua deleted file mode 100644 index 1bb4aa8..0000000 --- a/lua/codetyper/adapters/nvim/ui/thinking.lua +++ /dev/null @@ -1,179 +0,0 @@ ----@mod codetyper.ui.thinking Thinking indicator (99-style status window + throbber) ----@brief [[ ---- Shows a small top-right floating window with animated spinner while prompts are processing. ---- Replaces opening the full logs panel during code generation. ----@brief ]] - -local M = {} - -local throbber = require("codetyper.adapters.nvim.ui.throbber") -local queue = require("codetyper.core.events.queue") - ----@class ThinkingState ----@field win_id number|nil ----@field buf_id number|nil ----@field throbber Throbber|nil ----@field queue_listener_id number|nil ----@field timer number|nil Defer timer for polling - -local state = { - win_id = nil, - buf_id = nil, - throbber = nil, - queue_listener_id = nil, - timer = nil, - stage_text = "Thinking...", -} - -local function get_ui_dimensions() - local ui = vim.api.nvim_list_uis()[1] - if ui then - return ui.width, ui.height - end - return vim.o.columns, vim.o.lines -end - ---- Top-right status window config (like 99) -local function status_window_config() - local width, _ = get_ui_dimensions() - local win_width = math.min(40, math.floor(width / 3)) - return { - relative = "editor", - row = 0, - col = width, - width = win_width, - height = 2, - anchor = "NE", - style = "minimal", - border = nil, - zindex = 100, - } -end - -local function active_count() - return queue.pending_count() + queue.processing_count() -end - -local function close_window() - if state.timer then - pcall(vim.fn.timer_stop, state.timer) - state.timer = nil - end - if state.throbber then - state.throbber:stop() - state.throbber = nil - end - if state.queue_listener_id then - queue.remove_listener(state.queue_listener_id) - state.queue_listener_id = nil - end - if state.win_id and vim.api.nvim_win_is_valid(state.win_id) then - vim.api.nvim_win_close(state.win_id, true) - end - if state.buf_id and vim.api.nvim_buf_is_valid(state.buf_id) then - vim.api.nvim_buf_delete(state.buf_id, { force = true }) - end - state.win_id = nil - state.buf_id = nil -end - -local function update_display(icon, force) - if not state.buf_id or not vim.api.nvim_buf_is_valid(state.buf_id) then - return - end - local count = active_count() - if count <= 0 and not force then - return - end - local text = state.stage_text or "Thinking..." - local line = (count <= 1) and (icon .. " " .. text) - or (icon .. " " .. text .. " (" .. tostring(count) .. " requests)") - vim.schedule(function() - if state.buf_id and vim.api.nvim_buf_is_valid(state.buf_id) then - vim.bo[state.buf_id].modifiable = true - vim.api.nvim_buf_set_lines(state.buf_id, 0, -1, false, { line }) - vim.bo[state.buf_id].modifiable = false - end - end) -end - -local function check_and_hide() - if active_count() > 0 then - return - end - close_window() -end - ---- Ensure the thinking status window is shown and throbber is running. ---- Call when starting prompt processing (instead of logs_panel.ensure_open). -function M.ensure_shown() - if state.win_id and vim.api.nvim_win_is_valid(state.win_id) then - -- Already shown; throbber keeps running - return - end - - state.buf_id = vim.api.nvim_create_buf(false, true) - vim.bo[state.buf_id].buftype = "nofile" - vim.bo[state.buf_id].bufhidden = "wipe" - vim.bo[state.buf_id].swapfile = false - - local config = status_window_config() - state.win_id = vim.api.nvim_open_win(state.buf_id, false, config) - vim.wo[state.win_id].wrap = true - vim.wo[state.win_id].number = false - vim.wo[state.win_id].relativenumber = false - - state.throbber = throbber.new(function(icon) - update_display(icon) - -- When active count drops to 0, hide after a short delay - if active_count() <= 0 then - vim.defer_fn(check_and_hide, 300) - end - end) - state.throbber:start() - - -- Queue listener: when queue updates, check if we should hide - state.queue_listener_id = queue.add_listener(function(_, _, _) - vim.schedule(function() - if active_count() <= 0 then - vim.defer_fn(check_and_hide, 400) - end - end) - end) - - -- Initial line (force show before enqueue so window is not empty) - local icon = (state.throbber and state.throbber.icon_set and state.throbber.icon_set[1]) or "⠋" - update_display(icon, true) -end - ---- Update the displayed stage text (e.g. "Reading context...", "Sending to LLM..."). ----@param text string -function M.update_stage(text) - state.stage_text = text -end - ---- Force close the thinking window (e.g. on VimLeavePre). -function M.close() - state.stage_text = "Thinking..." - close_window() -end - ---- Check if thinking window is currently visible. ----@return boolean -function M.is_shown() - return state.win_id ~= nil and vim.api.nvim_win_is_valid(state.win_id) -end - ---- Register autocmds for cleanup on exit. -function M.setup() - local group = vim.api.nvim_create_augroup("CodetyperThinking", { clear = true }) - vim.api.nvim_create_autocmd("VimLeavePre", { - group = group, - callback = function() - M.close() - end, - desc = "Close thinking window before exiting Neovim", - }) -end - -return M diff --git a/lua/codetyper/adapters/nvim/ui/thinking/active_count.lua b/lua/codetyper/adapters/nvim/ui/thinking/active_count.lua new file mode 100644 index 0000000..81068d8 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/active_count.lua @@ -0,0 +1,9 @@ +local queue = require("codetyper.core.events.queue") + +--- Get the total number of pending + processing queue items +---@return number +local function active_count() + return queue.pending_count() + queue.processing_count() +end + +return active_count diff --git a/lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua b/lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua new file mode 100644 index 0000000..0e7dc88 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua @@ -0,0 +1,12 @@ +local active_count = require("codetyper.adapters.nvim.ui.thinking.active_count") +local close_window = require("codetyper.adapters.nvim.ui.thinking.close_window") + +--- Hide the thinking window if no active queue items remain +local function check_and_hide() + if active_count() > 0 then + return + end + close_window() +end + +return check_and_hide diff --git a/lua/codetyper/adapters/nvim/ui/thinking/close.lua b/lua/codetyper/adapters/nvim/ui/thinking/close.lua new file mode 100644 index 0000000..e59cabe --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/close.lua @@ -0,0 +1,10 @@ +local state = require("codetyper.state.state") +local close_window = require("codetyper.adapters.nvim.ui.thinking.close_window") + +--- Force close the thinking window and reset stage text +local function close() + state.stage_text = "Thinking..." + close_window() +end + +return close diff --git a/lua/codetyper/adapters/nvim/ui/thinking/close_window.lua b/lua/codetyper/adapters/nvim/ui/thinking/close_window.lua new file mode 100644 index 0000000..12a7877 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/close_window.lua @@ -0,0 +1,28 @@ +local state = require("codetyper.state.state") +local queue = require("codetyper.core.events.queue") + +--- Tear down the thinking window, throbber, timer, and queue listener +local function close_window() + if state.timer then + pcall(vim.fn.timer_stop, state.timer) + state.timer = nil + end + if state.throbber then + state.throbber:stop() + state.throbber = nil + end + if state.queue_listener_id then + queue.remove_listener(state.queue_listener_id) + state.queue_listener_id = nil + end + if state.win_id and vim.api.nvim_win_is_valid(state.win_id) then + vim.api.nvim_win_close(state.win_id, true) + end + if state.buf_id and vim.api.nvim_buf_is_valid(state.buf_id) then + vim.api.nvim_buf_delete(state.buf_id, { force = true }) + end + state.win_id = nil + state.buf_id = nil +end + +return close_window diff --git a/lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua b/lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua new file mode 100644 index 0000000..3e4dab9 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua @@ -0,0 +1,47 @@ +local state = require("codetyper.state.state") +local throbber_new = require("codetyper.adapters.nvim.ui.throbber.new") +local queue = require("codetyper.core.events.queue") +local status_window_config = require("codetyper.adapters.nvim.ui.thinking.status_window_config") +local update_display = require("codetyper.adapters.nvim.ui.thinking.update_display") +local check_and_hide = require("codetyper.adapters.nvim.ui.thinking.check_and_hide") +local active_count = require("codetyper.adapters.nvim.ui.thinking.active_count") + +--- Ensure the thinking status window is shown and throbber is running. +--- Call when starting prompt processing. +local function ensure_shown() + if state.win_id and vim.api.nvim_win_is_valid(state.win_id) then + return + end + + state.buf_id = vim.api.nvim_create_buf(false, true) + vim.bo[state.buf_id].buftype = "nofile" + vim.bo[state.buf_id].bufhidden = "wipe" + vim.bo[state.buf_id].swapfile = false + + local config = status_window_config() + state.win_id = vim.api.nvim_open_win(state.buf_id, false, config) + vim.wo[state.win_id].wrap = true + vim.wo[state.win_id].number = false + vim.wo[state.win_id].relativenumber = false + + state.throbber = throbber_new(function(icon) + update_display(icon) + if active_count() <= 0 then + vim.defer_fn(check_and_hide, 300) + end + end) + state.throbber:start() + + state.queue_listener_id = queue.add_listener(function(_, _, _) + vim.schedule(function() + if active_count() <= 0 then + vim.defer_fn(check_and_hide, 400) + end + end) + end) + + local initial_icon = (state.throbber and state.throbber.icon_set and state.throbber.icon_set[1]) or "⠋" + update_display(initial_icon, true) +end + +return ensure_shown diff --git a/lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua b/lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua new file mode 100644 index 0000000..7925c1e --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua @@ -0,0 +1,9 @@ +local state = require("codetyper.state.state") + +--- Check if the thinking window is currently visible +---@return boolean +local function is_shown() + return state.win_id ~= nil and vim.api.nvim_win_is_valid(state.win_id) +end + +return is_shown diff --git a/lua/codetyper/adapters/nvim/ui/thinking/setup.lua b/lua/codetyper/adapters/nvim/ui/thinking/setup.lua new file mode 100644 index 0000000..8683baa --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/setup.lua @@ -0,0 +1,15 @@ +local close = require("codetyper.adapters.nvim.ui.thinking.close") + +--- Register autocmds for cleanup on exit +local function setup() + local group = vim.api.nvim_create_augroup("CodetyperThinking", { clear = true }) + vim.api.nvim_create_autocmd("VimLeavePre", { + group = group, + callback = function() + close() + end, + desc = "Close thinking window before exiting Neovim", + }) +end + +return setup diff --git a/lua/codetyper/adapters/nvim/ui/thinking/status_window_config.lua b/lua/codetyper/adapters/nvim/ui/thinking/status_window_config.lua new file mode 100644 index 0000000..9eedc8b --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/status_window_config.lua @@ -0,0 +1,21 @@ +local get_ui_dimensions = require("codetyper.utils.get_config") + +--- Build the floating window config for the top-right status indicator +---@return table +local function status_window_config() + local width, _ = get_ui_dimensions() + local win_width = math.min(40, math.floor(width / 3)) + return { + relative = "editor", + row = 0, + col = width, + width = win_width, + height = 2, + anchor = "NE", + style = "minimal", + border = nil, + zindex = 100, + } +end + +return status_window_config diff --git a/lua/codetyper/adapters/nvim/ui/thinking/update_display.lua b/lua/codetyper/adapters/nvim/ui/thinking/update_display.lua new file mode 100644 index 0000000..bb89687 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/update_display.lua @@ -0,0 +1,27 @@ +local state = require("codetyper.state.state") +local active_count = require("codetyper.adapters.nvim.ui.thinking.active_count") + +--- Update the thinking window buffer text with spinner icon and request count +---@param icon string Spinner icon character +---@param force? boolean Show even when active count is 0 +local function update_display(icon, force) + if not state.buf_id or not vim.api.nvim_buf_is_valid(state.buf_id) then + return + end + local count = active_count() + if count <= 0 and not force then + return + end + local text = state.stage_text or "Thinking..." + local line = (count <= 1) and (icon .. " " .. text) + or (icon .. " " .. text .. " (" .. tostring(count) .. " requests)") + vim.schedule(function() + if state.buf_id and vim.api.nvim_buf_is_valid(state.buf_id) then + vim.bo[state.buf_id].modifiable = true + vim.api.nvim_buf_set_lines(state.buf_id, 0, -1, false, { line }) + vim.bo[state.buf_id].modifiable = false + end + end) +end + +return update_display diff --git a/lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua b/lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua new file mode 100644 index 0000000..dbc119a --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua @@ -0,0 +1,9 @@ +local state = require("codetyper.state.state") + +--- Update the displayed stage text (e.g. "Reading context...", "Sending to LLM...") +---@param text string +local function update_stage(text) + state.stage_text = text +end + +return update_stage diff --git a/lua/codetyper/adapters/nvim/ui/throbber.lua b/lua/codetyper/adapters/nvim/ui/throbber.lua deleted file mode 100644 index 7b53e5d..0000000 --- a/lua/codetyper/adapters/nvim/ui/throbber.lua +++ /dev/null @@ -1,87 +0,0 @@ ----@mod codetyper.ui.throbber Animated thinking spinner (99-style) ----@brief [[ ---- Unicode throbber icons, runs a timer and calls cb(icon) every tick. ----@brief ]] - -local M = {} - -local throb_icons = { - { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, - { "◐", "◓", "◑", "◒" }, - { "⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷" }, - { "◰", "◳", "◲", "◱" }, - { "◜", "◠", "◝", "◞", "◡", "◟" }, -} - -local throb_time = 1200 -local cooldown_time = 100 -local tick_time = 100 - -local function now() - return vim.uv and vim.uv.now() or (os.clock() * 1000) -end - ----@class Throbber ----@field state "init"|"throbbing"|"cooldown"|"stopped" ----@field start_time number ----@field section_time number ----@field opts { throb_time: number, cooldown_time: number } ----@field cb fun(icon: string) ----@field icon_set string[] ----@field _run fun(self: Throbber) - -local Throbber = {} -Throbber.__index = Throbber - ----@param cb fun(icon: string) ----@param opts? { throb_time?: number, cooldown_time?: number } ----@return Throbber -function M.new(cb, opts) - opts = opts or {} - local throb_time_ms = opts.throb_time or throb_time - local cooldown_ms = opts.cooldown_time or cooldown_time - local icon_set = throb_icons[math.random(#throb_icons)] - return setmetatable({ - state = "init", - start_time = 0, - section_time = throb_time_ms, - opts = { throb_time = throb_time_ms, cooldown_time = cooldown_ms }, - cb = cb, - icon_set = icon_set, - }, Throbber) -end - -function Throbber:_run() - if self.state ~= "throbbing" and self.state ~= "cooldown" then - return - end - local elapsed = now() - self.start_time - local percent = math.min(1, elapsed / self.section_time) - local idx = math.floor(percent * #self.icon_set) + 1 - idx = math.min(idx, #self.icon_set) - local icon = self.icon_set[idx] - - if percent >= 1 then - self.state = self.state == "cooldown" and "throbbing" or "cooldown" - self.start_time = now() - self.section_time = (self.state == "cooldown") and self.opts.cooldown_time or self.opts.throb_time - end - - self.cb(icon) - vim.defer_fn(function() - self:_run() - end, tick_time) -end - -function Throbber:start() - self.start_time = now() - self.section_time = self.opts.throb_time - self.state = "throbbing" - self:_run() -end - -function Throbber:stop() - self.state = "stopped" -end - -return M diff --git a/lua/codetyper/adapters/nvim/ui/throbber/class.lua b/lua/codetyper/adapters/nvim/ui/throbber/class.lua new file mode 100644 index 0000000..700e660 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/throbber/class.lua @@ -0,0 +1,13 @@ +---@class Throbber +---@field state "init"|"throbbing"|"cooldown"|"stopped" +---@field start_time number +---@field section_time number +---@field opts { throb_time: number, cooldown_time: number } +---@field cb fun(icon: string) +---@field icon_set string[] +---@field _run fun(self: Throbber) + +local Throbber = require("codetyper.constants.constants").Throbber +Throbber.__index = Throbber + +return Throbber diff --git a/lua/codetyper/adapters/nvim/ui/throbber/new.lua b/lua/codetyper/adapters/nvim/ui/throbber/new.lua new file mode 100644 index 0000000..3b70b16 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/throbber/new.lua @@ -0,0 +1,28 @@ +local Throbber = require("codetyper.adapters.nvim.ui.throbber.class") +local throb_icons = require("codetyper.constants.constants").throb_icons +local throb_time = require("codetyper.constants.constants").throb_time +local cooldown_time = require("codetyper.constants.constants").cooldown_time + +Throbber._run = require("codetyper.adapters.nvim.ui.throbber.run") +Throbber.start = require("codetyper.adapters.nvim.ui.throbber.start") +Throbber.stop = require("codetyper.adapters.nvim.ui.throbber.stop") + +---@param cb fun(icon: string) +---@param opts? { throb_time?: number, cooldown_time?: number } +---@return Throbber +local function new(cb, opts) + opts = opts or {} + local throb_time_ms = opts.throb_time or throb_time + local cooldown_ms = opts.cooldown_time or cooldown_time + local icon_set = throb_icons[math.random(#throb_icons)] + return setmetatable({ + state = "init", + start_time = 0, + section_time = throb_time_ms, + opts = { throb_time = throb_time_ms, cooldown_time = cooldown_ms }, + cb = cb, + icon_set = icon_set, + }, Throbber) +end + +return new diff --git a/lua/codetyper/adapters/nvim/ui/throbber/run.lua b/lua/codetyper/adapters/nvim/ui/throbber/run.lua new file mode 100644 index 0000000..307ab12 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/throbber/run.lua @@ -0,0 +1,28 @@ +local now = require("codetyper.utils.get_now") +local tick_time = require("codetyper.constants.constants").tick_time + +--- Internal tick loop: compute current icon, transition state, schedule next tick +---@param self Throbber +local function run(self) + if self.state ~= "throbbing" and self.state ~= "cooldown" then + return + end + local elapsed = now() - self.start_time + local percent = math.min(1, elapsed / self.section_time) + local icon_index = math.floor(percent * #self.icon_set) + 1 + icon_index = math.min(icon_index, #self.icon_set) + local icon = self.icon_set[icon_index] + + if percent >= 1 then + self.state = self.state == "cooldown" and "throbbing" or "cooldown" + self.start_time = now() + self.section_time = (self.state == "cooldown") and self.opts.cooldown_time or self.opts.throb_time + end + + self.cb(icon) + vim.defer_fn(function() + self:_run() + end, tick_time) +end + +return run diff --git a/lua/codetyper/adapters/nvim/ui/throbber/start.lua b/lua/codetyper/adapters/nvim/ui/throbber/start.lua new file mode 100644 index 0000000..7356115 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/throbber/start.lua @@ -0,0 +1,12 @@ +local now = require("codetyper.utils.get_now") + +--- Begin the throbbing animation +---@param self Throbber +local function start(self) + self.start_time = now() + self.section_time = self.opts.throb_time + self.state = "throbbing" + self:_run() +end + +return start diff --git a/lua/codetyper/adapters/nvim/ui/throbber/stop.lua b/lua/codetyper/adapters/nvim/ui/throbber/stop.lua new file mode 100644 index 0000000..c71e630 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/throbber/stop.lua @@ -0,0 +1,7 @@ +--- Stop the throbbing animation +---@param self Throbber +local function stop(self) + self.state = "stopped" +end + +return stop diff --git a/lua/codetyper/constants/config.lua b/lua/codetyper/constants/config.lua new file mode 100644 index 0000000..4950aad --- /dev/null +++ b/lua/codetyper/constants/config.lua @@ -0,0 +1,14 @@ +local config = { + enabled = true, + auto_trigger = true, + debounce = 150, + use_copilot = true, -- Use copilot when available + keymap = { + accept = "", + next = "", + prev = "", + dismiss = "", + }, +} + +return config diff --git a/lua/codetyper/constants/constants.lua b/lua/codetyper/constants/constants.lua new file mode 100644 index 0000000..cb76dee --- /dev/null +++ b/lua/codetyper/constants/constants.lua @@ -0,0 +1,23 @@ +local M = {} + +M.throb_time = 1200 +M.cooldown_time = 100 +M.tick_time = 100 +M.Throbber = {} +M.AUGROUP = "Codetyper" +M.tree_update_timer = nil +M.TREE_UPDATE_DEBOUNCE_MS = 1000 -- 1 second debounce +M.processed_prompts = {} +M.asking_preference = false +M.is_processing = false +M.previous_mode = "n" +M.prompt_process_timer = nil +M.PROMPT_PROCESS_DEBOUNCE_MS = 200 -- Wait 200ms after mode change before processing +M.hl_group = "CmpGhostText" +M.throb_icons = { + { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, + { "◐", "◓", "◑", "◒" }, + { "⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷" }, +} + +return M diff --git a/lua/codetyper/config/defaults.lua b/lua/codetyper/constants/defaults.lua similarity index 100% rename from lua/codetyper/config/defaults.lua rename to lua/codetyper/constants/defaults.lua diff --git a/lua/codetyper/constants/prices.lua b/lua/codetyper/constants/prices.lua new file mode 100644 index 0000000..26b3372 --- /dev/null +++ b/lua/codetyper/constants/prices.lua @@ -0,0 +1,88 @@ +--- Model pricing table (per 1M tokens in USD) +---@type table +local pricing = { + -- GPT-5.x series + ["gpt-5.2"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, + ["gpt-5.1"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5-mini"] = { input = 0.25, cached_input = 0.025, output = 2.00 }, + ["gpt-5-nano"] = { input = 0.05, cached_input = 0.005, output = 0.40 }, + ["gpt-5.2-chat-latest"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, + ["gpt-5.1-chat-latest"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5-chat-latest"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5.2-codex"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, + ["gpt-5.1-codex-max"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5.1-codex"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5-codex"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + ["gpt-5.2-pro"] = { input = 21.00, cached_input = nil, output = 168.00 }, + ["gpt-5-pro"] = { input = 15.00, cached_input = nil, output = 120.00 }, + ["gpt-5.1-codex-mini"] = { input = 0.25, cached_input = 0.025, output = 2.00 }, + ["gpt-5-search-api"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, + + -- GPT-4.x series + ["gpt-4.1"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, + ["gpt-4.1-mini"] = { input = 0.40, cached_input = 0.10, output = 1.60 }, + ["gpt-4.1-nano"] = { input = 0.10, cached_input = 0.025, output = 0.40 }, + ["gpt-4o"] = { input = 2.50, cached_input = 1.25, output = 10.00 }, + ["gpt-4o-2024-05-13"] = { input = 5.00, cached_input = nil, output = 15.00 }, + ["gpt-4o-mini"] = { input = 0.15, cached_input = 0.075, output = 0.60 }, + + -- Realtime models + ["gpt-realtime"] = { input = 4.00, cached_input = 0.40, output = 16.00 }, + ["gpt-realtime-mini"] = { input = 0.60, cached_input = 0.06, output = 2.40 }, + ["gpt-4o-realtime-preview"] = { input = 5.00, cached_input = 2.50, output = 20.00 }, + ["gpt-4o-mini-realtime-preview"] = { input = 0.60, cached_input = 0.30, output = 2.40 }, + + -- Audio models + ["gpt-audio"] = { input = 2.50, cached_input = nil, output = 10.00 }, + ["gpt-audio-mini"] = { input = 0.60, cached_input = nil, output = 2.40 }, + ["gpt-4o-audio-preview"] = { input = 2.50, cached_input = nil, output = 10.00 }, + ["gpt-4o-mini-audio-preview"] = { input = 0.15, cached_input = nil, output = 0.60 }, + + -- O-series reasoning models + ["o1"] = { input = 15.00, cached_input = 7.50, output = 60.00 }, + ["o1-pro"] = { input = 150.00, cached_input = nil, output = 600.00 }, + ["o3-pro"] = { input = 20.00, cached_input = nil, output = 80.00 }, + ["o3"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, + ["o3-deep-research"] = { input = 10.00, cached_input = 2.50, output = 40.00 }, + ["o4-mini"] = { input = 1.10, cached_input = 0.275, output = 4.40 }, + ["o4-mini-deep-research"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, + ["o3-mini"] = { input = 1.10, cached_input = 0.55, output = 4.40 }, + ["o1-mini"] = { input = 1.10, cached_input = 0.55, output = 4.40 }, + + -- Codex + ["codex-mini-latest"] = { input = 1.50, cached_input = 0.375, output = 6.00 }, + + -- Search models + ["gpt-4o-mini-search-preview"] = { input = 0.15, cached_input = nil, output = 0.60 }, + ["gpt-4o-search-preview"] = { input = 2.50, cached_input = nil, output = 10.00 }, + + -- Computer use + ["computer-use-preview"] = { input = 3.00, cached_input = nil, output = 12.00 }, + + -- Image models + ["gpt-image-1.5"] = { input = 5.00, cached_input = 1.25, output = 10.00 }, + ["chatgpt-image-latest"] = { input = 5.00, cached_input = 1.25, output = 10.00 }, + ["gpt-image-1"] = { input = 5.00, cached_input = 1.25, output = nil }, + ["gpt-image-1-mini"] = { input = 2.00, cached_input = 0.20, output = nil }, + + -- Claude models + ["claude-3-opus"] = { input = 15.00, cached_input = 7.50, output = 75.00 }, + ["claude-3-sonnet"] = { input = 3.00, cached_input = 1.50, output = 15.00 }, + ["claude-3-haiku"] = { input = 0.25, cached_input = 0.125, output = 1.25 }, + ["claude-3.5-sonnet"] = { input = 3.00, cached_input = 1.50, output = 15.00 }, + ["claude-3.5-haiku"] = { input = 0.80, cached_input = 0.40, output = 4.00 }, + + -- Ollama/Local models (free) + ["ollama"] = { input = 0, cached_input = 0, output = 0 }, + ["codellama"] = { input = 0, cached_input = 0, output = 0 }, + ["llama2"] = { input = 0, cached_input = 0, output = 0 }, + ["llama3"] = { input = 0, cached_input = 0, output = 0 }, + ["mistral"] = { input = 0, cached_input = 0, output = 0 }, + ["deepseek-coder"] = { input = 0, cached_input = 0, output = 0 }, + + -- Copilot (included in subscription, but tracking usage) + ["copilot"] = { input = 0, cached_input = 0, output = 0 }, +} + +return pricing diff --git a/lua/codetyper/core/cost/init.lua b/lua/codetyper/core/cost/init.lua index be6fbca..4a4799f 100644 --- a/lua/codetyper/core/cost/init.lua +++ b/lua/codetyper/core/cost/init.lua @@ -32,116 +32,9 @@ M.free_models = { ["copilot"] = true, } ---- Model pricing table (per 1M tokens in USD) ----@type table -M.pricing = { - -- GPT-5.x series - ["gpt-5.2"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, - ["gpt-5.1"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5-mini"] = { input = 0.25, cached_input = 0.025, output = 2.00 }, - ["gpt-5-nano"] = { input = 0.05, cached_input = 0.005, output = 0.40 }, - ["gpt-5.2-chat-latest"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, - ["gpt-5.1-chat-latest"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5-chat-latest"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5.2-codex"] = { input = 1.75, cached_input = 0.175, output = 14.00 }, - ["gpt-5.1-codex-max"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5.1-codex"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5-codex"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, - ["gpt-5.2-pro"] = { input = 21.00, cached_input = nil, output = 168.00 }, - ["gpt-5-pro"] = { input = 15.00, cached_input = nil, output = 120.00 }, - ["gpt-5.1-codex-mini"] = { input = 0.25, cached_input = 0.025, output = 2.00 }, - ["gpt-5-search-api"] = { input = 1.25, cached_input = 0.125, output = 10.00 }, +M.pricing = require("codetyper.constants.prices") - -- GPT-4.x series - ["gpt-4.1"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, - ["gpt-4.1-mini"] = { input = 0.40, cached_input = 0.10, output = 1.60 }, - ["gpt-4.1-nano"] = { input = 0.10, cached_input = 0.025, output = 0.40 }, - ["gpt-4o"] = { input = 2.50, cached_input = 1.25, output = 10.00 }, - ["gpt-4o-2024-05-13"] = { input = 5.00, cached_input = nil, output = 15.00 }, - ["gpt-4o-mini"] = { input = 0.15, cached_input = 0.075, output = 0.60 }, - - -- Realtime models - ["gpt-realtime"] = { input = 4.00, cached_input = 0.40, output = 16.00 }, - ["gpt-realtime-mini"] = { input = 0.60, cached_input = 0.06, output = 2.40 }, - ["gpt-4o-realtime-preview"] = { input = 5.00, cached_input = 2.50, output = 20.00 }, - ["gpt-4o-mini-realtime-preview"] = { input = 0.60, cached_input = 0.30, output = 2.40 }, - - -- Audio models - ["gpt-audio"] = { input = 2.50, cached_input = nil, output = 10.00 }, - ["gpt-audio-mini"] = { input = 0.60, cached_input = nil, output = 2.40 }, - ["gpt-4o-audio-preview"] = { input = 2.50, cached_input = nil, output = 10.00 }, - ["gpt-4o-mini-audio-preview"] = { input = 0.15, cached_input = nil, output = 0.60 }, - - -- O-series reasoning models - ["o1"] = { input = 15.00, cached_input = 7.50, output = 60.00 }, - ["o1-pro"] = { input = 150.00, cached_input = nil, output = 600.00 }, - ["o3-pro"] = { input = 20.00, cached_input = nil, output = 80.00 }, - ["o3"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, - ["o3-deep-research"] = { input = 10.00, cached_input = 2.50, output = 40.00 }, - ["o4-mini"] = { input = 1.10, cached_input = 0.275, output = 4.40 }, - ["o4-mini-deep-research"] = { input = 2.00, cached_input = 0.50, output = 8.00 }, - ["o3-mini"] = { input = 1.10, cached_input = 0.55, output = 4.40 }, - ["o1-mini"] = { input = 1.10, cached_input = 0.55, output = 4.40 }, - - -- Codex - ["codex-mini-latest"] = { input = 1.50, cached_input = 0.375, output = 6.00 }, - - -- Search models - ["gpt-4o-mini-search-preview"] = { input = 0.15, cached_input = nil, output = 0.60 }, - ["gpt-4o-search-preview"] = { input = 2.50, cached_input = nil, output = 10.00 }, - - -- Computer use - ["computer-use-preview"] = { input = 3.00, cached_input = nil, output = 12.00 }, - - -- Image models - ["gpt-image-1.5"] = { input = 5.00, cached_input = 1.25, output = 10.00 }, - ["chatgpt-image-latest"] = { input = 5.00, cached_input = 1.25, output = 10.00 }, - ["gpt-image-1"] = { input = 5.00, cached_input = 1.25, output = nil }, - ["gpt-image-1-mini"] = { input = 2.00, cached_input = 0.20, output = nil }, - - -- Claude models - ["claude-3-opus"] = { input = 15.00, cached_input = 7.50, output = 75.00 }, - ["claude-3-sonnet"] = { input = 3.00, cached_input = 1.50, output = 15.00 }, - ["claude-3-haiku"] = { input = 0.25, cached_input = 0.125, output = 1.25 }, - ["claude-3.5-sonnet"] = { input = 3.00, cached_input = 1.50, output = 15.00 }, - ["claude-3.5-haiku"] = { input = 0.80, cached_input = 0.40, output = 4.00 }, - - -- Ollama/Local models (free) - ["ollama"] = { input = 0, cached_input = 0, output = 0 }, - ["codellama"] = { input = 0, cached_input = 0, output = 0 }, - ["llama2"] = { input = 0, cached_input = 0, output = 0 }, - ["llama3"] = { input = 0, cached_input = 0, output = 0 }, - ["mistral"] = { input = 0, cached_input = 0, output = 0 }, - ["deepseek-coder"] = { input = 0, cached_input = 0, output = 0 }, - - -- Copilot (included in subscription, but tracking usage) - ["copilot"] = { input = 0, cached_input = 0, output = 0 }, -} - ----@class CostUsage ----@field model string Model name ----@field input_tokens number Input tokens used ----@field output_tokens number Output tokens used ----@field cached_tokens number Cached input tokens ----@field timestamp number Unix timestamp ----@field cost number Calculated cost in USD - ----@class CostState ----@field usage CostUsage[] Current session usage ----@field all_usage CostUsage[] All historical usage from brain ----@field session_start number Session start timestamp ----@field win number|nil Window handle ----@field buf number|nil Buffer handle ----@field loaded boolean Whether historical data has been loaded -local state = { - usage = {}, - all_usage = {}, - session_start = os.time(), - win = nil, - buf = nil, - loaded = false, -} +local state = require("codetyper.state.state") --- Load historical usage from disk function M.load_from_history() diff --git a/lua/codetyper/core/diff/conflict.lua b/lua/codetyper/core/diff/conflict.lua index a85d873..6a7597d 100644 --- a/lua/codetyper/core/diff/conflict.lua +++ b/lua/codetyper/core/diff/conflict.lua @@ -66,8 +66,8 @@ local function validate_after_accept(bufnr, start_line, end_line, accepted_type) -- If errors found and auto-fix is enabled, queue fix automatically if result.has_errors and config.auto_fix_lint_errors then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = "Auto-queuing fix for lint errors...", }) @@ -974,8 +974,8 @@ function M.process(bufnr) -- Log pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.info(string.format("Found %d conflict(s) - use co/ct/cb/cn to resolve, [x/]x to navigate", #conflicts)) + local logs_info = require("codetyper.adapters.nvim.ui.logs.info") + logs_info(string.format("Found %d conflict(s) - use co/ct/cb/cn to resolve, [x/]x to navigate", #conflicts)) end) else -- No conflicts - clean up diff --git a/lua/codetyper/core/diff/patch.lua b/lua/codetyper/core/diff/patch.lua index cf3d4c0..ee280f3 100644 --- a/lua/codetyper/core/diff/patch.lua +++ b/lua/codetyper/core/diff/patch.lua @@ -161,8 +161,8 @@ function M.queue_patch(patch) -- Log patch creation pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "patch", message = string.format("Patch queued: %s (confidence: %.2f)", patch.id, patch.confidence or 0), data = { @@ -230,8 +230,8 @@ function M.create_from_event(event, generated_code, confidence, strategy) } ) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "Using override strategy: %s (%s)", @@ -244,8 +244,8 @@ function M.create_from_event(event, generated_code, confidence, strategy) elseif use_search_replace then injection_strategy = "search_replace" pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Using SEARCH/REPLACE mode with %d block(s)", #sr_blocks), }) @@ -268,8 +268,8 @@ function M.create_from_event(event, generated_code, confidence, strategy) end injection_range = { start_line = start_line, end_line = end_line } pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Inline: replace lines %d-%d", start_line, end_line), }) @@ -287,8 +287,8 @@ function M.create_from_event(event, generated_code, confidence, strategy) end_line = event.range.end_line, } pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "Inline prompt: will replace tag region (lines %d-%d)", @@ -308,8 +308,8 @@ function M.create_from_event(event, generated_code, confidence, strategy) end_line = event.range.end_line, } pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "warning", message = "No scope found, using tag range as fallback", }) @@ -562,8 +562,8 @@ function M.apply(patch) M.mark_applied(patch.id) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "success", message = string.format( "Patch %s applied via SEARCH/REPLACE (%d block(s))", @@ -600,8 +600,8 @@ function M.apply(patch) else -- SEARCH/REPLACE failed: use only REPLACE parts for fallback (never inject raw markers) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "warning", message = string.format( "SEARCH/REPLACE failed: %s. Using REPLACE content only for injection.", @@ -663,8 +663,8 @@ function M.apply(patch) start_line = sr + 1 end_line = er + 1 pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Applying at extmark range (lines %d-%d)", start_line, end_line), }) @@ -746,8 +746,8 @@ function M.apply(patch) -- Log inline prompt handling if is_inline_prompt then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "Inline prompt: replacing lines %d-%d", @@ -787,9 +787,9 @@ function M.apply(patch) -- Log injection details pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") if inject_result.imports_added > 0 then - logs.add({ + logs_add({ type = "info", message = string.format( "%s %d import(s), injected %d body line(s)", @@ -799,7 +799,7 @@ function M.apply(patch) ), }) else - logs.add({ + logs_add({ type = "info", message = string.format("Injected %d line(s) of code", inject_result.body_lines), }) @@ -819,8 +819,8 @@ function M.apply(patch) M.mark_applied(patch.id) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "success", message = string.format("Patch %s applied successfully", patch.id), data = { @@ -1046,8 +1046,8 @@ function M.apply_with_conflict(patch) M.mark_applied(patch.id) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "success", message = string.format("Created %d conflict(s) for review - use co/ct/cb/cn to resolve", applied_count), }) @@ -1072,8 +1072,8 @@ function M.apply_with_conflict(patch) M.mark_applied(patch.id) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "success", message = "Created conflict for review - use co/ct/cb/cn to resolve", }) diff --git a/lua/codetyper/core/events/queue.lua b/lua/codetyper/core/events/queue.lua index f1ae55d..d8820d3 100644 --- a/lua/codetyper/core/events/queue.lua +++ b/lua/codetyper/core/events/queue.lua @@ -190,8 +190,8 @@ function M.enqueue(event) -- Log to agent logs if available pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "queue", message = string.format("Event queued: %s (priority: %d)", event.id, event.priority), data = { diff --git a/lua/codetyper/core/llm/selector.lua b/lua/codetyper/core/llm/selector.lua index 7251dcf..7d2ff56 100644 --- a/lua/codetyper/core/llm/selector.lua +++ b/lua/codetyper/core/llm/selector.lua @@ -383,8 +383,8 @@ function M.smart_generate(prompt, context, callback) -- Log selection pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "LLM: %s (confidence: %.1f%%, %s)", diff --git a/lua/codetyper/core/scheduler/executor.lua b/lua/codetyper/core/scheduler/executor.lua index 257aacf..2791a3c 100644 --- a/lua/codetyper/core/scheduler/executor.lua +++ b/lua/codetyper/core/scheduler/executor.lua @@ -4,7 +4,8 @@ local M = {} local utils = require("codetyper.support.utils") -local logs = require("codetyper.adapters.nvim.ui.logs") +local logs_add = require("codetyper.adapters.nvim.ui.logs.add") +local logs_read = require("codetyper.adapters.nvim.ui.logs.read") ---@class ExecutionResult ---@field success boolean Whether the execution succeeded @@ -104,14 +105,14 @@ function M.handle_read_file(params, callback) -- Log the read operation in Claude Code style local relative_path = vim.fn.fnamemodify(path, ":~:.") - logs.read(relative_path) + logs_read(relative_path) local content = utils.read_file(path) if content then -- Log how many lines were read local lines = vim.split(content, "\n", { plain = true }) - logs.add({ type = "result", message = string.format(" ⎿ Read %d lines", #lines) }) + logs_add({ type = "result", message = string.format(" ⎿ Read %d lines", #lines) }) -- Open the file in a buffer so user can see it open_file_in_buffer(path) @@ -122,7 +123,7 @@ function M.handle_read_file(params, callback) requires_approval = false, }) else - logs.add({ type = "error", message = " ⎿ File not found" }) + logs_add({ type = "error", message = " ⎿ File not found" }) callback({ success = false, result = "Could not read file: " .. path, @@ -139,12 +140,12 @@ function M.handle_edit_file(params, callback) local relative_path = vim.fn.fnamemodify(path, ":~:.") -- Log the edit operation - logs.add({ type = "action", message = string.format("Edit(%s)", relative_path) }) + logs_add({ type = "action", message = string.format("Edit(%s)", relative_path) }) local original = utils.read_file(path) if not original then - logs.add({ type = "error", message = " ⎿ File not found" }) + logs_add({ type = "error", message = " ⎿ File not found" }) callback({ success = false, result = "File not found: " .. path, @@ -158,7 +159,7 @@ function M.handle_edit_file(params, callback) local new_content, count = original:gsub(escaped_find, params.replace, 1) if count == 0 then - logs.add({ type = "error", message = " ⎿ Content not found" }) + logs_add({ type = "error", message = " ⎿ Content not found" }) callback({ success = false, result = "Could not find content to replace in: " .. path, @@ -172,11 +173,11 @@ function M.handle_edit_file(params, callback) local new_lines = #vim.split(new_content, "\n", { plain = true }) local diff = new_lines - original_lines if diff > 0 then - logs.add({ type = "result", message = string.format(" ⎿ +%d lines (pending approval)", diff) }) + logs_add({ type = "result", message = string.format(" ⎿ +%d lines (pending approval)", diff) }) elseif diff < 0 then - logs.add({ type = "result", message = string.format(" ⎿ %d lines (pending approval)", diff) }) + logs_add({ type = "result", message = string.format(" ⎿ %d lines (pending approval)", diff) }) else - logs.add({ type = "result", message = " ⎿ Modified (pending approval)" }) + logs_add({ type = "result", message = " ⎿ Modified (pending approval)" }) end -- Requires user approval - show diff @@ -204,20 +205,20 @@ function M.handle_write_file(params, callback) -- Log the write operation if operation == "create" then - logs.add({ type = "action", message = string.format("Write(%s)", relative_path) }) + logs_add({ type = "action", message = string.format("Write(%s)", relative_path) }) local new_lines = #vim.split(params.content, "\n", { plain = true }) - logs.add({ type = "result", message = string.format(" ⎿ New file (%d lines, pending approval)", new_lines) }) + logs_add({ type = "result", message = string.format(" ⎿ New file (%d lines, pending approval)", new_lines) }) else - logs.add({ type = "action", message = string.format("Update(%s)", relative_path) }) + logs_add({ type = "action", message = string.format("Update(%s)", relative_path) }) local original_lines = #vim.split(original, "\n", { plain = true }) local new_lines = #vim.split(params.content, "\n", { plain = true }) local diff = new_lines - original_lines if diff > 0 then - logs.add({ type = "result", message = string.format(" ⎿ +%d lines (pending approval)", diff) }) + logs_add({ type = "result", message = string.format(" ⎿ +%d lines (pending approval)", diff) }) elseif diff < 0 then - logs.add({ type = "result", message = string.format(" ⎿ %d lines (pending approval)", diff) }) + logs_add({ type = "result", message = string.format(" ⎿ %d lines (pending approval)", diff) }) else - logs.add({ type = "result", message = " ⎿ Modified (pending approval)" }) + logs_add({ type = "result", message = " ⎿ Modified (pending approval)" }) end end @@ -247,11 +248,11 @@ function M.handle_bash(params, callback) local command = params.command -- Log the bash operation - logs.add({ + logs_add({ type = "action", message = string.format("Bash(%s)", command:sub(1, 50) .. (#command > 50 and "..." or "")), }) - logs.add({ type = "result", message = " ⎿ Pending approval" }) + logs_add({ type = "result", message = " ⎿ Pending approval" }) -- Requires user approval first callback({ diff --git a/lua/codetyper/core/scheduler/loop.lua b/lua/codetyper/core/scheduler/loop.lua index d89f1c0..55d0500 100644 --- a/lua/codetyper/core/scheduler/loop.lua +++ b/lua/codetyper/core/scheduler/loop.lua @@ -280,8 +280,8 @@ function M.run(opts) local tool_opts = { on_log = function(msg) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ type = "tool", message = msg }) + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "tool", message = msg }) end) end, on_complete = function(result, err) diff --git a/lua/codetyper/core/scheduler/scheduler.lua b/lua/codetyper/core/scheduler/scheduler.lua index a97afb6..cf2f6e8 100644 --- a/lua/codetyper/core/scheduler/scheduler.lua +++ b/lua/codetyper/core/scheduler/scheduler.lua @@ -10,21 +10,15 @@ local queue = require("codetyper.core.events.queue") local patch = require("codetyper.core.diff.patch") local worker = require("codetyper.core.scheduler.worker") local confidence_mod = require("codetyper.core.llm.confidence") -local context_modal = require("codetyper.adapters.nvim.ui.context_modal") -local params = require("codetyper.params.agents.scheduler") +local context_modal_setup = require("codetyper.adapters.nvim.ui.context_modal.setup") +local context_modal_open = require("codetyper.adapters.nvim.ui.context_modal.open") local logger = require("codetyper.support.logger") -- Setup context modal cleanup on exit -context_modal.setup() +context_modal_setup() --- Scheduler state -local state = { - running = false, - timer = nil, - poll_interval = 100, -- ms - paused = false, - config = params.config, -} +local state = require("codetyper.state.state") --- Autocommand group for injection timing local augroup = nil @@ -139,8 +133,8 @@ local function retry_with_context(original_event, additional_context, attached_f -- Log the retry pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Retrying with additional context (original: %s)", original_event.id), }) @@ -234,8 +228,8 @@ local function handle_worker_result(event, result) if result.needs_context then require("codetyper.core.thinking_placeholder").remove_on_failure(event.id) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Event %s: LLM needs more context, opening modal", event.id), }) @@ -284,14 +278,14 @@ local function handle_worker_result(event, result) local suggested_cmds = detect_suggested_commands(result.response or "") if suggested_cmds and #suggested_cmds > 0 then -- Open modal and show suggested commands for user approval - context_modal.open(result.original_event or event, result.response or "", retry_with_context, suggested_cmds) + context_modal_open(result.original_event or event, result.response or "", retry_with_context, suggested_cmds) queue.update_status(event.id, "needs_context", { response = result.response }) return end if requested and #requested > 0 then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ type = "info", message = string.format("Auto-attaching %d requested file(s)", #requested) }) + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Auto-attaching %d requested file(s)", #requested) }) end) -- Build attached_files entries @@ -322,7 +316,7 @@ local function handle_worker_result(event, result) end -- If no files parsed, open modal for manual context entry - context_modal.open(result.original_event or event, result.response or "", retry_with_context) + context_modal_open(result.original_event or event, result.response or "", retry_with_context) -- Mark original event as needing context (not failed) queue.update_status(event.id, "needs_context", { response = result.response }) @@ -335,8 +329,8 @@ local function handle_worker_result(event, result) -- Failed - try escalation if this was ollama if result.worker_type == "ollama" and event.attempt_count < 2 then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Escalating event %s to remote provider (ollama failed)", event.id), }) @@ -359,8 +353,8 @@ local function handle_worker_result(event, result) if needs_escalation and result.worker_type == "ollama" and event.attempt_count < 2 then -- Low confidence from ollama - escalate to remote pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "Escalating event %s to remote provider (confidence: %.2f < %.2f)", @@ -381,8 +375,8 @@ local function handle_worker_result(event, result) pcall(function() local tp = require("codetyper.core.thinking_placeholder") tp.update_inline_status(event.id, "Generating patch...") - local thinking = require("codetyper.adapters.nvim.ui.thinking") - thinking.update_stage("Generating patch...") + local thinking_update_stage = require("codetyper.adapters.nvim.ui.thinking.update_stage") + thinking_update_stage("Generating patch...") end) vim.notify("Generating patch...", vim.log.levels.INFO) @@ -396,14 +390,14 @@ local function handle_worker_result(event, result) pcall(function() local tp = require("codetyper.core.thinking_placeholder") tp.update_inline_status(event.id, "Applying code...") - local thinking = require("codetyper.adapters.nvim.ui.thinking") - thinking.update_stage("Applying code...") + local thinking_update_stage = require("codetyper.adapters.nvim.ui.thinking.update_stage") + thinking_update_stage("Applying code...") end) vim.notify("Applying code...", vim.log.levels.INFO) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Code ready. Applying in %.1f seconds...", delay / 1000), }) @@ -435,8 +429,8 @@ local function dispatch_next() local should_skip, skip_reason = queue.check_precedence(event) if should_skip then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "warning", message = string.format("Event %s skipped: %s", event.id, skip_reason or "conflict"), }) @@ -451,13 +445,13 @@ local function dispatch_next() -- Log dispatch with intent/scope info pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") local intent_info = event.intent and event.intent.type or "unknown" local scope_info = event.scope and event.scope.type ~= "file" and string.format("%s:%s", event.scope.type, event.scope.name or "anon") or "file" - logs.add({ + logs_add({ type = "info", message = string.format( "Dispatching %s [intent: %s, scope: %s, provider: %s]", @@ -470,8 +464,8 @@ local function dispatch_next() end) -- Show thinking indicator: top-right window (always) + in-buffer or 99-style inline - local thinking = require("codetyper.adapters.nvim.ui.thinking") - thinking.ensure_shown() + local thinking_ensure_shown = require("codetyper.adapters.nvim.ui.thinking.ensure_shown") + thinking_ensure_shown() local is_inline = event.target_path and not event.target_path:match("%.codetyper%.") @@ -677,8 +671,8 @@ function M.start(config) scheduler_loop() pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = "Scheduler started", data = { @@ -709,8 +703,8 @@ function M.stop() end pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = "Scheduler stopped", }) diff --git a/lua/codetyper/core/scheduler/worker.lua b/lua/codetyper/core/scheduler/worker.lua index a7f2a18..179874a 100644 --- a/lua/codetyper/core/scheduler/worker.lua +++ b/lua/codetyper/core/scheduler/worker.lua @@ -41,8 +41,8 @@ local function notify_stage(event_id, text) end end) pcall(function() - local thinking = require("codetyper.adapters.nvim.ui.thinking") - thinking.update_stage(text) + local thinking_update_stage = require("codetyper.adapters.nvim.ui.thinking.update_stage") + thinking_update_stage(text) end) vim.notify(text, vim.log.levels.INFO) end @@ -751,8 +751,8 @@ function M.create(event, worker_type, callback) -- Log worker creation pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "worker", message = string.format("Worker %s started (%s)", worker.id, worker_type), data = { @@ -805,8 +805,8 @@ function M.start(worker) worker.worker_type = usage_or_metadata.provider if usage_or_metadata.pondered then pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format( "Pondering: %s (agreement: %.0f%%)", @@ -848,8 +848,8 @@ function M.complete(worker, response, error, usage) active_workers[worker.id] = nil pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "error", message = string.format("Worker %s failed: %s", worker.id, error), }) @@ -874,8 +874,8 @@ function M.complete(worker, response, error, usage) active_workers[worker.id] = nil pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Worker %s: LLM needs more context", worker.id), }) @@ -898,8 +898,8 @@ function M.complete(worker, response, error, usage) -- Log the full raw LLM response (for debugging) pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "response", message = "--- LLM Response ---", data = { @@ -919,8 +919,8 @@ function M.complete(worker, response, error, usage) active_workers[worker.id] = nil pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "success", message = string.format( "Worker %s completed (%.2fs, confidence: %.2f - %s)", @@ -961,8 +961,8 @@ function M.cancel(worker_id) active_workers[worker_id] = nil pcall(function() - local logs = require("codetyper.adapters.nvim.ui.logs") - logs.add({ + local logs_add = require("codetyper.adapters.nvim.ui.logs.add") + logs_add({ type = "info", message = string.format("Worker %s cancelled", worker_id), }) diff --git a/lua/codetyper/core/thinking_placeholder.lua b/lua/codetyper/core/thinking_placeholder.lua index 047c58e..ccd197b 100644 --- a/lua/codetyper/core/thinking_placeholder.lua +++ b/lua/codetyper/core/thinking_placeholder.lua @@ -138,8 +138,8 @@ function M.start_inline(event) virt_lines = { { { " Implementing", "Comment" } } }, virt_lines_above = true, }) - local Throbber = require("codetyper.adapters.nvim.ui.throbber") - local throb = Throbber.new(function(icon) + local throbber_new = require("codetyper.adapters.nvim.ui.throbber.new") + local throb = throbber_new(function(icon) if not inline_status[event.id] then return end diff --git a/lua/codetyper/features/completion/suggestion.lua b/lua/codetyper/features/completion/suggestion.lua index b427133..95e910f 100644 --- a/lua/codetyper/features/completion/suggestion.lua +++ b/lua/codetyper/features/completion/suggestion.lua @@ -7,48 +7,10 @@ local M = {} ----@class SuggestionState ----@field current_suggestion string|nil Current suggestion text ----@field suggestions string[] List of available suggestions ----@field current_index number Current suggestion index ----@field extmark_id number|nil Virtual text extmark ID ----@field bufnr number|nil Buffer where suggestion is shown ----@field line number|nil Line where suggestion is shown ----@field col number|nil Column where suggestion starts ----@field timer any|nil Debounce timer ----@field using_copilot boolean Whether currently using copilot - -local state = { - current_suggestion = nil, - suggestions = {}, - current_index = 0, - extmark_id = nil, - bufnr = nil, - line = nil, - col = nil, - timer = nil, - using_copilot = false, -} - ---- Namespace for virtual text +local state = require("codetyper.state.state") local ns = vim.api.nvim_create_namespace("codetyper_suggestion") - ---- Highlight group for ghost text -local hl_group = "CmpGhostText" - ---- Configuration -local config = { - enabled = true, - auto_trigger = true, - debounce = 150, - use_copilot = true, -- Use copilot when available - keymap = { - accept = "", - next = "", - prev = "", - dismiss = "", - }, -} +local hl_group = require("codetyper.constants.constants").hl_group +local config = require("codetyper.constants.config") --- Check if copilot is available and enabled ---@return boolean, table|nil available, copilot_suggestion module diff --git a/lua/codetyper/init.lua b/lua/codetyper/init.lua index 4462051..5ed5494 100644 --- a/lua/codetyper/init.lua +++ b/lua/codetyper/init.lua @@ -24,7 +24,7 @@ function M.setup(opts) return end - local config = require("codetyper.config.defaults") + local config = require("codetyper.constants.defaults") M.config = config.setup(opts) -- Initialize modules diff --git a/lua/codetyper/state/state.lua b/lua/codetyper/state/state.lua index 731df68..c780ffa 100644 --- a/lua/codetyper/state/state.lua +++ b/lua/codetyper/state/state.lua @@ -1,3 +1,5 @@ +local params = require("codetyper.params.agents.scheduler") + local state = { buf = nil, win = nil, @@ -21,6 +23,26 @@ local state = { queue_win = nil, listener_id = nil, queue_listener_id = nil, + win_id = nil, + buf_id = nil, + throbber = nil, + timer = nil, + stage_text = "Thinking...", + current_suggestion = nil, + suggestions = {}, + extmark_id = nil, + bufnr = nil, + line = nil, + col = nil, + using_copilot = false, + usage = {}, + all_usage = {}, + session_start = os.time(), + loaded = false, + running = false, + poll_interval = 100, -- ms + paused = false, + config = params.config, } return state diff --git a/lua/codetyper/utils/get_config.lua b/lua/codetyper/utils/get_config.lua index 73d1c53..18bd58f 100644 --- a/lua/codetyper/utils/get_config.lua +++ b/lua/codetyper/utils/get_config.lua @@ -8,7 +8,7 @@ function M.get_config() if codetyper_loaded and codetyper.get_config then return codetyper.get_config() or {} end - local defaults = require("codetyper.config.defaults") + local defaults = require("codetyper.constants.defaults") return defaults.get_defaults() end @@ -36,4 +36,12 @@ function M.count_diff_entries() return #state.entries end +function M.get_ui_dimensions() + local ui = vim.api.nvim_list_uis()[1] + if ui then + return ui.width, ui.height + end + return vim.o.columns, vim.o.lines +end + return M diff --git a/lua/codetyper/utils/get_now.lua b/lua/codetyper/utils/get_now.lua new file mode 100644 index 0000000..e2e00d4 --- /dev/null +++ b/lua/codetyper/utils/get_now.lua @@ -0,0 +1,5 @@ +local function now() + return vim.uv and vim.uv.now() or (os.clock() * 1000) +end + +return now