WhichKey / Hydra makeshift in Obsidian
Introduction
What are we talking about
Whichkey or Hydra is a dynamic, interactive key binding and command discovery system that displays available keystrokes and commands in real-time based on the current context or partially entered key sequence.
That's a mouthful, what does it mean?
It provides a hierarchical view of complex keybindings allowing:
- To have key "sequences", where shorcuts are triggered with more than one modifer + key.
For instance: something like:space + a + b
without having to hold space - "Teaching yourself" the commands by interactively providing the available sequences.
For instance: you can remember that date related commands are inspace + d
and Hydra will display all the related commands. Pressingt
could insert today's date while pressingm
could insert tomorrow's date. Next time, you can just pressspace + d + t
.
Let's look at the most popular implementations from 2 massive editor ecosystems
Which-key for Neovim
Hydra for Emacs
in Obsidian
First let's take a look at what my attempt, HydraShard, looks like:
(This takes after a random theme I installed for testing purposes)
We will go over how I met these few requirements:
- allow for keychords/key sequences
- display whichkey/hydra style context during the key sequence
- programmaticaly assign keystrokes to commands
The last point is an issue I have with Obsidian coming from emacs/neovim where each template or command has to be registered as a command and then bound to a key manually in GUI's.
Honorable mentions
Obsidian-sequence-hotkeys
- Very powerful, but no programmatic assignment as far as I can tell
- The functions have to be "palette commands" which is not a requirement for HydraShard
Obsidian vimrc
I'm sure there are a lot of interesting idea there but I am avoid emulating vim these days.
Usage
As per the last article, we'll make heavy use of templater user scripts but anything should be possible without it. First add the HydraShard script to the script folder.
Get it from here.
HydraShard relies on being called using templater as an "insert", not a new file. Then this the only binding necessary to set up in GUI.
If you bind your hydra-powered template to ctrl+n
all the subsequent keys defined in HydraShard will be ctrl+n+a
, ctrl+n+b
etc.
HydraShard, as any Templater user script can be called with
tp.user.hydra(menuitems)
Where menuitems
is expected to be an object of the following shape:
let menuitems = [
{
key: "f",
name: "Name to be displayed here",
action: async () => {
let var = await tp.user.someModule();
return var;
},
},
{...}
]
Each element has a key to trigger, name to be displayed and function to be called upon being selected.
You can capture the returned value from the tp.user.hydra()
call such as:
Output
let result = tp.user.hydra(menuitems)
console.log(result.result) // to get the captured output
console.log(result.item) // to get the selected option
Structure and trigger
Then there's a few ways to go about it but here is my favourite: by defining as much as possible in JS and using the shortest template possible.
(pass around the tp
object as a context to be able to us the templater API and other user scripts)
function myNestedShortcuts(tp) {
let menuitems = [
{
key: "t",
name: "Add Tag",
action: async () => {
const tag = await tp.system.prompt("Enter tag:");
// Do something with it
console.log(`Tag added: ${tag}`);
return tag;
},
},
];
let result = tp.user.hydra(menuitems);
return result;
}
module.exports = myNestedShortcuts;
then in the template, this is the only content needed:
<%*
let result = tp.user.myNestedShortcuts(tp)
%>
Note that nothing stops you from defining the menuitems object in templater instead.
You can also implement more logic using the returned values from Hydra.
If your setup is not too long to define you can also define your function and your menuitems in the same template.
Conclusion
I am very happy with the results for such a hacky solutions.
I can define a lot of key-sequence based shortcuts in JS without having to register them as commands and it has been pretty performant too.
Be aware of certain limitations since it's Templater based such as the need to be in a note, HydraShard does not work in an empty tab or even in Excalidraw.
Going further
Adding an optional timeout: currently pressing any non-binded key cancels it out so pressing 'escape' works pretty well.
Better html: I imagine there are more proper ways to add GUI elements to Obsidian and use native components.
Mobile version: The command palette can be horrible to use on mobile the little I tried it. Maybe there's a smart way to trigger it from within the app or even through URI to get a touch-based command menu of sort.
Register as an API instead of a templater script: it could simplify installation by registering in some app.plugins.plugins['hydra'].hydra()
instead of Templater's space but that probably wouldn't change much the functionality at this point.