Subject: bug#20545: New minor mode Electric Punct

From: Paul Eggert <[email-masked]>
Date: Sun, 10 May 2015 16:55:42 -0700

Attached are two proposed patches.  The first creates a new minor mode Electric 
Punct that does a variant of "smart quoting" designed for the Emacs tradition of 
using ` and ' as aliases for left and right single quotation marks.  This new 
minor mode is inactive by default, but the user can enable it in certain 
contexts.  The second patch modifies .dir-locals.el to enable the minor mode in 
contexts where it should be useful when editing Emacs itself.

These patches are related to Bug#20385 in that they make it much easier to edit 
doc strings formatted ‘like this’, which is the point of Bug#20385.  (All that 
one needs to do is to type the string "`like this'".)  However, the main idea of 
this patch is independent of the Emacs doc string issue, as Emacs should make it 
easy to quote ‘like this’ when the user prefers this form.

I named this mode "Electric Punct" because I anticipate that there may be a 
similar need to enter em and en dashes, and a few other non-ASCII punctuation 
marks commonly used in text.  Quotes are a good place to start, so that's where 
I started.
>From 106bdf81a5d27e6b055da002f8f000614268a8a1 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sun, 10 May 2015 16:06:05 -0700
Subject: [PROPOSED PATCH 1/2] New minor mode Electric Punct

* doc/emacs/basic.texi (Inserting Text):
Briefly mention how to insert curved quotation marks.
* doc/emacs/modes.texi (Minor Modes): Mention Electric Punct mode.
* doc/emacs/text.texi (Punctuation): New section.
* etc/NEWS: Mention this.
* lisp/electric.el (electric-punct-comment)
(electric-punct-string, electric-punct-paragraph):
New custom vars.
(electric-punct-post-self-insert-function): New function.
(electric-punct-mode, electric-punct-local-mode): New minor modes.
---
 doc/emacs/basic.texi |  7 ++++
 doc/emacs/modes.texi |  9 +++++
 doc/emacs/text.texi  | 38 ++++++++++++++++++++++
 etc/NEWS             |  3 +-
 lisp/electric.el     | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 148 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/basic.texi b/doc/emacs/basic.texi
index be45856..2ade172 100644
--- a/doc/emacs/basic.texi
+++ b/doc/emacs/basic.texi
@@ -127,6 +127,13 @@ sign (Unicode code-point @code{U+221E}):
   A numeric argument to @kbd{C-q} or @kbd{C-x 8 @key{RET}} specifies
 how many copies of the character to insert (@pxref{Arguments}).
 
+  In some contexts, if you type a quotation using grave accent and
+apostrophe @samp{`like this'}, it is converted to a form @samp{‘like
+this’} using single quotation marks.  Similarly, typing a quotation
+@samp{``like this''} using double grave accent and apostrophe converts
+it to a form @samp{“like this”} using double quotation marks.
+@xref{Punctuation}.
+
 @node Moving Point
 @section Changing the Location of Point
 
diff --git a/doc/emacs/modes.texi b/doc/emacs/modes.texi
index 0e8f46a..98a8858 100644
--- a/doc/emacs/modes.texi
+++ b/doc/emacs/modes.texi
@@ -200,6 +200,15 @@ Auto Save mode saves the buffer contents periodically to reduce the
 amount of work you can lose in case of a crash.  @xref{Auto Save}.
 
 @item
+@cindex Electric Punct mode
+@cindex mode, Electric Punct
+@findex electric-punct-mode
+Electric Punct mode automatically converts some punctation marks.  For
+example, it requotes text typed @samp{`like this'} to text @samp{‘like
+this’}.  You can control what kind of text it operates in, and you can
+disable it entirely in individual buffers.  @xref{Punctuation}.
+
+@item
 Enriched mode enables editing and saving of formatted text.
 @xref{Enriched Text}.
 
diff --git a/doc/emacs/text.texi b/doc/emacs/text.texi
index 9bc5ade..06eb4fd 100644
--- a/doc/emacs/text.texi
+++ b/doc/emacs/text.texi
@@ -69,6 +69,7 @@ for editing such pictures.
 * Sentences::           Moving over and killing sentences.
 * Paragraphs::          Moving over paragraphs.
 * Pages::               Moving over pages.
+* Punctuation::         Inserting punctuation.
 * Filling::             Filling or justifying text.
 * Case::                Changing the case of text.
 * Text Mode::           The major modes for editing text files.
