Taking back control of hjkl with modifiers keys

The arrow cluster

One of the most famous lore about vim and its derivative like kakoune is the use of h j k l as arrow keys.
This keys placement is often lauded by the users of these editors as a power-feature because the fingers barely have to move from the home-row of the keyboard.

Strangely, this kind of spatial considerations is almost unique to the h j k l cluster. Other keys tend to be placed on the keyboard according to some mnemonics related to their name.
Non exhaustive examples using the ā€œfirst letterā€:

  • w for Word or Window
  • f for Find
  • b for Backward or Before
  • n for Next
  • p for Previous or Paste
  • ā€¦

More extreme examples, coming from regexp notation:

  • ^ for Goto first non-blank char of line
  • $ for Goto end of the line

So as we can see the rules to affect a command to a key are quite arbitrary.
But letā€™s focus back on the arrow cluster and on kakoune.

  • h, j, k, l move the selection by 1 char
  • shift + h, j, k, l extend the selection by 1 char (or line)

These 2 sets of keys above are coherent.
The discrepancy appears when the alt modifier is involved.

Good:

  • alt + h, alt + l select to line begin / end

Less good:

  • alt + j Join lines (legacy from vim)
  • alt + k Keep matching selections (in vim K opens the keywordprg)

Suddenly the ā€œspatial traitā€ of j and k are exchanged for the ā€œmnemonic traitā€.
Arguably, joining lines is a pretty common operation, but filtering selections on a regex is less common (at least in my usage of kakoune, your mileage may vary).

This got me thinking:

Should these 2 commands move somewhere else? If so, which commands should take their place?

The ctrl modifier

But before diving into these questions, we should address the elephant in the room: what about the ctrl modifier?
Because of well-known historical reasons dating back to the prehistory of computers, terminal generate ambiguous keys for the following scenarios:

  • ctrl + i ā†’ tab
  • ctrl + m ā†’ ret

More painfully related to our present discussion:

  • ctrl + h ā†’ backspace
  • ctrl + j ā†’ ret

Which means that in a regular setup, our mighty arrow cluster is amputated of the h and j keys when used with the ctrl modifier.

One could argue that vim and kakoune try to minimize the use of actions involving modifiers. (a great counter example in vim world is the u / ctrl + r combo).
I agree that commands involving 2 modifiers like shift + alt + k are indeed cumbersome to type. But on the contrary, commands where the left ctrl (pinky because remapped on caps-lock) and left alt (under the thumb) is pressed
in combination with a right-handed letter (like our h, j, k, l candidates here) are quite comfortable (for my hands at least).

Therefore, what would happen if we were able to disambiguate the ctrl + h and ctrl + j, to claim back the arrow cluster? Along the years, attempts have been made to fix this problem, most notably libtermkey. Kitty, also offers an interesting fullkbd mode but kakoune CSI parser needs to be adapted to take this protocol into account (in the future maybe?). But right now, we can nevertheless use kitty to build our own ā€œhackyā€ solution.

~/.config/kitty/kitty.conf

map ctrl+h send_text all \u24D7
map ctrl+i send_text all \u24D8
map ctrl+j send_text all \u24D9
map ctrl+m send_text all \u24DC

These unicode code-points have been chosen because they are rare in the wild and their representation is a circled letter like this: ā“˜ (\u24D8).
Thanks to this ā€œhackā€ (which can be adapted to other terminal emulators having remapping capabilities), we can now use these chars in kakoune mapping, like I will show in the rest of this post.

Adapting the layout

Without further ado, letā€™s discover my current experiment resuming all of the above observations. (I stress out the word experiment as itā€™s by no means a definitive stance)

Letā€™s free the alt + k (and alt + K) key, by moving it to the D (and alt + D) key. The mnemonic kind of work. Regular d ā€œdelete the content of selectionsā€, whereas this new D ā€œdelete the selections themselvesā€.
Letā€™s free the alt + j (and alt + J) key, by moving it in place of C (and alt + C). This time the mnemonic can be ā€œCombine linesā€.

Now that they are free, letā€™s affect them a duo of commands that are often requested in the GitHub issues. Kakoune offers the X key to extend down, but has no equivalent to extend up.
Hopefully, many users provided dedicated commands like these ones:

define-command -hidden -params 1 extend-line-down %{
  execute-keys "<a-:>%arg{1}X"
}

