Emacs: Replace Straight Quotes to Curly πŸ“œ

By Xah Lee. Date: . Last updated: .

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

(defun xah-ascii-to-math-symbol (&optional Begin End)
  "Replace ASCII less-or-equal etc to unicode chars, on text block or region.

Example:
<= β†’ ≀
>= β†’ β‰₯
---> β†’ ⟢
--> β†’ ⟢
-- β†’ β€”
~= β†’ β‰ˆ
Each of these on the left must have a space around them.

URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Created: 2019-07-25
Version: 2025-12-19"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (let ((case-fold-search nil))
      (save-excursion
        (save-restriction
          (narrow-to-region xbeg xend)
          (goto-char (point-min))
          (while (search-forward " <= " nil t) (replace-match " ≀ "))
          (goto-char (point-min))
          (while (search-forward " >= " nil t) (replace-match " β‰₯ "))
          (goto-char (point-min))
          (while (search-forward " ---> " nil t) (replace-match " ⟢ "))
          (goto-char (point-min))
          (while (search-forward " --> " nil t) (replace-match " ⟢ "))
          (goto-char (point-min))
          (while (search-forward "--" nil t) (replace-match " β€” "))
          (goto-char (point-min))
          (while (search-forward "~=" nil t) (replace-match "β‰ˆ")))))))
(defun xah-prettify-punctuations (&optional Begin End)
  "Replace ASCII punctuations etc to Unicode symbols.

Example:
 -- β†’ β€”
 & β†’ οΌ†
 ... β†’ …
 :) β†’ 😊

URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Created: 2019-07-25
Version: 2025-12-19"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (save-restriction
      (narrow-to-region xbeg xend)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "..." nil t) (replace-match "…"))
        (goto-char (point-min))
        (while (search-forward " & " nil t) (replace-match " οΌ† "))
        (goto-char (point-min))
        (while (search-forward " :)" nil t) (replace-match " 😊"))
        (goto-char (point-min))
        (while (search-forward " :(" nil t) (replace-match " ☹"))
        (goto-char (point-min))
        (while (search-forward " ;)" nil t) (replace-match " πŸ˜‰"))
        (goto-char (point-min))
        (while (search-forward " , " nil t) (replace-match ", "))
        (goto-char (point-min))
        (while (search-forward "β€”" nil t) (replace-match " β€” "))
        (goto-char (point-min))
        (while (search-forward "  β€”  " nil t) (replace-match " β€” "))))))
