Compare commits

...

4 Commits

6 changed files with 77 additions and 14 deletions

View File

@@ -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.

View File

@@ -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"

View File

@@ -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
@@ -145,6 +173,12 @@ function M.load()
end
end
-- Include project name in the final initial prompt, if set
if config.project_name ~= "" then
config.initial_prompt =
"[Project Name: " .. config.project_name .. "]\n\n" .. config.initial_prompt
end
if config.debug then
vim.api.nvim_out_write("[chatgpt_nvim:config] Loaded config from: " .. path .. "\n")
vim.api.nvim_out_write("[chatgpt_nvim:config] Debug logging is enabled.\n")

View File

@@ -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. Its contents must be in your context before writing. This does not apply if you create a new file.
- 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.
]],

View File

@@ -14,7 +14,7 @@ M.available_tools = {
},
{
name = "editFile",
usage = "Overwrite an entire file's content. Provide { tool='editFile', path='...', content='...' }",
usage = "Overwrite an entire file's content. Provide { tool='editFile', path='...', content='...' }, Allways include the whole file content",
explanation = "Use this when you want to replace a file with new content."
},
{

View File

@@ -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 Luas 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("<final_file_content path=\"%s\">\n%s\n</final_file_content>", 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