diff --git a/lua/chatgpt_nvim/context.lua b/lua/chatgpt_nvim/context.lua index 35e7e02..53ef6b6 100644 --- a/lua/chatgpt_nvim/context.lua +++ b/lua/chatgpt_nvim/context.lua @@ -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 @@ -150,4 +146,4 @@ function M.get_file_contents(files) return table.concat(sections, "\n") end -return M +return M \ No newline at end of file diff --git a/lua/chatgpt_nvim/handler.lua b/lua/chatgpt_nvim/handler.lua index b5ebc26..2ebaf3f 100644 --- a/lua/chatgpt_nvim/handler.lua +++ b/lua/chatgpt_nvim/handler.lua @@ -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) @@ -73,4 +72,4 @@ function M.finish() print("Finished processing files.") end -return M +return M \ No newline at end of file diff --git a/lua/chatgpt_nvim/init.lua b/lua/chatgpt_nvim/init.lua index b2b0135..384f7c8 100644 --- a/lua/chatgpt_nvim/init.lua +++ b/lua/chatgpt_nvim/init.lua @@ -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 don’t 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]) diff --git a/lua/chatgpt_nvim/ui.lua b/lua/chatgpt_nvim/ui.lua index 9cc5ca5..c5d1bf4 100644 --- a/lua/chatgpt_nvim/ui.lua +++ b/lua/chatgpt_nvim/ui.lua @@ -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. - 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") +-- 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 @@ -91,4 +95,4 @@ function M.pick_directories(dirs) return selected_dirs end -return M +return M \ No newline at end of file