feat: initial release of codetyper.nvim v0.2.0
AI-powered coding partner for Neovim with LLM integration. Features: - Split view for coder files (*.coder.*) and target files - Tag-based prompts with /@ and @/ syntax - Claude API and Ollama (local) LLM support - Smart prompt detection (refactor, add, document, explain) - Automatic code injection into target files - Project tree logging (.coder/tree.log) - Auto .gitignore management Ask Panel (chat interface): - Fixed at 1/4 screen width - File attachment with @ key - Ctrl+n for new chat - Ctrl+Enter to submit - Proper window close behavior - Navigation with Ctrl+h/j/k/l Commands: Coder, CoderOpen, CoderClose, CoderToggle, CoderProcess, CoderAsk, CoderTree, CoderTreeView
This commit is contained in:
330
lua/codetyper/commands.lua
Normal file
330
lua/codetyper/commands.lua
Normal file
@@ -0,0 +1,330 @@
|
||||
---@mod codetyper.commands Command definitions for Codetyper.nvim
|
||||
|
||||
local M = {}
|
||||
|
||||
local utils = require("codetyper.utils")
|
||||
local window = require("codetyper.window")
|
||||
|
||||
--- Open coder view for current file or select one
|
||||
---@param opts? table Command options
|
||||
local function cmd_open(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local current_file = vim.fn.expand("%:p")
|
||||
|
||||
-- If no file is open, prompt user to select one
|
||||
if current_file == "" or vim.bo.buftype ~= "" then
|
||||
-- Use telescope or vim.ui.select to pick a file
|
||||
if pcall(require, "telescope") then
|
||||
require("telescope.builtin").find_files({
|
||||
prompt_title = "Select file for Coder",
|
||||
attach_mappings = function(prompt_bufnr, map)
|
||||
local actions = require("telescope.actions")
|
||||
local action_state = require("telescope.actions.state")
|
||||
|
||||
actions.select_default:replace(function()
|
||||
actions.close(prompt_bufnr)
|
||||
local selection = action_state.get_selected_entry()
|
||||
if selection then
|
||||
local target_path = selection.path or selection[1]
|
||||
local coder_path = utils.get_coder_path(target_path)
|
||||
window.open_split(target_path, coder_path)
|
||||
end
|
||||
end)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
else
|
||||
-- Fallback to input prompt
|
||||
vim.ui.input({ prompt = "Enter file path: " }, function(input)
|
||||
if input and input ~= "" then
|
||||
local target_path = vim.fn.fnamemodify(input, ":p")
|
||||
local coder_path = utils.get_coder_path(target_path)
|
||||
window.open_split(target_path, coder_path)
|
||||
end
|
||||
end)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local target_path, coder_path
|
||||
|
||||
-- Check if current file is a coder file
|
||||
if utils.is_coder_file(current_file) then
|
||||
coder_path = current_file
|
||||
target_path = utils.get_target_path(current_file)
|
||||
else
|
||||
target_path = current_file
|
||||
coder_path = utils.get_coder_path(current_file)
|
||||
end
|
||||
|
||||
window.open_split(target_path, coder_path)
|
||||
end
|
||||
|
||||
--- Close coder view
|
||||
local function cmd_close()
|
||||
window.close_split()
|
||||
end
|
||||
|
||||
--- Toggle coder view
|
||||
local function cmd_toggle()
|
||||
local current_file = vim.fn.expand("%:p")
|
||||
|
||||
if current_file == "" then
|
||||
utils.notify("No file in current buffer", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local target_path, coder_path
|
||||
|
||||
if utils.is_coder_file(current_file) then
|
||||
coder_path = current_file
|
||||
target_path = utils.get_target_path(current_file)
|
||||
else
|
||||
target_path = current_file
|
||||
coder_path = utils.get_coder_path(current_file)
|
||||
end
|
||||
|
||||
window.toggle_split(target_path, coder_path)
|
||||
end
|
||||
|
||||
--- Process prompt at cursor and generate code
|
||||
local function cmd_process()
|
||||
local parser = require("codetyper.parser")
|
||||
local llm = require("codetyper.llm")
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local current_file = vim.fn.expand("%:p")
|
||||
|
||||
if not utils.is_coder_file(current_file) then
|
||||
utils.notify("Not a coder file. Use *.coder.* files", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local prompt = parser.get_last_prompt(bufnr)
|
||||
if not prompt then
|
||||
utils.notify("No prompt found. Use /@ your prompt @/", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local target_path = utils.get_target_path(current_file)
|
||||
local prompt_type = parser.detect_prompt_type(prompt.content)
|
||||
local context = llm.build_context(target_path, prompt_type)
|
||||
local clean_prompt = parser.clean_prompt(prompt.content)
|
||||
|
||||
llm.generate(clean_prompt, context, function(response, err)
|
||||
if err then
|
||||
utils.notify("Generation failed: " .. err, vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
if response then
|
||||
-- Inject code into target file
|
||||
local inject = require("codetyper.inject")
|
||||
inject.inject_code(target_path, response, prompt_type)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Show plugin status
|
||||
local function cmd_status()
|
||||
local codetyper = require("codetyper")
|
||||
local config = codetyper.get_config()
|
||||
local tree = require("codetyper.tree")
|
||||
|
||||
local stats = tree.get_stats()
|
||||
|
||||
local status = {
|
||||
"Codetyper.nvim Status",
|
||||
"====================",
|
||||
"",
|
||||
"Provider: " .. config.llm.provider,
|
||||
}
|
||||
|
||||
if config.llm.provider == "claude" then
|
||||
local has_key = (config.llm.claude.api_key or vim.env.ANTHROPIC_API_KEY) ~= nil
|
||||
table.insert(status, "Claude API Key: " .. (has_key and "configured" or "NOT SET"))
|
||||
table.insert(status, "Claude Model: " .. config.llm.claude.model)
|
||||
else
|
||||
table.insert(status, "Ollama Host: " .. config.llm.ollama.host)
|
||||
table.insert(status, "Ollama Model: " .. config.llm.ollama.model)
|
||||
end
|
||||
|
||||
table.insert(status, "")
|
||||
table.insert(status, "Window Position: " .. config.window.position)
|
||||
table.insert(status, "Window Width: " .. tostring(config.window.width * 100) .. "%")
|
||||
table.insert(status, "")
|
||||
table.insert(status, "View Open: " .. (window.is_open() and "yes" or "no"))
|
||||
table.insert(status, "")
|
||||
table.insert(status, "Project Stats:")
|
||||
table.insert(status, " Files: " .. stats.files)
|
||||
table.insert(status, " Directories: " .. stats.directories)
|
||||
table.insert(status, " Tree Log: " .. (tree.get_tree_log_path() or "N/A"))
|
||||
|
||||
utils.notify(table.concat(status, "\n"))
|
||||
end
|
||||
|
||||
--- Refresh tree.log manually
|
||||
local function cmd_tree()
|
||||
local tree = require("codetyper.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.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.autocmds")
|
||||
autocmds.reset_processed()
|
||||
end
|
||||
|
||||
--- Force update gitignore
|
||||
local function cmd_gitignore()
|
||||
local gitignore = require("codetyper.gitignore")
|
||||
gitignore.force_update()
|
||||
end
|
||||
|
||||
--- Open ask panel
|
||||
local function cmd_ask()
|
||||
local ask = require("codetyper.ask")
|
||||
ask.open()
|
||||
end
|
||||
|
||||
--- Close ask panel
|
||||
local function cmd_ask_close()
|
||||
local ask = require("codetyper.ask")
|
||||
ask.close()
|
||||
end
|
||||
|
||||
--- Toggle ask panel
|
||||
local function cmd_ask_toggle()
|
||||
local ask = require("codetyper.ask")
|
||||
ask.toggle()
|
||||
end
|
||||
|
||||
--- Clear ask history
|
||||
local function cmd_ask_clear()
|
||||
local ask = require("codetyper.ask")
|
||||
ask.clear_history()
|
||||
end
|
||||
|
||||
--- Switch focus between coder and target windows
|
||||
local function cmd_focus()
|
||||
if not window.is_open() then
|
||||
utils.notify("Coder view not open", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
if current_win == window.get_coder_win() then
|
||||
window.focus_target()
|
||||
else
|
||||
window.focus_coder()
|
||||
end
|
||||
end
|
||||
|
||||
--- Main command dispatcher
|
||||
---@param args table Command arguments
|
||||
local function coder_cmd(args)
|
||||
local subcommand = args.fargs[1] or "toggle"
|
||||
|
||||
local commands = {
|
||||
open = cmd_open,
|
||||
close = cmd_close,
|
||||
toggle = cmd_toggle,
|
||||
process = cmd_process,
|
||||
status = cmd_status,
|
||||
focus = cmd_focus,
|
||||
tree = cmd_tree,
|
||||
["tree-view"] = cmd_tree_view,
|
||||
reset = cmd_reset,
|
||||
ask = cmd_ask,
|
||||
["ask-close"] = cmd_ask_close,
|
||||
["ask-toggle"] = cmd_ask_toggle,
|
||||
["ask-clear"] = cmd_ask_clear,
|
||||
gitignore = cmd_gitignore,
|
||||
}
|
||||
|
||||
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 {
|
||||
"open", "close", "toggle", "process", "status", "focus",
|
||||
"tree", "tree-view", "reset", "gitignore",
|
||||
"ask", "ask-close", "ask-toggle", "ask-clear",
|
||||
}
|
||||
end,
|
||||
desc = "Codetyper.nvim commands",
|
||||
})
|
||||
|
||||
-- Convenience aliases
|
||||
vim.api.nvim_create_user_command("CoderOpen", function()
|
||||
cmd_open()
|
||||
end, { desc = "Open Coder view" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderClose", function()
|
||||
cmd_close()
|
||||
end, { desc = "Close Coder view" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderToggle", function()
|
||||
cmd_toggle()
|
||||
end, { desc = "Toggle Coder view" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderProcess", function()
|
||||
cmd_process()
|
||||
end, { desc = "Process prompt and generate code" })
|
||||
|
||||
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" })
|
||||
|
||||
-- Ask panel commands
|
||||
vim.api.nvim_create_user_command("CoderAsk", function()
|
||||
cmd_ask()
|
||||
end, { desc = "Open Ask panel" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderAskToggle", function()
|
||||
cmd_ask_toggle()
|
||||
end, { desc = "Toggle Ask panel" })
|
||||
|
||||
vim.api.nvim_create_user_command("CoderAskClear", function()
|
||||
cmd_ask_clear()
|
||||
end, { desc = "Clear Ask history" })
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user