fix: load config just once per call

This commit is contained in:
2025-01-05 01:45:43 +01:00
parent 6950c542ad
commit ca729140c3
4 changed files with 103 additions and 93 deletions

View File

@@ -1,10 +1,11 @@
local M = {}
local uv = vim.loop
local config = require('chatgpt_nvim.config')
-- Remove local config = require('chatgpt_nvim.config') so we don't load config each time
-- We'll accept a 'conf' parameter in our functions instead
-- local config = require('chatgpt_nvim.config')
local function load_gitignore_patterns(root)
local conf = config.load()
local function load_gitignore_patterns(root, conf)
local gitignore_path = root .. "/.gitignore"
local fd = uv.fs_open(gitignore_path, "r", 438)
if not fd then
@@ -19,7 +20,7 @@ local function load_gitignore_patterns(root)
if not data then return {} end
local patterns = {}
for line in data:gmatch("[^\r\n]+") do
line = line:match("^%s*(.-)%s*$") -- trim
line = line:match("^%s*(.-)%s*$")
if line ~= "" and not line:match("^#") then
patterns[#patterns+1] = line
end
@@ -30,8 +31,7 @@ local function load_gitignore_patterns(root)
return patterns
end
local function should_ignore_file(file, ignore_patterns)
local conf = config.load()
local function should_ignore_file(file, ignore_patterns, conf)
for _, pattern in ipairs(ignore_patterns) do
if file:find(pattern, 1, true) then
if conf.debug then
@@ -43,8 +43,7 @@ local function should_ignore_file(file, ignore_patterns)
return false
end
local function is_text_file(file)
local conf = config.load()
local function is_text_file(file, conf)
local fd = uv.fs_open(file, "r", 438)
if not fd then
if conf.debug then
@@ -54,7 +53,6 @@ local function is_text_file(file)
end
local chunk = uv.fs_read(fd, 1024, 0) or ""
uv.fs_close(fd)
-- Check for null bytes as a heuristic for binary files
if chunk:find("\0") then
if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:context] File appears binary: " .. file .. "\n")
@@ -64,8 +62,7 @@ local function is_text_file(file)
return true
end
local function scandir(dir, ignore_patterns, files)
local conf = config.load()
local function scandir(dir, ignore_patterns, files, conf)
local fd = uv.fs_opendir(dir, nil, 50)
if not fd then
if conf.debug then
@@ -78,11 +75,11 @@ local function scandir(dir, ignore_patterns, files)
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" and is_text_file(fullpath) then
if not should_ignore_file(fullpath, ignore_patterns, conf) then
if ent.type == "file" and is_text_file(fullpath, conf) then
table.insert(files, fullpath)
elseif ent.type == "directory" and ent.name ~= ".git" then
scandir(fullpath, ignore_patterns, files)
scandir(fullpath, ignore_patterns, files, conf)
end
end
end
@@ -91,17 +88,17 @@ local function scandir(dir, ignore_patterns, files)
return files
end
function M.get_project_files(directories)
local conf = config.load()
function M.get_project_files(directories, conf)
-- conf is passed in from outside so we don't load config repeatedly
local root = vim.fn.getcwd()
local ignore_patterns = load_gitignore_patterns(root)
local ignore_patterns = load_gitignore_patterns(root, conf)
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)
scandir(abs_dir, ignore_patterns, all_files, conf)
end
local rel_files = {}
@@ -117,14 +114,13 @@ function M.get_project_files(directories)
return rel_files
end
function M.get_project_structure(directories)
local files = M.get_project_files(directories)
function M.get_project_structure(directories, conf)
local files = M.get_project_files(directories, conf)
local structure = "Files:\n" .. table.concat(files, "\n")
return structure
end
function M.get_file_contents(files)
local conf = config.load()
function M.get_file_contents(files, conf)
local root = vim.fn.getcwd()
local sections = {}
for _, f in ipairs(files) do

View File

