From: Philipp Stephani <p.stephani2@gmail.com>
To: 24709@debbugs.gnu.org
Subject: bug#24709: 26.0.50; Support for Markdown in electric-quote-mode
Date: Wed, 28 Jun 2017 21:59:52 +0000 [thread overview]
Message-ID: <CAArVCkQBkN=s8HNEDF=ptp-0EgeEMK4v0vzrumBZo08BNBmLuA@mail.gmail.com> (raw)
In-Reply-To: <m237jwxldb.fsf@gmail.com>
[-- Attachment #1.1: Type: text/plain, Size: 1053 bytes --]
Philipp Stephani <p.stephani2@gmail.com> 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.
[-- Attachment #1.2: Type: text/html, Size: 1411 bytes --]
[-- Attachment #2: 0001-Electric-quotes-Improve-support-for-Markdown-mode-Bug-.txt --]
[-- Type: text/plain, Size: 13187 bytes --]
From 126557c6998950c522baab9d344bb62629b7f9c0 Mon Sep 17 00:00:00 2001
From: Philipp Stephani <phst@google.com>
Date: Wed, 28 Jun 2017 23:47:57 +0200
Subject: [PATCH] Electric quotes: Improve support for Markdown mode
(Bug#24709)
Introduce a new user option 'electric-quote-context-sensitive'. If
non-nil, have ' insert an opening quote if sensible.
Also introduce a new variable 'electric-quote-code-faces'. Major
modes such as 'markdown-mode' can add faces to this list to treat text
as inline code and disable electric quoting.
* lisp/electric.el (electric-quote-context-sensitive): New user
option.
(electric-quote-code-faces): New variable.
(electric-quote-post-self-insert-function): Treat ' as ` if
desired and applicable; disable electric quoting for given faces.
* test/lisp/electric-tests.el (electric-quote-opening-single)
(electric-quote-closing-single, electric-quote-opening-double)
(electric-quote-closing-double)
(electric-quote-context-sensitive-backtick)
(electric-quote-context-sensitive-bob-single)
(electric-quote-context-sensitive-bob-double)
(electric-quote-context-sensitive-bol-single)
(electric-quote-context-sensitive-bol-double)
(electric-quote-context-sensitive-after-space-single)
(electric-quote-context-sensitive-after-space-double)
(electric-quote-context-sensitive-after-letter-single)
(electric-quote-context-sensitive-after-letter-double)
(electric-quote-context-sensitive-after-paren-single)
(electric-quote-context-sensitive-after-paren-double)
(electric-quote-markdown-in-text)
(electric-quote-markdown-in-code): New unit tests.
---
etc/NEWS | 16 ++++++
lisp/electric.el | 66 +++++++++++++++++--------
test/lisp/electric-tests.el | 116 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 179 insertions(+), 19 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 5e10ca9cb6..b0bb5e1465 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -129,6 +129,22 @@ given file is on a case-insensitive filesystem.
of curved quotes for 'electric-quote-mode', allowing user to choose
the types of quotes to be used.
+** The new user option 'electric-quote-context-sensitive' makes
+'electric-quote-mode' context sensitive. If it is non-nil, you can
+type an ASCII apostrophe to insert an opening or closing quote,
+depending on context. Emacs will replace the apostrophe by an opening
+quote character at the beginning of the buffer, the beginning of a
+line, after a whitespace character, and after an opening parenthesis;
+and it will replace the apostrophe by a closing quote character in all
+other cases.
+
+** The new variable 'electric-quote-code-faces' controls when to
+disable electric quoting in text modes. Major modes can add faces to
+this list; Emacs will temporarily disable 'electric-quote-mode'
+whenever point is before a character having such a face. This is
+intended for major modes that derive from 'text-mode' but allow inline
+code segments, such as 'markdown-mode'.
+
+++
** The new user variable 'dired-omit-case-fold' allows the user to
customize the case-sensitivity of dired-omit-mode. It defaults to
diff --git a/lisp/electric.el b/lisp/electric.el
index 4078ef8193..1564df5949 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -443,11 +443,24 @@ electric-quote-paragraph
:version "25.1"
:type 'boolean :safe 'booleanp :group 'electricity)
+(defcustom electric-quote-context-sensitive nil
+ "Non-nil means to replace \\=' with an electric quote depending on context.
+If `electric-quote-context-sensitive' is non-nil, Emacs replaces
+\\=' and \\='\\=' with an opening quote after a line break,
+whitespace, opening parenthesis, or quote and leaves \\=` alone."
+ :version "26.1"
+ :type 'boolean :safe #'booleanp :group 'electricity)
+
+(defvar electric-quote-code-faces ()
+ "List of faces to treat as inline code in `text-mode'.")
+
(defun electric-quote-post-self-insert-function ()
"Function that `electric-quote-mode' adds to `post-self-insert-hook'.
This requotes when a quoting key is typed."
(when (and electric-quote-mode
- (memq last-command-event '(?\' ?\`)))
+ (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)
@@ -462,30 +475,45 @@ electric-quote-post-self-insert-function
(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
- (if (eq last-command-event ?\`)
- (cond ((search-backward (string q< ?`) (- (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 "`" (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>)))))))))))
+ (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)
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el
index 78a3765061..6f63d30e75 100644
--- a/test/lisp/electric-tests.el
+++ b/test/lisp/electric-tests.el
@@ -593,5 +593,121 @@ electric-quote-string
:bindings '((electric-quote-string . t))
:test-in-comments nil :test-in-strings nil)
+(define-electric-pair-test electric-quote-opening-single
+ "" "`" :expected-string "‘" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-closing-single
+ "" "'" :expected-string "’" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-opening-double
+ "‘" "-`" :expected-string "“" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-closing-double
+ "’" "-'" :expected-string "”" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-backtick
+ "" "`" :expected-string "`" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-bob-single
+ "" "'" :expected-string "‘" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-bob-double
+ "‘" "-'" :expected-string "“" :expected-point 2
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-bol-single
+ "a\n" "--'" :expected-string "a\n‘" :expected-point 4
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-bol-double
+ "a\n‘" "---'" :expected-string "a\n“" :expected-point 4
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-space-single
+ " " "-'" :expected-string " ‘" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-space-double
+ " ‘" "--'" :expected-string " “" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-letter-single
+ "a" "-'" :expected-string "a’" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-letter-double
+ "a’" "--'" :expected-string "a”" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-paren-single
+ "(" "-'" :expected-string "(‘" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-context-sensitive-after-paren-double
+ "(‘" "--'" :expected-string "(“" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-context-sensitive . t))
+ :test-in-comments nil :test-in-strings nil)
+
+(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))
+ :test-in-comments nil :test-in-strings nil)
+
+(define-electric-pair-test electric-quote-markdown-in-code
+ #("`a`" 1 2 (face font-lock-constant-face)) "-'"
+ :expected-string "`'a`" :expected-point 3
+ :modes '(text-mode)
+ :fixture-fn #'electric-quote-local-mode
+ :bindings '((electric-quote-code-faces font-lock-constant-face))
+ :test-in-comments nil :test-in-strings nil)
+
(provide 'electric-tests)
;;; electric-tests.el ends here
--
2.13.2
next prev parent reply other threads:[~2017-06-28 21:59 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-10-16 17:19 bug#24709: 26.0.50; Support for Markdown in electric-quote-mode Philipp Stephani
2016-10-19 11:10 ` Alan Third
2017-06-28 21:59 ` Philipp Stephani [this message]
2017-07-02 15:49 ` Philipp Stephani
2017-07-02 16:20 ` Philipp Stephani
2017-07-09 19:57 ` Philipp Stephani
2017-07-14 12:36 ` Paul Eggert
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAArVCkQBkN=s8HNEDF=ptp-0EgeEMK4v0vzrumBZo08BNBmLuA@mail.gmail.com' \
--to=p.stephani2@gmail.com \
--cc=24709@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.