qmk / qmk_firmware

Open-source keyboard firmware for Atmel AVR and Arm USB families
https://qmk.fm
GNU General Public License v2.0
17.8k stars 38.15k forks source link

Implementing a leader key (Vim style) #169

Closed ezuk closed 8 years ago

ezuk commented 8 years ago

Another brainstorming session with @jackhumbert.

The idea would be to pick a modifier key, and have it double as a "leader", just like the Vim leader.

Let's say I pick Ctrl. When held down, it's a Ctrl. But when tapped, and then followed in rapid succession by a number of other keys, that registers as leader + those keys.

So the algorithm would be:

  1. Tap the leader
  2. A timer starts (say, 600ms by default?)
  3. Anything I tap during this time is not sent as a keystroke to the computer, but is saved in memory (so let's say "wc" is what I type)
  4. There's a LEADER lookup table. At the end of the timer, we go and look through that table for the substring I typed in (wc) and execute the macro that follows it.

So you could have <leader>wc to close the current window, <leader>wm to maximize it, etc. Or with media keys: <leader>ms (media stop) <leader>mp (media play) etc.

You could have multiple leader keys and multiple dictionaries.

And then in the keymap, what this would look like is just as LK0_LCTL for one of the keys. This would make it work as Leader when tapped, and LCTRL when held. And then if you wanted, you can also have LK1_NO for example, which would make the key work as just a leader when tapped, and have no hold action. This leader would point to the second subhash in dictionary. And somewhere in the file a data structure that looks something like:

LEADER_DICTIONARY = [ 
  LK0 = [ 
    wc: LALT(F4),
    wm: LALT(F5),
  ],
  LK1 = [
    e8b: cmk(":8ball:"), // cmk would be a function that transforms 8ball into Colemak-in-software mappings (if that makes sense) for people who have Colemak
    etu: ":+1:", // send these strings out, so I get "emoji leader sequences"
  ]
];

This particular feature, though, only encompasses the idea of the leader key and multiple dictionaries.

Sending out strings from the dictionary, and the cmk() string transformation function, are out of scope for this -- just ideas for later maybe.

mecanogrh commented 8 years ago

This would be great, still have to think of a clever use of this, I use a lot the leader in vim but for one shot tasks ie tasks that are not repeating by design as reindent file (once it's reindented you don't have to indent it again unless you make changes), go to end of line (easier than depressing 3 keys on my planck layout and again once you are in the end of the line you don't need to execute it once more), etc. While your examples make sense in that way, what about media fast forward? I mean people will try this. Wouldn't it be too cumbersome?

ezuk commented 8 years ago

Sure it would be cumbersome to use this for FFWD -- this isn't for stuff like this. This is where documentation comes in. I'd make it clear in the docs what this is for, and provide use cases.

Combined with a nice syntax for long-strring macros, this can be amazing for built-in "quick strings". So sia ("short I agree") expands out to "I fully agree", no matter where you are. That's a compelling use case imho.

Then you have stuff like window management, not to mention binding it to regular hotkeys in various apps. Like, if one of your apps uses Ctrl-F2 or something inconvenient like that and won't let you remap, you can have d (or something) send the same hotkey. This is great when using keystroke-heavy apps like Blender and other 3D apps and video editing software, for example.

mecanogrh commented 8 years ago

I guess the timer for this is a bad idea, because you will be dependent on a fix amount of time to wait before issuing another key tap or things will start to act strange (unless you have a leader sequence escape key to stop the recording but that will defeat the benefit of having a leader key for leader+single key triggers as you would have to do leader+single key+leader). If I read correctly you are describing two distinct features. 1/ a leader behaviour being quiet similar than one shot mod or the ability to turn any key into an one shot mod. You can't really use the leader key as it is in Vim because in Vim you use it in Normal mode, while this can be perfectly fine if you use video or audio software I let you imagine the consequences if you suddenly need to take some notes in a text software. Maybe what we need here is to mimic the Normal/Insert modes paradigm clearly rather than having another only for mod keys behaviour. 2/ abbreviations extension through input recording and expansion once the input sequence is ended. I add you should allow the user to decide when the sequence ends. Now the trouble with this is that the user get no feedback of what he is typing, giving him feedback would be a huge plus. Feedback could be given through led for init of recording sequence then everything typed is output normally and recorded, if the user press the recording key once more it is translated if he does not after a certain amount of time or goes beyond an input amount limit the recording stops, the led switch off and what was typed remains.

ezuk commented 8 years ago

Not quite; what you're describing is overcomplicating the feature a little bit :)

The idea is super simple. The timeout is there to naturally enforce two- or three-char sequences after . So you go ab for example, to make something happen. And yes, ab do not get sent to the computer. Since it's just two chars, it'll be fine -- not confusing (and of course, completely optional and up to people whether or not they want to use it).

The timer is important here (overall timer that starts counting from the moment you actuate ) because that's exactly what would prevent people from going overboard with this. Four or five chars after leader is not what I had in mind, and not a good MVP for this IMHO.

What's nice about this idea is that it's simple to understand and (relatively) simple to implement, thus serving as a useful starting point. Once (if?) we have something working, we could all test it out and really see what it feels like in daily use, and then go from there. That's my take on it, because imho it's very hard to theorize about keyboard ideas without trying them out in actual use (feel plays a huge role here). Maybe once I try this I'll discover you're right and it feels super weird, but on paper, this seems quite straightforward and natural to me, especially with two/three-key sequences :)

mecanogrh commented 8 years ago

Oh yes, and sorry, I misread your last example. Kind of a enhanced one shot mod mode, I see, I got carried over and didn't read all precisely when I saw Vim and leader :)

Still would be great to be able to pick any key as leader as in vim but doing so will need another key stroke like put keyboard in leader mode then tap leader then tap key.

BeatVids commented 8 years ago

Hi gentlemen! Erez just directed me here after I was suggesting something similar independently!

Initially, I was thinking about holding down a key, instead of a timer like you guys are suggesting.

I can see three different ways to go about this: 1) The timer/leader method discussed above 2) Hold and release method 3) Hold method