@@ -404,6 +405,43 @@ that separates pages (@pxref{Regexps}).  The normal value of this
 variable is @code{"^\f"}, which matches a formfeed character at the
 beginning of a line.
 
+@node Punctuation
+@section Punctuation
+@cindex Punctuation
+@cindex Quotation marks
+@cindex Electric Punct mode
+@cindex mode, Electric Punct
+  One common way to quote is the typewriter convention, which quotes
+uses apostrophes @samp{'like this'} or double-quotes @samp{"like
+this"}.  Another common way is the curved quote convention, which uses
+left and right single and double quotation marks @samp{‘like this’} or
+@samp{“like this”}.  Typewriter quotes are simple and portable; curved
+quotes are unambiguous and look nicer.
+
+  Electric Punct mode makes it easier to type curved quotes.  It
+optionally converts a quotation using grave accent and apostrophe
+@samp{`like this'}, to a form using single quotation marks @samp{‘like
+this’}.  Similarly, it converts a quotation using double grave accent
+and double apostrophe @samp{``like this''} to a form using double
+quotation marks @samp{“like this”}.
+
+  You can customize the behavior of Electric Punct mode by setting
+variables that control where it is active.  If
+@code{electric-punct-comment} is non-nil, it is active within
+programming-language comments; if @code{electric-punct-string} is
+non-nil, it is active within programming-language strings; and if
+@code{electric-punct-paragraph} is non-nil, it is active within text
+paragraphs.  These variables default to nil, which causes Electric
+Punct mode to be inactive everywhere.
+
+  Electric Punct mode is enabled by default.  To suppress it for a
+single use, type @kbd{C-q `} or @kbd{C-q '} instead of @kbd{`} or
+@kbd{'}.  To toggle it, type @kbd{M-x electric-punct-mode}.  To toggle
+it in a single buffer, use @kbd{M-x electric-punct-local-mode}.  To
+insert a curved quote even when Electric Punct is disabled or
+inactive, use @kbd{C-x 8 @key{RET}} (@code{insert-char}).
+@xref{Inserting Text}.
+
 @node Filling
 @section Filling Text
 @cindex filling text
diff --git a/etc/NEWS b/etc/NEWS
index 844181c..61a2c35 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -213,7 +213,8 @@ successive char insertions.
 
 ** Unicode names entered via C-x 8 RET now use substring completion by default.
 
-** New minor mode global-eldoc-mode is enabled by default.
+** New minor modes electric-punct-mode and global-eldoc-mode
+are enabled by default.
 
 ** Emacs now supports "bracketed paste mode" when running on a terminal
 that supports it.  This facility allows Emacs to understand pasted
diff --git a/lisp/electric.el b/lisp/electric.el
index dd7767f..bac1918 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -413,6 +413,98 @@ The variable `electric-layout-rules' says when and how to insert newlines."
          (remove-hook 'post-self-insert-hook
                       #'electric-layout-post-self-insert-function))))
 
+;;; Electric punctuation.
+
+(defcustom electric-punct-comment nil
+  "Non-nil means to use electric punctuation in program comments."
+  :type 'boolean :safe 'booleanp :group 'electricity)
+
+(defcustom electric-punct-string nil
+  "Non-nil means to use electric punctuation in program strings."
+  :type 'boolean :safe 'booleanp :group 'electricity)
+
+(defcustom electric-punct-paragraph nil
+  "Non-nil means to use electric punctuation in text paragraphs."
+  :type 'boolean :safe 'booleanp :group 'electricity)
+
+(defun electric-punct-post-self-insert-function ()
+  "Function that ‘electric-punct-mode’ adds to ‘post-self-insert-hook’.
+This repunctuates when a punctuation key is typed."
+  (when (and electric-punct-mode enable-multibyte-characters
+             (memq last-command-event '(?' ?`)))
+    (let ((start
+           (if comment-start
+               (when (or electric-punct-comment electric-punct-string)
+                 (let ((syntax (syntax-ppss)))
+                   (and (or (and electric-punct-comment (nth 4 syntax))
+                            (and electric-punct-string (nth 3 syntax)))
+                        (nth 8 syntax))))
+             (and electric-punct-paragraph
+                  (derived-mode-p 'text-mode)
+                  (or (eq last-command-event ?`)
+                      (save-excursion (backward-paragraph) (point)))))))
+      (when start
+        (save-excursion
+          (if (eq last-command-event ?`)
+              (cond ((re-search-backward "[`‘]`" (- (point) 2) t)
+                     (replace-match "“"))
+                    ((search-backward "`" (1- (point)) t)
+                     (replace-match "‘")))
+            (let ((pos (point)))
+              (if (memq (char-before (1- (point))) '(?' ?’))
+                  (when (and (search-backward "“" start t)
+                             (eq pos (re-search-forward
+                                      "“\\([^”]*\\)['’]'" pos t)))
+                    (replace-match "“\\1”"))
+                (when (and (search-backward "‘" start t)
+                           (eq pos (re-search-forward
+                                    "‘\\([^’]*\\)'" pos t)))
+                  (replace-match "‘\\1’"))))))))))
+
+(put 'electric-punct-post-self-insert-function 'priority  60)
+
+;;;###autoload
+(define-minor-mode electric-punct-mode
+  "Toggle on-the-fly repunctuation (Electric Punctuation mode).
+With a prefix argument ARG, enable Electric Punctuation mode if
+ARG is positive, and disable it otherwise.  If called from Lisp,
+enable the mode if ARG is omitted or nil.
+
+When enabled, this replaces `foo bar' with ‘foo bar’ and replaces
+``foo bar'' with “foo bar” as you type.  This occurs only in
+comments, strings, and text paragraphs, and these are selectively
+controlled with ‘electric-punct-comment’,
+‘electric-punct-string’, and ‘electric-punct-paragraph’.
+
+This is a global minor mode.  To toggle the mode in a single buffer,
+use ‘electric-punct-local-mode’."
+  :global t :group 'electricity
+  :initialize 'custom-initialize-delay
+  :init-value t
+  (if (not electric-punct-mode)
+      (unless (catch 'found
+                (dolist (buf (buffer-list))
+                  (with-current-buffer buf
+                    (if electric-punct-mode (throw 'found t)))))
+        (remove-hook 'post-self-insert-hook
+                     #'electric-punct-post-self-insert-function))
+    (add-hook 'post-self-insert-hook
+              #'electric-punct-post-self-insert-function)
+    (electric--sort-post-self-insertion-hook)))
+
+;;;###autoload
+(define-minor-mode electric-punct-local-mode
+  "Toggle ‘electric-punct-mode’ only in this buffer."
+  :variable (buffer-local-value 'electric-punct-mode (current-buffer))
+  (cond
+   ((eq electric-punct-mode (default-value 'electric-punct-mode))
+    (kill-local-variable 'electric-punct-mode))
+   ((not (default-value 'electric-punct-mode))
+    ;; Locally enabled, but globally disabled.
+    (electric-punct-mode 1)                ; Setup the hooks.
+    (setq-default electric-punct-mode nil) ; But keep it globally disabled.
+    )))
+
 (provide 'electric)
 
 ;;; electric.el ends here
-- 
2.1.0

>From 401bf3e899ac84d062c86da469c33c662f969ef8 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sun, 10 May 2015 16:07:13 -0700
Subject: [PROPOSED PATCH 2/2] Use Electric Punct when editing Emacs source
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is a companion to the patch proposed in Bug#20385.
The idea is to let maintainers quote ‘like this’ in doc strings
just as easily as they traditionally quote `like this'.
* .dir-locals.el: Add electric-punct-comment,
electric-punct-paragraph.
(emacs-lisp-mode): Add electric-punct-string.
---
 .dir-locals.el | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/.dir-locals.el b/.dir-locals.el
index 9853d7b..9c73abe 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,5 +1,7 @@
 ((nil . ((tab-width . 8)
          (sentence-end-double-space . t)
+         (electric-punct-comment . t)
+         (electric-punct-paragraph . t)
          (fill-column . 70)))
  (c-mode . ((c-file-style . "GNU")))
  (objc-mode . ((c-file-style . "GNU")))
@@ -10,4 +12,5 @@
 		     (bug-reference-url-format . "http://debbugs.gnu.org/%s")
 		     (mode . bug-reference)))
  (diff-mode . ((mode . whitespace)))
- (emacs-lisp-mode . ((indent-tabs-mode . nil))))
+ (emacs-lisp-mode . ((electric-punct-string . t)
+                     (indent-tabs-mode . nil))))
-- 
2.1.0