Subject: Re: bug#24709: 26.0.50; Support for Markdown in electric-quote-mode

From: Philipp Stephani <[email-masked]>
Date: Sun, 02 Jul 2017 16:20:00 +0000

Philipp Stephani <[email-masked]> schrieb am So., 2. Juli 2017 um
17:49 Uhr:

> Philipp Stephani <[email-masked]> schrieb am Mi., 28. Juni 2017 um
> 23:59 Uhr:
>
>> Philipp Stephani <[email-masked]> schrieb am So., 16. Okt. 2016
>> um 19:22 Uhr:
>>
>>>
>>> When editing a Markdown document with electric-quote-mode enabled, the
>>> experience is not optimal because ` (backtick) in Markdown is a syntax
>>> element (for introducing code spans and blocks), but is converted by
>>> electric-quote-mode.  Also ` and ' inside code spans and blocks continue
>>> to insert curly quotes where they shouldn't.  I propose the following:
>>>
>>> - Provide an alternative behavior of electric-quote-mode where ` doesn't
>>>   trigger replacement and ' can insert left or right curly quotes,
>>>   depending on whether it follows a space or similar (newline, beginning
>>>   of buffer, opening parenthesis).
>>>
>>> - Provide a way to disable electric-quote-mode for certain regions,
>>>   effectively the inverse of the existing electric-quote-string etc.
>>>   This could be in the form of syntactic elements or text properties.
>>>
>>>
>>>
>> I've attached a patch. It doesn't cover all cases, though; e.g. an
>> unterminated Markdown code span is not supported yet.
>>
>
> Pushed as 34d4720f83.
>

Here are a couple more patches.
From 7d9439e3096a37ba381a9fd03d63d1b5fcf4683a Mon Sep 17 00:00:00 2001
From: Philipp Stephani <[email-masked]>
Date: Sun, 2 Jul 2017 18:03:06 +0200
Subject: [PATCH 1/3] Refactor 'electric-quote-mode'

* lisp/electric.el (electric-quote-post-self-insert-function): Remove
local variable 'start', which was misnamed and only used once.
---
 lisp/electric.el | 106 +++++++++++++++++++++++++++----------------------------
 1 file changed, 52 insertions(+), 54 deletions(-)

diff --git a/lisp/electric.el b/lisp/electric.el
index 1564df5949..103f378ed3 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -460,60 +460,58 @@ electric-quote-post-self-insert-function
   (when (and electric-quote-mode
              (or (eq last-command-event ?\')
                  (and (not electric-quote-context-sensitive)
-                      (eq last-command-event ?\`))))
-    (let ((start
-           (if (and comment-start comment-use-syntax)
-               (when (or electric-quote-comment electric-quote-string)
-                 (let* ((syntax (syntax-ppss))
-                        (beg (nth 8 syntax)))
-                   (and beg
-                        (or (and electric-quote-comment (nth 4 syntax))
-                            (and electric-quote-string (nth 3 syntax)))
-                        ;; Do not requote a quote that starts or ends
-                        ;; a comment or string.
-                        (eq beg (nth 8 (save-excursion
-                                         (syntax-ppss (1- (point)))))))))
-             (and electric-quote-paragraph
-                  (derived-mode-p 'text-mode)
-                  ;; FIXME: There should be a ‘cl-disjoint’ function.
-                  (null (cl-intersection (face-at-point nil 'multiple)
-                                         electric-quote-code-faces
-                                         :test #'eq))
-                  ;; FIXME: Why is the next form there?  It’s never
-                  ;; nil.
-                  (or (eq last-command-event ?\`)
-                      (save-excursion (backward-paragraph) (point)))))))
-      (pcase electric-quote-chars
-        (`(,q< ,q> ,q<< ,q>>)
-         (when start
-           (save-excursion
-             (let ((backtick ?\`))
-               (if (or (eq last-command-event ?\`)
-                       (and electric-quote-context-sensitive
-                            (save-excursion
-                              (backward-char)
-                              (or (bobp) (bolp)
-                                  (memq (char-before) (list q< q<<))
-                                  (memq (char-syntax (char-before))
-                                        '(?\s ?\())))
-                            (setq backtick ?\')))
-                   (cond ((search-backward (string q< backtick) (- (point) 2) t)
-                          (replace-match (string q<<))
-                          (when (and electric-pair-mode
-                                     (eq (cdr-safe
-                                          (assq q< electric-pair-text-pairs))
-                                         (char-after)))
-                            (delete-char 1))
-                          (setq last-command-event q<<))
-                         ((search-backward (string backtick) (1- (point)) t)
-                          (replace-match (string q<))
-                          (setq last-command-event q<)))
-                 (cond ((search-backward (string q> ?') (- (point) 2) t)
-                        (replace-match (string q>>))
-                        (setq last-command-event q>>))
-                       ((search-backward "'" (1- (point)) t)
-                        (replace-match (string q>))
-                        (setq last-command-event q>))))))))))))
+                      (eq last-command-event ?\`)))
+             (if (and comment-start comment-use-syntax)
+                 (when (or electric-quote-comment electric-quote-string)
+                   (let* ((syntax (syntax-ppss))
+                          (beg (nth 8 syntax)))
+                     (and beg
+                          (or (and electric-quote-comment (nth 4 syntax))
+                              (and electric-quote-string (nth 3 syntax)))
+                          ;; Do not requote a quote that starts or ends
+                          ;; a comment or string.
+                          (eq beg (nth 8 (save-excursion
+                                           (syntax-ppss (1- (point)))))))))
+               (and electric-quote-paragraph
+                    (derived-mode-p 'text-mode)
+                    ;; FIXME: There should be a ‘cl-disjoint’ function.
+                    (null (cl-intersection (face-at-point nil 'multiple)
+                                           electric-quote-code-faces
+                                           :test #'eq))
+                    ;; FIXME: Why is the next form there?  It’s never
+                    ;; nil.
+                    (or (eq last-command-event ?\`)
+                        (save-excursion (backward-paragraph) (point))))))
+    (pcase electric-quote-chars
+      (`(,q< ,q> ,q<< ,q>>)
+       (save-excursion
+         (let ((backtick ?\`))
+           (if (or (eq last-command-event ?\`)
+                   (and electric-quote-context-sensitive
+                        (save-excursion
+                          (backward-char)
+                          (or (bobp) (bolp)
+                              (memq (char-before) (list q< q<<))
+                              (memq (char-syntax (char-before))
+                                    '(?\s ?\())))
+                        (setq backtick ?\')))
+               (cond ((search-backward (string q< backtick) (- (point) 2) t)
+                      (replace-match (string q<<))
+                      (when (and electric-pair-mode
+                                 (eq (cdr-safe
+                                      (assq q< electric-pair-text-pairs))
+                                     (char-after)))
+                        (delete-char 1))
+                      (setq last-command-event q<<))
+                     ((search-backward (string backtick) (1- (point)) t)
+                      (replace-match (string q<))
+                      (setq last-command-event q<)))
+             (cond ((search-backward (string q> ?') (- (point) 2) t)
+                    (replace-match (string q>>))
+                    (setq last-command-event q>>))
+                   ((search-backward "'" (1- (point)) t)
+                    (replace-match (string q>))
+                    (setq last-command-event q>))))))))))
 
 (put 'electric-quote-post-self-insert-function 'priority 10)
 
-- 
2.13.2

From 1a4d0ada2450f22246ad22d25e7c15d8da3c5249 Mon Sep 17 00:00:00 2001
From: Philipp Stephani <[email-masked]>
Date: Sun, 2 Jul 2017 18:14:21 +0200
Subject: [PATCH 3/3] Further improve electric quote support for Markdown
 (Bug#24709)

Markdown sets both 'comment-start' and 'comment-use-syntax' to non-nil
values.  Therefore 'electric-quote-mode' recognized it as a
programming mode.  Fix this by first checking whether the current
major mode is derived from 'text-mode'.

* lisp/electric.el (electric-quote-post-self-insert-function): Treat
'text-mode' as stronger signal than comment syntax.

* test/lisp/electric-tests.el (electric-quote-markdown-in-text)
(electric-quote-markdown-in-code): Adapt unit tests.
---
 lisp/electric.el            | 34 +++++++++++++++++-----------------
 test/lisp/electric-tests.el |  8 ++++++--
 2 files changed, 23 insertions(+), 19 deletions(-)

diff --git a/lisp/electric.el b/lisp/electric.el
index a1e9079cb1..c335ae5d72 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -461,23 +461,23 @@ electric-quote-post-self-insert-function
              (or (eq last-command-event ?\')
                  (and (not electric-quote-context-sensitive)
                       (eq last-command-event ?\`)))
-             (if (and comment-start comment-use-syntax)
-                 (when (or electric-quote-comment electric-quote-string)
-                   (let* ((syntax (syntax-ppss))
-                          (beg (nth 8 syntax)))
-                     (and beg
-                          (or (and electric-quote-comment (nth 4 syntax))
-                              (and electric-quote-string (nth 3 syntax)))
-                          ;; Do not requote a quote that starts or ends
-                          ;; a comment or string.
-                          (eq beg (nth 8 (save-excursion
-                                           (syntax-ppss (1- (point)))))))))
-               (and electric-quote-paragraph
-                    (derived-mode-p 'text-mode)
-                    ;; FIXME: There should be a ‘cl-disjoint’ function.
-                    (null (cl-intersection (face-at-point nil 'multiple)
-                                           electric-quote-code-faces
-                                           :test #'eq)))))
+             (if (derived-mode-p 'text-mode)
+                 (and electric-quote-paragraph
+                      ;; FIXME: There should be a ‘cl-disjoint’ function.
+                      (null (cl-intersection (face-at-point nil 'multiple)
+                                             electric-quote-code-faces
+                                             :test #'eq)))
+               (and comment-start comment-use-syntax
+                    (or electric-quote-comment electric-quote-string)
+                    (let* ((syntax (syntax-ppss))
+                           (beg (nth 8 syntax)))
+                      (and beg
+                           (or (and electric-quote-comment (nth 4 syntax))
+                               (and electric-quote-string (nth 3 syntax)))
+                           ;; Do not requote a quote that starts or ends
+                           ;; a comment or string.
+                           (eq beg (nth 8 (save-excursion
+                                            (syntax-ppss (1- (point)))))))))))
     (pcase electric-quote-chars
       (`(,q< ,q> ,q<< ,q>>)
        (save-excursion
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el
index 6f63d30e75..4212198b0a 100644
--- a/test/lisp/electric-tests.el
+++ b/test/lisp/electric-tests.el
@@ -694,11 +694,14 @@ electric-quote-context-sensitive-after-paren-double
   :bindings '((electric-quote-context-sensitive . t))
   :test-in-comments nil :test-in-strings nil)
 
+;; Simulate ‘markdown-mode’: it sets both ‘comment-start’ and
+;; ‘comment-use-syntax’, but derives from ‘text-mode’.
 (define-electric-pair-test electric-quote-markdown-in-text
   "" "'" :expected-string "’" :expected-point 2
   :modes '(text-mode)
   :fixture-fn #'electric-quote-local-mode
-  :bindings '((electric-quote-code-faces font-lock-constant-face))
+  :bindings '((electric-quote-code-faces font-lock-constant-face)
+              (comment-start "<!--") (comment-use-syntax t))
   :test-in-comments nil :test-in-strings nil)
 
 (define-electric-pair-test electric-quote-markdown-in-code
@@ -706,7 +709,8 @@ electric-quote-markdown-in-code
   :expected-string "`'a`" :expected-point 3
   :modes '(text-mode)
   :fixture-fn #'electric-quote-local-mode
-  :bindings '((electric-quote-code-faces font-lock-constant-face))
+  :bindings '((electric-quote-code-faces font-lock-constant-face)
+              (comment-start "<!--") (comment-use-syntax t))
   :test-in-comments nil :test-in-strings nil)
 
 (provide 'electric-tests)
-- 
2.13.2

From 0acddd26f1bdb9b08dea9812af2883925e023557 Mon Sep 17 00:00:00 2001
From: Philipp Stephani <[email-masked]>
Date: Sun, 2 Jul 2017 18:04:38 +0200
Subject: [PATCH 2/3] Remove pointless code in 'electric-quote-mode'

* lisp/electric.el (electric-quote-post-self-insert-function): Remove
pointless form.
---
 lisp/electric.el | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/lisp/electric.el b/lisp/electric.el
index 103f378ed3..a1e9079cb1 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -477,11 +477,7 @@ electric-quote-post-self-insert-function
                     ;; FIXME: There should be a ‘cl-disjoint’ function.
                     (null (cl-intersection (face-at-point nil 'multiple)
                                            electric-quote-code-faces
-                                           :test #'eq))
-                    ;; FIXME: Why is the next form there?  It’s never
-                    ;; nil.
-                    (or (eq last-command-event ?\`)
-                        (save-excursion (backward-paragraph) (point))))))
+                                           :test #'eq)))))
     (pcase electric-quote-chars
       (`(,q< ,q> ,q<< ,q>>)
        (save-excursion
-- 
2.13.2