Using Emacs advice to silence messages from functions
— Kaushal ModiUsing the :around
advice to prevent messages from being displayed in
the echo area or the ∗Messages∗ buffer.
Today I was working on a minor cleanup of the ox-hugo
package and as
a part of that, I figured out a way to reduce the noisy messages
printed in the ∗Messages∗
Random tidbit: You can quickly access the ∗Messages∗ buffer using the
default binding C-h e
.
buffer. This post describes how I used the 📖 Emacs Advice
feature to solve the noisy-messages-buffer problem.
- What is this ‘advising’?
- In Emacs, advising
Advice with a ‘c’ is the noun form. The noun form is used
everywhere in the Emacs advising API e.g.
advice-add
,advice-remove
. Advise with an ’s’ is the verb form. More Reading. is a means to modify a function defined in any library — You can modify the arguments to the function, return values of that function, run other functions before and/or after that advised function, .. or may be just replace that function with something else entirely!
Problem Statement #
If I run C-c C-e H A
This is the default binding of ox-hugo
to export all the post
subtrees in the current Org file.
in this site’s Org file, I will see a wall of messages, which won’t be
too useful to look at if I am exporting dozens of posts in one go.
Here’s a snippet from that which shows the text printed while two of the posts were being exporting:
[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
org-babel-exp process nim at position 246918...
Evaluation of this nim code block is disabled.
org-babel-exp process shell at position 246985...
org-babel-exp process shell at position 247505...
org-babel-exp process shell at position 248054...
org-babel-exp process nim at position 250620...
Evaluation of this nim code block (code__musl_config_nims) is disabled.
org-babel-exp process yaml at position 253830...
Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md
[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
org-babel-exp process nim at position 261418...
Evaluation of this nim code block (code__nim_bindings_importcpp) is disabled.
org-babel-exp process nim at position 262802...
Evaluation of this nim code block (code__nim_std_list_bindings) is disabled.
org-babel-exp process cpp at position 266921...
org-babel-exp process nim at position 267957...
Evaluation of this nim code block (code__nim_bindings_emit) is disabled.
org-babel-exp process nim at position 268861...
Evaluation of this nim code block (code__nim_bindings_test_nim_code) is disabled.
org-babel-exp process text at position 269849...
org-babel-exp process nim at position 270412...
Evaluation of this nim code block (code__nim_bindings_toSeq) is disabled.
org-babel-exp process nim at position 271808...
Evaluation of this nim code block (code__nim_bindings_full_code) is disabled.
Wrote /home/kmodi/hugo/scripter.co/content/posts/binding-nim-to-c-plus-plus-std-list/index.md
After applying the advices described in this post, that output reduced to this:
[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
Course of action #
Here is how I tackled this problem:
- Find the functions responsible for printing those messages.
- Figure out how to silence those messages.
- Advise those functions (temporarily, while the export is happening) to not print those messages.
1 Finding the culprit functions #
In Code Snippet 1, I have highlighted 2 lines. I first needed to figure out which functions printed those messages. I’ll show two methods of finding sources of any printed messages.
Using plain-old grep or ripgrep (rg) #
This method is pretty easy (but not robust) to use if the search string is unique enough. The first highlighted line above is a good candidate for this:
org-babel-exp process nim at position 246918…
The message gives me a clue that it’s originating somewhere in the
Org Babel source code (one of the ob-*.el
files). So I cd to the
Org source directory and search for the “org-babel-exp process ..”
string using rg
..
.. and sure enough, there is this one and only one hit .. in the
org-babel-exp-src-block
function in lisp/ob-exp.el
.
But this method is not robust for some obvious reasons like,
- the string being grepped might not be present in raw literal form like above,
- may be it’s created through variable interpolation, or
- may be that whole string is not present on the same line; may be it wrapped to the next line.
While this grep method helped find the source of “org-babel-exp process ..”, let’s use a better way of source tracing for the next message.
Using debug-on-message
#
From C-h v debug-on-message
, we see:
If non-nil, debug if a message matching this regexp is displayed.
This means that each time a message gets displayed that matches the regular expression set in this variable, we get a backtrace to that message. Who said we can get backtraces for errors only‽ 🤓
The second highlighted line from Code Snippet 1 is:
Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md
After evaluating (setq debug-on-message "Wrote")
, when I exported this post, I saw a backtrace that looked
like this.
Debugger entered--Lisp error: "Wrote /home/kmodi/hugo/scripter.co/content/posts/u..."
write-region(nil nil "/home/kmodi/hugo/scripter.co/ ..
basic-save-buffer-2()
basic-save-buffer-1()
..
basic-save-buffer(nil)
save-buffer()
write-file("/home/kmodi/hugo/scripter.co/content/posts/using-e...")
..
org-export-to-file(hugo "/home/kmodi/hugo/scripter.co/content/posts/using-e..." ..
org-hugo-export-to-md(nil :subtreep nil)
..
The top line of that backtrace shows the source of that message:
write-region
.
Remember to reset debug-on-message
back to nil by evaluating
(setq debug-on-message nil)
once
you are done with this debug!
2 Figure out how to silence those messages #
Now that I knew that I needed to alter the org-babel-exp-src-block
and write-region
functions
There was a third function too that I found using this second method:
table-generate-source
. This was printing “Generating source…”
when parsing each table.el
message.
, I wanted to quiet these messages in both the echo area and the
∗Messages∗ buffer, but only when exporting all the post
subtrees from a file.
Emacs provides us ways to separately control the displaying of messages in those regions:
- Prevent messages in the echo area
- From 📖 Elisp –
Displaying Messages
You can visit the same manual page from within Emacs by
C-h i g (elisp)Displaying Messages
. , ifinhibit-message
is set to t ,message
outputs are not shown in the echo area (though they would still be logged in the ∗Messages∗ buffer. - Prevent messages in the ∗Messages∗ buffer
- From 📖 Elisp
– Logging Messages
C-h i g (elisp)Logging Messages
, ifmessage-log-max
is set to nil , no messages will be printed in the ∗Messages∗ buffer.
3 Applying the ‘silence’ advice #
With that information, I created this advising function which calls the function name it receives in its first argument.
|
|
Note that the above function does not set the inhibit-message
and
message-log-max
variables globally (e.g. using setq
). We do not
want to permanently stop the printing of messages for all the
functions! That’s why they are set in the let
scope or the local
scope, and the orig-fun
is called within that scope.
This snippet, though, only defines the advising function. The advices
are actually applied using the advice-add
The counterpart of advice-add
is advice-remove
, to remove the
advising functions from the original functions.
function.
Emacs provides a variety of Advice Combinators. These combinators
are used to specify how an advice should be applied to the original
function. I have found the :around
combinator to usually fit my
needs because the advices I add typically look like:
- Do something before.
- Set some variables in a
let
scope. - Call the original function with its arguments inside that scope.
- Do something afterwards.
As I wanted to apply the same advice to multiple functions
(org-babel-exp-src-block
and write-region
), I used dolist
to
loop through them and advise them to 🤫 :
|
|
Limiting the scope of these advices #
You might wonder — If ox-hugo
is applying these advices, wouldn’t
it affect the global behavior of write-region
, etc. outside of
ox-hugo
exports as well?
The answer is “no”.
In ox-hugo
, the advice-add
calls are made just before the “export
all post subtrees” operation begins. Once the export command finishes,
advice-remove
calls remove those advices from those functions. So
those advices are effective only for the duration of the export.
Details of the implementation can be seen in this ox-hugo
commit.
Conclusion #
The main effort here was to (i) identify the problem, (ii) find out which functions were causing the problem, and (iii) what knobs or variables are available to us to help fix that.
Once that is done, you write an advising function and advice-add
that function to the original function using a combinator of your
choice
If you are not sure which combinator to use, pick :around
😃.
.
In this post, I am using Emacs advices to make ox-hugo
exports less
noisy. But you can add similar advices to your Emacs config
You can find the ways I am using advices in my Emacs config here.
to tweak and fine-tune any function that’s getting on your nerves
😄.