Hammerspoon config inspired by Spacemacs
Keyboard-oriented workflows are often far more efficient and less frustrating than similar mouse-driven techniques. However, the most popular strategy in that space is to use a multitude of keyboard shortcuts. And obviously, that approach is not very scalable. You start adding keyboard shortcuts for various actions, and soon you will be blocked by conflicting shortcuts.
Command composability (first explored in Vi
and later expanded in its successor Vim
), although does require some initial learning and getting used to, allows you to expand your keyboard-oriented workflow with a minimal effort to memorize keys. There’s so much you can do with the h/j/k/l
keys alone.
However, the “one-dimensional” approach utilized in vanilla Vim, where a single modal (to switch from Normal to Edit to Select mode) is used, also has limitations. Fortunately, the basic idea of modality can be expanded further. The Spacemacs project is an excellent example of where that was done. In Spacemacs there is a single primary “modifier” key SPACE
. To trigger an action, user is required to press a mnemonically recognizable combination of keys (that usually starts with SPACE
key), e.g., SPC w m
is used to maximize the current window/buffer.
The Spacehammer project explores these ideas to allow you to take your keyboard-driven workflow to the next level. Jumping between applications, controlling the size and position of their windows, searching for things, etc. - everything follows simple, mnemonic semantics. It lets you keep your fingers on the home row and liberates you from having to memorize a myriad of keystrokes, or require you to drag your hand to reach for mouse/touchpad/arrow keys - which inevitably slows you down.
Spacehammer initially was written in Lua (as the majority of Hammerspoon configs), but later was completely re-written in Fennel - a tiny Lisp that compiles to Lua. There is nothing wrong with Lua, but Lisp has many benefits (sadly often overlooked and ignored by majority of programmers today). Switching to Fennel allowed us to keep the code more structured and concise.
You can use brew:
brew --cask install hammerspoon
brew install luarocks
luarocks install fennel
Older versions of Fennel are incompatible with Spacehammer.
git clone https://github.com/agzam/spacehammer ~/.hammerspoon
LEAD
is the main and major keybinding that invokes the main Spacehammer modal. By default it is set to Option+SPC
, but it can be re-configured in ~/.spacehammer/config.fnl
by changing the :mods
and :key
bindings for the lib.modal:activate-modal
action string. You might want to set it, for example, to Ctrl+Shift+SPC
.
If you want to use Cmd+SPC
as LEAD
you will have to rebind it in your system, since it is normally used for Spotlight.
Go to your Preferences/Keyboard, find Cmd+SPC
keybinding and change it to something else. Unfortunately, simply disabling it sometimes is not enough. You’d have to set it to be something else e.g. Ctrl+Cmd+Shift+\
or anything else , it doesn’t really matter, since you can then un-check the checkbox and disable it.
hjkl
- moving windows around halves of the screenCtrl + hjkl
- for jumping between application windows (handy for side by side windows)w
- jump to previous windown/p
- moving current window to prev/next monitorOption + hjkl
- moving in increments (works across monitors)Shift + hjkl
- re-sizing active windowg
- re-sizing with hs.gridm
- maximize active windowc
- center active windowu
- undo last window operation (similar to Spacemacs’sSPC w u
)
e
- Emacsg
- Chromei
- iTerms
- Slack
you can add more, also try LEAD j j
pressing SPC
in the main modal takes you to Alfred search popup, pressing SPC
in other modals returns to previous modal.
Why not use media-keys?
a) because different external keyboards impose their own ways to control media.
b) because Spacehammer allows you to keep fingers on the home row.
By default LEAD m a
- jump to music app
is configured to work with Spotify, but you can change that in ~./spacehammer/config.fnl
You can edit any text in any app Cmd+Ctrl+O
. Currently, it supports only Emacs. Read more here.
- Scroll through current Slack thread
Ctrl-j/Ctrl-k
(slow) orCtrl-e/Ctrl-y
(fast) - Jump to the end of the thread with
Cmd-g
- Add emoji to the last message -
Cmd-r
(Slack’s defaultCmd-Shift+\
is quite inconvenient) - Jump back and forth through history -
Ctrl-o/Ctrl-i
All menu, app, and key bindings are defined in ~/.spacehammer/config.fnl
.
That is your custom config and will be safe from any upstream changes to the default config.fnl.
The reason to keep it in its own directory is so that it can be maintained in version-control in your own repo.
Menu items are listed when you press LEAD
and they can be nested.
Items map a key binding to an action, either a function or "module:function-name"
string.
Menu items may either define an action or a table list of items.
For menu items that should be repeated, add repeatable: true
to the item table.
The repeatable flag keeps the menu option after the action has been triggered.
Repeating a menu item is ideal for actions like window layouts where you may wish to move the window from the left third to the right third.
(local launch-alfred {:title "Alfred"
:key :SPACE
:action (fn [] (hs.appplication.launchOrFocus "Alfred"))})
(local slack-jump {:title "Slack"
:key :s
:action "slack:quick-switcher"})
(local window-inc {:title "Window Halves"
:mods [:cmd]
:key :l
:action "windows:resize-inc-right"})
(local submenu {:title "Submenu"
:key :t
:items [{:key :m
:title "Show a message"
:action (fn [] (alert "I'm a submenu action"))}]})
(local config {:items [launch-alfred
slack-jump
window-inc
submenu]})
Menu items may also define :enter
and :exit
functions or action strings. The parent menu item will call the enter
function when it is opened and exit
when it is closed. This may be used to manage more complex or dynamic menus.
Global keys are used to set up universal hot-keys for the actions you specify.
Unlike menu items they do not require a title attribute.
Additionally you may specify :repeat true
to repeat the action while the key is held down.
If you place :hyper
as a mod, it will use a hyper mode that can be configured by the hyper
config attribute.
This can be used to help create bindings that won’t interfere with other apps.
For instance you may make your hyper trigger the virtual :F18
key and use a program like karabiner-elements to map caps-lock to F18
.
(local config {:hyper {:key :F18}
:keys [{:mods [:cmd]
:key :space
:action "lib.modal:activate-modal"}
{:mods [:cmd]
:key :h
:action "chrome:prev-tab"
:repeat true}
{:mods [:hyper]
:key :f
:action (fn [] (alert "Haha you pressed f!"))}]})
Configure separate menu options and key bindings while specified apps are active. Additionally, several lifecycle functions or action strings may be provided for each app.
:activate
When an application receives keyboard focus:deactivate
When an application loses keyboard focus:launch
When an application is launched:close
When an application is terminated
(local emacs-config
{:key "Emacs"
:activate "vim:disable"
:deactivate "vim:enable"
:launch "emacs:maximize"
:items []
:keys []})
(local config {:apps [emacs-config]})
The ~/.spacehammer
directory is added to the module search paths.
If you wish to change the behavior of a feature, such as vim mode, you can create ~/.spacehammer/vim.fnl
to override the default implementation.