Usage

Calling Visual Replace

Visual Replace needs to be bound to a key to be of any use.

Choose a reasonably short key combination and bind visual-replace to it. It should be reasonably short, because visual-replace, by default, uses the key combination it’s called with as prefix for the commands available in the minibuffer.

Here’s an example that uses M-% as key combination, since this is bound by default to query-replace, which Visual Replace then, well, replaces:

(use-package visual-replace
  :defer t
  :bind (("M-%" . visual-replace)
         :map isearch-mode-map
         ("M-%" . visual-replace-from-isearch))
  :config
  (define-key visual-replace-mode-map (kbd "M-%")
              visual-replace-secondary-mode-map))

The above example also binds M-% in isearch, so you can just switch from isearch to Visual Replace. Additionally, while Visual Replace is active M-% is the prefix for Visual Replace commands, so, for example, toggling regexp mode on and off is M-% r.

An alternative, which you might prefer to try things out, is to replace query-replace and others with Visual Replace. This then uses whatever shortcut you’ve already installed.

(use-package visual-replace
  :defer nil
  :config
  (visual-replace-global-mode 1))

Once this is done, launch visual-replace with the keybinding you chose.

Visual Replace Mode

When Visual Replace is running, you’ll see, something like the following in the minibuffer Replace from point […]: ┃ →. The text before the arrow is the text to replace and the text after the arrow is the replacement. You can navigate back and forth with TAB or by moving the cursor.

See also the example below.

Screen grab showing Visual Replace in action

Once both fields are filled, press RET to execute the replacement.

When there’s no replacement RET instead moves the cursor to the replacement, in case muscle memory kicks in and you type: text to replace RET replacement RET. That’ll work.

The prompt also displays the mode of replacement:

  • textreplacement executes string-replace

  • text →? replacement executes query-replace

  • text →.* replacement executes replace-regexp

  • text →?.* replacement executes query-replace-regexp

After typing a few characters of the string to match visual-replace enters preview mode, and highlights the matches. It also scrolls the window to keep at least one example of matches visible. You can also press up and down to go through the matches.

In Visual Replace mode:

  • TAB navigates between the text to replace and the replacement string

  • RET switches to the replacement string, the first time, then executes the replacement

  • M-% r toggles regexp mode on and off. You know this mode is on when a .* follows the arrow.

  • M-% q toggles query mode one and off, that is, it toggles between calling replace-string and query-replace. You know this mode is on when a ? follows the arrow. For an alternative way of replacing only some matches, see Single replacements.

  • M-% SPC switches between different scopes: full buffer, from point, in region. The scope is indicated in the prompt. Additionally, for from point and in region, the region is highlighted.

  • M-% w toggle limiting search to whole words. You know this mode is on when a w follows the arrow.

  • M-% c toggle case-fold. You know this mode is on when a c follows the arrow.

  • M-% s toggle lax whitespace. You know this mode is on when (lax ws) follows the arrow.

  • M-% b toggle backward replace. You know this mode is on when a follows the arrow. This only matters if query search mode

is on of when a replacement contains \\#.

  • <up> and <down> move the cursor to the next or previous match, scrolling if necessary.

  • M-% a applies a single replacement, to the match right under the cursor or following the cursor, then move on to the next match. With a prefix argument N, apply N replacements. See also Single replacements.

  • M-% u calls undo on the original buffer, to revert a previous replacement. With a prefix argument N, repeat undo N times.

  • As usual, C-p and C-n go up and down the history, like on any prompt.

(Reminder: replace M-% with the keyboard shortcut you chose.)

If you leave visual-replace without confirming, with C-g, you can continue where you left off next time by going up in the history.

See Search in the Emacs manual for details of the different modes listed above.

Yank and Pop

Yank, usually bound to C-y, works differently in Visual Replace than it does normally. In Visual Replace mode, it calls visual-replace-yank.

  • In the search section, yanking copies text from the current buffer into the search section. This avoids typing text when it’s right under the point.

    You can also move to a match with <up> and <down> to capture more text from the buffer.

  • In the replacement section, yanking copies text from the search section. This avoids typing the search string again when you just want to make some small changes to it.

The normal yank can be executed by calling yank-pop, usually bound to M-y.

