9

Here is the list of experiments I performed that shows that <Bar> is treated as literal <Bar> in some commands but it is treated as | in the :command command.

I am trying to understand why or what in the documentation dictates that <Bar> should be treated as | in the :command command.

Case 1: :! and <Bar>

Enter this command-line mode command in Vim:

:!uname <Bar> grep i

We get this error:

/bin/bash: Bar: No such file or directory

This error occurred because the following shell command was invoked: uname < Bar > grep i and there is no file named Bar to redirect the standard input of uname to.

We see that the string <Bar> was literally sent to the shell.

Case 2: :echo and <Bar>

Enter this command-line mode command in Vim:

:echo 'hi' <Bar> echo 'bye'

We get this error:

E121: Undefined variable: Bar
E15: Invalid expression: 'hi' <Bar> echo 'bye'

Once again <Bar> was literally used as argument in the :echo command.

Case 3: :command, :! and <Bar>

Enter these command-line mode commands in Vim:

:command! A !uname <Bar> grep i
:A

I get this output:

Darwin

You may get Linux in your output.

The argument <Bar> was interpreted as |.

Case 4: :command, :echo and <Bar>

Enter these command-line mode commands in Vim:

:command! A echo 'hi' <Bar> echo 'bye'
:A

We get this output:

hi
bye
Press ENTER or type command to continue

Again, the argument <Bar> was interpreted as |.

Question

Why is <Bar> treated as 5-character literal argument in case of :!, :echo, etc. but it is treated as | in :command? I searched the documentation but I can't seem to find anything.

The documentation does mention that <Bar> can be treated as | in the :map and some other commands, but it never mentions anything about it being treated as | in the :command command.

Here are some excerpts from the documentation that one can find by entering :helpgrep <Bar>:

  1. This talks about usage of <Bar> in the :map command. See :help :bar.

     There is one exception: When the 'b' flag is present in 'cpoptions', with the
     ":map" and ":abbr" commands and friends CTRL-V needs to be used instead of
     '\'.  You can also use "<Bar>" instead.  See also |map_bar|.
    
  2. This talks about the usage of many keycodes including <Bar> in :map command and a few other commands but no mention of the :command command. See :help keycodes.

                                             *key-notation* *key-codes* *keycodes*
     These names for keys are used in the documentation.  They can also be used
     with the ":map" command (insert the key name by pressing CTRL-K and then the
     key you want the name for).
    
     ...
    
     <Bslash>        backslash               \        92     *backslash* *<Bslash>*
     <Bar>           vertical bar            |       124     *<Bar>*
     <Del>           delete                          127
    
     ...
    
     For mapping, abbreviation and menu commands you can then copy-paste the
     examples and use them directly.  Or type them literally, including the '<' and
     '>' characters.  This does NOT work for other commands, like ":set" and
     ":autocmd"!
    
  3. This one also talks about usage of <Bar> in the :map command. See :help map-bar.

                                                         *map_bar* *map-bar*
     Since the '|' character is used to separate a map command from the next
     command, you will have to do something special to include  a '|' in {rhs}.
     There are three methods:
        use       works when                    example      ~
        <Bar>     '<' is not in 'cpoptions'     :map _l :!ls <Bar> more^M
        \|        'b' is not in 'cpoptions'     :map _l :!ls \| more^M
        ^V|       always, in Vim and Vi         :map _l :!ls ^V| more^M
    
     (here ^V stands for CTRL-V; to get one CTRL-V you have to type it twice; you
     cannot use the <> notation "<C-V>" here).
    
  4. Again, this talks about the :map command only. See :help usr_40.

     SPECIAL CHARACTERS
    
     The ":map" command can be followed by another command.  A | character
     separates the two commands.  This also means that a | character can't be used
     inside a map command.  To include one, use <Bar> (five characters).  Example:
     >
             :map <F8> :write <Bar> !checkin %:S<CR>
    

How can we explain the treatment of <Bar> as | in the :command command then?

  • 3
    Kudos for making a good effort to find the answer yourself in the docs. – B Layer Aug 11 at 19:21
  • 1
    I suspect the answer to this is going to turn out to be that it’s not documented, due to oversight. – Rich Aug 16 at 7:37
3
+50

So I went digging for this one and as far as I can tell this behavior has been present ever since the user-defined command feature was introduced in Vim 5.2.

Initially I thought this was present because a user comand takes its arguments from <...> special sequences, such as <args> or <bang>, which makes it so that it takes <lt> to escape a literal <. So I thought the code would use the normal expansion of <...> special sequences to handle <lt>. But looking at the code, that's not really the case, since special sequences are handled quite early, but <lt> is only handled when the command is actually expanded, which is when the other sequences such as <args> and <bang> are actually replaced.

There is one behavior of user-defined commands that does depend on this feature, and it's the usage of <SID> to be able to run script-local functions from the same script in which the command is defined.

If you'd like to follow along in the code:

  • When a user-defined command is defined, replace_termcodes() is called very early in that definition.

  • The code in replace_termcodes() is the one that recognizes the <SID> sequence and replaces it with an appropriate <SNR> sequence including the unique id of the script being sourced.

  • That same code block is handling all special <...> sequences, including <Bar>, but also <C-X> or <Esc>, etc.

  • That code path can't really be disabled by passing additional arguments. Whenever &cpo doesn't include < (which means that <...> sequences are enabled), the code in replace_termcodes() will replace all special sequences, including <SID> but also all the others.

That <SID> is allowed in a user-defined command is actually documented, right at the end of :help :command (scroll down to the very end) you'll see (emphasis mine):

When defining a user command in a script, it will be able to call functions local to the script and use mappings local to the script. When the user invokes the user command, it will run in the context of the script it was defined in. This matters if <SID> is used in a command.

So, while :command doesn't really need to handle sequences such as <Bar>, since using | is perfectly acceptable in a command body (unlike in :map), I guess it was found that it was easier to just let those sequences be expanded in a :command replacement text. Unfortunately, this didn't seem to be documented in Vim's help docs, but it seems that this behavior was intentionally introduced (probably as part of supporting use of <SID>) and that it was present since user-commands were first introduced in Vim 5.2.

| improve this answer | |
  • There's obviously some speculation in my answer, but I think it's probably close enough to the original intent... I took a look at Vim 5.3 source code (couldn't really find 5.2 anywhere, even in official repos) and I found two problems with my argument: First, it seems to use \< in a :command replacement to escape a < instead of <lt>, even though :map uses <lt>. Second, it doesn't really support <SID> at all, that was introduced later. So I'm a bit puzzled as to why it would originally call replace_termcodes(), since the <SID> explanation doesn't work there... – filbranden Aug 17 at 0:02
  • 1
    Either way, this is excellent detective work. +1 – Rich Aug 17 at 0:13

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.