├── README.md ├── RELEASE.md ├── lua └── nvim_copy │ ├── buffer_utils.lua │ ├── clipboard.lua │ ├── commands.lua │ ├── file_utils.lua │ ├── init.lua │ └── utils.lua └── plugin └── nvim-copy.vim /README.md: -------------------------------------------------------------------------------- 1 | # nvim-copy 2 | 3 | **nvim-copy** is a Neovim plugin written in Lua that allows you to copy the content of files—from visible buffers, Git-modified files, quickfix lists, Harpoon marks, or entire directories—to the clipboard. The plugin supports preserving code folds, making it easier to view folded sections in the copied output. 4 | It is also highly configurable: 5 | 6 | - **Folding:** All commands support the `nofolds` flag to disable preserving fold information. 7 | - **Recursion:** The copy directory and copy Harpoon commands support the `norecurse` flag to disable recursive file search. 8 | - **Ignore Patterns:** Configure which files or directories should be ignored using Gitignore-style patterns. 9 | 10 | ## Features 11 | 12 | - **Copy Visible Buffers:** Copy the content of all visible buffers. 13 | - **Copy Current Buffer:** Copy the current buffer’s content. 14 | - **Copy Git Files:** Copy files modified in Git. 15 | - **Copy Quickfix Files:** Copy files listed in the quickfix list. 16 | - **Copy Harpoon Files:** Copy files marked with [Harpoon](https://github.com/ThePrimeagen/harpoon). 17 | - **Copy Directory Files:** Copy all files in a directory (supports recursive search). 18 | - **Ignore Patterns:** Automatically exclude files and directories using `ignore` settings. 19 | 20 | ## Installation 21 | 22 | ### Using packer.nvim 23 | 24 | ```lua 25 | use { 26 | 'YounesElhjouji/nvim-copy', 27 | config = function() 28 | require("nvim_copy").setup({ 29 | ignore = { 30 | "*node_modules/*", 31 | "*__pycache__/*", 32 | "*.git/*", 33 | "*dist/*", 34 | "*build/*", 35 | "*.log" 36 | } 37 | }) 38 | 39 | -- Optional key mappings: 40 | vim.api.nvim_set_keymap('n', 'cb', ':CopyBuffersToClipboard', { noremap = true, silent = true }) 41 | end 42 | } 43 | ``` 44 | 45 | ### Using lazy.nvim 46 | 47 | If you're using lazy.nvim, add the following to your lazy configuration: 48 | 49 | ```lua 50 | { 51 | "YounesElhjouji/nvim-copy", 52 | lazy = false, -- ensures the plugin is loaded on startup 53 | config = function() 54 | require("nvim_copy").setup({ 55 | ignore = { 56 | "*node_modules/*", 57 | "*__pycache__/*", 58 | "*.git/*", 59 | "*dist/*", 60 | "*build/*", 61 | "*.log" 62 | } 63 | }) 64 | 65 | -- Optional key mappings: 66 | vim.api.nvim_set_keymap('n', 'cb', ':CopyBuffersToClipboard', { noremap = true, silent = true }) 67 | end, 68 | } 69 | ``` 70 | 71 | ## Usage 72 | 73 | After installation, the following commands become available: 74 | 75 | - `:CopyBuffersToClipboard` 76 | Copies the content of all visible buffers to the clipboard. 77 | _Supports the flag:_ `nofolds` (to disable fold preservation). 78 | 79 | - `:CopyCurrentBufferToClipboard` 80 | Copies the current buffer’s content to the clipboard. 81 | _Supports the flag:_ `nofolds`. 82 | 83 | - `:CopyGitFilesToClipboard` 84 | Copies the content of files modified in Git to the clipboard. 85 | _Supports the flag:_ `nofolds`. 86 | 87 | - `:CopyQuickfixFilesToClipboard` 88 | Copies the content of files from the quickfix list to the clipboard. 89 | _Supports the flag:_ `nofolds`. 90 | 91 | - `:CopyHarpoonFilesToClipboard` 92 | Copies the content of files marked in Harpoon to the clipboard. 93 | _Supports the flags:_ 94 | 95 | - `nofolds` (to disable fold preservation) 96 | - `norecurse` (to disable recursive search) 97 | 98 | - `:CopyDirectoryFilesToClipboard [directory] [flags]` 99 | Copies the content of all files in the given directory (or the current buffer’s directory if omitted) to the clipboard. 100 | _Flags (optional):_ 101 | - `nofolds`: Do not preserve fold information. 102 | - `norecurse`: Do not traverse directories recursively. 103 | 104 | ### Flags Example 105 | 106 | ``` 107 | :CopyDirectoryFilesToClipboard /path/to/dir nofolds norecurse 108 | ``` 109 | 110 | ## Ignore Patterns 111 | 112 | `nvim-copy` allows you to configure files and directories that should be ignored when copying directory contents. This is done using `ignore` patterns, which follow a simple glob-like convention. 113 | 114 | ### Custom Ignore Configuration 115 | 116 | You can customize ignore patterns via `setup()`: 117 | 118 | ```lua 119 | require("nvim_copy").setup({ 120 | ignore = { "*tmp/*", "*logs/*.log", "*backup/*" } 121 | }) 122 | ``` 123 | 124 | This ensures that: 125 | 126 | - `tmp/` and its contents are ignored. 127 | - Any `.log` file inside `logs/` is ignored. 128 | - `backup/` and all its files are ignored. 129 | 130 | ### Behavior of Ignore Patterns 131 | 132 | | Pattern | What It Ignores | 133 | | ------------------- | ------------------------------------------------------ | 134 | | `"*node_modules/*"` | Any `node_modules/` directory, regardless of depth. | 135 | | `"*.log"` | All files ending in `.log` anywhere in the project. | 136 | | `"*dist/*"` | Any `dist/` directory and all its contents. | 137 | | `"*.git/*"` | Any `.git/` directory and all its contents. | 138 | | `"*configs/*"` | Any `configs/` directory **and everything inside it**. | 139 | 140 | > **Note:** Since patterns start with `"*"`, they match paths **anywhere** in the project. 141 | > This ensures that all instances of `node_modules/` or `dist/`—even deeply nested ones—are ignored. 142 | 143 | ## How It Works 144 | 145 | 1. **File Reading:** 146 | The plugin reads file content from loaded buffers (or directly from disk if the buffer is not loaded). If the buffer is visible, it uses a window context to preserve folding information. 147 | 148 | 2. **Content Aggregation:** 149 | Files are processed and concatenated with a header (showing the file path relative to the project root), then the aggregated content is set to the clipboard. 150 | 151 | 3. **Command Registration:** 152 | Commands are registered automatically when the plugin is loaded via the `plugin/nvim-copy.vim` loader file. 153 | 154 | 4. **Ignore Handling:** 155 | - The plugin filters files and directories **before** reading them. 156 | - Patterns with `"*dir/*"` ignore **all instances** of the directory `dir/` anywhere in the project. 157 | - File-based ignores (`"*.log"`) are applied at the file level. 158 | 159 | ## Contributing 160 | 161 | Contributions and feedback are welcome! Please open an issue or submit a pull request on GitHub. 162 | 163 | ## License 164 | 165 | [MIT](LICENSE) 166 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # nvim-copy Release Notes 2 | 3 | This document tracks changes and improvements made to **nvim-copy**. 4 | 5 | --- 6 | 7 | ## **v1.1.0 - Added Ignore Patterns Support** (Latest) 8 | 9 | ### **New Features** 10 | 11 | - **Added `ignore` option** to allow users to specify files and directories to be excluded when copying. 12 | - **Supports Gitignore-style patterns**, making it easy to ignore `node_modules`, logs, and other unwanted files. 13 | - **Patterns can match directories (`*dir/*`) and file types (`*.log`).** 14 | 15 | ### **Recommended Ignore Configuration** 16 | 17 | Although no files are ignored by default, we recommend the following: 18 | 19 | ```lua 20 | require("nvim_copy").setup({ 21 | ignore = { 22 | "*node_modules/*", -- Ignore any node_modules directory 23 | "*__pycache__/*", -- Ignore Python cache directories 24 | "*.git/*", -- Ignore Git repository metadata 25 | "*dist/*", -- Ignore compiled distribution files 26 | "*build/*", -- Ignore build artifacts 27 | "*.log" -- Ignore log files 28 | } 29 | }) 30 | ``` 31 | 32 | - Ignore patterns now work **before** directory traversal, improving efficiency. 33 | - `"*dir/*"` ignores any instance of `dir/`, while `"*.log"` ignores all `.log` files. 34 | 35 | --- 36 | 37 | ## **v1.0.0 - Initial Release** 38 | 39 | ### **Features** 40 | 41 | - **Copy Visible Buffers**: Copy the content of all open buffers. 42 | - **Copy Current Buffer**: Copy only the currently focused buffer. 43 | - **Copy Git Files**: Copy all modified files in the current Git repository. 44 | - **Copy Quickfix Files**: Copy all files listed in the quickfix list. 45 | - **Copy Harpoon Files**: Copy files saved in Harpoon marks. 46 | - **Copy Directory Files**: Copy all files from a directory, with optional recursive traversal. 47 | - **Folding Support**: Preserve folds when copying buffer content. 48 | - **Recursive & Non-Recursive Copying**: Toggle recursion for directory-based copy operations. 49 | 50 | ### **Commands Available** 51 | 52 | - `:CopyBuffersToClipboard` 53 | - `:CopyCurrentBufferToClipboard` 54 | - `:CopyGitFilesToClipboard` 55 | - `:CopyQuickfixFilesToClipboard` 56 | - `:CopyHarpoonFilesToClipboard` 57 | - `:CopyDirectoryFilesToClipboard` 58 | 59 | --- 60 | 61 | ## **Upcoming Features** 62 | 63 | - **Optional Project Tree in Clipboard**: 64 | - Add an option to **include the project tree** as text in the clipboard. 65 | - Users can choose to copy **only** the project tree or append it alongside copied files. 66 | - This helps provide an **overview of the project structure** when sharing code. 67 | 68 | --- 69 | 70 | ## **How to Update** 71 | 72 | To get the latest version, update via your package manager: 73 | 74 | #### **For Packer:** 75 | 76 | ```lua 77 | :PackerSync 78 | ``` 79 | 80 | #### **For Lazy.nvim:** 81 | 82 | ```lua 83 | :Lazy sync 84 | ``` 85 | 86 | --- 87 | 88 | ## **Contributing** 89 | 90 | Have suggestions or found a bug? Open an issue or submit a pull request on GitHub. 91 | -------------------------------------------------------------------------------- /lua/nvim_copy/buffer_utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Function to read visible file content with preserved folds. 4 | function M.read_visible_file_content_with_folds(file_path) 5 | local lines = {} 6 | 7 | -- Check if there's a loaded buffer for this file. 8 | local bufnr = nil 9 | for _, buf in ipairs(vim.api.nvim_list_bufs()) do 10 | if vim.api.nvim_buf_is_loaded(buf) and vim.api.nvim_buf_get_name(buf) == file_path then 11 | bufnr = buf 12 | break 13 | end 14 | end 15 | 16 | -- Define a helper that uses the fold API to process lines. 17 | local function process_lines() 18 | local line_count = vim.api.nvim_buf_line_count(bufnr) 19 | local current_line = 1 20 | 21 | while current_line <= line_count do 22 | local fold_start = vim.fn.foldclosed(current_line) 23 | if fold_start == current_line then 24 | -- We are at the start of a closed fold. 25 | local fold_end = vim.fn.foldclosedend(current_line) 26 | local header_line = vim.api.nvim_buf_get_lines(bufnr, current_line - 1, current_line, false)[1] 27 | local folded_lines = fold_end - current_line 28 | -- Append a note about the number of folded lines. 29 | header_line = header_line .. " ... (" .. folded_lines .. " folded lines)" 30 | table.insert(lines, header_line) 31 | current_line = fold_end + 1 32 | else 33 | table.insert(lines, vim.api.nvim_buf_get_lines(bufnr, current_line - 1, current_line, false)[1]) 34 | current_line = current_line + 1 35 | end 36 | end 37 | end 38 | 39 | if bufnr then 40 | -- Check if the buffer is currently displayed in any window. 41 | local win_id = nil 42 | for _, win in ipairs(vim.api.nvim_list_wins()) do 43 | if vim.api.nvim_win_get_buf(win) == bufnr then 44 | win_id = win 45 | break 46 | end 47 | end 48 | 49 | if win_id then 50 | -- If visible, process lines within that window's context. 51 | vim.api.nvim_win_call(win_id, process_lines) 52 | else 53 | -- If not visible, create a temporary hidden window so that fold info is computed. 54 | local width = math.floor(vim.o.columns * 0.8) 55 | local height = math.floor(vim.o.lines * 0.8) 56 | local opts = { 57 | relative = 'editor', 58 | width = width, 59 | height = height, 60 | row = math.floor((vim.o.lines - height) / 2), 61 | col = math.floor((vim.o.columns - width) / 2), 62 | style = 'minimal', 63 | focusable = false, -- keep it out of focus 64 | border = 'none', 65 | } 66 | local temp_win = vim.api.nvim_open_win(bufnr, false, opts) 67 | -- Ensure that folding is enabled. 68 | vim.api.nvim_buf_set_option(bufnr, "foldenable", true) 69 | -- Process lines in the temporary window context. 70 | vim.api.nvim_win_call(temp_win, process_lines) 71 | vim.api.nvim_win_close(temp_win, true) 72 | end 73 | else 74 | -- Fallback: read from file if the buffer is not loaded. 75 | local file = io.open(file_path, "r") 76 | if file then 77 | for line in file:lines() do 78 | table.insert(lines, line) 79 | end 80 | file:close() 81 | else 82 | table.insert(lines, "-- File could not be read --") 83 | end 84 | end 85 | 86 | return table.concat(lines, "\n") 87 | end 88 | 89 | -- Function to read visible file content without preserving folds. 90 | function M.read_visible_file_content_without_folds(file_path) 91 | local lines = {} 92 | 93 | -- Check if there's a loaded buffer for this file. 94 | local bufnr = nil 95 | for _, buf in ipairs(vim.api.nvim_list_bufs()) do 96 | if vim.api.nvim_buf_is_loaded(buf) and vim.api.nvim_buf_get_name(buf) == file_path then 97 | bufnr = buf 98 | break 99 | end 100 | end 101 | 102 | if bufnr then 103 | -- Simply retrieve all lines. 104 | lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) 105 | else 106 | -- Fallback: read directly from the file. 107 | local file = io.open(file_path, "r") 108 | if file then 109 | for line in file:lines() do 110 | table.insert(lines, line) 111 | end 112 | file:close() 113 | else 114 | table.insert(lines, "-- File could not be read --") 115 | end 116 | end 117 | 118 | return table.concat(lines, "\n") 119 | end 120 | 121 | return M 122 | -------------------------------------------------------------------------------- /lua/nvim_copy/clipboard.lua: -------------------------------------------------------------------------------- 1 | local utils = require("nvim_copy.utils") 2 | local buffer_utils = require("nvim_copy.buffer_utils") 3 | 4 | local M = {} 5 | 6 | local function get_file_type(file_path) 7 | local extension = vim.fn.fnamemodify(file_path, ":e") 8 | local file_type = extension 9 | return file_type 10 | end 11 | 12 | function M.build_content_buffer(files, opts) 13 | opts = opts or {} 14 | local result = {} 15 | local seen_files = {} 16 | 17 | for _, file_path in ipairs(files) do 18 | if file_path and not seen_files[file_path] then 19 | seen_files[file_path] = true 20 | local relative_path = utils.get_relative_path(file_path) 21 | local file_type = get_file_type(file_path) 22 | local header = string.format("File: %s\n```%s\n", relative_path, file_type) 23 | table.insert(result, header) 24 | if opts.preserve_folds == false then 25 | table.insert(result, buffer_utils.read_visible_file_content_without_folds(file_path) .. "\n```\n") 26 | else 27 | table.insert(result, buffer_utils.read_visible_file_content_with_folds(file_path) .. "\n```\n") 28 | end 29 | end 30 | end 31 | return result 32 | end 33 | 34 | function M.copy_to_clipboard_from_files(files, source, opts) 35 | local content_buffer = M.build_content_buffer(files, opts) 36 | if #content_buffer == 0 then 37 | print("No valid files found for " .. source .. ".") 38 | return 39 | end 40 | 41 | local aggregated_content = table.concat(content_buffer) 42 | if vim.fn.has('clipboard') == 1 then 43 | vim.fn.setreg('+', aggregated_content) 44 | local file_count = #content_buffer / 2 45 | print(string.format("%s contents of %d files copied to clipboard!", source, file_count)) 46 | else 47 | print("Clipboard support not available.") 48 | end 49 | end 50 | 51 | return M 52 | -------------------------------------------------------------------------------- /lua/nvim_copy/commands.lua: -------------------------------------------------------------------------------- 1 | local clipboard = require("nvim_copy.clipboard") 2 | local file_utils = require("nvim_copy.file_utils") 3 | 4 | local M = {} 5 | 6 | -- Helper: merge opts with defaults. 7 | -- Default preserve_folds is true and default recursive is true. 8 | local function default_opts(opts) 9 | opts = opts or {} 10 | if opts.preserve_folds == nil then 11 | opts.preserve_folds = true 12 | end 13 | if opts.recursive == nil then 14 | opts.recursive = true 15 | end 16 | opts.ignore = opts.ignore or {} -- Ensure ignore is always present 17 | return opts 18 | end 19 | 20 | local function parse_flags(args) 21 | local opts = {} 22 | for _, arg in ipairs(args) do 23 | local flag = arg:lower() 24 | if flag == "nofolds" then 25 | opts.preserve_folds = false 26 | elseif flag == "norecurse" then 27 | opts.recursive = false 28 | end 29 | end 30 | return opts 31 | end 32 | 33 | function M.setup(global_opts) 34 | M.global_opts = global_opts or {} 35 | 36 | local function wrap_command(fn) 37 | return function(opts) 38 | local flags = parse_flags(opts.fargs or {}) 39 | flags.ignore = M.global_opts.ignore -- Pass ignore option 40 | fn(flags) 41 | end 42 | end 43 | 44 | vim.api.nvim_create_user_command('CopyBuffersToClipboard', wrap_command(M.copy_buffers_to_clipboard), { nargs = "*" }) 45 | vim.api.nvim_create_user_command('CopyGitFilesToClipboard', wrap_command(M.copy_git_files_to_clipboard), 46 | { nargs = "*" }) 47 | vim.api.nvim_create_user_command('CopyQuickfixFilesToClipboard', wrap_command(M.copy_quickfix_files_to_clipboard), 48 | { nargs = "*" }) 49 | vim.api.nvim_create_user_command('CopyHarpoonFilesToClipboard', wrap_command(M.copy_harpoon_files_to_clipboard), 50 | { nargs = "*" }) 51 | vim.api.nvim_create_user_command('CopyDirectoryFilesToClipboard', function(opts) 52 | local dir = nil 53 | local flags = {} 54 | 55 | if #opts.fargs > 0 then 56 | if vim.fn.isdirectory(opts.fargs[1]) == 1 then 57 | dir = opts.fargs[1] 58 | for i = 2, #opts.fargs do 59 | table.insert(flags, opts.fargs[i]) 60 | end 61 | else 62 | flags = opts.fargs 63 | end 64 | else 65 | dir = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":h") 66 | end 67 | 68 | local flags_opts = parse_flags(flags) 69 | flags_opts.ignore = M.global_opts.ignore 70 | M.copy_directory_files_to_clipboard(dir, flags_opts) 71 | end, { nargs = "*" }) 72 | 73 | vim.api.nvim_create_user_command('CopyCurrentBufferToClipboard', wrap_command(M.copy_current_buffer_to_clipboard), 74 | { nargs = "*" }) 75 | end 76 | 77 | function M.copy_buffers_to_clipboard(opts) 78 | opts = default_opts(opts) 79 | local files = {} 80 | local seen_files = {} 81 | 82 | for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do 83 | if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_get_option(bufnr, 'buflisted') then 84 | local file_path = vim.api.nvim_buf_get_name(bufnr) 85 | if file_path ~= "" and not seen_files[file_path] then 86 | seen_files[file_path] = true 87 | table.insert(files, file_path) 88 | end 89 | end 90 | end 91 | 92 | clipboard.copy_to_clipboard_from_files(files, "Buffer", opts) 93 | end 94 | 95 | function M.copy_current_buffer_to_clipboard(opts) 96 | opts = default_opts(opts) 97 | local bufnr = vim.api.nvim_get_current_buf() 98 | local file_path = vim.api.nvim_buf_get_name(bufnr) 99 | 100 | if file_path == "" then 101 | print("Current buffer is not associated with a file.") 102 | return 103 | end 104 | 105 | clipboard.copy_to_clipboard_from_files({ file_path }, "Current buffer", opts) 106 | end 107 | 108 | function M.copy_git_files_to_clipboard(opts) 109 | opts = default_opts(opts) 110 | local git_files = vim.fn.systemlist("git diff --name-only HEAD") 111 | 112 | if #git_files == 0 then 113 | print("No modified files in Git.") 114 | return 115 | end 116 | 117 | clipboard.copy_to_clipboard_from_files(git_files, "Git-modified file", opts) 118 | end 119 | 120 | function M.copy_quickfix_files_to_clipboard(opts) 121 | opts = default_opts(opts) 122 | local qf_list = vim.fn.getqflist() 123 | 124 | if #qf_list == 0 then 125 | print("Quickfix list is empty.") 126 | return 127 | end 128 | 129 | local files = {} 130 | local seen_files = {} 131 | 132 | for _, entry in ipairs(qf_list) do 133 | local file_path = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr) 134 | if file_path and file_path ~= "" and not seen_files[file_path] then 135 | seen_files[file_path] = true 136 | table.insert(files, file_path) 137 | end 138 | end 139 | 140 | if #files == 0 then 141 | print("No valid files found in the quickfix list.") 142 | return 143 | end 144 | 145 | clipboard.copy_to_clipboard_from_files(files, "Quickfix file", opts) 146 | end 147 | 148 | function M.copy_harpoon_files_to_clipboard(opts) 149 | opts = default_opts(opts) 150 | local harpoon = require("harpoon") 151 | local marks = harpoon.get_mark_config().marks 152 | 153 | if #marks == 0 then 154 | print("No files marked in Harpoon.") 155 | return 156 | end 157 | 158 | local paths = {} 159 | 160 | for _, mark in ipairs(marks) do 161 | if mark.filename then 162 | table.insert(paths, mark.filename) 163 | end 164 | end 165 | 166 | local all_files = file_utils.process_paths(paths, opts.recursive, opts.ignore) 167 | 168 | if #all_files == 0 then 169 | print("No valid files found in Harpoon marks.") 170 | return 171 | end 172 | 173 | clipboard.copy_to_clipboard_from_files(all_files, "Harpoon", opts) 174 | end 175 | 176 | function M.copy_directory_files_to_clipboard(directory, opts) 177 | opts = default_opts(opts) 178 | local recursive = opts.recursive 179 | 180 | if not directory or directory == "" then 181 | local current_file = vim.api.nvim_buf_get_name(0) 182 | 183 | if current_file == "" then 184 | print("No directory specified and no file in current buffer.") 185 | return 186 | end 187 | 188 | directory = vim.fn.fnamemodify(current_file, ":h") 189 | end 190 | 191 | if vim.fn.isdirectory(directory) == 0 then 192 | print("Invalid directory: " .. directory) 193 | return 194 | end 195 | 196 | local all_files = file_utils.process_paths({ directory }, recursive, opts.ignore) 197 | 198 | if #all_files == 0 then 199 | print("No files found in directory: " .. directory) 200 | return 201 | end 202 | 203 | clipboard.copy_to_clipboard_from_files(all_files, "Directory", opts) 204 | end 205 | 206 | return M 207 | -------------------------------------------------------------------------------- /lua/nvim_copy/file_utils.lua: -------------------------------------------------------------------------------- 1 | local utils = require("nvim_copy.utils") 2 | local M = {} 3 | 4 | function M.get_all_files(path, recursive, ignore) 5 | local files = {} 6 | ignore = ignore or {} 7 | 8 | -- Determine if this is a directory 9 | local is_directory = vim.fn.isdirectory(path) == 1 10 | 11 | -- Skip this path entirely if it should be ignored. 12 | if utils.is_ignored(path, ignore, is_directory) then 13 | return files 14 | end 15 | 16 | if is_directory then 17 | local items = vim.fn.glob(path .. "/*", true, true) 18 | for _, item in ipairs(items) do 19 | local item_is_directory = vim.fn.isdirectory(item) == 1 20 | 21 | -- Check if this file or directory should be ignored. 22 | if not utils.is_ignored(item, ignore, item_is_directory) then 23 | if item_is_directory then 24 | if recursive then 25 | for _, f in ipairs(M.get_all_files(item, recursive, ignore)) do 26 | table.insert(files, f) 27 | end 28 | end 29 | else 30 | table.insert(files, item) 31 | end 32 | end 33 | end 34 | else 35 | table.insert(files, path) 36 | end 37 | return files 38 | end 39 | 40 | -- Process a list of paths (files and/or directories) into a flat list of files. 41 | -- `ignore` is passed along to `get_all_files`. 42 | function M.process_paths(paths, recursive, ignore) 43 | local all_files = {} 44 | for _, path in ipairs(paths) do 45 | for _, f in ipairs(M.get_all_files(path, recursive, ignore)) do 46 | table.insert(all_files, f) 47 | end 48 | end 49 | return all_files 50 | end 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /lua/nvim_copy/init.lua: -------------------------------------------------------------------------------- 1 | local commands = require("nvim_copy.commands") 2 | 3 | local M = {} 4 | 5 | -- Default options 6 | M.options = { 7 | ignore = {} -- Default empty list 8 | } 9 | 10 | -- Setup function to allow users to configure options 11 | function M.setup(opts) 12 | -- Ensure the options are updated globally 13 | M.options = vim.tbl_deep_extend("force", M.options, opts or {}) 14 | 15 | -- Register commands with updated options 16 | commands.setup(M.options) 17 | end 18 | 19 | return M 20 | -------------------------------------------------------------------------------- /lua/nvim_copy/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Utility function to compute relative paths 4 | function M.get_relative_path(file_path) 5 | local project_root = vim.loop.cwd() 6 | local relative_path = vim.fn.fnamemodify(file_path, ":.") 7 | return vim.fn.substitute(relative_path, "^" .. vim.fn.escape(project_root, "/"), "", "") 8 | end 9 | 10 | -- Utility function to read file content 11 | function M.read_file_content(file_path) 12 | local lines = {} 13 | local file = io.open(file_path, "r") 14 | if file then 15 | for line in file:lines() do 16 | table.insert(lines, line) 17 | end 18 | file:close() 19 | else 20 | table.insert(lines, "-- File could not be read --") 21 | end 22 | return table.concat(lines, "\n") 23 | end 24 | 25 | -- Convert a glob (e.g. "*.log") into a Lua pattern. 26 | -- This is a simple conversion: it escapes Lua magic characters and 27 | -- replaces '*' with '.*'. (Gitignore matching can be more complex.) 28 | function M.glob_to_pattern(glob) 29 | local pattern = "^" .. glob:gsub("([%^%$%(%)%%%.%+%-%[%]$])", "%%%1") 30 | :gsub("%*", ".*") .. "$" 31 | return pattern 32 | end 33 | 34 | -- Check if a file/directory path matches any of the ignore patterns. 35 | -- `ignore_patterns` should be a table of glob strings. 36 | function M.is_ignored(file_path, ignore_patterns) 37 | ignore_patterns = ignore_patterns or {} 38 | for _, pattern in ipairs(ignore_patterns) do 39 | local lua_pattern = M.glob_to_pattern(pattern) 40 | if file_path:match(lua_pattern) then 41 | return true 42 | end 43 | end 44 | return false 45 | end 46 | 47 | return M 48 | -------------------------------------------------------------------------------- /plugin/nvim-copy.vim: -------------------------------------------------------------------------------- 1 | " plugin/nvim_copy.vim 2 | lua require("nvim_copy") 3 | --------------------------------------------------------------------------------