Smart quotes in Emacs 22


Support for U+2018 LEFT SINGLE QUOTATION MARK and friends seems pretty widespread these days, so I think it’s time for this:

;;; smart-quotes.el --- Smart Quotes mode for Emacs

;; Copyright (C) 2007 Gareth Rees

;; Author: Gareth Rees <>
;; Created: 
;; Version: 1.1
;; Keywords: abbrev

;; Smart Quotes mode is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2, or (at
;; your option) any later version.

;;; Commentary:

;; In Smart Quotes minor mode, the ' and \" keys insert left and
;; right quotation marks according to the context around point.

;;; Code:

(defcustom smart-quotes-left-context "^\\|\\s-\\|\\s("
  "Regular expression matching the context in which a left
quotation mark will be inserted (a right quotation mark will
be inserted in all other contexts)."
  :type 'regexp)

(defun smart-quotes-insert-single ()
  "Insert U+2018 LEFT SINGLE QUOTATION MARK if point is preceded
by `smart-quotes-left-context'; U+2019 RIGHT SINGLE QUOTATION MARK
  (ucs-insert (if (looking-back smart-quotes-left-context) #x2018 #x2019)))

(defun smart-quotes-insert-double ()
  "Insert U+201C LEFT DOUBLE QUOTATION MARK if point is preceded
by `smart-quotes-left-context'; U+201D RIGHT DOUBLE QUOTATION MARK
  (ucs-insert (if (looking-back smart-quotes-left-context) #x201C #x201D)))

(define-minor-mode smart-quotes-mode
  "Toggle Smart Quotes mode in the current buffer.
With argument ARG, turn Smart Quotes mode on iff ARG is positive.
In Smart Quotes mode, the ' and \" keys insert left quotation
marks if point is preceded by text matching the option
`smart-quotes-left-context' and right quotation marks otherwise."
  :lighter (:eval (string ?  (decode-char 'ucs #x201C)
                          (decode-char 'ucs #x201D)))
  :keymap '(("'" . smart-quotes-insert-single)
            ("\"" . smart-quotes-insert-double)))

(provide 'smart-quotes)

ucs-insert and looking-back are new in Emacs 22. In later releases of Emacs 21 I think you can probably get away with the following:

(defun ucs-insert (c)
  "Insert the character whose Unicode code point is C."
  (insert (decode-char 'ucs c)))

(defun looking-back (regexp)
  "Return non-nil if text before point matches regular expression REGEXP."
  (and (save-excursion (re-search-backward regexp nil t))
       (= (match-end 0) (point))))

But if you’re editing text in a wide range of character sets, you probably want to upgrade to Emacs 22 anyway, as there are lots of improvements to international character support.

Update . Reuben Thomas pointed out that smart-quotes-left-context needs to match the start of a line. I had wrongly assumed that it would, arguing that the start of any line is preceded by either a newline (which matches the whitespace character class, \s-), or by the beginning of the buffer (which matches \`). But newline is only in the whitespace character class in modes like text mode:

(with-syntax-table text-mode-syntax-table (char-syntax ?\n))
        ==> 32   ; whitespace class

In programming language modes, newline has special properties:

(with-syntax-table python-mode-syntax-table (char-syntax ?\n))
        ==> 62   ; comment ender class

I’m not sure why you’d want to use Smart Quotes minor mode when editing a computer program. But if you do, at least it now gets it right.

Update . Smart Quotes Mode is now on GitHub. Please fork it and send pull requests!