notes from /dev/null

by Charles Choi 최민수


Improving Emacs isearch Usability with Transient

18 Dec 2023  Charles Choi

Emacs incremental search (isearch) has an embarrassingly rich feature set. That said, I rarely use isearch beyond its basics for the twofold reasons:

  • There are many isearch commands that are difficult to discover, much less remember.
  • The default keybindings for said commands are cursed. (Seriously, C-M-@ to call isearch-query-replace-regexp ?)

However with the addition of the Transient package to Emacs 29.1, there is now recourse to work around these objections: when in isearch mode, we can make available an isearch-specific Transient menu. This post shows you how.

Use Case/Workflow

This is the use case I want solved:

When in basic isearch, I want to be able to use the function key (in this case <f2>) to invoke a Transient menu offering a set of isearch-specific commands. These commands would be grouped as follows:

  • Edit commands on the search string/regexp

    • isearch-edit-string
    • isearch-yank-word-or-char
    • isearch-yank-symbol-or-char
    • isearch-yank-line
    • isearch-yank-kill
    • isearch-forward-thing-at-point (imho, this is an amazing odd-ball function)
  • Replace commands (provided the buffer is not read-only)

    • isearch-query-replace
    • isearch-query-replace-regexp
  • Toggle commands

    • isearch-toggle-regexp
    • isearch-toggle-symbol
    • isearch-toggle-word
    • isearch-toggle-case-fold
    • isearch-toggle-lax-whitespace
  • Misc

    • isearch-occur

While not all isearch commands are enumerated in this menu, this selection seems more than sufficient for my routine needs.

Demo

The following demo screenshots show this menu at work. First is a screenshot of Emacs in basic isearch mode, searching for the string “Screenshot 2”.

Screenshot of Emacs with basic isearch.

Pressing the <f2> button will activate the isearch-specific Transient menu shown below. The user can then select the next action by either a key press or mouse button (typically 1) press.

Screenshot of Emacs isearch Transient menu.

Implementation

The following source shows the implementation of the workflow above, patterned after the examples shown in Transient Showcase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
(require 'transient)

(transient-define-prefix cc/isearch-menu ()
  "isearch Menu"
  [["Edit Search String"
    ("e"
     "Edit the search string (recursive)"
     isearch-edit-string
     :transient nil)
    ("w"
     "Pull next word or character word from buffer"
     isearch-yank-word-or-char
     :transient nil)
    ("s"
     "Pull next symbol or character from buffer"
     isearch-yank-symbol-or-char
     :transient nil)
    ("l"
     "Pull rest of line from buffer"
     isearch-yank-line
     :transient nil)
    ("y"
     "Pull string from kill ring"
     isearch-yank-kill
     :transient nil)
    ("t"
     "Pull thing from buffer"
     isearch-forward-thing-at-point
     :transient nil)]

   ["Replace"
    ("q"
     "Start ‘query-replace’"
     isearch-query-replace
     :if-nil buffer-read-only
     :transient nil)
    ("x"
     "Start ‘query-replace-regexp’"
     isearch-query-replace-regexp
     :if-nil buffer-read-only
     :transient nil)]]

  [["Toggle"
    ("X"
     "Toggle regexp searching"
     isearch-toggle-regexp
     :transient nil)
    ("S"
     "Toggle symbol searching"
     isearch-toggle-symbol
     :transient nil)
    ("W"
     "Toggle word searching"
     isearch-toggle-word
     :transient nil)
    ("F"
     "Toggle case fold"
     isearch-toggle-case-fold
     :transient nil)
    ("L"
     "Toggle lax whitespace"
     isearch-toggle-lax-whitespace
     :transient nil)]

   ["Misc"
    ("o"
     "occur"
     isearch-occur
     :transient nil)]])

(define-key isearch-mode-map (kbd "<f2>") 'cc/isearch-menu)

Verified in Emacs 29.1

Closing Thoughts

Since implementing this Transient menu, isearch has become my primary interface to query-replace and query-replace-regexp. Also the ability to yank different object types and toggle different search behavior as I go along has been revelatory. Despite that fact that these features have always been there, their discoverability and keybindings have made them all but unusable to me. With Transient these features are finally made usable.

While the inclusion of Transient in Emacs 29.1 is relatively recent, the potential to revisit older Emacs workflows with this new capability seems to be a green field. I look forward to seeing what others do with Transient.

References

Addendum - 4 March 2024

The above code has been modified, packaged, and published on MELPA as cc-isearch-menu. More info here.

emacs

 

AboutMastodonInstagramGitHub

Feeds & TagsGet Captee for macOS

Powered by Pelican