2

According to my understanding of the post-command-hook from the documentation (https://www.gnu.org/software/emacs/manual/html_node/elisp/Command-Overview.html): the post-command-hook is executed after any and every interactive method completes (i.e. which is almost everything in Emacs ranging from typing a character, scrolling, clicking, etc.). This means that it executes after each interactive method either fully completes or runs into a non-standard exit (i.e. quitting, thrown exception, etc.). This seem to be logical and make sense on the surface as we would expect it to be invoked as the very last statement of an interactive method, something similar to following:

(prog1 (<your_interactive_method>) (run-hooks 'post-command-hook) ) 

However, based on my testing, it doesn't seem that the post-command-hook is executed that way, in a similar execution lifecycle. Meaning it doesn't wait on being invoked when there is a user-blocking event occurring in the middle of a method.

Put simply, say you have a method my_command_method that has invocations to read-string (or any other similar method that requests user input), it is expected that the post-command-hook is NOT executed, for the my_command_method invocation, until after ALL method invocations complete in my_command_method or there is a non-standard exiting of the method (i.e. quitting, thrown exception, etc.).

However, this seems to be false and the hook is just immediately executed in the method without waiting for it to be completed (in whatever way) and all of its internal methods invoked.

The code example that re-produces the issue:

(progn (defun my_command_method() "Insert text entered by the user in the current buffer." (interactive) (let* ((user_text (read-string "Say something: "))) (if user_text (insert user_text)))) (let* ((post_command_printer (lambda(&rest arguments) (if (symbolp this-command) (progn ; Just for clarity/testing (if (s-contains-p "my_command_method" (symbol-name this-command)) (progn (message "Post-Hook: This Command = %S" (symbol-name this-command)) (message "Post-Hook (This-Command): Buffer Contents = %S" (buffer-substring-no-properties (point-min) (point-max))) ; Should have the user's inserted text since this should only run _AFTER_ the method fully completes ... but it does NOT )))) (if (symbolp last-command) (progn ; Just for clarity/testing (if (s-contains-p "my_command_method" (symbol-name last-command)) (progn (message "Post-Hook: Last Command = %S" (symbol-name last-command)) (message "Post-Hook (Last-Command): Buffer Contents = %S" (buffer-substring-no-properties (point-min) (point-max))) ; Should have the user's inserted text since this should only run _AFTER_ the method fully completes ... but it does NOT ))))))) (add-hook 'post-command-hook post_command_printer))) 

Invoking my_command_method via M-x causes the hook to immediately fire off before the user has a chance to enter anything into the prompt for read-string. When I do enter some text for read-string and the method completes successfully, the hook does NOT execute again since it already executed. This overall behavior cannot be correct.

Could someone explain why this is?

How can we achieve the desired effect (i.e. only execute after the method completes entirely) via post-command-hook?

Standard environment disclosure:

  • OS: MacOS
  • Chip: Intel
  • Emacs Version: 27.2.1
  • Port: Mitsuharu Yamamoto / Railwaycat
  • Other Notes: - No special distributions / etc.
  • Relevant Common Packages Used:
    • Standard internal Emacs packages
    • s.el (String Library)

1 Answer 1

2

You want to consider a whole bunch of things:

  1. post-command-hook runs after every trip around the command loop.

  2. Every self-inserting key (i.e. typing text) is a command (self-insert-command).

  3. Therefore, in order for read-string to obtain user input, a whole bunch of commands are happening in addition to my_command_method (and hence additional trips around the command loop), via a recursive edit.

  4. Variables can be buffer-local -- including hook variables, which have special behaviour for buffer-local values (refer to the LOCAL argument to add-hook).

  5. When you read input in the minibuffer, you're in the minibuffer, which may have (or may subsequently acquire) buffer-local values for things, including post-command-hook.

  6. What happens at the end of the command loop when post-command-hook runs depends on which buffer is current at that time, and what the value of the post-command-hook variables is in that buffer at that time.

So if you want to watch what's going on, you'd want to add a whole bunch of additional debugging messages. E.g.:

(defun my_command_method () "Insert text entered by the user in the current buffer." (interactive) (when-let ((user_text (read-string "Say something: "))) (insert user_text))) (defun my_postcommand () "Added to `post-command-hook'." (message "this-command = %s" this-command) (message "last-command = %s" last-command) ;; this-command (when (and (symbolp this-command) (string-match-p "my_command_method" (symbol-name this-command))) (message "current-buffer = %s" (current-buffer)) (message "post-command-hook = %s" post-command-hook (current-buffer)) (message "Post-Hook: This Command = %S" (symbol-name this-command)) (message "Post-Hook (This-Command): Buffer Contents = %S" (buffer-substring-no-properties (point-min) (point-max)))) ;; last-command (when (and (symbolp last-command) (string-match-p "my_command_method" (symbol-name last-command))) (message "current-buffer = %s" (current-buffer)) (message "post-command-hook = %s" post-command-hook (current-buffer)) (message "Post-Hook: Last Command = %S" (symbol-name last-command)) (message "Post-Hook (Last-Command): Buffer Contents = %S" (buffer-substring-no-properties (point-min) (point-max))))) (add-hook 'post-command-hook 'my_postcommand) 

With this, you can observe what's happening, and the simple answer that this-command and last-command are affected by the commands happening when the user is entering the string, and therefore when post-command-hook runs at the end of my_command_method the chances are that neither this-command nor last-command are a match for your tests.

(In fact the only way that could happen is if you enter an empty string at the prompt, in which case you'll wind up with this-command = exit-minibuffer and last-command = my_command_method.)

6
  • Thanks for the comprehensive details. For simple testing, I did this all in a single scratch buffer, however you are correct there is a lot in play with the post-command-hook whenever it is executed (and where it is executed). Commented Jan 12 at 0:59
  • However, I would imagine then my misunderstanding is that for any given callback for the post-command-hook, the this-command and last-command variables are not necessarily reliable or optimal for what is expected (i.e. they are one level too deep): they are at the level of the methods being executed in my_command_method (i.e. they will refer to perhaps the last method invoked in the command loop or method executed in my_command_method itself). That all makes sense. Thanks Commented Jan 12 at 0:59
  • 1
    You can always let-bind copies of this-command and last-command as they were at the time your function was invoked, and test for those in your hook function. Commented Jan 12 at 1:12
  • Ah, thinking about this even further, it's not that there is any kind of "blocking" being done or waiting. It's merely a stack/list of methods that were executed. When my_command_method is executed, its internal details (method invocations) are pushed onto the stack as "to be executed" then the post-command-hook runs since the method was interactive then the rest of the "to be executed" methods are executed, then any appropriate calls to post-command-hook are executed during those calls as well. Correct? Commented Jan 12 at 1:16
  • Having cached versions of this-command and last-command via let wrapping (or even via custom defvar variables at that point too) the entire inner workings of the method is pretty clever. I can see where that would work; every method being targeted for verification in a callback from post-command-hook would need to have that wrapping/caching done as part of its definition (either manually or through a macro). I am guessing this is where the usage of pre-command-hook is thought of as an easy way to achieve this effect without modifying the methods' definitions? Commented Jan 12 at 1:21

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.