This can be configured by editing visual-mode-map. For example, to use the normal yank commands, you can do:

(define-key visual-replace-mode-map [remap yank] nil)
(define-key visual-replace-mode-map [remap yank-pop] nil)

Single replacements

If you want to replace only some matches within the scope, you can:

  • use the query-replace UI to go through all matches using M-% q, then typing RET to enter Query Replace mode. `

  • in preview mode, click on the replacements you want to apply. You can scroll the buffer as needed, normally or, from the minibuffer with <up> and <down>.

  • navigate to the replacements you want to apply with <up> and <down>, the call M-% a to apply one replacement.

    On Emacs 29.1 or later, this enters a mode that allows applying replacement with a, the last part of the key sequence, and also moving through the matches with <down> or <up>. u reverts the last replacement.

Customization

This section lists a few of the most interesting customization options available in visual replace. Call M-x customize-group visual-replace to see all options. For face customization, see the :ref`next section<faces>`.

visual-replace-previewcustomization option

With this option enabled, Visual Replace highlights matches and offer a preview of their replacements. This is enabled by default.

visual-replace-first-matchcustomization option

With this option enabled, Visual Replace always tries to have at least one match visible in the preview, even if it means jumping to another section of the buffer. This is enabled by default.

keep-initial-positioncustomization option

With this option enabled, Visual Replace goes back to the point it was called from, even if the point was moved during preview, to display the first match, or manually with <down> or <up>.

Note that in the case where the point is moved during preview, Visual Replace sets a mark at the original location, to go back too if necessary.

visual-replace-display-totalcustomization option

By default, in preview mode, visual Replace only searches for and display matches in the visible portions of the buffer. With this option enabled, Visual Replace searches the whole buffer, in an idle timer, and displays the total number of matches in the prompt.

When the point is on a match, the index of the match is also displayed, in front of the total.

The total might be slow to update on large buffers or when using complicated regexps.

This is not enabled by default.

visual-replace-initial-scopecustomization option

With this option set, the initial scope ignores the active region entirely and is always set to either “From Point” or “Full Buffer”.

By default, the initial scope is:

  • the active region, if there is one

  • from point if visual-replace-default-to-full-scope is nil, see below

  • the full buffer otherwise

visual-replace-default-full-scopecustomization option

With this option set, when no region is active, replacement applies by default to the full buffer, instead of to the region following the point.

visual-replace-defaults-hookcustomization option

To modify search and replace defaults, such as, for example, having searches default to regular expressions or search default to word mode, call the command that turns it on from this hook. This is called when Visual Replace is started with no initial text, so these customizations won’t apply to visual-replace-from-isearch, for example.

visual-replace-minibuffer-mode-hookcustomization option

This hook is called when Visual Replace is started in the minibuffer. It can be used to turn on query mode in all cases by registering the command visual-replace-toggle-query in this hook.

Rather than setting the as a customization, with use-package, you can force Visual Replace to call query-replace by default with:

(use-package visual-replace
  [...]
  :hook ((visual-replace-minibuffer-mode . visual-replace-toggle-query))
visual-replace-min-lengthcustomization option

This specifies the minimum number of characters that need to be typed before Visual Replace enters preview mode.

Setting this too low might result in strange highlights happening when starting to type in the match string.

Face Customization

Visual Replace relies an a large number of faces to display things the way they should be:

  • visual-replace-match, for matches with no replacement

  • visual-replace-match-highlight, for matches at point with no replacement

  • visual-replace-replacement, for match replacement

  • visual-replace-replacement-highlight, for match replacement, at point

  • visual-replace-delete-match, for text to be deleted

  • visual-replace-delete-match-highlight, for text to be deleted at point

  • visual-replace-match-count, for displaying the number of matches before the prompt

  • visual-replace-separator, for displaying the separator between the search and replacement strings in the prompt

  • visual-replace-region, for highlighting the area of the buffer to which search and replace apply

The defaults values for this faces attempt to reuse existing faces as much as possible to try and look reasonable whatever the current Emacs theme, but the result isn’t always too great. In particular, visual-replace-region, which uses the same face as the region, is typically too bright and in-your-face. It should ideally use a fainter color than the region, still visible, but not too different from the normal background as to cause readability issues.

Therefore, it’s a good idea to configure the Visual Replace faces to match your theme and preferences.

The sections below list my attempts at configuring Visual Replace for the Modus themes, now installed in Emacs by default, and the Ef themes, by the same author. This should hopefully help you get started.

The code snippets rely on after-enable-theme-hook to detect theme changes, from the Section 5.23 of the Emacs 29 Manual:

(defun run-after-enable-theme-hook (&rest _args)
   "Run `after-enable-theme-hook'."
   (run-hooks 'after-enable-theme-hook))

(advice-add 'enable-theme :after #'run-after-enable-theme-hook)

Modus Themes

(defun my-modus-themes-custom-faces ()
  (when (delq nil (mapcar (lambda (t) (string-prefix-p "modus-" (symbol-name t)))
                          custom-enabled-themes))
    (modus-themes-with-colors
      (custom-set-faces
       `(visual-replace-match-count ((t :inherit modus-themes-prompts)))
       `(visual-replace-separator ((t :inherit modus-themes-prompts)))
       `(visual-replace-match ((t :inherit modus-themes-search-success-lazy)))
       `(visual-replace-replacement ((t :background ,bg-diff-added :foreground ,fg-diff-added)))
       `(visual-replace-delete-match ((t :strike-through t :background ,bg-diff-removed :foreground ,fg-diff-removed)))
       `(visual-replace-match-highlight ((t  :inherit modus-themes-search-success)))
       `(visual-replace-delete-match-highlight ((t :strike-through t :background ,bg-diff-refine-removed :foreground ,fg-diff-refine-removed)))
       `(visual-replace-replacement-highlight ((t :background ,bg-diff-refine-added :foreground ,fg-diff-refine-added)))
       `(visual-replace-region ((t :background ,bg-special-faint-cold :extend t )))))))
(add-hook 'after-enable-theme-hook #'my-modus-themes-custom-faces)

Ef Themes

 (defun my-ef-themes-custom-faces ()
  (when (delq nil (mapcar (lambda (t) (string-prefix-p "ef-" (symbol-name t)))
                          custom-enabled-themes))
    (ef-themes-with-colors
      (let ((bg-region-fainter (my-color-closer bg-region bg-main 0.3)))
        (custom-set-faces
         `(visual-replace-match-count ((,c :foreground ,prompt)))
         `(visual-replace-separator ((,c :foreground ,prompt)))
         `(visual-replace-match ((,c :background ,bg-search-lazy :foreground ,fg-intense)))
         `(visual-replace-replacement ((,c :background ,bg-added :foreground ,fg-added)))
         `(visual-replace-delete-match ((,c :strike-through t :background ,bg-removed-faint :foreground ,fg-removed)))
         `(visual-replace-match-highlight ((,c  :background ,bg-search-match :foreground ,fg-intense )))
         `(visual-replace-delete-match-highlight ((,c :strike-through t :background ,bg-removed-refine :foreground ,fg-intense)))
         `(visual-replace-replacement-highlight ((,c :background ,bg-added-refine :foreground ,fg-intense)))
         `(visual-replace-region ((,c :background ,bg-region-fainter :extend t ))))))))

  (defun my-color-closer (from to fraction)
  "Move FROM luminance closer to TO by the given FRACTION."
  (let* ((from-hsl (apply 'color-rgb-to-hsl (color-name-to-rgb from)))
         (to-hsl (apply 'color-rgb-to-hsl (color-name-to-rgb to))))
    (apply 'color-rgb-to-hex
           (color-hsl-to-rgb
            (nth 0 from-hsl)
            (nth 1 from-hsl)
            (+ (nth 2 from-hsl) (* fraction (- (nth 2 to-hsl) (nth 2 from-hsl))))))))

(add-hook 'after-enable-theme-hook #'my-ef-themes-custom-faces)

Commands

visual-replacecommand

This is the main command that starts Visual Replace and then executes the search-and-replace. It can replace replace-string, query-replace, replace-regexp and query-replace-regexp.

visual-replace-thing-at-pointcommand

This command starts a visual replace session with the symbol at point as text to replace.

visual-replace-selectedcommand

This command starts with the text within the current active region as text to replace.

visual-replace-from-isearchcommand

This command switches from an active isearch session to visual-replace, keeping the current search text and settings, such as regexp mode. This is meant to be called while isearch is in progress, and bound to isearch-mode-map.

The following commands are meant to be called while in Visual Replace mode, from visual-mode-map. By default, they’re bound in visual-replace-secondary-mode-map:

visual-replace-toggle-regexp<prefix> r , command

toggles regexp mode on and off.

visual-replace-toggle-scope<prefix> SPC, command

changes the scope of the search.

visual-replace-toggle-query<prefix> q, command

toggles the query mode on and off.

visual-replace-toggle-word<prefix> w, command

toggles the word mode on and off.

visual-replace-toggle-case-fold<prefix> c, command

toggles the case fold mode on and off.

visual-replace-toggle-backwards<prefix> d, command

toggles backward replacement on and off.

visual-replace-toggle-lax-ws<prefix> s, command

toggles the lax whitespace mode on and off.

visual-replace-next-match<down>, command

moves cursor to the next match

visual-replace-prev-match<up>, command

moves cursor to the previous match

visual-replace-apply-one<prefix> a, command

applies a single replacement, to the match at or after the cursor, then moves on to the next match. With a prefix argument N, apply N replacements instead of just one.

This command, used together with visual-replace-next-match and visual-replace-prev-match is in many cases functionally equivalent to using the query mode, but with a different interface that the possibility of changing the query as you go.

visual-replace-apply-one-repeat<prefix> a, command

on Emacs 29.1 and later, executes visual-replace-apply-one, then installs a transient map that allows:

  • repeating visual-replace-apply-one by typing the last part of the key sequence used to call visual-replace-apply-one-repeat

  • skipping matches with <down>, which calls visual-replace-next-match

  • going up the match previews with <up>, which calls visual-replace-prev-match

  • undoing the last replacement with u

  • Typing anything else deactivates the transient map.

The keybindings can be configured by modifying the map visual-replace-transient-map.

visual-replace-undo<prefix> u, command

reverts the last call to visual-replace-apply-one. This just executes undo in the original buffer. With a prefix argument N, call undo N times instead of just one.

Keymaps

visual-replace-mode-mapkeymap

This is the map that is active in the minibuffer in Visual Replace mode. You can add your own keybindings to it.

visual-replace-secondary-mode-mapkeymap

This is the map that defines keyboard shortcuts for modifying the search mode, such as r to toggle regexp mode on or off. It is bound by default in visual-replace-mode-map to the shortcut that was used to launch Visual Replace, but you can bind it to whatever you want, or define custom shortcuts directly in visual-replace-mode-map.

In the example below, C-l is bound to secondary mode map and C-r toggles the regexp mode, so it is possible to toggle the regexp mode using either C-l r or C-r.

(use-package visual-replace
  :defer t
  :bind (("C-c l" . visual-replace)
         :map visual-replace-mode-map
         ("C-r" . visual-replace-toggle-regexp))
  :config
  (define-key visual-replace-mode-map (kbd "C-l")
      visual-replace-secondary-mode-map))

Hooks

visual-replace-minibuffer-mode-hookhook

This is a normal hook that is run when entering the visual replace mode, so you can set things up just before Visual Replace starts.

visual-replace-defaults-hookhook

This is a normal hook that is run when entering the visual replace mode with no initial match or replacement, so you can provide some default mode without interfering with visual-replace-from-isearch or visual-replace-thing-at-point.

visual-replace-functionshook

Functions in this abnormal hook are called just before executing the replacement or just before building the previews. They are passed a struct of type visual-replace-args, which they can modify. You can use it to customize the behavior of the search or modify the regexp language.

Limitations

  • Visual Replace avoids executing replacement in the whole buffer during preview; it just executes them in the parts of the buffer that are currently visible. This means that the preview can show incorrect replacement in some cases, such as when replacement uses \# directly or within a \, In such cases, the preview can be wrong but execution will be correct.

    Replacements that call stateful functions in \, such as a function that increment an internal counter, will be executed too many times during preview, with unpredictable results.

    In all other cases, the preview should match what is eventually executed. If that’s not the case, please report an issue. (Reporting issues)

  • If you use visual-replace-apply-one to replace single matches, \\# in the replacement is always 1, because single matches are applied separately.