How can I chain vim commands when one expects a user input - vim

As a minimal example, say I'd like to search in the current buffer then save it.
something like / [user input...]
then w
how would you chain that in a single command ?
It's pointless in my example, but the real situtation is :
:GoRename [user input] <CR>
then :GoBuildTags '' <CR>
And something like that doesn't work :
nnoremap <leader>rr :GoRename <bar> :GoBuildTags ''<CR>

The real situtation is: :GoRename [user input] <CR> then :GoBuildTags '' <CR>
My recommendation here is that you create a new user-command such as :GoRenameBuildTags that executes the two commands, passing its argument to the first command.
You can usually chain commands together using |, but that would require that the :GoRename command was declared with -bar, which is not really the case... So you'll need to wrap it in a :execute so that you can separate it from the second command.
So you can define the command as follows:
command! -nargs=? -complete=customlist,go#rename#Complete
\ GoRenameBuildTags
\ execute "GoRename" <q-args>
\ | GoBuildTags ''
I'm using here the same -nargs and -complete as the original GoRename definition, since you're forwarding the passed argument to that command, it makes sense to match it.

Related

Vim nested execute commands syntax

I am trying execute a nested execute command within a vimscript. I know that this command works in ex mode:
g/\(^\n\|\%1l\).\_.\{-}\n$/execute "normal! vap:call MCformat()\<cr>"
I want to be able to run that command from within a script. I have tried a number of permutations of the following code but can't get it to work.
function! RunMCformat()
silent! execute "normal! g/\(^\n\|\%1l\).\_.\{-}\n$/execute \"normal! vap:call MCformat\(\)\<cr>\""
endfunction
Probably I am not escaping the string properly but I don't know where I am going wrong.
Because of the double quotes, you'd have to escape (i.e. double) the backslashes inside the /.../ pattern definition. However, the biggest problem is the first :normal!; :g[lobal] is an Ex command. So, you're lucky, you can just prepend :silent! (which invokes Ex commands like :global), are you should be done; no nested :execute is necessary:
function! RunMCformat()
silent! global/\(^\n\|\%1l\).\_.\{-}\n$/execute "normal! vap:call MCformat()\<cr>"
endfunction
In general, I would avoid nesting of :execute; that's not readable in any case. Rather, extract parts of your code into a function / custom command (in which you can use :execute), and invoke that.

Vi multiple command in one line

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.

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

Vim multiple chain commands, first one uses range

Some Vim functions work on a range:
:'<,'>TOhtml
What is the syntax for the first command taking a range, and the latter commands pipe the result?
In the comments on the wiki it suggests a plugin to allow the range to be run on by all the commands; but here I only need the first argument to handle the range.
# These are the commands I am attempting to chain
:'<,'>TOhtml
:w! ~/mylink
:q!
# The last two can chain or be one command
:w! ~/mylink | q!
:wq! ~/mylink
# But these fail
:'<,'>TOhtml | wq! ~/mylink
:execute "'<,'>TOhtml" | "wq! ~/mylink"
Using execute is the way to go, but you only have to quote the command for :execute, not the second one.
That is, replace:
:execute "'<,'>TOhtml" | "wq! ~/mylink"
With this:
:execute "'<,'>TOhtml" | wq! ~/mylink
ClothSword, you are not far off the mark. Depending on your VIM settings, there are three expressions that could potentially be used to chain multiple commands on a single line: |, \| and <bar>, as in:
:echom "test 1" | echom "OK"
:echom "test 2" \| echom "OK"
:echom "test 3" <bar> echom "OK"
The way to test which one of them would work for you, would be to run all three of the above commands, followed by :messages. In the output, you should see error messages for the commands that didn't work: Invalid exprecion, Undefined variable, etc... the actual error message is irrelevant. While, for the command that did work, you will see two lines of the output, similar to:
test [number]
OK
There are a couple of pitfall to watch out for when using command chaining:
| behaves differently to what I described above when used to chain multiple system commands, eg: :read !ls | wc
care must be taken when used with :g, :s and :map commands as it may not do what you expect, eg: :%g/foo/p|>, :%s/foo/bar/|> or :nmap 10\| map \ l
NOTE: You could also use <NL> in the same way you use | (can be inserted with Ctrl-V Ctrl-J, which will output ^#). However, this usage is not recommended as it is more inline with chaining external commands.
SEE ALSO: If you want to get a full picture on how command chaining works in VIM then I would recommend you read VIM's manual on :bar (:help :bar) and have a look at the b flag of the cpoption (:help cpoption).

vim: map command with confirmation to key

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.

Resources