4

I'm had some trouble defining a mapping in my .vimrc. I want <leader>sv to source my .vimrc, but first I want to write, if I'm currently editing it.

Goal

The intention of the code is this:

if expand('%:p') ==? expand($MYVIMRC)
    write
endif
source $MYVIMRC<cr>

Attempt 1

I tried writing my commands inline with pipes:

nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) | write | endif | source $MYVIMRC<cr>

But I get this error when I load it:

Error detected while processing /Users/jack/.vimrc:
E580: :endif without :if:  endif

Attempt 2

I also tried:

let s:source_vimrc='if expand('%:p') ==? expand($MYVIMRC) | write | endif | source $MYVIMRC'
nnoremap <leader>sv :exec(s:source_vimrc)<cr>

which got me:

E15: Invalid expression: 'if expand('%:p') ==? expand($MYVIMRC) | write | endif | source $MYVIMRC'

Attempt 3 (working)

The only way I can get it to work is using <cr> to simulate typing the whole thing out across multiple lines:

nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) <cr> :write <cr> :endif <cr> :source $MYVIMRC<cr>

Attempt 4 (sort of?)

nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) <bar> write <bar> endif <bar> source $MYVIMRC<cr>

That does it, but leaves me with Press Enter or type command to continue instead of returning to normal mode.

Questions

Do mappings not support pipes for multiple commands? Or do ifs not? Why didn't the first one work like I expected?
A: Because the pipes are interpreted as new commands in the file. Use <bar> to get them written to the command line.

Why doesn't #4 return me to normal mode? When I type in the command (with |s), it does.

Note: I'm open to suggestions for better ways to accomplish this, too.

  • @SatoKatsura You're right. Fixed, retested, and edited the question. Thanks. – Jacktose Sep 16 '16 at 22:18
  • 2
    To avoid Press Enter or type command to continue, make your mapping a <silent> one. You can also increase your cmdheight. See :h hit-enter. – VanLaser Sep 16 '16 at 22:49
  • @VanLaser Ah, thanks. Now that I understand the problem, I can fix it by just taking out the extra spaces in the command. Want to make that an answer? – Jacktose Sep 16 '16 at 23:31
  • I only answered some small part of it; normally I would recommend the function-based approach (where possible). Perhaps, you could try to make an answer with the "final" working solution. – VanLaser Sep 17 '16 at 0:08
3

This is where <expr> mappings are useful. From :h <expr>

                        *:map-<expr>* *:map-expression*
If the first argument to one of these commands is "<expr>" and it is used to
define a new mapping or abbreviation, the argument is an expression.  The
expression is evaluated to obtain the {rhs} that is used.  Example: >
    :inoremap <expr> . InsertDot()
The result of the InsertDot() function will be inserted.  It could check the
text before the cursor and start omni completion when some condition is met.

You can just use expand('%:p') ==? expand($MYVIMRC) in a ternary expression to find out if you want to write or not, and the concatenate that with source ($MYVIMRC). Try this:

nnoremap <expr> <leader>sv (expand('%:p') ==? expand($MYVIMRC) ? ":w \|" : ":")."source $MYVIMRC<cr>"

If you run this in your .vimrc, it will evaluate to

:w | source $MYVIMRC

Otherwise, it will evaluate to

:source $MYVIMRC

This is assuming you want to source your vimrc either way (which I assume you do from the mappings you have shown). If not, you could just do

nnoremap <expr> <leader>sv expand('%:p') ==? expand($MYVIMRC) ? ":w \| source $MYVIMRC<cr>" : ""
  • Aha! Now that's why I added the line about being open to other suggestions. I didn't know <expr> or ternary operators were options. That's clearly a better way to do it. – Jacktose Sep 17 '16 at 3:57
2

DJMcMayhem's answer is better than what I was trying to do, but for posterity's sake, here is the information I was asking for. Short answers:

  1. Use <bar> or \| in lieu of | in a map.
  2. Don't print overlong lines to the command line.

Mistake 1: hitting the bars

Pipes/bars (|) separate multiple commands on one line on the command line, but they also separate multiple commands on one line in a script file. So my original attempt:

nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) | write | endif | source $MYVIMRC<cr>

is parsed as these four separate commands:

:nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC)
:write
:endif
:source $MYVIMRC<cr>

The first two are valid, but not what I want. The third, endif, is not valid without its if buddy, and the fourth is a legal command, but points to a nonexistent file. Trying to parse those commands is what generates the errors I quoted.

Swapping the |s for <cr>s works around this problem by making the map print out each line as if I'd entered them separately:

:if expand('%:p') ==? expand($MYVIMRC)
:  write
:endif
:source $MYVIMRC

The direct solution is to use <bar> or \| in place of | in the map. That causes it to print a | at runtime, giving the expected behavior.

Credit to @SatoKatsura.


Mistake 2: sesquipedalianism

With <bar> in place of | it's like this:

nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) <bar> write <bar> endif <bar> source $MYVIMRC<cr>

which works fine. However, it prints 71 characters to the command line, and I had my vim window squeezed to half width when I was testing. It turns out that entering a command longer than one line leaves the CLI on a Press Enter or type command to continue prompt.

There are a few ways to avoid this:

  1. Use the <silent> argument to suppress all output. However, I wouldn't see any errors if when I mess up my .vimrc.
  2. Make the window wider.
  3. Increase the value of cmdheight to handle the extra lines.
  4. Shorten the string. There are some spaces that could be removed.
  5. Separate commands with <cr> to print multiple shorter lines.
  6. As DJMcMayhem showed, leave the logic to the script and print only the necessary commands, which are quite short.

Credit to @VanLaser.

  • 1
    Nice self answer, there's some good stuff in here! As a side note, you can also use \| in place of <bar> (which I used in my answer) – DJMcMayhem Sep 17 '16 at 6:13
  • I sure learned a lot more than I would have if it had worked right the first time! I'll slip \| into this answer. – Jacktose Sep 17 '16 at 6:15
0

You can use pipes for mappings, but I would recomend just wrapping it all in a function. Something like:

nnoremap <leader>sv :call SourceVimrc()<CR>

function! SourceVimrc()
    if fnamemodify('%', ':p') ==? expand($MYVIMRC)
        write
    endif
    source $MYVIMRC<cr>
endfunction
  • I tried exactly that, (and learned about function! in the process), but the source line throws an error because it redefines the function while the function is still in use. Oh wait, I could move source to the mapping .... – Jacktose Sep 16 '16 at 21:36
  • 1
    You're confusing expand() with fnamemodify(). Try expand('%:p', 1) instead of expand('%', ':p'). – Sato Katsura Sep 16 '16 at 21:51
  • Okay, it worked that way: nnoremap <leader>sv :call SourceVimrc()<cr>:source $MYVIMRC<cr>. But I'm still separating commands with <cr> rather than |, so we're back to a variation on my original question. – Jacktose Sep 16 '16 at 21:52
  • 1
    @P1h3r1e3d13 Use <bar> instead of | (or <cr>) as separators. – Sato Katsura Sep 16 '16 at 21:53
  • @SatoKatsura Oh, because | is interpreted as a new command in the file. Okay, so now my original works: nnoremap <leader>sv :if expand('%:p') ==? expand($MYVIMRC) <bar> write <bar> endif <bar> source $MYVIMRC<cr>, but after activating it, I get Press Enter or type command to continue. Why doesn't it just go back to normal mode? (It does when I type it into the command line with pipes.) – Jacktose Sep 16 '16 at 22:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.