Key maps as key locations

Probably every (modal) editor out there uses a mental model of mnemonic key symbol shortcuts for the editing language. Kakoune is no exception.

They are all wrong.

Rather the editing language should be conceived as muscle memory primitives that interact on a command bus. Problem is, we have no identifiers for muscle memory, and looking at the source code of kakoune, we don’t even have them as part of the public api for the commands.

To fix this, we need an alternative mental model of configuring the editing language to our needs:

  • We need a muscle memory location identifier on keyboards
  • We need a public commands api that can be used in mappings directly

Keyboard Location Identifier

A mental model of keyboard location identifier accommodates the fact that there are very different physical key layouts with very distinct hand strain coefficients.

There is a finger naming scheme used by an evolutionary genome algorithm to determine the “best possible key layout” for English language which could be expanded upon in a way that key locations are encoded by their respective finger and a vector in the x-y coordinates describing distance and direction of it’s travel from the home row to reach said key.

Without the desire to further elaborate on this proposal, a potential application could manifest as follows:

  • map global normal l-point[1,-1] SelectToNextChar“map unique finger position (“muscle memory”) X to command Y” / would be qwerty b
  • instead of map global v t“map qwerty v to querty t command” — but I’m not using qwerty!
1 Like

First of all – interesting idea, I have a few questions and points.

  1. Wouldn’t this require a map of every layout AND physical keyboard combination? Consider these: https://www.typing.com/blog/gettin-crazy-keyboards/
  2. Why does it not address the two thumbs separately (split keyboards)?
  • Choosing not to have a command API was a choice, not an oversight, I don’t suspect it will be revisited.

Thank you for those fast forwarding questions. On the road to victory, my current checkpoint answer would be:

ad 1.

  • I think you identify a two-fold problem here: How to fence against changing key layouts (us, fr, …) and how to fence against changing physical layouts (emulated, off the shelf querty, etc)
  • I hope it would be possible to access some static handle on the physical keys that is unique per keyboard (maybe such as the ones returned by showkey --ascii)?
  • Then, we would “train” kakoune on a physical keyboard layout to correspond positions to let’s say ascii codes or keycodes or similar.

ad 2.

  • Surely, r-thumb & l-thumb would have to be part of the spec.

The XY coordinates plane does limit the way that non-columnar key layouts could be specified. The more extreme example in the link you provided with the circular layout is for gamers. Maybe it can be safe to postulate: non-columnar-resembling layouts shall not be a first class citizen in the domain of a text editor. Something that a human brain can transform-project onto a grid would be enough which covers almost all keyboard formats, even the most ergonomic ones.


Ideally, a small helper script would guide the user through the mapping process:

Place your hands on what is the home row for you
Type l-pinky[ 0, 0]:
Type l-pinky[-1, 0]:
Type l-pinky[-2, 0]:  # omit with ESC since my (most) keyboard doesn't have it, for example
...

would generate some type of ingestable muscle memory map: (ring and middle would probably spare the x axis)

l-pinky[-1, 2]:12  l-pinky[ 0, 2]:13  l-ring[ 3]:14 ...

l-pinky[-1, 1]:34  l-pinky[ 0, 1]:35  l-ring[ 1]:36 ...

l-pinky[-1, 0]:56  l-pinky[ 0, 0]:57  l-ring[ 0]:58 ...

l-pinky[-1,-1]:81  l-pinky[ 0,-1]:82  l-ring[-1]:83 ...

...

Should kakoune ship with a default layout US QUERTY mapping? Maybe. One reason could be to keep current behaviour out of the box when someone just wants to give kakoune a quick shot.

But emphasis should rather be put on a generator tool than trying to ship too much mapping specs.
Kakoune’s default binding could work entirely with the muscle memory locator syntax internally.

Over time, I suspect such change would trigger a slow paradigm shift in the community away from keysym logic towards muscle memory logic. At that stage, I think it will be natural to optimize the command ergonomics accordingly. When that is the case, I could imagine that it will feel natural to revisit the stance on remapping (to muscle memory positions — what would remapping muscle memory positions actually mean?!?!? — such indirection would feel obsolete) vs genuine mapping (to commands). But that’s something that can happily emerge over time, if it wants to.

What do you think? Is this possible?

