1

I find myself wanting to use vim one-handed now and again. Practicality aside, I want to define a function along the lines of:

function! GetControlKey()
    echo "Control? "
    let key = nr2char(getchar())
    execute "normal! " . key 
endfunction

that I can map to another key combination <Leader>, maybe, that will allow me to get the keycode for a character, then mask it with whatever constant vim uses internally for key -> Control-key conversion. Then use normal! to emit the keypress, so I don't have to stretch my hand to reach the control key as often. Does vim use the same technique as other software (ControlMask,ShiftMask in Xlib) or does it use something specific to vim? I am aware that Sticky shift - or getting <shift> with letter combinations discusses a similar concept, but that uses subtraction to get lowercase->uppercase ascii conversion. I have tried bitwise &-ing the pairs for different keys echo and(char2nr('<C-f>'),char2nr('f')) to try and get an idea of which bits are toggled between the two, but this doesn't produce a consistent result when doing the same thing for other characters (a, e, g etc.).

1

My recommendation would be to build a "\<C-...>" string and then use eval() to have Vim evaluate it as a key sequence.

For example:

function! GetControlKey()
    let key = getchar()
    let keyname = nr2char(key)
    return eval('"\<C-'.escape(keyname,'\').'>"')
endfunction

You can even find cases where the escape sequence fails, since then the \<...> will not expand and the resulting string will keep the <C- as a literal prefix, so checking for that flags failures.

There's still one set of keys to handle, which are the keys with a symbolic name, such as arrows, F-keys, etc.

You can handle those, but I can't really see a way other than handling them one by one, essentially checking whether the key matches something like "\<left>" and turning that into left as keyname.

For example:

function! GetControlKey()
    let key = getchar()
    if key ==# "\<left>"
        let keyname = 'left'
    elseif key ==# "\<right>"
        let keyname = 'right'
    ...
    elseif key ==# "\<f2>"
        let keyname = 'f2'
    ...
    elseif key ==# "\<S-f2>"
        let keyname = 'S-f2'
    ...
    endif
    return eval('"\<C-'.escape(keyname,'\').'>"')
endfunction

As you can see, it's quite a bit of work to list all combinations, especially if you want to cover the M- and S- prefixes (and possibly the C- ones as well, to preserve them.)

Using a long if/elseif/endif chain is probably pretty inefficient too. If you can use a Dict, that's probably better. (See :help Dict for more details.) If you use a Dict, you can probably use the eval() expression to generate the keys (probably only once, as you load a plug-in.) So all you need is a list of valid key names, as you can also generate the combinations of the M-, S- and C- modifiers programmatically as well.

So, in short, it's doable, but it's quite a bit of work. My recommendation is to use eval() and build a Dict with the mapping of named keys.

0

The control keys are the ASCII values before ' ' (space). So <C-A> is 1, <C-B> is 2, ..., <C-Z> is 26, <C-[> is 27 (aka ESC), <C-\> is 28, <C-]> is 29, <C-'> is 30 and <C-_> is 31.

Maybe something like this:

let key = getchar()
if key >= 97 && key <= 122
    " make it upper-case
    let key = key - 32
endif
let c_key = key - 64
if c_key < 0 || c_key > 31
    echoerr "Invalid"
else
    call feedkeys(nr2char(c_key))
endif

I use feedkeys() to feed the determined control character to vim. That way it looks like it was typed by the user. See :help feedkeys().

Don't know how to handle other control sequences (like <C-F2>).

  • This does assume ASCII, but that might be reasonable – D. Ben Knoble Sep 25 at 19:18
  • @D.BenKnoble Your are right. Won't work on Vim on z/OS (because of EBCDIC). Embarrassing as I sometimes work on z/OS. – Ralf Sep 25 at 19:26
0

Historically, terminals implemented Ctrl by masking off the 6th and 7th bits. This is equivalent to doing bitwise and with (octal) 037.

Try the following adaptation of your code:

function! GetControlKey()
    echo "Control? "
    let key = nr2char(and(037, getchar()))
    execute "normal " . key 
endfunction

Note that I removed the ! from your :normal command. Given your intended use, it seems likely that you do want this code to respect your mappings.

Note also, that this will only work for keystrokes which are encoded in a single byte. For example, in my setup, the End keystoke is encoded as the three bytes 0x80 0x40 0x37 and Ctrl-End is encoded as 0x80 0xFD 0x58. I'm not sure if there's a simple mapping between such multibyte combinations, but I suspect not.

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.