Skip to content

fgeller/fingers.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fingers.el

Modal text editing for Emacs: A collection of key bindings for navigation and text manipulation.

https://melpa.org/packages/fingers-badge.svg

Cheat sheet:

https://raw.githubusercontent.com/fgeller/fingers.el/master/images/cheatsheet.png

Installation

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.

Recent change

  • [2015-08-09 Sun]: Added fingers-replace-with-yank bound to V 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.

Details

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.

Left Hand: Text manipulation

Available bindings on the left hand (Workman layout):

https://raw.githubusercontent.com/fgeller/fingers.el/master/images/text_manipulation.png

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):

https://raw.githubusercontent.com/fgeller/fingers.el/master/images/region_specifiers.png

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:

https://raw.githubusercontent.com/fgeller/fingers.el/master/images/fingers-mode.gif

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.

Right Hand: Navigation

Available bindings on the right hand (Workman layout), prefixs are color coded::

https://raw.githubusercontent.com/fgeller/fingers.el/master/images/navigation.png

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.

Mappings

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.

Extensions

Third party libraries

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.

Visual feedback

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)

References

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 excellent expand-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 and x prefix which are following mnemonics as the original ones. For example, xs still triggers the save command or xv 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.

Releases

No releases published

Packages

No packages published