diff options
| author | Stefan Monnier | 2013-12-27 07:53:57 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2013-12-27 07:53:57 -0500 |
| commit | 7514d3f8063a829d681faa76c408961ade61733f (patch) | |
| tree | 1253904636eab36fb1e7ded85e59ce9268395ee9 | |
| parent | 765fe182c4b39093b4763c065b98cf7019cc6979 (diff) | |
| download | emacs-7514d3f8063a829d681faa76c408961ade61733f.tar.gz emacs-7514d3f8063a829d681faa76c408961ade61733f.zip | |
* lisp/electric.el: Move all electric-pair-* to elec-pair.el.
* lisp/elec-pair.el: New file, split from electric.el.
| -rw-r--r-- | lisp/ChangeLog | 52 | ||||
| -rw-r--r-- | lisp/elec-pair.el | 566 | ||||
| -rw-r--r-- | lisp/electric.el | 527 |
3 files changed, 594 insertions, 551 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 379e49dc11d..e9685a23299 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,8 @@ | |||
| 1 | 2013-12-27 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 2 | |||
| 3 | * electric.el: Move all electric-pair-* to elec-pair.el. | ||
| 4 | * elec-pair.el: New file, split from electric.el. | ||
| 5 | |||
| 1 | 2013-12-27 Lars Ingebrigtsen <larsi@gnus.org> | 6 | 2013-12-27 Lars Ingebrigtsen <larsi@gnus.org> |
| 2 | 7 | ||
| 3 | * net/shr.el (shr-find-fill-point): Don't try to fill if the | 8 | * net/shr.el (shr-find-fill-point): Don't try to fill if the |
| @@ -8,26 +13,25 @@ | |||
| 8 | (shr-find-fill-point): Off by one error in comparison with the | 13 | (shr-find-fill-point): Off by one error in comparison with the |
| 9 | indentation. | 14 | indentation. |
| 10 | 15 | ||
| 11 | 2013-12-26 João Távora <joaotavora@gmail.com> | 16 | 2013-12-26 João Távora <joaotavora@gmail.com> |
| 12 | 17 | ||
| 13 | * electric.el (electric-pair-mode): More flexible engine for skip- | 18 | * electric.el (electric-pair-mode): More flexible engine for skip- |
| 14 | and inhibit predicates, new options for pairing-related | 19 | and inhibit predicates, new options for pairing-related functionality. |
| 15 | functionality. | ||
| 16 | (electric-pair-preserve-balance): Pair/skip parentheses and quotes | 20 | (electric-pair-preserve-balance): Pair/skip parentheses and quotes |
| 17 | if that keeps or improves their balance in buffers. | 21 | if that keeps or improves their balance in buffers. |
| 18 | (electric-pair-delete-adjacent-pairs): Delete the pair when | 22 | (electric-pair-delete-adjacent-pairs): Delete the pair when |
| 19 | backspacing over adjacent matched delimiters. | 23 | backspacing over adjacent matched delimiters. |
| 20 | (electric-pair-open-extra-newline): Open extra newline when | 24 | (electric-pair-open-extra-newline): Open extra newline when |
| 21 | inserting newlines between adjacent matched delimiters. | 25 | inserting newlines between adjacent matched delimiters. |
| 22 | (electric--sort-post-self-insertion-hook): Sort | 26 | (electric--sort-post-self-insertion-hook): |
| 23 | post-self-insert-hook according to priority values when | 27 | Sort post-self-insert-hook according to priority values when |
| 24 | minor-modes are activated. | 28 | minor-modes are activated. |
| 25 | * simple.el (newline-and-indent): Call newline with interactive | 29 | * simple.el (newline-and-indent): Call newline with interactive |
| 26 | set to t. | 30 | set to t. |
| 27 | (blink-paren-post-self-insert-function): Set priority to 100. | 31 | (blink-paren-post-self-insert-function): Set priority to 100. |
| 28 | * emacs-lisp/lisp-mode.el (lisp-mode-variables): Use | 32 | * emacs-lisp/lisp-mode.el (lisp-mode-variables): |
| 29 | electric-pair-text-pairs to pair backtick-and-quote in strings and | 33 | Use electric-pair-text-pairs to pair backtick-and-quote in strings and |
| 30 | comments. Locally set electric-pair-skip-whitespace to 'chomp and | 34 | comments. Locally set electric-pair-skip-whitespace to 'chomp and |
| 31 | electric-pair-open-newline-between-pairs to nil. | 35 | electric-pair-open-newline-between-pairs to nil. |
| 32 | 36 | ||
| 33 | 2013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org> | 37 | 2013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org> |
| @@ -64,8 +68,8 @@ | |||
| 64 | (Bug#16191). | 68 | (Bug#16191). |
| 65 | (python-nav-backward-sexp, python-nav-forward-sexp-safe) | 69 | (python-nav-backward-sexp, python-nav-forward-sexp-safe) |
| 66 | (python-nav-backward-sexp-safe): New functions. | 70 | (python-nav-backward-sexp-safe): New functions. |
| 67 | (python-shell-buffer-substring): Use | 71 | (python-shell-buffer-substring): |
| 68 | `python-nav-forward-sexp-safe'. | 72 | Use `python-nav-forward-sexp-safe'. |
| 69 | 73 | ||
| 70 | 2013-12-25 Lars Ingebrigtsen <larsi@gnus.org> | 74 | 2013-12-25 Lars Ingebrigtsen <larsi@gnus.org> |
| 71 | 75 | ||
| @@ -73,8 +77,7 @@ | |||
| 73 | quotation mark. | 77 | quotation mark. |
| 74 | (shr-char-kinsoku-bol-p): The quotation mark isn't a kinsoky BOL char. | 78 | (shr-char-kinsoku-bol-p): The quotation mark isn't a kinsoky BOL char. |
| 75 | (shr-find-fill-point): Remove the special checks for the quotation | 79 | (shr-find-fill-point): Remove the special checks for the quotation |
| 76 | mark, since `shr-char-kinsoku-bol-p' should now return the right | 80 | mark, since `shr-char-kinsoku-bol-p' should now return the right thing. |
| 77 | thing. | ||
| 78 | 81 | ||
| 79 | 2013-12-25 Kenjiro NAKAYAMA <nakayamakenjiro@gmail.com> | 82 | 2013-12-25 Kenjiro NAKAYAMA <nakayamakenjiro@gmail.com> |
| 80 | 83 | ||
| @@ -84,8 +87,8 @@ | |||
| 84 | 87 | ||
| 85 | 2013-12-24 Fabián Ezequiel Gallina <fgallina@gnu.org> | 88 | 2013-12-24 Fabián Ezequiel Gallina <fgallina@gnu.org> |
| 86 | 89 | ||
| 87 | * progmodes/python.el (python-nav-beginning-of-statement): Speed | 90 | * progmodes/python.el (python-nav-beginning-of-statement): |
| 88 | up (Bug#15295). | 91 | Speed up (Bug#15295). |
| 89 | 92 | ||
| 90 | 2013-12-24 Lars Ingebrigtsen <larsi@gnus.org> | 93 | 2013-12-24 Lars Ingebrigtsen <larsi@gnus.org> |
| 91 | 94 | ||
| @@ -115,8 +118,7 @@ | |||
| 115 | (icomplete-show-matches-on-no-input): New customizable variable. | 118 | (icomplete-show-matches-on-no-input): New customizable variable. |
| 116 | (icomplete-minibuffer-setup): Call `icomplete-exhibit' on setup if | 119 | (icomplete-minibuffer-setup): Call `icomplete-exhibit' on setup if |
| 117 | we have something to show. | 120 | we have something to show. |
| 118 | (icomplete-exhibit): Compute completions even if we have no user | 121 | (icomplete-exhibit): Compute completions even if we have no user input. |
| 119 | input. | ||
| 120 | 122 | ||
| 121 | 2013-12-23 Daniel Colascione <dancol@dancol.org> | 123 | 2013-12-23 Daniel Colascione <dancol@dancol.org> |
| 122 | 124 | ||
| @@ -146,7 +148,7 @@ | |||
| 146 | 148 | ||
| 147 | * net/eww.el (eww-tag-select): Add text-property to jump to next | 149 | * net/eww.el (eww-tag-select): Add text-property to jump to next |
| 148 | select field. | 150 | select field. |
| 149 | (eww) : Add non-supported ftp error. | 151 | (eww) : Add non-supported ftp error. |
| 150 | 152 | ||
| 151 | 2013-12-22 Dmitry Gutov <dgutov@yandex.ru> | 153 | 2013-12-22 Dmitry Gutov <dgutov@yandex.ru> |
| 152 | 154 | ||
| @@ -156,8 +158,8 @@ | |||
| 156 | 2013-12-22 Chong Yidong <cyd@gnu.org> | 158 | 2013-12-22 Chong Yidong <cyd@gnu.org> |
| 157 | 159 | ||
| 158 | * faces.el (face-spec-recalc): If the theme specs are not | 160 | * faces.el (face-spec-recalc): If the theme specs are not |
| 159 | applicable to a frame, fall back on the defface spec. This | 161 | applicable to a frame, fall back on the defface spec. |
| 160 | prevents themes from obliterating faces on low-color terminals. | 162 | This prevents themes from obliterating faces on low-color terminals. |
| 161 | 163 | ||
| 162 | 2013-12-22 Dmitry Gutov <dgutov@yandex.ru> | 164 | 2013-12-22 Dmitry Gutov <dgutov@yandex.ru> |
| 163 | 165 | ||
| @@ -774,8 +776,10 @@ | |||
| 774 | * emacs-lisp/package.el (package--prepare-dependencies): New function. | 776 | * emacs-lisp/package.el (package--prepare-dependencies): New function. |
| 775 | (package-buffer-info): Use it (bug#15108). | 777 | (package-buffer-info): Use it (bug#15108). |
| 776 | 778 | ||
| 779 | 2013-12-14 Stefan Monnier <monnier@iro.umontreal.ca> | ||
| 780 | |||
| 777 | * icomplete.el (icomplete-completions): Make sure the prefix is already | 781 | * icomplete.el (icomplete-completions): Make sure the prefix is already |
| 778 | displayed elsewhere before hiding it. | 782 | displayed elsewhere before hiding it (bug#16219). |
| 779 | 783 | ||
| 780 | 2013-12-14 Dmitry Gutov <dgutov@yandex.ru> | 784 | 2013-12-14 Dmitry Gutov <dgutov@yandex.ru> |
| 781 | 785 | ||
| @@ -976,7 +980,7 @@ | |||
| 976 | * bindings.el: Map kp keys to non-kp keys systematically | 980 | * bindings.el: Map kp keys to non-kp keys systematically |
| 977 | with basic modifiers control, meta and shift. (Bug#14397) | 981 | with basic modifiers control, meta and shift. (Bug#14397) |
| 978 | 982 | ||
| 979 | 2013-12-11 Kenjiro NAKAYAMA <nakayamakenjiro@gmail.com> (tiny change) | 983 | 2013-12-11 Kenjiro NAKAYAMA <nakayamakenjiro@gmail.com> |
| 980 | 984 | ||
| 981 | * net/eww.el (eww-mode-map): Instead of "Quit" show "Exit" and | 985 | * net/eww.el (eww-mode-map): Instead of "Quit" show "Exit" and |
| 982 | "Close browser" menu items. Fix wrong function of "List | 986 | "Close browser" menu items. Fix wrong function of "List |
| @@ -1593,10 +1597,10 @@ | |||
| 1593 | * leim/quail/uni-input.el (ucs-input-activate): Add autoload cookie. | 1597 | * leim/quail/uni-input.el (ucs-input-activate): Add autoload cookie. |
| 1594 | (generated-autoload-load-name): Set file-local value. | 1598 | (generated-autoload-load-name): Set file-local value. |
| 1595 | 1599 | ||
| 1596 | 2013-11-26 Kenjiro NAKAYAMA <knakayam@redhat.com> (tiny change) | 1600 | 2013-11-26 Kenjiro NAKAYAMA <knakayam@redhat.com> |
| 1597 | 1601 | ||
| 1598 | * net/eww.el (eww-bookmark-browse): Use 'eww-browse-url'. | 1602 | * net/eww.el (eww-bookmark-browse): Use 'eww-browse-url'. |
| 1599 | (eww-add-bookmark): Ask confirmation when add to bookmarks | 1603 | (eww-add-bookmark): Ask confirmation when add to bookmarks. |
| 1600 | (eww-quit): Ask confirmation before quitting eww. | 1604 | (eww-quit): Ask confirmation before quitting eww. |
| 1601 | 1605 | ||
| 1602 | 2013-11-26 Eli Zaretskii <eliz@gnu.org> | 1606 | 2013-11-26 Eli Zaretskii <eliz@gnu.org> |
| @@ -1777,7 +1781,7 @@ | |||
| 1777 | Add `octave-source-file'. | 1781 | Add `octave-source-file'. |
| 1778 | (octave-source-file): New function. (Bug#15935) | 1782 | (octave-source-file): New function. (Bug#15935) |
| 1779 | 1783 | ||
| 1780 | 2013-11-21 Kenjiro Nakayama <nakayamakenjiro@gmail.com> (tiny change) | 1784 | 2013-11-21 Kenjiro Nakayama <nakayamakenjiro@gmail.com> |
| 1781 | 1785 | ||
| 1782 | * net/eww.el (eww-local-regex): New variable. | 1786 | * net/eww.el (eww-local-regex): New variable. |
| 1783 | (eww): Use it to detect localhost and similar. | 1787 | (eww): Use it to detect localhost and similar. |
diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el new file mode 100644 index 00000000000..130ce9f4ca2 --- /dev/null +++ b/lisp/elec-pair.el | |||
| @@ -0,0 +1,566 @@ | |||
| 1 | ;;; elec-pair.el --- Automatic parenthesis pairing -*- lexical-binding:t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2013 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: João Távora <joaotavora@gmail.com> | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 12 | ;; (at your option) any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 21 | |||
| 22 | ;;; Commentary: | ||
| 23 | |||
| 24 | ;;; Code: | ||
| 25 | |||
| 26 | (require 'electric) | ||
| 27 | |||
| 28 | ;;; Electric pairing. | ||
| 29 | |||
| 30 | (defcustom electric-pair-pairs | ||
| 31 | '((?\" . ?\")) | ||
| 32 | "Alist of pairs that should be used regardless of major mode. | ||
| 33 | |||
| 34 | Pairs of delimiters in this list are a fallback in case they have | ||
| 35 | no syntax relevant to `electric-pair-mode' in the mode's syntax | ||
| 36 | table. | ||
| 37 | |||
| 38 | See also the variable `electric-pair-text-pairs'." | ||
| 39 | :version "24.1" | ||
| 40 | :group 'electricity | ||
| 41 | :type '(repeat (cons character character))) | ||
| 42 | |||
| 43 | ;;;###autoload | ||
| 44 | (defcustom electric-pair-text-pairs | ||
| 45 | '((?\" . ?\" )) | ||
| 46 | "Alist of pairs that should always be used in comments and strings. | ||
| 47 | |||
| 48 | Pairs of delimiters in this list are a fallback in case they have | ||
| 49 | no syntax relevant to `electric-pair-mode' in the syntax table | ||
| 50 | defined in `electric-pair-text-syntax-table'" | ||
| 51 | :version "24.4" | ||
| 52 | :group 'electricity | ||
| 53 | :type '(repeat (cons character character))) | ||
| 54 | |||
| 55 | (defcustom electric-pair-skip-self #'electric-pair-default-skip-self | ||
| 56 | "If non-nil, skip char instead of inserting a second closing paren. | ||
| 57 | |||
| 58 | When inserting a closing paren character right before the same character, | ||
| 59 | just skip that character instead, so that hitting ( followed by ) results | ||
| 60 | in \"()\" rather than \"())\". | ||
| 61 | |||
| 62 | This can be convenient for people who find it easier to hit ) than C-f. | ||
| 63 | |||
| 64 | Can also be a function of one argument (the closer char just | ||
| 65 | inserted), in which case that function's return value is | ||
| 66 | considered instead." | ||
| 67 | :version "24.1" | ||
| 68 | :group 'electricity | ||
| 69 | :type '(choice | ||
| 70 | (const :tag "Never skip" nil) | ||
| 71 | (const :tag "Help balance" electric-pair-default-skip-self) | ||
| 72 | (const :tag "Always skip" t) | ||
| 73 | function)) | ||
| 74 | |||
| 75 | (defcustom electric-pair-inhibit-predicate | ||
| 76 | #'electric-pair-default-inhibit | ||
| 77 | "Predicate to prevent insertion of a matching pair. | ||
| 78 | |||
| 79 | The function is called with a single char (the opening char just inserted). | ||
| 80 | If it returns non-nil, then `electric-pair-mode' will not insert a matching | ||
| 81 | closer." | ||
| 82 | :version "24.4" | ||
| 83 | :group 'electricity | ||
| 84 | :type '(choice | ||
| 85 | (const :tag "Conservative" electric-pair-conservative-inhibit) | ||
| 86 | (const :tag "Help balance" electric-pair-default-inhibit) | ||
| 87 | (const :tag "Always pair" ignore) | ||
| 88 | function)) | ||
| 89 | |||
| 90 | (defcustom electric-pair-preserve-balance t | ||
| 91 | "Non-nil if default pairing and skipping should help balance parentheses. | ||
| 92 | |||
| 93 | The default values of `electric-pair-inhibit-predicate' and | ||
| 94 | `electric-pair-skip-self' check this variable before delegating to other | ||
| 95 | predicates reponsible for making decisions on whether to pair/skip some | ||
| 96 | characters based on the actual state of the buffer's parenthesis and | ||
| 97 | quotes." | ||
| 98 | :version "24.4" | ||
| 99 | :group 'electricity | ||
| 100 | :type 'boolean) | ||
| 101 | |||
| 102 | (defcustom electric-pair-delete-adjacent-pairs t | ||
| 103 | "If non-nil, backspacing an open paren also deletes adjacent closer. | ||
| 104 | |||
| 105 | Can also be a function of no arguments, in which case that function's | ||
| 106 | return value is considered instead." | ||
| 107 | :version "24.4" | ||
| 108 | :group 'electricity | ||
| 109 | :type '(choice | ||
| 110 | (const :tag "Yes" t) | ||
| 111 | (const :tag "No" nil) | ||
| 112 | function)) | ||
| 113 | |||
| 114 | (defcustom electric-pair-open-newline-between-pairs t | ||
| 115 | "If non-nil, a newline between adjacent parentheses opens an extra one. | ||
| 116 | |||
| 117 | Can also be a function of no arguments, in which case that function's | ||
| 118 | return value is considered instead." | ||
| 119 | :version "24.4" | ||
| 120 | :group 'electricity | ||
| 121 | :type '(choice | ||
| 122 | (const :tag "Yes" t) | ||
| 123 | (const :tag "No" nil) | ||
| 124 | function)) | ||
| 125 | |||
| 126 | (defcustom electric-pair-skip-whitespace t | ||
| 127 | "If non-nil skip whitespace when skipping over closing parens. | ||
| 128 | |||
| 129 | The specific kind of whitespace skipped is given by the variable | ||
| 130 | `electric-pair-skip-whitespace-chars'. | ||
| 131 | |||
| 132 | The symbol `chomp' specifies that the skipped-over whitespace | ||
| 133 | should be deleted. | ||
| 134 | |||
| 135 | Can also be a function of no arguments, in which case that function's | ||
| 136 | return value is considered instead." | ||
| 137 | :version "24.4" | ||
| 138 | :group 'electricity | ||
| 139 | :type '(choice | ||
| 140 | (const :tag "Yes, jump over whitespace" t) | ||
| 141 | (const :tag "Yes, and delete whitespace" 'chomp) | ||
| 142 | (const :tag "No, no whitespace skipping" nil) | ||
| 143 | function)) | ||
| 144 | |||
| 145 | (defcustom electric-pair-skip-whitespace-chars (list ?\t ?\s ?\n) | ||
| 146 | "Whitespace characters considered by `electric-pair-skip-whitespace'." | ||
| 147 | :version "24.4" | ||
| 148 | :group 'electricity | ||
| 149 | :type '(choice (set (const :tag "Space" ?\s) | ||
| 150 | (const :tag "Tab" ?\t) | ||
| 151 | (const :tag "Newline" ?\n)) | ||
| 152 | (list character))) | ||
| 153 | |||
| 154 | (defun electric-pair--skip-whitespace () | ||
| 155 | "Skip whitespace forward, not crossing comment or string boundaries." | ||
| 156 | (let ((saved (point)) | ||
| 157 | (string-or-comment (nth 8 (syntax-ppss)))) | ||
| 158 | (skip-chars-forward (apply #'string electric-pair-skip-whitespace-chars)) | ||
| 159 | (unless (eq string-or-comment (nth 8 (syntax-ppss))) | ||
| 160 | (goto-char saved)))) | ||
| 161 | |||
| 162 | (defvar electric-pair-text-syntax-table prog-mode-syntax-table | ||
| 163 | "Syntax table used when pairing inside comments and strings. | ||
| 164 | |||
| 165 | `electric-pair-mode' considers this syntax table only when point in inside | ||
| 166 | quotes or comments. If lookup fails here, `electric-pair-text-pairs' will | ||
| 167 | be considered.") | ||
| 168 | |||
| 169 | (defun electric-pair-backward-delete-char (n &optional killflag untabify) | ||
| 170 | "Delete characters backward, and maybe also two adjacent paired delimiters. | ||
| 171 | |||
| 172 | Remaining behaviour is given by `backward-delete-char' or, if UNTABIFY is | ||
| 173 | non-nil, `backward-delete-char-untabify'." | ||
| 174 | (interactive "*p\nP") | ||
| 175 | (let* ((prev (char-before)) | ||
| 176 | (next (char-after)) | ||
| 177 | (syntax-info (electric-pair-syntax-info prev)) | ||
| 178 | (syntax (car syntax-info)) | ||
| 179 | (pair (cadr syntax-info))) | ||
| 180 | (when (and (if (functionp electric-pair-delete-adjacent-pairs) | ||
| 181 | (funcall electric-pair-delete-adjacent-pairs) | ||
| 182 | electric-pair-delete-adjacent-pairs) | ||
| 183 | next | ||
| 184 | (memq syntax '(?\( ?\" ?\$)) | ||
| 185 | (eq pair next)) | ||
| 186 | (delete-char 1 killflag)) | ||
| 187 | (if untabify | ||
| 188 | (backward-delete-char-untabify n killflag) | ||
| 189 | (backward-delete-char n killflag)))) | ||
| 190 | |||
| 191 | (defun electric-pair-backward-delete-char-untabify (n &optional killflag) | ||
| 192 | "Delete characters backward, and maybe also two adjacent paired delimiters. | ||
| 193 | |||
| 194 | Remaining behaviour is given by `backward-delete-char-untabify'." | ||
| 195 | (interactive "*p\nP") | ||
| 196 | (electric-pair-backward-delete-char n killflag t)) | ||
| 197 | |||
| 198 | (defun electric-pair-conservative-inhibit (char) | ||
| 199 | (or | ||
| 200 | ;; I find it more often preferable not to pair when the | ||
| 201 | ;; same char is next. | ||
| 202 | (eq char (char-after)) | ||
| 203 | ;; Don't pair up when we insert the second of "" or of ((. | ||
| 204 | (and (eq char (char-before)) | ||
| 205 | (eq char (char-before (1- (point))))) | ||
| 206 | ;; I also find it often preferable not to pair next to a word. | ||
| 207 | (eq (char-syntax (following-char)) ?w))) | ||
| 208 | |||
| 209 | (defun electric-pair-syntax-info (command-event) | ||
| 210 | "Calculate a list (SYNTAX PAIR UNCONDITIONAL STRING-OR-COMMENT-START). | ||
| 211 | |||
| 212 | SYNTAX is COMMAND-EVENT's syntax character. PAIR is | ||
| 213 | COMMAND-EVENT's pair. UNCONDITIONAL indicates the variables | ||
| 214 | `electric-pair-pairs' or `electric-pair-text-pairs' were used to | ||
| 215 | lookup syntax. STRING-OR-COMMENT-START indicates that point is | ||
| 216 | inside a comment of string." | ||
| 217 | (let* ((pre-string-or-comment (nth 8 (save-excursion | ||
| 218 | (syntax-ppss (1- (point)))))) | ||
| 219 | (post-string-or-comment (nth 8 (syntax-ppss (point)))) | ||
| 220 | (string-or-comment (and post-string-or-comment | ||
| 221 | pre-string-or-comment)) | ||
| 222 | (table (if string-or-comment | ||
| 223 | electric-pair-text-syntax-table | ||
| 224 | (syntax-table))) | ||
| 225 | (table-syntax-and-pair (with-syntax-table table | ||
| 226 | (list (char-syntax command-event) | ||
| 227 | (or (matching-paren command-event) | ||
| 228 | command-event)))) | ||
| 229 | (fallback (if string-or-comment | ||
| 230 | (append electric-pair-text-pairs | ||
| 231 | electric-pair-pairs) | ||
| 232 | electric-pair-pairs)) | ||
| 233 | (direct (assq command-event fallback)) | ||
| 234 | (reverse (rassq command-event fallback))) | ||
| 235 | (cond | ||
| 236 | ((memq (car table-syntax-and-pair) | ||
| 237 | '(?\" ?\( ?\) ?\$)) | ||
| 238 | (append table-syntax-and-pair (list nil string-or-comment))) | ||
| 239 | (direct (if (eq (car direct) (cdr direct)) | ||
| 240 | (list ?\" command-event t string-or-comment) | ||
| 241 | (list ?\( (cdr direct) t string-or-comment))) | ||
| 242 | (reverse (list ?\) (car reverse) t string-or-comment))))) | ||
| 243 | |||
| 244 | (defun electric-pair--insert (char) | ||
| 245 | (let ((last-command-event char) | ||
| 246 | (blink-matching-paren nil) | ||
| 247 | (electric-pair-mode nil)) | ||
| 248 | (self-insert-command 1))) | ||
| 249 | |||
| 250 | (defun electric-pair--syntax-ppss (&optional pos where) | ||
| 251 | "Like `syntax-ppss', but sometimes fallback to `parse-partial-sexp'. | ||
| 252 | |||
| 253 | WHERE is list defaulting to '(string comment) and indicates | ||
| 254 | when to fallback to `parse-partial-sexp'." | ||
| 255 | (let* ((pos (or pos (point))) | ||
| 256 | (where (or where '(string comment))) | ||
| 257 | (quick-ppss (syntax-ppss)) | ||
| 258 | (quick-ppss-at-pos (syntax-ppss pos))) | ||
| 259 | (if (or (and (nth 3 quick-ppss) (memq 'string where)) | ||
| 260 | (and (nth 4 quick-ppss) (memq 'comment where))) | ||
| 261 | (with-syntax-table electric-pair-text-syntax-table | ||
| 262 | (parse-partial-sexp (1+ (nth 8 quick-ppss)) pos)) | ||
| 263 | ;; HACK! cc-mode apparently has some `syntax-ppss' bugs | ||
| 264 | (if (memq major-mode '(c-mode c++ mode)) | ||
| 265 | (parse-partial-sexp (point-min) pos) | ||
| 266 | quick-ppss-at-pos)))) | ||
| 267 | |||
| 268 | ;; Balancing means controlling pairing and skipping of parentheses so | ||
| 269 | ;; that, if possible, the buffer ends up at least as balanced as | ||
| 270 | ;; before, if not more. The algorithm is slightly complex because some | ||
| 271 | ;; situations like "()))" need pairing to occur at the end but not at | ||
| 272 | ;; the beginning. Balancing should also happen independently for | ||
| 273 | ;; different types of parentheses, so that having your {}'s unbalanced | ||
| 274 | ;; doesn't keep `electric-pair-mode' from balancing your ()'s and your | ||
| 275 | ;; []'s. | ||
| 276 | (defun electric-pair--balance-info (direction string-or-comment) | ||
| 277 | "Examine lists forward or backward according to DIRECTIONS's sign. | ||
| 278 | |||
| 279 | STRING-OR-COMMENT is info suitable for running `parse-partial-sexp'. | ||
| 280 | |||
| 281 | Return a cons of two descritions (MATCHED-P . PAIR) for the | ||
| 282 | innermost and outermost lists that enclose point. The outermost | ||
| 283 | list enclosing point is either the first top-level or first | ||
| 284 | mismatched list found by uplisting. | ||
| 285 | |||
| 286 | If the outermost list is matched, don't rely on its PAIR. If | ||
| 287 | point is not enclosed by any lists, return ((T) (T))." | ||
| 288 | (let* (innermost | ||
| 289 | outermost | ||
| 290 | (table (if string-or-comment | ||
| 291 | electric-pair-text-syntax-table | ||
| 292 | (syntax-table))) | ||
| 293 | (at-top-level-or-equivalent-fn | ||
| 294 | ;; called when `scan-sexps' ran perfectly, when when it | ||
| 295 | ;; found a parenthesis pointing in the direction of | ||
| 296 | ;; travel. Also when travel started inside a comment and | ||
| 297 | ;; exited it | ||
| 298 | #'(lambda () | ||
| 299 | (setq outermost (list t)) | ||
| 300 | (unless innermost | ||
| 301 | (setq innermost (list t))))) | ||
| 302 | (ended-prematurely-fn | ||
| 303 | ;; called when `scan-sexps' crashed against a parenthesis | ||
| 304 | ;; pointing opposite the direction of travel. After | ||
| 305 | ;; traversing that character, the idea is to travel one sexp | ||
| 306 | ;; in the opposite direction looking for a matching | ||
| 307 | ;; delimiter. | ||
| 308 | #'(lambda () | ||
| 309 | (let* ((pos (point)) | ||
| 310 | (matched | ||
| 311 | (save-excursion | ||
| 312 | (cond ((< direction 0) | ||
| 313 | (condition-case nil | ||
| 314 | (eq (char-after pos) | ||
| 315 | (with-syntax-table table | ||
| 316 | (matching-paren | ||
| 317 | (char-before | ||
| 318 | (scan-sexps (point) 1))))) | ||
| 319 | (scan-error nil))) | ||
| 320 | (t | ||
| 321 | ;; In this case, no need to use | ||
| 322 | ;; `scan-sexps', we can use some | ||
| 323 | ;; `electric-pair--syntax-ppss' in this | ||
| 324 | ;; case (which uses the quicker | ||
| 325 | ;; `syntax-ppss' in some cases) | ||
| 326 | (let* ((ppss (electric-pair--syntax-ppss | ||
| 327 | (1- (point)))) | ||
| 328 | (start (car (last (nth 9 ppss)))) | ||
| 329 | (opener (char-after start))) | ||
| 330 | (and start | ||
| 331 | (eq (char-before pos) | ||
| 332 | (or (with-syntax-table table | ||
| 333 | (matching-paren opener)) | ||
| 334 | opener)))))))) | ||
| 335 | (actual-pair (if (> direction 0) | ||
| 336 | (char-before (point)) | ||
| 337 | (char-after (point))))) | ||
| 338 | (unless innermost | ||
| 339 | (setq innermost (cons matched actual-pair))) | ||
| 340 | (unless matched | ||
| 341 | (setq outermost (cons matched actual-pair))))))) | ||
| 342 | (save-excursion | ||
| 343 | (while (not outermost) | ||
| 344 | (condition-case err | ||
| 345 | (with-syntax-table table | ||
| 346 | (scan-sexps (point) (if (> direction 0) | ||
| 347 | (point-max) | ||
| 348 | (- (point-max)))) | ||
| 349 | (funcall at-top-level-or-equivalent-fn)) | ||
| 350 | (scan-error | ||
| 351 | (cond ((or | ||
| 352 | ;; some error happened and it is not of the "ended | ||
| 353 | ;; prematurely" kind"... | ||
| 354 | (not (string-match "ends prematurely" (nth 1 err))) | ||
| 355 | ;; ... or we were in a comment and just came out of | ||
| 356 | ;; it. | ||
| 357 | (and string-or-comment | ||
| 358 | (not (nth 8 (syntax-ppss))))) | ||
| 359 | (funcall at-top-level-or-equivalent-fn)) | ||
| 360 | (t | ||
| 361 | ;; exit the sexp | ||
| 362 | (goto-char (nth 3 err)) | ||
| 363 | (funcall ended-prematurely-fn))))))) | ||
| 364 | (cons innermost outermost))) | ||
| 365 | |||
| 366 | (defun electric-pair--looking-at-unterminated-string-p (char) | ||
| 367 | "Say if following string starts with CHAR and is unterminated." | ||
| 368 | ;; FIXME: ugly/naive | ||
| 369 | (save-excursion | ||
| 370 | (skip-chars-forward (format "^%c" char)) | ||
| 371 | (while (not (zerop (% (save-excursion (skip-syntax-backward "\\")) 2))) | ||
| 372 | (unless (eobp) | ||
| 373 | (forward-char 1) | ||
| 374 | (skip-chars-forward (format "^%c" char)))) | ||
| 375 | (and (not (eobp)) | ||
| 376 | (condition-case nil | ||
| 377 | (progn (forward-sexp) nil) | ||
| 378 | (scan-error t))))) | ||
| 379 | |||
| 380 | (defun electric-pair--inside-string-p (char) | ||
| 381 | "Say if point is inside a string started by CHAR. | ||
| 382 | |||
| 383 | A comments text is parsed with `electric-pair-text-syntax-table'. | ||
| 384 | Also consider strings within comments, but not strings within | ||
| 385 | strings." | ||
| 386 | ;; FIXME: could also consider strings within strings by examining | ||
| 387 | ;; delimiters. | ||
| 388 | (let* ((ppss (electric-pair--syntax-ppss (point) '(comment)))) | ||
| 389 | (memq (nth 3 ppss) (list t char)))) | ||
| 390 | |||
| 391 | (defun electric-pair-inhibit-if-helps-balance (char) | ||
| 392 | "Return non-nil if auto-pairing of CHAR would hurt parentheses' balance. | ||
| 393 | |||
| 394 | Works by first removing the character from the buffer, then doing | ||
| 395 | some list calculations, finally restoring the situation as if nothing | ||
| 396 | happened." | ||
| 397 | (pcase (electric-pair-syntax-info char) | ||
| 398 | (`(,syntax ,pair ,_ ,s-or-c) | ||
| 399 | (unwind-protect | ||
| 400 | (progn | ||
| 401 | (delete-char -1) | ||
| 402 | (cond ((eq ?\( syntax) | ||
| 403 | (let* ((pair-data | ||
| 404 | (electric-pair--balance-info 1 s-or-c)) | ||
| 405 | (outermost (cdr pair-data))) | ||
| 406 | (cond ((car outermost) | ||
| 407 | nil) | ||
| 408 | (t | ||
| 409 | (eq (cdr outermost) pair))))) | ||
| 410 | ((eq syntax ?\") | ||
| 411 | (electric-pair--looking-at-unterminated-string-p char)))) | ||
| 412 | (insert-char char))))) | ||
| 413 | |||
| 414 | (defun electric-pair-skip-if-helps-balance (char) | ||
| 415 | "Return non-nil if skipping CHAR would benefit parentheses' balance. | ||
| 416 | |||
| 417 | Works by first removing the character from the buffer, then doing | ||
| 418 | some list calculations, finally restoring the situation as if nothing | ||
| 419 | happened." | ||
| 420 | (pcase (electric-pair-syntax-info char) | ||
| 421 | (`(,syntax ,pair ,_ ,s-or-c) | ||
| 422 | (unwind-protect | ||
| 423 | (progn | ||
| 424 | (delete-char -1) | ||
| 425 | (cond ((eq syntax ?\)) | ||
| 426 | (let* ((pair-data | ||
| 427 | (electric-pair--balance-info | ||
| 428 | -1 s-or-c)) | ||
| 429 | (innermost (car pair-data)) | ||
| 430 | (outermost (cdr pair-data))) | ||
| 431 | (and | ||
| 432 | (cond ((car outermost) | ||
| 433 | (car innermost)) | ||
| 434 | ((car innermost) | ||
| 435 | (not (eq (cdr outermost) pair))))))) | ||
| 436 | ((eq syntax ?\") | ||
| 437 | (electric-pair--inside-string-p char)))) | ||
| 438 | (insert-char char))))) | ||
| 439 | |||
| 440 | (defun electric-pair-default-skip-self (char) | ||
| 441 | (if electric-pair-preserve-balance | ||
| 442 | (electric-pair-skip-if-helps-balance char) | ||
| 443 | t)) | ||
| 444 | |||
| 445 | (defun electric-pair-default-inhibit (char) | ||
| 446 | (if electric-pair-preserve-balance | ||
| 447 | (electric-pair-inhibit-if-helps-balance char) | ||
| 448 | (electric-pair-conservative-inhibit char))) | ||
| 449 | |||
| 450 | (defun electric-pair-post-self-insert-function () | ||
| 451 | (let* ((pos (and electric-pair-mode (electric--after-char-pos))) | ||
| 452 | (skip-whitespace-info)) | ||
| 453 | (pcase (electric-pair-syntax-info last-command-event) | ||
| 454 | (`(,syntax ,pair ,unconditional ,_) | ||
| 455 | (cond | ||
| 456 | ((null pos) nil) | ||
| 457 | ;; Wrap a pair around the active region. | ||
| 458 | ;; | ||
| 459 | ((and (memq syntax '(?\( ?\) ?\" ?\$)) (use-region-p)) | ||
| 460 | ;; FIXME: To do this right, we'd need a post-self-insert-function | ||
| 461 | ;; so we could add-function around it and insert the closer after | ||
| 462 | ;; all the rest of the hook has run. | ||
| 463 | (if (or (eq syntax ?\") | ||
| 464 | (and (eq syntax ?\)) | ||
| 465 | (>= (point) (mark))) | ||
| 466 | (and (not (eq syntax ?\))) | ||
| 467 | (>= (mark) (point)))) | ||
| 468 | (save-excursion | ||
| 469 | (goto-char (mark)) | ||
| 470 | (electric-pair--insert pair)) | ||
| 471 | (delete-region pos (1- pos)) | ||
| 472 | (electric-pair--insert pair) | ||
| 473 | (goto-char (mark)) | ||
| 474 | (electric-pair--insert last-command-event))) | ||
| 475 | ;; Backslash-escaped: no pairing, no skipping. | ||
| 476 | ((save-excursion | ||
| 477 | (goto-char (1- pos)) | ||
| 478 | (not (zerop (% (skip-syntax-backward "\\") 2)))) | ||
| 479 | nil) | ||
| 480 | ;; Skip self. | ||
| 481 | ((and (memq syntax '(?\) ?\" ?\$)) | ||
| 482 | (and (or unconditional | ||
| 483 | (if (functionp electric-pair-skip-self) | ||
| 484 | (funcall electric-pair-skip-self last-command-event) | ||
| 485 | electric-pair-skip-self)) | ||
| 486 | (save-excursion | ||
| 487 | (when (setq skip-whitespace-info | ||
| 488 | (if (functionp electric-pair-skip-whitespace) | ||
| 489 | (funcall electric-pair-skip-whitespace) | ||
| 490 | electric-pair-skip-whitespace)) | ||
| 491 | (electric-pair--skip-whitespace)) | ||
| 492 | (eq (char-after) last-command-event)))) | ||
| 493 | ;; This is too late: rather than insert&delete we'd want to only | ||
| 494 | ;; skip (or insert in overwrite mode). The difference is in what | ||
| 495 | ;; goes in the undo-log and in the intermediate state which might | ||
| 496 | ;; be visible to other post-self-insert-hook. We'll just have to | ||
| 497 | ;; live with it for now. | ||
| 498 | (when skip-whitespace-info | ||
| 499 | (electric-pair--skip-whitespace)) | ||
| 500 | (delete-region (1- pos) (if (eq skip-whitespace-info 'chomp) | ||
| 501 | (point) | ||
| 502 | pos)) | ||
| 503 | (forward-char)) | ||
| 504 | ;; Insert matching pair. | ||
| 505 | ((and (memq syntax `(?\( ?\" ?\$)) | ||
| 506 | (not overwrite-mode) | ||
| 507 | (or unconditional | ||
| 508 | (not (funcall electric-pair-inhibit-predicate | ||
| 509 | last-command-event)))) | ||
| 510 | (save-excursion (electric-pair--insert pair))))) | ||
| 511 | (t | ||
| 512 | (when (and (if (functionp electric-pair-open-newline-between-pairs) | ||
| 513 | (funcall electric-pair-open-newline-between-pairs) | ||
| 514 | electric-pair-open-newline-between-pairs) | ||
| 515 | (eq last-command-event ?\n) | ||
| 516 | (not (eobp)) | ||
| 517 | (eq (save-excursion | ||
| 518 | (skip-chars-backward "\t\s") | ||
| 519 | (char-before (1- (point)))) | ||
| 520 | (matching-paren (char-after)))) | ||
| 521 | (save-excursion (newline 1 t))))))) | ||
| 522 | |||
| 523 | (put 'electric-pair-post-self-insert-function 'priority 20) | ||
| 524 | |||
| 525 | (defun electric-pair-will-use-region () | ||
| 526 | (and (use-region-p) | ||
| 527 | (memq (car (electric-pair-syntax-info last-command-event)) | ||
| 528 | '(?\( ?\) ?\" ?\$)))) | ||
| 529 | |||
| 530 | (defvar electric-pair-mode-map | ||
| 531 | (let ((map (make-sparse-keymap))) | ||
| 532 | (define-key map [remap backward-delete-char-untabify] | ||
| 533 | 'electric-pair-backward-delete-char-untabify) | ||
| 534 | (define-key map [remap backward-delete-char] | ||
| 535 | 'electric-pair-backward-delete-char) | ||
| 536 | (define-key map [remap delete-backward-char] | ||
| 537 | 'electric-pair-backward-delete-char) | ||
| 538 | map) | ||
| 539 | "Keymap used by `electric-pair-mode'.") | ||
| 540 | |||
| 541 | ;;;###autoload | ||
| 542 | (define-minor-mode electric-pair-mode | ||
| 543 | "Toggle automatic parens pairing (Electric Pair mode). | ||
| 544 | With a prefix argument ARG, enable Electric Pair mode if ARG is | ||
| 545 | positive, and disable it otherwise. If called from Lisp, enable | ||
| 546 | the mode if ARG is omitted or nil. | ||
| 547 | |||
| 548 | Electric Pair mode is a global minor mode. When enabled, typing | ||
| 549 | an open parenthesis automatically inserts the corresponding | ||
| 550 | closing parenthesis. \(Likewise for brackets, etc.)." | ||
| 551 | :global t :group 'electricity | ||
| 552 | (if electric-pair-mode | ||
| 553 | (progn | ||
| 554 | (add-hook 'post-self-insert-hook | ||
| 555 | #'electric-pair-post-self-insert-function) | ||
| 556 | (electric--sort-post-self-insertion-hook) | ||
| 557 | (add-hook 'self-insert-uses-region-functions | ||
| 558 | #'electric-pair-will-use-region)) | ||
| 559 | (remove-hook 'post-self-insert-hook | ||
| 560 | #'electric-pair-post-self-insert-function) | ||
| 561 | (remove-hook 'self-insert-uses-region-functions | ||
| 562 | #'electric-pair-will-use-region))) | ||
| 563 | |||
| 564 | (provide 'elec-pair) | ||
| 565 | |||
| 566 | ;;; elec-pair.el ends here | ||
diff --git a/lisp/electric.el b/lisp/electric.el index fc5e63f90bb..9e9a25213e8 100644 --- a/lisp/electric.el +++ b/lisp/electric.el | |||
| @@ -325,533 +325,6 @@ insert a character from `electric-indent-chars'." | |||
| 325 | (setq-default electric-indent-mode nil) ; But keep it globally disabled. | 325 | (setq-default electric-indent-mode nil) ; But keep it globally disabled. |
| 326 | ))) | 326 | ))) |
| 327 | 327 | ||
| 328 | ;;; Electric pairing. | ||
| 329 | |||
| 330 | (defcustom electric-pair-pairs | ||
| 331 | '((?\" . ?\")) | ||
| 332 | "Alist of pairs that should be used regardless of major mode. | ||
| 333 | |||
| 334 | Pairs of delimiters in this list are a fallback in case they have | ||
| 335 | no syntax relevant to `electric-pair-mode' in the mode's syntax | ||
| 336 | table. | ||
| 337 | |||
| 338 | See also the variable `electric-pair-text-pairs'." | ||
| 339 | :version "24.1" | ||
| 340 | :type '(repeat (cons character character))) | ||
| 341 | |||
| 342 | (defcustom electric-pair-text-pairs | ||
| 343 | '((?\" . ?\" )) | ||
| 344 | "Alist of pairs that should always be used in comments and strings. | ||
| 345 | |||
| 346 | Pairs of delimiters in this list are a fallback in case they have | ||
| 347 | no syntax relevant to `electric-pair-mode' in the syntax table | ||
| 348 | defined in `electric-pair-text-syntax-table'" | ||
| 349 | :version "24.4" | ||
| 350 | :type '(repeat (cons character character))) | ||
| 351 | |||
| 352 | (defcustom electric-pair-skip-self #'electric-pair-default-skip-self | ||
| 353 | "If non-nil, skip char instead of inserting a second closing paren. | ||
| 354 | |||
| 355 | When inserting a closing paren character right before the same character, | ||
| 356 | just skip that character instead, so that hitting ( followed by ) results | ||
| 357 | in \"()\" rather than \"())\". | ||
| 358 | |||
| 359 | This can be convenient for people who find it easier to hit ) than C-f. | ||
| 360 | |||
| 361 | Can also be a function of one argument (the closer char just | ||
| 362 | inserted), in which case that function's return value is | ||
| 363 | considered instead." | ||
| 364 | :version "24.1" | ||
| 365 | :type '(choice | ||
| 366 | (const :tag "Never skip" nil) | ||
| 367 | (const :tag "Help balance" electric-pair-default-skip-self) | ||
| 368 | (const :tag "Always skip" t) | ||
| 369 | function)) | ||
| 370 | |||
| 371 | (defcustom electric-pair-inhibit-predicate | ||
| 372 | #'electric-pair-default-inhibit | ||
| 373 | "Predicate to prevent insertion of a matching pair. | ||
| 374 | |||
| 375 | The function is called with a single char (the opening char just inserted). | ||
| 376 | If it returns non-nil, then `electric-pair-mode' will not insert a matching | ||
| 377 | closer." | ||
| 378 | :version "24.4" | ||
| 379 | :type '(choice | ||
| 380 | (const :tag "Conservative" electric-pair-conservative-inhibit) | ||
| 381 | (const :tag "Help balance" electric-pair-default-inhibit) | ||
| 382 | (const :tag "Always pair" ignore) | ||
| 383 | function)) | ||
| 384 | |||
| 385 | (defcustom electric-pair-preserve-balance t | ||
| 386 | "Non-nil if default pairing and skipping should help balance parentheses. | ||
| 387 | |||
| 388 | The default values of `electric-pair-inhibit-predicate' and | ||
| 389 | `electric-pair-skip-self' check this variable before delegating to other | ||
| 390 | predicates reponsible for making decisions on whether to pair/skip some | ||
| 391 | characters based on the actual state of the buffer's parenthesis and | ||
| 392 | quotes." | ||
| 393 | :version "24.4" | ||
| 394 | :type 'boolean) | ||
| 395 | |||
| 396 | (defcustom electric-pair-delete-adjacent-pairs t | ||
| 397 | "If non-nil, backspacing an open paren also deletes adjacent closer. | ||
| 398 | |||
| 399 | Can also be a function of no arguments, in which case that function's | ||
| 400 | return value is considered instead." | ||
| 401 | :version "24.4" | ||
| 402 | :type '(choice | ||
| 403 | (const :tag "Yes" t) | ||
| 404 | (const :tag "No" nil) | ||
| 405 | function)) | ||
| 406 | |||
| 407 | (defcustom electric-pair-open-newline-between-pairs t | ||
| 408 | "If non-nil, a newline between adjacent parentheses opens an extra one. | ||
| 409 | |||
| 410 | Can also be a function of no arguments, in which case that function's | ||
| 411 | return value is considered instead." | ||
| 412 | :version "24.4" | ||
| 413 | :type '(choice | ||
| 414 | (const :tag "Yes" t) | ||
| 415 | (const :tag "No" nil) | ||
| 416 | function)) | ||
| 417 | |||
| 418 | (defcustom electric-pair-skip-whitespace t | ||
| 419 | "If non-nil skip whitespace when skipping over closing parens. | ||
| 420 | |||
| 421 | The specific kind of whitespace skipped is given by the variable | ||
| 422 | `electric-pair-skip-whitespace-chars'. | ||
| 423 | |||
| 424 | The symbol `chomp' specifies that the skipped-over whitespace | ||
| 425 | should be deleted. | ||
| 426 | |||
| 427 | Can also be a function of no arguments, in which case that function's | ||
| 428 | return value is considered instead." | ||
| 429 | :version "24.4" | ||
| 430 | :type '(choice | ||
| 431 | (const :tag "Yes, jump over whitespace" t) | ||
| 432 | (const :tag "Yes, and delete whitespace" 'chomp) | ||
| 433 | (const :tag "No, no whitespace skipping" nil) | ||
| 434 | function)) | ||
| 435 | |||
| 436 | (defcustom electric-pair-skip-whitespace-chars (list ?\t ?\s ?\n) | ||
| 437 | "Whitespace characters considered by `electric-pair-skip-whitespace'." | ||
| 438 | :version "24.4" | ||
| 439 | :type '(choice (set (const :tag "Space" ?\s) | ||
| 440 | (const :tag "Tab" ?\t) | ||
| 441 | (const :tag "Newline" ?\n)) | ||
| 442 | (list character))) | ||
| 443 | |||
| 444 | (defun electric-pair--skip-whitespace () | ||
| 445 | "Skip whitespace forward, not crossing comment or string boundaries." | ||
| 446 | (let ((saved (point)) | ||
| 447 | (string-or-comment (nth 8 (syntax-ppss)))) | ||
| 448 | (skip-chars-forward (apply #'string electric-pair-skip-whitespace-chars)) | ||
| 449 | (unless (eq string-or-comment (nth 8 (syntax-ppss))) | ||
| 450 | (goto-char saved)))) | ||
| 451 | |||
| 452 | (defvar electric-pair-text-syntax-table prog-mode-syntax-table | ||
| 453 | "Syntax table used when pairing inside comments and strings. | ||
| 454 | |||
| 455 | `electric-pair-mode' considers this syntax table only when point in inside | ||
| 456 | quotes or comments. If lookup fails here, `electric-pair-text-pairs' will | ||
| 457 | be considered.") | ||
| 458 | |||
| 459 | (defun electric-pair-backward-delete-char (n &optional killflag untabify) | ||
| 460 | "Delete characters backward, and maybe also two adjacent paired delimiters. | ||
| 461 | |||
| 462 | Remaining behaviour is given by `backward-delete-char' or, if UNTABIFY is | ||
| 463 | non-nil, `backward-delete-char-untabify'." | ||
| 464 | (interactive "*p\nP") | ||
| 465 | (let* ((prev (char-before)) | ||
| 466 | (next (char-after)) | ||
| 467 | (syntax-info (electric-pair-syntax-info prev)) | ||
| 468 | (syntax (car syntax-info)) | ||
| 469 | (pair (cadr syntax-info))) | ||
| 470 | (when (and (if (functionp electric-pair-delete-adjacent-pairs) | ||
| 471 | (funcall electric-pair-delete-adjacent-pairs) | ||
| 472 | electric-pair-delete-adjacent-pairs) | ||
| 473 | next | ||
| 474 | (memq syntax '(?\( ?\" ?\$)) | ||
| 475 | (eq pair next)) | ||
| 476 | (delete-char 1 killflag)) | ||
| 477 | (if untabify | ||
| 478 | (backward-delete-char-untabify n killflag) | ||
| 479 | (backward-delete-char n killflag)))) | ||
| 480 | |||
| 481 | (defun electric-pair-backward-delete-char-untabify (n &optional killflag) | ||
| 482 | "Delete characters backward, and maybe also two adjacent paired delimiters. | ||
| 483 | |||
| 484 | Remaining behaviour is given by `backward-delete-char-untabify'." | ||
| 485 | (interactive "*p\nP") | ||
| 486 | (electric-pair-backward-delete-char n killflag t)) | ||
| 487 | |||
| 488 | (defun electric-pair-conservative-inhibit (char) | ||
| 489 | (or | ||
| 490 | ;; I find it more often preferable not to pair when the | ||
| 491 | ;; same char is next. | ||
| 492 | (eq char (char-after)) | ||
| 493 | ;; Don't pair up when we insert the second of "" or of ((. | ||
| 494 | (and (eq char (char-before)) | ||
| 495 | (eq char (char-before (1- (point))))) | ||
| 496 | ;; I also find it often preferable not to pair next to a word. | ||
| 497 | (eq (char-syntax (following-char)) ?w))) | ||
| 498 | |||
| 499 | (defun electric-pair-syntax-info (command-event) | ||
| 500 | "Calculate a list (SYNTAX PAIR UNCONDITIONAL STRING-OR-COMMENT-START). | ||
| 501 | |||
| 502 | SYNTAX is COMMAND-EVENT's syntax character. PAIR is | ||
| 503 | COMMAND-EVENT's pair. UNCONDITIONAL indicates the variables | ||
| 504 | `electric-pair-pairs' or `electric-pair-text-pairs' were used to | ||
| 505 | lookup syntax. STRING-OR-COMMENT-START indicates that point is | ||
| 506 | inside a comment of string." | ||
| 507 | (let* ((pre-string-or-comment (nth 8 (save-excursion | ||
| 508 | (syntax-ppss (1- (point)))))) | ||
| 509 | (post-string-or-comment (nth 8 (syntax-ppss (point)))) | ||
| 510 | (string-or-comment (and post-string-or-comment | ||
| 511 | pre-string-or-comment)) | ||
| 512 | (table (if string-or-comment | ||
| 513 | electric-pair-text-syntax-table | ||
| 514 | (syntax-table))) | ||
| 515 | (table-syntax-and-pair (with-syntax-table table | ||
| 516 | (list (char-syntax command-event) | ||
| 517 | (or (matching-paren command-event) | ||
| 518 | command-event)))) | ||
| 519 | (fallback (if string-or-comment | ||
| 520 | (append electric-pair-text-pairs | ||
| 521 | electric-pair-pairs) | ||
| 522 | electric-pair-pairs)) | ||
| 523 | (direct (assq command-event fallback)) | ||
| 524 | (reverse (rassq command-event fallback))) | ||
| 525 | (cond | ||
| 526 | ((memq (car table-syntax-and-pair) | ||
| 527 | '(?\" ?\( ?\) ?\$)) | ||
| 528 | (append table-syntax-and-pair (list nil string-or-comment))) | ||
| 529 | (direct (if (eq (car direct) (cdr direct)) | ||
| 530 | (list ?\" command-event t string-or-comment) | ||
| 531 | (list ?\( (cdr direct) t string-or-comment))) | ||
| 532 | (reverse (list ?\) (car reverse) t string-or-comment))))) | ||
| 533 | |||
| 534 | (defun electric-pair--insert (char) | ||
| 535 | (let ((last-command-event char) | ||
| 536 | (blink-matching-paren nil) | ||
| 537 | (electric-pair-mode nil)) | ||
| 538 | (self-insert-command 1))) | ||
| 539 | |||
| 540 | (defun electric-pair--syntax-ppss (&optional pos where) | ||
| 541 | "Like `syntax-ppss', but sometimes fallback to `parse-partial-sexp'. | ||
| 542 | |||
| 543 | WHERE is list defaulting to '(string comment) and indicates | ||
| 544 | when to fallback to `parse-partial-sexp'." | ||
| 545 | (let* ((pos (or pos (point))) | ||
| 546 | (where (or where '(string comment))) | ||
| 547 | (quick-ppss (syntax-ppss)) | ||
| 548 | (quick-ppss-at-pos (syntax-ppss pos))) | ||
| 549 | (if (or (and (nth 3 quick-ppss) (memq 'string where)) | ||
| 550 | (and (nth 4 quick-ppss) (memq 'comment where))) | ||
| 551 | (with-syntax-table electric-pair-text-syntax-table | ||
| 552 | (parse-partial-sexp (1+ (nth 8 quick-ppss)) pos)) | ||
| 553 | ;; HACK! cc-mode apparently has some `syntax-ppss' bugs | ||
| 554 | (if (memq major-mode '(c-mode c++ mode)) | ||
| 555 | (parse-partial-sexp (point-min) pos) | ||
| 556 | quick-ppss-at-pos)))) | ||
| 557 | |||
| 558 | ;; Balancing means controlling pairing and skipping of parentheses so | ||
| 559 | ;; that, if possible, the buffer ends up at least as balanced as | ||
| 560 | ;; before, if not more. The algorithm is slightly complex because some | ||
| 561 | ;; situations like "()))" need pairing to occur at the end but not at | ||
| 562 | ;; the beginning. Balancing should also happen independently for | ||
| 563 | ;; different types of parentheses, so that having your {}'s unbalanced | ||
| 564 | ;; doesn't keep `electric-pair-mode' from balancing your ()'s and your | ||
| 565 | ;; []'s. | ||
| 566 | (defun electric-pair--balance-info (direction string-or-comment) | ||
| 567 | "Examine lists forward or backward according to DIRECTIONS's sign. | ||
| 568 | |||
| 569 | STRING-OR-COMMENT is info suitable for running `parse-partial-sexp'. | ||
| 570 | |||
| 571 | Return a cons of two descritions (MATCHED-P . PAIR) for the | ||
| 572 | innermost and outermost lists that enclose point. The outermost | ||
| 573 | list enclosing point is either the first top-level or first | ||
| 574 | mismatched list found by uplisting. | ||
| 575 | |||
| 576 | If the outermost list is matched, don't rely on its PAIR. If | ||
| 577 | point is not enclosed by any lists, return ((T) (T))." | ||
| 578 | (let* (innermost | ||
| 579 | outermost | ||
| 580 | (table (if string-or-comment | ||
| 581 | electric-pair-text-syntax-table | ||
| 582 | (syntax-table))) | ||
| 583 | (at-top-level-or-equivalent-fn | ||
| 584 | ;; called when `scan-sexps' ran perfectly, when when it | ||
| 585 | ;; found a parenthesis pointing in the direction of | ||
| 586 | ;; travel. Also when travel started inside a comment and | ||
| 587 | ;; exited it | ||
| 588 | #'(lambda () | ||
| 589 | (setq outermost (list t)) | ||
| 590 | (unless innermost | ||
| 591 | (setq innermost (list t))))) | ||
| 592 | (ended-prematurely-fn | ||
| 593 | ;; called when `scan-sexps' crashed against a parenthesis | ||
| 594 | ;; pointing opposite the direction of travel. After | ||
| 595 | ;; traversing that character, the idea is to travel one sexp | ||
| 596 | ;; in the opposite direction looking for a matching | ||
| 597 | ;; delimiter. | ||
| 598 | #'(lambda () | ||
| 599 | (let* ((pos (point)) | ||
| 600 | (matched | ||
| 601 | (save-excursion | ||
| 602 | (cond ((< direction 0) | ||
| 603 | (condition-case nil | ||
| 604 | (eq (char-after pos) | ||
| 605 | (with-syntax-table table | ||
| 606 | (matching-paren | ||
| 607 | (char-before | ||
| 608 | (scan-sexps (point) 1))))) | ||
| 609 | (scan-error nil))) | ||
| 610 | (t | ||
| 611 | ;; In this case, no need to use | ||
| 612 | ;; `scan-sexps', we can use some | ||
| 613 | ;; `electric-pair--syntax-ppss' in this | ||
| 614 | ;; case (which uses the quicker | ||
| 615 | ;; `syntax-ppss' in some cases) | ||
| 616 | (let* ((ppss (electric-pair--syntax-ppss | ||
| 617 | (1- (point)))) | ||
| 618 | (start (car (last (nth 9 ppss)))) | ||
| 619 | (opener (char-after start))) | ||
| 620 | (and start | ||
| 621 | (eq (char-before pos) | ||
| 622 | (or (with-syntax-table table | ||
| 623 | (matching-paren opener)) | ||
| 624 | opener)))))))) | ||
| 625 | (actual-pair (if (> direction 0) | ||
| 626 | (char-before (point)) | ||
| 627 | (char-after (point))))) | ||
| 628 | (unless innermost | ||
| 629 | (setq innermost (cons matched actual-pair))) | ||
| 630 | (unless matched | ||
| 631 | (setq outermost (cons matched actual-pair))))))) | ||
| 632 | (save-excursion | ||
| 633 | (while (not outermost) | ||
| 634 | (condition-case err | ||
| 635 | (with-syntax-table table | ||
| 636 | (scan-sexps (point) (if (> direction 0) | ||
| 637 | (point-max) | ||
| 638 | (- (point-max)))) | ||
| 639 | (funcall at-top-level-or-equivalent-fn)) | ||
| 640 | (scan-error | ||
| 641 | (cond ((or | ||
| 642 | ;; some error happened and it is not of the "ended | ||
| 643 | ;; prematurely" kind"... | ||
| 644 | (not (string-match "ends prematurely" (nth 1 err))) | ||
| 645 | ;; ... or we were in a comment and just came out of | ||
| 646 | ;; it. | ||
| 647 | (and string-or-comment | ||
| 648 | (not (nth 8 (syntax-ppss))))) | ||
| 649 | (funcall at-top-level-or-equivalent-fn)) | ||
| 650 | (t | ||
| 651 | ;; exit the sexp | ||
| 652 | (goto-char (nth 3 err)) | ||
| 653 | (funcall ended-prematurely-fn))))))) | ||
| 654 | (cons innermost outermost))) | ||
| 655 | |||
| 656 | (defun electric-pair--looking-at-unterminated-string-p (char) | ||
| 657 | "Say if following string starts with CHAR and is unterminated." | ||
| 658 | ;; FIXME: ugly/naive | ||
| 659 | (save-excursion | ||
| 660 | (skip-chars-forward (format "^%c" char)) | ||
| 661 | (while (not (zerop (% (save-excursion (skip-syntax-backward "\\")) 2))) | ||
| 662 | (unless (eobp) | ||
| 663 | (forward-char 1) | ||
| 664 | (skip-chars-forward (format "^%c" char)))) | ||
| 665 | (and (not (eobp)) | ||
| 666 | (condition-case err | ||
| 667 | (progn (forward-sexp) nil) | ||
| 668 | (scan-error t))))) | ||
| 669 | |||
| 670 | (defun electric-pair--inside-string-p (char) | ||
| 671 | "Say if point is inside a string started by CHAR. | ||
| 672 | |||
| 673 | A comments text is parsed with `electric-pair-text-syntax-table'. | ||
| 674 | Also consider strings within comments, but not strings within | ||
| 675 | strings." | ||
| 676 | ;; FIXME: could also consider strings within strings by examining | ||
| 677 | ;; delimiters. | ||
| 678 | (let* ((ppss (electric-pair--syntax-ppss (point) '(comment)))) | ||
| 679 | (memq (nth 3 ppss) (list t char)))) | ||
| 680 | |||
| 681 | (defun electric-pair-inhibit-if-helps-balance (char) | ||
| 682 | "Return non-nil if auto-pairing of CHAR would hurt parentheses' balance. | ||
| 683 | |||
| 684 | Works by first removing the character from the buffer, then doing | ||
| 685 | some list calculations, finally restoring the situation as if nothing | ||
| 686 | happened." | ||
| 687 | (pcase (electric-pair-syntax-info char) | ||
| 688 | (`(,syntax ,pair ,_ ,s-or-c) | ||
| 689 | (unwind-protect | ||
| 690 | (progn | ||
| 691 | (delete-char -1) | ||
| 692 | (cond ((eq ?\( syntax) | ||
| 693 | (let* ((pair-data | ||
| 694 | (electric-pair--balance-info 1 s-or-c)) | ||
| 695 | (innermost (car pair-data)) | ||
| 696 | (outermost (cdr pair-data))) | ||
| 697 | (cond ((car outermost) | ||
| 698 | nil) | ||
| 699 | (t | ||
| 700 | (eq (cdr outermost) pair))))) | ||
| 701 | ((eq syntax ?\") | ||
| 702 | (electric-pair--looking-at-unterminated-string-p char)))) | ||
| 703 | (insert-char char))))) | ||
| 704 | |||
| 705 | (defun electric-pair-skip-if-helps-balance (char) | ||
| 706 | "Return non-nil if skipping CHAR would benefit parentheses' balance. | ||
| 707 | |||
| 708 | Works by first removing the character from the buffer, then doing | ||
| 709 | some list calculations, finally restoring the situation as if nothing | ||
| 710 | happened." | ||
| 711 | (pcase (electric-pair-syntax-info char) | ||
| 712 | (`(,syntax ,pair ,_ ,s-or-c) | ||
| 713 | (unwind-protect | ||
| 714 | (progn | ||
| 715 | (delete-char -1) | ||
| 716 | (cond ((eq syntax ?\)) | ||
| 717 | (let* ((pair-data | ||
| 718 | (electric-pair--balance-info | ||
| 719 | -1 s-or-c)) | ||
| 720 | (innermost (car pair-data)) | ||
| 721 | (outermost (cdr pair-data))) | ||
| 722 | (and | ||
| 723 | (cond ((car outermost) | ||
| 724 | (car innermost)) | ||
| 725 | ((car innermost) | ||
| 726 | (not (eq (cdr outermost) pair))))))) | ||
| 727 | ((eq syntax ?\") | ||
| 728 | (electric-pair--inside-string-p char)))) | ||
| 729 | (insert-char char))))) | ||
| 730 | |||
| 731 | (defun electric-pair-default-skip-self (char) | ||
| 732 | (if electric-pair-preserve-balance | ||
| 733 | (electric-pair-skip-if-helps-balance char) | ||
| 734 | t)) | ||
| 735 | |||
| 736 | (defun electric-pair-default-inhibit (char) | ||
| 737 | (if electric-pair-preserve-balance | ||
| 738 | (electric-pair-inhibit-if-helps-balance char) | ||
| 739 | (electric-pair-conservative-inhibit char))) | ||
| 740 | |||
| 741 | (defun electric-pair-post-self-insert-function () | ||
| 742 | (let* ((pos (and electric-pair-mode (electric--after-char-pos))) | ||
| 743 | (skip-whitespace-info)) | ||
| 744 | (pcase (electric-pair-syntax-info last-command-event) | ||
| 745 | (`(,syntax ,pair ,unconditional ,_) | ||
| 746 | (cond | ||
| 747 | ((null pos) nil) | ||
| 748 | ;; Wrap a pair around the active region. | ||
| 749 | ;; | ||
| 750 | ((and (memq syntax '(?\( ?\) ?\" ?\$)) (use-region-p)) | ||
| 751 | ;; FIXME: To do this right, we'd need a post-self-insert-function | ||
| 752 | ;; so we could add-function around it and insert the closer after | ||
| 753 | ;; all the rest of the hook has run. | ||
| 754 | (if (or (eq syntax ?\") | ||
| 755 | (and (eq syntax ?\)) | ||
| 756 | (>= (point) (mark))) | ||
| 757 | (and (not (eq syntax ?\))) | ||
| 758 | (>= (mark) (point)))) | ||
| 759 | (save-excursion | ||
| 760 | (goto-char (mark)) | ||
| 761 | (electric-pair--insert pair)) | ||
| 762 | (delete-region pos (1- pos)) | ||
| 763 | (electric-pair--insert pair) | ||
| 764 | (goto-char (mark)) | ||
| 765 | (electric-pair--insert last-command-event))) | ||
| 766 | ;; Backslash-escaped: no pairing, no skipping. | ||
| 767 | ((save-excursion | ||
| 768 | (goto-char (1- pos)) | ||
| 769 | (not (zerop (% (skip-syntax-backward "\\") 2)))) | ||
| 770 | nil) | ||
| 771 | ;; Skip self. | ||
| 772 | ((and (memq syntax '(?\) ?\" ?\$)) | ||
| 773 | (and (or unconditional | ||
| 774 | (if (functionp electric-pair-skip-self) | ||
| 775 | (funcall electric-pair-skip-self last-command-event) | ||
| 776 | electric-pair-skip-self)) | ||
| 777 | (save-excursion | ||
| 778 | (when (setq skip-whitespace-info | ||
| 779 | (if (functionp electric-pair-skip-whitespace) | ||
| 780 | (funcall electric-pair-skip-whitespace) | ||
| 781 | electric-pair-skip-whitespace)) | ||
| 782 | (electric-pair--skip-whitespace)) | ||
| 783 | (eq (char-after) last-command-event)))) | ||
| 784 | ;; This is too late: rather than insert&delete we'd want to only | ||
| 785 | ;; skip (or insert in overwrite mode). The difference is in what | ||
| 786 | ;; goes in the undo-log and in the intermediate state which might | ||
| 787 | ;; be visible to other post-self-insert-hook. We'll just have to | ||
| 788 | ;; live with it for now. | ||
| 789 | (when skip-whitespace-info | ||
| 790 | (electric-pair--skip-whitespace)) | ||
| 791 | (delete-region (1- pos) (if (eq skip-whitespace-info 'chomp) | ||
| 792 | (point) | ||
| 793 | pos)) | ||
| 794 | (forward-char)) | ||
| 795 | ;; Insert matching pair. | ||
| 796 | ((and (memq syntax `(?\( ?\" ?\$)) | ||
| 797 | (not overwrite-mode) | ||
| 798 | (or unconditional | ||
| 799 | (not (funcall electric-pair-inhibit-predicate | ||
| 800 | last-command-event)))) | ||
| 801 | (save-excursion (electric-pair--insert pair))))) | ||
| 802 | (t | ||
| 803 | (when (and (if (functionp electric-pair-open-newline-between-pairs) | ||
| 804 | (funcall electric-pair-open-newline-between-pairs) | ||
| 805 | electric-pair-open-newline-between-pairs) | ||
| 806 | (eq last-command-event ?\n) | ||
| 807 | (not (eobp)) | ||
| 808 | (eq (save-excursion | ||
| 809 | (skip-chars-backward "\t\s") | ||
| 810 | (char-before (1- (point)))) | ||
| 811 | (matching-paren (char-after)))) | ||
| 812 | (save-excursion (newline 1 t))))))) | ||
| 813 | |||
| 814 | (put 'electric-pair-post-self-insert-function 'priority 20) | ||
| 815 | |||
| 816 | (defun electric-pair-will-use-region () | ||
| 817 | (and (use-region-p) | ||
| 818 | (memq (car (electric-pair-syntax-info last-command-event)) | ||
| 819 | '(?\( ?\) ?\" ?\$)))) | ||
| 820 | |||
| 821 | (defvar electric-pair-mode-map | ||
| 822 | (let ((map (make-sparse-keymap))) | ||
| 823 | (define-key map [remap backward-delete-char-untabify] | ||
| 824 | 'electric-pair-backward-delete-char-untabify) | ||
| 825 | (define-key map [remap backward-delete-char] | ||
| 826 | 'electric-pair-backward-delete-char) | ||
| 827 | (define-key map [remap delete-backward-char] | ||
| 828 | 'electric-pair-backward-delete-char) | ||
| 829 | map) | ||
| 830 | "Keymap used by `electric-pair-mode'.") | ||
| 831 | |||
| 832 | ;;;###autoload | ||
| 833 | (define-minor-mode electric-pair-mode | ||
| 834 | "Toggle automatic parens pairing (Electric Pair mode). | ||
| 835 | With a prefix argument ARG, enable Electric Pair mode if ARG is | ||
| 836 | positive, and disable it otherwise. If called from Lisp, enable | ||
| 837 | the mode if ARG is omitted or nil. | ||
| 838 | |||
| 839 | Electric Pair mode is a global minor mode. When enabled, typing | ||
| 840 | an open parenthesis automatically inserts the corresponding | ||
| 841 | closing parenthesis. \(Likewise for brackets, etc.)." | ||
| 842 | :global t :group 'electricity | ||
| 843 | (if electric-pair-mode | ||
| 844 | (progn | ||
| 845 | (add-hook 'post-self-insert-hook | ||
| 846 | #'electric-pair-post-self-insert-function) | ||
| 847 | (electric--sort-post-self-insertion-hook) | ||
| 848 | (add-hook 'self-insert-uses-region-functions | ||
| 849 | #'electric-pair-will-use-region)) | ||
| 850 | (remove-hook 'post-self-insert-hook | ||
| 851 | #'electric-pair-post-self-insert-function) | ||
| 852 | (remove-hook 'self-insert-uses-region-functions | ||
| 853 | #'electric-pair-will-use-region))) | ||
| 854 | |||
| 855 | ;;; Electric newlines after/before/around some chars. | 328 | ;;; Electric newlines after/before/around some chars. |
| 856 | 329 | ||
| 857 | (defvar electric-layout-rules nil | 330 | (defvar electric-layout-rules nil |