4

Is there a way to program some Emacs command in elisp that could tell whether it is called interactively through some key binding or by typing its full name after M-x? I thought this could be done using called-interactively-p but it seems that it isn't.

My motivation: if called via M-x command-name, the command would gently remind me of the key binding. Would be great for learning again key bindings that I have forgotten after some lapse in usage.

  • 3
    emacs -Q version 25.3.1 outputs the keysequence for commands input per M-x by default in the echo area. – Tobias Feb 1 '18 at 9:03
  • 1
    @Tobias: But how is it done? Can it be easily reproduced in elisp? – phs Feb 1 '18 at 9:34
  • 3
    You can study it easily. It is part of execute-extended-command. Note that execute-extended-command is bound to M-x. You can also determine that by C-h k M-x. Emacs is open source and self-documenting. Since the info about the binding is part of the command bound to M-x they don't need to identify whether the command is called via M-x or via another key sequence. – Tobias Feb 1 '18 at 9:37
  • @Tobias: thanks for the explanation on how it is done. The way I understand it, execute-extended-command knows as an axiom how the command was invoked so it does not have to determine if a key binding was used. Too bad for me. It would be great if one could use (if (called-interactively-p 'extended-command) ... or something similar. BTW, I know emacs is open source (it includes elisp code I contributed 30 years ago). – phs Feb 1 '18 at 9:55
2

You can define a predicate function indicating that execute-extended-command is just running by advising execute-extended-command. The around advice wraps execute-extended-command with some active flag that can be tested in the predicate.

The following lisp code shows how it is done and presents also a test example. Call foobar once with M-x and once with key sequence C-c c to see the difference.

(defvar execute-extended-command--p) ;; Just declare to silence the byte compiler.

(eval ; for the sake of lexical binding (LEXICAL is t)
 (function ; quote but let the byte compiler do its job...
  (let (execute-extended-command--p)
    (defun execute-extended-command-p ()
      "Non-nil when `execute-extended-command' is just running."
      execute-extended-command--p)

     (defun execute-extended-command-wrapper (execute-extended-command &rest args)
       "Run EXECUTE-EXTENDED-COMMAND with ARGS.
Enable the predicate `execute-extended-command-p'
indicating that `execute-extended-command' is running."
       (unwind-protect
           (progn
             (setq execute-extended-command--p t)
             (apply execute-extended-command args))
         (setq execute-extended-command--p nil)))
     nil)) ;< Let eval just return nil.
 t) ;< LEXICAL arg of eval

(advice-add 'execute-extended-command :around #'execute-extended-command-wrapper)

(defun foobar (some-arg)
  "Test function."
  (interactive "sInput some string:")
  (message "Called `foobar' via %s with arg %S."
           (if (execute-extended-command-p)
               "via execute-extended-command"
             "via key sequence or menu item")
           some-arg))

(global-set-key (kbd "C-c c") #'foobar)
  • 1
    @phs Your question about execute-extended-command-p has some justification. One should replace the variable execute-extended-command-p by execute-extended-command--p (two slashes) to mark that variable as internal. The reason for the predicate (execute-extended-command-p) is to give (at least semantically) some kind of read-only access. The user should never fiddle around with the variable execute-extended-command-p itself -- (s)he should only use the predicate. This should also be clearly documented... Sorry for the lapsus. Pityingly one does not have private variables in elisp. – Tobias Feb 1 '18 at 11:39
  • 1
    In thise case you could simulate a private variable with lexical binding, i.e., put a let-binding around both of your functions instead of using defvar. – npostavs Feb 1 '18 at 12:54
  • 1
    The eval thingy is not only ugly but ineffective: the arg to eval will be evaluated first (in lexical-binding or not depending on context) and eval will simply return the function it received as arg. – Stefan Feb 1 '18 at 14:00
  • 2
    @Tobias: Why would you want to restrict it to a "local" form? Just add ;; -*- lexical-binding:t -*- on the first line of all your Elisp files. As for why it's like quote: pure accident since a function form like above is basically invalid. – Stefan Feb 1 '18 at 14:56
  • 1
    @Stefan Because I don't want to potentially break the OP's init files. I know that the probability is low. E.g., all variables defined with defvar are automatically marked special. But, maybe one uses setq and wonders why the let-binding does not work in one of the called functions not covered by the let. Such errors can be difficult to be tracked down for not so experienced users. Be aware that many emacs users configure just by copying and pasting code from others! (I don't.) – Tobias Feb 1 '18 at 15:01
1

I like @Tobias's solution. I will add that I use third-party package counsel, which overrides M-x with its own counsel-M-x function, so advising execute-extended-command would not work for me.

Here's a barely-tested alternative that tests whether the keys reported by this-command-keys are bound to a given command, where "a given command" is typically the caller itself (i.e. some-example-command in the example below).

(defun called-via-key-binding (cmd)
  "Returns non-nil if `this-command-keys' is bound to CMD."
  (eq (key-binding (this-command-keys)) cmd))

(defun some-example-command ()
  (interactive)
  (cond
    ((not (called-interactively-p 'any))
     (message "Not called interactively"))
    ((called-via-key-binding #'some-example-command)
     (message "Called via key binding and not M-x"))
    (t
     (message "Not called via key binding, so maybe called via M-x"))))

(The call to called-interactively-p should arguably be moved into called-via-key-binding.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.