I'm trying to use ag (silver searcher) within Vim (https://thoughtbot.com/blog/faster-grepping-in-vim). I'm trying to be a bit more fancy and create a custom command to only search through files matching certain file extentions. I.e. use the -G regex pattern that ag supports.
This works except that trying to create a mapping for this has shown me that I don't have a good handle on quoting in this context.
The linked article shows a mapping such as this:
nnoremap K :grep! "\b<C-R><C-W>\b"<CR>:cw<CR>
What I'd like is a second one which just checks the C files in a project.
Something like this except that this doesn't work because I can't figure out the required quoting:
nnoremap ,k :grep! "\b<C-R><C-W>\b" . '-G \.+(h<Bar>c)$'<CR>:cw<CR>
Welcome to this particularly confusing hell.
Let's start with the right command to use in the shell:
$ ag "\bfoo\b" -G '\.+(h|c)$'
Assuming we have the following line in our vimrc, as instructed in the linked post:
set grepprg=ag\ --nogroup\ --nocolor
If we try to use that -G flag as-is in :help :grep:
:grep! "\bfoo\b" -G '\.+(h|c)$'
we get this, from Vim:
:!ag --vimgrep "\bfoo\b" -G '\.+(h 2>&1| tee /var/folders/_d/bdwh8scx4hb1v2w__l0x6nk19ndqk1/T/vcaYrBc/6
this, from bash:
/bin/bash: -c: line 0: unexpected EOF while looking for matching `''
/bin/bash: -c: line 1: syntax error: unexpected end of file
and finally this, from Vim:
E426: tag not found: c)$
which tells us a) that the pattern is cut short before it reaches ag and b) that some part of the command even reaches Vim after the external command is executed, which is not desirable at this point.
The problem is the | in h|c, which is interpreted by Vim as a command separator so we send one part of the command to the shell and another part to Vim and everything is broken because both programs choke on what we send to them.
This is easy enough to fix, by escaping the bar:
:grep! "\bfoo\b" -G '\.+(h\|c)$'
And it works!
But not in a mapping:
nnoremap ,k :grep! "\b<C-r><C-w>\b" -G '\.+(h\|c)$'<CR>:cw<CR>
where we need yet another backslash:
nnoremap ,k :grep! "\b<C-r><C-w>\b" -G '\.+(h\\|c)$'<CR>:cw<CR>
because \| is expanded to | in this context so a second backslash is required to make sure we get an actual \|. See :help map-bar.
And we are done.
Note that you can do --cc as an alternative to -G '\.+(h|c)$'.
Related
I am trying to have to custom commands in vim as follows:
The first command just runs a bash script. this works fine on its own.
:command Build :!./build-linux.sh
Now I want to use this command in another such as this:
:command BuildAndDebug :Build | :call vimspector#Launch()
This command is supposed to run the bash script and then launch a debugger.
When I try to do this, I get this error back:
E488: Trailing characters: :Build | :call vimspector#Launch()
I can't seem to find the trailing characters here. I suspect the :Build is the cause of this because it does not know where the command ends. However I can't seem to get it to work at all.
You need to add the -bar attribute to your command definition if you want to be able to "chain" other commands after it with |:
:command -bar Build :!./build-linux.sh
This is described in chapter 40 of the user manual: :help usr_40, and documented under :help :command-bar.
I want to achieve the following things in vi :
Remove first few columns
Remove lines starting with specific words
Remove everything after first word.
I have the following command with respect to above requirements
:%s/new page //g to remove first two columns.
:g/abc/d , :g/xyz/d , :g/ddd/d to remove lines starting with specific words.
:%s/ .*//g to remove everything after first word.
Overall I want to run the following commands :
:%s/new page //g
:g/abc/d
:g/xyz/d
:g/ddd/d
:%s/ .*//g
How can I execute all the above commands in one single command.
I have tried | but it did not worked.
:g/abc/d|:g/xyz/d|:g/ddd/d
I am getting the following error :
E147: Cannot do :global recursive
How can I achieve this. I want to execute all commands in one single command.
Thanks
You can put all those commands in a function:
function! AllMyCommands()
%s/new page //g
g/abc/d
g/xyz/d
g/ddd/d
%s/ .*//g
endfunction
and call it either directly:
:call AllMyCommands()
or via a custom command:
command! Foo call AllMyCommands()
:Foo
or via a custom mapping:
nnoremap <key> :<C-u>call AllMyCommands()<CR>
<key>
I have tried | but it did not worked.
:g/abc/d|:g/xyz/d|:g/ddd/d
In general, commands can be executed sequentially, separated by |, but there are exceptions, as :help :bar tells:
These commands see the '|' as their argument, and can therefore not be
followed by another Vim command:
[...]
:global
[...]
As a workaround, you can wrap them in :execute:
:exe 'g/abc/d'|exe 'g/xyz/d'|g/ddd/d
But putting them into a :function, as per #romainl's answer, is probably better.
I am trying to create a simple mapping in vim to execute a shell command. The command I want to execute is this:
ruby -e "Dir.glob('./spec/*_spec.rb').each {|f| require f}"
which works fine when I run it at the command line.
However, if I run the following in vim:
nmap ,t :!ruby -e "Dir.glob('./spec/*_spec.rb').each {|f| require f}"<cr>
I get the error:
E492: Not an editor command: require f}"<cr>
Note: I'm on windows, if that's relevant.
What am I doing wrong?
Bonus: How can alter the above command so that it does not depend on the current file being in the directory containing "spec"? Ideally, if the current file's directory did not contain "spec", it would check the parent directory, and so on, recursively, until it found a directory containing "spec". At that point it would run the command with "." replaced by the directory it found in my code above.
Final Solution
Based on Ingo's answer, my final solution was this:
nnoremap ,tt :call RunAllMinitestSpecs()<cr>
function! RunAllMinitestSpecs()
let l:dir = finddir('spec', '.;')
let l:dir = substitute(l:dir, '\', '/', 'g') " so it works on windows
let l:ruby_cmd = "\"Dir.glob('" . l:dir . "/*_spec.rb').each {|f| require f}\""
exe('!ruby -e ' . l:ruby_cmd)
endfunction
The | separates Vim commands; for Vim, the mapping ends at the first |, and Vim tries to interpret the remainder as a command (which obviously fails). You need to either escape via \ or (better) use the special <Bar> notation in mappings:
:nnoremap ,t :!ruby -e "Dir.glob('./spec/*_spec.rb').each {<Bar>f<Bar> require f}"<cr>
Tips
You should use :noremap; it makes the mapping immune to remapping and recursion.
Bonus answer
You can get an upward directory search (:help file-searching) via finddir(), then pass the result to glob(). See
:echo finddir('spec', '.;')
(I would then move the implementation into a :function, and invoke that from the mapping. This would have also avoided the | escaping problem.)
I've written a few macros in my .vimrc for the version control system I'm using (Perforce) (please don't suggest the perforce plugin for vim, I tried it and I don't like it). They all work fine except the revert macro, which breaks due to a confirmation prompt (which I need so I don't accidentally fat-finger my changes away). It currently looks like this:
map <F8> :if confirm('Revert to original?', "&Yes\n&No", 1)==1 | !p4 revert <C-R>=expand("%:p")<CR><CR><CR>:edit<CR> | endif
This causes bash to complain when vim tries to load the file:
bin/bash: -c: line 0: syntax error near unexpected token `('
Looking at the buffer bash sees, it looks like the error is that vim sends it everything after the first pipe, not just the part meant for bash. I tried a few alternatives but I can't seem to make it work. I've got it to show confirm dialog correctly when I removed the pipes and endif (using shorthand if), but then vim complains after the user gives a response.
I think you want something along these lines:
:map <F8> :if confirm('Revert to original?', "&Yes\n&No", 1)==1 <Bar> exe "!p4 revert" . expand("%:p") <Bar> edit <Bar> endif<CR><CR>
Remember that :map is a dumb sequence of keystrokes: what you're mapping F8 to has to be a sequence of keystrokes that would work if typed. A <CR> in the middle of the :if statement doesn't mean ‘and press Enter when executing the command at this point if the condition is true’; it means ‘press Enter here when in the middle of typing in the :if command’, which obviously isn't what you want.
Building it up a piece at time, from the inside out:
There's a shell command you sometimes want to run.
That shell command needs to be inside an :if to do the ‘sometimes’ bit, and so have an :endif following it.
After a literal ! everything following is passed to the shell, including | characters which normally signify the start of another Vim command. That's reasonable, because | is a perfectly good character to use in shell commands. So we need some way of containing the shell command. :exe can do this; it executes the supplied string as a command — and its argument, being a string, has a defined end. So the general form is :if condition | exe "!shell command" | endif.
Your shell command has an expression in it. Using :exe makes this easy, since you can simply concatenate the string constant parts of the command with the result of the expression. So the command becomes :exe "!p4 revert" . expand("%:p") — try that out on its own on a file, and check it does what you want before going any further.
Putting that inside the condition gives you :if confirm('Revert to original?', "&Yes\n&No", 1)==1 | exe "!p4 revert" . expand("%:p") | edit | endif — again try that out before defining the mapping.
Once you have that working, define the mapping. A literal | does end a mapping and signify the start of the next Vim command. In your original the mapping definition only went to the end of the condition (check it with :map <F8> after loading a file) and the !p4 part was being run immediately, on the Vim file that defines the mapping! You need to change each | in your command into <Bar>, similarly to how each press of Enter in your command needs writing as <CR>. That gives you the mapping above. Try it by typing it at the command line first, then do :map <F8> again to check it's what you think it is. And only then try pressing F8.
If that works, put the mapping in your .vimrc.
Use of the pipe to string multiple vim commands together is not particularly well-defined, and there are numerous eccentricities. Critically, (see :help :bar) it can't be used after a command like the shell command :! which sees a | character as its argument.
You might find it easier to use the system() function.
E.G.
:echo system("p4 revert " . shellescape(expand("%:p")))
The shellescape() wrapper is useful in case you have characters like spaces or quotes in the filename (or have cleverly named it ; rm -rf ~ (Don't try this at home!)).
In the interest of creating more readable/maintainable code, you may want to move your code into a function:
function Revert()
if confirm('Revert to original?', "&Yes\n&No", 1)==1
return system("p4 revert " . shellescape(expand("%:p")))
endif
endfunction
which you would access by using the :call or :echo command in your macro.
My problem is simple. I search a specific pattern in a file (let's say label in a Tex file)
:g/label/#
but there are lots of occurrences. So I'd like to redirect this output to another file to be able to work easily with it.
Do you have a trick or a command that I don't know?
it's not clear from the original post what you mean by "work easily with it" but it's often useful to see and quickly jump between all of the matches in a buffer without "extracting" the matches to a separate buffer.
vim has an internal grep built in. your example would be something like this (in vim, % denotes the current file)
:vimgrep /label/ %
This will take you to the first occurrence and report how many matches there were. What's cool is that you can look at all of the matches listed by opening up the quickfix error list using
:cope
Now you can just scroll around and press enter on a line to jump to the exact position of the match.
The quickfix error list is exactly the same buffer you use if you run make from inside vim and your compiler throws errors: it gives you a list of what and where the errors are.
After you've jumped to one location pointed by quickfix, you can go to forwards and backwards in the list via :cn and :cp. :ccl closes the error list.
You can also expand your "error" list via :vimgrepa /newpattern/ % or :vimgrepadd
The (documented) caveat is that vim's internal grep is slower than most native grep implementations (but you do get it "for free" in windows, for example). If you do have a grep installed, you can use :grep instead of :vimgrep for similar results.
quoting :help grep
Vim has two ways to find matches for a
pattern: Internal and external. The
advantage of the internal grep is that
it works on all systems and uses the
powerful Vim search patterns. An
external grep program can be used when
the Vim grep does not do what you
want.
The internal method will be slower,
because files are read into memory.
The advantages are:
- Line separators and encoding are automatically recognized, as if a file
is being edited.
- Uses Vim search patterns. Multi-line patterns can be used.
- When plugins are enabled: compressed and remote files can be searched.
You can also use the location list if you're already using the error list for dealing with compilation errors. just add l (for location) to the beginning of the grep command (:lvimgrep,:lvimgrepa :lgrep, :lgrepa) and use :lopen :ln :lp :lcl instead of the :c* ones.
For more commands consult
:help grep
:help quickfix-window
:help quickfix
:help quickfix-error-lists
:redir > matches.txt|execute 'g/foo/#'|redir END
See :h :redir, you can also redirect to registers, variables, the clipboard etc.
What you're doing is essentially 'grep -n label file' from command line. So you can run that command and > it into a file easily enough.
The derivation of 'grep' is even from basically the same source.
I've gotten this of the net at some point:
function GoToLine(mainbuffer)
let linenumber = expand("<cword>")
silent bd!
silent execute "buffer" a:mainbuffer
silent execute ":"linenumber
silent nunmap <Enter>
endfunction
command -nargs=1 GoToLine :call GoToLine(<f-args>)
function GrepToBuffer(pattern)
let mainbuffer = bufnr("%")
silent %yank g
enew
silent put! g
execute "%!egrep -n" a:pattern "| cut -b1-80 | sed 's/:/ /'"
silent 1s/^/\="# Press Enter on a line to view it\n"/
silent :2
silent execute "nmap <Enter> 0:silent GoToLine" mainbuffer "<Enter>"
" silent nmap <C-G> <C-O>:bd!<Enter>
endfunction
command -nargs=+ Grep :call GrepToBuffer(<q-args>)
Put it in your .vimrc, then :Grep Foo
Requires external grep program to work properly.
(Just an idea -- untested.)
You can delete all the lines with your pattern in it, write to another file, and undo the delete.
:g/label/d
:w matches
u