Search code examples
functionargumentslispelispoption-type

ELISP interactive function with both prefix argument and user input as optional arguments


In ELISP, the documentation for interactive codes mentions:

P -- Prefix arg in raw form. Does not do I/O. ... s -- Arbitrary text, read in the minibuffer and returned as a string ... Prompt.

I presumed that I could write a function with an optional prefix argument, as in:

(defun some-function (&optional prefix)
    (interactive "P")
    ...
)

or a function with user input, as in:

(defun some-function (user-argument)
  (interactive "sProvide an argument: ")
  ...
)

but not both. Then I found the Org-mode function org-match-sparse-tree, which I can call with C-u C-c \, where the prefix argument restricts the match to open org-mode headings and I am still prompted for a match. The source code is below and I cannot find how the variable match is assigned:

(defun org-match-sparse-tree (&optional todo-only match)
  "..."
  (interactive "P")
  (org-agenda-prepare-buffers (list (current-buffer)))
  (let ((org--matcher-tags-todo-only todo-only))
    (org-scan-tags 'sparse-tree (cdr (org-make-tags-matcher match))
           org--matcher-tags-todo-only)))

How does this function take both prefix argument and user input?


Solution

  • How does this function [interactively] take both prefix argument and user input?

    It doesn't -- the match argument is not obtained, and is therefore nil. What you're seeing is the effect of the subsequent call to (org-make-tags-matcher match) with that nil value as the argument:

    (defun org-make-tags-matcher (match)
      "..."
      (unless match
        ;; Get a new match request, with completion against the global
        ;; tags table and the local tags in current buffer.
        (let ((org-last-tags-completion-table
               (org-tag-add-to-alist
                (org-get-buffer-tags)
                (org-global-tags-completion-table))))
          (setq match
                (completing-read
                 "Match: "
                 'org-tags-completion-function nil nil nil 'org-tags-history))))
      ...)
    

    Functions can take multiple interactive arguments, though.

    See C-hf interactive

    To pass several arguments to the command, concatenate the individual strings, separating them by newline characters.

    The very first example in that help demonstrates this:

    (defun foo (arg buf) "Doc string" (interactive "P\nbbuffer: ") .... )
    

    This is elaborated upon at (elisp)Using Interactive -- up one level in the documentation you'd linked to:

    It may be a string; its contents are a sequence of elements
    separated by newlines, one for each argument(1).  Each element
    consists of a code character (*note Interactive Codes::) optionally
    followed by a prompt (which some code characters use and some
    ignore).  Here is an example:
    
         (interactive "P\nbFrobnicate buffer: ")
    
    The code letter ‘P’ sets the command’s first argument to the raw
    command prefix (*note Prefix Command Arguments::).  ‘bFrobnicate
    buffer: ’ prompts the user with ‘Frobnicate buffer: ’ to enter the
    name of an existing buffer, which becomes the second and final
    argument.
    

    You should read that documentation fully, though -- there are more sophisticated things you can do, including writing arbitrary elisp to produce the interactive arguments (which may or may not involve prompting the user).