1

I wanted to define a mapping for flipping the texts on both sides of a pipe (|) character. For example, substitute

dům|Haus

with

Hause|dům

The substitution command

:s/\([^|]\+\)|\([^|]\+\)/\2|\1/

works fine in normal mode, but I failed to map it to a key combination in a script using

nnoremap <C-I><C-S> <ESC>:s/\([^\|]+)\|(.+)/\2\|\1/<CR>

The output is

|dům|Haus

I also tried using the magic \v option,

nnoremap <C-I><C-S> <ESC>:s/\v([^\|]+)\|(.+)/\2\|\1/<CR>

but the output was the same. The first expression for capturing everything left of the pipe character seems to be wrong. Maybe the pipe symbol is not escaped correctly.

Do I have a misconception on how substitution commands can be used in scripts? What's wrong here?

3

It's just a matter of escaping the pipe character correctly. For those unfamiliar, | is a tricky cat due to its special role as the separator between different commands on the same line. We require careful application of the escape character, \.

Jumping ahead, here's the correct mapping command when using "very-magic mode" (\v):

nnoremap <C-I><C-S> :s/\v([^\|]+)\\|([^\|]+)/\2\|\1/<CR>

The least obvious part is the double escaping of the literal '|' between the two words.

To understand why we need that (and why the others need single-escaping) you need to know that right hand side of the mapping gets processed/parsed twice: once when we submit the nnoremap command and once when we enter the mapped keys. The latter is familiar as it's the same as when we enter and run the substitution directly. It also has rules that differ from the general parsing applied to the map command which unfortunately makes this whole thing a bit non-intuitive as you'll see.

The other critical piece of information needed is that a round of parsing strips off one layer of escapes. (FYI this is not Vim specific it's a general thing you'll encounter almost anywhere you have to deal with escaped strings.)

Okay, we know what we'd enter if we ran the command directly...

s/\v([^|]+)\|([^|]+)/\2|\1/

Specifically, we escape the pipe in the pattern to indicate that it is the literal character and not the alternation operator/atom. But now we need another round of escaping and, as I alluded to before, the processing that will occur when nnoremap is submitted is a general parse not the particular parsing that is done when we run a substitution. Thus, we need to escape every pipe character to prevent their interpretation as a command separator. In addition, we need to re-escape the literal pipe. Otherwise, the stripping of escapes that I mentioned will leave the literal pipe unescaped when the substitution occurs and it will be treated as an alternation atom. <Phew!>

So that give us this:

s/\v([^\|]+)\\|([^\|]+)/\2\|\1/

I don't blame you if you're saying "how the heck am I going to remember this". Fortunately, there's an easy way to see what happens after the first parse: leave off the <CR> in your mapping. When you run it the result of the first parse will be on the command line waiting for you to hit Enter. You can examine it and correct things from there.

5
  • 1
    Thank you for your detailed explanation! I learned something new :) – Hermann Schachner Feb 12 at 19:51
  • I'm now very curious why the non-magic approach didn't work, but nice job – D. Ben Knoble Feb 12 at 20:21
  • 1
    @D.BenKnoble For the (non-very) magic mode they have the same escaping for both the alternation atom and the literal pipe. The alternation atoms need double-escaping. – B Layer Feb 12 at 22:19
  • @HermannSchachner Glad it helped. But I actually wanted to make it even more user friendly. Tell me what you think of the rewrite. I'll revert if you liked the first better. – B Layer Feb 12 at 23:03
  • @BLayer Honestly speaking, I liked the first version best, because it was the most succinct one. However, you could alternatively add the in-depth explanation under a separate "Details" section. – Hermann Schachner Feb 18 at 8:50

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.