Modal text editing for Emacs: A collection of key bindings for navigation and text manipulation.
Cheat sheet:
You can install this package via MELPA
or add this repository manually to your load-path
. Load the library in your
preferred way, for example:
(require 'fingers)
For Qwerty users, add the following to change the main bindings:
(require 'fingers-qwerty)
(setq fingers-region-specifiers fingers-qwerty-region-specifiers)
(setq fingers-keyboard-layout-mapper 'fingers-workman-to-qwerty)
(fingers-reset-bindings)
Refer to fingers-qwerty.el
and fingers-neo.el
for more information about
the mappings.
Next you should bind the function global-fingers-mode
to enable
and disable fingers-mode
globally. You can bind this function to any
convenient key sequence. For example, I use
key-chord to toggle it via oe
,
where jk
would be better suited for Qwerty users:
(key-chord-define-global "oe" 'global-fingers-mode)
Continue reading for more information on the available bindings.
- [2015-08-09 Sun]: Added
fingers-replace-with-yank
bound toV
by default. - [2015-03-21 Sat]: Added + and - bindings to increment and decrement number under point.
- [2015-03-21 Sat]: Switched bindings for region specifiers “line” and “till line end”
- [2015-03-07 Sat]: Search prefixes and bindings for repition changed:
uu
/pp
to start searching,U
/P
to repeat search. - [2015-03-07 Sat]: Added bindings for
universal-argument
, and commands to pop local and global marks. - [2015-03-07 Sat]: Added hook for customizing key-bindings. Simplifies
eval-buffer
to test new bindings while maintaining custom bindings.
fingers-mode
is a global minor mode that introduces key bindings for
navigation and text manipulation. It relies on modal editing to reduce the
usage of modifiers like Control and Meta. It introduces a new keymap to
trigger commands without the need for modifiers and can easily be toggled to
fall back to inserting text as usually in Emacs.
fingers-mode
is activated for every buffer except the minibuffer. While
this works best for me, you might want to exclude additional major modes like
magit
:
(add-to-list 'fingers-mode-excluded-major-modes 'magit-status-mode)
The following tables describe the bindings when fingers-mode
is active. The
commands are sometimes abbreviated for formatting, you can always use C-h k
to get the actual binding. The tables are based on the
Workman keyboard layout, but there
are mappings available for Qwerty
and Neo. The mappings modify the main
fingers-mode-map
where the layout is based on ease of key presses rather
than mnemonics. The bindings available in the x-map
and c-map
are usually
mnemonics and based on the standard Emacs bindings, so they aren’t modified
by the mappings by default.
While fingers-mode
aims to offer convenient bindings for most cases, it does
not modify the underlying bindings. You can always fall back to regular
bindings like C-n
for moving to the next line.
Available bindings on the left hand (Workman layout):
The home row binds common killing t
, copying T
and there are also
functions to add a pair of surrounding strings a
or remove them s
, similar to
paredit’s splice command. These commands expect a region as an argument that
can either be active prior to triggering the command or be selected
afterwards. Regular navigation commands on the right hand can be used to
make a selection but also a set of bindings on the left hand allow to
quickly specify a region. There are bindings available for selecting the
char v
, word h
, symbol t
or line g
under point as well as the region
between s
or with an enclosing pair a
. Upper case versions trigger
selections with surrounding whitespace or from point till line end for G
.
The following table lists the region specifiers on the left hand (Workman layout):
The available pairs are currently ()
, {}
, []
, <>
, “”, “ and
”. Additionally, for pair selection you can double press the key for a
command and fingers-mode
will identify the next pair that starts left of
point. For example, if |
represents point and []
the active region:
def greet(): Unit = {
println("Hello, |world")
}
To remove the surrounding double quotes, you can use s
. It will prompt for
a starting character of the pair you are trying to remove. You can use s"
to effectively remove the string delimiters (splice):
def greet(): Unit = {
println(|Hello, world)
}
Pressing ss
will yield the same result, as the double quote to the left
of point is the first character that is identified as the start of a pair:
def greet(): Unit = {
println(|Hello, world)
}
You can use t
to kill a region. The command expects either a region
specifier or a navigation command (for example, next line). In the above
snippet pressing tss
will yield:
def greet(): Unit = {
println(|)
}
The first s
is a region selector (between pair) and the second s
causes
fingers-mode
to look to the left for the first starting character of a
supported pair. In this case, the (
is interpreted as the start of a pair
and everything until the matching parenthesis is killed. Now, you can select the
function body explicitly via ta{
:
def greet(): Unit = |
The double key press is simply looking to the left of point for the next
character that is the start of a known pair, it does not look whether the
character has a well balanced matching end character. Selecting a region
based on the pairs ()
, {}
, []
and <>
will attempt to find the
matching end character. For example:
(defun hello-there ()
(interactive)
(message "1 + |1 + 2 + 3 = %s" (+ 1 1 2 3)))
Pressing ts(
will yield:
(defun hello-there ()
(interactive)
(|))
Or for:
(defun hello-there| ()
(interactive)
(message "1 + 1 + 2 + 3 = %s" (+ 1 1 2 3)))
Pressing ta(
will kill the entire function definition and yield:
|
Notice that the a
is a region specifier similar to s
, but that includes
the surrounding pair. Many of the region specifiers have an upper case
analog that includes the surrounding whitespace. For example, pressing taa
for the following snippet:
(defun hello-there ()
(interactive)
(mess|age "1 + 1 + 2 + 3 = %s" (+ 1 1 2 3)))
Removes the contents and the surrounding ()
pair:
(defun hello-there ()
(interactive)
|)
Pressing tAA
would clean up the whitespace and yield:
(defun hello-there ()
(interactive)|)
Notice that the same region specifiers work for marking as well, bound by
default to SPC
. Pressing SPCaa
for the above snippet yields the
following active region:
[(defun hello-there ()
(interactive))]
Where ]
also denotes point. Alternatively, pressing SPCh
for the
following snippet:
(defun he|llo-there ()
(interactive))
Yields the active region:
(defun [hello]-there ()
(interactive))
Where pressing SPCT
(that’s SPC
followed by T
) would yield:
(defun[ hello-there ]()
(interactive))
T
causes the selection of the symbol hello-there
plus surrounding
whitespace.
Any navigation command can be used to manually define the active
region. For example, pressing SPCG
for the following snippet:
(defun |hello-there ()
(interactive))
Activates a region from point till end of line:
(defun [hello-there ()]
(interactive))
Pressing SPC'
has the same effect, where '
is the navigation command to
move point to then end of the line.
Active regions can be used as input to the commands to kill a region or
enclose it with a pair. For example, pressing t
with the acitve region in
the above snippet yields:
(defun |
(interactive))
So pressing any of SPC't
, SPCGt
, t'
, tG
has the same effect.
Here’s a demo for some of the examples above:
All of these manipulation commands are text based rather than identifying syntactic components in the buffer. The goal are generally applicable commands for text manipulation, rather than major-mode specific ones.
While many of these bindings are specific to fingers-mode
, many common
bindings are easily available as well. Bindings that are prefixed by C-x
or C-c
are available by pressing x
or c
respectively. For example, to
save the current buffer, you can press xs
rather than C-x C-s
. Modify
fingers-x-bindings
and fingers-c-bindings
if a common binding for either
is missing. In addition, similar to god-mode, g
and G
bind meta prefixes
M-
and C-M-
respectively. So pressing g;
is like pressing M-;
and
commonly triggers comment-dwim
.
The universal-argument
is bound to b
by default to easily toggle between
different modes for commands. For example, pressing w
will join the current
line to the previous one, pressing bw
will join the next line to the current
one.
Available bindings on the right hand (Workman layout), prefixs are color coded::
Regular cursor motion is available on the home row via bindings that mirror
Vim’s hjkl
for left, down, up and right plus additional bindings for
jumping to the beginning and end of the current line respectively. Upper
case variants increase the jump range. For example: n
triggers left-char
and N
triggers backward-word
, or y
to jump to the beginning of the
line, Y
to jump to the beginning of the buffer.
The top row introduces several prefixes to make use of registers and
isearch. For registers, you can store a point in register a
by pressing
fna
and return to it by pressing ffa
. Supplying a prefix works as
regularly. To store the current window configuration in b
you can use C-u
ffb
and to restore it ffb
.
Middle and ring finger start prefixes for searching down u
and up p
. To
start a search from point forward, press uu
and enter the search string
(pp
for backwards search). For example, pressing uuwhite
for the
following snippet:
(defvar fing|ers-region-specifiers
'((char . ?v)
(char-and-whitespace . ?V)
(line . ?G)
(line-rest . ?g)
(word . ?h)
(word-and-whitespace . ?H)
(symbol . ?t)
(symbol-and-whitespace . ?T)
(between-whitespace . ?c)
(with-surrounding-whitespace . ?C)
(inside-pair . ?s)
(with-pair . ?a)
(with-pair-and-whitespace . ?A))
"Mapping from region type to identifier key")
(defun fingers-region-specifier (type)
(cdr (assoc type fingers-region-specifiers)))
Will move point and highlight the occurrences of white
(denoted by []
where the first ]
is also point):
(defvar fingers-region-specifiers
'((char . ?v)
(char-and-[white]space . ?V)
(line . ?G)
(line-rest . ?g)
(word . ?h)
(word-and-[white]space . ?H)
(symbol . ?t)
(symbol-and-[white]space . ?T)
(between-[white]space . ?c)
(with-surrounding-[white]space . ?C)
(inside-pair . ?s)
(with-pair . ?a)
(with-pair-and-[white]space . ?A))
"Mapping from region type to identifier key")
(defun fingers-region-specifier (type)
(cdr (assoc type fingers-region-specifiers)))
Exit isearch via RET
and continue searching downward via U
or upward
via P
. Alternatively you can press uo
to trigger occur
for the
current search string white
.
Additionally you can use ut
and pt
to jump to the next or previous
occurrence of the symbol under point. For jumping to occurrences of the word
under point you can use uh
and ph
respectively. Pressing ut
in the
original snippet:
(defvar finge|rs-region-specifiers
'((char . ?v)
(char-and-whitespace . ?V)
(line . ?G)
(line-rest . ?g)
(word . ?h)
(word-and-whitespace . ?H)
(symbol . ?t)
(symbol-and-whitespace . ?T)
(between-whitespace . ?c)
(with-surrounding-whitespace . ?C)
(inside-pair . ?s)
(with-pair . ?a)
(with-pair-and-whitespace . ?A))
"Mapping from region type to identifier key")
(defun fingers-region-specifier (type)
(cdr (assoc type fingers-region-specifiers)))
Will move point to the next occurrence of the symbol
fingers-region-specifiers
:
(defvar fingers-region-specifiers
'((char . ?v)
(char-and-whitespace . ?V)
(line . ?G)
(line-rest . ?g)
(word . ?h)
(word-and-whitespace . ?H)
(symbol . ?t)
(symbol-and-whitespace . ?T)
(between-whitespace . ?c)
(with-surrounding-whitespace . ?C)
(inside-pair . ?s)
(with-pair . ?a)
(with-pair-and-whitespace . ?A))
"Mapping from region type to identifier key")
(defun fingers-region-specifier (type)
(cdr (assoc type |fingers-region-specifiers)))
Pressing uo
would trigger occur
and show you all of the occurrences of
the last symbol or word you jumped to via ut=/=pt
or uh=/=ph
.
fingers-mode
has defaults that I tuned for the Workman layout, but
currently there are mappings available for the Qwerty and the Neo
layout. You can use fingers-qwerty.el
and fingers-neo.el
as templates to
add mappings for a different layout.
The Qwerty mappings have one difference to the Workman bindings: The
bindings for m
and c
on the Workman layout are switched so that the
common prefix C-c
is in the usual place. More specifically, pressing c
for the Qwerty layout will trigger the bindings in fingers-mode-c-map
and
pressing v
will trigger macro related commands that are bound to m
on
the Workman layout.
fingers-mode
has no external requirements, it only loads thingatpt
which
is bundled with GNU Emacs. But I personally use several extensions for which
I either use unbound keys or replace existing bindings. For example, I
replace the built-in functionality for query-replace
with
anzu’s version that offers
immediate visual feedback:
(define-key fingers-mode-map (kbd "r") 'anzu-query-replace)
(define-key fingers-mode-map (kbd "R") 'anzu-query-replace-regexp)
Or I use helm to replace find-file
or execute-extended-command
via:
(define-key fingers-mode-x-map (kbd "f") 'helm-find-files)
(define-key fingers-mode-x-map (kbd "x") 'helm-M-x)
You can find more of my personal customizations here.
You can use the following snippet to color the mode-line to indicate
whether fingers-mode
is active:
(defun fingers-mode-visual-toggle ()
(let ((faces-to-toggle '(mode-line mode-line-inactive))
(enabled-color (if terminal-p "gray" "#e8e8e8"))
(disabled-color (if terminal-p "green" "#a1b56c")))
(cond (fingers-mode
(mapcar (lambda (face) (set-face-background face enabled-color))
faces-to-toggle))
(t
(mapcar (lambda (face) (set-face-background face disabled-color))
faces-to-toggle)))))
(add-hook 'fingers-mode-hook 'fingers-mode-visual-toggle)
fingers-mode
is based on excellent ideas found in
boon and
god-mode.
Compared to god-mode
, fingers-mode
is a bigger step away from the usual
key bindings in Emacs. Both share the M-
and M-C-
prefix via g
and G
,
and the common bindings for the C-x
and C-c
prefix are accessible via x
and c
respectively. fingers-mode
also bundles several text manipulation
commands and introduces new bindings for these and for navigation commands.
fingers-mode
is very similarly to boon
with a couple of details that are
different (in no particular order):
fingers-mode
has no external dependencies. This means that the package is standalone, but also that some of the text manipulation commands might not accomodate specific cases. More specifically,fingers-mode
does not rely on the excellentexpand-region
, which introduces selection helpers specific to major modes. Instead, the goal are simple and easily understandable defaults that are applicable to all text.- Navigation commands are bound a little differently: Most navigation is on
the home row for
fingers-mode
, rather than split acroos home and top row.fingers-mode
also uses VIM-like keys (hjkl for left, down, up and right) on home row but in the default position, not shifted to the left. The search commands are available on the right side as well and there are some helpers to jump to the next or previous occurrence of a word or symbol. - Several of the bindings for manipulation commands are different as well, but I imagine they are mostly specific to personal taste and usage frequency.
- No dependency on a specific keyboard layout. Some mappings are included,
and adding one should be straight-forward. The mappings are currently only
for the main bindings, not the bindings behind the
c
andx
prefix which are following mnemonics as the original ones. For example,xs
still triggers the save command orxv
is still a prefix for version control related commands.
Compared to both, fingers-mode
is by default active for every mode, except
the minibuffer. I prefer this consistency, but you can customize this to
exclude modes like dired
and magit
, similarly to boon
and god-mode
.