(defun xah-fix-curly-single-quote-to-apostrophe (&optional Begin End)
  "Replace RIGHT SINGLE QUOTATION MARK to APOSTROPHE.

Example:
don’t  β†’ don't
i’ve β†’ i've
it’s β†’ it's

URL `http://xahlee.info/emacs/emacs/elisp_straight_curly_quotes.html'
Created: 2019-07-25
Version: 2025-12-19"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (save-excursion
      (save-restriction
        (narrow-to-region xbeg xend)
        (let ((case-fold-search t))
          (goto-char (point-min))
          (while (re-search-forward "\\bcan’t\\b" nil t) (replace-match "can't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bdon’t\\b" nil t) (replace-match "don't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bdoesn’t\\b" nil t) (replace-match "doesn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bwon’t\\b" nil t) (replace-match "won't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bisn’t\\b" nil t) (replace-match "isn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\baren’t\\b" nil t) (replace-match "aren't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bain’t\\b" nil t) (replace-match "ain't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bdidn’t\\b" nil t) (replace-match "didn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\baren’t\\b" nil t) (replace-match "aren't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bwasn’t\\b" nil t) (replace-match "wasn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bweren’t\\b" nil t) (replace-match "weren't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bcouldn’t\\b" nil t) (replace-match "couldn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\bshouldn’t\\b" nil t) (replace-match "shouldn't"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’ve\\b" nil t) (replace-match "'ve"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’re\\b" nil t) (replace-match "'re"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’ll\\b" nil t) (replace-match "'ll"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’m\\b" nil t) (replace-match "'m"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’d\\b" nil t) (replace-match "'d"))
          (goto-char (point-min))
          (while (re-search-forward "\\b’s\\b" nil t) (replace-match "'s"))
          (goto-char (point-min))
          (while (re-search-forward "s’ " nil t) (replace-match "s' "))
          (goto-char (point-min))
          (while (re-search-forward "s’\n" nil t) (replace-match "s'\n"))
          (goto-char (point-min))
          (while (re-search-forward "\\bβ€˜em\\b" nil t) (replace-match "'em")))))))
(defun xah-fix-double-quote-to-curly (&optional Begin End)
  "Change straight double quotes to curly.
This is a heuristic based algo, result can be wrong in some places.

todo: text inside <something something>, do not change inside

Created: 2023-01-04
Version: 2025-03-25"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (let ((case-fold-search nil))
      (save-excursion
        (save-restriction
          (narrow-to-region xbeg xend)
          (xah-replace-regexp-pairs-region
           (point-min) (point-max)
           [ ["^\"" "β€œ"]
             ["\"\\'" "”"]
             ["\\([0-9]+\\)” \\([0-9]+\\)" "\\1β€³ \\2"]
             ] t )
          (xah-replace-pairs-region
           (point-min) (point-max)
           [
            ["\n\"" "\nβ€œ"]
            [">\"" ">β€œ"]
            ["(\"" "(β€œ"]
            [" \"" " β€œ"]
            ["\" " "” "]
            ["\"," "”,"]
            ["\",\n" "”,\n"]
            ["\". " "”. "]
            ["\".\n" "”.\n"]
            ["\"." "”."]
            ["\"?" "”?"]
            ["\";" "”;"]
            ["\":" "”:"]
            ["\")" "”)"]
            ["\"]" "”]"]
            ;; ["\"[" "\”["]
            [".\"" ".”"]
            [",\"" ",”"]
            ["!\"" "!”"]
            ["?\"" "?”"]
            ["\"<" "”<"]
            ;; ["\"\n" "”\n"]
            ] t t))))))
(defun xah-fix-double-quote-to-curly (&optional Begin End)
  "Change straight double quotes to curly.
This is a heuristic based algo, result can be wrong in some places.

todo: text inside <something something>, do not change inside

Created: 2023-01-04
Version: 2025-03-25"
  (interactive)
  (let (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (let ((case-fold-search nil))
      (save-excursion
        (save-restriction
          (narrow-to-region xbeg xend)
          (xah-replace-regexp-pairs-region
           (point-min) (point-max)
           [ ["^\"" "β€œ"]
             ["\"\\'" "”"]
             ["\\([0-9]+\\)” \\([0-9]+\\)" "\\1β€³ \\2"]
             ] t )
          (xah-replace-pairs-region
           (point-min) (point-max)
           [
            ["\n\"" "\nβ€œ"]
            [">\"" ">β€œ"]
            ["(\"" "(β€œ"]
            [" \"" " β€œ"]
            ["\" " "” "]
            ["\"," "”,"]
            ["\",\n" "”,\n"]
            ["\". " "”. "]
            ["\".\n" "”.\n"]
            ["\"." "”."]
            ["\"?" "”?"]
            ["\";" "”;"]
            ["\":" "”:"]
            ["\")" "”)"]
            ["\"]" "”]"]
            ;; ["\"[" "\”["]
            [".\"" ".”"]
            [",\"" ",”"]
            ["!\"" "!”"]
            ["?\"" "?”"]
            ["\"<" "”<"]
            ;; ["\"\n" "”\n"]
            ] t t))))))
(defun xah-replace-straight-quotes (&optional 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'
Created: 2015-04-29
Version: 2025-03-25"
  ;; 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 (xbeg xend)
    (seq-setq (xbeg xend) (if (and Begin End) (list Begin End) (if (region-active-p) (list (region-beginning) (region-end)) (list (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point))) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point)))))))
    (let ((case-fold-search nil))
      (save-excursion
        (save-restriction
          (narrow-to-region xbeg xend)
          (xah-prettify-punctuations (point-min) (point-max))
          (xah-ascii-to-math-symbol (point-min) (point-max))
          (xah-fix-double-quote-to-curly (point-min) (point-max))
          (xah-fix-curly-single-quote-to-apostrophe (point-min) (point-max)))))))

requires package Emacs: xah-replace-pairs.el πŸ“¦