Emacs: Convert Straight/Curly Quotes 🚀

By Xah Lee. Date: . Last updated: .

Here's a command to convert straight quote to curly quotes.

(defun xah-single-quote-to-curly (Begin End)
  "Replace straight double quotes to curly ones etc.

URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Version: 2019-07-25 2021-08-17"
  (interactive
   (let ($p1 $p2)
     (let (($bds (xah-get-bounds-of-block-or-region))) (setq $p1 (car $bds) $p2 (cdr $bds)))
     (list $p1 $p2)))
  (let ( (case-fold-search nil))
    (save-excursion
      (save-restriction
        (narrow-to-region Begin End )
        (xah-replace-pairs-region
         (point-min) (point-max)
         [
          [">\'" ">‘"]
          [" \'" " ‘"]
          ["(\'" "(‘"]

          ["\' " "’ "]
          ["\'," "’,"]
          [".\'" ".’"]
          ["!\'" "!’"]
          ["?\'" "?’"]
          ["\')" "’)"]
          ["\']" "’]"]
          ] "REPORT" "HILIGHT")

        (xah-replace-regexp-pairs-region
         (point-min) (point-max)
         [
          ["\\bcan’t\\b" "can't"]
          ["\\bdon’t\\b" "don't"]
          ["\\bdoesn’t\\b" "doesn't"]
          ["\\bwon’t\\b" "won't"]
          ["\\bisn’t\\b" "isn't"]
          ["\\baren’t\\b" "aren't"]
          ["\\bain’t\\b" "ain't"]
          ["\\bdidn’t\\b" "didn't"]
          ["\\baren’t\\b" "aren't"]
          ["\\bwasn’t\\b" "wasn't"]
          ["\\bweren’t\\b" "weren't"]
          ["\\bcouldn’t\\b" "couldn't"]
          ["\\bshouldn’t\\b" "shouldn't"]

          ["\\b’ve\\b" "'ve"]
          ["\\b’re\\b" "'re"]
          ["\\b‘em\\b" "'em"]
          ["\\b’ll\\b" "'ll"]
          ["\\b’m\\b" "'m"]
          ["\\b’d\\b" "'d"]
          ["\\b’s\\b" "'s"]
          ["s’ " "s' "]
          ["s’\n" "s'\n"]

          ["\"$" "”"]
          ] t t "HILIGHT")
        ;;
        ))))

(defun xah-ascii-to-math-symbol (Begin End)
  "Replace straight double quotes to curly ones etc.
URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Version: 2019-07-25 2021-08-17"
  (interactive
   (let ($p1 $p2)
     (let (($bds (xah-get-bounds-of-block-or-region))) (setq $p1 (car $bds) $p2 (cdr $bds)))
     (list $p1 $p2)))
  (let ( (case-fold-search nil))
    (save-excursion
      (save-restriction
        (narrow-to-region Begin End )
        (xah-replace-pairs-region
         (point-min) (point-max)
         [
          [" ---> " " ⟶ "]
          [" --> " " ⟶ "]
          [" <= " " ≤ "]
          [" >= " " ≥ "]
          ["--" " — "]
          ["~=" "≈"]
          ] "REPORT" "HILIGHT")
        ;;
        ))))

(defun xah-prettify-punctuations (Begin End)
  "Replace straight double quotes to curly ones etc.
URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Version: 2019-07-25 2021-08-17"
  (interactive
   (let ($p1 $p2)
     (let (($bds (xah-get-bounds-of-block-or-region))) (setq $p1 (car $bds) $p2 (cdr $bds)))
     (list $p1 $p2)))
  (let ( (case-fold-search nil))
    (save-excursion
      (save-restriction
        (narrow-to-region Begin End )
        (xah-replace-pairs-region
         (point-min) (point-max)
         [
          ["  —  " " — "] ; rid of extra space in em-dash
          ["..." "…"]
          [" & " " & "]
          [" :)" " 😊"]
          [" :(" " ☹"]
          [" ;)" " 😉"]
          [" , " ", "]
          ["—" " — "]
          ] "REPORT" "HILIGHT")
        ;;
        ))))

(defun xah-replace-straight-quotes (Begin End)
  "Replace straight double quotes to curly ones, and others.
Works on current text block or selection.

Examples of changes:
 「\"…\"」 → 「“…”」
 「...」 → 「…」
 「I’m」 → 「I'm」
 「--」 → 「—」
 「~=」 → 「≈」

When called in lisp code, Begin and End are region begin/end positions.

WARNING: this command does not guarantee 100% correct conversion of quotes, because it impossible. You should double check highlighted places after.

URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Version: 2015-04-29 2021-08-17"
  ;; some examples for debug
  ;; do "‘em all -- done..."
  ;; I’am not
  ;; said "can’t have it, can’t, just can’t"
  ;; ‘I’ve can’t’
  (interactive
   (let ($p1 $p2)
     (let (($bds (xah-get-bounds-of-block-or-region))) (setq $p1 (car $bds) $p2 (cdr $bds)))
     (list $p1 $p2)))
  (let ( (case-fold-search nil))
    (save-excursion
      (save-restriction
        (narrow-to-region Begin End )
        (xah-prettify-punctuations (point-min) (point-max))
        (xah-ascii-to-math-symbol (point-min) (point-max))
        ;; Note: order is important since this is huristic.
        (xah-replace-pairs-region
         (point-min)
         (point-max)
         [
          ;; fix GNU style ASCII quotes
          ["``" "“"]
          ["''" "”"]
          ] "REPORT" "HILIGHT")
        (xah-replace-pairs-region
         (point-min) (point-max)
         [
          ;; double straight quote → double curly quotes
          ["\n\"" "\n“"]
          [">\"" ">“"]
          ["(\"" "(“"]
          [" \"" " “"]
          ["\" " "” "]
          ["\"," "”,"]
          ["\",\n" "”,\n"]
          ["\". " "”. "]
          ["\".\n" "”.\n"]
          ["\"." "”."]
          ["\"?" "”?"]
          ["\";" "”;"]
          ["\":" "”:"]
          ["\")" "”)"]
          ["\"]" "”]"]
          ;; ["\"[" "\”["]
          [".\"" ".”"]
          [",\"" ",”"]
          ["!\"" "!”"]
          ["?\"" "?”"]
          ["\"<" "”<"]
          ["\"\n" "”\n"]
          ] "REPORT" "HILIGHT")
        ;; fix straight double quotes by regex
        ;; (xah-replace-regexp-pairs-region (point-min) (point-max) [ ["\"\\([-A-Za-z0-9]+\\)\"" "“\\1”"] ] t nil "HILIGHT")
        (xah-single-quote-to-curly (point-min) (point-max))
        ;; fix back escaped quotes in code
        ;; (xah-replace-pairs-region (point-min) (point-max) [ ["\\”" "\\\""] ["\\”" "\\\""] ] "REPORT" "HILIGHT")
        ;; fix back. quotes in HTML code
        ;; (xah-replace-regexp-pairs-region
        ;;  (point-min) (point-max)
        ;;  [
        ;;   ["” \\([-a-z]+\\)="       "\" \\1="] ; any 「” some-thing=」
        ;;   ["=”" "=\""]
        ;;   ["/” " "/\" "]
        ;;   ["\\([0-9]+\\)” "     "\\1\" "]
        ;;   ] t nil "HILIGHT"
        ;;  )
        ))))

You need the elisp library Emacs: Xah Replace Pairs, xah-replace-pairs.el.

you need xah-get-bounds-of-block and xah-get-bounds-of-block-or-region from Emacs: Xah Fly Keys