Content AND Presentation

2025-10-06 A debug helper in Elisp

A few days ago I was writing a pretty complex Elisp function which didn’t work correctly. My usual tool to use in such cases if of course Edebug – but in this case, it didn’t help much. One of the things my function did was messing around with buffers and windows, and this interferes with Edebug insisting on showing the source code in a specific window. In such cases, I miss the old-school “printf debugging”. In Elisp, you can say (message “...”) anywhere in your code – in fact, message accepts more arguments and works a bit like format – but it still is not the most useful thing. The echo area is small, and when you have more messages, you need to go to the *Messages* buffer or use tricks like my show-some-last-messages command.

I figured that it would be very useful if I could display something (like with message) but then wait for a keypress, so that I’d have enough time to read what was displayed. Well, a bit like what alert does in a browser.

Well, easy enough.

;; Note: a better version of this function comes later in the post
(defun alert (&rest message-args)
  "Like `message', but waiting for a keypress."
  (read-key (concat (apply #'format message-args)
                    "\n[press any key to continue]")))

Believe it or not, but this made my life so much nicer!

After using this for a while, I noticed a pattern: my most often usage of alert was like this.

(alert "var1: %s, var2: %s" var1 var2)

So, why not create a proper abstraction for that, that is, a function which would accept a list of symbols and use alert to show their names and values?

(defvar alert-vars-format "%s: %s")

;; Note: a better version of this comes later in the post
(defun alert-vars (&rest vars)
  "Use `alert' to show the values of VARS.
VARS should be a list of symbols."
  (funcall #'alert
           "%s"
           (mapconcat (lambda (var)
                        (format alert-vars-format
                                (symbol-name var)
                                (symbol-value var)))
                      vars
                      ", ")))

(Of course, that defvar should really be a defcustom – again, if this were a proper package, I would definitely do it that way. One day I’ll learn how to publish my code on Melpa…)

Now I can say (alert-vars 'var1 'var2) and see my variables alert​ed to me. How cool is that?

I started using that and almost immediately ran into a problem. Can you see it? It turns out that symbol-value only works for dynamic variables, and I was using lexical bindings in my code. Apparently there is no function similar to symbol-value for lexical bindings (and I think there can’t be because of the nature of lexical scope), so the situation is hopeless, right?

Of course not.

After a short while I realized that the solution to my problem is to make alert-vars a macro and not a function. Of course, macros are more difficult to write than functions, and honestly, I’m not 100% sure my macro doesn’t have some well-hidden bug (and I’m almost 100% sure it could be written in a more elegant way), but it seems to work for me.

(defmacro alert-vars (&rest vars)
  "Use `alert' to show the values of VARS.
VARS should be a list of symbols."
  `(alert ,(mapconcat
            (lambda (var) alert-vars-format)
            vars
            ", ")
          ,@(reverse
             (seq-reduce (lambda (acc var)
                           (cons var (cons (symbol-name var) acc)))
                         vars
                         '()))))

The mapconcat is a simple way to construct a format string consisting of the correct number of instances of alert-vars-format joined by comma-space separators. Then I used seq-reduce to create the suitable list out of vars (which is a list of symbols). Of course, mapcar wouldn’t have worked – I need to generate a list which is twice as long as vars, and I considered using flatten-list to be less elegant than a seq-reduce. The double cons is a bit ugly, but lets me construct the list one step at a time in an efficient way, that is, adding to the beginning and not the end – hence the need for reverse.

I am pretty sure most (or all) of the magic could have been done by two (or maybe even one, who knows) applications of cl-loop. Well, maybe I’ll learn cl-loop one day;-).

Anyway, this is how you use the alert-vars macro:

(alert-vars major-mode default-directory)

(Since it is a macro, you don’t need to quote anything – it takes care of things like that itself.) And now you can use it inside a lexically binding let, too!

Once I started using this macro, I noticed one more issue with it. It turns out (unsurprisingly) that read-key accepts C-g and just returns 7 (which is its ASCII code). I would prefer, however, if C-g interrupted the function I’m alert-debugging. One way to do that is to use the higher-level read-char function. This has a (minor) drawback that it requires the user to type an actual character on the keyboard, and clicking a mouse button (or even pressing a function key) causes an error. Hence I decided to use read-key and just implement quitting myself.

(defun alert (&rest message-args)
  "Like `message', but waiting for a keypress."
  (when (eq (read-key
             (concat
              (apply #'format message-args)
              "\n[press C-g to quit or any other key to continue]"))
            ?\C-g)
    (keyboard-quit)))

One last drawback I can see is that alert has no meaningful return value. Currently I cannot imagine a scenario where a return value would be useful, but last week I encountered a situation when I used the return value from message, so you never know. For now I decided that I’m going to apply the YAGNI principle and leave it as it is.

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2025-09-29 Improving dired-show-file-type

It is not astonishing at all that many people (including me) use Dired as their main file manager. The default look of Dired – just the output of ls -l – is deceptively crude, but underneath there is an immense power. Many Dired commands are just frontends to GNU Coreutils, but with much improved user interface, like reasonable default arguments (accessible via the “future history” of the minibuffer). But Dired is more than just a wrapper around Coreutils. For example, it has the dired-show-file-type command (bound to y by default), which runs the file command on the file at point. The file command is a great way to learn the basic information about any file. Besides telling the filetype, it often provides some information about its contents. For example, it guesses the encoding of text files and shows the resolution of image files.

There are some information it does not give, however. (Note: this is not a criticism of file, just a plain statement of the fact!) I often deal with media files, and one of the most useful piece of information about a media file is its duration. Another one is the resolution (of course, this makes more sense for video files than for sounds – but even mp3s often have a cover image embedded, so it could make sense to show that for them, too).

This means I have two problems to solve. The first one is to be able to actually gather the duration and resolution data about the media file. I used an LLM to tell me how to invoke ffprobe to do that and then studied the manpage to understand its output. I have to admit that it worked pretty well, although I modified it a little bit. Here is what I’m going to use to obtain the data:

ffprobe -loglevel error \
        -select_streams v:0 \
        -show_entries stream=width,height:format=duration \
        -output_format csv=p=0 \
        -sexagesimal \
        filename

Of course, wrapping this in Elisp is fairly easy. Since I need to transform the output of ffprobe, and process-file puts it in a buffer, I’m going to use the Emacs approach and do the transformations in the buffer instead of using strings.

(defun get-media-file-metadata (file)
  "Get FILE's duration and resolution, assuming it’s a media file."
  (let (process-file-side-effects)
    (with-temp-buffer
      (process-file "ffprobe" nil t nil
                    "-loglevel" "error"
                    "-select_streams" "v:0"
                    "-show_entries" "format=duration:stream=width,height"
                    "-output_format" "csv=p=0"
                    "-sexagesimal"
                    file)
      (goto-char (point-min))
      (when (looking-at-p "[0-9]+,[0-9]+")
        (insert "resolution: ")
        (search-forward "," nil t)
        (delete-char -1)
        (insert "x")
        (search-forward "\n")
        (delete-char -1)
        (insert ", "))
      (insert "duration: ")
      (buffer-substring-no-properties
       (point-min)
       (1- (point-max))))))

The other problem is to know when to run ffprobe – it doesn’t make much sense to run it for text files, for example. Let’s assume that I want to run it for audio and video files only. The go-to tool to check the MIME type of a given file is – you’ve guessed it – file again. The code is simple, though the if at the end may be a bit mysterious – its purpose is to return a meaningless result (an empty string) instead of erroring out when file is a broken symlink.

(defun get-file-mime-type (file deref-symlinks)
  "Get the mime type of FILE, according to the `file' command."
  (let (process-file-side-effects)
    (with-temp-buffer
      (process-file "file" nil t nil
                    "--brief"
                    "--mime-type"
                    (if deref-symlinks "--dereference" "--no-dereference")
                    file)
      (buffer-substring-no-properties
       (point-min)
       (progn
         (goto-char (point-min))
         (if (search-forward "/" nil t)
             (1- (point))
           (point-min)))))))

Now, the hard part is this. Assuming I want to advise dired-show-file-type, how do I get its result? (By the way, I could remap it instead of using advice. The difference is subtle – advising also works when I invoke it via M-x dired-show-file-type, so I figured this is the better solution here. There are some caveats, though.) The situation seems hopeless, since it message​s the result, not returns it. However, there is a light in the tunnel. First of all, message is the last function it calls, and (as I learned while researching this), message indeed returns the string it displays. If only there was a way to make message not to display anything, just return it, I would be good.

Well, it turns out that there is – apparently, there are even two ways of doing that!

One of them is something I’ve already used a few times. Recently, I’ve learned about another one. Emacs has a variable called set-message-function. You can set it to a function which can transform the message in any way you like – for example, concatenate it with something – or even cause it not to be displayed. However, I discovered that it doesn’t influence the string added to the *Messages* buffer, so it is not that useful for this particular case.

Anyway, the only thing left now is to gather all I know into a piece of Elisp code. One (slightly) tricky challenge is a syntactical one – I need to run the original function with message displaying (and logging) disabled (which means inside let setting inhibit-message and message-log-max), but on the other hand I need to store its result (the original message) and use it outside that let (to actually display and log the augmented message). When you’re accustomed to imperative programming, the first solution that comes to mind may be this:

(let (msg)
  (let ((inhibit-message t)
        (message-log-max nil))
    (setq msg (funcall orig-function ...)))
  (message (concat msg ...)))

but of course the setq here is really ugly. Of course, we can do better.

(defun get-file-metadata-if-media-file (file deref-symlinks)
  "If FILE is a audio or video file, get its duration and resolution."
  (when (member (get-file-mime-type file deref-symlinks) '("audio" "video"))
    (get-media-file-metadata file)))

(defun dired-show-file-type-media-advice (orig-function file deref-symlinks)
  "Augment the information about file type for media files.
This is a function to be attached as `:around' advice to
`dired-show-file-type'."
  (let ((msg
         (let ((inhibit-message t)
               (message-log-max nil))
           (funcall orig-function file deref-symlinks)))
        (media-msg (get-file-metadata-if-media-file file deref-symlinks)))
    (message (if media-msg
                 (concat msg ", " media-msg)
               msg))))

(advice-add 'dired-show-file-type :around #'dired-show-file-type-media-advice)

Again, if this code were to be distributed as a package, I would probably allow to turn this on and off via a global minor mode, but as this is something I’d like to have always on, I just put all the code (together with the advice) in my init.el.

I think if there are any other types of files where this approach could be useful, all this could be made more general. For example, there could be an alist where the keys are the MIME types and the values are functions returning strings with additional description to be displayed by dired-show-file-type. For example, I could write some code to show pagecounts for pdfs or document classes for LaTeX files. Maybe one day I will extend and publish this code as a proper package!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryDired

Comments on this page

2025-09-22 Faking today's date in Org

A long time ago I wrote about the datefudge tool, which can be very useful to test time-related code (not only in Elisp). Today I’m going to write about another way of solving a similar problem.

I am currently building (yet another) tool on top of Org mode. This tool heavily uses the notion of “today”. I made a conscious effort to always use the org-today function to get today’s date, precisely because I want to be able to mock it for tests.

However, writing automated tests is one thing, and testing stuff manually while developing it is another – both have their place. I thought that it would be pretty cool if I could interactively make org-today return any given date, so that I’d be able to test things quickly.

One thing that bothers me is this. Assuming I write an interactive wrapper around (advice-add 'org-today :override ...), how do I make sure I won’t forget to remove that advice?

And then, an idea occurred to me. I could (again!) write my own version of execute-extended-command which would first ask for a date (using org-read-date, of course) and then run the given command with org-today advised.

;;; -*- lexical-binding: t -*-

(defun org-fake-today--create-advice (fake-date)
  "Return the function used to advise `org-today'."
  (lambda () fake-date))

(defun org-fake-today-execute-command (prefix-arg)
  "Execute a command with `org-today' temporarily advised.
First ask the user for the date."
  (interactive "P")
  (advice-add
   'org-today
   :override (org-fake-today--create-advice
              (time-to-days
               (org-read-date nil t)))
   '((name . org-fake-today)))
  (execute-extended-command prefix-arg)
  (advice-remove 'org-today 'org-fake-today))

(global-set-key (kbd "C-s-X") #'org-fake-today-execute-command)

Note the comment setting lexical-binding to t in the first line – we need the value returned from org-fake-today--create-advice to be a closure which will take the place of org-today. This means that the above snippet needs to be in a file on its own – or a part of a file where lexical binding is on.

And now I can say, for example, this: C-s-X -7 RET org-agenda RET a and see the agenda as it had been shown a week ago. How cool is that?

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

More...

CategoryEnglish, CategoryBlog