define-command -hidden  -params 1 extend-line-up %{
  execute-keys "<a-:><a-;>%arg{1}K<a-;>"
  try %{
    execute-keys -draft ';<a-K>\n<ret>'
    execute-keys X
  }
  execute-keys '<a-;><a-X>'
}

So we can map alt + k to extend-line-up and alt + j to extend-line-down.

But ā€œwhat about the C and alt + C key you sacrificed earlier?ā€ you may asked.

Hereā€™s where hacking the ctrl key becomes very handy. Because we can now map C (copy selection to next line) to ctrl + j and alt + C to ctrl + k!

map global normal ā“™ 'C'         -docstring 'copy selection on next line'
map global normal <c-k> '<a-C>'  -docstring 'copy selection on previous line'

Iā€™m still thinking about what would be a great choice for ctrl + h and ctrl + l to complement this arrow. It could be to generate arbitrary selections on the left/right of the same line. (WIP)

Conclusion

Overall it feels very natural to gain back the ā€œspatial attributesā€ of h j k l with the shift, alt or ctrl modifiers.

I also try to apply the same philosophy when designing user-modes, privileging this arrow for commands having a before / after or next / previous behaviors.

14 Likes

I love my ctrl+j, thank you so much :100:

I think itā€™s also beneficial to remap y to ' at least for qwerty users. That way yank and select register are on the same key. And here is win win, map <y> to <a-k> and
<Y> to <a-K>. Letter Y reminds a funnel which I think corresponds to keep matching operations thus easy to mnemonic.

2 Likes

Iā€™ve just stumbled back on this GH issue detailing that <c-space> canā€™t be mapped by default: https://github.com/mawww/kakoune/issues/2553

The ā€œkitty trickā€ can also be applied in this context, for example:

map ctrl+space send_text all \u2420
map global normal ā  ': foo<ret>' -docstring 'foo mode'

As the space-bar is such a big and convenient key to hit with the thumbs, the <c-space> combo feels pretty nice to type and offers a good opportunity to bind your favorite user mode.

2 Likes

@Delapouite Which letters do you recommend for the shifted version?

@alexherbo2 Iā€™m not sure if I understand the question correctly, but my explorations are less ā€œextremeā€ that the one proposed by @blaggacao

In particular I do appreciate the spatial logic behind the idea of having chords like <c-a-H> but at the opposite I try to stay as far as possible of key mappings involving multiple modifiers.
Any chords combining ctrl+alt, ctrl+shift, alt+shift or even the magnificient trio ctrl+alt+shift are way too demanding for my clumsy fingers.

I was thinking to leverage the Control + Shift keys for unprefixed tmux bindings, to feel like a window manager, e.g. Control + W or F4 to close the pane.

@blaggacao It could be nice to bundle the keys in a tiny preamble section, so you can refer to them later.

declare-option -docstring 'Control + h' str ctrl_h {unicode_point}
...

map -docstring 'Select previous word' global normal %opt{ctrl_h} b
...

@Delapouite, @blaggacao Do you have the list of all common keys not recognized by the terminal?, e.g. numbers, etc.

Kakoune supports ā€œCSI uā€ out of the box. So instead of mapping problematic ctrl-key to fancy unicode characters, imho itā€™s better to configure terminal to send appropriate CSI u escape sequences.

For example, if you map your favorite terminal to send ^[[106;5u on <c-j> then you can map in kakoune as usual:

map global normal <c-j> 'whatever action'
3 Likes

If you want all kind of Ctrl + Shift support, you should definitely check out CSI u specification.

1 Like

@vbauerster Do you have an example of Alacritty and kitty config?

<c-j> for alacritty would be:

  - { key: J,   mods: Control,   chars: "\x1b[106;5u" }
1 Like

@vbauerster In CSI u mode, you have to set up all the keys you want or iTerm2 defines them for you?

iTerm2 omits Ctrl - j,i,m,h. The rest is handled as per CSI u by default.

just wanted to drop in my recent discovery into the discussion:

in this scenario, a, s, d, f (a, r, s, t on a colemak) contextually become your mod keys

thanks for the CSI u tipp. iā€™m going to check it out and update my findings, here.

thanks @alexherbo2:

https://github.com/alexherbo2/alacritty-extended-keys


EDIT: I deleted my post about using private area unicodes, since I couldnā€™t modify it any more after that much time has passed. In any case: its superseeded by the superior CSI u approach, which @alexherbo2 has implemented for alacritty above. Iā€™ve adopted it and it is so much cleaner, albeit still asking for complement: Key maps as key locations.