If your physical keyboard changes, just re-train kakoune. Should be a quick no brainer to do for the user compared to current very convoluted remapping.


EDIT: In case the shift accessor is handled keyboard side, it shall be l-pinky[-1, 2]:12:23 (is it? or does the keyboard still emit a unique identifier to denote the physical key that has been pressed?)

EDIT2: This suggestion operates under the assumption of CSI u being confgured on the terminal, so that no restrictions apply to what modifiers can be used on any given key. See: https://github.com/alexherbo2/alacritty-extended-keys and Taking back control of hjkl with modifiers keys

I think maybe you could create a plugin that did this, that translated your input into a file with standard kakoune mappings that you could then autoload. And when you needed to change it, you could regenerate the file.

Love the out of the box thinking. I think this adds a layer of difficulty to onboarding new users as you no longer can say “start with familiar vim bindings”. Also we show which key to press in the clippy hints everywhere, so what would we do with that? Just trying to parse what you’re suggesting is making me quite confused. No vitriol intended here, just sharing my reaction.

Thanks for your input items — they are growth hormone, not vitriol. :wink:

I think there are various possible interaction points for an anti-corruption-layer to ensure a seamless transition: it could be a plugin, it could be in the source. Currently default bindings cannot be unmapped, only mapped to no-op. This is an uncomfortable backing data structure for this idea to work with and it would loose most of it’s conceptional clarity and beauty when having to embrace an extensive external anti-corruption-layer, so I’m inclined to suggest incorporating a well design proposal into the source might benefit the cause.

I would also postulate as requirement that the final editor UX (as oposed to config UX) is indistinguishable. Shipping default bindings for QWERTY (and not hand strain optimizing default bindings) means that “start with familiar vim bindings” is preserved as a feature.

Admittedly, I havn’t thought of the requirement to visually optimize the locator format for clippy hints (It didn’t occur to me while staring at my blank keycaps).

We could go down a path to visually optimize for display like for example lP0,1 but then, giving an extra round in the hermeneutic cycle, I think for mere display hints (as oposed to configuration primitives) we could dynamically map back to key symbols for whatever r-point[-1,1] might represent (y on QWERY / j on Workman, …)

I’m posting a possible QWERTY muscle memory locator to key symbols map so you could check out for yourself how comfortable the notation feels. I did not visually arrange them, so you get no visual clue and might be guided entirely by the syntax when mapping.

l-pinky[ 0, 2]: 1
l-pinky[ 0, 1]: q
l-pinky[ 0, 0]: a
l-pinky[ 0,-1]: z

l-ring[ 0, 2]: 2
l-ring[ 0, 1]: w
l-ring[ 0, 0]: s
l-ring[ 0,-1]: x

l-middle[ 0, 2]: 3
l-middle[ 0, 1]: e
l-middle[ 0, 0]: d
l-middle[ 0,-1]: c

l-point[ 0, 2]: 4
l-point[ 0, 1]: r
l-point[ 0, 0]: f
l-point[ 0,-1]: v

l-point[ 1, 2]: 5
l-point[ 1, 1]: t
l-point[ 1, 0]: g
l-point[ 1,-1]: b

r-point[-1, 2]: 6
r-point[-1, 1]: y
r-point[-1, 0]: h
r-point[-1,-1]: n

r-point[ 0, 2]: 7
r-point[ 0, 1]: u
r-point[ 0, 0]: j
r-point[ 0,-1]: m

r-middle[ 0, 2]: 8
r-middle[ 0, 1]: i
r-middle[ 0, 0]: k
r-middle[ 0,-1]: ,

r-ring[ 0, 2]: 9
r-ring[ 0, 1]: o
r-ring[ 0, 0]: l
r-ring[ 0,-1]: .

r-pinky[ 0, 2]: 0
r-pinky[ 0, 1]: p
r-pinky[ 0, 0]: ;
r-pinky[ 0,-1]: /

r-pinky[ 1, 2]: -
r-pinky[ 1, 1]: [
r-pinky[ 1, 0]: '

r-pinky[ 2, 2]: =
r-pinky[ 2, 1]: ]
r-pinky[ 2, 0]: \

