Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 58

Mastering `advice-add`: `:before`, `:after`, and `:around`

Recommended resources

Introduction

Welcome back! In our last lesson, we introduced the concept of function advice as a clean, modular way to modify the behavior of existing Emacs functions without touching their source code. You learned that advice is like giving stage directions to a function, allowing you to run your own code before, after, or even in place of the original.

This lesson moves from theory to practice. Our goal is to use advice-add with the :before, :after, and :around combinators to modify function behavior. We will work through practical examples for each combinator, giving you hands-on experience in using this powerful tool to start molding Emacs to your specific needs. This is a fundamental skill for the deep customization that makes Emacs so powerful for writers, developers, and thinkers.


1. The advice-add Function: A Quick Review

As we discussed, the modern way to advise functions is with advice-add. Its basic structure is:

(advice-add 'TARGET-FUNCTION :WHERE #'ADVISING-FUNCTION)

  • TARGET-FUNCTION: The symbol of the function you want to modify (e.g., 'save-buffer).
  • :WHERE: The combinator keyword that specifies how to apply the advice (e.g., :before).
  • ADVISING-FUNCTION: The symbol of the function you wrote that contains the new logic (e.g., #'my-custom-logic).

To remove advice, you simply use advice-remove with the same arguments:

(advice-remove 'TARGET-FUNCTION #'ADVISING-FUNCTION)

This ability to easily add and remove advice makes it a safe and flexible tool for experimentation.


2. The :before Combinator: Running Pre-Actions

The :before combinator runs your advising function before the original function is called. The arguments passed to the original function are also passed to your advising function, but its return value is ignored. This makes it perfect for setup tasks, validation, or actions that need to happen just before the main event.

A classic and highly useful customization for anyone who writes is to automatically clean up trailing whitespace from a file just before it's saved.

Your Task: Automatically Clean Up on Save

Let's create an advice that does exactly this.

  1. Define the advising function. This function will simply call the built-in Emacs command delete-trailing-whitespace. To make our function robust, we'll define it to accept any number of arguments using &rest, even though we won't use them. This ensures it won't cause an error if the advised function (save-buffer) is ever called with arguments.

    (defun my-cleanup-whitespace-before-save (&rest args)
      "Delete trailing whitespace in the current buffer."
      (delete-trailing-whitespace))
    
  2. Add the advice. Now, connect this function to save-buffer using :before advice.

    (advice-add 'save-buffer :before #'my-cleanup-whitespace-before-save)
    
  3. Test it.

    • Open your *scratch* buffer or a new file.
    • Type a line of text and add several spaces at the end.
    • Save the buffer (C-x C-s).
    • Observe that the trailing spaces are automatically removed just before the save completes.
  4. Clean up. To avoid this behavior persisting if you don't want it, you can remove the advice.

    (advice-remove 'save-buffer #'my-cleanup-whitespace-before-save)
    

    You can evaluate each of these S-expressions by placing your cursor after the closing parenthesis and pressing C-x C-e.


3. The :after Combinator: Running Post-Actions

The :after combinator is the mirror image of :before. It runs your advising function after the original function has finished executing. Like :before advice, it receives the same arguments as the original function and its return value is ignored. This is ideal for logging, sending notifications, or performing cleanup that relies on the main function having already completed.

For a writer, getting immediate feedback can be valuable. Let's add advice that displays the buffer's word count in the echo area every time you save.

Your Task: Report Word Count After Save

  1. Define the advising function. This function will calculate the number of words between the beginning and end of the buffer and display it using message.

    (defun my-report-word-count-after-save (&rest args)
      "Display the buffer's word count in the echo area."
      (let ((wc (count-words (point-min) (point-max))))
        (message "File saved. Word count: %d" wc)))
    

    Note: count-words is a function that returns a number. (point-min) and (point-max) return the character positions of the very beginning and very end of the buffer, respectively.

  2. Add the advice.

    (advice-add 'save-buffer :after #'my-report-word-count-after-save)
    
  3. Test it.

    • With some text in a buffer, save it (C-x C-s).
    • Look at the echo area at the bottom of your Emacs frame. You should see a message like File saved. Word count: 123.
    • You can have both this :after advice and the previous :before advice active at the same time, demonstrating their modularity.
  4. Clean up. Remember to remove the advice when you're done experimenting.

    (advice-remove 'save-buffer #'my-report-word-count-after-save)
    

4. The :around Combinator: Taking Full Control

The :around combinator is the most powerful and flexible. It effectively replaces the original function with your advising function. Your function receives the original function itself as its first argument, followed by any arguments that were passed to it.

This gives you complete control:

  • You can run code before calling the original function.
  • You can choose whether or not to call the original function.
  • You can modify the arguments you pass to the original function.
  • You can inspect or modify the return value from the original function.
  • You can run code after the original function returns.

Because you are taking over execution, it is your responsibility to call the original function using funcall or apply if you want its behavior to be included.

Let's look at some real-world examples and then create our own.

Some Emacs advice - Occasionally Cogent

The article 'Some Emacs advice' gives excellent, practical examples of using different advice types. Let's focus on its use of :around.

Please read the section 'Examples of Use'. Pay special attention to the final example, 'Making My Own Little Theming System', which uses :around advice to wrap the load-theme function. Notice how the advising function receives the original function as f and then calls it with (apply f ...) in the middle of its own logic.

Your Task: Modify Command Behavior

Let's use :around advice for something tangible in your editing workflow. The command forward-word (bound to M-f by default) moves the point forward by one word. Let's advise it to move forward by two words instead.

  1. Define the advising function. This function needs to accept the original function and its optional argument. forward-word takes an optional numeric argument for how many words to move. We will intercept this, double it, and then pass the new value to the original function.

    (defun my-double-word-movement (original-function &optional arg)
      "Advice to make a word-motion command move by twice the amount."
      ;; If arg is nil, `forward-word` treats it as 1.
      ;; We'll calculate the new amount, doubling the intended movement.
      (let ((new-arg (* 2 (or arg 1))))
        ;; Now, call the original function with our new argument.
        (funcall original-function new-arg)))
    

    Here, (or arg 1) handles the case where no argument is given. If arg is nil, it evaluates to 1. We then double that amount.

  2. Add the advice.

    (advice-add 'forward-word :around #'my-double-word-movement)
    
  3. Test it.

    • Place your cursor in a line of text.
    • Press M-f. You should see the point jump forward two words.
    • Now, try giving it a prefix argument: C-u 3 M-f. This would normally move 3 words. With our advice, it should move 6 words (3 * 2).
  4. Clean up. This is a fun experiment, but you'll likely want the default behavior back!

    (advice-remove 'forward-word #'my-double-word-movement)
    

This example clearly shows the power of :around advice to fundamentally alter how a command operates by intercepting and modifying its arguments before execution.


Conclusion

In this lesson, you put theory into practice by using the three main advice combinators to solve practical problems. You have seen how to inject behavior before, after, and around existing functions, giving you a powerful new layer of control over your Emacs environment.

Key Takeaways:

  • :before is for pre-flight checks and setup actions that don't need to alter the main function's execution path (e.g., cleaning whitespace before saving).
  • :after is for post-execution actions like logging or notifications (e.g., reporting the word count after saving).
  • :around gives you complete control to wrap a function, modify its arguments, change its return value, or even prevent it from running. It is your responsibility to use funcall or apply to execute the original function.

For a handy reference, the article "Emacs Lisp: Advice Combinators" provides a wonderful summary table.

Emacs Lisp: Advice Combinators - A Scripter's Notes

As you continue to use advice, you may want a quick way to remember what each combinator does. This resource has a perfect summary table.

Scroll down to the 'Summary' section and review 'Table 1'. It provides a concise description of :before, :after, :around, and several other more advanced combinators you may explore later. You can bookmark this page for future reference.


Preview of the Next Lesson

With great power comes the potential for great confusion! Advising functions can sometimes lead to unexpected behavior or errors. In our next lesson, we will begin our module on debugging. You will learn how to read and interpret common Elisp error messages, which is the first step toward diagnosing and fixing problems in your own custom code.