region-or-thing

Emacs Lisp has an amazingly useful function called thing-at-point, which will return the object that the point is currently on. You call it as

(thing-at-point THING)

where THING is a symbol that specifies the kind of object you want. Possibilities, as listed on the help page, include ‘symbol’, ‘list’, ‘sexp’, ‘defun’, ‘filename’, ‘url’, ‘email’, ‘word’, ‘sentence’, ‘whitespace’, ‘line’, ‘page’, and others. There is a companion function called bounds-of-thing-at-point that returns the bounds of the object.

As an example of using thing-at-point, suppose we want a function that will do a Google lookup of the symbol that the point is on. We can do that as

(defun google-search ()
  "Do a Google search of the symbol at the point"
  (interactive)
  (browse-url (concat "http://www.google.com/search?q="
                      (thing-at-point 'symbol))))

We’re using symbol instead of word because it’s more inclusive.

Of course, as soon as we have this function we realize that we sometimes want to look up a phrase. So now we have to worry about whether or not a region is active. We end up with something like this:

(defun google-search ()
  "Do a Google search of the region or symbol at the point"
  (interactive)
  (let ((phrase (if (use-region-p)
                    (buffer-substring-no-properties
                     (region-beginning) (region-end))
                  (thing-at-point 'symbol))))
    (browse-url (concat "http://www.google.com/search?q="
                        (replace-regexp-in-string " " "+" phrase)))))

The buffer-substring-no-properties function returns the substring in the region but removes all the text properties. The call to replace-regexp-in-string replaces any spaces in the text with plus signs as required by Google.

This version of google-search is reasonably complete except that it doesn’t cover some corner cases such as an ‘&’ in the region’s text. But for purposes of the example, let’s call it good enough.

The next thing we notice is that we keep writing code like the above to handle the two cases of a region or a single object at the point. So we abstract that code out to a function called region-or-thing, which returns a vector whose first entry is the text in question, and whose second and third entries are the bounds of that text.

(defun region-or-thing (thing)
  "Return a vector containing the region and its bounds if there is one
or the thing at the point and its bounds if there is no region"
  (if (use-region-p)
      (vector (buffer-substring-no-properties (region-beginning) (region-end))
              (region-beginning) (region-end))
    (let* ((bounds (bounds-of-thing-at-point thing))
           (beg (car bounds))
           (end (cdr bounds)))
      (vector (buffer-substring-no-properties beg end) beg end))))

The region-or-thing function is based on an idea from Xah Lee who has a much more elaborate version called get-selection-or-unit.

With region-or-thing we can rewrite google-search as

(defun google-search ()
  "Do a Google search of the region or symbol at the point"
  (interactive)
  (let ((phrase (elt (region-or-thing 'symbol) 0)))
    (browse-url (concat "http://www.google.com/search?q="
                        (replace-regexp-in-string " " "+" phrase)))))

The google-search function doesn’t make use of the bounds information that region-or-thing returns. In a subsequent post, I’ll show some examples that do make use of the bounds.

This entry was posted in Programming and tagged . Bookmark the permalink.