6

I want to remap <C-i>*character* to i*character*<Esc> for easy inserting of single characters. This is what I've written:

let printable_characters = [ '!', '"', '#', '$', '%', '&', '''', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']
for val in printable_characters
    silent! execute "nnoremap <C-i>" . val . " i" . val . "<Esc>"
    silent! execute "nnoremap <C-a>" . val . " a" . val . "<Esc>"
    unlet val
endfor

When I try to source it by executing :source %, the following happens:

  1. It will complain about how I forgot the endfor
  2. Once I press return, the file will look like this:

    let printable_characters = [ '!', '"', '#', '$', '%', '&', '''', '(',          ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']
    for val in printable_characters
        silent! execute "nnoremap <C-i>" . val . " i" . val . "<Esc>"
        silent! execute "nnoremap <C-a>" . val . " a" . val . "<Esc>"
        unlet val
        silent! execute "nnoremap <C-a>" . val . " a" . val . "<Esc>"
        unlet val
        endfor
    endfor
    
8

There is a much better way of accomplishing this than brute forcing it. You can use these simple expr mappings instead:

nnoremap <expr> <c-i> "i" . nr2char(getchar()) . "<Esc>"
nnoremap <expr> <c-a> "a" . nr2char(getchar()) . "<Esc>"

Here is how they work:

  • nnoremap <expr> <c-i> - Create a mapping for Ctrli with the <expr> flag. This flag tells vim to interpret all of the characters after <c-i> as an expression when the mapping is triggered, which means you can use functions, variables, operators and the like.
  • "i" . nr2char(getchar()) . "<Esc>" - A simple expression which concatenates "i", a single character given by the user, and "<Esc>". getchar returns a number representing the pressed key, so nr2char is used to convert it to a string. The result of this concatenation is what is used as the final mapping when the user presses Ctrli. For example, if the user presses Ctrlit then this expression evaluates to "it<Esc>", which then results in those keys being pressed.

For more, see :help :map-<expr>.

Also, look at the other answers for why your current solution doesn't work.

  • This works wonderfully, thank you very much! – hgiesel Feb 4 '16 at 22:39
7

One of your "printable" characters is a vertical bar, |. When that value comes up in your loop, you effectively execute:

nnoremap <C-i>|i|<Esc>

The bar denotes the end of the map command (see :help map_bar). You probably want to escape it in the array, like '\|' (or choose another method of generating these mappings).

(You can observe this by reducing, temporarily, your printable characters array to just [ '|' ] or alternatively by removing '|' from your set and seeing the behavior change.)

6

To include a pipe or bar (|) in a map, use <bar>. A literal | will end the map command and take what follows as a new command. That means that the execute command does something very different from what you intend when val == '|'. The different involves all the characters trailing the pipe, although I'm not sure at a glance exactly why you get this particular effect.

If you comment out the loop and variable assignment, replace the concatenation with a literal |, and source the file you will see a similar effect:

" let printable_characters = [ '!', '"', '#', '$', '%', '&', '''', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']
" for val in printable_characters
    silent! execute "nnoremap <C-i>|i|<Esc>"
    silent! execute "nnoremap <C-a>|a|<Esc>"
    " unlet val
" endfor

becomes

" let printable_characters = [ '!', '"', '#', '$', '%', '&', '''', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']
" for val in printable_characters
    silent! execute "nnoremap <C-i>|i|<Esc>"
    silent! execute "nnoremap <C-a>|a|<Esc>"
    " unlet val
    silent! execute "nnoremap <C-a>|a|<Esc>"
    " unlet val
" endfor
" endfor

See :help map_bar.

  • I read this which said, that ' is the only character that has a special meaning in literal strings, which lead to my own confusion, thank you very much! – hgiesel Feb 4 '16 at 22:42

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.