Adding the migration to pure files only
This commit is contained in:
@@ -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<string, boolean>
|
||||
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).
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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", "<Esc>", 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
9
lua/codetyper/adapters/nvim/ui/thinking/active_count.lua
Normal file
9
lua/codetyper/adapters/nvim/ui/thinking/active_count.lua
Normal file
@@ -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
|
||||
12
lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua
Normal file
12
lua/codetyper/adapters/nvim/ui/thinking/check_and_hide.lua
Normal file
@@ -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
|
||||
10
lua/codetyper/adapters/nvim/ui/thinking/close.lua
Normal file
10
lua/codetyper/adapters/nvim/ui/thinking/close.lua
Normal file
@@ -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
|
||||
28
lua/codetyper/adapters/nvim/ui/thinking/close_window.lua
Normal file
28
lua/codetyper/adapters/nvim/ui/thinking/close_window.lua
Normal file
@@ -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
|
||||
47
lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua
Normal file
47
lua/codetyper/adapters/nvim/ui/thinking/ensure_shown.lua
Normal file
@@ -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
|
||||
9
lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua
Normal file
9
lua/codetyper/adapters/nvim/ui/thinking/is_shown.lua
Normal file
@@ -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
|
||||
15
lua/codetyper/adapters/nvim/ui/thinking/setup.lua
Normal file
15
lua/codetyper/adapters/nvim/ui/thinking/setup.lua
Normal file
@@ -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
|
||||
@@ -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
|
||||
27
lua/codetyper/adapters/nvim/ui/thinking/update_display.lua
Normal file
27
lua/codetyper/adapters/nvim/ui/thinking/update_display.lua
Normal file
@@ -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
|
||||
9
lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua
Normal file
9
lua/codetyper/adapters/nvim/ui/thinking/update_stage.lua
Normal file
@@ -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
|
||||
@@ -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
|
||||
13
lua/codetyper/adapters/nvim/ui/throbber/class.lua
Normal file
13
lua/codetyper/adapters/nvim/ui/throbber/class.lua
Normal file
@@ -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
|
||||
28
lua/codetyper/adapters/nvim/ui/throbber/new.lua
Normal file
28
lua/codetyper/adapters/nvim/ui/throbber/new.lua
Normal file
@@ -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
|
||||
28
lua/codetyper/adapters/nvim/ui/throbber/run.lua
Normal file
28
lua/codetyper/adapters/nvim/ui/throbber/run.lua
Normal file
@@ -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
|
||||
12
lua/codetyper/adapters/nvim/ui/throbber/start.lua
Normal file
12
lua/codetyper/adapters/nvim/ui/throbber/start.lua
Normal file
@@ -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
|
||||
7
lua/codetyper/adapters/nvim/ui/throbber/stop.lua
Normal file
7
lua/codetyper/adapters/nvim/ui/throbber/stop.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
--- Stop the throbbing animation
|
||||
---@param self Throbber
|
||||
local function stop(self)
|
||||
self.state = "stopped"
|
||||
end
|
||||
|
||||
return stop
|
||||
14
lua/codetyper/constants/config.lua
Normal file
14
lua/codetyper/constants/config.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
local config = {
|
||||
enabled = true,
|
||||
auto_trigger = true,
|
||||
debounce = 150,
|
||||
use_copilot = true, -- Use copilot when available
|
||||
keymap = {
|
||||
accept = "<Tab>",
|
||||
next = "<M-]>",
|
||||
prev = "<M-[>",
|
||||
dismiss = "<C-]>",
|
||||
},
|
||||
}
|
||||
|
||||
return config
|
||||
23
lua/codetyper/constants/constants.lua
Normal file
23
lua/codetyper/constants/constants.lua
Normal file
@@ -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
|
||||
88
lua/codetyper/constants/prices.lua
Normal file
88
lua/codetyper/constants/prices.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
--- Model pricing table (per 1M tokens in USD)
|
||||
---@type table<string, {input: number, cached_input: number|nil, output: number|nil}>
|
||||
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
|
||||
@@ -32,116 +32,9 @@ M.free_models = {
|
||||
["copilot"] = true,
|
||||
}
|
||||
|
||||
--- Model pricing table (per 1M tokens in USD)
|
||||
---@type table<string, {input: number, cached_input: number|nil, output: number|nil}>
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = "<Tab>",
|
||||
next = "<M-]>",
|
||||
prev = "<M-[>",
|
||||
dismiss = "<C-]>",
|
||||
},
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
5
lua/codetyper/utils/get_now.lua
Normal file
5
lua/codetyper/utils/get_now.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
local function now()
|
||||
return vim.uv and vim.uv.now() or (os.clock() * 1000)
|
||||
end
|
||||
|
||||
return now
|
||||
Reference in New Issue
Block a user