the scratch castle

 _   _   _             _   _   _
| |_| |_| |           | |_| |_| |
 \ _____ /      o_     \ _____ /
  |     |       |_|     |     |
  |  |  |       |       |  |  |
  |   _ | _   _ | _   _ | _   |
  |  | |_| |_| ||| |_| |_| |  |
  |  |                     |  |
  || |                     | ||
  |  |        _____        |  |
  |  |       /_|_|_\       |  |
  |  |      /|_|_|_|\      |  |
  |  |      ||_|_|_||      |  |
  |  |      ||_|_|_||      |  |
  |__|      ||_|_|_||      |__|
 /   |      ||_|_|_||      |   \
=================================

Shorty: An Interactive Tool for Learning Keyboard Shortcuts in Emacs (WIP)

February 26, 2016

Preface

(If you're in a hurry, click here to skip to the goods)

I started Emacs because I wanted an editor that would let me do things efficiently. A huge part of being efficient in Emacs is learning keyboard shortcuts. This is unfortunate because my leaky brain isn't very good at remembering a bunch of little details and there are a lot of default Emacs bindings you must internalize. There's a tutorial that can be accessed with C-h t and it's OK. I do like that it's just a simple text file, but this comes with downsides. The default tutorial is kludgy to navigate for a newbie and it offers no feedback on what you're doing. Another issue is that the tutorial only covers Emacs core shortcuts, but we all know that third party packages make Emacs so much more than it's core.

I think we could bring more people to the editor if there was a tool that let users interactively learn ALL the shortcuts available in Emacs from within Emacs. I decided this tool should have the following features:

Each shortcut should have a demonstration, or a "demo"

A demo is a live, automated demonstration of a shortcut in a buffer. A live demo shows users what should happen when that shortcut is pressed. This is much better than telling them.

Demos can be played and replayed

Once a demo is done playing, a replay option should be presented to the user.

A log buffer should be displayed along with the demo buffer

This buffer would show the keys being pressed and their associated command. This gives users a script for replicating the demo later.

Demos should execute in a controlled environment

The author of the demo should be able to specify what major and minor modes are on while the demo is executing.

Users should be able to practice the shortcut from within the tool

Learning follows doing so once a demo is done playing, users should have the option to jump into the demo buffer and try it out.

Similar demos should be organized into "playlists"

For example, C-n, C-p, C-f, and C-b would be in the "Basic Emacs Navigation" playlist.

A user should be able to pick a playlist and play through all the demos contained within

Once a demo is over the user should have the option to move forward or backward through the playlist.

Playlists should be organized into albums

An album represents all the shortcuts for a particular thing. I would assume an Emacs package would define a single album with one or more playlists.

An album must be an elisp data structure and contain all the information necessary for playing the demos/playlists within

This is an obvious format to work with because Emacs understands it and package authors understand it.

It must be easy to use

To be better than a text file tutorial, the proposed tool must be just as easy to use.

It must be extendable

Emacs is so much more than Emacs core so third party packages the user has installed should be able to hook into the tool and add their tutorials too.

Albums should be easy to build

Package authors won't use the tool if making albums isn't easy.

Good News

If all that sounds nice to you well I have some good news, I've almost finished building it.

At the moment I'm calling it shorty. Here's it is in action:

Here's what happened:

  1. I told shorty to open up my albums buffer. This is an outline of all the albums, demos, and playlists that shorty knows about. This is the main UI for interacting with shorty.
  2. I put my cursor on Playlist 1 > Demo 1 and tell shorty to play that demo.
  3. Shorty opens up a demo buffer and a log buffer then starts the demo while logging the keys being "pressed" by the program.
  4. Once the demo is over, I press "n" to move to the next demo.
  5. Once this demo is done playing I press "q" to quit.

Shorty currently satisfies most of the features I outlined above but it's still not 100%. Let's take a deeper look.

Overview

As mentioned before, demos are organized into playlists, and playlists are organized into albums. Let's start at the top level and see what an album looks like:

(list
    ;; The name of the album
    :name          "Example Album"
    ;; A list of the default minor modes descendant demos will use (Optional)
    :minor-modes   '(paredit-mode)
    ;; References are relative filepaths to a a textfile.
    ;; These refs may be used as values for the :text property in descendant demos
    :text-refs     (list :emacs-program  "sample-text/emacs-program"
                         :hello-world    "sample-text/hello-world"
                         :lorem-ipsum    "sample-text/lorem-ipsum")
    :playlists     ...) 

Pretty straightforward.

Here's a playlist.

(list
    :name   "Playlist 1"
    :demos  ...) 

Playlists aren't very interesting :)

And finally, here's a demo:

(list
    :name   "Demo 1"
    ;; The text to be used in the demo buffer
    :text :lorem-ipsum
    ;; The key presses that are executed in the demo.
    :macro  "M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k") 

The entire album looks like this:

(list :name          "Example Album"
      :minor-modes   '(paredit-mode)
      :text-refs     (list :emacs-program  "sample-text/emacs-program"
                           :hello-world    "sample-text/hello-world"
                           :lorem-ipsum    "sample-text/lorem-ipsum")
      :playlists     (list (list :name   "Playlist 1"
                                 :demos  (list (list :name   "Demo 1"
                                                     :macro  "M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k C-n M-f C-k"
                                                     :text   :lorem-ipsum)
                                               (list :name   "Demo 2"
                                                     :macro  "2*M-f M-b 5*C-d g o o d b y e SPC c r u e l"
                                                     :text   "(message \"hello world\")")))
                           (list :name   "Playlist 2"
                                 :demos  (list (list :name    "Demo 1"
                                                     :macro  "a b c d e f g"
                                                     :text   :hello-world))))) 

The idea is that packages would define an album like foo-shorty-album in their library and then users could add this album to the global shorty-album-list. When the user calls shorty-album-buffer-open, shorty takes the list of albums and creates a read-only org buffer. This buffer is the main UI and is basically just an outline of all the available albums. Users can then navigate this file as they wish and play an album, playlist, or demo by calling shorty-album-play.

Issues

There's currently a blocking issue regarding modes. Package authors need to specify exactly what minor modes and what major mode should be on for a particular demo. Shorty must respect this when it creates a new demo buffer.

My current strategy is to first turn off all the minor modes for a particular buffer then set the ones the author has specified. I'm currently using a package called manage-minor-modes to handle this. Unfortunately when you turn off all the minor modes for a buffer, it often causes nasty side effects in other buffers too. It's possible that an error during demo execution could wipe out all the minor modes in all the users buffers. The author of manage-minor-modes is aware of this so he included a function manage-minor-mode-restore-from-bals that restores all the current buffers to their original state.

Unfortunately, I've found that calling manage-minor-mode-restore-from-bals sometimes will have no effect and the user will either have to turn all minor modes back on manually or restart Emacs. This isn't so much an issue as when I first started developing shorty, but it's still possible. This is obviously an unacceptable error so I need to figure out how to minimize this problem as much as possible before releasing.

TODO

There's still some work left to do before I can release this into the wild.

  1. Need to add support for controlling the major mode of a demo. This is kind of tricky.
  2. Eliminate or minimize the minor mode problem described above.
  3. Provide an easy way for package authors to write albums. I don't know what this looks like yet.
  4. Need to iron out the format of an album. I don't know if keeping the full macro string in the demo is the right idea.
  5. Allow the starting point of the cursor to be specified for a demo.
  6. Add a description property somewhere that explains the purpose of shortcut. Maybe this goes in the log buffer?
  7. ???

I posted this with the tag WIP which means "work in progress". I plan to do more of these types of posts since I usually want to show off the stuff I'm working on so I can get ideas from the community before finishing it. With that being said, please leave a comment below if you have any ideas or suggestions regarding shorty. Here's a link to the source:

View shorty on github

Thanks for reading.

— sean

Tags: emacs programming WIP shorty