Revert "feat: change to diff workflow"

This reverts commit df206dce88.
This commit is contained in:
2025-01-04 19:13:26 +01:00
parent a77dbb683d
commit 452253cdd0
4 changed files with 87 additions and 174 deletions

View File

@@ -6,7 +6,9 @@ local ok_yaml, lyaml = pcall(require, "lyaml")
local prompt_blocks = {
["go-development"] = [[
You are a coding assistant specialized in Go development.
You will receive a projects context and user instructions related to Go code.
You will receive a projects context and user instructions related to Go code,
and you must return the requested modifications or guidance.
When returning modifications, follow the specified YAML structure.
Keep your suggestions aligned with Go best practices and idiomatic Go.
]],
["typo3-development"] = [[
@@ -17,36 +19,51 @@ local prompt_blocks = {
]],
["rust-development"] = [[
You are a coding assistant specialized in Rust development.
You will receive a projects context and user instructions related to Rust code.
Keep your suggestions aligned with Rust best practices and idiomatic Rust.
You will receive a projects context and user instructions related to Rust code,
and you must return the requested modifications or guidance.
When returning modifications, follow the specified YAML structure.
Keep your suggestions aligned with Rust best practices and idiomatic Rust.
]],
["basic-prompt"] = [[
You are a coding assistant who receives a project's context and user instructions.
The user will provide a prompt, and you will guide them through a workflow:
1. Analyse which files you need. Ask for file contents in YAML if needed.
2. Request additional context outside YAML if necessary.
3. When ready, provide final changes in YAML with:
- 'project_name'
- 'files', each having:
* 'path'
* 'diff' (for patching an existing file) OR 'content' (for a new file) OR 'delete'
Important: do not provide entire file content for updates; instead provide a unified diff in 'diff'.
Only modify or delete files whose contents you have explicitly requested and seen beforehand.
1. First, you should analyse which files you need to solve the request.
You can see which files are present in the provided project structure.
Additionally if presented you could also ask for files of a library which is provided in for example composer.json.
2. If file contents is needed provide a yaml which asks for the file contents.
For example:
project_name: example_project
files:
- path: "relative/path/to/file"
3. If more information or context is needed, ask the user (outside of the YAML) to provide that.
4. When all necessary information is gathered, provide the final YAML with the
project's name and a list of files to be created or modified.
Also explain the changes you made below the yaml.
The final YAML must have a top-level key named 'project_name' that matches the project's configured name,
and a top-level key named 'files', which is a list of file changes. Each element in 'files' must be a mapping with:
- 'path' for the file path relative to the projects root directory.
- either 'content' with a multiline string for new content, or 'delete: true' if the file should be deleted.
Important: dont use comments in the code to explain which steps you have taken.
Comments should just explain the code and not your thought process.
You can explain your thought process outside of the YAML.
If more context is needed at any point before providing the final YAML, request it outside of the YAML.
Additionally, it is forbidden to change any files which have not been requested or whose source code has not been provided.
]],
["secure-coding"] = [[
You are a coding assistant specialized in secure software development.
Always consider security impacts. Use diffs for updates, new content for new files,
and 'delete: true' for removals.
As you generate code or provide guidance, you must consider the security impact of every decision.
You will write and review code with a focus on minimizing vulnerabilities and following best security practices,
such as validating all user inputs, avoiding unsafe libraries or functions, and following secure coding standards.
]],
["workflow-prompt"] = [[
You are a coding assistant focusing on making the Neovim ChatGPT workflow straightforward and user-friendly.
Remind the user to:
- List needed files for further context
- Request additional information outside YAML if needed
- Provide final changes in YAML with 'project_name' and 'files', using:
* 'diff' for existing file modifications
* 'content' for new files
* 'delete: true' for file deletions
Provide a concise set of steps or guidance, reminding the user:
- How to list needed files for further context
- How to request additional information outside of the YAML
- How to finalize changes with a YAML response containing project_name and files
Always ensure that prompts and explanations remain clear and minimal, reducing user errors.
]]
}
@@ -86,6 +103,7 @@ function M.load()
initial_prompt = "",
directories = { "." },
default_prompt_blocks = {},
-- Changed default from 128000 to 16384 as requested
token_limit = 16384,
project_name = "",
debug = false,
@@ -95,6 +113,7 @@ function M.load()
partial_acceptance = false,
improved_debug = false,
enable_chunking = false,
-- New default for step-by-step
enable_step_by_step = true,
enable_debug_commands = false
}
@@ -142,6 +161,7 @@ function M.load()
if type(result.enable_chunking) == "boolean" then
config.enable_chunking = result.enable_chunking
end
-- Added logic to load enable_step_by_step from user config
if type(result.enable_step_by_step) == "boolean" then
config.enable_step_by_step = result.enable_step_by_step
end
@@ -154,6 +174,7 @@ function M.load()
config.initial_prompt = "You are a coding assistant who receives a project's context and user instructions..."
end
-- Merge the default prompt blocks with the config's initial prompt
if type(config.default_prompt_blocks) == "table" and #config.default_prompt_blocks > 0 then
local merged_prompt = {}
for _, block_name in ipairs(config.default_prompt_blocks) do

