Compare commits
10 Commits
change-to-
...
6723b802bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 6723b802bf | |||
| aa12bca3ab | |||
| e032be0118 | |||
| deaec8094b | |||
| a27e3da769 | |||
| 84e71dcbef | |||
| 50ba937ae0 | |||
| d5b05ede36 | |||
| 5dc568e9e5 | |||
| e9fd204977 |
@@ -4,8 +4,8 @@
|
|||||||
This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to:
|
This plugin integrates a ChatGPT O1 model workflow into Neovim. It allows you to:
|
||||||
|
|
||||||
1. Generate prompts containing:
|
1. Generate prompts containing:
|
||||||
- An **initial prompt** (from `.chatgpt_config.yaml`)
|
- An **initial prompt** (from `.chatgpt_config.yaml` or `chatgpt_config.yaml`)
|
||||||
- A list of directories (also specified in `.chatgpt_config.yaml`) from which it gathers the project structure and file contents
|
- 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
|
- **Interactive file selection** if enabled, so you can pick exactly which directories to include
|
||||||
- Any **initial files** you define (e.g., `README.md`, etc.)
|
- 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.
|
- **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.
|
- **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
|
```yaml
|
||||||
project_name: "chatgpt_nvim"
|
project_name: "chatgpt_nvim"
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
project_name: "chatgpt_nvim"
|
project_name: "chatgpt.vim"
|
||||||
default_prompt_blocks:
|
default_prompt_blocks:
|
||||||
- "basic-prompt"
|
- "basic-prompt"
|
||||||
- "secure-coding"
|
- "secure-coding"
|
||||||
initial_files:
|
|
||||||
- "README.md"
|
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
improved_debug: false
|
improved_debug: false
|
||||||
@@ -20,8 +18,9 @@ auto_lint: true
|
|||||||
|
|
||||||
# New tool auto-accept config
|
# New tool auto-accept config
|
||||||
tool_auto_accept:
|
tool_auto_accept:
|
||||||
readFile: false
|
readFile: true
|
||||||
editFile: false
|
editFile: true
|
||||||
|
replace_in_file: true
|
||||||
executeCommand: false
|
executeCommand: false
|
||||||
# If you set any of these to true, it will auto accept them without prompting.
|
# 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.
|
# 'executeCommand' should remain false by default unless you're certain it's safe.
|
||||||
@@ -4,6 +4,7 @@ local uv = vim.loop
|
|||||||
local ok_yaml, lyaml = pcall(require, "lyaml")
|
local ok_yaml, lyaml = pcall(require, "lyaml")
|
||||||
local prompts = require("chatgpt_nvim.prompts")
|
local prompts = require("chatgpt_nvim.prompts")
|
||||||
|
|
||||||
|
-- Determines the Git root or fallback directory.
|
||||||
local function get_project_root()
|
local function get_project_root()
|
||||||
local current_file = vim.fn.expand("%:p")
|
local current_file = vim.fn.expand("%:p")
|
||||||
local root_dir
|
local root_dir
|
||||||
@@ -28,14 +29,30 @@ local function get_project_root()
|
|||||||
return root_dir
|
return root_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Attempt to locate either .chatgpt_config.yaml or chatgpt_config.yaml, returning whichever exists first.
|
||||||
local function get_config_path()
|
local function get_config_path()
|
||||||
local root = get_project_root()
|
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
|
end
|
||||||
|
|
||||||
function M.load()
|
function M.load()
|
||||||
|
-- Attempt to find either config file, else fallback
|
||||||
local path = get_config_path()
|
local path = get_config_path()
|
||||||
local fd = uv.fs_open(path, "r", 438)
|
|
||||||
local config = {
|
local config = {
|
||||||
initial_prompt = "",
|
initial_prompt = "",
|
||||||
directories = { "." },
|
directories = { "." },
|
||||||
@@ -44,6 +61,7 @@ function M.load()
|
|||||||
project_name = "",
|
project_name = "",
|
||||||
debug = false,
|
debug = false,
|
||||||
initial_files = {},
|
initial_files = {},
|
||||||
|
include_file_contents = false, -- NEW FLAG: include file contents in prompt if true
|
||||||
preview_changes = false,
|
preview_changes = false,
|
||||||
interactive_file_selection = false,
|
interactive_file_selection = false,
|
||||||
partial_acceptance = false,
|
partial_acceptance = false,
|
||||||
@@ -62,10 +80,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
|
if fd then
|
||||||
local stat = uv.fs_fstat(fd)
|
local stat = uv.fs_fstat(fd)
|
||||||
local data = uv.fs_read(fd, stat.size, 0)
|
local data = uv.fs_read(fd, stat.size, 0)
|
||||||
uv.fs_close(fd)
|
uv.fs_close(fd)
|
||||||
|
|
||||||
if data and ok_yaml then
|
if data and ok_yaml then
|
||||||
local ok, result = pcall(lyaml.load, data)
|
local ok, result = pcall(lyaml.load, data)
|
||||||
if ok and type(result) == "table" then
|
if ok and type(result) == "table" then
|
||||||
@@ -90,6 +120,9 @@ function M.load()
|
|||||||
if type(result.initial_files) == "table" then
|
if type(result.initial_files) == "table" then
|
||||||
config.initial_files = result.initial_files
|
config.initial_files = result.initial_files
|
||||||
end
|
end
|
||||||
|
if type(result.include_file_contents) == "boolean" then -- LOAD NEW FLAG
|
||||||
|
config.include_file_contents = result.include_file_contents
|
||||||
|
end
|
||||||
if type(result.preview_changes) == "boolean" then
|
if type(result.preview_changes) == "boolean" then
|
||||||
config.preview_changes = result.preview_changes
|
config.preview_changes = result.preview_changes
|
||||||
end
|
end
|
||||||
@@ -109,7 +142,6 @@ function M.load()
|
|||||||
config.enable_step_by_step = result.enable_step_by_step
|
config.enable_step_by_step = result.enable_step_by_step
|
||||||
end
|
end
|
||||||
|
|
||||||
-- auto_lint controls whether we do LSP-based checks
|
|
||||||
if type(result.auto_lint) == "boolean" then
|
if type(result.auto_lint) == "boolean" then
|
||||||
config.auto_lint = result.auto_lint
|
config.auto_lint = result.auto_lint
|
||||||
end
|
end
|
||||||
@@ -145,6 +177,12 @@ function M.load()
|
|||||||
end
|
end
|
||||||
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
|
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] Loaded config from: " .. path .. "\n")
|
||||||
vim.api.nvim_out_write("[chatgpt_nvim:config] Debug logging is enabled.\n")
|
vim.api.nvim_out_write("[chatgpt_nvim:config] Debug logging is enabled.\n")
|
||||||
|
|||||||
@@ -185,4 +185,22 @@ function M.get_file_contents(files, conf)
|
|||||||
return table.concat(sections, "\n")
|
return table.concat(sections, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- NEW FUNCTION: Build the project prompt by optionally including file contents.
|
||||||
|
function M.get_project_prompt(directories, conf)
|
||||||
|
local structure = M.get_project_structure(directories, conf)
|
||||||
|
if conf.include_file_contents then
|
||||||
|
local files = M.get_project_files(directories, conf)
|
||||||
|
local contents = M.get_file_contents(files, conf)
|
||||||
|
local total_chars = #contents
|
||||||
|
if total_chars > conf.prompt_char_limit then
|
||||||
|
vim.notify("Total file contents (" .. total_chars .. " characters) exceed the prompt limit (" .. conf.prompt_char_limit .. "). Please disable 'include_file_contents' in your config.", vim.log.levels.ERROR)
|
||||||
|
return structure
|
||||||
|
else
|
||||||
|
return structure .. "\n" .. contents
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return structure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
@@ -133,7 +133,8 @@ local function build_prompt(user_input, dirs, conf)
|
|||||||
env_lines[#env_lines+1] = os.date("%x, %X (%Z)")
|
env_lines[#env_lines+1] = os.date("%x, %X (%Z)")
|
||||||
env_lines[#env_lines+1] = ""
|
env_lines[#env_lines+1] = ""
|
||||||
env_lines[#env_lines+1] = "# Current Working Directory (" .. root .. ") Files"
|
env_lines[#env_lines+1] = "# Current Working Directory (" .. root .. ") Files"
|
||||||
env_lines[#env_lines+1] = context.get_project_structure(dirs, conf) or ""
|
-- Using the new get_project_prompt function instead of get_project_structure.
|
||||||
|
env_lines[#env_lines+1] = context.get_project_prompt(dirs, conf) or ""
|
||||||
env_lines[#env_lines+1] = ""
|
env_lines[#env_lines+1] = ""
|
||||||
env_lines[#env_lines+1] = "# Current Mode"
|
env_lines[#env_lines+1] = "# Current Mode"
|
||||||
env_lines[#env_lines+1] = "ACT MODE"
|
env_lines[#env_lines+1] = "ACT MODE"
|
||||||
|
|||||||
@@ -289,8 +289,62 @@ local M = {
|
|||||||
- If multiple tools are needed, list them sequentially in the `tools` array.
|
- 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 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.
|
- 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.
|
- 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.
|
- If the response grows too large, I'll guide you to break it into smaller steps.
|
||||||
|
|
||||||
|
You are assisting me in a coding workflow for a project (e.g., "my_project").
|
||||||
|
|
||||||
|
1. **Gather Context / Ask Questions**
|
||||||
|
- If you need more information or something is unclear, **ask me directly** in plain text, without calling any tools.
|
||||||
|
|
||||||
|
2. **Inspect Files**
|
||||||
|
- When you need to check a file’s content, use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "readFile"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
```
|
||||||
|
- Read the file before deciding on any modifications.
|
||||||
|
|
||||||
|
3. **Make Changes**
|
||||||
|
- If you need to modify an existing file (after reading it), use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "editFile"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
content: |
|
||||||
|
# Full updated file content
|
||||||
|
```
|
||||||
|
- Or perform incremental text replacements with:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "replace_in_file"
|
||||||
|
path: "relative/path/to/file"
|
||||||
|
replacements:
|
||||||
|
- search: "old text"
|
||||||
|
replace: "new text"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run Commands (Optional)**
|
||||||
|
- To run tests, list files, or do other checks, use:
|
||||||
|
```yaml
|
||||||
|
project_name: "my_project"
|
||||||
|
tools:
|
||||||
|
- tool: "executeCommand"
|
||||||
|
command: "shell command here"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Important Rules**
|
||||||
|
- Always start with 1 or 2, but afterwards you can mix 1, 2, 3, and 4 as needed.
|
||||||
|
- Include `project_name: "my_project"` whenever you call `tools`.
|
||||||
|
- Keep each tool call in the `tools` array (multiple if needed).
|
||||||
|
- **Never write to a file you haven’t read** in this session and already got the content from an response (unless creating a new file).
|
||||||
|
- Follow secure coding guidelines (input validation, least privilege, no sensitive info in logs, etc.).
|
||||||
|
- When done, provide a final answer **without** calling any tools.
|
||||||
]],
|
]],
|
||||||
["secure-coding"] = [[
|
["secure-coding"] = [[
|
||||||
### Secure Coding Guidelines
|
### Secure Coding Guidelines
|
||||||
|
|||||||
@@ -1,17 +1,36 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||||
|
-- Validate the command exists
|
||||||
local cmd = tool_call.command
|
local cmd = tool_call.command
|
||||||
if not cmd then
|
if not cmd then
|
||||||
return "[executeCommand] Missing 'command'."
|
return "[executeCommand] Missing 'command'."
|
||||||
end
|
end
|
||||||
local handle = io.popen(cmd)
|
|
||||||
|
-- Capture stderr and stdout together by redirecting stderr to stdout
|
||||||
|
-- This will help diagnose if there's an error causing no output
|
||||||
|
cmd = cmd .. " 2>&1"
|
||||||
|
|
||||||
|
-- Attempt to popen the command
|
||||||
|
local handle = io.popen(cmd, "r")
|
||||||
if not handle then
|
if not handle then
|
||||||
return string.format("Tool [executeCommand '%s'] FAILED to popen.", cmd)
|
return string.format("Tool [executeCommand '%s'] FAILED to popen.", cmd)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Read the full output (stdout + stderr)
|
||||||
local result = handle:read("*a") or ""
|
local result = handle:read("*a") or ""
|
||||||
handle:close()
|
|
||||||
return string.format("Tool [executeCommand '%s'] Result:\n%s", cmd, result)
|
-- Attempt to close, capturing exit info
|
||||||
|
local _, exit_reason, exit_code = handle:close()
|
||||||
|
|
||||||
|
-- Provide a richer summary including exit code and reason
|
||||||
|
return string.format(
|
||||||
|
"Tool [executeCommand '%s'] exited with code %s (%s)\n%s",
|
||||||
|
cmd,
|
||||||
|
tostring(exit_code),
|
||||||
|
tostring(exit_reason),
|
||||||
|
result
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ M.available_tools = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "editFile",
|
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."
|
explanation = "Use this when you want to replace a file with new content."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ M.available_tools = {
|
|||||||
{
|
{
|
||||||
name = "executeCommand",
|
name = "executeCommand",
|
||||||
usage = "Run a shell command. Provide { tool='executeCommand', command='...' }",
|
usage = "Run a shell command. Provide { tool='executeCommand', command='...' }",
|
||||||
explanation = "Use with caution, especially for destructive operations (rm, sudo, etc.)."
|
explanation = "Just run one single command per tool invocation, without comment. It must be a single line. Use with caution, especially for destructive operations (rm, sudo, etc.)."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ local function send_did_open(bufnr, client_id, path, filetype)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function send_did_change(bufnr, client_id)
|
local function send_did_change(bufnr, client_id, path)
|
||||||
local client = lsp.get_client_by_id(client_id)
|
local client = lsp.get_client_by_id(client_id)
|
||||||
if not client then return end
|
if not client then return end
|
||||||
|
|
||||||
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, false), "\n")
|
||||||
local uri = vim.uri_from_bufnr(bufnr)
|
local uri = vim.uri_from_fname(path)
|
||||||
local version = 1
|
local version = 1
|
||||||
|
|
||||||
client.rpc.notify("textDocument/didChange", {
|
client.rpc.notify("textDocument/didChange", {
|
||||||
@@ -105,6 +105,7 @@ local function send_did_change(bufnr, client_id)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Use vim.wait to allow the event loop to process LSP diagnostics
|
||||||
local function wait_for_diagnostics(bufnr, timeout_ms)
|
local function wait_for_diagnostics(bufnr, timeout_ms)
|
||||||
local done = false
|
local done = false
|
||||||
local result_diags = {}
|
local result_diags = {}
|
||||||
@@ -121,12 +122,9 @@ local function wait_for_diagnostics(bufnr, timeout_ms)
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
local waited = 0
|
vim.wait(timeout_ms, function()
|
||||||
local interval = 50
|
return done
|
||||||
while not done and waited < timeout_ms do
|
end, 50)
|
||||||
vim.cmd(("sleep %d m"):format(interval))
|
|
||||||
waited = waited + interval
|
|
||||||
end
|
|
||||||
|
|
||||||
pcall(api.nvim_del_augroup_by_id, augrp)
|
pcall(api.nvim_del_augroup_by_id, augrp)
|
||||||
return result_diags
|
return result_diags
|
||||||
@@ -164,8 +162,8 @@ function M.lsp_check_file_content(path, new_content, timeout_ms)
|
|||||||
return "(LSP) " .. err2
|
return "(LSP) " .. err2
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Optionally do a didChange
|
-- Optionally do a didChange with consistent URI
|
||||||
send_did_change(bufnr, client_id)
|
send_did_change(bufnr, client_id, path)
|
||||||
|
|
||||||
local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000)
|
local diags = wait_for_diagnostics(bufnr, timeout_ms or 2000)
|
||||||
local diag_str = diagnostics_to_string(diags)
|
local diag_str = diagnostics_to_string(diags)
|
||||||
|
|||||||
@@ -3,14 +3,41 @@ local robust_lsp = require("chatgpt_nvim.tools.lsp_robust_diagnostics")
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
-- Function to escape all Lua pattern magic characters:
|
||||||
|
local function escape_lua_pattern(s)
|
||||||
|
return s:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
||||||
|
end
|
||||||
|
|
||||||
local function search_and_replace(original, replacements)
|
local function search_and_replace(original, replacements)
|
||||||
local updated = original
|
local updated = original
|
||||||
|
local info_msgs = {}
|
||||||
|
|
||||||
for _, r in ipairs(replacements) do
|
for _, r in ipairs(replacements) do
|
||||||
local search_str = r.search or ""
|
local search_str = r.search or ""
|
||||||
local replace_str = r.replace or ""
|
local replace_str = r.replace or ""
|
||||||
updated = updated:gsub(search_str, replace_str)
|
|
||||||
|
-- Escape special pattern chars to ensure literal matching:
|
||||||
|
local escaped_search = escape_lua_pattern(search_str)
|
||||||
|
|
||||||
|
local replacement_count = 0
|
||||||
|
updated, replacement_count = updated:gsub(escaped_search, 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
|
end
|
||||||
|
|
||||||
|
return updated, table.concat(info_msgs, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file)
|
||||||
@@ -20,8 +47,8 @@ M.run = function(tool_call, conf, prompt_user_tool_accept, is_subpath, read_file
|
|||||||
if not path or #replacements == 0 then
|
if not path or #replacements == 0 then
|
||||||
return "[replace_in_file] Missing 'path' or 'replacements'."
|
return "[replace_in_file] Missing 'path' or 'replacements'."
|
||||||
end
|
end
|
||||||
local root = vim.fn.getcwd()
|
|
||||||
|
|
||||||
|
local root = vim.fn.getcwd()
|
||||||
if not is_subpath(root, path) then
|
if not is_subpath(root, path) then
|
||||||
return string.format("Tool [replace_in_file for '%s'] REJECTED. Path outside project root.", path)
|
return string.format("Tool [replace_in_file for '%s'] REJECTED. Path outside project root.", path)
|
||||||
end
|
end
|
||||||
@@ -31,7 +58,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)
|
return string.format("[replace_in_file for '%s'] FAILED. Could not read file.", path)
|
||||||
end
|
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)
|
handler.write_file(path, updated_data, conf)
|
||||||
|
|
||||||
local msg = {}
|
local msg = {}
|
||||||
@@ -40,6 +68,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] = 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"
|
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
|
if conf.auto_lint then
|
||||||
local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
|
local diag_str = robust_lsp.lsp_check_file_content(path, updated_data, 3000)
|
||||||
msg[#msg+1] = "\n" .. diag_str
|
msg[#msg+1] = "\n" .. diag_str
|
||||||
|
|||||||
Reference in New Issue
Block a user