I am using the Ack plugin in Vim, which helps me to quickly search for strings in my project. However, sometimes I want to replace all or some occurrences of the found strings. You can do some kind of global search and replace using the Vim arglist like this (source)
:
:args app/views/*/*
:argdo %s/, :expire.*)/)/ge | update
But instead of using args, I would prefer to do a search via Ack and then do the replace in all files that have been found. Is there a way to do it similar to the argdo command?
I've decided to use ack and perl to solve this problem outside of Vim so I could use the more powerful Perl regular expressions instead of the GNU subset. You could map this to a key stroke in your .vimrc.
ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'
Explanation
ack
ack is an awesome command line tool that is a mix of grep, find, and full Perl regular expressions (not just the GNU subset). It's written in pure Perl, it's fast, it has match highlighting, it works on Windows and it's friendlier to programmers than the traditional command line tools. Install it on Ubuntu with sudo apt-get install ack-grep.
xargs
xargs is an old Unix command line tool. It reads items from standard input and executes the command specified followed by the items read for standard input. So basically the list of files generated by ack are being appended to the end of the perl -pi -E 's/pattern/replacement/g' command.
perl -pi -E
Perl is a programming language.
The -p option causes Perl to create a loop around your program which iterates over filename arguments.
The -i option causes Perl to edit the file in place. You can modify this to create backups.
The -E option causes Perl to execute the one line of code specified as the program. In our case the program is just a Perl regex substitution.
For more information on Perl command line options, see perldoc perlrun. For more information on Perl, see http://www.perl.org/.
Now, Vim has this new command cdo that will run the given command to each line of the quickfix list.
So you can use
:Ack pattern
:cdo s/pattern/newpattern/g
I don't believe there's a built in way of doing this, but it should be easy to make one.
What you need to do is create a command that calls a custom function. The function should then use the getqflist() function to get all of the entries in the quickfix list and exe to do the dirty work. Be careful what you pass as an argument!
" Define a command to make it easier to use
command! -nargs=+ QFDo call QFDo(<q-args>)
" Function that does the work
function! QFDo(command)
" Create a dictionary so that we can
" get the list of buffers rather than the
" list of lines in buffers (easy way
" to get unique entries)
let buffer_numbers = {}
" For each entry, use the buffer number as
" a dictionary key (won't get repeats)
for fixlist_entry in getqflist()
let buffer_numbers[fixlist_entry['bufnr']] = 1
endfor
" Make it into a list as it seems cleaner
let buffer_number_list = keys(buffer_numbers)
" For each buffer
for num in buffer_number_list
" Select the buffer
exe 'buffer' num
" Run the command that's passed as an argument
exe a:command
" Save if necessary
update
endfor
endfunction
You could using ack by this way
:args `ack -l User app/`
:argdo %s/, :expire.*)/)/ge | update
Or use ag
:args `ag -l User app/`
:argdo %s/, :expire.*)/)/gec | w
I use MacVim (activated with mvim in a shell). I pipe the results of ack to mvim:
mvim -f $(ack -l $#)
Then in MacVim, I search/replace using bufdo:
:bufdo %s/SEARCH/REPLACE/gce | update
Omit the c option if confirmation is not needed.
Related
I know how to fzf.vim, but I'd like to open from terminal.
Grepping history or viminfo may be achieve thst, but I wonder if there is any smart way.
This is how you can save the list of recent files from vim to a file:
vim -c "call append(0, v:oldfiles)" -c "write vim-oldfiles.tmp" -c exit
Put v:oldfiles (the list of recent files saved in ~/.viminfo) into the first (new and empty at the start) buffer, write the buffer to a file, exit.
Now you can pass the content of file to fzf.
Not exact solution. But you could open a terminal buffer on the lower part of your vim edit like an IDE and use your terminal fzf
However, not sure if this will let you open a file in a new vim tab
I have an zsh autoloaded function called old:
function old(){
vim -c 'redir >> /tmp/oldfiles.txt | silent oldfiles | redir end | q'
sed -i '/NvimTree$/d' /tmp/oldfiles.txt
local fname
fname=$(awk '/home/ && !/man:/ {print $2}' /tmp/oldfiles.txt | fzf) || return
vim "$fname"
\rm /tmp/oldfiles.txt
}
If you're having trouble executing vim on files that have ~ in their path (vim open a new blank file instead of the desired file) because fzf and vim don't expand tilde (~), here's how I do it:
export FZF_DEFAULT_OPTS=$FZF_DEFAULT_OPTS"
--bind 'ctrl-e:execute(vim -c \"execute \\\"edit\\\" expand({})\" >/dev/tty)'
"
It's trial and error, based on this.
Combining some of the other answers, here's a version that does not need a temporary file and writes to stdout (so you can pipe this into another command, or capture the output using $(...)).
vim -e -c "redir >> /dev/fd/100 | for f in v:oldfiles | silent echo substitute(f, \"^\\\\~\", \$HOME, \"g\") | endfor | redir end | q" 100>&1 &>/dev/null
This solution combines elements from other solutions, but with some improvements:
It uses some shell redirection to duplicate stdout to some free fd (100>&1) and then uses /dev/fd/100 to force writing output there. This ensures that vim actually writes to stdout rather than the terminal. Note that this can also be made to work using /dev/fd/1 (but only when omitting redir end for some reason), but then we cannot apply the next point.
It redirects stdout (and for good measure) also stderr to /dev/null, to prevent vim writing some terminal escape codes to stdout on startup, so using a different fd ensures clean output.
It uses vim in "ex" mode (vim -e) to suppress the "Vim: Warning: Output is not to a terminal" output and accompanying delay. [source]
It uses a for-loop to iterate over v:oldfiles to output just the filenames (the oldfiles command used by https://stackoverflow.com/a/70749181/740048 adds line numbers).
It uses a substitute to expand ~ in the filenames returned by vim (making the returned filenames easier to proces. Normally, shells like bash expand ~ in arguments passed to commands, but this happens only for tildes in the command typed, not tildes that result from variables or command substitution. To prevent having to rely on unsafe eval'ing later, better to expand (just) the tildes beforehand.
I also tried using the append / write combo from https://stackoverflow.com/a/60018642/740048, which worked with the /dev/fd/100 trick, but then ended up putting /dev/fd/100 in the list of oldfiles, so I did not use that approach.
I have a complected project hierarchy for my FPGA projects. I have written a passer which examines the "Vivado/ISE" project file and returns a file containing a list of all the source file the project users (I then run ctags over this list).
I would like to be able to search this list of files from vim 7.4 without needing to do a whole recursive search through the FPGA library. I can do this from the linux command line using something like:
cat ./files.txt | xargs grep -Hn my_function
Where ./files.txt contains the list of files I would to search over. However I would like to be able to use vim's internal grep function. I would also like to be able to run this from both Windows and Linux.
Is there an easy way to pass a list of files (contained within a file) to vim's grep ex function?
You should probably use a plugin such as CtrlSF. But if you insist to do it with Vim alone, you can do something like this:
function! Grep(what, where)
exec join(extend(['vimgrep', a:what],
\ map(filter(readfile(a:where), 'v:val !=# "" && filereadable(v:val)'),
\ 'fnameescape(v:val)')))
copen
endfunction
command! -nargs=+ Grep call Grep(<f-args>)
Then you'd just call :Grep /pattern/ filelist, instead of :vimgrep /pattern/ ....
First step, populate the argument list with all the files in your list:
" in UNIX-like environments
:args `cat files.txt`
" in Windows
:args `type files.txt`
Second step, search for pattern in the argument list:
:vim pattern ##
If you have opened all files in Vim—eg with
vim `<files.txt`—then you can search all of them with (e.g.)
:bufdo g/my_function/
If you :set nu you'll get line numbers as per
grep -nHth
Is there a way to limit :Ag output so it always takes one line and doesn't blow up the quickfix window?
At the moment it looks like this and it's awful. I can't see filenames, everything is super slow and just sucks:
Update For the record, I scrolled Quickfix window a bit to illustrate the point better. And while it is usable via :cn :cp, I would like to be able to quickly glance over the results with j k.
Looking over the man page, there does not seem to be any way to limit the output built into Ag itself.
Is there another way of limiting the line length? Actually, you do have the built in "cut" command in Linux, e.g. using it on the shell:
ag --column foo | cut -c 1-80
Limit all lines to 80.
Now we have to make ag.vim execute our specially crafted command, for which the g:agprg exists. So the first thing I thought of is this:
let g:agprg='ag --column \| cut -c 1-80' " doesn't work
The problem with this is that the ag.vim plugin just appends extra arguments to the end, and thus you end up executing something like ag --column | cut -c 1-80 something-i-searched-for. Is there a way to directly "insert" the arguments before the |?
One trick is to use a temporary shell function, like this:
f() { ag --column "$#" | cut -c 1-80 }; f something-i-search-for
Unfortunately, we still can't use this. ag.vim checks whether or not the first word is an actual command. So it complains that no executable by the name of "f()" exists. So my final solution:
let g:agprg='true ; f(){ ag --column "$#" \| cut -c 1-80 }; f'
As true always exists and doesn't do anything except return true, this works!
To your actual screenwidth instead of 80, you could use:
let g:agprg='true ; f(){ ag --column "$#" \| cut -c 1-'.(&columns - 6).' }; f'
I added the magic - 6 here to account for the extra characters Vim itself adds.
ag now supports a --width switch. rg has a similar --max-columns switch.
Assuming you are using this plugin. You should add this to your ~/.vimrc as specified by :h g:ag_qhandler
let g:ag_qhandler = 'copen 1'
However you can probably just do let :g:ag_qhandler = 'cc'. This will print the results at the in the bottom. When you move through the quickfix list via :cnext or :cprev it will print the current result as well.
For more help see:
:h g:ag_qhandler
:h :cope
Changing the geometry of the quickfix window won't help you fix your problem: the window is unusable not because of its size but because your search results are polluted by superfluous matches in minimized files.
Minimized JavaScript or CSS is the frontend development's equivalent of a binary and that kind of file should be ignored by search tools, indexing tools, file navigation tools and even version control tools, sometimes, because they are generally irrelevant.
Adding these lines to your ~/.agignore will make Ag search only in actual source files:
*.min*
*-min*
*_min*
*.min.*
bundle
min
vendor
tags
cscope.*
Adjust that list to your liking.
I frequently send files to Vim from Visual Studio. I have it set up as an external tool with the following parameter:
"+call cursor($(CurLine), $(CurCol))"
However, I also want to be able to call my own function as well. When I'm editing a file from VS I want the window to be large, so I expected to be able to do something like this:
"+call cursor($(CurLine), $(CurCol)); +call Embiggen()"
However, that doesn't work. I've tried a few variations (e.g. , call Embiggen(), etc).
Obviously I could write my own PlaceCursorAndEmbiggen function, but I don't really want to do that. Is there any way to call multiple functions on Vim startup?
Eureka!
Simply pass two strings:
"+call cursor($(CurLine), $(CurCol));" "+call Embiggen()"
Maybe the solution would have been easier to find had you used the alternative, more commonplace syntax: -c "cmd" instead of "+cmd". According to :help -c, you can pass up to 10 of these.
These exact commands can be combined into one using pipe symbol:
"+call cursor($(CurLine), $(CurCol)|call Embiggen()"
. There are much more that can be combined this way, but some like :normal can’t, use #Ingo Karkat’s or your own answer for them. If you are short* on +commands and still don’t want to create a .vim file you can use either :execute
vim -c "execute 'normal! 1' | execute 'normal! 2'"
or (bash/zsh) -S with process substitution:
vim -S <(echo '
normal! 1
normal! 2
')
. Though most of time it is better to just create a .vim file.
* You can pass up to 10 + or -c (they are equivalent and they are not counted separately) and 10 other --cmd, though letter is less useful.
So far, I have been manually refactoring code by using the find-and-replace operation
%s:/stringiwanttoreplace/newstring/g
in vim.
But this is a slow and laborious process if I have stringiwanttoreplace in many files inside a specific directory.
My current/typical slow and laborious process involves a grep:-
grep -rn "stringiwanttoreplace" .
in my terminal to reveal all the locations/filenames where stringiwanttoreplace are; and now that I know which files contain stringiwanttoreplace, I will open each file one-by-one to perform the find-and-replace operation in each file.
Is there a more efficient workflow (in vim) to get this done?
CLARIFICATION: I would prefer a vim-based solution instead of a bash script/one-liner.
Here's the full sequence of commands that I would use:
/stringiwanttoreplace
:vimgrep /<c-r>// **
:Qargs
:argdo %s//newstring/g
:argdo update
In the first line, we search for the target pattern. That populates the last search pattern register (:help quote/), which means that we won't have to type it out in full again.
The :vimgrep command searches the entire project for the specified pattern. Type <c-r>/ as ctlr+r followed by / - this inserts the contents of the last search pattern register onto the command line. The first and last / symbols are delimiters for the search field. The trailing ** tells Vim to look inside every file and directory below the current directory.
At this point, the quickfix list will be populated with search matches from all matching files. :Qargs is a custom command, which populates the argument list with all of the files listed in the quickfix list. Here's the implementation:
command! -nargs=0 -bar Qargs execute 'args ' . QuickfixFilenames()
function! QuickfixFilenames()
" Building a hash ensures we get each buffer only once
let buffer_numbers = {}
for quickfix_item in getqflist()
let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
endfor
return join(values(buffer_numbers))
endfunction
Add that to your vimrc file.
Having run :Qargs, our argument list should now contain all of the files that include our target string. So we can run the substitution command with :argdo, to execute the command in each file. We can leave the search field of the substitution command blank, and it will automatically use the most recent search pattern. If you want, you could include the c flag when you run the substitution command, then you'll be prompted for confirmation.
Finally, the :argdo update command saves each file that was changed.
As #Peter Rincker pointed out, you should ensure that Vim's 'hidden' option is enabled, otherwise it will raise an error when you try to switch to another buffer before writing any changes to the active buffer.
Also, note that the last 3 commands can be executed in a single command line, by separating them with a pipe character.
:Qargs | argdo %s//replacement/gc | update
The :Qargs command is pinched from this answer (by me), which in turn was inspired by this answer by DrAl. A very similar solution was posted by #ib, which suggests to me that Vim should really implement something like :quickfixdo natively.
If you really want to do it in Vim you can follow the suggestions here.
You can call this from within Vim (:!find ...) but you don't need to:
find . -type f | xargs sed -i 's/stringiwanttoreplace/newstring/g'
Fine-tune the file selection with the dozens of parameters described in
man find
(e.g., replace only in HTML files: -name \*.html)
This solution will try to attempt the replacement in all files. You can filter that through grep before, but that is just doing twice the work for no gain.
By the way: sed uses almost the same syntax for regular expressions as Vim (stemming from the same history).
You could open all the files and type
:bufdo :s/stringiwanttoreplace/newstring/g
It performs the search/replace in all your buffers.
You don't need vim to do this, you can use command line tools. Using sed in a loop on the list of files to do this for you automatically. Something like this:
for each in `grep -l "stringiwanttoreplace" *` ;
do
cat $each | sed -e "s/stringiwanttoreplace/newstring/g" > $each
; done
vim7 has recursive grep built-in
:vimgrep /pattern/[j][g] file file1 file2 ... fileN
the result will be shown in a quickfix-window (:help quickfix)
to do the search recursively use the **-wildcard like
**/*.c to search through the current folder and recursively through all subdirectories.