View File

@@ -69,76 +69,6 @@ function M.delete_file(filepath)
end
end
-- Applies a unified diff to the specified file.
-- This spawns an external 'patch' command if available.
function M.apply_diff(filepath, diff_content)
local conf = config.load()
local tmp_original = vim.fn.tempname()
local tmp_patch = vim.fn.tempname()
-- Read original file (or empty if it doesn't exist)
local fd_in = uv.fs_open(filepath, "r", 438)
local original_data = ""
if fd_in then
local stat = uv.fs_fstat(fd_in)
original_data = uv.fs_read(fd_in, stat.size, 0)
uv.fs_close(fd_in)
end
-- Write original content to temp file
local fd_orig = uv.fs_open(tmp_original, "w", 438)
if fd_orig then
uv.fs_write(fd_orig, original_data, -1)
uv.fs_close(fd_orig)
end
-- Write diff to temp file
local fd_patch = uv.fs_open(tmp_patch, "w", 438)
if fd_patch then
uv.fs_write(fd_patch, diff_content, -1)
uv.fs_close(fd_patch)
else
return false, "Could not open temporary file to write patch."
end
-- Attempt to run 'patch'
local patch_cmd = "patch -u " .. vim.fn.shellescape(tmp_original) .. " < " .. vim.fn.shellescape(tmp_patch)
local handle = io.popen(patch_cmd)
if not handle then
return false, "Failed to run patch command."
end
local result = handle:read("*a")
local success_close, errmsg = handle:close()
if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:handler] Patch command output:\n" .. (result or "") .. "\n")
end
if not success_close then
if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:handler] Patch command failed: " .. (errmsg or "unknown") .. "\n")
end
return false, errmsg
end
-- If successful, read the patched file and write it back
local fd_out = uv.fs_open(tmp_original, "r", 438)
if fd_out then
local stat_out = uv.fs_fstat(fd_out)
local new_data = uv.fs_read(fd_out, stat_out.size, 0)
uv.fs_close(fd_out)
M.write_file(filepath, new_data)
if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:handler] Successfully applied patch to: " .. filepath .. "\n")
end
else
return false, "Could not read patched file."
end
return true
end
function M.finish()
print("Finished processing files.")
end

View File