2) Hold and release method

I think with these new methods, it may require a new type of key, (let's call it the Macro Modifier for now) Heres is how the process should look like: 1) Press and hold the Macro Modifier 2) Press and release any non-modifier key/s 3) Release the Macro Modifier 4) What ever was pressed while the MM was held down, check if there is a macro for it 5) Execute the desired string/macro

Here are 2 examples with quick strings: Mod + U + S + A = "United States of America" Mod + U + S = "United States"

3) Hold method

This method is similar to the previous one, that it will use the theoretical Macro Modifier, but may be a smoother typing experience which does not rely on the release of the Macro Modifier.

Heres is how the shorter process should look like: 1) Press and hold the Macro Modifier 2) Press and release any non-modifier key/s 3) If a match has been found, immediately execute the registered string/macro.

Although it may be a bit faster and smoother typing experience for the user, it limits the combinations a bit: Mod + U + S = "United States" But "USA" will be impossible since the combination "US" will take priority.

I don't even have an Ergodox yet, I'm just throwing some ideas out there. I'm stoked that you have recently been discussing this, and it would be awesome to see it implemented some day!

GCorbel commented 8 years ago

I think the most useful vim thing is motions. I don't know if it's possible without a program but I will enjoy to use MOD + b to go back, MOD + w to go to the next word, etc. but it may not be relevant for the keyboard and more appropriate for program on the os.

It can be useful to have something to record macros and replay them with a leader key.

ezuk commented 8 years ago

These ideas are lovely, but i feel we need to start with a simple implementation of the most basic idea. @jackhumbert said he might get around to it, but if anyone else wants to step up with some code that would be awesome! :)

BeatVids commented 8 years ago

