Keybinds as a DSL
As a heads up I will say that I am writing this in the context of using a tool like emacs, or some kind of fictional unified interactive environment focused on a text interfaces. So pull up a pew.
When I think of editing or doing pretty much any kind of interactive text munching. I tend to think just dump it in an emacs buffer and get evil with it. Which leads me to the title of this talk.
Keybinds are a DSL, or at least could be a DSL.
As an evil enjoyer, when editing in emacs I tend to pop in and out of
INSERT mode as I hop around a page and do my thing. Recently I've been
thinking of attempting to write a text first programmable environment,
and as part of this I've been brainstorming the UX part of it.
I was thinking about how I tend to navigate text with evil mode,
and the main thing that came to mind is the composability of the
keybinds. For example a common key motion (what do you even call this? A
riff?) that I use is, c i (. When pressed in normal mode this will
delete the contents of the next matching parentheses and leave the
cursor just after the ( in insert mode. Instead of moving over to
the mouse and hunting around for things, which interrupts my flow I
just play my little key riff thingie and I can carry on with whatever
I was attempting to do.
The great thing about these key riffs is that when you figure out the language of the key presses, you can start to compose your own little riffs to help speed up your editing.
So what would a UI that is built around this paradigm look like?
Well in vim and evil we have the following kind of sentence structure:
- Command / Context(?) / Object
For example we could write the prior example of c i ( like Change Inside Parentheses.
Sometimes we omit the context in our riffs, and just use the command
and object like in c w (Change Word), which will delete to the end
of the word from the cursor and then leave the cursor in insert mode.
So if we take this idea of Commands, Contexts and Objects, and create a structure way of manner of utilising them like the normal mode vim riff thingie. We can create a unified interface for interacting with various types of objects across a set of commands.
So what are commands, contexts and objects?
A command is just an operation to be applied to the text. This could be copying, pasting or deleting.
A object is a view of the underlying text. This could be a single character, or a word, or even the inside of a pair of parentheses.
And a context is a way of further describing how the object should be used. An example would be the inside context which could be used for selecting only the contents of the parentheses and not the parentheses themselves.
Not every object requires a context. For example the character object, there is no need to imply anything here. A character will always be a single character point. I am not mentioning encoding here and that is left to be decided by the file / environment.
Here are a list of commands, contexts and objects that could be useful.
Commands:
- [s] Select (highlight)
- [d] Delete
- [c] Change
- [y] Copy
- [p] Paste
- [f] Inverse / flip
- [S] Snipe (jump to next occurrence)
Objects (objects prefixed with * are special and don't have keybinds):
- [l] Line
- [w] Word
- [s] Section (A section surrounded by new lines. Also known as a paragraph.)
- [(] Matched Pair (A matched pair of characters like, <>, (), {}, []. the Keybind could be either char)
- [^] Selection (The previously highlighted selection)
- * Character (Mentioned so we don't forget about the lil fella)
Contexts:
- [i] Inside (don't include the beginning and end of the object if applicable)
- [a] Around (include the beginning and end of the object if applicable)
- [t] To (invoke the command to the specified object)
We can then use these and out sentence structure to compose commands in a DSL-esque way. Here are some example ways we could do this.
s i {: could be used for selecting the inside of a function definitions i w f i (: could be used to select the first arg of a function and then flipped to select all other args, which could then be deleted or something elses i w S ^: could be used to select a word and then jump to its next occurrence
The great thing about this way of thinking is it gives us a structured way around thinking of editing. We can always add more contexts, objects or commands. As well as being able to bind a longer riff to a single key press if needed via a macro recorder or a scriptable interface if one is present.
The only problem is that it requires a model editing paradigm or a activation key to trigger the command mode. so that we don't have to worry about overloading keys that are used for typing like the alphas.