├── .gitignore ├── LICENSE ├── README.md ├── dev └── init.lua └── lua └── nvim-search-and-replace ├── config.lua ├── init.lua └── util.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 s1n7ax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-search-and-replace 2 | 3 | Absolutly minimal plugin to search and replace multiple files in current working directory. This only uses `vim` built-in features to search and replace 4 | 5 | ## Install 6 | 7 | ```lua 8 | use { 9 | 's1n7ax/nvim-search-and-replace', 10 | config = function() require'nvim-search-and-replace'.setup() end, 11 | } 12 | ``` 13 | 14 | ## Keymaps 15 | 16 | leader + g + r = Search and replace (Respects 17 | ignored files) 18 | 19 | leader + g + R = Search and replace 20 | everything (Don't give a shit about the ignored files) 21 | 22 | leader + g + u = Search, replace and save (Respects 23 | ignored files) 24 | 25 | leader + g + U = Search, replace and save 26 | everything (Don't give a shit about the ignored files) 27 | 28 | ## Commands 29 | 30 | ```vim 31 | :SReplace - Search and replace 32 | :SReplaceAll - Search and replace all including ignored files 33 | :SReplaceAndSave - Search, replace and save 34 | :SReplaceAllAndSave - Search, replace and save including ignored files 35 | ``` 36 | 37 | ## Syntax 38 | 39 | ### Search Query Syntax 40 | 41 | `:h su` for more information 42 | 43 | ``` 44 | // 45 | // 46 | / 47 | // 48 | 49 | ``` 50 | 51 | Ex:- 52 | 53 | ```lua 54 | -- search the word "test" in ".js" files and replace them glabally in every file 55 | /test/g **/*.js 56 | 57 | -- search the word "test" in all files and replace them glabally in every file 58 | test/g 59 | 60 | -- search the word "test" in all files and replace one time for single line 61 | test 62 | 63 | -- seach any word starts with "te" and ends with "st" and replace one time for single line 64 | te.*st 65 | 66 | -- seach "print(something)" and add something to match group 67 | print(\(.*\)) 68 | ``` 69 | 70 | ### Replace Query Syntax 71 | 72 | ``` 73 | 74 | ``` 75 | 76 | Ex:- 77 | 78 | ``` 79 | -- replace the matched queries with "test" 80 | test 81 | 82 | -- replace the matched queries with "console.log" and replace \1 with the first 83 | match value 84 | console.log(\1) 85 | ``` 86 | 87 | ## Configurations 88 | 89 | ```lua 90 | require('nvim-search-and-replace').setup{ 91 | -- file patters to ignore 92 | ignore = {'**/node_modules/**', '**/.git/**', '**/.gitignore', '**/.gitmodules','build/**'}, 93 | 94 | -- save the changes after replace 95 | update_changes = false, 96 | 97 | -- keymap for search and replace 98 | replace_keymap = 'gr', 99 | 100 | -- keymap for search and replace ( this does not care about ignored files ) 101 | replace_all_keymap = 'gR', 102 | 103 | -- keymap for search and replace 104 | replace_and_save_keymap = 'gu', 105 | 106 | -- keymap for search and replace ( this does not care about ignored files ) 107 | replace_all_and_save_keymap = 'gU', 108 | } 109 | ``` 110 | 111 | ## Available functions 112 | 113 | ```lua 114 | require('nvim-global-replace').setup(config) 115 | 116 | -- require('nvim-global-replace').search_and_replace({ 117 | -- ignore = { 'tests/**'} , update_changes = true 118 | -- }) 119 | require('nvim-global-replace').replace(opts) 120 | 121 | -- require('nvim-global-replace').search_and_replace({ 122 | -- update_changes = true 123 | -- }) 124 | require('nvim-global-replace').replace_all(opts) 125 | ``` 126 | -------------------------------------------------------------------------------- /dev/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | -- plugin name will be used to reload the loaded modules 3 | --]] 4 | local package_name = 'nvim-search-and-replace' 5 | 6 | -- add the escape character to special characters 7 | local escape_pattern = function (text) 8 | return text:gsub("([^%w])", "%%%1") 9 | end 10 | 11 | -- unload loaded modules by the matching text 12 | local unload_packages = function () 13 | local esc_package_name = escape_pattern(package_name) 14 | 15 | for module_name, _ in pairs(package.loaded) do 16 | if string.find(module_name, esc_package_name) then 17 | package.loaded[module_name] = nil 18 | end 19 | end 20 | end 21 | 22 | -- executes the run method in the package 23 | local run_action = function () 24 | require(package_name).setup() 25 | end 26 | 27 | -- unload and run the function from the package 28 | function Reload_and_run() 29 | unload_packages() 30 | run_action() 31 | end 32 | 33 | local set_keymap = vim.api.nvim_set_keymap 34 | 35 | set_keymap('n', ',r', 'luafile dev/init.lua', {}) 36 | set_keymap('n', ',w', 'lua Reload_and_run()', {}) 37 | -------------------------------------------------------------------------------- /lua/nvim-search-and-replace/config.lua: -------------------------------------------------------------------------------- 1 | local conf = { 2 | -- file patters to ignore 3 | ignore = {'**/node_modules/**', '**/.git/**', '**/.gitignore', '**/.gitmodules','build/**'}, 4 | 5 | -- save the changes after replace 6 | update_changes = false, 7 | 8 | -- keymap for search and replace 9 | replace_keymap = 'gr', 10 | 11 | -- keymap for search and replace ( this does not care about ignored files ) 12 | replace_all_keymap = 'gR', 13 | 14 | -- keymap for search and replace 15 | replace_and_save_keymap = 'gu', 16 | 17 | -- keymap for search and replace ( this does not care about ignored files ) 18 | replace_all_and_save_keymap = 'gU', 19 | } 20 | 21 | return conf 22 | -------------------------------------------------------------------------------- /lua/nvim-search-and-replace/init.lua: -------------------------------------------------------------------------------- 1 | local config = require('nvim-search-and-replace.config') 2 | local util = require('nvim-search-and-replace.util') 3 | 4 | local V = vim 5 | local Ui = V.ui 6 | local CMD = V.cmd 7 | local API = V.api 8 | 9 | local M = {} 10 | 11 | local VIMGREP_SEARCH_PATTERN = 'vimgrep /%s/j %s' 12 | 13 | local FILE_SEARCH_PATTERN = 'cfdo %%s/%s/%s/%s' 14 | local FILE_SEARCH_UPDATE_PATTERN = 'cfdo %%s/%s/%s/%s | update' 15 | 16 | local patterns = { 17 | '^/(.-)/(.-)%s(.*)$', 18 | '^/(.-)/(.-)$', 19 | '^/(.-)/(.-)$', 20 | '^(.-)/(.-)$', 21 | '^/(.-)/$', 22 | '^(.-)$', 23 | } 24 | 25 | local function set_n_keymap(keymap, action, opts) 26 | if keymap and not string.is_empty(keymap) then 27 | API.nvim_set_keymap('n', keymap, action, opts) 28 | end 29 | end 30 | 31 | function string.is_empty(str) 32 | if str == nil then 33 | return true 34 | end 35 | 36 | str = str:gsub('%s*', '') 37 | 38 | return str == '' 39 | end 40 | 41 | local function get_maching_results(str) 42 | for _, pattern in ipairs(patterns) do 43 | if str:match(pattern) then 44 | return str:match(pattern) 45 | end 46 | end 47 | end 48 | 49 | -- Setup the plugin 50 | -- ex:- 51 | -- @Param require('nvim-search-and-replace').setup({ 52 | -- ignore = {'node_modules/**', '.git/**'}, 53 | -- update_changes = true, 54 | -- search_keymap = 'gs', 55 | -- search_all_keymap = 'ga' 56 | -- }) 57 | function M.setup(opts) 58 | if opts then 59 | config = util.merge_tables(config, opts) 60 | end 61 | 62 | V.cmd('command! SReplace lua require("nvim-search-and-replace").replace()') 63 | V.cmd('command! SReplaceAll lua require("nvim-search-and-replace").replace_all()') 64 | 65 | V.cmd('command! SReplaceAndSave lua require("nvim-search-and-replace").replace({update_changes = true})') 66 | 67 | V.cmd('command! SReplaceAllAndSave lua require("nvim-search-and-replace").replace_all({update_changes = true})') 68 | 69 | set_n_keymap(config.replace_keymap, ':SReplace', {}) 70 | set_n_keymap(config.replace_all_keymap, ':SReplaceAll', {}) 71 | set_n_keymap(config.replace_and_save_keymap, ':SReplaceAndSave', {}) 72 | set_n_keymap(config.replace_all_and_save_keymap, ':SReplaceAllAndSave', {}) 73 | end 74 | 75 | -- Search and replace all the files in the current directory 76 | -- This function completly ignore the files in config.ignore list 77 | -- @Param opts { update_changes: boolean } 78 | -- ex:- 79 | -- require('nvim-search-and-replace').search_and_replace({ 80 | -- update_changes = true } 81 | -- }) 82 | function M.replace_all(opts) 83 | opts = opts or {} 84 | opts.ignore = {} 85 | M.replace(opts) 86 | end 87 | 88 | -- Search and replace all EXCEPT ignored files 89 | -- @Param opts { ignore: Array, update_changes: boolean } 90 | -- ex:- 91 | -- require('nvim-search-and-replace').search_and_replace({ 92 | -- ignore = { 'dir/**', update_changes = true } 93 | -- }) 94 | function M.replace(opts) 95 | opts = opts or {} 96 | 97 | opts.update_changes = opts.update_changes or config.update_changes 98 | opts.ignore = opts.ignore or config.ignore 99 | 100 | -- get the search query 101 | Ui.input({ prompt = 'Search Query: ' }, function(search_query) 102 | if string.is_empty(search_query) then 103 | return 104 | end 105 | 106 | local search_pattern, options, files = get_maching_results(search_query) 107 | options = options or '' 108 | files = files or '**/*' 109 | 110 | if search_pattern == nil or string.is_empty(search_pattern) then 111 | error('Invalid search query') 112 | return 113 | end 114 | 115 | Ui.input({ prompt = 'Replace with: ' }, function(replace_pattern) 116 | if string.is_empty(replace_pattern) then 117 | return 118 | end 119 | 120 | local wildignore = V.o.wildignore 121 | V.o.wildignore = '' 122 | 123 | for _, ignore_pattern in ipairs(opts.ignore) do 124 | V.opt.wildignore:append(ignore_pattern) 125 | end 126 | 127 | local status, err = pcall(function() 128 | CMD(VIMGREP_SEARCH_PATTERN:format(search_pattern, files)) 129 | end) 130 | 131 | if status then 132 | pcall(function() 133 | if opts.update_changes then 134 | CMD(FILE_SEARCH_UPDATE_PATTERN:format(search_pattern, replace_pattern, options)) 135 | else 136 | CMD(FILE_SEARCH_PATTERN:format(search_pattern, replace_pattern, options)) 137 | end 138 | end) 139 | end 140 | 141 | V.o.wildignore = wildignore 142 | 143 | if not status then 144 | error(err) 145 | end 146 | end) 147 | end) 148 | end 149 | 150 | return M 151 | -------------------------------------------------------------------------------- /lua/nvim-search-and-replace/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Merge content of two table and returns a new table 4 | function M.merge_tables(t1, t2) 5 | for k, v in pairs(t2) do 6 | if (type(v) == "table") and (type(t1[k] or false) == "table") then 7 | M.merge_tables(t1[k], t2[k]) 8 | else 9 | t1[k] = v 10 | end 11 | end 12 | 13 | return t1 14 | end 15 | 16 | return M 17 | --------------------------------------------------------------------------------