diff options
| author | Philipp Stephani | 2017-06-28 23:47:57 +0200 |
|---|---|---|
| committer | Philipp Stephani | 2017-07-02 17:48:23 +0200 |
| commit | 34d4720f833bb382b28d9faecf82d34db1eb4494 (patch) | |
| tree | f149e3b03da94c7db458610007e82b33ac735018 | |
| parent | d90b98a2a52abf67b84aa12df282b0defec8505b (diff) | |
| download | emacs-34d4720f833bb382b28d9faecf82d34db1eb4494.tar.gz emacs-34d4720f833bb382b28d9faecf82d34db1eb4494.zip | |
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.
| -rw-r--r-- | etc/NEWS | 16 | ||||
| -rw-r--r-- | lisp/electric.el | 66 | ||||
| -rw-r--r-- | test/lisp/electric-tests.el | 116 |
3 files changed, 179 insertions, 19 deletions
| @@ -129,6 +129,22 @@ given file is on a case-insensitive filesystem. | |||
| 129 | of curved quotes for 'electric-quote-mode', allowing user to choose | 129 | of curved quotes for 'electric-quote-mode', allowing user to choose |
| 130 | the types of quotes to be used. | 130 | the types of quotes to be used. |
| 131 | 131 | ||
| 132 | ** The new user option 'electric-quote-context-sensitive' makes | ||
| 133 | 'electric-quote-mode' context sensitive. If it is non-nil, you can | ||
| 134 | type an ASCII apostrophe to insert an opening or closing quote, | ||
| 135 | depending on context. Emacs will replace the apostrophe by an opening | ||
| 136 | quote character at the beginning of the buffer, the beginning of a | ||
| 137 | line, after a whitespace character, and after an opening parenthesis; | ||
| 138 | and it will replace the apostrophe by a closing quote character in all | ||
| 139 | other cases. | ||
| 140 | |||
| 141 | ** The new variable 'electric-quote-code-faces' controls when to | ||
| 142 | disable electric quoting in text modes. Major modes can add faces to | ||
| 143 | this list; Emacs will temporarily disable 'electric-quote-mode' | ||
| 144 | whenever point is before a character having such a face. This is | ||
| 145 | intended for major modes that derive from 'text-mode' but allow inline | ||
| 146 | code segments, such as 'markdown-mode'. | ||
| 147 | |||
| 132 | +++ | 148 | +++ |
| 133 | ** The new user variable 'dired-omit-case-fold' allows the user to | 149 | ** The new user variable 'dired-omit-case-fold' allows the user to |
| 134 | customize the case-sensitivity of dired-omit-mode. It defaults to | 150 | customize the case-sensitivity of dired-omit-mode. It defaults to |
diff --git a/lisp/electric.el b/lisp/electric.el index 4078ef8193e..1564df5949c 100644 --- a/lisp/electric.el +++ b/lisp/electric.el | |||
| @@ -443,11 +443,24 @@ quote, left double quote, and right double quote, respectively." | |||
| 443 | :version "25.1" | 443 | :version "25.1" |
| 444 | :type 'boolean :safe 'booleanp :group 'electricity) | 444 | :type 'boolean :safe 'booleanp :group 'electricity) |
| 445 | 445 | ||
| 446 | (defcustom electric-quote-context-sensitive nil | ||
| 447 | "Non-nil means to replace \\=' with an electric quote depending on context. | ||
| 448 | If `electric-quote-context-sensitive' is non-nil, Emacs replaces | ||
| 449 | \\=' and \\='\\=' with an opening quote after a line break, | ||
| 450 | whitespace, opening parenthesis, or quote and leaves \\=` alone." | ||
| 451 | :version "26.1" | ||
| 452 | :type 'boolean :safe #'booleanp :group 'electricity) | ||
| 453 | |||
| 454 | (defvar electric-quote-code-faces () | ||
| 455 | "List of faces to treat as inline code in `text-mode'.") | ||
| 456 | |||
| 446 | (defun electric-quote-post-self-insert-function () | 457 | (defun electric-quote-post-self-insert-function () |
| 447 | "Function that `electric-quote-mode' adds to `post-self-insert-hook'. | 458 | "Function that `electric-quote-mode' adds to `post-self-insert-hook'. |
| 448 | This requotes when a quoting key is typed." | 459 | This requotes when a quoting key is typed." |
| 449 | (when (and electric-quote-mode | 460 | (when (and electric-quote-mode |
| 450 | (memq last-command-event '(?\' ?\`))) | 461 | (or (eq last-command-event ?\') |
| 462 | (and (not electric-quote-context-sensitive) | ||
| 463 | (eq last-command-event ?\`)))) | ||
| 451 | (let ((start | 464 | (let ((start |
| 452 | (if (and comment-start comment-use-syntax) | 465 | (if (and comment-start comment-use-syntax) |
| 453 | (when (or electric-quote-comment electric-quote-string) | 466 | (when (or electric-quote-comment electric-quote-string) |
| @@ -462,30 +475,45 @@ This requotes when a quoting key is typed." | |||
| 462 | (syntax-ppss (1- (point))))))))) | 475 | (syntax-ppss (1- (point))))))))) |
| 463 | (and electric-quote-paragraph | 476 | (and electric-quote-paragraph |
| 464 | (derived-mode-p 'text-mode) | 477 | (derived-mode-p 'text-mode) |
| 478 | ;; FIXME: There should be a ‘cl-disjoint’ function. | ||
| 479 | (null (cl-intersection (face-at-point nil 'multiple) | ||
| 480 | electric-quote-code-faces | ||
| 481 | :test #'eq)) | ||
| 482 | ;; FIXME: Why is the next form there? It’s never | ||
| 483 | ;; nil. | ||
| 465 | (or (eq last-command-event ?\`) | 484 | (or (eq last-command-event ?\`) |
| 466 | (save-excursion (backward-paragraph) (point))))))) | 485 | (save-excursion (backward-paragraph) (point))))))) |
| 467 | (pcase electric-quote-chars | 486 | (pcase electric-quote-chars |
| 468 | (`(,q< ,q> ,q<< ,q>>) | 487 | (`(,q< ,q> ,q<< ,q>>) |
| 469 | (when start | 488 | (when start |
| 470 | (save-excursion | 489 | (save-excursion |
| 471 | (if (eq last-command-event ?\`) | 490 | (let ((backtick ?\`)) |
| 472 | (cond ((search-backward (string q< ?`) (- (point) 2) t) | 491 | (if (or (eq last-command-event ?\`) |
| 473 | (replace-match (string q<<)) | 492 | (and electric-quote-context-sensitive |
| 474 | (when (and electric-pair-mode | 493 | (save-excursion |
| 475 | (eq (cdr-safe | 494 | (backward-char) |
| 476 | (assq q< electric-pair-text-pairs)) | 495 | (or (bobp) (bolp) |
| 477 | (char-after))) | 496 | (memq (char-before) (list q< q<<)) |
| 478 | (delete-char 1)) | 497 | (memq (char-syntax (char-before)) |
| 479 | (setq last-command-event q<<)) | 498 | '(?\s ?\()))) |
| 480 | ((search-backward "`" (1- (point)) t) | 499 | (setq backtick ?\'))) |
| 481 | (replace-match (string q<)) | 500 | (cond ((search-backward (string q< backtick) (- (point) 2) t) |
| 482 | (setq last-command-event q<))) | 501 | (replace-match (string q<<)) |
| 483 | (cond ((search-backward (string q> ?') (- (point) 2) t) | 502 | (when (and electric-pair-mode |
| 484 | (replace-match (string q>>)) | 503 | (eq (cdr-safe |
| 485 | (setq last-command-event q>>)) | 504 | (assq q< electric-pair-text-pairs)) |
| 486 | ((search-backward "'" (1- (point)) t) | 505 | (char-after))) |
| 487 | (replace-match (string q>)) | 506 | (delete-char 1)) |
| 488 | (setq last-command-event q>))))))))))) | 507 | (setq last-command-event q<<)) |
| 508 | ((search-backward (string backtick) (1- (point)) t) | ||
| 509 | (replace-match (string q<)) | ||
| 510 | (setq last-command-event q<))) | ||
| 511 | (cond ((search-backward (string q> ?') (- (point) 2) t) | ||
| 512 | (replace-match (string q>>)) | ||
| 513 | (setq last-command-event q>>)) | ||
| 514 | ((search-backward "'" (1- (point)) t) | ||
| 515 | (replace-match (string q>)) | ||
| 516 | (setq last-command-event q>)))))))))))) | ||
| 489 | 517 | ||
| 490 | (put 'electric-quote-post-self-insert-function 'priority 10) | 518 | (put 'electric-quote-post-self-insert-function 'priority 10) |
| 491 | 519 | ||
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el index 78a37650619..6f63d30e755 100644 --- a/test/lisp/electric-tests.el +++ b/test/lisp/electric-tests.el | |||
| @@ -593,5 +593,121 @@ baz\"\"" | |||
| 593 | :bindings '((electric-quote-string . t)) | 593 | :bindings '((electric-quote-string . t)) |
| 594 | :test-in-comments nil :test-in-strings nil) | 594 | :test-in-comments nil :test-in-strings nil) |
| 595 | 595 | ||
| 596 | (define-electric-pair-test electric-quote-opening-single | ||
| 597 | "" "`" :expected-string "‘" :expected-point 2 | ||
| 598 | :modes '(text-mode) | ||
| 599 | :fixture-fn #'electric-quote-local-mode | ||
| 600 | :test-in-comments nil :test-in-strings nil) | ||
| 601 | |||
| 602 | (define-electric-pair-test electric-quote-closing-single | ||
| 603 | "" "'" :expected-string "’" :expected-point 2 | ||
| 604 | :modes '(text-mode) | ||
| 605 | :fixture-fn #'electric-quote-local-mode | ||
| 606 | :test-in-comments nil :test-in-strings nil) | ||
| 607 | |||
| 608 | (define-electric-pair-test electric-quote-opening-double | ||
| 609 | "‘" "-`" :expected-string "“" :expected-point 2 | ||
| 610 | :modes '(text-mode) | ||
| 611 | :fixture-fn #'electric-quote-local-mode | ||
| 612 | :test-in-comments nil :test-in-strings nil) | ||
| 613 | |||
| 614 | (define-electric-pair-test electric-quote-closing-double | ||
| 615 | "’" "-'" :expected-string "”" :expected-point 2 | ||
| 616 | :modes '(text-mode) | ||
| 617 | :fixture-fn #'electric-quote-local-mode | ||
| 618 | :test-in-comments nil :test-in-strings nil) | ||
| 619 | |||
| 620 | (define-electric-pair-test electric-quote-context-sensitive-backtick | ||
| 621 | "" "`" :expected-string "`" :expected-point 2 | ||
| 622 | :modes '(text-mode) | ||
| 623 | :fixture-fn #'electric-quote-local-mode | ||
| 624 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 625 | :test-in-comments nil :test-in-strings nil) | ||
| 626 | |||
| 627 | (define-electric-pair-test electric-quote-context-sensitive-bob-single | ||
| 628 | "" "'" :expected-string "‘" :expected-point 2 | ||
| 629 | :modes '(text-mode) | ||
| 630 | :fixture-fn #'electric-quote-local-mode | ||
| 631 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 632 | :test-in-comments nil :test-in-strings nil) | ||
| 633 | |||
| 634 | (define-electric-pair-test electric-quote-context-sensitive-bob-double | ||
| 635 | "‘" "-'" :expected-string "“" :expected-point 2 | ||
| 636 | :modes '(text-mode) | ||
| 637 | :fixture-fn #'electric-quote-local-mode | ||
| 638 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 639 | :test-in-comments nil :test-in-strings nil) | ||
| 640 | |||
| 641 | (define-electric-pair-test electric-quote-context-sensitive-bol-single | ||
| 642 | "a\n" "--'" :expected-string "a\n‘" :expected-point 4 | ||
| 643 | :modes '(text-mode) | ||
| 644 | :fixture-fn #'electric-quote-local-mode | ||
| 645 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 646 | :test-in-comments nil :test-in-strings nil) | ||
| 647 | |||
| 648 | (define-electric-pair-test electric-quote-context-sensitive-bol-double | ||
| 649 | "a\n‘" "---'" :expected-string "a\n“" :expected-point 4 | ||
| 650 | :modes '(text-mode) | ||
| 651 | :fixture-fn #'electric-quote-local-mode | ||
| 652 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 653 | :test-in-comments nil :test-in-strings nil) | ||
| 654 | |||
| 655 | (define-electric-pair-test electric-quote-context-sensitive-after-space-single | ||
| 656 | " " "-'" :expected-string " ‘" :expected-point 3 | ||
| 657 | :modes '(text-mode) | ||
| 658 | :fixture-fn #'electric-quote-local-mode | ||
| 659 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 660 | :test-in-comments nil :test-in-strings nil) | ||
| 661 | |||
| 662 | (define-electric-pair-test electric-quote-context-sensitive-after-space-double | ||
| 663 | " ‘" "--'" :expected-string " “" :expected-point 3 | ||
| 664 | :modes '(text-mode) | ||
| 665 | :fixture-fn #'electric-quote-local-mode | ||
| 666 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 667 | :test-in-comments nil :test-in-strings nil) | ||
| 668 | |||
| 669 | (define-electric-pair-test electric-quote-context-sensitive-after-letter-single | ||
| 670 | "a" "-'" :expected-string "a’" :expected-point 3 | ||
| 671 | :modes '(text-mode) | ||
| 672 | :fixture-fn #'electric-quote-local-mode | ||
| 673 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 674 | :test-in-comments nil :test-in-strings nil) | ||
| 675 | |||
| 676 | (define-electric-pair-test electric-quote-context-sensitive-after-letter-double | ||
| 677 | "a’" "--'" :expected-string "a”" :expected-point 3 | ||
| 678 | :modes '(text-mode) | ||
| 679 | :fixture-fn #'electric-quote-local-mode | ||
| 680 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 681 | :test-in-comments nil :test-in-strings nil) | ||
| 682 | |||
| 683 | (define-electric-pair-test electric-quote-context-sensitive-after-paren-single | ||
| 684 | "(" "-'" :expected-string "(‘" :expected-point 3 | ||
| 685 | :modes '(text-mode) | ||
| 686 | :fixture-fn #'electric-quote-local-mode | ||
| 687 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 688 | :test-in-comments nil :test-in-strings nil) | ||
| 689 | |||
| 690 | (define-electric-pair-test electric-quote-context-sensitive-after-paren-double | ||
| 691 | "(‘" "--'" :expected-string "(“" :expected-point 3 | ||
| 692 | :modes '(text-mode) | ||
| 693 | :fixture-fn #'electric-quote-local-mode | ||
| 694 | :bindings '((electric-quote-context-sensitive . t)) | ||
| 695 | :test-in-comments nil :test-in-strings nil) | ||
| 696 | |||
| 697 | (define-electric-pair-test electric-quote-markdown-in-text | ||
| 698 | "" "'" :expected-string "’" :expected-point 2 | ||
| 699 | :modes '(text-mode) | ||
| 700 | :fixture-fn #'electric-quote-local-mode | ||
| 701 | :bindings '((electric-quote-code-faces font-lock-constant-face)) | ||
| 702 | :test-in-comments nil :test-in-strings nil) | ||
| 703 | |||
| 704 | (define-electric-pair-test electric-quote-markdown-in-code | ||
| 705 | #("`a`" 1 2 (face font-lock-constant-face)) "-'" | ||
| 706 | :expected-string "`'a`" :expected-point 3 | ||
| 707 | :modes '(text-mode) | ||
| 708 | :fixture-fn #'electric-quote-local-mode | ||
| 709 | :bindings '((electric-quote-code-faces font-lock-constant-face)) | ||
| 710 | :test-in-comments nil :test-in-strings nil) | ||
| 711 | |||
| 596 | (provide 'electric-tests) | 712 | (provide 'electric-tests) |
| 597 | ;;; electric-tests.el ends here | 713 | ;;; electric-tests.el ends here |