Emacs Lisp: Writing Command to Accept universal-argument

Perm URL with updates: http://xahlee.org/emacs/elisp_universal_argument.html

This page shows you how to make your emacs lisp command accept universal-argumentCtrl+u】 given by user.

Problem Description

You have written a emacs command. You want the command's behavior to be slightly different if user presses 【Ctrl+u】 before calling your command.

Detail

Emacs has a mechanism for a command to have variant behavior if user calls universal-argumentCtrl+u】.

The universal argument has many uses. It can let user repeat a command n times. For example, type 【Ctrl+u 20 -】 and it'll type out --------------------.

For some commands, the universal argument is useful for variant behavior of the command. For example, in dired, typing w will copy the file name (dired-copy-filename-as-kill), but if you type 【Ctrl+u 0 w】, the copied name will be file full path.

Solution

Let's say you want a command to copy the file path of current file. But if universal-argument is called, then copy just the dir path. Here's how you code it:

(defun copy-file-path (prefixArgCode)
  "Copy the current buffer's file path or dired path to `kill-ring'.
If `universal-argument' is called, copy only the dir path."
  (interactive "P")
  (let ((fPath
         (if (equal major-mode 'dired-mode)
             default-directory
           (buffer-file-name)
           )))
    (kill-new 
     (if (equal prefixArgCode nil)
         fPath
       (file-name-directory fPath)
       )))  
  (message "File path copied.") )

First, we set fPath.

Then, we check “prefixArgCode”. If it's not “nil”, then we truncate the file path to just the dir.

The gist of getting the universal arg is this:

(defun copy-file-path (prefixArgCode)
  …
  (interactive "P")
  (if (equal prefixArgCode nil)
         fPath
       (file-name-directory fPath)
       ) )

The (interactive "P") will pass the arg from universal-argument as your command's first argument.

Universal Argument Values

To make your command aware of universal argument, there are 2 major ways:

  • ① Query the global variable “current-prefix-arg”. It holds the value of universal argument.
  • ② Add (interactive "P") to your function. It will pass the value of “current-prefix-arg” to your function's first argument.
Key InputValue of “current-prefix-arg”
Ctrl+unil
Ctrl+u -Symbol -
Ctrl+u - 2Number -2
Ctrl+u 1Number 1
Ctrl+u 4Number 4
Ctrl+uList '(4)
Ctrl+u Ctrl+uList '(16)

(info "(elisp) Prefix Command Arguments")

Complex Example of using universal-argument

Sometimes you do not want your function's parameter spec to explicitly contain universal argument. So, (interactive "P") is not appropriate. For example, suppose you are writing this command:

(defun wrap-html-tag (tagName &optional className id) 
  "Add a HTML tag to beginning and ending of current word or text selection."
)

Your command will create a HTML tag like this:

something
   ↓  
<div>something</div>
or
<div class="xyz">something</div>
or
<div id="id8295" class="xyz">something</div>

When called interactively, it can prompt user to enter “class” and “id” values. If user just press Enter without giving them any value, then don't add these attributes to the tag.

However, often, the prompting for “class” and “id” are annoying, because, in practice, many tags don't need them. e.g. {<p>One day, …</p>, <b>bold</b>, <i>italic</i>}.

So, you want your command to do extra prompt only when preceded by universal-argument.

Here's solution:

(defun wrap-html-tag (tagName &optional className ξid)
  "Add a HTML tag to beginning and ending of current word or text selection.

When preceded with `universal-argument',
no arg = prompt for tag, class.
2 = prompt for tag, id.
any = prompt for tag, id, class.

When called interactively,
Default id value is 「id‹random number›」.
Default class value is 「xyz」.

When called in lisp program, if className is nil or empty string, don't add the attribute. Same for ξid."
  (interactive
   (cond
    ((equal current-prefix-arg nil)     ; universal-argument not called
     (list
      (read-string "Tag (span):" nil nil "span") ))
    ((equal current-prefix-arg '(4))    ; C-u
     (list
      (read-string "Tag (span):" nil nil "span")
      (read-string "Class (xyz):" nil nil "xyz") ))
    ((equal current-prefix-arg 2)       ; C-u 2
     (list
      (read-string "Tag (span):" nil nil "span")
      (read-string "id:" nil nil (format "id%d" (random (expt 2 28 ))))
      ))
    (t                                  ; all other cases
     (list
        (read-string "Tag (span):" nil nil "span")
        (read-string "Class (xyz):" nil nil "xyz")
        (read-string "id:" nil nil (format "id%d" (random (expt 2 28 )))) )) ) )
  (let (bds p1 p2 inputText outputText
            (classStr (if (equal className nil) "" (format " class=\"%s\"" className)))
            (idStr (if (equal ξid nil) "" (format " id=\"%s\"" ξid)))      
            )
    (setq bds (get-selection-or-unit 'word))
    (setq inputText (elt bds 0) )
    (setq p1 (elt bds 1) )
    (setq p2 (elt bds 2) )
    
    (setq outputText (format "<%s%s%s>%s</%s>" tagName idStr classStr inputText tagName ) )

    (delete-region p1 p2)
    (goto-char p1)
    (insert outputText) ) )

The skeleton of the code is this:

(defun wrap-html-tag (tagName &optional className ξid)
  …

  (interactive
   (cond
    ((equal current-prefix-arg nil)     ; universal-argument not called
     (list
      (read-string "Tag (span):" nil nil "span") ))
    ((equal current-prefix-arg '(4))    ; C-u
     (list
      (read-string "Tag (span):" nil nil "span")
      (read-string "Class (xyz):" nil nil "xyz") ))
    ((equal current-prefix-arg 2)       ; C-u 2
     (list
      (read-string "Tag (span):" nil nil "span")
      (read-string "id:" nil nil (format "id%d" (random (expt 2 28 ))))
      ))
    (t                                  ; all other cases
     (list
        (read-string "Tag (span):" nil nil "span")
        (read-string "Class (xyz):" nil nil "xyz")
        (read-string "id:" nil nil (format "id%d" (random (expt 2 28 )))) )) ) )

  ;; function body  

 )

The (interactive …) is used to fill out the parameters, when your function is called by user interactively (as opposed to from a lisp program).

One way to use the interactive function is for it to return a list. This list's element will be fed to the function as arguments.

In our code, we use a conditional (cond …) to check the values of “current-prefix-arg”. If it's “nil”, then that means universal-argument is not called. So, we simply prompt for the tag name. But if the value is other, then we prompt for more.

The weird ξ you see in my elisp code is Greek x. I use Unicode char in symbol name for easy distinction from builtin symbols. You can just ignore it. (➲ Programing Style: Variable Naming: English Words Considered Harmful)

The command uses get-selection-or-unit. You can get the code for that at get-selection-or-unit.

Popular posts from this blog

Browser User Agent Strings 2012

11 Years of Writing About Emacs

does md5 creates more randomness?