From 5dc568e9e540344efded356c409b9e94a52ab8b6 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Fri, 31 Jan 2025 14:35:32 +0100 Subject: [PATCH] feat: also allow the config file without dot, give an error message at search replace if search was not found in file --- .chatgpt_config.yaml | 7 +++-- README.md | 8 ++--- lua/chatgpt_nvim/config.lua | 34 ++++++++++++++++++++-- lua/chatgpt_nvim/prompts.lua | 1 + lua/chatgpt_nvim/tools/replace_in_file.lua | 33 +++++++++++++++++++-- 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/.chatgpt_config.yaml b/.chatgpt_config.yaml index d0ad42b..8d36183 100644 --- a/.chatgpt_config.yaml +++ b/.chatgpt_config.yaml @@ -1,4 +1,4 @@ -project_name: "chatgpt_nvim" +project_name: "chatgpt.vim" default_prompt_blocks: - "basic-prompt" - "secure-coding" @@ -20,8 +20,9 @@ auto_lint: true # New tool auto-accept config tool_auto_accept: - readFile: false - editFile: false + readFile: true + editFile: true + replace_in_file: true executeCommand: false # If you set any of these to true, it will auto accept them without prompting. # 'executeCommand' should remain false by default unless you're certain it's safe. diff --git a/README.md b/README.md index e613435..809609b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to: 1. Generate prompts containing: - - An **initial prompt** (from `.chatgpt_config.yaml`) - - A list of directories (also specified in `.chatgpt_config.yaml`) from which it gathers the project structure and file contents + - An **initial prompt** (from `.chatgpt_config.yaml` or `chatgpt_config.yaml`) + - A list of directories (also specified in `.chatgpt_config.yaml` or `chatgpt_config.yaml`) from which it gathers the project structure and file contents - **Interactive file selection** if enabled, so you can pick exactly which directories to include - Any **initial files** you define (e.g., `README.md`, etc.) @@ -21,11 +21,11 @@ This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to - **Preview Changes**: If `preview_changes: true`, you get a buffer showing proposed changes before you apply them. -- **Interactive File Selection**: If `interactive_file_selection: true`, you choose which directories from `.chatgpt_config.yaml` get included in the prompt, reducing token usage. +- **Interactive File Selection**: If `interactive_file_selection: true`, you choose which directories from `.chatgpt_config.yaml` or `chatgpt_config.yaml` get included in the prompt, reducing token usage. - **Improved Debug**: If `improved_debug: true`, debug logs go into a dedicated `ChatGPT_Debug_Log` buffer for easier reading. -## Example `.chatgpt_config.yaml` +## Example `.chatgpt_config.yaml` or `chatgpt_config.yaml` ```yaml project_name: "chatgpt_nvim" diff --git a/lua/chatgpt_nvim/config.lua b/lua/chatgpt_nvim/config.lua index 2bd541f..63da008 100644 --- a/lua/chatgpt_nvim/config.lua +++ b/lua/chatgpt_nvim/config.lua @@ -4,6 +4,7 @@ local uv = vim.loop local ok_yaml, lyaml = pcall(require, "lyaml") local prompts = require("chatgpt_nvim.prompts") +-- Determines the Git root or fallback directory. local function get_project_root() local current_file = vim.fn.expand("%:p") local root_dir @@ -28,14 +29,30 @@ local function get_project_root() return root_dir end +-- Attempt to locate either .chatgpt_config.yaml or chatgpt_config.yaml, returning whichever exists first. local function get_config_path() local root = get_project_root() - return root .. "/.chatgpt_config.yaml" + local dot_config = root .. "/.chatgpt_config.yaml" + local non_dot_config = root .. "/chatgpt_config.yaml" + + local dot_fd = uv.fs_open(dot_config, "r", 438) + if dot_fd then + uv.fs_close(dot_fd) + return dot_config + end + + local non_dot_fd = uv.fs_open(non_dot_config, "r", 438) + if non_dot_fd then + uv.fs_close(non_dot_fd) + return non_dot_config + end + + return nil end function M.load() + -- Attempt to find either config file, else fallback local path = get_config_path() - local fd = uv.fs_open(path, "r", 438) local config = { initial_prompt = "", directories = { "." }, @@ -62,10 +79,22 @@ function M.load() } } + -- If no config file found, alert user and return default config + if not path then + config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..." + vim.notify( + "No config file found (tried .chatgpt_config.yaml, chatgpt_config.yaml). Using defaults.", + vim.log.levels.WARN + ) + return config + end + + local fd = uv.fs_open(path, "r", 438) if fd then local stat = uv.fs_fstat(fd) local data = uv.fs_read(fd, stat.size, 0) uv.fs_close(fd) + if data and ok_yaml then local ok, result = pcall(lyaml.load, data) if ok and type(result) == "table" then @@ -109,7 +138,6 @@ function M.load() config.enable_step_by_step = result.enable_step_by_step end - -- auto_lint controls whether we do LSP-based checks if type(result.auto_lint) == "boolean" then config.auto_lint = result.auto_lint end diff --git a/lua/chatgpt_nvim/prompts.lua b/lua/chatgpt_nvim/prompts.lua index 941ddab..6c4e040 100644 --- a/lua/chatgpt_nvim/prompts.lua +++ b/lua/chatgpt_nvim/prompts.lua @@ -289,6 +289,7 @@ local M = { - If multiple tools are needed, list them sequentially in the `tools` array. - Always run at least one tool (e.g., `readFile`, `editFile`, `executeCommand`), exept you have finished. - Always just include one yaml in the response with all the tools you want to run in that yaml. + - Never do write operations on a file which you have not read before. - The plugin will verify the `project_name` is correct before running any tools. - If the response grows too large, I'll guide you to break it into smaller steps. ]], diff --git a/lua/chatgpt_nvim/tools/replace_in_file.lua b/lua/chatgpt_nvim/tools/replace_in_file.lua index 976f360..c49fa46 100644 --- a/lua/chatgpt_nvim/tools/replace_in_file.lua +++ b/lua/chatgpt_nvim/tools/replace_in_file.lua @@ -3,14 +3,35 @@ local robust_lsp = require("chatgpt_nvim.tools.lsp_robust_diagnostics") local M = {} +-- Enhanced search_and_replace to track if a string was found. We use Lua’s gsub return value +-- (updatedString, replacementCount) to see if any replacements occurred. local function search_and_replace(original, replacements) local updated = original + local info_msgs = {} + for _, r in ipairs(replacements) do local search_str = r.search or "" local replace_str = r.replace or "" - updated = updated:gsub(search_str, replace_str) + + local replacement_count = 0 + updated, replacement_count = updated:gsub(search_str, replace_str) + + -- If the string was not found, append an info message + if replacement_count == 0 then + table.insert(info_msgs, string.format( + "[replace_in_file Info] The string '%s' was NOT found in the file and was not replaced.", + search_str + )) + else + table.insert(info_msgs, string.format( + "[replace_in_file Info] The string '%s' was replaced %d time(s).", + search_str, + replacement_count + )) + end end - return updated + + return updated, table.concat(info_msgs, "\n") end M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file) @@ -31,7 +52,8 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file return string.format("[replace_in_file for '%s'] FAILED. Could not read file.", path) end - local updated_data = search_and_replace(orig_data, replacements) + -- Now we get not only the updated data but also info messages about replacements + local updated_data, info_messages = search_and_replace(orig_data, replacements) handler.write_file(path, updated_data, conf) local msg = {} @@ -40,6 +62,11 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file msg[#msg+1] = string.format("\n%s\n", path, updated_data) msg[#msg+1] = "\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference.\n" + -- If there are any info messages (strings not found, etc.), include them + if info_messages and info_messages ~= "" then + msg[#msg+1] = "\nAdditional info about the replacement operation:\n" .. info_messages .. "\n" + end + if conf.auto_lint then local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000) msg[#msg+1] = "\n" .. diag_str