changes for a better approach
This commit is contained in:
51
lua/chatgpt_nvim/config.lua
Normal file
51
lua/chatgpt_nvim/config.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
-- lua/chatgpt_nvim/config.lua
|
||||
-- Changed to use YAML for configuration instead of JSON.
|
||||
-- Reads from .chatgpt_config.yaml and uses lyaml to parse it.
|
||||
-- If no config file is found, uses default values.
|
||||
|
||||
local M = {}
|
||||
local uv = vim.loop
|
||||
|
||||
-- Attempt to require lyaml for YAML parsing.
|
||||
-- Make sure lyaml is installed (e.g., via luarocks install lyaml)
|
||||
local ok_yaml, lyaml = pcall(require, "lyaml")
|
||||
|
||||
local function get_config_path()
|
||||
local cwd = vim.fn.getcwd()
|
||||
local config_path = cwd .. "/.chatgpt_config.yaml"
|
||||
return config_path
|
||||
end
|
||||
|
||||
function M.load()
|
||||
local path = get_config_path()
|
||||
local fd = uv.fs_open(path, "r", 438) -- 438 = 0o666
|
||||
if not fd then
|
||||
-- Return some default configuration if no file found
|
||||
return {
|
||||
initial_prompt = "",
|
||||
directories = { "." },
|
||||
}
|
||||
end
|
||||
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
|
||||
local config = result[1]
|
||||
if type(config) == "table" then
|
||||
return {
|
||||
initial_prompt = config.initial_prompt or "",
|
||||
directories = config.directories or { "." },
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Fallback if decode fails
|
||||
return {
|
||||
initial_prompt = "",
|
||||
directories = { "." },
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,61 +1,134 @@
|
||||
-- lua/chatgpt_nvim/context.lua
|
||||
-- Modified to:
|
||||
-- 1) Use directories from config to build project structure.
|
||||
-- 2) Include file contents from those directories.
|
||||
-- 3) Skip files listed in .gitignore.
|
||||
--
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.get_current_file()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local path = vim.api.nvim_buf_get_name(buf)
|
||||
if path == "" then
|
||||
path = "untitled"
|
||||
end
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
local content = table.concat(lines, "\n")
|
||||
return content, path
|
||||
end
|
||||
local uv = vim.loop
|
||||
|
||||
function M.get_project_structure()
|
||||
local handle = io.popen("git ls-files 2>/dev/null")
|
||||
if handle then
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
if result and result ~= "" then
|
||||
return "Files:\n" .. result
|
||||
-- Returns a set of files mentioned in .gitignore patterns.
|
||||
local function load_gitignore_patterns(root)
|
||||
local gitignore_path = root .. "/.gitignore"
|
||||
local fd = uv.fs_open(gitignore_path, "r", 438)
|
||||
if not fd then
|
||||
return {}
|
||||
end
|
||||
local stat = uv.fs_fstat(fd)
|
||||
local data = uv.fs_read(fd, stat.size, 0)
|
||||
uv.fs_close(fd)
|
||||
if not data then return {} end
|
||||
local patterns = {}
|
||||
for line in data:gmatch("[^\r\n]+") do
|
||||
line = line:match("^%s*(.-)%s*$") -- trim
|
||||
if line ~= "" and not line:match("^#") then
|
||||
patterns[#patterns+1] = line
|
||||
end
|
||||
end
|
||||
|
||||
local dhandle = io.popen("find . -type f")
|
||||
if dhandle then
|
||||
local dresult = dhandle:read("*a")
|
||||
dhandle:close()
|
||||
return "Files:\n" .. (dresult or "")
|
||||
end
|
||||
|
||||
return "No files found."
|
||||
return patterns
|
||||
end
|
||||
|
||||
local function get_git_root()
|
||||
local handle = io.popen("git rev-parse --show-toplevel 2>/dev/null")
|
||||
if handle then
|
||||
local root = handle:read("*l")
|
||||
handle:close()
|
||||
return root
|
||||
local function should_ignore_file(file, ignore_patterns)
|
||||
for _, pattern in ipairs(ignore_patterns) do
|
||||
-- Simple pattern matching. For more complex patterns, consider using lua patterns.
|
||||
-- This is a basic implementation. Adjust as needed.
|
||||
if file:find(pattern, 1, true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return nil
|
||||
return false
|
||||
end
|
||||
|
||||
local function scandir(dir, ignore_patterns, files)
|
||||
files = files or {}
|
||||
local fd = uv.fs_opendir(dir, nil, 50)
|
||||
if not fd then return files end
|
||||
while true do
|
||||
local ents = uv.fs_readdir(fd)
|
||||
if not ents then break end
|
||||
for _, ent in ipairs(ents) do
|
||||
local fullpath = dir .. "/" .. ent.name
|
||||
if not should_ignore_file(fullpath, ignore_patterns) then
|
||||
if ent.type == "file" then
|
||||
table.insert(files, fullpath)
|
||||
elseif ent.type == "directory" and ent.name ~= ".git" then
|
||||
scandir(fullpath, ignore_patterns, files)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
uv.fs_closedir(fd)
|
||||
return files
|
||||
end
|
||||
|
||||
function M.get_project_files(directories)
|
||||
local root = vim.fn.getcwd()
|
||||
local ignore_patterns = load_gitignore_patterns(root)
|
||||
local all_files = {}
|
||||
for _, dir in ipairs(directories) do
|
||||
local abs_dir = dir
|
||||
if not abs_dir:match("^/") then
|
||||
abs_dir = root .. "/" .. dir
|
||||
end
|
||||
scandir(abs_dir, ignore_patterns, all_files)
|
||||
end
|
||||
return all_files
|
||||
end
|
||||
|
||||
function M.get_project_structure(directories)
|
||||
local files = M.get_project_files(directories)
|
||||
-- Create a listing of files only (relative to root)
|
||||
local root = vim.fn.getcwd()
|
||||
local rel_files = {}
|
||||
for _, f in ipairs(files) do
|
||||
local rel = f:gsub("^" .. root .. "/", "")
|
||||
table.insert(rel_files, rel)
|
||||
end
|
||||
|
||||
local structure = "Files:\n" .. table.concat(rel_files, "\n")
|
||||
return structure
|
||||
end
|
||||
|
||||
function M.get_file_contents(files)
|
||||
local root = vim.fn.getcwd()
|
||||
local sections = {}
|
||||
for _, f in ipairs(files) do
|
||||
local fd = uv.fs_open(root .. "/" .. f, "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 then
|
||||
table.insert(sections, "\n<<<CGPT Current File\n" .. (root .. "/" .. f) .. "\n" .. data .. "\n<<<CGPT Current File END\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat(sections, "\n")
|
||||
end
|
||||
|
||||
function M.get_current_file()
|
||||
local current_path = vim.fn.expand("%:p")
|
||||
if current_path == "" then
|
||||
return nil, nil
|
||||
end
|
||||
local fd = uv.fs_open(current_path, "r", 438)
|
||||
if not fd then return nil, current_path end
|
||||
local stat = uv.fs_fstat(fd)
|
||||
local data = uv.fs_read(fd, stat.size, 0)
|
||||
uv.fs_close(fd)
|
||||
return data, current_path
|
||||
end
|
||||
|
||||
function M.get_readme_content()
|
||||
local root = get_git_root()
|
||||
if not root then
|
||||
return nil
|
||||
end
|
||||
local readme_path = root .. "/README.md"
|
||||
local f = io.open(readme_path, "r")
|
||||
if f then
|
||||
local content = f:read("*a")
|
||||
f:close()
|
||||
if content and content ~= "" then
|
||||
return content
|
||||
end
|
||||
end
|
||||
return nil
|
||||
local root = vim.fn.getcwd()
|
||||
local fd = uv.fs_open(root .. "/README.md", "r", 438)
|
||||
if not fd then return nil end
|
||||
local stat = uv.fs_fstat(fd)
|
||||
local data = uv.fs_read(fd, stat.size, 0)
|
||||
uv.fs_close(fd)
|
||||
return data
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
-- lua/chatgpt_nvim/handler.lua
|
||||
-- No major changes needed, just ensure it can be reused for the new command.
|
||||
-- Ensuring we have get_clipboard_content and write_file as is.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Retrieves clipboard content and trims trailing whitespace/newlines
|
||||
function M.get_clipboard_content()
|
||||
local content = vim.fn.getreg('+')
|
||||
content = content:gsub("%s+$", "")
|
||||
return content
|
||||
return vim.fn.getreg('+')
|
||||
end
|
||||
|
||||
function M.write_file(filepath, content)
|
||||
local dir = filepath:match("(.*/)")
|
||||
if dir and dir ~= "" then
|
||||
vim.fn.mkdir(dir, "p")
|
||||
end
|
||||
|
||||
local f = io.open(filepath, "w")
|
||||
if f then
|
||||
f:write(content)
|
||||
f:close()
|
||||
else
|
||||
vim.api.nvim_err_writeln("Failed to write file: " .. filepath)
|
||||
local fd = vim.loop.fs_open(filepath, "w", 438)
|
||||
if not fd then
|
||||
vim.api.nvim_err_writeln("Could not open file: " .. filepath)
|
||||
return
|
||||
end
|
||||
vim.loop.fs_write(fd, content, -1)
|
||||
vim.loop.fs_close(fd)
|
||||
end
|
||||
|
||||
function M.finish()
|
||||
print("All files processed. You can now continue working.")
|
||||
print("Finished processing files.")
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,88 +1,96 @@
|
||||
-- lua/chatgpt_nvim/init.lua
|
||||
-- Modified to:
|
||||
-- 1) Use YAML for config and response parsing.
|
||||
-- 2) Assume ChatGPT response is YAML of form:
|
||||
-- files:
|
||||
-- - path: "somefile.lua"
|
||||
-- content: |
|
||||
-- multi line
|
||||
-- content
|
||||
--
|
||||
|
||||
local M = {}
|
||||
|
||||
local context = require('chatgpt_nvim.context')
|
||||
local handler = require('chatgpt_nvim.handler')
|
||||
local config = require('chatgpt_nvim.config')
|
||||
|
||||
local ok_yaml, lyaml = pcall(require, "lyaml")
|
||||
|
||||
local function copy_to_clipboard(text)
|
||||
vim.fn.setreg('+', text)
|
||||
end
|
||||
|
||||
-- Parse the response from ChatGPT in YAML.
|
||||
local function parse_response(raw)
|
||||
if not ok_yaml then
|
||||
vim.api.nvim_err_writeln("lyaml is not available. Please install lyaml for YAML parsing.")
|
||||
return nil
|
||||
end
|
||||
local ok, data = pcall(lyaml.load, raw)
|
||||
if not ok or not data then
|
||||
vim.api.nvim_err_writeln("Failed to parse YAML response.")
|
||||
return nil
|
||||
end
|
||||
-- lyaml.load returns a list of documents. We assume only one document is given.
|
||||
data = data[1]
|
||||
return data
|
||||
end
|
||||
|
||||
function M.run_chatgpt_command()
|
||||
-- Prompt the user for input
|
||||
local conf = config.load()
|
||||
local user_input = vim.fn.input("Message for O1 Model: ")
|
||||
if user_input == "" then
|
||||
print("No input provided.")
|
||||
return
|
||||
end
|
||||
|
||||
local project_structure = context.get_project_structure()
|
||||
local dirs = conf.directories or {"."}
|
||||
local project_structure = context.get_project_structure(dirs)
|
||||
local all_files = context.get_project_files(dirs)
|
||||
local file_sections = context.get_file_contents(all_files)
|
||||
local current_file_content, current_file_path = context.get_current_file()
|
||||
local readme_content = context.get_readme_content()
|
||||
|
||||
local sections = {
|
||||
"Following you will get instructions for a development project. First you get a prompt of the user which starts with \"<<<CGP User Message\" in a line and ends with \"<<<CGP User Message End\" in a line.",
|
||||
"\nThen you get the project structure which starts with \"<<<CGPT Project Structure\" in a line and ends with \"<<<CGPT Project Structure End\" in a line",
|
||||
"\nThen you get the current file for context. It contains at the first line the file path, followed by its content.It starts with \"<<<CGPT Current File\" in a line and ends with \"<<<CGPT Current File End\" in a line",
|
||||
"\nLastly you get the content of the README.md for context if it is present. It starts with \"<<<CGPT README\" in a line and ends with \"<<<CGPT README End\" in a line",
|
||||
"\nReturn code blocks for the changes.",
|
||||
"\nIf it makes sense, also update the README.md to reflect the changes made.",
|
||||
"\n\n<<<CGPT User Message\n",
|
||||
conf.initial_prompt .. "\n",
|
||||
user_input,
|
||||
"\n<<<CGPT User Message END",
|
||||
"\n\n<<<CGPT Project Structure\n",
|
||||
"\n\nproject structure:\n",
|
||||
project_structure,
|
||||
"\n<<<CGPT Project Structure END",
|
||||
"\n\n<<<CGPT Current File\n",
|
||||
current_file_path .. "\n" .. current_file_content,
|
||||
"\n\n<<<CGPT Current File END\n",
|
||||
"\n\nproject files:\n"
|
||||
}
|
||||
|
||||
if readme_content then
|
||||
table.insert(sections, "\n\n<<<CGPT README\n" .. readme_content .. "\n<<<CGPT README END")
|
||||
end
|
||||
-- Add all other files in configured directories
|
||||
table.insert(sections, file_sections)
|
||||
|
||||
local prompt = table.concat(sections, "\n")
|
||||
|
||||
-- Copy prompt to clipboard so user can paste it into the website form.
|
||||
vim.fn.setreg('+', prompt)
|
||||
print("Prompt copied to clipboard! Please paste it into the website form and get the response from the ChatGPT O1 model.")
|
||||
print("Press <Enter> once you've pasted the prompt into the website and have the first file ready to copy.")
|
||||
vim.fn.input("") -- wait for user to press Enter
|
||||
-- Copy prompt to clipboard
|
||||
copy_to_clipboard(prompt)
|
||||
print("Prompt copied to clipboard! Please paste it into the ChatGPT O1 model and get the YAML response.")
|
||||
end
|
||||
|
||||
while true do
|
||||
local another = vim.fn.input("Do you want to paste another file? (y/n): ")
|
||||
if another:lower() ~= "y" then
|
||||
handler.finish()
|
||||
break
|
||||
function M.run_chatgpt_paste_command()
|
||||
print("Reading ChatGPT YAML response from clipboard...")
|
||||
local raw = handler.get_clipboard_content()
|
||||
if raw == "" then
|
||||
vim.api.nvim_err_writeln("Clipboard is empty. Please copy the YAML response from ChatGPT first.")
|
||||
return
|
||||
end
|
||||
|
||||
local data = parse_response(raw)
|
||||
if not data or not data.files then
|
||||
vim.api.nvim_err_writeln("No 'files' field found in the YAML response.")
|
||||
return
|
||||
end
|
||||
|
||||
for _, fileinfo in ipairs(data.files) do
|
||||
if fileinfo.path and fileinfo.content then
|
||||
handler.write_file(fileinfo.path, fileinfo.content)
|
||||
print("Wrote file: " .. fileinfo.path)
|
||||
else
|
||||
vim.api.nvim_err_writeln("Invalid file entry. Must have 'path' and 'content'.")
|
||||
end
|
||||
|
||||
-- Ask user for filepath
|
||||
local filepath = ""
|
||||
while true do
|
||||
print("Please copy the FILE PATH (the first line of the code block) to your clipboard.")
|
||||
print("Press <Enter> when done.")
|
||||
vim.fn.input("") -- Wait for user confirmation
|
||||
filepath = handler.get_clipboard_content()
|
||||
if filepath == "" then
|
||||
vim.api.nvim_err_writeln("Clipboard is empty. Please copy the file path and try again.")
|
||||
else
|
||||
print("Got file path: " .. filepath)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Ask user for file content
|
||||
local filecontent = ""
|
||||
while true do
|
||||
print("Now copy the FILE CONTENT (everything after the first line) to your clipboard.")
|
||||
print("Press <Enter> when done.")
|
||||
vim.fn.input("") -- Wait for user confirmation
|
||||
filecontent = handler.get_clipboard_content()
|
||||
if filecontent == "" then
|
||||
vim.api.nvim_err_writeln("Clipboard is empty. Please copy the file content and try again.")
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
handler.write_file(filepath, filecontent)
|
||||
print("Wrote file: " .. filepath)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user