Emacs Lisp Idioms

Xah Lee, 2008-06, 2009-02

This page collects some basic emacs lisp programing patterns related to writing a command when actively editing. For example, google search the word under cursor, switch to web browser to visit the url under cursor, do some find/replace in a text selection, insert XML template at cursor point, rename a given function in the current file, etc.

You should first be familiar with basic emacs functions that get cursor position, move cursor, search text, inserting and deleting text. See Emacs Lisp Basic Text-editing Functions. For emacs lisp idioms on text processing in batch like Perl, see: Text Processing with Emacs Lisp.

Typical Command Template

This is the typical template for all user-defined emacs commands.

(defun myCommand ()
  "... do this returns that ..."
  (interactive)
  (let (localVar1 localVar2 ...)
    (setq localVar1 ...)
    (setq localVar2 ...)
    ...
    ;; do something ...
  )
)

Grabbing Text

Grab text of given starting and ending positions

; get the string from buffer
(setq myStr (buffer-substring myStartPos myEndPos))
(setq myStr (buffer-substring-no-properties myStartPos myEndPos))

Emacs's string can have properties for the purposes of syntax coloring, active button, hypertext, etc. The “buffer-substring-no-properties” function just return a plain string without these properties. However, most functions that takes string as argument can also accept a string that has properties.

(info "(elisp)Buffer Contents")

Grabbing the current word or line

Grabbing the current word, line, sentence, url, file name etc.

; grab a thing at point. The “thing” is a semantic unit. It can be a
; word, symbol, line, sentence, filename, url and others.

; grab the current word
(setq myStr (thing-at-point 'word))

; grab the current word with hyphens or underscore
(setq myStr (thing-at-point 'symbol))

; grab the current line
(setq myStr (thing-at-point 'line))

; grab the start and end positions of a word (or any other thing)
(setq myBoundaries (bounds-of-thing-at-point 'word))

Note that, when the thing is a “symbol”, it usually means any alphanumeric sequence with dash “-” or underscore “_” characters. For example, if you are writing php reference lookup command, and the cursor is on p in “ print_r($y);”, you want to grab the whole “print_r” not just “print”. The exact meaning of symbol depends on the mode's Syntax Table.

(info "(elisp)Syntax Tables")

Here's a example of php reference lookup command that grabs by “symbol” if there's no active region.

(defun php-lookup ()
  "Look up current word in PHP ref site in a browser.\n
  If a region is active (a phrase), lookup that phrase."
  (interactive)
  (let (myword myurl)
    (setq myword
          (if (and transient-mark-mode mark-active)
              (buffer-substring-no-properties (region-beginning) (region-end))
            (thing-at-point 'symbol)))
    (setq myurl
          (concat "http://us.php.net/" myword))
    (browse-url myurl)))

(info "(elisp)Buffer Contents")

Grab Between Matching Pairs

Grab the current text between delimiters such as between angle brackets “<>”, parens “()”, double quotes “""”, etc.

The trick is to use skip-chars-backward and forward. In the following example, the p1 is set to the position of the double quote to the left of cursor, and p2 is set to the position on the double quote to the right of the cursor.

(defun select-inside-quotes ()
  "Select text between double straight quotes
on each side of cursor."
  (interactive)
  (let (p1 p2)
    (skip-chars-backward "^\"")
    (setq p1 (point))
    (skip-chars-forward "^\"")
    (setq p2 (point))

    (goto-char p1)
    (push-mark p2)
    (setq mark-active t)
  )
)

If you want to grab text inside parens, you can change the “"^\""” to “"^("” and “"^)"”. Similar for matching brakets “<>”. However, note that this code does not consider nested matching pairs.

Acting on Region

The last Mark position to the current cursor position is called a Region in emacs, or sometimes the “current region”. As soon as you did set a mark in a buffer, there is a region. (The command name is set-mark-command.)

When transient-mark-mode is on, it will highlight the region only when the mark is active, because otherwise highlighting is almost always on (because as soon as user sets a mark for the first time, there is a region, and it won't go away). The current active/inactive status of mark is stored in the variable mark-active.

When a mark is active, the region from last mark to current cursor position is called Active Region. The term “active region” is roughly equivalent to the modern term “Text Selection”. In other words, when a region is active, there is a text selection.

In summary: A region is the last mark position to current cursor position in the buffer. A region exist as soon as the user set a mark in a buffer. If transient-mark-mode is on, then there's a concept of Active Region. The value of the variable mark-active indicates whether the region is active. When a region is active, it is highlighted.

By convention, commands ending in the word “-region” acts on the region, regardless whether the region is active. For example: kill-region, comment-region, fill-region, indent-region. Some command's behavior changes depending whether there is a text selection (that is: whether the region is active). If there's text selection, it acts on it, else it may acts on the current word or do other things. For example: comment-dwim.

(info "(elisp)The Mark") (info "(emacs)Transient Mark")

Working On The Region

Here's a idiom for a command that process the current region's text.

Let your function have 2 parameters, suppose they are named “start” and “end”, then use “(interactive "r")”, then the parameters will be filled with beginning and ending positions of the region. Example:

(defun ff (start end)
  "Prints region starting and ending positions." 
  (interactive "r")
  (let () 
    (message "Region starts: %d, end at: %d" start end)
  )
)

Working On Active Region or Current Word

Often you want a command that works on the current word (line, or block of text), but if there is a text selection, take the text selection as input. Here's a template for this.

(defun downcase-word-or-region ()
  "Downcase current word or region."
(interactive)
(let (pos1 pos2 bds)
  (if (and transient-mark-mode
           mark-active)
      (setq pos1 (region-beginning) pos2 (region-end))
    (progn
      (setq bds (bounds-of-thing-at-point 'symbol))
      (setq pos1 (car bds) pos2 (cdr bds))))

  ;; now, pos1 and pos2 are the starting and ending positions of the
  ;; current word, or current text selection if exist.
  (downcase-region pos1 pos2)
  ))

Prompting User for Input

Get User Input as Arguments

Often you want a command that prompts user to enter some argument.

Use this code “(interactive "‹code›‹promp string›")”. Example:

(defun query-friends-phone (name)
  "..."
  (interactive "sEnter friend's name: ")
  (message "Name: %s" name)
)

What the “(interactive "sEnter friend's name:")” does is that it will ask user to input something, taken as string, and becomes the value of your command's parameter.

The “interactive” can be used to get other types of input. Here are some basic examples of using “interactive”.

The prompt text can follow the single-letter code string.

If your function takes multiple inputs, you can promp user multiple times, using a single call “interactive”, by joining the promp code string with “\n” in between, like this:

(defun query-friends-phone (name age)
  "..."
  (interactive "sEnter friend's name: \nnEnter friend's age: ")
  (message "Name: %s, Age: %d" name age)
)

(info "(elisp)Defining Commands")

Query For User Input

The “(interactive ...)” clause is useful for filling parameters of your command. But sometimes you need to promp user in the middle of a program. For example: “Make change to this file?”. You can use “y-or-n-p” function. Like this:

(if (y-or-n-p "Do it?")
    (progn
      ;; code to do something here
    )
  (progn
    ;; code if user answered no.
  )
)

The y-or-n-p will ask the user to type a “y” or “n” character. You can also use “yes-and-no-p”, which forces user to type full “yes” and “no” to answer. This can be used for example when you want to confirm deleting files.

(info "(elisp)Yes-or-No Queries")

If you need more general mechanism for getting user input, you'll need to use “read-from-minibuffer”. This can be useful for example, when you want use features like keyword completion or input history.

(info "(elisp)Text from Minibuffer")

Processing String

In perl, there are maybe 20 functions that act on string. In elisp, there are only about 5, because elisp has a buffer data type that's more powerful and flexible, and you have over 3 thousand functions that acts on text in a buffer. When you have a string or text, and you need to do more than just getting substring or number of chars, put it in a temp buffer. Here's a example:

;; suppose mystr is a var whose value is a string
(setq mystr "some string here you need to process")
(setq mystrNew
      (with-temp-buffer
        (insert mystr)

        ;; code to manipulate the string here
        ;; ...

        (buffer-string) ; get result
        ))

Find Replace text

Find and Replace string is the hallmark of text processing. Here's how you do it.

; idiom for string replacement in current buffer;

  (goto-char (point-min))
  (while (search-forward "myStr1" nil t) (replace-match "myReplaceStr1"))

  (goto-char (point-min))
  (while (search-forward "myStr2" nil t) (replace-match "myReplaceStr2"))

  ;; repeat for other string pairs

; if you need regexp, use search-forward-regexp 

If you need to do find replace on a region only, wrap the code with “(save-restriction (narrow-to-region start end) ‹find replace code here› )”.

; idiom for string replacement in region
(save-restriction
  (narrow-to-region start end)

  (goto-char (point-min))
  (while (search-forward "myStr1" nil t) (replace-match "myReplaceStr1"))

  ;; repeat for other string pairs
)

Apply to dired's Marked Files

To apply a function to marked files in dired, use “dired-get-marked-files”, like this:

;; idiom for processing a list of files in dired's marked files
 
;; suppose myProcessFile is your function that takes a file path
;; and do some processing on the file

(defun dired-myProcessFile ()
  "apply myProcessFile function to marked files in dired."
  (interactive)
  (require 'dired)
  (mapc 'myProcessFile (dired-get-marked-files))
)

For emacs lisp idioms on text processing in batch like Perl, see: Text Processing with Emacs Lisp.

The idioms on this page are packaged as a yasnippet template set. Download it at: Emacs Lisp Idiom Templates.


Related essays:

2008-06
© 2008 by Xah Lee.