I haven’t checked the keycodes (showkey --ascii) for all, but two (this is what is returned by my terminal, I’m not sure about the wire format here and if kakoune has only ever access to those terminal interpreted values — but that only means we have to move the anti-corruption-layer further down the stack and not let it bleed in and affect kakoune muscle memory config):

QWERY:
l-middle[ 0, 1]: e 	101 0145 0x65

Workman: (emulated by the keyboard firmware)
l-middle[ 0, 1]: r 	114 0162 0x72

And here is an example where CSI u encoding comes into play (but fortunately kakoune understands this perfectly):

Return:
^M 	 13 0015 0x0d

Ctrl+Return: (CSI u encoded)
^[[13;5u 	 27 0033 0x1b
 	 91 0133 0x5b
 	 49 0061 0x31
 	 51 0063 0x33
 	 59 0073 0x3b
 	 53 0065 0x35
 	117 0165 0x75

And here are xev events when swithing between spanish and english layout (confirming that keycodes are invariant):

KeyPress event, serial 38, synthetic NO, window 0x400001,
    root 0x39f, subw 0x0, time 3176346, (90,570), root:(1372,1697),
    state 0x0, keycode 47 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XmbLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False

KeyRelease event, serial 38, synthetic NO, window 0x400001,
    root 0x39f, subw 0x0, time 3176498, (90,570), root:(1372,1697),
    state 0x0, keycode 47 (keysym 0x3b, semicolon), same_screen YES,
    XLookupString gives 1 bytes: (3b) ";"
    XFilterEvent returns: False

KeyPress event, serial 38, synthetic NO, window 0x400001,
    root 0x39f, subw 0x0, time 3180682, (90,570), root:(1372,1697),
    state 0x2000, keycode 47 (keysym 0xf1, ntilde), same_screen YES,
    XLookupString gives 2 bytes: (c3 b1) "ñ"
    XmbLookupString gives 2 bytes: (c3 b1) "ñ"
    XFilterEvent returns: False

KeyRelease event, serial 38, synthetic NO, window 0x400001,
    root 0x39f, subw 0x0, time 3180794, (90,570), root:(1372,1697),
    state 0x2000, keycode 47 (keysym 0xf1, ntilde), same_screen YES,
    XLookupString gives 2 bytes: (c3 b1) "ñ"
    XFilterEvent returns: False

This raises the question if it is feasible for kakoune to work with raw key events when it’s expected to interpret muscle memory locators (in all but insert mode).

I think as a first step, an external training and “compilation” tool that outputs a kak friendly config would work rather well. It would have real-world value to those using very different layouts or keyboard styles.

I use a custom-built Dvorak keyboard and know the hassle of remapping editors so they are more ergonomic. A command API would definitely be helpful to make configs more readable. On the topic of muscle memory, switching input languages is messy since kakoune in normal mode expects commands in English.

What could help here would be to think out of the box a step further. Could we have a keyboard interface that directly reports key layout? Then, we could have a plugin with a more visual interface for creating our own mappings. There’s a ton of legacy involved in keyboard drivers, and it seems like every input method is built around ISO, ANSI, or a JIS layout keyboard.

Perhaps the folks over at QMK and ZMK could be able to offer some insight. A lot of custom-built mechanical keyboards use unusual physical layouts, but in the end have to work around QWERTY legacy when reporting keycodes to the computer.

2 Likes

This reminds me of the Keyboard Setup Assistant on the Mac:

At the end of the day when you report your device as a USB keyboard, it has to obey its specs, i.e. emit HID keycodes to the computer as defined in the keyboard usage page (p. 123). With QMK, you can do things like send raw HID messages instead of just sending regular keycodes, but you would have to process and map them in the host device. At that point it is not much different than sending regular keycodes and using a OS keyboard layout to do the mapping – just like how a Dvorak OS layout converts inputs assuming QWERTY keyboard layout to Dvorak keys.

That’s very sad. It makes it much harder for user who don’t use the default keybinding or non-qwerty keymap.
It block the freedom users could have creating their own keymap.

2 Likes

A config builder for the key remapping could be relatively cheaply built through nix, take for example: We need a global keymap · Issue #1685 · nix-community/home-manager · GitHub

After all this is by no means kak-specific.

A call to arms, this can be!

See also: remap global keys easily · Issue #57 · nrdxp/nixflk · GitHub