@@ -1,7 +1,9 @@
local M = {}
local uv = vim.loop
local config = require('chatgpt_nvim.config')
-- Remove local config = require('chatgpt_nvim.config')
-- We'll accept conf from outside or init.lua
-- local config = require('chatgpt_nvim.config')
local function ensure_dir(path)
local st = uv.fs_stat(path)
@@ -16,8 +18,7 @@ local function ensure_dir(path)
return true
end
function M.get_clipboard_content()
local conf = config.load()
function M.get_clipboard_content(conf)
local content = vim.fn.getreg('+')
if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:handler] Clipboard content length: " .. #content .. "\n")
@@ -25,8 +26,7 @@ function M.get_clipboard_content()
return content
end
function M.write_file(filepath, content)
local conf = config.load()
function M.write_file(filepath, content, conf)
local dir = filepath:match("(.*)/")
if dir and dir ~= "" then
ensure_dir(dir)
@@ -46,8 +46,7 @@ function M.write_file(filepath, content)
end
end
function M.delete_file(filepath)
local conf = config.load()
function M.delete_file(filepath, conf)
local st = uv.fs_stat(filepath)
if st then
local success, err = uv.fs_unlink(filepath)

View File

@@ -12,7 +12,7 @@ local function copy_to_clipboard(text)
vim.fn.setreg('+', text)
end
-- We now expect 'conf' as a parameter, instead of loading config inside parse_response.
-- Expecting 'conf' instead of loading config in parse_response:
local function parse_response(raw, conf)
if not ok_yaml then
vim.api.nvim_err_writeln("lyaml not available. Install with `luarocks install lyaml`.")
@@ -54,7 +54,7 @@ local function is_directory(path)
return stat and stat.type == "directory"
end
-- We now expect 'conf' as a parameter, instead of loading config inside handle_step_by_step_if_needed.
-- Expect 'conf' instead of loading config in handle_step_by_step_if_needed:
local function handle_step_by_step_if_needed(prompt, conf)
local length = #prompt
if not conf.enable_step_by_step or length <= (conf.prompt_char_limit or 8000) then
@@ -72,7 +72,7 @@ local function close_existing_buffer_by_name(pattern)
end
end
local function preview_changes(changes)
local function preview_changes(changes, conf)
close_existing_buffer_by_name("ChatGPT_Changes_Preview$")
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "ChatGPT_Changes_Preview")
@@ -100,7 +100,7 @@ local function preview_changes(changes)
vim.cmd("buffer " .. bufnr)
end
local function partial_accept(changes)
local function partial_accept(changes, conf)
close_existing_buffer_by_name("ChatGPT_Partial_Accept$")
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "ChatGPT_Partial_Accept")
@@ -201,7 +201,7 @@ local function partial_accept(changes)
return final_changes
end
local function store_prompt_for_reference(prompt)
local function store_prompt_for_reference(prompt, conf)
close_existing_buffer_by_name("ChatGPT_Generated_Prompt$")
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "ChatGPT_Generated_Prompt")
@@ -221,27 +221,7 @@ local function store_prompt_for_reference(prompt)
vim.cmd("buffer " .. bufnr)
end
local function grep_in_file(search_string, filepath)
local content = read_file(filepath)
if not content then
return "Could not read file: " .. filepath
end
local results = {}
local line_num = 0
for line in content:gmatch("([^\n]*)\n?") do
line_num = line_num + 1
if line:find(search_string, 1, true) then
table.insert(results, filepath .. ":" .. line_num .. ":" .. line)
end
end
if #results == 0 then
return "No matches in " .. filepath
else
return table.concat(results, "\n")
end
end
local function execute_debug_command(cmd)
local function execute_debug_command(cmd, conf)
if type(cmd) ~= "table" or not cmd.command then
return "Invalid command object."
end
@@ -277,6 +257,21 @@ local function execute_debug_command(cmd)
end
handle:close()
local results = {}
local function grep_in_file(search_string, filepath)
local content = read_file(filepath)
if not content then
return "Could not read file: " .. filepath
end
local lines = {}
local line_num = 0
for line in content:gmatch("([^\n]*)\n?") do
line_num = line_num + 1
if line:find(search_string, 1, true) then
table.insert(lines, filepath .. ":" .. line_num .. ":" .. line)
end
end
return (#lines == 0) and ("No matches in " .. filepath) or table.concat(lines, "\n")
end
for _, f in ipairs(all_files) do
local fstat = vim.loop.fs_stat(f)
if fstat and fstat.type == "file" then
@@ -285,6 +280,21 @@ local function execute_debug_command(cmd)
end
return table.concat(results, "\n")
else
local function grep_in_file(search_string, filepath)
local content = read_file(filepath)
if not content then
return "Could not read file: " .. filepath
end
local lines = {}
local line_num = 0
for line in content:gmatch("([^\n]*)\n?") do
line_num = line_num + 1
if line:find(search_string, 1, true) then
table.insert(lines, filepath .. ":" .. line_num .. ":" .. line)
end
end
return (#lines == 0) and ("No matches in " .. filepath) or table.concat(lines, "\n")
end
return grep_in_file(pattern, target)
end
else
@@ -293,12 +303,12 @@ local function execute_debug_command(cmd)
end
function M.run_chatgpt_command()
-- Load the config once here for the entire command.
local conf = config.load()
ui.setup_ui(conf)
ui.debug_log("Running :ChatGPT command.")
local dirs = conf.directories or {"."}
if conf.interactive_file_selection then
dirs = ui.pick_directories(dirs)
dirs = ui.pick_directories(dirs, conf)
if #dirs == 0 then
dirs = conf.directories
end
@@ -329,7 +339,7 @@ function M.run_chatgpt_command()
return
end
local project_structure = context.get_project_structure(dirs)
local project_structure = context.get_project_structure(dirs, conf)
local initial_files = conf.initial_files or {}
local included_sections = {}
@@ -337,7 +347,7 @@ function M.run_chatgpt_command()
local root = vim.fn.getcwd()
local full_path = root .. "/" .. item
if is_directory(full_path) then
local dir_files = context.get_project_files({item})
local dir_files = context.get_project_files({item}, conf)
for _, f in ipairs(dir_files) do
local path = root .. "/" .. f
local data = read_file(path)
@@ -371,7 +381,7 @@ function M.run_chatgpt_command()
end
local prompt = table.concat(initial_sections, "\n")
store_prompt_for_reference(prompt)
store_prompt_for_reference(prompt, conf)
local chunks = handle_step_by_step_if_needed(prompt, conf)
copy_to_clipboard(chunks[1])
@@ -390,15 +400,15 @@ end
function M.run_chatgpt_paste_command()
local conf = config.load()
ui.setup_ui(conf)
ui.debug_log("Running :ChatGPTPaste command.")
print("Reading ChatGPT YAML response from clipboard...")
local raw = handler.get_clipboard_content()
local raw = handler.get_clipboard_content(conf)
if raw == "" then
vim.api.nvim_err_writeln("Clipboard is empty. Please copy the YAML response from ChatGPT first.")
return
end
-- Pass the loaded config into parse_response, so we dont load config.lua again.
local data = parse_response(raw, conf)
if not data then
return
@@ -407,7 +417,7 @@ function M.run_chatgpt_paste_command()
if data.commands and conf.enable_debug_commands then
local results = {}
for _, cmd in ipairs(data.commands) do
table.insert(results, execute_debug_command(cmd))
table.insert(results, execute_debug_command(cmd, conf))
end
local output = table.concat(results, "\n\n")
copy_to_clipboard(output)
@@ -432,7 +442,7 @@ function M.run_chatgpt_paste_command()
if is_final then
if conf.preview_changes then
preview_changes(data.files)
preview_changes(data.files, conf)
print("Close the preview window to apply changes, or use :q to cancel.")
local closed = vim.wait(60000, function()
local bufs = vim.api.nvim_list_bufs()
@@ -452,7 +462,7 @@ function M.run_chatgpt_paste_command()
local final_files = data.files
if conf.partial_acceptance then
final_files = partial_accept(data.files)
final_files = partial_accept(data.files, conf)
if #final_files == 0 then
vim.api.nvim_err_writeln("No changes remain after partial acceptance. Aborting.")
return
@@ -473,11 +483,11 @@ function M.run_chatgpt_paste_command()
if fileinfo.delete == true then
ui.debug_log("Deleting file: " .. fileinfo.path)
handler.delete_file(fileinfo.path)
handler.delete_file(fileinfo.path, conf)
print("Deleted: " .. fileinfo.path)
elseif fileinfo.content then
ui.debug_log("Writing file: " .. fileinfo.path)
handler.write_file(fileinfo.path, fileinfo.content)
handler.write_file(fileinfo.path, fileinfo.content, conf)
print("Wrote: " .. fileinfo.path)
else
vim.api.nvim_err_writeln("Invalid file entry. Must have 'content' or 'delete'.")
@@ -537,18 +547,19 @@ end
function M.run_chatgpt_current_buffer_command()
local conf = config.load()
ui.setup_ui(conf)
ui.debug_log("Running :ChatGPTCurrentBuffer command.")
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local user_input = table.concat(lines, "\n")
local dirs = conf.directories or {"."}
if conf.interactive_file_selection then
dirs = ui.pick_directories(dirs)
dirs = ui.pick_directories(dirs, conf)
if #dirs == 0 then
dirs = conf.directories
end
end
local project_structure = context.get_project_structure(dirs)
local project_structure = context.get_project_structure(dirs, conf)
local initial_files = conf.initial_files or {}
local included_sections = {}
@@ -576,7 +587,7 @@ function M.run_chatgpt_current_buffer_command()
local root = vim.fn.getcwd()
local full_path = root .. "/" .. item
if is_directory(full_path) then
local dir_files = context.get_project_files({item})
local dir_files = context.get_project_files({item}, conf)
for _, f in ipairs(dir_files) do
local path = root .. "/" .. f
local data = read_file(path)
@@ -631,7 +642,7 @@ function M.run_chatgpt_current_buffer_command()
vim.cmd("buffer " .. bufnr_ref)
end
store_prompt_for_reference(prompt)
store_prompt_for_reference(prompt, conf)
local chunks = handle_step_by_step_if_needed(prompt, conf)
copy_to_clipboard(chunks[1])

View File

@@ -1,34 +1,41 @@
local M = {}
local config = require('chatgpt_nvim.config')
local conf = config.load()
-- Remove local conf = config.load()
-- We'll have each function accept 'conf' from outside or handle it in init.lua
-- local config = require('chatgpt_nvim.config')
-- local conf = config.load()
local prompts = require('chatgpt_nvim.prompts')
local debug_bufnr = nil
if conf.improved_debug then
-- Check if a debug buffer is already open. Close it first to avoid duplicates.
-- We'll store a reference to conf at runtime
local runtime_conf = nil
function M.setup_ui(conf)
runtime_conf = conf
if runtime_conf.improved_debug then
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
local name = vim.api.nvim_buf_get_name(buf)
if name == "ChatGPT_Debug_Log" then
vim.api.nvim_buf_delete(buf, {force = true})
end
end
debug_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(debug_bufnr, "ChatGPT_Debug_Log")
vim.api.nvim_buf_set_option(debug_bufnr, "filetype", "log")
end
end
function M.debug_log(msg)
if conf.improved_debug and debug_bufnr then
if runtime_conf and runtime_conf.improved_debug and debug_bufnr then
vim.api.nvim_buf_set_lines(debug_bufnr, -1, -1, false, { msg })
else
if conf.debug then
if runtime_conf and runtime_conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:debug] " .. msg .. "\n")
end
end
end
function M.pick_directories(dirs)
function M.pick_directories(dirs, conf)
local selected_dirs = {}
local selection_instructions = prompts["file-selection-instructions"]
local lines = { selection_instructions }
@@ -37,7 +44,6 @@ function M.pick_directories(dirs)
table.insert(lines, d)
end
-- If a file selection buffer is already open, close it to avoid confusion
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
local name = vim.api.nvim_buf_get_name(buf)
if name:match("ChatGPT_File_Selection") then
@@ -68,7 +74,6 @@ function M.pick_directories(dirs)
once = true,
callback = function()
on_write()
-- Automatically close the buffer once saved
vim.cmd("bd! " .. bufnr)
end
})
@@ -76,7 +81,6 @@ function M.pick_directories(dirs)
vim.cmd("split")
vim.cmd("buffer " .. bufnr)
-- Wait up to 30s for user to close
vim.wait(30000, function()
local winids = vim.api.nvim_tabpage_list_wins(0)
for _, w in ipairs(winids) do