Hey guys, yesterday I was experimenting on this macro feature, and was able to get it work using a simple and short bash script. It is dependent on the window manager (I am using AwesomeWM in Arch Linux), and xdotool which fakes/scripts keyboard input. Sorry if it looks rather noobish, Bash is basically the only thing I am familiar with, but I would love help and maybe get this to work in c for the firmware someday. This was made just for fun and to hopefully inspire.

Here are all the 5 sample macros being put to action consecutively: https://www.youtube.com/watch?v=8HZuY5TeZzg And here is the short script I came up with:

#!/bin/bash

#This file is updated by the window manager using a keyboard shortcut.
#In this case, ctrl+shift+letter is held, and the letter is appended to macroinput.
#The macroinput file records all letters temporarily.
macroinput=$(cat ~/.macroinput)

#Once the timer is finished, the macroinput file is cleared.
#Since I may use 2 macros in a row, I don't want to clear the macroinput file 
#while I am inputting the second macro. This is the purpose of the macrolock file.
> ~/.macrolock

#Define custom macros
[[ $macroinput = bbb   ]] && macro="Better Business Bureau "
[[ $macroinput = bc    ]] && macro="because "
[[ $macroinput = ee    ]] && macro="Ergodox EZ "
[[ $macroinput = kb    ]] && macro="keyboard "
[[ $macroinput = byob  ]] && macro="Bring Your Own Beer "

#If a macro has successfully been found, clear the macroinput file, and execute the macro.
if [[ $macro != "" ]] ; then
    > ~/.macroinput
    rm ~/.macrolock
    xdotool keyup ${macroinput: -1}
    xdotool type --clearmodifiers --delay 10 "$macro"

#Otherwise, wait 1 second; if the macrolock file does not exist, clear the macroinput file
else
    sleep 1s
    if [[ ! -f ~/.macrolock ]] ; then
        > ~/.macroinput
    fi
    rm ~/.macrolock
fi

This is more of a combination of methods 1 and 3 I mentioned a few days ago. There's also more ways to do this, but I need to figure out how to capture the release modifier events. Here is some code, sorry it's not usable, but I hope the concept is!

ezuk commented 8 years ago

Thank you, @BeatVids! It's a nice demo/proof of concept -- let's see what happens when we convert it over to live entirely on the keyboard. Not sure about the timeline for this but it's cool to see the sustained interest in it :)

algernon commented 8 years ago

This is something I could use, and I have something similar - albeit much simpler - stashed away here. I have a number of uses for this:

There's a number of other things I could use it for, eg, inputting Hungarian chars. Right now, I do that with a one-shot layer, but it would be more convenient with a leader key, I believe.

Anyway, I'll see if I can find some time to have a stab at this.

jackhumbert commented 8 years ago

For those interested, I have an implementation up here with a dedicated leader key - we're still throwing around some ideas for working a chording-like functionality into it, but that will come in down the road.

jackhumbert commented 8 years ago

This has been merged into the base - we have some more ideas to implement (and docs to write), but I think we can close this for now.

BeatVids commented 8 years ago

Just installed the newest build. I would love to give this a shot and let you know if I run into any bugs and/or have any ideas. However, I have no clue on how to get started, some quick documentation will help greatly! :)

jackhumbert commented 8 years ago

Check out the experimental Planck keymap - I use it there a little :) you'll need to put KC_LEAD somewhere as well.

ezuk commented 8 years ago

@BeatVids Documentation coming up in the next few days :)

BeatVids commented 8 years ago

I wasn't competent enough to get it to work with my Ergodox EZ. :cry: But I'll be on the lookout for that documentation, my layout already has KC_LEAD flashed :smile:

By the way, did @eltang ever make the pull request for the awesome fix for this issue? I'd love to see it implemented in the base. I even think it's default worthy for users who use mouse keys frequently.

eltang commented 8 years ago

@BeatVids I haven't done that yet. I need to do some more work in order to completely separate the two delays.