vim: map command with confirmation to key - vim

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.

Related

How to escape a shell command in the rhs of a mapping?

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.)

How to show the command that last executed in vim?

Sometimes when I write a wrong script, I really have no idea why it doesn't have the expected effects. For example, the following script:
nnoremap gr :call Look_For_String()<CR><C-L>
function! Look_For_String()
exe "grep -R " . expand("<cword>") . &dir
endfunction
The function is to search the current word under cursor in directory &dir, but it doesn't do as expected. So I want to know what command it executed.Is that possible?
What I would do in this case is replacing the :execute with :echomsg, re-source the script (:so %), and re-execute.
For more complex situations, you can set the 'verbose' option. With a value of 15, every Ex command is printed. Since the display often interferes with the script's functionality, it's often advisable to redirect the diagnostic output to a file by setting 'verbosefile', too.

How can I execute the current line as Vim EX commands?

Say I'm editing my _vimrc file and I've just added a couple of lines, for instance a new key mapping. I don't want to reload the whole file (:so %) since that will reset a lot of temporary stuff I'm experimenting with. I just want to run the two lines that I'm currently working on.
I'm having no luck trying to copy/paste the lines into the command buffer, since I can't use the put command in there. Is there any way I could run the current line (or current selection) as EX commands?
Summary:
After Anton Kovalenko's answer and Peter Rincker's comment I ended up with these key maps, which either executes the current line, or the current selected lines if in visual mode:
" Execute current line or current selection as Vim EX commands.
nnoremap <F2> :exe getline(".")<CR>
vnoremap <F2> :<C-w>exe join(getline("'<","'>"),'<Bar>')<CR>
To execute the current line as an ex command, you may also use:
yy:#"
This will yank the current line to the "-register and execute it. I don't think it is too much typing.
Executing the line under cursor as an Ex command:
:execute getline(".")
Convenient enough for 2 lines. (I'd figure out something for doing it with regions, but I'm not a vim user). And for currently selected region, the following seems to do the job:
:execute getreg("*")
As commented by Peter Rincker, this mapping can be used for executing the currently selected lines:
:vnoremap <f2> :<c-u>exe join(getline("'<","'>"),'<bar>')<cr>
For that purpose, I have defined the following commands and mappings:
":[range]Execute Execute text lines as ex commands.
" Handles |line-continuation|.
" The same can be achieved via "zyy#z (or yy#" through the unnamed register);
" but there, the ex command must be preceded by a colon (i.e. :ex)
command! -bar -range Execute silent <line1>,<line2>yank z | let #z = substitute(#z, '\n\s*\\', '', 'g') | #z
" [count]<Leader>e Execute current [count] line(s) as ex commands, then
" {Visual}<Leader>e jump to the following line (to allow speedy sequential
" execution of multiple lines).
nnoremap <silent> <Leader>e :Execute<Bar>execute 'normal! ' . v:count1 . 'j'<CR>
xnoremap <silent> <Leader>e :Execute<Bar>execute 'normal! ' . v:count1 . 'j'<CR>
Just after posting this, I found a work-around. I can copy text into the clipboard using "*y, then put that text into the command buffer by using the middle mouse button. This works for me, but is hardly a convenient solution for people without clipboard support, mouse support or just an aversion to removing their hands from the Vim position.
The accepted answer doesn't handle continuation sections. Also, surprisingly, the bar isn't needed, newlines are fine. This will work, first yanking the text into register x:
vno <c-x> "xy:exe substitute(#x,"\n\\",'','g')<cr>
As someone has already mentioned, the only exception are commands that "eat up" newlines. Eg, executing the above mapping on:
:sign define piet text=>> texthl=Search
:exe ":sign place 2 line=23 name=piet file=" . expand("%:p")
will cause vim to to think that the user is trying to define textl as "Search\n:exe ":sign place... etc.
You could also try
:<C-R><C-L><CR>
Per the vim docs, the combination will plop the current line into the command line. From there, hitting enter should do the trick. I realize that this does not handle multiline cases, however it doesn't require a .vimrc and therefore works out of the box.
If you're doing a lot of experimenting (trying things out that you might want to add to your vimrc, I assume?) it might help to do so in a scratch file like experimental.vim so you aren't just relying on your history to know what you're trying out. Now that you have these great mappings, it will be easy to rerun things from experimental or vimrc without sourcing the whole file.
Also (sorry, I can't comment on answers yet, it seems), I tried this mapping of Peter's:
vnoremap <Leader>es :<c-u>exec join(getline("'<","'>"),'<BAR>')<CR>
This works in most cases, but it fails specifically on function definitions.
function! TestMe()
echo "Yay!"
endfunction
This mapping joins the lines into a single string, separated by <BAR> and then execs them.
I'm not entirely sure why, but if I try to do that with a function definition in normal mode:
:exec 'function! TestMe()| echo "Yay!"|endfunction'
-> E488: Trailing characters
After some testing, I've found that it will work with newline separators instead:
:exec "function! TestMe()\n echo 'Yay!'\nendfunction"
:call TestMe()
-> Yay!
So, I've changed my mapping to this:
vnoremap <Leader>es :<c-u>exec join(getline("'<","'>"),"\n")<CR>
I suppose there is a vim or ex reason why the <BAR> method doesn't work on functions (maybe even some setting I have on?), and I'm curious to hear what it is if someone knows.
I don't want to reload the whole file (:so %) since that will reset a lot of temporary stuff I'm experimenting. I just want to run the two lines that I'm currently working on.
If you want to execute a command because you want to refine it before committing it to _.vimrc, then you should launch a Command Line Window for Ex-mode commands with q:.
At launch the Command Line Window is buffered with the contents of the command line history. It is a normal Vim window the contents of which can be edited as any text buffer with the exception of pressing on any line which executes the command on that line. It is very useful when you want to slightly change a long, complex command you wrote earlier and re-run it.
To launch a 'Command Line Window' for search strings press q/.
!! (shorthand for :.!) executes the current line as input to a command, per POSIX ex & vi. You may need to append sh if it is a system command.
Executing !! on a blank line (and omitting sh) is a shortcut for reading a shell command straight into the buffer. By it's nature :.! overwrites the current line while :.r! inserts on the line below.
ls -A | head -n +4
~
~
!sh
Results:
.sh_history
.sh_logout
.kshrc
corelist.txt
~
~
4 lines added; 1 line deleted`
This means there is no need to redirect pipelines to a file and then examine the data to see if the contents are valid. Just execute commands in vi directly and undo if you make a mistake.
Alternately, yanking a line as a named buffer allows you to execute it as an ex command, almost like a macro. You can still edit and undo the line to get it correct instead of trying to edit the : line in command mode.
The functions recommended here are all POSIX and have been supported for over 40 years, so no special vim or other enhanced features are required.
:%s/meep/pEEp/ | g/foo/ s//BAR
foo
grok
meep
~
~
Yank the ex command (line 1, :%s...) into a named buffer / macro.
I just use the label m for "macro".
"myy
or
:1y m
Now execute the named buffer / macro, in command mode, using #:
#m
Results:
:%s/pEEp/pEEp/ | g/BAR / s//BAR
BAR
grok
pEEp
~
~
4 lines changed
But remember that "multiple undo" is not POSIX. undo is only a toggle between undo and redo in a "historically accurate & compliant" ex / vi implementation.
The work-around is to save to a temporary (or valid) file name before executing a questionable edit:
:w $$.tmp
Then just :e! to "reset and reload" if needed.
You can also use :pre (preserve) to make a special temporary backup file prior to making multiple changes.
Then use :reco! % (recover this!) to restore back to that point.
Realize that :preserve creates a snapshot-like file which is deleted as soon as it is rolled back to. It does not matter if you save the edit(s) or not.
Therefore writing your own file (:w ...) and restoring with :e! may still have value because the system will not automatically delete it.
:pre is perfect when you should have ran sudo vi ... or otherwise do not have the necessary permissions - but you only realized the mistake after making several changes. i.e. vi /etc/sudoers instead of sudo vi /etc/sudoers.
^^ NEVER DO THIS! ONLY AN EXAMPLE! USE sudo visudo INSTEAD!
You can get a list of existing recovery files with vi -r and recover one directly with vi -r filename as needed, optionally with something like sudo vi -r filename.
The distinction here is that even though the ":preserved file" has it's own "special" name and path internally, it will :write to the original, intended location when ":recovered ==> /etc/sudoers
Just be sure to use :wq! and not something like ZZ when done with your "recovery" or you will still lose the edits which you tried to save.
By the way, ^R is expected to redraw or repaint the display per POSIX; it is not "undo" in any compliant vi implementation.

Vim: For Multiple Files: Copy all Text, Replace and Paste

I want to do the following for multiple files using Vim:
Copy all text in each file
Replace some text
Paste the copied text at the end of the each file
Replace some other text
Here are my commands for one file:
:%y
:%s/old1/new1/g
:G
:P
:%s/old2/new2/g
Can anybody tell me the syntax to do so? Especially that I'm new to Vim!
I found out that argdo can execute commands on multiple files. I found many examples to use argdo in replacing text, but I couldn't find the syntax to use argdo with :%y, :G, or :P
Thanks.
Like #ib mentioned, I'd do this with ex commands1
:argdo %y | %s/old1/new1/g | $pu | %s/old2/new2/g
There's also a good chance that you might want to operate on exclusive ranges (do the first substitution only on the first part, and the second only on the second):
:argdo $mark a | %co$ | 1,'a s/old1/new1/g | 'a,$s/old2/new2/g
To allow non-matching substitutions, add s///e and add silent! to make operation much faster in the case of many files.
:silent! argdo $mark a | %co$ | 1,'a s/old1/new1/ge | 'a,$s/old2/new2/ge
1 (note that argdo expects an Ex command list by default. You'd use e.g. argdo norm! ggyG to use normal mode commands)
UPD: my Vim-fu is not as strong as #ib's or #sehe's ones, so you might want to use the solutions they suggested instead of mine one.
But, my solution is easier to edit and to debug personally for me (as a Vim apprentice), so, let it be here anyway.
You can add the following temporary function in your vimrc:
function! MyTmpFunc()
:%y
:%s/old1/new1/g
normal! G
normal! P
:%s/old2/new2/g
endfunction
Then restart Vim with the files you need to affect (something like vim myfile1.txt myfile2.txt myfile3.txt), and execute the following command:
:argdo call MyTmpFunc()
That's what you described in your question: function MyTmpFunc() will be called for each argument given to Vim.
Now you can delete MyTmpFunc() from vimrc.
Be also aware with :bufdo - it calls some command for each opened buffer. There is also :windo, which executes command for each window, but personally I found :bufdo the most useful.
Also please note that you don't have to create temporary function if you need to execute just a single command in the each buffer. Say, if you need just to replace "old1" to "new1" in the each buffer, then you can execute the following command:
:bufdo %s/old1/new1/g
and that's it.

redirection to a file of a search in Vim

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

Resources