├── .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 |
--------------------------------------------------------------------------------