From d11a99ee61464337f450bcc16ae42ad2d521436e Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Sun, 11 Jan 2026 15:38:41 -0500 Subject: [PATCH] feat: add :CoderTransform command for inline tag replacement New feature: Transform /@ @/ tags directly in any file, not just .coder.* files. The tags are replaced inline with LLM-generated code. New commands: - :CoderTransform - Transform ALL /@ @/ tags in current file - :CoderTransformCursor - Transform only the tag at cursor position - :Coder transform - Same as :CoderTransform - :Coder transform-cursor - Same as :CoderTransformCursor How it works: 1. Write /@ your prompt @/ anywhere in your code 2. Run :CoderTransform 3. The tag is replaced with generated code matching your file's style Example: /@ create a function to validate email @/ Becomes: function validateEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } --- lua/codetyper/autocmds.lua | 494 ++++++++++++++++----------------- lua/codetyper/commands.lua | 216 ++++++++++++++ lua/codetyper/prompts/code.lua | 7 + 3 files changed, 470 insertions(+), 247 deletions(-) diff --git a/lua/codetyper/autocmds.lua b/lua/codetyper/autocmds.lua index ff172f3..93aa9cb 100644 --- a/lua/codetyper/autocmds.lua +++ b/lua/codetyper/autocmds.lua @@ -20,182 +20,182 @@ local processed_prompts = {} ---@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)) + return string.format("%d:%d:%d:%s", bufnr, prompt.start_line, prompt.end_line, prompt.content:sub(1, 50)) end --- Schedule tree update with debounce local function schedule_tree_update() - if tree_update_timer then - tree_update_timer:stop() - end + if tree_update_timer then + tree_update_timer:stop() + end - tree_update_timer = vim.defer_fn(function() - local tree = require("codetyper.tree") - tree.update_tree_log() - tree_update_timer = nil - end, TREE_UPDATE_DEBOUNCE_MS) + tree_update_timer = vim.defer_fn(function() + local tree = require("codetyper.tree") + tree.update_tree_log() + tree_update_timer = nil + end, TREE_UPDATE_DEBOUNCE_MS) end --- Setup autocommands function M.setup() - local group = vim.api.nvim_create_augroup(AUGROUP, { clear = true }) + local group = vim.api.nvim_create_augroup(AUGROUP, { clear = true }) - -- Auto-save coder file when leaving insert mode - vim.api.nvim_create_autocmd("InsertLeave", { - group = group, - pattern = "*.coder.*", - callback = function() - -- Auto-save the coder file - if vim.bo.modified then - vim.cmd("silent! write") - end - -- Check for closed prompts and auto-process - M.check_for_closed_prompt() - end, - desc = "Auto-save and check for closed prompt tags", - }) + -- Auto-save coder file when leaving insert mode + vim.api.nvim_create_autocmd("InsertLeave", { + group = group, + pattern = "*.coder.*", + callback = function() + -- Auto-save the coder file + if vim.bo.modified then + vim.cmd("silent! write") + end + -- Check for closed prompts and auto-process + M.check_for_closed_prompt() + end, + desc = "Auto-save and check for closed prompt tags", + }) - -- Auto-set filetype for coder files based on extension - vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, { - group = group, - pattern = "*.coder.*", - callback = function() - M.set_coder_filetype() - end, - desc = "Set filetype for coder files", - }) + -- Auto-set filetype for coder files based on extension + vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, { + group = group, + pattern = "*.coder.*", + callback = function() + M.set_coder_filetype() + end, + desc = "Set filetype for coder files", + }) - -- Auto-open split view when opening a coder file directly (e.g., from nvim-tree) - vim.api.nvim_create_autocmd("BufEnter", { - group = group, - pattern = "*.coder.*", - callback = function() - -- Delay slightly to ensure buffer is fully loaded - vim.defer_fn(function() - M.auto_open_target_file() - end, 50) - end, - desc = "Auto-open target file when coder file is opened", - }) + -- Auto-open split view when opening a coder file directly (e.g., from nvim-tree) + vim.api.nvim_create_autocmd("BufEnter", { + group = group, + pattern = "*.coder.*", + callback = function() + -- Delay slightly to ensure buffer is fully loaded + vim.defer_fn(function() + M.auto_open_target_file() + end, 50) + end, + desc = "Auto-open target file when coder file is opened", + }) - -- Cleanup on buffer close - vim.api.nvim_create_autocmd("BufWipeout", { - group = group, - pattern = "*.coder.*", - callback = function(ev) - local window = require("codetyper.window") - if window.is_open() then - window.close_split() - end - -- Clear processed prompts for this buffer - 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 tracking - M.clear_auto_opened(bufnr) - end, - desc = "Cleanup on coder buffer close", - }) + -- Cleanup on buffer close + vim.api.nvim_create_autocmd("BufWipeout", { + group = group, + pattern = "*.coder.*", + callback = function(ev) + local window = require("codetyper.window") + if window.is_open() then + window.close_split() + end + -- Clear processed prompts for this buffer + 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 tracking + M.clear_auto_opened(bufnr) + end, + desc = "Cleanup on coder buffer close", + }) - -- Update tree.log when files are created/written - vim.api.nvim_create_autocmd({ "BufWritePost", "BufNewFile" }, { - group = group, - pattern = "*", - callback = function(ev) - -- Skip coder files and tree.log itself - local filepath = ev.file or vim.fn.expand("%:p") - if filepath:match("%.coder%.") or filepath:match("tree%.log$") then - return - end - -- Schedule tree update with debounce - schedule_tree_update() - end, - desc = "Update tree.log on file creation/save", - }) + -- Update tree.log when files are created/written + vim.api.nvim_create_autocmd({ "BufWritePost", "BufNewFile" }, { + group = group, + pattern = "*", + callback = function(ev) + -- Skip coder files and tree.log itself + local filepath = ev.file or vim.fn.expand("%:p") + if filepath:match("%.coder%.") or filepath:match("tree%.log$") then + return + end + -- Schedule tree update with debounce + schedule_tree_update() + end, + desc = "Update tree.log on file creation/save", + }) - -- Update tree.log when files are deleted (via netrw or file explorer) - vim.api.nvim_create_autocmd("BufDelete", { - group = group, - pattern = "*", - callback = function(ev) - local filepath = ev.file or "" - -- Skip special buffers and coder files - if filepath == "" or filepath:match("%.coder%.") or filepath:match("tree%.log$") then - return - end - schedule_tree_update() - end, - desc = "Update tree.log on file deletion", - }) + -- Update tree.log when files are deleted (via netrw or file explorer) + vim.api.nvim_create_autocmd("BufDelete", { + group = group, + pattern = "*", + callback = function(ev) + local filepath = ev.file or "" + -- Skip special buffers and coder files + if filepath == "" or filepath:match("%.coder%.") or filepath:match("tree%.log$") then + return + end + schedule_tree_update() + end, + desc = "Update tree.log on file deletion", + }) - -- Update tree on directory change - vim.api.nvim_create_autocmd("DirChanged", { - group = group, - pattern = "*", - callback = function() - schedule_tree_update() - end, - desc = "Update tree.log on directory change", - }) + -- Update tree on directory change + vim.api.nvim_create_autocmd("DirChanged", { + group = group, + pattern = "*", + callback = function() + schedule_tree_update() + end, + desc = "Update tree.log on directory change", + }) end --- Check if the buffer has a newly closed prompt and auto-process function M.check_for_closed_prompt() - local codetyper = require("codetyper") - local config = codetyper.get_config() - local parser = require("codetyper.parser") + local codetyper = require("codetyper") + local config = codetyper.get_config() + local parser = require("codetyper.parser") - local bufnr = vim.api.nvim_get_current_buf() + local bufnr = vim.api.nvim_get_current_buf() - -- Get current line - 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) + -- Get current line + 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 - return - end + if #lines == 0 then + return + end - local current_line = lines[1] + local current_line = lines[1] - -- Check if line contains closing tag - if parser.has_closing_tag(current_line, config.patterns.close_tag) then - -- Find the complete prompt - local prompt = parser.get_last_prompt(bufnr) - if prompt and prompt.content and prompt.content ~= "" then - -- Generate unique key for this prompt - local prompt_key = get_prompt_key(bufnr, prompt) + -- Check if line contains closing tag + if parser.has_closing_tag(current_line, config.patterns.close_tag) then + -- Find the complete prompt + local prompt = parser.get_last_prompt(bufnr) + if prompt and prompt.content and prompt.content ~= "" then + -- Generate unique key for this prompt + local prompt_key = get_prompt_key(bufnr, prompt) - -- Check if already processed - if processed_prompts[prompt_key] then - return - end + -- Check if already processed + if processed_prompts[prompt_key] then + return + end - -- Mark as processed - processed_prompts[prompt_key] = true + -- Mark as processed + processed_prompts[prompt_key] = true - -- Auto-process the prompt (no confirmation needed) - utils.notify("Processing prompt...", vim.log.levels.INFO) - vim.schedule(function() - vim.cmd("CoderProcess") - end) - end - end + -- Auto-process the prompt (no confirmation needed) + utils.notify("Processing prompt...", vim.log.levels.INFO) + vim.schedule(function() + vim.cmd("CoderProcess") + end) + end + end end --- Reset processed prompts for a buffer (useful for re-processing) ---@param bufnr? number Buffer number (default: current) function M.reset_processed(bufnr) - 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 - utils.notify("Prompt history cleared - prompts can be re-processed") + 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 + utils.notify("Prompt history cleared - prompts can be re-processed") end --- Track if we already opened the split for this buffer @@ -204,146 +204,146 @@ local auto_opened_buffers = {} --- Auto-open target file when a coder file is opened directly function M.auto_open_target_file() - local window = require("codetyper.window") + local window = require("codetyper.window") - -- Skip if split is already open - if window.is_open() then - return - end + -- Skip if split is already open + if window.is_open() then + return + end - local bufnr = vim.api.nvim_get_current_buf() + local bufnr = vim.api.nvim_get_current_buf() - -- Skip if we already handled this buffer - if auto_opened_buffers[bufnr] then - return - end + -- Skip if we already handled this buffer + if auto_opened_buffers[bufnr] then + return + end - local current_file = vim.fn.expand("%:p") + local current_file = vim.fn.expand("%:p") - -- Skip empty paths - if not current_file or current_file == "" then - return - end + -- Skip empty paths + if not current_file or current_file == "" then + return + end - -- Verify it's a coder file - if not utils.is_coder_file(current_file) then - return - end + -- Verify it's a coder file + if not utils.is_coder_file(current_file) then + return + end - -- Skip if we're in a special buffer (nvim-tree, etc.) - local buftype = vim.bo[bufnr].buftype - if buftype ~= "" then - return - end + -- Skip if we're in a special buffer (nvim-tree, etc.) + local buftype = vim.bo[bufnr].buftype + if buftype ~= "" then + return + end - -- Mark as handled - auto_opened_buffers[bufnr] = true + -- Mark as handled + auto_opened_buffers[bufnr] = true - -- Get the target file path - local target_path = utils.get_target_path(current_file) + -- Get the target file path + local target_path = utils.get_target_path(current_file) - -- Check if target file exists - if not utils.file_exists(target_path) then - utils.notify("Target file not found: " .. vim.fn.fnamemodify(target_path, ":t"), vim.log.levels.WARN) - return - end + -- Check if target file exists + if not utils.file_exists(target_path) then + utils.notify("Target file not found: " .. vim.fn.fnamemodify(target_path, ":t"), vim.log.levels.WARN) + return + end - -- Get config with fallback defaults - local codetyper = require("codetyper") - local config = codetyper.get_config() + -- Get config with fallback defaults + local codetyper = require("codetyper") + local config = codetyper.get_config() - -- Fallback width if config not fully loaded - local width = (config and config.window and config.window.width) or 0.4 - if width <= 1 then - width = math.floor(vim.o.columns * width) - end + -- Fallback width if config not fully loaded + local width = (config and config.window and config.window.width) or 0.4 + if width <= 1 then + width = math.floor(vim.o.columns * width) + end - -- Store current coder window - local coder_win = vim.api.nvim_get_current_win() - local coder_buf = bufnr + -- Store current coder window + local coder_win = vim.api.nvim_get_current_win() + local coder_buf = bufnr - -- Open target file in a vertical split on the right - local ok, err = pcall(function() - vim.cmd("vsplit " .. vim.fn.fnameescape(target_path)) - end) + -- Open target file in a vertical split on the right + local ok, err = pcall(function() + vim.cmd("vsplit " .. vim.fn.fnameescape(target_path)) + end) - if not ok then - utils.notify("Failed to open target file: " .. tostring(err), vim.log.levels.ERROR) - auto_opened_buffers[bufnr] = nil -- Allow retry - return - end + if not ok then + utils.notify("Failed to open target file: " .. tostring(err), vim.log.levels.ERROR) + auto_opened_buffers[bufnr] = nil -- Allow retry + return + end - -- Now we're in the target window (right side) - local target_win = vim.api.nvim_get_current_win() - local target_buf = vim.api.nvim_get_current_buf() + -- Now we're in the target window (right side) + local target_win = vim.api.nvim_get_current_win() + local target_buf = vim.api.nvim_get_current_buf() - -- Set the coder window width (left side) - pcall(vim.api.nvim_win_set_width, coder_win, width) + -- Set the coder window width (left side) + pcall(vim.api.nvim_win_set_width, coder_win, width) - -- Update window module state - window._coder_win = coder_win - window._coder_buf = coder_buf - window._target_win = target_win - window._target_buf = target_buf + -- Update window module state + window._coder_win = coder_win + window._coder_buf = coder_buf + window._target_win = target_win + window._target_buf = target_buf - -- Set up window options for coder window - pcall(function() - vim.wo[coder_win].number = true - vim.wo[coder_win].relativenumber = true - vim.wo[coder_win].signcolumn = "yes" - end) + -- Set up window options for coder window + pcall(function() + vim.wo[coder_win].number = true + vim.wo[coder_win].relativenumber = true + vim.wo[coder_win].signcolumn = "yes" + end) - utils.notify("Opened target: " .. vim.fn.fnamemodify(target_path, ":t")) + utils.notify("Opened target: " .. vim.fn.fnamemodify(target_path, ":t")) end --- Clear auto-opened tracking for a buffer ---@param bufnr number Buffer number function M.clear_auto_opened(bufnr) - auto_opened_buffers[bufnr] = nil + auto_opened_buffers[bufnr] = nil end --- Set appropriate filetype for coder files function M.set_coder_filetype() - local filepath = vim.fn.expand("%:p") + local filepath = vim.fn.expand("%:p") - -- Extract the actual extension (e.g., index.coder.ts -> ts) - local ext = filepath:match("%.coder%.(%w+)$") + -- Extract the actual extension (e.g., index.coder.ts -> ts) + local ext = filepath:match("%.coder%.(%w+)$") - if ext then - -- Map extension to filetype - 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", - } + if ext then + -- Map extension to filetype + 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 + local filetype = ft_map[ext] or ext + vim.bo.filetype = filetype + end end --- Clear all autocommands function M.clear() - vim.api.nvim_del_augroup_by_name(AUGROUP) + vim.api.nvim_del_augroup_by_name(AUGROUP) end return M diff --git a/lua/codetyper/commands.lua b/lua/codetyper/commands.lua index cc9bfa0..ea948f8 100644 --- a/lua/codetyper/commands.lua +++ b/lua/codetyper/commands.lua @@ -267,6 +267,210 @@ local function cmd_focus() end end +--- Transform inline /@ @/ tags in current file +--- Works on ANY file, not just .coder.* files +local function cmd_transform() + local parser = require("codetyper.parser") + local llm = require("codetyper.llm") + + local bufnr = vim.api.nvim_get_current_buf() + local filepath = vim.fn.expand("%:p") + + if filepath == "" then + utils.notify("No file in current buffer", vim.log.levels.WARN) + return + end + + -- Find all prompts in the current buffer + local prompts = parser.find_prompts_in_buffer(bufnr) + + if #prompts == 0 then + utils.notify("No /@ @/ tags found in current file", vim.log.levels.INFO) + return + end + + utils.notify("Found " .. #prompts .. " prompt(s) to transform...", vim.log.levels.INFO) + + -- Build context for this file + local ext = vim.fn.fnamemodify(filepath, ":e") + local context = llm.build_context(filepath, "code_generation") + + -- Process prompts in reverse order (bottom to top) to maintain line numbers + local sorted_prompts = {} + for i = #prompts, 1, -1 do + table.insert(sorted_prompts, prompts[i]) + end + + -- Track how many are being processed + local pending = #sorted_prompts + local completed = 0 + local errors = 0 + + -- Process each prompt + for _, prompt in ipairs(sorted_prompts) do + local clean_prompt = parser.clean_prompt(prompt.content) + local prompt_type = parser.detect_prompt_type(prompt.content) + + -- Build enhanced user prompt + local enhanced_prompt = "TASK: " .. clean_prompt .. "\n\n" + enhanced_prompt = enhanced_prompt .. "REQUIREMENTS:\n" + enhanced_prompt = enhanced_prompt .. "- Generate ONLY " .. (context.language or "code") .. " code\n" + enhanced_prompt = enhanced_prompt .. "- NO markdown code blocks (no ```)\n" + enhanced_prompt = enhanced_prompt .. "- NO explanations or comments about what you did\n" + enhanced_prompt = enhanced_prompt .. "- Match the coding style of the existing file exactly\n" + enhanced_prompt = enhanced_prompt .. "- Output must be ready to insert directly into the file\n" + + utils.notify("Processing: " .. clean_prompt:sub(1, 40) .. "...", vim.log.levels.INFO) + + -- Generate code for this prompt + llm.generate(enhanced_prompt, context, function(response, err) + if err then + utils.notify("Failed: " .. err, vim.log.levels.ERROR) + errors = errors + 1 + elseif response then + -- Replace the prompt tag with generated code + vim.schedule(function() + -- Get current buffer lines + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + + -- Calculate the exact range to replace + local start_line = prompt.start_line + local end_line = prompt.end_line + + -- Find the full lines containing the tags + local start_line_content = lines[start_line] or "" + local end_line_content = lines[end_line] or "" + + -- Check if there's content before the opening tag on the same line + local codetyper = require("codetyper") + local config = codetyper.get_config() + local before_tag = "" + local after_tag = "" + + local open_pos = start_line_content:find(utils.escape_pattern(config.patterns.open_tag)) + if open_pos and open_pos > 1 then + before_tag = start_line_content:sub(1, open_pos - 1) + end + + local close_pos = end_line_content:find(utils.escape_pattern(config.patterns.close_tag)) + if close_pos then + local after_close = close_pos + #config.patterns.close_tag + if after_close <= #end_line_content then + after_tag = end_line_content:sub(after_close) + end + end + + -- Build the replacement lines + local replacement_lines = vim.split(response, "\n", { plain = true }) + + -- Add before/after content if any + if before_tag ~= "" and #replacement_lines > 0 then + replacement_lines[1] = before_tag .. replacement_lines[1] + end + if after_tag ~= "" and #replacement_lines > 0 then + replacement_lines[#replacement_lines] = replacement_lines[#replacement_lines] .. after_tag + end + + -- Replace the lines in buffer + vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, replacement_lines) + + completed = completed + 1 + if completed + errors >= pending then + utils.notify( + "Transform complete: " .. completed .. " succeeded, " .. errors .. " failed", + errors > 0 and vim.log.levels.WARN or vim.log.levels.INFO + ) + end + end) + end + end) + end +end + +--- Transform a single prompt at cursor position +local function cmd_transform_at_cursor() + local parser = require("codetyper.parser") + local llm = require("codetyper.llm") + + local bufnr = vim.api.nvim_get_current_buf() + local filepath = vim.fn.expand("%:p") + + if filepath == "" then + utils.notify("No file in current buffer", vim.log.levels.WARN) + return + end + + -- Find prompt at cursor + local prompt = parser.get_prompt_at_cursor(bufnr) + + if not prompt then + utils.notify("No /@ @/ tag at cursor position", vim.log.levels.WARN) + return + end + + local clean_prompt = parser.clean_prompt(prompt.content) + local context = llm.build_context(filepath, "code_generation") + + -- Build enhanced user prompt + local enhanced_prompt = "TASK: " .. clean_prompt .. "\n\n" + enhanced_prompt = enhanced_prompt .. "REQUIREMENTS:\n" + enhanced_prompt = enhanced_prompt .. "- Generate ONLY " .. (context.language or "code") .. " code\n" + enhanced_prompt = enhanced_prompt .. "- NO markdown code blocks (no ```)\n" + enhanced_prompt = enhanced_prompt .. "- NO explanations or comments about what you did\n" + enhanced_prompt = enhanced_prompt .. "- Match the coding style of the existing file exactly\n" + enhanced_prompt = enhanced_prompt .. "- Output must be ready to insert directly into the file\n" + + utils.notify("Transforming: " .. clean_prompt:sub(1, 40) .. "...", vim.log.levels.INFO) + + llm.generate(enhanced_prompt, context, function(response, err) + if err then + utils.notify("Transform failed: " .. err, vim.log.levels.ERROR) + return + end + + if response then + vim.schedule(function() + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local start_line = prompt.start_line + local end_line = prompt.end_line + + local start_line_content = lines[start_line] or "" + local end_line_content = lines[end_line] or "" + + local codetyper = require("codetyper") + local config = codetyper.get_config() + local before_tag = "" + local after_tag = "" + + local open_pos = start_line_content:find(utils.escape_pattern(config.patterns.open_tag)) + if open_pos and open_pos > 1 then + before_tag = start_line_content:sub(1, open_pos - 1) + end + + local close_pos = end_line_content:find(utils.escape_pattern(config.patterns.close_tag)) + if close_pos then + local after_close = close_pos + #config.patterns.close_tag + if after_close <= #end_line_content then + after_tag = end_line_content:sub(after_close) + end + end + + local replacement_lines = vim.split(response, "\n", { plain = true }) + + if before_tag ~= "" and #replacement_lines > 0 then + replacement_lines[1] = before_tag .. replacement_lines[1] + end + if after_tag ~= "" and #replacement_lines > 0 then + replacement_lines[#replacement_lines] = replacement_lines[#replacement_lines] .. after_tag + end + + vim.api.nvim_buf_set_lines(bufnr, start_line - 1, end_line, false, replacement_lines) + utils.notify("Transform complete!", vim.log.levels.INFO) + end) + end + end) +end + --- Main command dispatcher ---@param args table Command arguments local function coder_cmd(args) @@ -287,6 +491,8 @@ local function coder_cmd(args) ["ask-toggle"] = cmd_ask_toggle, ["ask-clear"] = cmd_ask_clear, gitignore = cmd_gitignore, + transform = cmd_transform, + ["transform-cursor"] = cmd_transform_at_cursor, } local cmd_fn = commands[subcommand] @@ -306,6 +512,7 @@ function M.setup() "open", "close", "toggle", "process", "status", "focus", "tree", "tree-view", "reset", "gitignore", "ask", "ask-close", "ask-toggle", "ask-clear", + "transform", "transform-cursor", } end, desc = "Codetyper.nvim commands", @@ -348,6 +555,15 @@ function M.setup() vim.api.nvim_create_user_command("CoderAskClear", function() cmd_ask_clear() end, { desc = "Clear Ask history" }) + + -- Transform commands (inline /@ @/ tag replacement) + vim.api.nvim_create_user_command("CoderTransform", function() + cmd_transform() + end, { desc = "Transform all /@ @/ tags in current file" }) + + vim.api.nvim_create_user_command("CoderTransformCursor", function() + cmd_transform_at_cursor() + end, { desc = "Transform /@ @/ tag at cursor" }) end return M diff --git a/lua/codetyper/prompts/code.lua b/lua/codetyper/prompts/code.lua index 1341d79..b5ff1f9 100644 --- a/lua/codetyper/prompts/code.lua +++ b/lua/codetyper/prompts/code.lua @@ -4,6 +4,13 @@ local M = {} +/@ +create a prompt to generate code based on user description +-- @usage M.create_function +-- @param description string Description of the code to generate +-- @return string Prompt template +@/ + --- Prompt template for creating a new function M.create_function = [[Create a function with the following requirements: