Emacs Lisp Idioms for Writing Interactive Commands

Master emacs+lisp, benefit for life. Testimonials. Thank you for support.
, , …,

This page is a collection of basic emacs lisp programing patterns for writing interactive commands. For example, a command that does google search of the word under cursor, a command that does find & replace in a text selection, a command that inserts XML template at cursor point, a command that rename a given function in the current file, etc.

You should be familiar with Emacs Lisp Language Basics and Basic Emacs Lisp Functions (⁖ {cursor position, moving cursor, search text, inserting text, deleting, …}).

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

Typical Command Template

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

(defun myCommand ()
  "One sentence summary of what this command do.

More details here. Be sure to mention the return value if relevant.
Lines here should not be longer than 70 chars,
and don't indent them."
  (interactive)
  (let (localVar1 localVar2 …)
    (setq localVar1 …)
    (setq localVar2 …)
    …
    ;; do something …
  )
)

In your inline documentation, if you want clickable URL, or clickable reference to other commands, etc., See: Emacs Function's Inline Doc String Markups.

Grabbing Text

Grab Text of Given Begin & End Positions

How to get a buffer region as string?

;; get the string from buffer
;; get the string of the first 10 chars in buffer
(setq myStr (buffer-substring 1 10))

Emacs's string can have text properties for the purposes of syntax coloring, button, clickable link, etc. (info "(elisp) Text Properties")

if you are doing text processing, usually you don't need text properties.

The buffer-substring-no-properties function just return a plain string without these properties.

;; get the string from buffer
(setq myStr (buffer-substring-no-properties startPos endPos))

Note: most function that take string as argument can also accept a string that has properties. The function simply ignore the properties.

(info "(elisp) Buffer Contents")

Get Current Word or Line

Here's how to get the word under cursor, or the current {line, sentence, URL, file name, …}.

;; 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 “symbol”. Symbol is current major mode's concept of a language's word unit. Usually includes underscore, may include hyphens or other
(setq myStr (thing-at-point 'symbol))

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

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 current major mode's Syntax Table. (info "(elisp) Syntax Tables")

(info "(elisp) Buffer Contents")

For more about thing-at-point, see: Emacs Lisp: Using thing-at-point.

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.

If a region is active (a phrase), lookup that phrase."
  (interactive)
  (let (myWord myUrl)
    (setq myWord
          (if (use-region-p)
              (buffer-substring-no-properties (region-beginning) (region-end))
            (thing-at-point 'symbol)))
    (setq myUrl
          (concat "http://us.php.net/" myWord))
    (browse-url myUrl)))

〔☛ Emacs: Command to Lookup Reference: {Dictionary, Wikipedia, Google, PHP, Perl, …}

Get Boundaries of a Text Unit

Sometimes, you need to not just grab current word, but do other things such as delete the word. You need to know the beginning and ending positions of the region you are interested.

Here's how to use thing-at-point to get the boundaries of a “thing”.

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

;; get the beginning and ending positions
(setq p1 (car myBoundaries))
(setq p2 (cdr myBoundaries))

;; grab it
(setq myStr (buffer-substring-no-properties p1 p2))

;; delete region
(delete-region p1 p2)

For more about thing-at-point, see: Emacs Lisp: Using thing-at-point.

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 skip-chars-forward. In the following example, the p1 is set to the position of the double quote to the left of cursor (the first char to the right of the quote). Similarly, for p2 to the right of cursor.

(defun xah-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 ^). However, note that this code does not consider nested matching pairs.

Text Selection, Region, Mark, Copy, Paste, Kill Ring

Concept of Mark, Region, Active Region, transient-mark-mode

Emacs: What's Region, Active Region, transient-mark-mode?

How to Cut/Copy/Paste to/from kill-ring?

See: Emacs Lisp Idioms: How to Cut/Copy/Paste to/from kill-ring?

Working on Region

How to make a command work on region?

Let your function have 2 parameters, then use (interactive "r"), then the parameters will be filled with beginning and ending positions of the region. Example:

(defun dosomething-region (p1 p2)
  "Prints region starting and ending positions."
  (interactive "r")
  (message "Region starts: %d, end at: %d" p1 p2)
)

Working on Active Region or Current Word/Paragraph/Buffer

Often you want a command that works on the current word (or line, paragraph), 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 (use-region-p)
     (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)
  ))

Starting with emacs 23, many commands will automatically act on text selection if there's one. 〔☛ New Features in Emacs 23〕 So, it's good that your own text processing commands also behave that way.

Working with Lines

Get beginning/end positions of current line:

;; get beginning/end positions of current line
(setq lineBeginPos (line-beginning-position) )
(setq lineEndPos (line-end-position) )

Move to beginning/end positions of current line:

;; move to beginning/end positions of current line
(beginning-of-line)
(end-of-line)

Move to previous/next line:

;; move to previous/next line
(forward-line -1)
(forward-line 1)

See: All About Processing Lines in Emacs Lisp.

Prompting User for Input

See: Emacs Lisp Idioms: Prompting for User Input.

Processing String in Temp Buffer

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, and you need to do more than just getting substring or number of chars, put it in a temp buffer. Here's a example:

;; process string in a temp buffer

(setq myStr "some big string here you need to process")
(setq myStrNew
      (with-temp-buffer
        (insert myStr)

        ;; code to manipulate your string as buffer text
        ;; 
        (buffer-string) ; get result
        ))

Find/Replace Text

Find/Replace string is one of the most important method in text processing. Here's how you do it.

;; idiom for string replacement in current buffer;

(let ((case-fold-search t)) ; or nil

  (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

To control the letter case of search, set case-fold-search to t or nil with let, as in the above.

To control letter case of the replacement, use the optional arguments in your replace-match function.

If you need to do find/replace on a region only, wrap the code with save-restriction and narrow-to-region. Example:

;; idiom for string replacement within a region
(save-restriction
  (narrow-to-region pos1 pos2)

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

  ;; repeat for other string pairs
)

If you need to find/replace multiple pairs frequently, see: Emacs Lisp: Multi-Pair String Replacement: xfrp_find_replace_pairs.el.

WARNING: Whenever you work in a region, remember that the boundaries of the text that you are interested is changed when you add or remove text in that region. For example, suppose {p1, p2} is the boundary of some text you are interested. After doing some change there, suppose you want to do some more change. Don't just call (something-region p1 p2) again, because p2 is no longer the correct boundary (of the region you are interested).

Use save-restriction and narrow-to-region, like this:

(save-restriction
  (narrow-to-region pos1 pos2)
  (something1-region (point-min) (point-max))
  (something2-region (point-min) (point-max))
  …
)

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 a yasnippet template for elisp. Download it at: Yasnippet Templates for Emacs Lisp Mode. For a tutorial on yasnippet, see: Emacs Templates with YASnippet.

Like what you read?
Buy Xah Emacs Tutorial
or share some
blog comments powered by Disqus