UP | HOME

Emacs Hydra menu

Table of Contents

Words….

It's been a while since I've written anything meaningful. I suppose I've just been feeling a bit lazy, but to my defense, I've spent a lot of time training on my bike 🚴. I needed a change of pace. Don’t get me wrong—I love programming and the exhilaration of being in the zone where everything flows effortlessly. But sometimes, I lose it all. Inspiration fades, and more than anything, I find it hard to sit down in front of the computer and actually get something done.

My daytime job as a software engineer often drains the energy right out of me. One way I try to cope is by taking deeper breaths—usually while cycling. Anyway, I realize I need to do something to kickstart my motivation. So here I am, trying to get myself moving again, having changed the spark plugs and added some starting fuel. Trying to jumpstart my writing once more… Honestly, it’s not going great yet.

Motivation

One of my main problems when using emacs is remebering the key shortcuts or even functions that I need to run. Don't take wrong its both a benefit and a curse, maybe it boils down to me not having a perfect memory. Though I guess im not the only one. The reason for the last statement is based on all the different meta-menu shortcuts that you can build. So what do I mean by that? Well, there are several different menu system that can be used and several different ways of searching for the right functionality. For example hydra which maps a popup menu, and this can be mapped to certain major-mode, and each item is based on a key and a function. Lets make a really short example

(defhydra hydra-zoom (:color amarath :title fisk :separator "🪓")
  "🐛 zoom 🐛"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out")
  ("q" nil "quit"))


As the result shows it returns a hydra-zooom/body , which is a interactive function and shows a nice menu as in , the red frame outlines the popup. zoom_hydra.png

This of course could be connected to a key-shortcut or run interactivly. Each of the defined hydra menu will automatically have a <name>/body function, which then can be bound to some key for example the above example could be bound to:

(bind-key "C-c c z" #'hydra-zoom/body)

Another nice way to deal with this, is to map a major mode to some hydra-popup, with a certain key-binding M-SPC. That means , we get a different popup depending on what major-mode is running. . This can be done using major-mode-hydra. The following example shows how to initialize major-mode-hydra

(use-package major-mode-hydra
:straight t
:config
    (global-set-key (kbd "M-SPC") #'major-mode-hydra)
    (setq hydra-hint-display-type 'posframe)
)

Lets make a simple example using the major mode: text-mode . The idea is to create a major mode that maps to M-SPC when in text-mode. I added some different options in the following example:

(major-mode-hydra-define text-mode
  (:color red :title "Ꙫ Text mode Ꙫ" :separator "⨝" :quit-key "q" :foreign-keys warn :hint t :timeout 15)
  (
   "🐛 zoom 🐛"
   (("g" text-scale-increase "in" :color blue :column "vertical")
    ("l" text-scale-decrease "out")
    ("d" whitespace-mode "Debug on error" :toggle (bound-and-true-p whitespace-mode ))
    ("q" nil "quit")
    )
   "Options"
   (("f" flyspell-mode "Flyspell mode" :toggle  (bound-and-true-p flyspell-mode))
    ("v" visual-line-mode "visual line mode" :toggle (bound-and-true-p visual-line-mode))
    ("b" abbrev-mode "abbrev mode" :toggle (bound-and-true-p abbrev-mode))
    ("a" auto-fill-mode "auto fill mode"  :toggle (and (boundp 'auto-fill-function) auto-fill-function))
    ("n" display-line-numbers-mode "line number" :toggle t)
    ("h" highlight-changes-mode "Highlight changes" :toggle t (bound-and-true-p highlight-changes-mode))
    )
   "Face"
   (("w" writeroom-mode "Writer room" :toggle ))
   ))

This creates a hydra function called: (major-mode-hydras/text-mode/body), which when called will bring up the popup, since we also are using major-mode-hydra which has mapped M-Spc, each time we use that shortcut it will bring up the menu. But if we want to use this menu in a different mode, for example org-mode we could do map a key to the function as in:

(add-hook 'org-mode-hook
          (lambda ()
            (local-set-key (kbd "<f6>") #'major-mode-hydras/text-mode/body)))

Now it is linked to M-Spc in text-mode and F6 in org-mode (noting that org-mode could have a different menu mapped to M-Spc).

major-mode-hydra also extends the possibility to use toggles, which can be very useful. In the above example I added a couple of differnt toggles for example whitespace-mode which shows the whitespaces in the current text. Its also possible to change the layout in the popup. The above creates three columns with headings Zoom, options ,/face/.

Some of the properties needs some explanation.

timeout
Timeout before the menu disappears
exit
After executing the command, the menu will dissapear
Color
Color actually have a side effect except for just coloring the field. It is an aggregation of foreign-keys and exit
red
blue
exit t
amaranth
Foreign-keys warn
teal
foreign-keys warn, exit t
pink
foreign-keys run
foreign-keys
If a key is hit that is not part of the defined, what will happen?
warn
A warning
nil
Hydra state will stop and the foreign key will do whatefer it was suppose to do.
run
Will not stop the hydra state, and try to run the foreign key, this can be somewhat confusing.

2025-04-25_13-13-39_screenshot.png The template for creating a layout can be something like the following yas-snippets.

# -*- mode: snippet -*-
# name: major-mode-hydra menu
# key: <hydra
# group: col
# --
  (major-mode-hydra-define ${1:text-mode}
    (:color ${2:$$(yas-choose-value '("red" "blue" "amaranth" "teal" "pink"))} :title "${3:My title}" :separator "${4:―}")
    (
    ${5:<hydra-column}$0
     ));

Now for the column snippet

  # -*- mode: snippet -*-
# name: Hydra column
# key: <hydra-column
# group: col
# --

   "${1:Column header}"
     (("${2:key}" ${3:function} "${4:description}")
      ${5:("${6:key}" ${7:function} "${8:description}")}
      )
      ${9:<hydra-column}$0

This snippets are kind of neat, since it provides a recursive way of implement different columns.

Adding this and pressing M-<space>= will open the above popup-menu above if in text-mode. This of course can be chained with other menus. For example; you have a some options where you want to change the theme or font or something else, this could potentially be done in another menu with other option, more on this in the later section. But lets first take a look at how to extend this the hydra

Extending Hydra

To be able to extend major-mode-hydra/.... with additional options there is a macro called pretty-hydra-define+ where you can define your extensions. For example lets make take the above example where we created major-mode-hydras/text-mode hydra. We could extend this by adding the following lines

(pretty-hydra-define+ major-mode-hydras/text-mode ()
("Face"
 (("C-e" ediff-buffers "Ediff buffers")
  ("C-f" ediff-current-file "Ediff current file"))
))

extend_options.png

Chaining Hydra

As discussed in the previous section maybe we want to some chaining of different menus. Think of it as a menu with submenus , obviously you can do alot of things with this.

Lets consider creating a project menu. The idea is to have a menu where the most common options from the the main menu, and then create a sub menu. So what are the common things I want to do with a project. Since I use many different modes, i dont want it to be directly connected to a mode, instead I will use some key, as i did before (F6).

So lets define the functions that i usually use in projects

Open
I want to open a project using o, this is achieved through projectile-open-project
Switch open
Switch to an already open project using s, this is achieved through projectile-switch-open-project.
Find file
Find a file in the current project f , using counsel-projectile-find-file
Find file with content
I use this quite a bit counsel-projectile-rg "r"

That f

(pretty-hydra-define col-project-main-menu (:foreign-key warn :title "Project" :quit "q" :separator "")
  ("Project"
     (("o" counsel-projectile-switch-project "Open Project" :color teal)
      ("s" projectile-switch-open-project "Switch Project" :color teal)
      ("f" counsel-projectile-find-file "Find file "))
     "Files"
     (("g" magit-status "Git status" :color teal)
     ("r" counsel-rg "Ripgrep")
     ("d" (dired-jump t) "Dired" :color teal)
     ("l" org-store-link "Store link" :color teal)
     )
     "Sub Menus"
     (("TAB" hydra-tab-bar/body "Tabs" :color teal)
      ("t" major-mode-hydras/text-mode/body "Text Menu" :color teal)
      ("c" hydra-org-clock/body "Hydra clock" :color teal )
      )))

Obviously this should not be connected to a mode, so we need some kind of key-binding.

(bind-key "C-c c SPC" #'col-project-main-menu/body)

Great, now we can open the menu whenever we hit C-c c SPC. But there is a small issue, we need the new sub menu for TAB and c, we have defined it. But there is no implementation. Lets make that.

(defhydra hydra-tab-bar (:color amaranth)
"Tab Bar Operations"
("c" tab-new "Create a new tab" :column "Creation")
("d" dired-other-tab "Open Dired in another tab")
("f" find-file-other-tab "Find file in another tab")
("0" tab-close "Close current tab")
("m" tab-move "Move current tab" :column "Management")
("r" tab-rename "Rename Tab")
("<return>" tab-bar-select-tab-by-name "Select tab by name" :column "Navigation")
("l" tab-next "Next Tab")
("j" tab-previous "Previous Tab")
("<right>" tab-next "Next Tab" )
("<left>" tab-previous "Previous Tab")
("q" nil "Exit" :exit t))

The only problem is that i really want this to have a short cut too.

(bind-key "C-c c TAB" #'hydra-tab-bar/body)

Let finally add the last piece of the puzzle. For this I was lazy, there is actually a small community wiki with different examples. So instead of creating a complete new I just copied it from Hydra wiki.

(defhydra hydra-org-clock (:color blue :hint nil)
   "
Clock   In/out^     ^Edit^   ^Summary     (_?_)
-----------------------------------------
        _i_n         _e_dit   _g_oto entry
        _c_ontinue   _q_uit   _d_isplay
        _o_ut        ^ ^      _r_eport
      "
   ("i" org-clock-in)
   ("o" org-clock-out)
   ("c" org-clock-in-last)
   ("e" org-clock-modify-effort-estimate)
   ("q" org-clock-cancel)
   ("g" org-clock-goto)
   ("d" org-clock-display)
   ("r" org-clock-report)
   ("?" (org-info "Clocking commands")))

Nice, we could continue extending this. But for now this is good enough as an example

Summary

Obviously hydra can be a useful tool especially since creating shortcuts for everything drives you crazy. Though, I would recommend taking your time and plan what you want todo and how the menu(s) should behave. Using major-mode-hydra makes life easier, but it is easy to make a mess of everything. There are however some issues with hydra that I'm missing. For example some functions needs a input argument. But maybe the next the next package can help out with this. Lets take a look at Transient which is another tool for creating menus.

Date: 2025-04-23 Wed 00:00

Author: Calle Olsen

Created: 2025-08-04 Mon 20:04

Validate