@@ -111,30 +111,17 @@ local function preview_changes(changes)
""
})
for _, fileinfo in ipairs(changes) do
if fileinfo.delete then
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {
string.format("=== Delete file: %s ===", fileinfo.path or "<no path>"),
""
})
elseif fileinfo.diff then
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {
string.format("=== Diff for file: %s ===", fileinfo.path or "<no path>")
})
local lines = vim.split(fileinfo.diff, "\n")
for _, line in ipairs(lines) do
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { line })
end
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { "" })
elseif fileinfo.content then
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {
string.format("=== New file: %s ===", fileinfo.path or "<no path>")
})
local indicator = (fileinfo.delete == true) and "Delete file" or "Write file"
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {
string.format("=== %s: %s ===", indicator, fileinfo.path or "<no path>")
})
if fileinfo.content then
local lines = vim.split(fileinfo.content, "\n")
for _, line in ipairs(lines) do
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { line })
end
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { "" })
end
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, { "" })
end
vim.cmd("vsplit")
@@ -152,16 +139,9 @@ local function partial_accept(changes)
""
}
for _, fileinfo in ipairs(changes) do
if fileinfo.delete then
table.insert(lines, string.format("[DELETE] %s", fileinfo.path or "<no path>"))
elseif fileinfo.diff then
table.insert(lines, string.format("[DIFF] %s", fileinfo.path or "<no path>"))
local diff_lines = vim.split(fileinfo.diff, "\n")
for _, dl in ipairs(diff_lines) do
table.insert(lines, " " .. dl)
end
elseif fileinfo.content then
table.insert(lines, string.format("[WRITE] %s", fileinfo.path or "<no path>"))
local action = (fileinfo.delete == true) and "[DELETE]" or "[WRITE]"
table.insert(lines, string.format("%s %s", action, fileinfo.path or "<no path>"))
if fileinfo.content then
local content_lines = vim.split(fileinfo.content, "\n")
for _, cl in ipairs(content_lines) do
table.insert(lines, " " .. cl)
@@ -176,53 +156,46 @@ local function partial_accept(changes)
local function on_write()
local edited_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local keep_current = false
local current_fileinfo = { path = nil, delete = false, diff = nil, content = nil }
local accum = {}
local current_fileinfo = { path = nil, content = nil, delete = false }
local content_accum = {}
for _, line in ipairs(edited_lines) do
if line:match("^#") or line == "" then
goto continue
end
local del_match = line:match("^%[DELETE%] (.+)")
local diff_match = line:match("^%[DIFF%] (.+)")
local write_match = line:match("^%[WRITE%] (.+)")
if del_match or diff_match or write_match then
-- store previous if any
if del_match then
if keep_current and (current_fileinfo.path ~= nil) then
if current_fileinfo.diff then
current_fileinfo.diff = table.concat(accum, "\n")
elseif current_fileinfo.content then
current_fileinfo.content = table.concat(accum, "\n")
if #content_accum > 0 then
current_fileinfo.content = table.concat(content_accum, "\n")
end
table.insert(final_changes, current_fileinfo)
end
accum = {}
keep_current = true
if del_match then
current_fileinfo = { path = del_match, delete = true, diff = nil, content = nil }
elseif diff_match then
current_fileinfo = { path = diff_match, delete = false, diff = "", content = nil }
elseif write_match then
current_fileinfo = { path = write_match, delete = false, diff = nil, content = "" }
current_fileinfo = { path = del_match, delete = true, content = nil }
content_accum = {}
elseif write_match then
if keep_current and (current_fileinfo.path ~= nil) then
if #content_accum > 0 then
current_fileinfo.content = table.concat(content_accum, "\n")
end
table.insert(final_changes, current_fileinfo)
end
keep_current = true
current_fileinfo = { path = write_match, delete = false, content = nil }
content_accum = {}
else
if keep_current then
table.insert(accum, line:gsub("^%s*", ""))
table.insert(content_accum, line:gsub("^%s*", ""))
end
end
::continue::
end
if keep_current and (current_fileinfo.path ~= nil) then
if current_fileinfo.diff ~= nil then
current_fileinfo.diff = table.concat(accum, "\n")
elseif current_fileinfo.content ~= nil then
current_fileinfo.content = table.concat(accum, "\n")
if #content_accum > 0 then
current_fileinfo.content = table.concat(content_accum, "\n")
end
table.insert(final_changes, current_fileinfo)
end
@@ -426,7 +399,7 @@ function M.run_chatgpt_command()
```yaml
commands:
- command: "list"
- command: "ls"
dir: "some/directory"
- command: "grep"
@@ -434,6 +407,7 @@ function M.run_chatgpt_command()
target: "path/to/file/or/directory"
```
The "ls" command uses the system's 'ls' command to list directory contents.
When these commands are present and enable_debug_commands is true, I'll execute them and return the results in the clipboard.
]])
end
@@ -493,7 +467,7 @@ function M.run_chatgpt_paste_command()
local is_final = false
for _, fileinfo in ipairs(data.files) do
if fileinfo.content or fileinfo.delete == true or fileinfo.diff then
if fileinfo.content or fileinfo.delete == true then
is_final = true
break
end
@@ -544,20 +518,12 @@ function M.run_chatgpt_paste_command()
ui.debug_log("Deleting file: " .. fileinfo.path)
handler.delete_file(fileinfo.path)
print("Deleted: " .. fileinfo.path)
elseif fileinfo.diff then
ui.debug_log("Applying diff to file: " .. fileinfo.path)
local success, err = handler.apply_diff(fileinfo.path, fileinfo.diff)
if not success then
vim.api.nvim_err_writeln("Error applying diff: " .. (err or "unknown"))
else
print("Patched: " .. fileinfo.path)
end
elseif fileinfo.content then
ui.debug_log("Writing new file: " .. fileinfo.path)
ui.debug_log("Writing file: " .. fileinfo.path)
handler.write_file(fileinfo.path, fileinfo.content)
print("Wrote: " .. fileinfo.path)
else
vim.api.nvim_err_writeln("Invalid file entry. Must have 'diff', 'content', or 'delete'.")
vim.api.nvim_err_writeln("Invalid file entry. Must have 'content' or 'delete'.")
end
::continue::
end
@@ -587,7 +553,7 @@ function M.run_chatgpt_paste_command()
"\n\nProject name: " .. (conf.project_name or ""),
"\n\nBelow are the requested files from the project, each preceded by its filename in backticks and enclosed in triple backticks.\n",
table.concat(file_sections, "\n"),
"\n\nIf you need more files, please respond again in YAML listing additional files. If you have all information you need, provide the final YAML with `project_name` and `files` (use `diff`, `content`, or `delete`) to apply changes.\n"
"\n\nIf you need more files, please respond again in YAML listing additional files. If you have all information you need, provide the final YAML with `project_name` and `files` (with `content` or `delete`) to apply changes.\n"
}
local prompt = table.concat(sections, "\n")
@@ -681,6 +647,7 @@ function M.run_chatgpt_current_buffer_command()
target: "path/to/file/or/directory"
```
The "list" command uses the system's 'ls' command to list directory contents.
When these commands are present and enable_debug_commands is true, I'll execute them and return the results in the clipboard.
]])
end