migrating autocmds
This commit is contained in:
File diff suppressed because it is too large
Load Diff
252
lua/codetyper/adapters/nvim/autocmds/auto_index_file.lua
Normal file
252
lua/codetyper/adapters/nvim/autocmds/auto_index_file.lua
Normal file
@@ -0,0 +1,252 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local autocmds_state = require("codetyper.adapters.nvim.autocmds.state")
|
||||
local is_supported_extension = require("codetyper.adapters.nvim.autocmds.is_supported_extension")
|
||||
local should_ignore_for_coder = require("codetyper.adapters.nvim.autocmds.should_ignore_for_coder")
|
||||
|
||||
--- Auto-index a file by creating/opening its coder companion
|
||||
---@param bufnr number Buffer number
|
||||
local function auto_index_file(bufnr)
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
|
||||
if autocmds_state.auto_indexed_buffers[bufnr] then
|
||||
return
|
||||
end
|
||||
|
||||
local filepath = vim.api.nvim_buf_get_name(bufnr)
|
||||
if not filepath or filepath == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if utils.is_coder_file(filepath) then
|
||||
return
|
||||
end
|
||||
|
||||
local buftype = vim.bo[bufnr].buftype
|
||||
if buftype ~= "" then
|
||||
return
|
||||
end
|
||||
|
||||
local ext = vim.fn.fnamemodify(filepath, ":e")
|
||||
if ext == "" or not is_supported_extension(ext) then
|
||||
return
|
||||
end
|
||||
|
||||
if should_ignore_for_coder(filepath) then
|
||||
return
|
||||
end
|
||||
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
if config and config.auto_index == false then
|
||||
return
|
||||
end
|
||||
|
||||
autocmds_state.auto_indexed_buffers[bufnr] = true
|
||||
|
||||
local coder_path = utils.get_coder_path(filepath)
|
||||
|
||||
local coder_exists = utils.file_exists(coder_path)
|
||||
|
||||
if not coder_exists then
|
||||
local filename = vim.fn.fnamemodify(filepath, ":t")
|
||||
local file_ext = vim.fn.fnamemodify(filepath, ":e")
|
||||
|
||||
local comment_prefix = "--"
|
||||
local comment_block_start = "--[["
|
||||
local comment_block_end = "]]"
|
||||
if
|
||||
file_ext == "ts"
|
||||
or file_ext == "tsx"
|
||||
or file_ext == "js"
|
||||
or file_ext == "jsx"
|
||||
or file_ext == "java"
|
||||
or file_ext == "c"
|
||||
or file_ext == "cpp"
|
||||
or file_ext == "cs"
|
||||
or file_ext == "go"
|
||||
or file_ext == "rs"
|
||||
then
|
||||
comment_prefix = "//"
|
||||
comment_block_start = "/*"
|
||||
comment_block_end = "*/"
|
||||
elseif file_ext == "py" or file_ext == "rb" or file_ext == "yaml" or file_ext == "yml" then
|
||||
comment_prefix = "#"
|
||||
comment_block_start = '"""'
|
||||
comment_block_end = '"""'
|
||||
end
|
||||
|
||||
local content = ""
|
||||
pcall(function()
|
||||
local lines = vim.fn.readfile(filepath)
|
||||
if lines then
|
||||
content = table.concat(lines, "\n")
|
||||
end
|
||||
end)
|
||||
|
||||
local functions = extract_functions(content, file_ext)
|
||||
local classes = extract_classes(content, file_ext)
|
||||
local imports = extract_imports(content, file_ext)
|
||||
|
||||
local pseudo_code = {}
|
||||
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ═══════════════════════════════════════════════════════════"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " CODER COMPANION: " .. filename)
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ═══════════════════════════════════════════════════════════"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " This file describes the business logic and behavior of " .. filename)
|
||||
table.insert(pseudo_code, comment_prefix .. " Edit this pseudo-code to guide code generation.")
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " MODULE PURPOSE:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " TODO: Describe what this module/file is responsible for")
|
||||
table.insert(pseudo_code, comment_prefix .. ' Example: "Handles user authentication and session management"')
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
|
||||
if #imports > 0 then
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " DEPENDENCIES:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
for _, imp in ipairs(imports) do
|
||||
table.insert(pseudo_code, comment_prefix .. " • " .. imp)
|
||||
end
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
end
|
||||
|
||||
if #classes > 0 then
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " CLASSES:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
for _, class in ipairs(classes) do
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
table.insert(pseudo_code, comment_prefix .. " class " .. class.name .. ":")
|
||||
table.insert(pseudo_code, comment_prefix .. " PURPOSE: TODO - describe what this class represents")
|
||||
table.insert(pseudo_code, comment_prefix .. " RESPONSIBILITIES:")
|
||||
table.insert(pseudo_code, comment_prefix .. " - TODO: list main responsibilities")
|
||||
end
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
end
|
||||
|
||||
if #functions > 0 then
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " FUNCTIONS:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
for _, func in ipairs(functions) do
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
table.insert(pseudo_code, comment_prefix .. " " .. func.name .. "():")
|
||||
table.insert(pseudo_code, comment_prefix .. " PURPOSE: TODO - what does this function do?")
|
||||
table.insert(pseudo_code, comment_prefix .. " INPUTS: TODO - describe parameters")
|
||||
table.insert(pseudo_code, comment_prefix .. " OUTPUTS: TODO - describe return value")
|
||||
table.insert(pseudo_code, comment_prefix .. " BEHAVIOR:")
|
||||
table.insert(pseudo_code, comment_prefix .. " - TODO: describe step-by-step logic")
|
||||
end
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
end
|
||||
|
||||
if #functions == 0 and #classes == 0 then
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " PLANNED STRUCTURE:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " TODO: Describe what you want to build in this file")
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
table.insert(pseudo_code, comment_prefix .. " Example pseudo-code:")
|
||||
|
||||
table.insert(pseudo_code, comment_prefix .. " Create a module that:")
|
||||
table.insert(pseudo_code, comment_prefix .. " 1. Exports a main function")
|
||||
table.insert(pseudo_code, comment_prefix .. " 2. Handles errors gracefully")
|
||||
table.insert(pseudo_code, comment_prefix .. " 3. Returns structured data")
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
end
|
||||
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " BUSINESS RULES:")
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ─────────────────────────────────────────────────────────────"
|
||||
)
|
||||
table.insert(pseudo_code, comment_prefix .. " TODO: Document any business rules, constraints, or requirements")
|
||||
table.insert(pseudo_code, comment_prefix .. " Example:")
|
||||
table.insert(pseudo_code, comment_prefix .. " - Users must be authenticated before accessing this feature")
|
||||
table.insert(pseudo_code, comment_prefix .. " - Data must be validated before saving")
|
||||
table.insert(pseudo_code, comment_prefix .. " - Errors should be logged but not exposed to users")
|
||||
table.insert(pseudo_code, comment_prefix .. "")
|
||||
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ═══════════════════════════════════════════════════════════"
|
||||
)
|
||||
table.insert(
|
||||
pseudo_code,
|
||||
comment_prefix
|
||||
.. " ═══════════════════════════════════════════════════════════"
|
||||
)
|
||||
table.insert(pseudo_code, "")
|
||||
|
||||
utils.write_file(coder_path, table.concat(pseudo_code, "\n"))
|
||||
end
|
||||
|
||||
local coder_filename = vim.fn.fnamemodify(coder_path, ":t")
|
||||
if coder_exists then
|
||||
utils.notify("Coder companion available: " .. coder_filename, vim.log.levels.DEBUG)
|
||||
else
|
||||
utils.notify("Created coder companion: " .. coder_filename, vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
return auto_index_file
|
||||
32
lua/codetyper/adapters/nvim/autocmds/check_all_prompts.lua
Normal file
32
lua/codetyper/adapters/nvim/autocmds/check_all_prompts.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
local process_single_prompt = require("codetyper.adapters.nvim.autocmds.process_single_prompt")
|
||||
|
||||
--- Check and process all closed prompts in the buffer
|
||||
local function check_all_prompts()
|
||||
local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer")
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local current_file = vim.fn.expand("%:p")
|
||||
|
||||
if current_file == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local prompts = find_prompts_in_buffer(bufnr)
|
||||
|
||||
if #prompts == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local codetyper = require("codetyper")
|
||||
local ct_config = codetyper.get_config()
|
||||
local scheduler_enabled = ct_config and ct_config.scheduler and ct_config.scheduler.enabled
|
||||
|
||||
if not scheduler_enabled then
|
||||
return
|
||||
end
|
||||
|
||||
for _, prompt in ipairs(prompts) do
|
||||
process_single_prompt(bufnr, prompt, current_file)
|
||||
end
|
||||
end
|
||||
|
||||
return check_all_prompts
|
||||
@@ -0,0 +1,35 @@
|
||||
local processed_prompts = require("codetyper.constants.constants").processed_prompts
|
||||
local get_prompt_key = require("codetyper.adapters.nvim.autocmds.get_prompt_key")
|
||||
local check_all_prompts = require("codetyper.adapters.nvim.autocmds.check_all_prompts")
|
||||
|
||||
--- Check all prompts with preference check
|
||||
--- Only processes if there are unprocessed prompts and auto_process is enabled
|
||||
local function check_all_prompts_with_preference()
|
||||
local preferences = require("codetyper.config.preferences")
|
||||
local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer")
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local prompts = find_prompts_in_buffer(bufnr)
|
||||
if #prompts == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local has_unprocessed = false
|
||||
for _, prompt in ipairs(prompts) do
|
||||
local prompt_key = get_prompt_key(bufnr, prompt)
|
||||
if not processed_prompts[prompt_key] then
|
||||
has_unprocessed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not has_unprocessed then
|
||||
return
|
||||
end
|
||||
|
||||
if auto_process then
|
||||
check_all_prompts()
|
||||
end
|
||||
end
|
||||
|
||||
return check_all_prompts_with_preference
|
||||
187
lua/codetyper/adapters/nvim/autocmds/check_for_closed_prompt.lua
Normal file
187
lua/codetyper/adapters/nvim/autocmds/check_for_closed_prompt.lua
Normal file
@@ -0,0 +1,187 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local processed_prompts = require("codetyper.constants.constants").processed_prompts
|
||||
local is_processing = require("codetyper.constants.constants").is_processing
|
||||
local get_prompt_key = require("codetyper.adapters.nvim.autocmds.get_prompt_key")
|
||||
local read_attached_files = require("codetyper.adapters.nvim.autocmds.read_attached_files")
|
||||
local create_injection_marks = require("codetyper.adapters.nvim.autocmds.create_injection_marks")
|
||||
|
||||
--- Check if the buffer has a newly closed prompt and auto-process
|
||||
function check_for_closed_prompt()
|
||||
if is_processing then
|
||||
return
|
||||
end
|
||||
is_processing = true
|
||||
|
||||
local has_closing_tag = require("codetyper.parser.has_closing_tag")
|
||||
local get_last_prompt = require("codetyper.parser.get_last_prompt")
|
||||
local clean_prompt = require("codetyper.parser.clean_prompt")
|
||||
local strip_file_references = require("codetyper.parser.strip_file_references")
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local current_file = vim.fn.expand("%:p")
|
||||
|
||||
if current_file == "" then
|
||||
is_processing = false
|
||||
return
|
||||
end
|
||||
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local line = cursor[1]
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, line - 1, line, false)
|
||||
|
||||
if #lines == 0 then
|
||||
is_processing = false
|
||||
return
|
||||
end
|
||||
|
||||
local current_line = lines[1]
|
||||
|
||||
if has_closing_tag(current_line, config.patterns.close_tag) then
|
||||
local prompt = get_last_prompt(bufnr)
|
||||
if prompt and prompt.content and prompt.content ~= "" then
|
||||
local prompt_key = get_prompt_key(bufnr, prompt)
|
||||
|
||||
if processed_prompts[prompt_key] then
|
||||
is_processing = false
|
||||
return
|
||||
end
|
||||
|
||||
processed_prompts[prompt_key] = true
|
||||
|
||||
local codetyper = require("codetyper")
|
||||
local ct_config = codetyper.get_config()
|
||||
local scheduler_enabled = ct_config and ct_config.scheduler and ct_config.scheduler.enabled
|
||||
|
||||
if scheduler_enabled then
|
||||
vim.schedule(function()
|
||||
local queue = require("codetyper.core.events.queue")
|
||||
local patch_mod = require("codetyper.core.diff.patch")
|
||||
local intent_mod = require("codetyper.core.intent")
|
||||
local scope_mod = require("codetyper.core.scope")
|
||||
|
||||
local snapshot = patch_mod.snapshot_buffer(bufnr, {
|
||||
start_line = prompt.start_line,
|
||||
end_line = prompt.end_line,
|
||||
})
|
||||
|
||||
local target_path
|
||||
if utils.is_coder_file(current_file) then
|
||||
target_path = utils.get_target_path(current_file)
|
||||
else
|
||||
target_path = current_file
|
||||
end
|
||||
|
||||
local attached_files = read_attached_files(prompt.content, current_file)
|
||||
|
||||
local cleaned = clean_prompt(strip_file_references(prompt.content))
|
||||
|
||||
local is_from_coder_file = utils.is_coder_file(current_file)
|
||||
|
||||
local target_bufnr = vim.fn.bufnr(target_path)
|
||||
local scope = nil
|
||||
local scope_text = nil
|
||||
local scope_range = nil
|
||||
|
||||
if not is_from_coder_file then
|
||||
if target_bufnr == -1 then
|
||||
target_bufnr = bufnr
|
||||
end
|
||||
scope = scope_mod.resolve_scope(target_bufnr, prompt.start_line, 1)
|
||||
if scope and scope.type ~= "file" then
|
||||
scope_text = scope.text
|
||||
scope_range = {
|
||||
start_line = scope.range.start_row,
|
||||
end_line = scope.range.end_row,
|
||||
}
|
||||
end
|
||||
else
|
||||
if target_bufnr == -1 then
|
||||
target_bufnr = vim.fn.bufadd(target_path)
|
||||
if target_bufnr ~= 0 then
|
||||
vim.fn.bufload(target_bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local intent = intent_mod.detect(cleaned)
|
||||
|
||||
if not is_from_coder_file and scope and (scope.type == "function" or scope.type == "method") then
|
||||
if intent.type == "add" or intent.action == "insert" or intent.action == "append" then
|
||||
intent = {
|
||||
type = "complete",
|
||||
scope_hint = "function",
|
||||
confidence = intent.confidence,
|
||||
action = "replace",
|
||||
keywords = intent.keywords,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if is_from_coder_file and (intent.action == "replace" or intent.type == "complete") then
|
||||
intent = {
|
||||
type = intent.type == "complete" and "add" or intent.type,
|
||||
confidence = intent.confidence,
|
||||
action = "append",
|
||||
keywords = intent.keywords,
|
||||
}
|
||||
end
|
||||
|
||||
local priority = 2
|
||||
if intent.type == "fix" or intent.type == "complete" then
|
||||
priority = 1
|
||||
elseif intent.type == "test" or intent.type == "document" then
|
||||
priority = 3
|
||||
end
|
||||
|
||||
local raw_start = (prompt.injection_range and prompt.injection_range.start_line) or prompt.start_line or 1
|
||||
local raw_end = (prompt.injection_range and prompt.injection_range.end_line) or prompt.end_line or 1
|
||||
local target_line_count = vim.api.nvim_buf_line_count(target_bufnr)
|
||||
target_line_count = math.max(1, target_line_count)
|
||||
local range_start = math.max(1, math.min(raw_start, target_line_count))
|
||||
local range_end = math.max(1, math.min(raw_end, target_line_count))
|
||||
if range_end < range_start then
|
||||
range_end = range_start
|
||||
end
|
||||
local event_range = { start_line = range_start, end_line = range_end }
|
||||
|
||||
local range_for_marks = scope_range or event_range
|
||||
local injection_marks = create_injection_marks(target_bufnr, range_for_marks)
|
||||
|
||||
queue.enqueue({
|
||||
id = queue.generate_id(),
|
||||
bufnr = bufnr,
|
||||
range = event_range,
|
||||
timestamp = os.clock(),
|
||||
changedtick = snapshot.changedtick,
|
||||
content_hash = snapshot.content_hash,
|
||||
prompt_content = cleaned,
|
||||
target_path = target_path,
|
||||
priority = priority,
|
||||
status = "pending",
|
||||
attempt_count = 0,
|
||||
intent = intent,
|
||||
scope = scope,
|
||||
scope_text = scope_text,
|
||||
scope_range = scope_range,
|
||||
attached_files = attached_files,
|
||||
injection_marks = injection_marks,
|
||||
})
|
||||
|
||||
local scope_info = scope
|
||||
and scope.type ~= "file"
|
||||
and string.format(" [%s: %s]", scope.type, scope.name or "anonymous")
|
||||
or ""
|
||||
utils.notify(string.format("Prompt queued: %s%s", intent.type, scope_info), vim.log.levels.INFO)
|
||||
end)
|
||||
else
|
||||
utils.notify("Processing prompt...", vim.log.levels.INFO)
|
||||
vim.schedule(function()
|
||||
vim.cmd("CoderProcess")
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
is_processing = false
|
||||
end
|
||||
|
||||
return check_for_closed_prompt
|
||||
@@ -0,0 +1,19 @@
|
||||
local check_for_closed_prompt = require("codetyper.adapters.nvim.autocmds.check_for_closed_prompt")
|
||||
|
||||
--- Check for closed prompt with preference check
|
||||
--- If auto_process is enabled, process; otherwise do nothing (manual mode)
|
||||
local function check_for_closed_prompt_with_preference()
|
||||
local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer")
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local prompts = find_prompts_in_buffer(bufnr)
|
||||
if #prompts == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if auto_process then
|
||||
check_for_closed_prompt()
|
||||
end
|
||||
end
|
||||
|
||||
return check_for_closed_prompt_with_preference
|
||||
@@ -0,0 +1,9 @@
|
||||
local autocmds_state = require("codetyper.adapters.nvim.autocmds.state")
|
||||
|
||||
--- Clear auto-opened tracking for a buffer
|
||||
---@param bufnr number Buffer number
|
||||
local function clear_auto_opened(bufnr)
|
||||
autocmds_state.auto_opened_buffers[bufnr] = nil
|
||||
end
|
||||
|
||||
return clear_auto_opened
|
||||
@@ -0,0 +1,31 @@
|
||||
--- Create extmarks for injection range so position survives user edits
|
||||
---@param target_bufnr number Target buffer (where code will be injected)
|
||||
---@param range { start_line: number, end_line: number } Range to mark (1-based)
|
||||
---@return table|nil injection_marks { start_mark, end_mark } or nil if buffer invalid
|
||||
local function create_injection_marks(target_bufnr, range)
|
||||
if not range or target_bufnr == -1 or not vim.api.nvim_buf_is_valid(target_bufnr) then
|
||||
return nil
|
||||
end
|
||||
local line_count = vim.api.nvim_buf_line_count(target_bufnr)
|
||||
if line_count == 0 then
|
||||
return nil
|
||||
end
|
||||
local start_line = math.max(1, math.min(range.start_line, line_count))
|
||||
local end_line = math.max(1, math.min(range.end_line, line_count))
|
||||
if start_line > end_line then
|
||||
end_line = start_line
|
||||
end
|
||||
local marks = require("codetyper.core.marks")
|
||||
local end_line_content = vim.api.nvim_buf_get_lines(target_bufnr, end_line - 1, end_line, false)
|
||||
local end_col_0 = 0
|
||||
if end_line_content and end_line_content[1] then
|
||||
end_col_0 = #end_line_content[1]
|
||||
end
|
||||
local start_mark, end_mark = marks.mark_range(target_bufnr, start_line, end_line, end_col_0)
|
||||
if not start_mark.id or not end_mark.id then
|
||||
return nil
|
||||
end
|
||||
return { start_mark = start_mark, end_mark = end_mark }
|
||||
end
|
||||
|
||||
return create_injection_marks
|
||||
9
lua/codetyper/adapters/nvim/autocmds/get_prompt_key.lua
Normal file
9
lua/codetyper/adapters/nvim/autocmds/get_prompt_key.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
--- Generate a unique key for a prompt
|
||||
---@param bufnr number Buffer number
|
||||
---@param prompt table Prompt object
|
||||
---@return string Unique key
|
||||
local function get_prompt_key(bufnr, prompt)
|
||||
return string.format("%d:%d:%d:%s", bufnr, prompt.start_line, prompt.end_line, prompt.content:sub(1, 50))
|
||||
end
|
||||
|
||||
return get_prompt_key
|
||||
25
lua/codetyper/adapters/nvim/autocmds/ignored_directories.lua
Normal file
25
lua/codetyper/adapters/nvim/autocmds/ignored_directories.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local ignored_directories = {
|
||||
".git",
|
||||
".codetyper",
|
||||
".claude",
|
||||
".vscode",
|
||||
".idea",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
"dist",
|
||||
"build",
|
||||
"target",
|
||||
"__pycache__",
|
||||
".cache",
|
||||
".npm",
|
||||
".yarn",
|
||||
"coverage",
|
||||
".next",
|
||||
".nuxt",
|
||||
".svelte-kit",
|
||||
"out",
|
||||
"bin",
|
||||
"obj",
|
||||
}
|
||||
|
||||
return ignored_directories
|
||||
48
lua/codetyper/adapters/nvim/autocmds/ignored_files.lua
Normal file
48
lua/codetyper/adapters/nvim/autocmds/ignored_files.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local ignored_files = {
|
||||
".gitignore",
|
||||
".gitattributes",
|
||||
".gitmodules",
|
||||
"package-lock.json",
|
||||
"yarn.lock",
|
||||
"pnpm-lock.yaml",
|
||||
"Cargo.lock",
|
||||
"Gemfile.lock",
|
||||
"poetry.lock",
|
||||
"composer.lock",
|
||||
".env",
|
||||
".env.local",
|
||||
".env.development",
|
||||
".env.production",
|
||||
".eslintrc",
|
||||
".eslintrc.json",
|
||||
".prettierrc",
|
||||
".prettierrc.json",
|
||||
".editorconfig",
|
||||
".dockerignore",
|
||||
"Dockerfile",
|
||||
"docker-compose.yml",
|
||||
"docker-compose.yaml",
|
||||
".npmrc",
|
||||
".yarnrc",
|
||||
".nvmrc",
|
||||
"tsconfig.json",
|
||||
"jsconfig.json",
|
||||
"babel.config.js",
|
||||
"webpack.config.js",
|
||||
"vite.config.js",
|
||||
"rollup.config.js",
|
||||
"jest.config.js",
|
||||
"vitest.config.js",
|
||||
".stylelintrc",
|
||||
"tailwind.config.js",
|
||||
"postcss.config.js",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE",
|
||||
"LICENSE.md",
|
||||
"CONTRIBUTING.md",
|
||||
"Makefile",
|
||||
"CMakeLists.txt",
|
||||
}
|
||||
|
||||
return ignored_files
|
||||
@@ -0,0 +1,18 @@
|
||||
local ignored_directories = require("codetyper.adapters.nvim.autocmds.ignored_directories")
|
||||
|
||||
--- Check if a file path contains an ignored directory
|
||||
---@param filepath string Full file path
|
||||
---@return boolean
|
||||
local function is_in_ignored_directory(filepath)
|
||||
for _, dir in ipairs(ignored_directories) do
|
||||
if filepath:match("/" .. dir .. "/") or filepath:match("/" .. dir .. "$") then
|
||||
return true
|
||||
end
|
||||
if filepath:match("^" .. dir .. "/") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return is_in_ignored_directory
|
||||
@@ -0,0 +1,15 @@
|
||||
local supported_extensions = require("codetyper.adapters.nvim.autocmds.supported_extensions")
|
||||
|
||||
--- Check if extension is supported for auto-indexing
|
||||
---@param ext string File extension
|
||||
---@return boolean
|
||||
local function is_supported_extension(ext)
|
||||
for _, supported in ipairs(supported_extensions) do
|
||||
if ext == supported then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return is_supported_extension
|
||||
178
lua/codetyper/adapters/nvim/autocmds/process_single_prompt.lua
Normal file
178
lua/codetyper/adapters/nvim/autocmds/process_single_prompt.lua
Normal file
@@ -0,0 +1,178 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local processed_prompts = require("codetyper.constants.constants").processed_prompts
|
||||
local get_prompt_key = require("codetyper.adapters.nvim.autocmds.get_prompt_key")
|
||||
local read_attached_files = require("codetyper.adapters.nvim.autocmds.read_attached_files")
|
||||
local create_injection_marks = require("codetyper.adapters.nvim.autocmds.create_injection_marks")
|
||||
|
||||
--- Process a single prompt through the scheduler
|
||||
---@param bufnr number Buffer number
|
||||
---@param prompt table Prompt object with start_line, end_line, content
|
||||
---@param current_file string Current file path
|
||||
---@param skip_processed_check? boolean Skip the processed check (for manual mode)
|
||||
local function process_single_prompt(bufnr, prompt, current_file, skip_processed_check)
|
||||
local clean_prompt = require("codetyper.parser.clean_prompt")
|
||||
local strip_file_references = require("codetyper.parser.strip_file_references")
|
||||
local scheduler = require("codetyper.core.scheduler.scheduler")
|
||||
|
||||
if not prompt.content or prompt.content == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if not scheduler.status().running then
|
||||
scheduler.start()
|
||||
end
|
||||
|
||||
local prompt_key = get_prompt_key(bufnr, prompt)
|
||||
|
||||
if not skip_processed_check and processed_prompts[prompt_key] then
|
||||
return
|
||||
end
|
||||
|
||||
processed_prompts[prompt_key] = true
|
||||
|
||||
vim.schedule(function()
|
||||
local queue = require("codetyper.core.events.queue")
|
||||
local patch_mod = require("codetyper.core.diff.patch")
|
||||
local intent_mod = require("codetyper.core.intent")
|
||||
local scope_mod = require("codetyper.core.scope")
|
||||
|
||||
local snapshot = patch_mod.snapshot_buffer(bufnr, {
|
||||
start_line = prompt.start_line,
|
||||
end_line = prompt.end_line,
|
||||
})
|
||||
|
||||
local target_path
|
||||
local is_from_coder_file = utils.is_coder_file(current_file)
|
||||
if is_from_coder_file then
|
||||
target_path = utils.get_target_path(current_file)
|
||||
else
|
||||
target_path = current_file
|
||||
end
|
||||
|
||||
local attached_files = read_attached_files(prompt.content, current_file)
|
||||
|
||||
local cleaned = clean_prompt(strip_file_references(prompt.content))
|
||||
|
||||
local target_bufnr = vim.fn.bufnr(target_path)
|
||||
local scope = nil
|
||||
local scope_text = nil
|
||||
local scope_range = nil
|
||||
|
||||
if not is_from_coder_file then
|
||||
if target_bufnr == -1 then
|
||||
target_bufnr = bufnr
|
||||
end
|
||||
scope = scope_mod.resolve_scope(target_bufnr, prompt.start_line, 1)
|
||||
if scope and scope.type ~= "file" then
|
||||
scope_text = scope.text
|
||||
scope_range = {
|
||||
start_line = scope.range.start_row,
|
||||
end_line = scope.range.end_row,
|
||||
}
|
||||
end
|
||||
else
|
||||
if target_bufnr == -1 then
|
||||
target_bufnr = vim.fn.bufadd(target_path)
|
||||
if target_bufnr ~= 0 then
|
||||
vim.fn.bufload(target_bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local intent = intent_mod.detect(cleaned)
|
||||
|
||||
if prompt.intent_override then
|
||||
intent.action = prompt.intent_override.action or intent.action
|
||||
if prompt.intent_override.type then
|
||||
intent.type = prompt.intent_override.type
|
||||
end
|
||||
elseif not is_from_coder_file and scope and (scope.type == "function" or scope.type == "method") then
|
||||
if intent.type == "add" or intent.action == "insert" or intent.action == "append" then
|
||||
intent = {
|
||||
type = "complete",
|
||||
scope_hint = "function",
|
||||
confidence = intent.confidence,
|
||||
action = "replace",
|
||||
keywords = intent.keywords,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if is_from_coder_file and (intent.action == "replace" or intent.type == "complete") then
|
||||
intent = {
|
||||
type = intent.type == "complete" and "add" or intent.type,
|
||||
confidence = intent.confidence,
|
||||
action = "append",
|
||||
keywords = intent.keywords,
|
||||
}
|
||||
end
|
||||
|
||||
local project_context = nil
|
||||
if prompt.is_whole_file then
|
||||
pcall(function()
|
||||
local tree = require("codetyper.support.tree")
|
||||
local tree_log = tree.get_tree_log_path()
|
||||
if tree_log and vim.fn.filereadable(tree_log) == 1 then
|
||||
local tree_lines = vim.fn.readfile(tree_log)
|
||||
if tree_lines and #tree_lines > 0 then
|
||||
local tree_content = table.concat(tree_lines, "\n")
|
||||
project_context = tree_content:sub(1, 4000)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local priority = 2
|
||||
if intent.type == "fix" or intent.type == "complete" then
|
||||
priority = 1
|
||||
elseif intent.type == "test" or intent.type == "document" then
|
||||
priority = 3
|
||||
end
|
||||
|
||||
local raw_start = (prompt.injection_range and prompt.injection_range.start_line) or prompt.start_line or 1
|
||||
local raw_end = (prompt.injection_range and prompt.injection_range.end_line) or prompt.end_line or 1
|
||||
local target_line_count = vim.api.nvim_buf_line_count(target_bufnr)
|
||||
target_line_count = math.max(1, target_line_count)
|
||||
local range_start = math.max(1, math.min(raw_start, target_line_count))
|
||||
local range_end = math.max(1, math.min(raw_end, target_line_count))
|
||||
if range_end < range_start then
|
||||
range_end = range_start
|
||||
end
|
||||
local event_range = { start_line = range_start, end_line = range_end }
|
||||
|
||||
local range_for_marks = scope_range or event_range
|
||||
local injection_marks = create_injection_marks(target_bufnr, range_for_marks)
|
||||
|
||||
queue.enqueue({
|
||||
id = queue.generate_id(),
|
||||
bufnr = bufnr,
|
||||
range = event_range,
|
||||
timestamp = os.clock(),
|
||||
changedtick = snapshot.changedtick,
|
||||
content_hash = snapshot.content_hash,
|
||||
prompt_content = cleaned,
|
||||
target_path = target_path,
|
||||
priority = priority,
|
||||
status = "pending",
|
||||
attempt_count = 0,
|
||||
intent = intent,
|
||||
intent_override = prompt.intent_override,
|
||||
scope = scope,
|
||||
scope_text = scope_text,
|
||||
scope_range = scope_range,
|
||||
attached_files = attached_files,
|
||||
injection_marks = injection_marks,
|
||||
injection_range = prompt.injection_range,
|
||||
is_whole_file = prompt.is_whole_file,
|
||||
project_context = project_context,
|
||||
})
|
||||
|
||||
local scope_info = scope
|
||||
and scope.type ~= "file"
|
||||
and string.format(" [%s: %s]", scope.type, scope.name or "anonymous")
|
||||
or ""
|
||||
utils.notify(string.format("Prompt queued: %s%s", intent.type, scope_info), vim.log.levels.INFO)
|
||||
end)
|
||||
end
|
||||
|
||||
return process_single_prompt
|
||||
42
lua/codetyper/adapters/nvim/autocmds/read_attached_files.lua
Normal file
42
lua/codetyper/adapters/nvim/autocmds/read_attached_files.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Read attached files from prompt content
|
||||
---@param prompt_content string Prompt content
|
||||
---@param base_path string Base path to resolve relative file paths
|
||||
---@return table[] attached_files List of {path, content} tables
|
||||
local function read_attached_files(prompt_content, base_path)
|
||||
local extract_file_references = require("codetyper.parser.extract_file_references")
|
||||
local file_refs = extract_file_references(prompt_content)
|
||||
local attached = {}
|
||||
local cwd = vim.fn.getcwd()
|
||||
local base_dir = vim.fn.fnamemodify(base_path, ":h")
|
||||
|
||||
for _, ref in ipairs(file_refs) do
|
||||
local file_path = nil
|
||||
|
||||
local cwd_path = cwd .. "/" .. ref
|
||||
if utils.file_exists(cwd_path) then
|
||||
file_path = cwd_path
|
||||
else
|
||||
local rel_path = base_dir .. "/" .. ref
|
||||
if utils.file_exists(rel_path) then
|
||||
file_path = rel_path
|
||||
end
|
||||
end
|
||||
|
||||
if file_path then
|
||||
local content = utils.read_file(file_path)
|
||||
if content then
|
||||
table.insert(attached, {
|
||||
path = ref,
|
||||
full_path = file_path,
|
||||
content = content,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return attached
|
||||
end
|
||||
|
||||
return read_attached_files
|
||||
19
lua/codetyper/adapters/nvim/autocmds/reset_processed.lua
Normal file
19
lua/codetyper/adapters/nvim/autocmds/reset_processed.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local processed_prompts = require("codetyper.constants.constants").processed_prompts
|
||||
|
||||
--- Reset processed prompts for a buffer (useful for re-processing)
|
||||
---@param bufnr? number Buffer number (default: current)
|
||||
---@param silent? boolean Suppress notification (default: false)
|
||||
local function reset_processed(bufnr, silent)
|
||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||
for key, _ in pairs(processed_prompts) do
|
||||
if key:match("^" .. bufnr .. ":") then
|
||||
processed_prompts[key] = nil
|
||||
end
|
||||
end
|
||||
if not silent then
|
||||
utils.notify("Prompt history cleared - prompts can be re-processed")
|
||||
end
|
||||
end
|
||||
|
||||
return reset_processed
|
||||
@@ -0,0 +1,17 @@
|
||||
local tree_update_timer = require("codetyper.constants.constants").tree_update_timer
|
||||
local TREE_UPDATE_DEBOUNCE_MS = require("codetyper.constants.constants").TREE_UPDATE_DEBOUNCE_MS
|
||||
|
||||
--- Schedule tree update with debounce
|
||||
local function schedule_tree_update()
|
||||
if tree_update_timer then
|
||||
tree_update_timer:stop()
|
||||
end
|
||||
|
||||
tree_update_timer = vim.defer_fn(function()
|
||||
local tree = require("codetyper.support.tree")
|
||||
tree.update_tree_log()
|
||||
tree_update_timer = nil
|
||||
end, TREE_UPDATE_DEBOUNCE_MS)
|
||||
end
|
||||
|
||||
return schedule_tree_update
|
||||
38
lua/codetyper/adapters/nvim/autocmds/set_coder_filetype.lua
Normal file
38
lua/codetyper/adapters/nvim/autocmds/set_coder_filetype.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
--- Set appropriate filetype for coder files based on extension
|
||||
local function set_coder_filetype()
|
||||
local filepath = vim.fn.expand("%:p")
|
||||
|
||||
local ext = filepath:match("%.codetyper%.(%w+)$")
|
||||
|
||||
if ext then
|
||||
local ft_map = {
|
||||
ts = "typescript",
|
||||
tsx = "typescriptreact",
|
||||
js = "javascript",
|
||||
jsx = "javascriptreact",
|
||||
py = "python",
|
||||
lua = "lua",
|
||||
go = "go",
|
||||
rs = "rust",
|
||||
rb = "ruby",
|
||||
java = "java",
|
||||
c = "c",
|
||||
cpp = "cpp",
|
||||
cs = "cs",
|
||||
json = "json",
|
||||
yaml = "yaml",
|
||||
yml = "yaml",
|
||||
md = "markdown",
|
||||
html = "html",
|
||||
css = "css",
|
||||
scss = "scss",
|
||||
vue = "vue",
|
||||
svelte = "svelte",
|
||||
}
|
||||
|
||||
local filetype = ft_map[ext] or ext
|
||||
vim.bo.filetype = filetype
|
||||
end
|
||||
end
|
||||
|
||||
return set_coder_filetype
|
||||
203
lua/codetyper/adapters/nvim/autocmds/setup.lua
Normal file
203
lua/codetyper/adapters/nvim/autocmds/setup.lua
Normal file
@@ -0,0 +1,203 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local AUGROUP = require("codetyper.constants.constants").AUGROUP
|
||||
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
|
||||
local schedule_tree_update = require("codetyper.adapters.nvim.autocmds.schedule_tree_update")
|
||||
local check_for_closed_prompt_with_preference = require("codetyper.adapters.nvim.autocmds.check_for_closed_prompt_with_preference")
|
||||
local check_all_prompts_with_preference = require("codetyper.adapters.nvim.autocmds.check_all_prompts_with_preference")
|
||||
local set_coder_filetype = require("codetyper.adapters.nvim.autocmds.set_coder_filetype")
|
||||
local clear_auto_opened = require("codetyper.adapters.nvim.autocmds.clear_auto_opened")
|
||||
local auto_index_file = require("codetyper.adapters.nvim.autocmds.auto_index_file")
|
||||
local update_brain_from_file = require("codetyper.adapters.nvim.autocmds.update_brain_from_file")
|
||||
|
||||
--- Setup autocommands
|
||||
local function setup()
|
||||
local group = vim.api.nvim_create_augroup(AUGROUP, { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
local buftype = vim.bo.buftype
|
||||
if buftype ~= "" then
|
||||
return
|
||||
end
|
||||
local filepath = vim.fn.expand("%:p")
|
||||
if utils.is_coder_file(filepath) and vim.bo.modified then
|
||||
vim.cmd("silent! write")
|
||||
end
|
||||
check_for_closed_prompt_with_preference()
|
||||
end,
|
||||
desc = "Check for closed prompt tags on InsertLeave",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("ModeChanged", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function(ev)
|
||||
local old_mode = ev.match:match("^(.-):")
|
||||
if old_mode then
|
||||
previous_mode = old_mode
|
||||
end
|
||||
end,
|
||||
desc = "Track previous mode for visual mode detection",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("ModeChanged", {
|
||||
group = group,
|
||||
pattern = "*:n",
|
||||
callback = function()
|
||||
local buftype = vim.bo.buftype
|
||||
if buftype ~= "" then
|
||||
return
|
||||
end
|
||||
|
||||
if is_processing then
|
||||
return
|
||||
end
|
||||
|
||||
if previous_mode == "v" or previous_mode == "V" or previous_mode == "\22" then
|
||||
return
|
||||
end
|
||||
|
||||
if prompt_process_timer then
|
||||
prompt_process_timer:stop()
|
||||
prompt_process_timer = nil
|
||||
end
|
||||
|
||||
prompt_process_timer = vim.defer_fn(function()
|
||||
prompt_process_timer = nil
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
if mode ~= "n" then
|
||||
return
|
||||
end
|
||||
check_all_prompts_with_preference()
|
||||
end, PROMPT_PROCESS_DEBOUNCE_MS)
|
||||
end,
|
||||
desc = "Auto-process closed prompts when entering normal mode",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("CursorHold", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
local buftype = vim.bo.buftype
|
||||
if buftype ~= "" then
|
||||
return
|
||||
end
|
||||
if is_processing then
|
||||
return
|
||||
end
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
if mode == "n" then
|
||||
check_all_prompts_with_preference()
|
||||
end
|
||||
end,
|
||||
desc = "Auto-process closed prompts when idle in normal mode",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
|
||||
group = group,
|
||||
pattern = "*.codetyper/*",
|
||||
callback = function()
|
||||
set_coder_filetype()
|
||||
end,
|
||||
desc = "Set filetype for coder files",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("BufWipeout", {
|
||||
group = group,
|
||||
pattern = "*.codetyper/*",
|
||||
callback = function(ev)
|
||||
local bufnr = ev.buf
|
||||
for key, _ in pairs(processed_prompts) do
|
||||
if key:match("^" .. bufnr .. ":") then
|
||||
processed_prompts[key] = nil
|
||||
end
|
||||
end
|
||||
clear_auto_opened(bufnr)
|
||||
end,
|
||||
desc = "Cleanup on coder buffer close",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufWritePost", "BufNewFile" }, {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function(ev)
|
||||
local filepath = ev.file or vim.fn.expand("%:p")
|
||||
if filepath:match("%.codetyper%.") or filepath:match("tree%.log$") then
|
||||
return
|
||||
end
|
||||
if filepath:match("node_modules") or filepath:match("%.git/") or filepath:match("%.codetyper/") then
|
||||
return
|
||||
end
|
||||
schedule_tree_update()
|
||||
|
||||
local indexer_loaded, indexer = pcall(require, "codetyper.indexer")
|
||||
if indexer_loaded then
|
||||
indexer.schedule_index_file(filepath)
|
||||
end
|
||||
|
||||
local brain_loaded, brain = pcall(require, "codetyper.brain")
|
||||
if brain_loaded and brain.is_initialized and brain.is_initialized() then
|
||||
vim.defer_fn(function()
|
||||
update_brain_from_file(filepath)
|
||||
end, 500)
|
||||
end
|
||||
end,
|
||||
desc = "Update tree.log, index, and brain on file creation/save",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("BufDelete", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function(ev)
|
||||
local filepath = ev.file or ""
|
||||
if filepath == "" or filepath:match("%.codetyper%.") or filepath:match("tree%.log$") then
|
||||
return
|
||||
end
|
||||
schedule_tree_update()
|
||||
end,
|
||||
desc = "Update tree.log on file deletion",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("DirChanged", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
schedule_tree_update()
|
||||
end,
|
||||
desc = "Update tree.log on directory change",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
local brain_loaded, brain = pcall(require, "codetyper.brain")
|
||||
if brain_loaded and brain.is_initialized and brain.is_initialized() then
|
||||
brain.shutdown()
|
||||
end
|
||||
end,
|
||||
desc = "Shutdown brain and flush pending changes",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("BufEnter", {
|
||||
group = group,
|
||||
pattern = "*",
|
||||
callback = function(ev)
|
||||
vim.defer_fn(function()
|
||||
auto_index_file(ev.buf)
|
||||
end, 100)
|
||||
end,
|
||||
desc = "Auto-index source files with coder companion",
|
||||
})
|
||||
|
||||
local thinking_setup = require("codetyper.adapters.nvim.ui.thinking.setup")
|
||||
thinking_setup()
|
||||
end
|
||||
|
||||
return setup
|
||||
@@ -0,0 +1,27 @@
|
||||
local ignored_files = require("codetyper.adapters.nvim.autocmds.ignored_files")
|
||||
local is_in_ignored_directory = require("codetyper.adapters.nvim.autocmds.is_in_ignored_directory")
|
||||
|
||||
--- Check if a file should be ignored for coder companion creation
|
||||
---@param filepath string Full file path
|
||||
---@return boolean
|
||||
local function should_ignore_for_coder(filepath)
|
||||
local filename = vim.fn.fnamemodify(filepath, ":t")
|
||||
|
||||
for _, ignored in ipairs(ignored_files) do
|
||||
if filename == ignored then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if filename:match("^%.") then
|
||||
return true
|
||||
end
|
||||
|
||||
if is_in_ignored_directory(filepath) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return should_ignore_for_coder
|
||||
7
lua/codetyper/adapters/nvim/autocmds/state.lua
Normal file
7
lua/codetyper/adapters/nvim/autocmds/state.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
local state = {
|
||||
auto_opened_buffers = {},
|
||||
auto_indexed_buffers = {},
|
||||
brain_update_timers = {},
|
||||
}
|
||||
|
||||
return state
|
||||
@@ -0,0 +1,29 @@
|
||||
local supported_extensions = {
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"py",
|
||||
"lua",
|
||||
"go",
|
||||
"rs",
|
||||
"rb",
|
||||
"java",
|
||||
"c",
|
||||
"cpp",
|
||||
"cs",
|
||||
"json",
|
||||
"yaml",
|
||||
"yml",
|
||||
"md",
|
||||
"html",
|
||||
"css",
|
||||
"scss",
|
||||
"vue",
|
||||
"svelte",
|
||||
"php",
|
||||
"sh",
|
||||
"zsh",
|
||||
}
|
||||
|
||||
return supported_extensions
|
||||
@@ -0,0 +1,91 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Update brain with patterns from a file
|
||||
---@param filepath string
|
||||
local function update_brain_from_file(filepath)
|
||||
local brain_loaded, brain = pcall(require, "codetyper.brain")
|
||||
if not brain_loaded or not brain.is_initialized() then
|
||||
return
|
||||
end
|
||||
|
||||
local content = utils.read_file(filepath)
|
||||
if not content or content == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local ext = vim.fn.fnamemodify(filepath, ":e")
|
||||
local lines = vim.split(content, "\n")
|
||||
|
||||
local functions = {}
|
||||
local classes = {}
|
||||
local imports = {}
|
||||
|
||||
for line_index, line in ipairs(lines) do
|
||||
local func_name = line:match("^%s*function%s+([%w_:%.]+)%s*%(")
|
||||
or line:match("^%s*local%s+function%s+([%w_]+)%s*%(")
|
||||
or line:match("^%s*def%s+([%w_]+)%s*%(")
|
||||
or line:match("^%s*func%s+([%w_]+)%s*%(")
|
||||
or line:match("^%s*async%s+function%s+([%w_]+)%s*%(")
|
||||
or line:match("^%s*public%s+.*%s+([%w_]+)%s*%(")
|
||||
or line:match("^%s*private%s+.*%s+([%w_]+)%s*%(")
|
||||
if func_name then
|
||||
table.insert(functions, { name = func_name, line = line_index })
|
||||
end
|
||||
|
||||
local class_name = line:match("^%s*class%s+([%w_]+)")
|
||||
or line:match("^%s*public%s+class%s+([%w_]+)")
|
||||
or line:match("^%s*interface%s+([%w_]+)")
|
||||
or line:match("^%s*struct%s+([%w_]+)")
|
||||
if class_name then
|
||||
table.insert(classes, { name = class_name, line = line_index })
|
||||
end
|
||||
|
||||
local import_path = line:match("import%s+.*%s+from%s+[\"']([^\"']+)[\"']")
|
||||
or line:match("require%([\"']([^\"']+)[\"']%)")
|
||||
or line:match("from%s+([%w_.]+)%s+import")
|
||||
if import_path then
|
||||
table.insert(imports, import_path)
|
||||
end
|
||||
end
|
||||
|
||||
if #functions == 0 and #classes == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
if #functions > 0 then
|
||||
local func_names = {}
|
||||
for func_index, func_entry in ipairs(functions) do
|
||||
if func_index <= 5 then
|
||||
table.insert(func_names, func_entry.name)
|
||||
end
|
||||
end
|
||||
table.insert(parts, "functions: " .. table.concat(func_names, ", "))
|
||||
end
|
||||
if #classes > 0 then
|
||||
local class_names = {}
|
||||
for _, class_entry in ipairs(classes) do
|
||||
table.insert(class_names, class_entry.name)
|
||||
end
|
||||
table.insert(parts, "classes: " .. table.concat(class_names, ", "))
|
||||
end
|
||||
|
||||
local summary = vim.fn.fnamemodify(filepath, ":t") .. " - " .. table.concat(parts, "; ")
|
||||
|
||||
brain.learn({
|
||||
type = "pattern_detected",
|
||||
file = filepath,
|
||||
timestamp = os.time(),
|
||||
data = {
|
||||
name = summary,
|
||||
description = #functions .. " functions, " .. #classes .. " classes",
|
||||
language = ext,
|
||||
symbols = vim.tbl_map(function(func_entry)
|
||||
return func_entry.name
|
||||
end, functions),
|
||||
example = nil,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return update_brain_from_file
|
||||
@@ -1,419 +0,0 @@
|
||||
---@mod codetyper.commands Command definitions for Codetyper.nvim
|
||||
|
||||
local M = {}
|
||||
|
||||
local transform = require("codetyper.core.transform")
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Refresh tree.log manually
|
||||
local function cmd_tree()
|
||||
local tree = require("codetyper.support.tree")
|
||||
if tree.update_tree_log() then
|
||||
utils.notify("Tree log updated: " .. tree.get_tree_log_path())
|
||||
else
|
||||
utils.notify("Failed to update tree log", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
--- Open tree.log file
|
||||
local function cmd_tree_view()
|
||||
local tree = require("codetyper.support.tree")
|
||||
local tree_log_path = tree.get_tree_log_path()
|
||||
|
||||
if not tree_log_path then
|
||||
utils.notify("Could not find tree.log", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
-- Ensure tree is up to date
|
||||
tree.update_tree_log()
|
||||
|
||||
-- Open in a new split
|
||||
vim.cmd("vsplit " .. vim.fn.fnameescape(tree_log_path))
|
||||
vim.bo.readonly = true
|
||||
vim.bo.modifiable = false
|
||||
end
|
||||
|
||||
--- Reset processed prompts to allow re-processing
|
||||
local function cmd_reset()
|
||||
local autocmds = require("codetyper.adapters.nvim.autocmds")
|
||||
autocmds.reset_processed()
|
||||
end
|
||||
|
||||
--- Force update gitignore
|
||||
local function cmd_gitignore()
|
||||
local gitignore = require("codetyper.support.gitignore")
|
||||
gitignore.force_update()
|
||||
end
|
||||
|
||||
--- Index the entire project
|
||||
local function cmd_index_project()
|
||||
local indexer = require("codetyper.features.indexer")
|
||||
|
||||
utils.notify("Indexing project...", vim.log.levels.INFO)
|
||||
|
||||
indexer.index_project(function(index)
|
||||
if index then
|
||||
local msg = string.format(
|
||||
"Indexed: %d files, %d functions, %d classes, %d exports",
|
||||
index.stats.files,
|
||||
index.stats.functions,
|
||||
index.stats.classes,
|
||||
index.stats.exports
|
||||
)
|
||||
utils.notify(msg, vim.log.levels.INFO)
|
||||
else
|
||||
utils.notify("Failed to index project", vim.log.levels.ERROR)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Show index status
|
||||
local function cmd_index_status()
|
||||
local indexer = require("codetyper.features.indexer")
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
local status = indexer.get_status()
|
||||
local mem_stats = memory.get_stats()
|
||||
|
||||
local lines = {
|
||||
"Project Index Status",
|
||||
"====================",
|
||||
"",
|
||||
}
|
||||
|
||||
if status.indexed then
|
||||
table.insert(lines, "Status: Indexed")
|
||||
table.insert(lines, "Project Type: " .. (status.project_type or "unknown"))
|
||||
table.insert(lines, "Last Indexed: " .. os.date("%Y-%m-%d %H:%M:%S", status.last_indexed))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Stats:")
|
||||
table.insert(lines, " Files: " .. (status.stats.files or 0))
|
||||
table.insert(lines, " Functions: " .. (status.stats.functions or 0))
|
||||
table.insert(lines, " Classes: " .. (status.stats.classes or 0))
|
||||
table.insert(lines, " Exports: " .. (status.stats.exports or 0))
|
||||
else
|
||||
table.insert(lines, "Status: Not indexed")
|
||||
table.insert(lines, "Run :CoderIndexProject to index")
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Memories:")
|
||||
table.insert(lines, " Patterns: " .. mem_stats.patterns)
|
||||
table.insert(lines, " Conventions: " .. mem_stats.conventions)
|
||||
table.insert(lines, " Symbols: " .. mem_stats.symbols)
|
||||
|
||||
utils.notify(table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
--- Show learned memories
|
||||
local function cmd_memories()
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
local all = memory.get_all()
|
||||
local lines = {
|
||||
"Learned Memories",
|
||||
"================",
|
||||
"",
|
||||
"Patterns:",
|
||||
}
|
||||
|
||||
local pattern_count = 0
|
||||
for _, mem in pairs(all.patterns) do
|
||||
pattern_count = pattern_count + 1
|
||||
if pattern_count <= 10 then
|
||||
table.insert(lines, " - " .. (mem.content or ""):sub(1, 60))
|
||||
end
|
||||
end
|
||||
if pattern_count > 10 then
|
||||
table.insert(lines, " ... and " .. (pattern_count - 10) .. " more")
|
||||
elseif pattern_count == 0 then
|
||||
table.insert(lines, " (none)")
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Conventions:")
|
||||
|
||||
local conv_count = 0
|
||||
for _, mem in pairs(all.conventions) do
|
||||
conv_count = conv_count + 1
|
||||
if conv_count <= 10 then
|
||||
table.insert(lines, " - " .. (mem.content or ""):sub(1, 60))
|
||||
end
|
||||
end
|
||||
if conv_count > 10 then
|
||||
table.insert(lines, " ... and " .. (conv_count - 10) .. " more")
|
||||
elseif conv_count == 0 then
|
||||
table.insert(lines, " (none)")
|
||||
end
|
||||
|
||||
utils.notify(table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
--- Clear memories
|
||||
---@param pattern string|nil Optional pattern to match
|
||||
local function cmd_forget(pattern)
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
if not pattern or pattern == "" then
|
||||
-- Confirm before clearing all
|
||||
vim.ui.select({ "Yes", "No" }, {
|
||||
prompt = "Clear all memories?",
|
||||
}, function(choice)
|
||||
if choice == "Yes" then
|
||||
memory.clear()
|
||||
utils.notify("All memories cleared", vim.log.levels.INFO)
|
||||
end
|
||||
end)
|
||||
else
|
||||
memory.clear(pattern)
|
||||
utils.notify("Cleared memories matching: " .. pattern, vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
--- Main command dispatcher
|
||||
---@param args table Command arguments
|
||||
--- Show LLM accuracy statistics
|
||||
local function cmd_llm_stats()
|
||||
local llm = require("codetyper.core.llm")
|
||||
local stats = llm.get_accuracy_stats()
|
||||
|
||||
local lines = {
|
||||
"LLM Provider Accuracy Statistics",
|
||||
"================================",
|
||||
"",
|
||||
string.format("Ollama:"),
|
||||
string.format(" Total requests: %d", stats.ollama.total),
|
||||
string.format(" Correct: %d", stats.ollama.correct),
|
||||
string.format(" Accuracy: %.1f%%", stats.ollama.accuracy * 100),
|
||||
"",
|
||||
string.format("Copilot:"),
|
||||
string.format(" Total requests: %d", stats.copilot.total),
|
||||
string.format(" Correct: %d", stats.copilot.correct),
|
||||
string.format(" Accuracy: %.1f%%", stats.copilot.accuracy * 100),
|
||||
"",
|
||||
"Note: Smart selection prefers Ollama when brain memories",
|
||||
"provide enough context. Accuracy improves over time via",
|
||||
"pondering (verification with other LLMs).",
|
||||
}
|
||||
|
||||
vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
--- Report feedback on last LLM response
|
||||
---@param was_good boolean Whether the response was good
|
||||
local function cmd_llm_feedback(was_good)
|
||||
local llm = require("codetyper.core.llm")
|
||||
-- Default to ollama for feedback
|
||||
local provider = "ollama"
|
||||
|
||||
llm.report_feedback(provider, was_good)
|
||||
local feedback_type = was_good and "positive" or "negative"
|
||||
utils.notify(string.format("Reported %s feedback for %s", feedback_type, provider), vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
--- Reset LLM accuracy statistics
|
||||
local function cmd_llm_reset_stats()
|
||||
local selector = require("codetyper.core.llm.selector")
|
||||
selector.reset_accuracy_stats()
|
||||
utils.notify("LLM accuracy statistics reset", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
local function coder_cmd(args)
|
||||
local subcommand = args.fargs[1] or "version"
|
||||
|
||||
local commands = {
|
||||
["version"] = function()
|
||||
local codetyper = require("codetyper")
|
||||
utils.notify("Codetyper.nvim " .. codetyper.version, vim.log.levels.INFO)
|
||||
end,
|
||||
tree = cmd_tree,
|
||||
["tree-view"] = cmd_tree_view,
|
||||
reset = cmd_reset,
|
||||
gitignore = cmd_gitignore,
|
||||
["transform-selection"] = transform.cmd_transform_selection,
|
||||
["index-project"] = cmd_index_project,
|
||||
["index-status"] = cmd_index_status,
|
||||
["llm-stats"] = cmd_llm_stats,
|
||||
["llm-reset-stats"] = cmd_llm_reset_stats,
|
||||
["cost"] = function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.toggle()
|
||||
end,
|
||||
["cost-clear"] = function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.clear()
|
||||
end,
|
||||
["credentials"] = function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.show_status()
|
||||
end,
|
||||
["switch-provider"] = function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.interactive_switch_provider()
|
||||
end,
|
||||
["model"] = function(args)
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
local provider = config.llm.provider
|
||||
|
||||
if provider ~= "copilot" then
|
||||
utils.notify(
|
||||
"CoderModel is only available when using Copilot provider. Current: " .. provider:upper(),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
local model_arg = args.fargs[2]
|
||||
if model_arg and model_arg ~= "" then
|
||||
local cost = credentials.get_copilot_model_cost(model_arg) or "custom"
|
||||
credentials.set_credentials("copilot", { model = model_arg, configured = true })
|
||||
utils.notify("Copilot model set to: " .. model_arg .. " — " .. cost, vim.log.levels.INFO)
|
||||
else
|
||||
credentials.interactive_copilot_config(true)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
local cmd_fn = commands[subcommand]
|
||||
if cmd_fn then
|
||||
cmd_fn(args)
|
||||
else
|
||||
utils.notify("Unknown subcommand: " .. subcommand, vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
--- Setup all commands
|
||||
function M.setup()
|
||||
vim.api.nvim_create_user_command("Coder", coder_cmd, {
|
||||
nargs = "?",
|
||||
complete = function()
|
||||
return {
|
||||
"version",
|
||||
"tree",
|
||||
"tree-view",
|
||||
"reset",
|
||||
"gitignore",
|
||||
"transform-selection",
|
||||
"index-project",
|
||||
"index-status",
|
||||
"llm-stats",
|
||||
"llm-reset-stats",
|
||||
"cost",
|
||||
"cost-clear",
|
||||
"credentials",
|
||||
"switch-provider",
|
||||
"model",
|
||||
}
|
||||
end,
|
||||
desc = "Codetyper.nvim commands",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTree", function()
|
||||
cmd_tree()
|
||||
end, { desc = "Refresh tree.log" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTreeView", function()
|
||||
cmd_tree_view()
|
||||
end, { desc = "View tree.log" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTransformSelection", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, { desc = "Transform visual selection with custom prompt input" })
|
||||
|
||||
-- Project indexer commands
|
||||
vim.api.nvim_create_user_command("CoderIndexProject", function()
|
||||
cmd_index_project()
|
||||
end, { desc = "Index the entire project" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderIndexStatus", function()
|
||||
cmd_index_status()
|
||||
end, { desc = "Show project index status" })
|
||||
|
||||
-- TODO: re-enable CoderMemories, CoderForget when memory UI is reworked
|
||||
-- TODO: re-enable CoderFeedback when feedback loop is reworked
|
||||
-- TODO: re-enable CoderBrain when brain management UI is reworked
|
||||
|
||||
-- Cost estimation command
|
||||
vim.api.nvim_create_user_command("CoderCost", function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.toggle()
|
||||
end, { desc = "Show LLM cost estimation window" })
|
||||
|
||||
-- TODO: re-enable CoderAddApiKey when multi-provider support returns
|
||||
|
||||
vim.api.nvim_create_user_command("CoderCredentials", function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.show_status()
|
||||
end, { desc = "Show credentials status" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderSwitchProvider", function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.interactive_switch_provider()
|
||||
end, { desc = "Switch active LLM provider" })
|
||||
|
||||
-- Quick model switcher command (Copilot only)
|
||||
vim.api.nvim_create_user_command("CoderModel", function(opts)
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
local provider = config.llm.provider
|
||||
|
||||
-- Only available for Copilot provider
|
||||
if provider ~= "copilot" then
|
||||
utils.notify(
|
||||
"CoderModel is only available when using Copilot provider. Current: " .. provider:upper(),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
-- If an argument is provided, set the model directly
|
||||
if opts.args and opts.args ~= "" then
|
||||
local cost = credentials.get_copilot_model_cost(opts.args) or "custom"
|
||||
credentials.set_credentials("copilot", { model = opts.args, configured = true })
|
||||
utils.notify("Copilot model set to: " .. opts.args .. " — " .. cost, vim.log.levels.INFO)
|
||||
return
|
||||
end
|
||||
|
||||
-- Show interactive selector with costs (silent mode - no OAuth message)
|
||||
credentials.interactive_copilot_config(true)
|
||||
end, {
|
||||
nargs = "?",
|
||||
desc = "Quick switch Copilot model (only available with Copilot provider)",
|
||||
complete = function()
|
||||
local codetyper = require("codetyper")
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local config = codetyper.get_config()
|
||||
if config.llm.provider == "copilot" then
|
||||
return credentials.get_copilot_model_names()
|
||||
end
|
||||
return {}
|
||||
end,
|
||||
})
|
||||
|
||||
-- Setup default keymaps
|
||||
M.setup_keymaps()
|
||||
end
|
||||
|
||||
--- Setup default keymaps for transform commands
|
||||
function M.setup_keymaps()
|
||||
-- Visual mode: transform selection with custom prompt input
|
||||
vim.keymap.set("v", "<leader>ctt", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, {
|
||||
silent = true,
|
||||
desc = "Coder: Transform selection with prompt",
|
||||
})
|
||||
-- Normal mode: prompt only (no selection); request is entered in the prompt
|
||||
vim.keymap.set("n", "<leader>ctt", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, {
|
||||
silent = true,
|
||||
desc = "Coder: Open prompt window",
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
23
lua/codetyper/adapters/nvim/commands/cmd_forget.lua
Normal file
23
lua/codetyper/adapters/nvim/commands/cmd_forget.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Clear memories
|
||||
---@param pattern string|nil Optional pattern to match
|
||||
local function cmd_forget(pattern)
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
if not pattern or pattern == "" then
|
||||
vim.ui.select({ "Yes", "No" }, {
|
||||
prompt = "Clear all memories?",
|
||||
}, function(choice)
|
||||
if choice == "Yes" then
|
||||
memory.clear()
|
||||
utils.notify("All memories cleared", vim.log.levels.INFO)
|
||||
end
|
||||
end)
|
||||
else
|
||||
memory.clear(pattern)
|
||||
utils.notify("Cleared memories matching: " .. pattern, vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
return cmd_forget
|
||||
7
lua/codetyper/adapters/nvim/commands/cmd_gitignore.lua
Normal file
7
lua/codetyper/adapters/nvim/commands/cmd_gitignore.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
--- Force update gitignore
|
||||
local function cmd_gitignore()
|
||||
local gitignore = require("codetyper.support.gitignore")
|
||||
gitignore.force_update()
|
||||
end
|
||||
|
||||
return cmd_gitignore
|
||||
25
lua/codetyper/adapters/nvim/commands/cmd_index_project.lua
Normal file
25
lua/codetyper/adapters/nvim/commands/cmd_index_project.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Index the entire project
|
||||
local function cmd_index_project()
|
||||
local indexer = require("codetyper.features.indexer")
|
||||
|
||||
utils.notify("Indexing project...", vim.log.levels.INFO)
|
||||
|
||||
indexer.index_project(function(index)
|
||||
if index then
|
||||
local msg = string.format(
|
||||
"Indexed: %d files, %d functions, %d classes, %d exports",
|
||||
index.stats.files,
|
||||
index.stats.functions,
|
||||
index.stats.classes,
|
||||
index.stats.exports
|
||||
)
|
||||
utils.notify(msg, vim.log.levels.INFO)
|
||||
else
|
||||
utils.notify("Failed to index project", vim.log.levels.ERROR)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return cmd_index_project
|
||||
41
lua/codetyper/adapters/nvim/commands/cmd_index_status.lua
Normal file
41
lua/codetyper/adapters/nvim/commands/cmd_index_status.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Show index status
|
||||
local function cmd_index_status()
|
||||
local indexer = require("codetyper.features.indexer")
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
local status = indexer.get_status()
|
||||
local mem_stats = memory.get_stats()
|
||||
|
||||
local lines = {
|
||||
"Project Index Status",
|
||||
"====================",
|
||||
"",
|
||||
}
|
||||
|
||||
if status.indexed then
|
||||
table.insert(lines, "Status: Indexed")
|
||||
table.insert(lines, "Project Type: " .. (status.project_type or "unknown"))
|
||||
table.insert(lines, "Last Indexed: " .. os.date("%Y-%m-%d %H:%M:%S", status.last_indexed))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Stats:")
|
||||
table.insert(lines, " Files: " .. (status.stats.files or 0))
|
||||
table.insert(lines, " Functions: " .. (status.stats.functions or 0))
|
||||
table.insert(lines, " Classes: " .. (status.stats.classes or 0))
|
||||
table.insert(lines, " Exports: " .. (status.stats.exports or 0))
|
||||
else
|
||||
table.insert(lines, "Status: Not indexed")
|
||||
table.insert(lines, "Run :CoderIndexProject to index")
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Memories:")
|
||||
table.insert(lines, " Patterns: " .. mem_stats.patterns)
|
||||
table.insert(lines, " Conventions: " .. mem_stats.conventions)
|
||||
table.insert(lines, " Symbols: " .. mem_stats.symbols)
|
||||
|
||||
utils.notify(table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
return cmd_index_status
|
||||
14
lua/codetyper/adapters/nvim/commands/cmd_llm_feedback.lua
Normal file
14
lua/codetyper/adapters/nvim/commands/cmd_llm_feedback.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Report feedback on last LLM response
|
||||
---@param was_good boolean Whether the response was good
|
||||
local function cmd_llm_feedback(was_good)
|
||||
local llm = require("codetyper.core.llm")
|
||||
local provider = "ollama"
|
||||
|
||||
llm.report_feedback(provider, was_good)
|
||||
local feedback_type = was_good and "positive" or "negative"
|
||||
utils.notify(string.format("Reported %s feedback for %s", feedback_type, provider), vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
return cmd_llm_feedback
|
||||
10
lua/codetyper/adapters/nvim/commands/cmd_llm_reset_stats.lua
Normal file
10
lua/codetyper/adapters/nvim/commands/cmd_llm_reset_stats.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Reset LLM accuracy statistics
|
||||
local function cmd_llm_reset_stats()
|
||||
local selector = require("codetyper.core.llm.selector")
|
||||
selector.reset_accuracy_stats()
|
||||
utils.notify("LLM accuracy statistics reset", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
return cmd_llm_reset_stats
|
||||
28
lua/codetyper/adapters/nvim/commands/cmd_llm_stats.lua
Normal file
28
lua/codetyper/adapters/nvim/commands/cmd_llm_stats.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--- Show LLM accuracy statistics
|
||||
local function cmd_llm_stats()
|
||||
local llm = require("codetyper.core.llm")
|
||||
local stats = llm.get_accuracy_stats()
|
||||
|
||||
local lines = {
|
||||
"LLM Provider Accuracy Statistics",
|
||||
"================================",
|
||||
"",
|
||||
string.format("Ollama:"),
|
||||
string.format(" Total requests: %d", stats.ollama.total),
|
||||
string.format(" Correct: %d", stats.ollama.correct),
|
||||
string.format(" Accuracy: %.1f%%", stats.ollama.accuracy * 100),
|
||||
"",
|
||||
string.format("Copilot:"),
|
||||
string.format(" Total requests: %d", stats.copilot.total),
|
||||
string.format(" Correct: %d", stats.copilot.correct),
|
||||
string.format(" Accuracy: %.1f%%", stats.copilot.accuracy * 100),
|
||||
"",
|
||||
"Note: Smart selection prefers Ollama when brain memories",
|
||||
"provide enough context. Accuracy improves over time via",
|
||||
"pondering (verification with other LLMs).",
|
||||
}
|
||||
|
||||
vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
return cmd_llm_stats
|
||||
47
lua/codetyper/adapters/nvim/commands/cmd_memories.lua
Normal file
47
lua/codetyper/adapters/nvim/commands/cmd_memories.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Show learned memories
|
||||
local function cmd_memories()
|
||||
local memory = require("codetyper.features.indexer.memory")
|
||||
|
||||
local all = memory.get_all()
|
||||
local lines = {
|
||||
"Learned Memories",
|
||||
"================",
|
||||
"",
|
||||
"Patterns:",
|
||||
}
|
||||
|
||||
local pattern_count = 0
|
||||
for _, mem in pairs(all.patterns) do
|
||||
pattern_count = pattern_count + 1
|
||||
if pattern_count <= 10 then
|
||||
table.insert(lines, " - " .. (mem.content or ""):sub(1, 60))
|
||||
end
|
||||
end
|
||||
if pattern_count > 10 then
|
||||
table.insert(lines, " ... and " .. (pattern_count - 10) .. " more")
|
||||
elseif pattern_count == 0 then
|
||||
table.insert(lines, " (none)")
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Conventions:")
|
||||
|
||||
local conv_count = 0
|
||||
for _, mem in pairs(all.conventions) do
|
||||
conv_count = conv_count + 1
|
||||
if conv_count <= 10 then
|
||||
table.insert(lines, " - " .. (mem.content or ""):sub(1, 60))
|
||||
end
|
||||
end
|
||||
if conv_count > 10 then
|
||||
table.insert(lines, " ... and " .. (conv_count - 10) .. " more")
|
||||
elseif conv_count == 0 then
|
||||
table.insert(lines, " (none)")
|
||||
end
|
||||
|
||||
utils.notify(table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
return cmd_memories
|
||||
7
lua/codetyper/adapters/nvim/commands/cmd_reset.lua
Normal file
7
lua/codetyper/adapters/nvim/commands/cmd_reset.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
--- Reset processed prompts to allow re-processing
|
||||
local function cmd_reset()
|
||||
local reset_processed = require("codetyper.adapters.nvim.autocmds.reset_processed")
|
||||
reset_processed()
|
||||
end
|
||||
|
||||
return cmd_reset
|
||||
13
lua/codetyper/adapters/nvim/commands/cmd_tree.lua
Normal file
13
lua/codetyper/adapters/nvim/commands/cmd_tree.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Refresh tree.log manually
|
||||
local function cmd_tree()
|
||||
local tree = require("codetyper.support.tree")
|
||||
if tree.update_tree_log() then
|
||||
utils.notify("Tree log updated: " .. tree.get_tree_log_path())
|
||||
else
|
||||
utils.notify("Failed to update tree log", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
return cmd_tree
|
||||
20
lua/codetyper/adapters/nvim/commands/cmd_tree_view.lua
Normal file
20
lua/codetyper/adapters/nvim/commands/cmd_tree_view.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
|
||||
--- Open tree.log file in a vertical split
|
||||
local function cmd_tree_view()
|
||||
local tree = require("codetyper.support.tree")
|
||||
local tree_log_path = tree.get_tree_log_path()
|
||||
|
||||
if not tree_log_path then
|
||||
utils.notify("Could not find tree.log", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
tree.update_tree_log()
|
||||
|
||||
vim.cmd("vsplit " .. vim.fn.fnameescape(tree_log_path))
|
||||
vim.bo.readonly = true
|
||||
vim.bo.modifiable = false
|
||||
end
|
||||
|
||||
return cmd_tree_view
|
||||
80
lua/codetyper/adapters/nvim/commands/coder_cmd.lua
Normal file
80
lua/codetyper/adapters/nvim/commands/coder_cmd.lua
Normal file
@@ -0,0 +1,80 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local transform = require("codetyper.core.transform")
|
||||
local cmd_tree = require("codetyper.adapters.nvim.commands.cmd_tree")
|
||||
local cmd_tree_view = require("codetyper.adapters.nvim.commands.cmd_tree_view")
|
||||
local cmd_reset = require("codetyper.adapters.nvim.commands.cmd_reset")
|
||||
local cmd_gitignore = require("codetyper.adapters.nvim.commands.cmd_gitignore")
|
||||
local cmd_index_project = require("codetyper.adapters.nvim.commands.cmd_index_project")
|
||||
local cmd_index_status = require("codetyper.adapters.nvim.commands.cmd_index_status")
|
||||
local cmd_llm_stats = require("codetyper.adapters.nvim.commands.cmd_llm_stats")
|
||||
local cmd_llm_reset_stats = require("codetyper.adapters.nvim.commands.cmd_llm_reset_stats")
|
||||
|
||||
--- Main command dispatcher
|
||||
---@param args table Command arguments
|
||||
local function coder_cmd(args)
|
||||
local subcommand = args.fargs[1] or "version"
|
||||
|
||||
local commands = {
|
||||
["version"] = function()
|
||||
local codetyper = require("codetyper")
|
||||
utils.notify("Codetyper.nvim " .. codetyper.version, vim.log.levels.INFO)
|
||||
end,
|
||||
tree = cmd_tree,
|
||||
["tree-view"] = cmd_tree_view,
|
||||
reset = cmd_reset,
|
||||
gitignore = cmd_gitignore,
|
||||
["transform-selection"] = transform.cmd_transform_selection,
|
||||
["index-project"] = cmd_index_project,
|
||||
["index-status"] = cmd_index_status,
|
||||
["llm-stats"] = cmd_llm_stats,
|
||||
["llm-reset-stats"] = cmd_llm_reset_stats,
|
||||
["cost"] = function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.toggle()
|
||||
end,
|
||||
["cost-clear"] = function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.clear()
|
||||
end,
|
||||
["credentials"] = function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.show_status()
|
||||
end,
|
||||
["switch-provider"] = function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.interactive_switch_provider()
|
||||
end,
|
||||
["model"] = function(cmd_args)
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
local provider = config.llm.provider
|
||||
|
||||
if provider ~= "copilot" then
|
||||
utils.notify(
|
||||
"CoderModel is only available when using Copilot provider. Current: " .. provider:upper(),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
local model_arg = cmd_args.fargs[2]
|
||||
if model_arg and model_arg ~= "" then
|
||||
local model_cost = credentials.get_copilot_model_cost(model_arg) or "custom"
|
||||
credentials.set_credentials("copilot", { model = model_arg, configured = true })
|
||||
utils.notify("Copilot model set to: " .. model_arg .. " — " .. model_cost, vim.log.levels.INFO)
|
||||
else
|
||||
credentials.interactive_copilot_config(true)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
local cmd_fn = commands[subcommand]
|
||||
if cmd_fn then
|
||||
cmd_fn(args)
|
||||
else
|
||||
utils.notify("Unknown subcommand: " .. subcommand, vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
return coder_cmd
|
||||
116
lua/codetyper/adapters/nvim/commands/setup.lua
Normal file
116
lua/codetyper/adapters/nvim/commands/setup.lua
Normal file
@@ -0,0 +1,116 @@
|
||||
local utils = require("codetyper.support.utils")
|
||||
local transform = require("codetyper.core.transform")
|
||||
local coder_cmd = require("codetyper.adapters.nvim.commands.coder_cmd")
|
||||
local cmd_tree = require("codetyper.adapters.nvim.commands.cmd_tree")
|
||||
local cmd_tree_view = require("codetyper.adapters.nvim.commands.cmd_tree_view")
|
||||
local cmd_index_project = require("codetyper.adapters.nvim.commands.cmd_index_project")
|
||||
local cmd_index_status = require("codetyper.adapters.nvim.commands.cmd_index_status")
|
||||
local setup_keymaps = require("codetyper.adapters.nvim.commands.setup_keymaps")
|
||||
|
||||
--- Setup all commands
|
||||
local function setup()
|
||||
vim.api.nvim_create_user_command("Coder", coder_cmd, {
|
||||
nargs = "?",
|
||||
complete = function()
|
||||
return {
|
||||
"version",
|
||||
"tree",
|
||||
"tree-view",
|
||||
"reset",
|
||||
"gitignore",
|
||||
"transform-selection",
|
||||
"index-project",
|
||||
"index-status",
|
||||
"llm-stats",
|
||||
"llm-reset-stats",
|
||||
"cost",
|
||||
"cost-clear",
|
||||
"credentials",
|
||||
"switch-provider",
|
||||
"model",
|
||||
}
|
||||
end,
|
||||
desc = "Codetyper.nvim commands",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTree", function()
|
||||
cmd_tree()
|
||||
end, { desc = "Refresh tree.log" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTreeView", function()
|
||||
cmd_tree_view()
|
||||
end, { desc = "View tree.log" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderTransformSelection", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, { desc = "Transform visual selection with custom prompt input" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderIndexProject", function()
|
||||
cmd_index_project()
|
||||
end, { desc = "Index the entire project" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderIndexStatus", function()
|
||||
cmd_index_status()
|
||||
end, { desc = "Show project index status" })
|
||||
|
||||
-- TODO: re-enable CoderMemories, CoderForget when memory UI is reworked
|
||||
-- TODO: re-enable CoderFeedback when feedback loop is reworked
|
||||
-- TODO: re-enable CoderBrain when brain management UI is reworked
|
||||
|
||||
vim.api.nvim_create_user_command("CoderCost", function()
|
||||
local cost = require("codetyper.core.cost")
|
||||
cost.toggle()
|
||||
end, { desc = "Show LLM cost estimation window" })
|
||||
|
||||
-- TODO: re-enable CoderAddApiKey when multi-provider support returns
|
||||
|
||||
vim.api.nvim_create_user_command("CoderCredentials", function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.show_status()
|
||||
end, { desc = "Show credentials status" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderSwitchProvider", function()
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
credentials.interactive_switch_provider()
|
||||
end, { desc = "Switch active LLM provider" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderModel", function(opts)
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
local provider = config.llm.provider
|
||||
|
||||
if provider ~= "copilot" then
|
||||
utils.notify(
|
||||
"CoderModel is only available when using Copilot provider. Current: " .. provider:upper(),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
if opts.args and opts.args ~= "" then
|
||||
local model_cost = credentials.get_copilot_model_cost(opts.args) or "custom"
|
||||
credentials.set_credentials("copilot", { model = opts.args, configured = true })
|
||||
utils.notify("Copilot model set to: " .. opts.args .. " — " .. model_cost, vim.log.levels.INFO)
|
||||
return
|
||||
end
|
||||
|
||||
credentials.interactive_copilot_config(true)
|
||||
end, {
|
||||
nargs = "?",
|
||||
desc = "Quick switch Copilot model (only available with Copilot provider)",
|
||||
complete = function()
|
||||
local codetyper = require("codetyper")
|
||||
local credentials = require("codetyper.config.credentials")
|
||||
local config = codetyper.get_config()
|
||||
if config.llm.provider == "copilot" then
|
||||
return credentials.get_copilot_model_names()
|
||||
end
|
||||
return {}
|
||||
end,
|
||||
})
|
||||
|
||||
setup_keymaps()
|
||||
end
|
||||
|
||||
return setup
|
||||
19
lua/codetyper/adapters/nvim/commands/setup_keymaps.lua
Normal file
19
lua/codetyper/adapters/nvim/commands/setup_keymaps.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local transform = require("codetyper.core.transform")
|
||||
|
||||
--- Setup default keymaps for transform commands
|
||||
local function setup_keymaps()
|
||||
vim.keymap.set("v", "<leader>ctt", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, {
|
||||
silent = true,
|
||||
desc = "Coder: Transform selection with prompt",
|
||||
})
|
||||
vim.keymap.set("n", "<leader>ctt", function()
|
||||
transform.cmd_transform_selection()
|
||||
end, {
|
||||
silent = true,
|
||||
desc = "Coder: Open prompt window",
|
||||
})
|
||||
end
|
||||
|
||||
return setup_keymaps
|
||||
@@ -334,8 +334,8 @@ function M.cmd_transform_selection()
|
||||
intent_override = doc_intent_override,
|
||||
is_whole_file = is_whole_file,
|
||||
}
|
||||
local autocmds = require("codetyper.adapters.nvim.autocmds")
|
||||
autocmds.process_single_prompt(bufnr, prompt, filepath, true)
|
||||
local process_single_prompt = require("codetyper.adapters.nvim.autocmds.process_single_prompt")
|
||||
process_single_prompt(bufnr, prompt, filepath, true)
|
||||
end
|
||||
|
||||
local augroup = vim.api.nvim_create_augroup("CodetyperPrompt_" .. prompt_buf, { clear = true })
|
||||
|
||||
@@ -28,17 +28,17 @@ function M.setup(opts)
|
||||
M.config = config.setup(opts)
|
||||
|
||||
-- Initialize modules
|
||||
local commands = require("codetyper.adapters.nvim.commands")
|
||||
local commands_setup = require("codetyper.adapters.nvim.commands.setup")
|
||||
local gitignore = require("codetyper.support.gitignore")
|
||||
local autocmds = require("codetyper.adapters.nvim.autocmds")
|
||||
local autocmds_setup = require("codetyper.adapters.nvim.autocmds.setup")
|
||||
local tree = require("codetyper.support.tree")
|
||||
local completion = require("codetyper.features.completion.inline")
|
||||
|
||||
-- Register commands
|
||||
commands.setup()
|
||||
commands_setup()
|
||||
|
||||
-- Setup autocommands
|
||||
autocmds.setup()
|
||||
autocmds_setup()
|
||||
|
||||
-- Setup file reference completion
|
||||
completion.setup()
|
||||
|
||||
Reference in New Issue
Block a user