---@mod codetyper.inject Code injection for Codetyper.nvim local M = {} local utils = require("codetyper.support.utils") --- Inject generated code into target file ---@param target_path string Path to target file ---@param code string Generated code ---@param prompt_type string Type of prompt (refactor, add, document, etc.) function M.inject_code(target_path, code, prompt_type) -- Normalize the target path target_path = vim.fn.fnamemodify(target_path, ":p") -- Try to find buffer by path local target_buf = nil for _, buf in ipairs(vim.api.nvim_list_bufs()) do local buf_name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":p") if buf_name == target_path then target_buf = buf break end end -- If still not found, open the file if not target_buf or not vim.api.nvim_buf_is_valid(target_buf) then -- Check if file exists if utils.file_exists(target_path) then vim.cmd("edit " .. vim.fn.fnameescape(target_path)) target_buf = vim.api.nvim_get_current_buf() utils.notify("Opened target file: " .. vim.fn.fnamemodify(target_path, ":t")) else utils.notify("Target file not found: " .. target_path, vim.log.levels.ERROR) return end end if not target_buf then utils.notify("Target buffer not found", vim.log.levels.ERROR) return end utils.notify("Injecting code into: " .. vim.fn.fnamemodify(target_path, ":t")) -- Different injection strategies based on prompt type if prompt_type == "refactor" then M.inject_refactor(target_buf, code) elseif prompt_type == "add" then M.inject_add(target_buf, code) elseif prompt_type == "document" then M.inject_document(target_buf, code) else -- For generic, auto-add instead of prompting M.inject_add(target_buf, code) end -- Mark buffer as modified and save vim.bo[target_buf].modified = true -- Auto-save the target file vim.schedule(function() if vim.api.nvim_buf_is_valid(target_buf) then local wins = vim.fn.win_findbuf(target_buf) if #wins > 0 then vim.api.nvim_win_call(wins[1], function() vim.cmd("silent! write") end) end end end) end --- Inject code with strategy and range (used by patch system) ---@param bufnr number Buffer number ---@param code string Generated code ---@param opts table|nil { strategy = "replace"|"insert"|"append", range = { start_line, end_line } (1-based) } ---@return table { imports_added: number, body_lines: number, imports_merged: boolean } function M.inject(bufnr, code, opts) opts = opts or {} local strategy = opts.strategy or "replace" local range = opts.range local lines = vim.split(code, "\n", { plain = true }) local body_lines = #lines if not vim.api.nvim_buf_is_valid(bufnr) then return { imports_added = 0, body_lines = 0, imports_merged = false } end local line_count = vim.api.nvim_buf_line_count(bufnr) if strategy == "replace" and range and range.start_line and range.end_line then local start_0 = math.max(0, range.start_line - 1) local end_0 = math.min(line_count, range.end_line) if end_0 < start_0 then end_0 = start_0 end vim.api.nvim_buf_set_lines(bufnr, start_0, end_0, false, lines) elseif strategy == "insert" and range and range.start_line then local at_0 = math.max(0, math.min(range.start_line - 1, line_count)) vim.api.nvim_buf_set_lines(bufnr, at_0, at_0, false, lines) else -- append vim.api.nvim_buf_set_lines(bufnr, line_count, line_count, false, lines) end return { imports_added = 0, body_lines = body_lines, imports_merged = false } end --- Inject code for refactor (replace entire file) ---@param bufnr number Buffer number ---@param code string Generated code function M.inject_refactor(bufnr, code) local lines = vim.split(code, "\n", { plain = true }) -- Save cursor position local cursor = nil local wins = vim.fn.win_findbuf(bufnr) if #wins > 0 then cursor = vim.api.nvim_win_get_cursor(wins[1]) end -- Replace buffer content vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) -- Restore cursor position if possible if cursor then local line_count = vim.api.nvim_buf_line_count(bufnr) cursor[1] = math.min(cursor[1], line_count) pcall(vim.api.nvim_win_set_cursor, wins[1], cursor) end utils.notify("Code refactored", vim.log.levels.INFO) end --- Inject code for add (append at cursor or end) ---@param bufnr number Buffer number ---@param code string Generated code function M.inject_add(bufnr, code) local lines = vim.split(code, "\n", { plain = true }) -- Try to find a window displaying this buffer to get cursor position local insert_line local wins = vim.fn.win_findbuf(bufnr) if #wins > 0 then local cursor = vim.api.nvim_win_get_cursor(wins[1]) insert_line = cursor[1] else insert_line = vim.api.nvim_buf_line_count(bufnr) end -- Insert lines at position vim.api.nvim_buf_set_lines(bufnr, insert_line, insert_line, false, lines) utils.notify("Code added at line " .. (insert_line + 1), vim.log.levels.INFO) end --- Inject documentation ---@param bufnr number Buffer number ---@param code string Generated documentation function M.inject_document(bufnr, code) -- Documentation typically goes above the current function/class -- For simplicity, insert at cursor position M.inject_add(bufnr, code) utils.notify("Documentation added", vim.log.levels.INFO) end --- Generic injection (prompt user for action) ---@param bufnr number Buffer number ---@param code string Generated code function M.inject_generic(bufnr, code) local actions = { "Replace entire file", "Insert at cursor", "Append to end", "Copy to clipboard", "Cancel", } vim.ui.select(actions, { prompt = "How to inject the generated code?", }, function(choice) if not choice then return end if choice == "Replace entire file" then M.inject_refactor(bufnr, code) elseif choice == "Insert at cursor" then M.inject_add(bufnr, code) elseif choice == "Append to end" then local lines = vim.split(code, "\n", { plain = true }) local line_count = vim.api.nvim_buf_line_count(bufnr) vim.api.nvim_buf_set_lines(bufnr, line_count, line_count, false, lines) utils.notify("Code appended to end", vim.log.levels.INFO) elseif choice == "Copy to clipboard" then vim.fn.setreg("+", code) utils.notify("Code copied to clipboard", vim.log.levels.INFO) end end) end --- Preview code in a floating window before injection ---@param code string Generated code ---@param callback fun(action: string) Callback with selected action function M.preview(code, callback) local codetyper = require("codetyper") local config = codetyper.get_config() local lines = vim.split(code, "\n", { plain = true }) -- Create buffer for preview local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) -- Calculate window size local width = math.min(80, vim.o.columns - 10) local height = math.min(#lines + 2, vim.o.lines - 10) -- Create floating window local win = vim.api.nvim_open_win(buf, true, { relative = "editor", width = width, height = height, row = math.floor((vim.o.lines - height) / 2), col = math.floor((vim.o.columns - width) / 2), style = "minimal", border = config.window.border, title = " Generated Code Preview ", title_pos = "center", }) -- Set buffer options vim.bo[buf].modifiable = false vim.bo[buf].bufhidden = "wipe" -- Add keymaps for actions local opts = { buffer = buf, noremap = true, silent = true } vim.keymap.set("n", "q", function() vim.api.nvim_win_close(win, true) callback("cancel") end, opts) vim.keymap.set("n", "", function() vim.api.nvim_win_close(win, true) callback("inject") end, opts) vim.keymap.set("n", "y", function() vim.fn.setreg("+", code) utils.notify("Copied to clipboard") end, opts) -- Show help in command line vim.api.nvim_echo({ { "Press ", "Normal" }, { "", "Keyword" }, { " to inject, ", "Normal" }, { "y", "Keyword" }, { " to copy, ", "Normal" }, { "q", "Keyword" }, { " to cancel", "Normal" }, }, false, {}) end return M