Adding the migration to pure files only

This commit is contained in:
2026-03-24 22:31:49 -04:00
parent 69c8061b8e
commit 75de3198cd
46 changed files with 576 additions and 653 deletions

View File

@@ -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).

View File

@@ -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

View File

@@ -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",
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,7 @@
--- Stop the throbbing animation
---@param self Throbber
local function stop(self)
self.state = "stopped"
end
return stop

View 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

View 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

View 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

View File

@@ -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()

View File

@@ -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

View File

@@ -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",
})

View File

@@ -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 = {

View File

@@ -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)",

View File

@@ -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({

View File

@@ -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)

View File

@@ -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",
})

View File

@@ -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),
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
local function now()
return vim.uv and vim.uv.now() or (os.clock() * 1000)
end
return now