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

View File

@@ -1,7 +1,9 @@
local M = {} local M = {}
local uv = vim.loop 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 function ensure_dir(path)
local st = uv.fs_stat(path) local st = uv.fs_stat(path)
@@ -16,8 +18,7 @@ local function ensure_dir(path)
return true return true
end end
function M.get_clipboard_content() function M.get_clipboard_content(conf)
local conf = config.load()
local content = vim.fn.getreg('+') local content = vim.fn.getreg('+')
if conf.debug then if conf.debug then
vim.api.nvim_out_write("[chatgpt_nvim:handler] Clipboard content length: " .. #content .. "\n") vim.api.nvim_out_write("[chatgpt_nvim:handler] Clipboard content length: " .. #content .. "\n")
@@ -25,8 +26,7 @@ function M.get_clipboard_content()
return content return content
end end
function M.write_file(filepath, content) function M.write_file(filepath, content, conf)
local conf = config.load()
local dir = filepath:match("(.*)/") local dir = filepath:match("(.*)/")
if dir and dir ~= "" then if dir and dir ~= "" then
ensure_dir(dir) ensure_dir(dir)
@@ -46,8 +46,7 @@ function M.write_file(filepath, content)
end end
end end
function M.delete_file(filepath) function M.delete_file(filepath, conf)
local conf = config.load()
local st = uv.fs_stat(filepath) local st = uv.fs_stat(filepath)
if st then if st then
local success, err = uv.fs_unlink(filepath) local success, err = uv.fs_unlink(filepath)
@@ -73,4 +72,4 @@ function M.finish()
print("Finished processing files.") print("Finished processing files.")
end end
return M return M

View File

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

View File

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