diff options
| author | João Távora | 2013-12-26 22:02:49 +0000 |
|---|---|---|
| committer | João Távora | 2013-12-26 22:02:49 +0000 |
| commit | 3b8d5131a316ad2fdc206744cec489a11f0bf1d3 (patch) | |
| tree | 7a717cd2152141fdd7e5abe20926c7c0d4092869 | |
| parent | fbcc63a3176389f39cb06f5a56f2abb29b06eaab (diff) | |
| download | emacs-3b8d5131a316ad2fdc206744cec489a11f0bf1d3.tar.gz emacs-3b8d5131a316ad2fdc206744cec489a11f0bf1d3.zip | |
Make Electric Pair mode smarter/more useful:
* lisp/electric.el: Pairing/skipping helps preserve
balance. Autobackspacing behaviour. Opens extra newlines between
pairs. Skip whitespace before closing delimiters.
* lisp/emacs-lisp/lisp-mode.el (lisp-mode-variables): Use new
features.
* test/automated/electric-tests.lisp: New file.
* doc/emacs/programs.texi: Describe new features.
* lisp/simple.el: Pass non-nil interactive arg to newline call inside
newline-and-indent.
| -rw-r--r-- | doc/emacs/ChangeLog | 4 | ||||
| -rw-r--r-- | doc/emacs/programs.texi | 30 | ||||
| -rw-r--r-- | etc/ChangeLog | 3 | ||||
| -rw-r--r-- | etc/NEWS | 36 | ||||
| -rw-r--r-- | lisp/ChangeLog | 22 | ||||
| -rw-r--r-- | lisp/electric.el | 599 | ||||
| -rw-r--r-- | lisp/emacs-lisp/lisp-mode.el | 8 | ||||
| -rw-r--r-- | lisp/simple.el | 8 | ||||
| -rw-r--r-- | test/ChangeLog | 3 | ||||
| -rw-r--r-- | test/automated/electric-tests.el | 510 |
10 files changed, 1126 insertions, 97 deletions
diff --git a/doc/emacs/ChangeLog b/doc/emacs/ChangeLog index 96008f63e93..c9e6682aeaa 100644 --- a/doc/emacs/ChangeLog +++ b/doc/emacs/ChangeLog | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | 2013-12-26 João Távora <joaotavora@gmail.com> | ||
| 2 | * emacs.texi (Matching): Describe new features of Electric Pair | ||
| 3 | mode. | ||
| 4 | |||
| 1 | 2013-12-25 Chong Yidong <cyd@gnu.org> | 5 | 2013-12-25 Chong Yidong <cyd@gnu.org> |
| 2 | 6 | ||
| 3 | * glossary.texi (Glossary): Define MULE in modern terms. | 7 | * glossary.texi (Glossary): Define MULE in modern terms. |
diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi index 8bb851e75a4..ff61ba8b1d7 100644 --- a/doc/emacs/programs.texi +++ b/doc/emacs/programs.texi | |||
| @@ -844,8 +844,34 @@ show-paren-mode}. | |||
| 844 | Electric Pair mode, a global minor mode, provides a way to easily | 844 | Electric Pair mode, a global minor mode, provides a way to easily |
| 845 | insert matching delimiters. Whenever you insert an opening delimiter, | 845 | insert matching delimiters. Whenever you insert an opening delimiter, |
| 846 | the matching closing delimiter is automatically inserted as well, | 846 | the matching closing delimiter is automatically inserted as well, |
| 847 | leaving point between the two. To toggle Electric Pair mode, type | 847 | leaving point between the two. Conversely, when you insert a closing |
| 848 | @kbd{M-x electric-pair-mode}. | 848 | delimiter over an existing one, no inserting takes places and that |
| 849 | position is simply skipped over. These variables control additional | ||
| 850 | features of Electric Pair mode: | ||
| 851 | |||
| 852 | @itemize @bullet | ||
| 853 | @item | ||
| 854 | @code{electric-pair-preserve-balance}, when non-@code{nil}, makes the | ||
| 855 | default pairing logic balance out the number of opening and closing | ||
| 856 | delimiters. | ||
| 857 | |||
| 858 | @item | ||
| 859 | @code{electric-pair-delete-adjacent-pairs}, when non-@code{nil}, makes | ||
| 860 | backspacing between two adjacent delimiters also automatically delete | ||
| 861 | the closing delimiter. | ||
| 862 | |||
| 863 | @item | ||
| 864 | @code{electric-pair-open-newline-between-pairs}, when non-@code{nil}, | ||
| 865 | makes inserting inserting a newline between two adjacent pairs also | ||
| 866 | automatically open and extra newline after point. | ||
| 867 | |||
| 868 | @item | ||
| 869 | @code{electric-skip-whitespace}, when non-@code{nil}, causes the minor | ||
| 870 | mode to skip whitespace forward before deciding whether to skip over | ||
| 871 | the closing delimiter. | ||
| 872 | @end itemize | ||
| 873 | |||
| 874 | To toggle Electric Pair mode, type @kbd{M-x electric-pair-mode}. | ||
| 849 | 875 | ||
| 850 | @node Comments | 876 | @node Comments |
| 851 | @section Manipulating Comments | 877 | @section Manipulating Comments |
diff --git a/etc/ChangeLog b/etc/ChangeLog index 34f40ae6e24..d3ce75f5368 100644 --- a/etc/ChangeLog +++ b/etc/ChangeLog | |||
| @@ -1,3 +1,6 @@ | |||
| 1 | 2013-12-26 João Távora <joaotavora@gmail.com> | ||
| 2 | * NEWS: Describe new features of Electric Pair mode. | ||
| 3 | |||
| 1 | 2013-12-23 Teodor Zlatanov <tzz@lifelogs.com> | 4 | 2013-12-23 Teodor Zlatanov <tzz@lifelogs.com> |
| 2 | 5 | ||
| 3 | * NEWS: Updated for `gnutls-verify-error', cfengine-mode, and | 6 | * NEWS: Updated for `gnutls-verify-error', cfengine-mode, and |
| @@ -439,6 +439,42 @@ and `desktop-restore-forces-onscreen' offer further customization. | |||
| 439 | 439 | ||
| 440 | ** Eldoc Mode works properly in the minibuffer. | 440 | ** Eldoc Mode works properly in the minibuffer. |
| 441 | 441 | ||
| 442 | ** Electric Pair mode | ||
| 443 | *** New `electric-pair-preserve-balance' enabled by default. | ||
| 444 | |||
| 445 | Pairing/skipping only kicks in when that help the balance of | ||
| 446 | parentheses and quotes, i.e. the buffer should end up at least as | ||
| 447 | balanced as before. | ||
| 448 | |||
| 449 | You can further control this behaviour by adjusting the predicates | ||
| 450 | stored in `electric-pair-inhibit-predicate' and | ||
| 451 | `electric-pair-skip-self'. | ||
| 452 | |||
| 453 | *** New `electric-pair-delete-adjacent-pairs' enabled by default. | ||
| 454 | |||
| 455 | In `electric-pair-mode', the commands `backward-delete-char' and | ||
| 456 | `backward-delete-char-untabify' are now bound to electric variants | ||
| 457 | that delete the closer when invoked between adjacent pairs. | ||
| 458 | |||
| 459 | *** New `electric-pair-open-newline-between-pairs' enabled by default. | ||
| 460 | |||
| 461 | In `electric-pair-mode', inserting a newline between adjacent pairs | ||
| 462 | opens an extra newline after point, which is indented if | ||
| 463 | `electric-indent-mode' is also set. | ||
| 464 | |||
| 465 | *** New `electric-pair-skip-whitespace' enabled by default. | ||
| 466 | |||
| 467 | Controls if skipping over closing delimiters should jump over any | ||
| 468 | whitespace slack. Setting it to `chomp' makes it delete this | ||
| 469 | whitespace. See also the variable | ||
| 470 | `electric-pair-skip-whitespace-chars'. | ||
| 471 | |||
| 472 | *** New variables control the pairing in strings and comments. | ||
| 473 | |||
| 474 | You can customize `electric-pair-text-pairs' and | ||
| 475 | `electric-pair-text-syntax-table' to tweak pairing behaviour inside | ||
| 476 | strings and comments. | ||
| 477 | |||
| 442 | ** EPA | 478 | ** EPA |
| 443 | 479 | ||
| 444 | *** New option `epa-mail-aliases'. | 480 | *** New option `epa-mail-aliases'. |
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 972106bd26f..2831988efd9 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,25 @@ | |||
| 1 | 2013-12-26 João Távora <joaotavora@gmail.com> | ||
| 2 | |||
| 3 | * electric.el (electric-pair-mode): More flexible engine for skip- | ||
| 4 | and inhibit predicates, new options for pairing-related | ||
| 5 | functionality. | ||
| 6 | (electric-pair-preserve-balance): Pair/skip parentheses and quotes | ||
| 7 | if that keeps or improves their balance in buffers. | ||
| 8 | (electric-pair-delete-adjacent-pairs): Delete the pair when | ||
| 9 | backspacing over adjacent matched delimiters. | ||
| 10 | (electric-pair-open-extra-newline): Open extra newline when | ||
| 11 | inserting newlines between adjacent matched delimiters. | ||
| 12 | (electric--sort-post-self-insertion-hook): Sort | ||
| 13 | post-self-insert-hook according to priority values when | ||
| 14 | minor-modes are activated. | ||
| 15 | * simple.el (newline-and-indent): Call newline with interactive | ||
| 16 | set to t. | ||
| 17 | (blink-paren-post-self-insert-function): Set priority to 100. | ||
| 18 | * emacs-lisp/lisp-mode.el (lisp-mode-variables): Use | ||
| 19 | electric-pair-text-pairs to pair backtick-and-quote in strings and | ||
| 20 | comments. Locally set electric-pair-skip-whitespace to 'chomp and | ||
| 21 | electric-pair-open-newline-between-pairs to nil. | ||
| 22 | |||
| 1 | 2013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org> | 23 | 2013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org> |
| 2 | 24 | ||
| 3 | * progmodes/python.el: Use lexical-binding. | 25 | * progmodes/python.el: Use lexical-binding. |
diff --git a/lisp/electric.el b/lisp/electric.el index 91b99b4bfe7..fc5e63f90bb 100644 --- a/lisp/electric.el +++ b/lisp/electric.el | |||
| @@ -187,6 +187,17 @@ Returns nil when we can't find this char." | |||
| 187 | (eq (char-before) last-command-event))))) | 187 | (eq (char-before) last-command-event))))) |
| 188 | pos))) | 188 | pos))) |
| 189 | 189 | ||
| 190 | (defun electric--sort-post-self-insertion-hook () | ||
| 191 | "Ensure order of electric functions in `post-self-insertion-hook'. | ||
| 192 | |||
| 193 | Hooks in this variable interact in non-trivial ways, so a | ||
| 194 | relative order must be maintained within it." | ||
| 195 | (setq-default post-self-insert-hook | ||
| 196 | (sort (default-value 'post-self-insert-hook) | ||
| 197 | #'(lambda (fn1 fn2) | ||
| 198 | (< (or (get fn1 'priority) 0) | ||
| 199 | (or (get fn2 'priority) 0)))))) | ||
| 200 | |||
| 190 | ;;; Electric indentation. | 201 | ;;; Electric indentation. |
| 191 | 202 | ||
| 192 | ;; Autoloading variables is generally undesirable, but major modes | 203 | ;; Autoloading variables is generally undesirable, but major modes |
| @@ -267,6 +278,8 @@ mode set `electric-indent-inhibit', but this can be used as a workaround.") | |||
| 267 | (> pos (line-beginning-position))) | 278 | (> pos (line-beginning-position))) |
| 268 | (indent-according-to-mode))))) | 279 | (indent-according-to-mode))))) |
| 269 | 280 | ||
| 281 | (put 'electric-indent-post-self-insert-function 'priority 60) | ||
| 282 | |||
| 270 | (defun electric-indent-just-newline (arg) | 283 | (defun electric-indent-just-newline (arg) |
| 271 | "Insert just a newline, without any auto-indentation." | 284 | "Insert just a newline, without any auto-indentation." |
| 272 | (interactive "*P") | 285 | (interactive "*P") |
| @@ -295,20 +308,9 @@ insert a character from `electric-indent-chars'." | |||
| 295 | #'electric-indent-post-self-insert-function)) | 308 | #'electric-indent-post-self-insert-function)) |
| 296 | (when (eq (lookup-key global-map [?\C-j]) 'newline-and-indent) | 309 | (when (eq (lookup-key global-map [?\C-j]) 'newline-and-indent) |
| 297 | (define-key global-map [?\C-j] 'electric-indent-just-newline)) | 310 | (define-key global-map [?\C-j] 'electric-indent-just-newline)) |
| 298 | ;; post-self-insert-hooks interact in non-trivial ways. | ||
| 299 | ;; It turns out that electric-indent-mode generally works better if run | ||
| 300 | ;; late, but still before blink-paren. | ||
| 301 | (add-hook 'post-self-insert-hook | 311 | (add-hook 'post-self-insert-hook |
| 302 | #'electric-indent-post-self-insert-function | 312 | #'electric-indent-post-self-insert-function) |
| 303 | 'append) | 313 | (electric--sort-post-self-insertion-hook))) |
| 304 | ;; FIXME: Ugly! | ||
| 305 | (let ((bp (memq #'blink-paren-post-self-insert-function | ||
| 306 | (default-value 'post-self-insert-hook)))) | ||
| 307 | (when (memq #'electric-indent-post-self-insert-function bp) | ||
| 308 | (setcar bp #'electric-indent-post-self-insert-function) | ||
| 309 | (setcdr bp (cons #'blink-paren-post-self-insert-function | ||
| 310 | (delq #'electric-indent-post-self-insert-function | ||
| 311 | (cdr bp)))))))) | ||
| 312 | 314 | ||
| 313 | ;;;###autoload | 315 | ;;;###autoload |
| 314 | (define-minor-mode electric-indent-local-mode | 316 | (define-minor-mode electric-indent-local-mode |
| @@ -327,32 +329,163 @@ insert a character from `electric-indent-chars'." | |||
| 327 | 329 | ||
| 328 | (defcustom electric-pair-pairs | 330 | (defcustom electric-pair-pairs |
| 329 | '((?\" . ?\")) | 331 | '((?\" . ?\")) |
| 330 | "Alist of pairs that should be used regardless of major mode." | 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'." | ||
| 331 | :version "24.1" | 339 | :version "24.1" |
| 332 | :type '(repeat (cons character character))) | 340 | :type '(repeat (cons character character))) |
| 333 | 341 | ||
| 334 | (defcustom electric-pair-skip-self t | 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 | ||
| 335 | "If non-nil, skip char instead of inserting a second closing paren. | 353 | "If non-nil, skip char instead of inserting a second closing paren. |
| 354 | |||
| 336 | When inserting a closing paren character right before the same character, | 355 | When inserting a closing paren character right before the same character, |
| 337 | just skip that character instead, so that hitting ( followed by ) results | 356 | just skip that character instead, so that hitting ( followed by ) results |
| 338 | in \"()\" rather than \"())\". | 357 | in \"()\" rather than \"())\". |
| 339 | This can be convenient for people who find it easier to hit ) than C-f." | 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." | ||
| 340 | :version "24.1" | 364 | :version "24.1" |
| 341 | :type 'boolean) | 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)) | ||
| 342 | 370 | ||
| 343 | (defcustom electric-pair-inhibit-predicate | 371 | (defcustom electric-pair-inhibit-predicate |
| 344 | #'electric-pair-default-inhibit | 372 | #'electric-pair-default-inhibit |
| 345 | "Predicate to prevent insertion of a matching pair. | 373 | "Predicate to prevent insertion of a matching pair. |
| 374 | |||
| 346 | The function is called with a single char (the opening char just inserted). | 375 | The function is called with a single char (the opening char just inserted). |
| 347 | If it returns non-nil, then `electric-pair-mode' will not insert a matching | 376 | If it returns non-nil, then `electric-pair-mode' will not insert a matching |
| 348 | closer." | 377 | closer." |
| 349 | :version "24.4" | 378 | :version "24.4" |
| 350 | :type '(choice | 379 | :type '(choice |
| 351 | (const :tag "Default" electric-pair-default-inhibit) | 380 | (const :tag "Conservative" electric-pair-conservative-inhibit) |
| 381 | (const :tag "Help balance" electric-pair-default-inhibit) | ||
| 352 | (const :tag "Always pair" ignore) | 382 | (const :tag "Always pair" ignore) |
| 353 | function)) | 383 | function)) |
| 354 | 384 | ||
| 355 | (defun electric-pair-default-inhibit (char) | 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) | ||
| 356 | (or | 489 | (or |
| 357 | ;; I find it more often preferable not to pair when the | 490 | ;; I find it more often preferable not to pair when the |
| 358 | ;; same char is next. | 491 | ;; same char is next. |
| @@ -363,14 +496,40 @@ closer." | |||
| 363 | ;; I also find it often preferable not to pair next to a word. | 496 | ;; I also find it often preferable not to pair next to a word. |
| 364 | (eq (char-syntax (following-char)) ?w))) | 497 | (eq (char-syntax (following-char)) ?w))) |
| 365 | 498 | ||
| 366 | (defun electric-pair-syntax (command-event) | 499 | (defun electric-pair-syntax-info (command-event) |
| 367 | (let ((x (assq command-event electric-pair-pairs))) | 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))) | ||
| 368 | (cond | 525 | (cond |
| 369 | (x (if (eq (car x) (cdr x)) ?\" ?\()) | 526 | ((memq (car table-syntax-and-pair) |
| 370 | ((rassq command-event electric-pair-pairs) ?\)) | 527 | '(?\" ?\( ?\) ?\$)) |
| 371 | ((nth 8 (syntax-ppss)) | 528 | (append table-syntax-and-pair (list nil string-or-comment))) |
| 372 | (with-syntax-table text-mode-syntax-table (char-syntax command-event))) | 529 | (direct (if (eq (car direct) (cdr direct)) |
| 373 | (t (char-syntax command-event))))) | 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))))) | ||
| 374 | 533 | ||
| 375 | (defun electric-pair--insert (char) | 534 | (defun electric-pair--insert (char) |
| 376 | (let ((last-command-event char) | 535 | (let ((last-command-event char) |
| @@ -378,56 +537,297 @@ closer." | |||
| 378 | (electric-pair-mode nil)) | 537 | (electric-pair-mode nil)) |
| 379 | (self-insert-command 1))) | 538 | (self-insert-command 1))) |
| 380 | 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 | |||
| 381 | (defun electric-pair-post-self-insert-function () | 741 | (defun electric-pair-post-self-insert-function () |
| 382 | (let* ((pos (and electric-pair-mode (electric--after-char-pos))) | 742 | (let* ((pos (and electric-pair-mode (electric--after-char-pos))) |
| 383 | (syntax (and pos (electric-pair-syntax last-command-event))) | 743 | (skip-whitespace-info)) |
| 384 | (closer (if (eq syntax ?\() | 744 | (pcase (electric-pair-syntax-info last-command-event) |
| 385 | (cdr (or (assq last-command-event electric-pair-pairs) | 745 | (`(,syntax ,pair ,unconditional ,_) |
| 386 | (aref (syntax-table) last-command-event))) | 746 | (cond |
| 387 | last-command-event))) | 747 | ((null pos) nil) |
| 388 | (cond | 748 | ;; Wrap a pair around the active region. |
| 389 | ((null pos) nil) | 749 | ;; |
| 390 | ;; Wrap a pair around the active region. | 750 | ((and (memq syntax '(?\( ?\) ?\" ?\$)) (use-region-p)) |
| 391 | ((and (memq syntax '(?\( ?\" ?\$)) (use-region-p)) | 751 | ;; FIXME: To do this right, we'd need a post-self-insert-function |
| 392 | ;; 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 |
| 393 | ;; so we could add-function around it and insert the closer after | 753 | ;; all the rest of the hook has run. |
| 394 | ;; all the rest of the hook has run. | 754 | (if (or (eq syntax ?\") |
| 395 | (if (>= (mark) (point)) | 755 | (and (eq syntax ?\)) |
| 396 | (goto-char (mark)) | 756 | (>= (point) (mark))) |
| 397 | ;; We already inserted the open-paren but at the end of the | 757 | (and (not (eq syntax ?\))) |
| 398 | ;; region, so we have to remove it and start over. | 758 | (>= (mark) (point)))) |
| 399 | (delete-region (1- pos) (point)) | 759 | (save-excursion |
| 400 | (save-excursion | 760 | (goto-char (mark)) |
| 401 | (goto-char (mark)) | 761 | (electric-pair--insert pair)) |
| 402 | (electric-pair--insert last-command-event))) | 762 | (delete-region pos (1- pos)) |
| 403 | ;; Since we're right after the closer now, we could tell the rest of | 763 | (electric-pair--insert pair) |
| 404 | ;; post-self-insert-hook that we inserted `closer', but then we'd get | 764 | (goto-char (mark)) |
| 405 | ;; blink-paren to kick in, which is annoying. | 765 | (electric-pair--insert last-command-event))) |
| 406 | ;;(setq last-command-event closer) | 766 | ;; Backslash-escaped: no pairing, no skipping. |
| 407 | (insert closer)) | 767 | ((save-excursion |
| 408 | ;; Backslash-escaped: no pairing, no skipping. | 768 | (goto-char (1- pos)) |
| 409 | ((save-excursion | 769 | (not (zerop (% (skip-syntax-backward "\\") 2)))) |
| 410 | (goto-char (1- pos)) | 770 | nil) |
| 411 | (not (zerop (% (skip-syntax-backward "\\") 2)))) | 771 | ;; Skip self. |
| 412 | nil) | 772 | ((and (memq syntax '(?\) ?\" ?\$)) |
| 413 | ;; Skip self. | 773 | (and (or unconditional |
| 414 | ((and (memq syntax '(?\) ?\" ?\$)) | 774 | (if (functionp electric-pair-skip-self) |
| 415 | electric-pair-skip-self | 775 | (funcall electric-pair-skip-self last-command-event) |
| 416 | (eq (char-after pos) last-command-event)) | 776 | electric-pair-skip-self)) |
| 417 | ;; This is too late: rather than insert&delete we'd want to only skip (or | 777 | (save-excursion |
| 418 | ;; insert in overwrite mode). The difference is in what goes in the | 778 | (when (setq skip-whitespace-info |
| 419 | ;; undo-log and in the intermediate state which might be visible to other | 779 | (if (functionp electric-pair-skip-whitespace) |
| 420 | ;; post-self-insert-hook. We'll just have to live with it for now. | 780 | (funcall electric-pair-skip-whitespace) |
| 421 | (delete-char 1)) | 781 | electric-pair-skip-whitespace)) |
| 422 | ;; Insert matching pair. | 782 | (electric-pair--skip-whitespace)) |
| 423 | ((not (or (not (memq syntax `(?\( ?\" ?\$))) | 783 | (eq (char-after) last-command-event)))) |
| 424 | overwrite-mode | 784 | ;; This is too late: rather than insert&delete we'd want to only |
| 425 | (funcall electric-pair-inhibit-predicate last-command-event))) | 785 | ;; skip (or insert in overwrite mode). The difference is in what |
| 426 | (save-excursion (electric-pair--insert closer)))))) | 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) | ||
| 427 | 815 | ||
| 428 | (defun electric-pair-will-use-region () | 816 | (defun electric-pair-will-use-region () |
| 429 | (and (use-region-p) | 817 | (and (use-region-p) |
| 430 | (memq (electric-pair-syntax last-command-event) '(?\( ?\" ?\$)))) | 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'.") | ||
| 431 | 831 | ||
| 432 | ;;;###autoload | 832 | ;;;###autoload |
| 433 | (define-minor-mode electric-pair-mode | 833 | (define-minor-mode electric-pair-mode |
| @@ -438,29 +838,33 @@ the mode if ARG is omitted or nil. | |||
| 438 | 838 | ||
| 439 | Electric Pair mode is a global minor mode. When enabled, typing | 839 | Electric Pair mode is a global minor mode. When enabled, typing |
| 440 | an open parenthesis automatically inserts the corresponding | 840 | an open parenthesis automatically inserts the corresponding |
| 441 | closing parenthesis. \(Likewise for brackets, etc.) | 841 | closing parenthesis. \(Likewise for brackets, etc.)." |
| 442 | |||
| 443 | See options `electric-pair-pairs' and `electric-pair-skip-self'." | ||
| 444 | :global t :group 'electricity | 842 | :global t :group 'electricity |
| 445 | (if electric-pair-mode | 843 | (if electric-pair-mode |
| 446 | (progn | 844 | (progn |
| 447 | (add-hook 'post-self-insert-hook | 845 | (add-hook 'post-self-insert-hook |
| 448 | #'electric-pair-post-self-insert-function) | 846 | #'electric-pair-post-self-insert-function) |
| 847 | (electric--sort-post-self-insertion-hook) | ||
| 449 | (add-hook 'self-insert-uses-region-functions | 848 | (add-hook 'self-insert-uses-region-functions |
| 450 | #'electric-pair-will-use-region)) | 849 | #'electric-pair-will-use-region)) |
| 451 | (remove-hook 'post-self-insert-hook | 850 | (remove-hook 'post-self-insert-hook |
| 452 | #'electric-pair-post-self-insert-function) | 851 | #'electric-pair-post-self-insert-function) |
| 453 | (remove-hook 'self-insert-uses-region-functions | 852 | (remove-hook 'self-insert-uses-region-functions |
| 454 | #'electric-pair-will-use-region))) | 853 | #'electric-pair-will-use-region))) |
| 455 | 854 | ||
| 456 | ;;; Electric newlines after/before/around some chars. | 855 | ;;; Electric newlines after/before/around some chars. |
| 457 | 856 | ||
| 458 | (defvar electric-layout-rules '() | 857 | (defvar electric-layout-rules nil |
| 459 | "List of rules saying where to automatically insert newlines. | 858 | "List of rules saying where to automatically insert newlines. |
| 460 | Each rule has the form (CHAR . WHERE) where CHAR is the char | 859 | |
| 461 | that was just inserted and WHERE specifies where to insert newlines | 860 | Each rule has the form (CHAR . WHERE) where CHAR is the char that |
| 462 | and can be: nil, `before', `after', `around', or a function of no | 861 | was just inserted and WHERE specifies where to insert newlines |
| 463 | arguments that returns one of those symbols.") | 862 | and can be: nil, `before', `after', `around', `after-stay', or a |
| 863 | function of no arguments that returns one of those symbols. | ||
| 864 | |||
| 865 | The symbols specify where in relation to CHAR the newline | ||
| 866 | character(s) should be inserted. `after-stay' means insert a | ||
| 867 | newline after CHAR but stay in the same place.") | ||
| 464 | 868 | ||
| 465 | (defun electric-layout-post-self-insert-function () | 869 | (defun electric-layout-post-self-insert-function () |
| 466 | (let* ((rule (cdr (assq last-command-event electric-layout-rules))) | 870 | (let* ((rule (cdr (assq last-command-event electric-layout-rules))) |
| @@ -469,23 +873,32 @@ arguments that returns one of those symbols.") | |||
| 469 | (setq pos (electric--after-char-pos)) | 873 | (setq pos (electric--after-char-pos)) |
| 470 | ;; Not in a string or comment. | 874 | ;; Not in a string or comment. |
| 471 | (not (nth 8 (save-excursion (syntax-ppss pos))))) | 875 | (not (nth 8 (save-excursion (syntax-ppss pos))))) |
| 472 | (let ((end (copy-marker (point) t))) | 876 | (let ((end (copy-marker (point))) |
| 877 | (sym (if (functionp rule) (funcall rule) rule))) | ||
| 878 | (set-marker-insertion-type end (not (eq sym 'after-stay))) | ||
| 473 | (goto-char pos) | 879 | (goto-char pos) |
| 474 | (pcase (if (functionp rule) (funcall rule) rule) | 880 | (pcase sym |
| 475 | ;; FIXME: we used `newline' down here which called | 881 | ;; FIXME: we used `newline' down here which called |
| 476 | ;; self-insert-command and ran post-self-insert-hook recursively. | 882 | ;; self-insert-command and ran post-self-insert-hook recursively. |
| 477 | ;; It happened to make electric-indent-mode work automatically with | 883 | ;; It happened to make electric-indent-mode work automatically with |
| 478 | ;; electric-layout-mode (at the cost of re-indenting lines | 884 | ;; electric-layout-mode (at the cost of re-indenting lines |
| 479 | ;; multiple times), but I'm not sure it's what we want. | 885 | ;; multiple times), but I'm not sure it's what we want. |
| 886 | ;; | ||
| 887 | ;; FIXME: check eolp before inserting \n? | ||
| 480 | (`before (goto-char (1- pos)) (skip-chars-backward " \t") | 888 | (`before (goto-char (1- pos)) (skip-chars-backward " \t") |
| 481 | (unless (bolp) (insert "\n"))) | 889 | (unless (bolp) (insert "\n"))) |
| 482 | (`after (insert "\n")) ; FIXME: check eolp before inserting \n? | 890 | (`after (insert "\n")) |
| 891 | (`after-stay (save-excursion | ||
| 892 | (let ((electric-layout-rules nil)) | ||
| 893 | (newline 1 t)))) | ||
| 483 | (`around (save-excursion | 894 | (`around (save-excursion |
| 484 | (goto-char (1- pos)) (skip-chars-backward " \t") | 895 | (goto-char (1- pos)) (skip-chars-backward " \t") |
| 485 | (unless (bolp) (insert "\n"))) | 896 | (unless (bolp) (insert "\n"))) |
| 486 | (insert "\n"))) ; FIXME: check eolp before inserting \n? | 897 | (insert "\n"))) ; FIXME: check eolp before inserting \n? |
| 487 | (goto-char end))))) | 898 | (goto-char end))))) |
| 488 | 899 | ||
| 900 | (put 'electric-layout-post-self-insert-function 'priority 40) | ||
| 901 | |||
| 489 | ;;;###autoload | 902 | ;;;###autoload |
| 490 | (define-minor-mode electric-layout-mode | 903 | (define-minor-mode electric-layout-mode |
| 491 | "Automatically insert newlines around some chars. | 904 | "Automatically insert newlines around some chars. |
| @@ -494,11 +907,13 @@ positive, and disable it otherwise. If called from Lisp, enable | |||
| 494 | the mode if ARG is omitted or nil. | 907 | the mode if ARG is omitted or nil. |
| 495 | The variable `electric-layout-rules' says when and how to insert newlines." | 908 | The variable `electric-layout-rules' says when and how to insert newlines." |
| 496 | :global t :group 'electricity | 909 | :global t :group 'electricity |
| 497 | (if electric-layout-mode | 910 | (cond (electric-layout-mode |
| 498 | (add-hook 'post-self-insert-hook | 911 | (add-hook 'post-self-insert-hook |
| 499 | #'electric-layout-post-self-insert-function) | 912 | #'electric-layout-post-self-insert-function) |
| 500 | (remove-hook 'post-self-insert-hook | 913 | (electric--sort-post-self-insertion-hook)) |
| 501 | #'electric-layout-post-self-insert-function))) | 914 | (t |
| 915 | (remove-hook 'post-self-insert-hook | ||
| 916 | #'electric-layout-post-self-insert-function)))) | ||
| 502 | 917 | ||
| 503 | (provide 'electric) | 918 | (provide 'electric) |
| 504 | 919 | ||
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index b7bd33f628f..f1eae18c507 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el | |||
| @@ -472,7 +472,13 @@ font-lock keywords will not be case sensitive." | |||
| 472 | (font-lock-mark-block-function . mark-defun) | 472 | (font-lock-mark-block-function . mark-defun) |
| 473 | (font-lock-syntactic-face-function | 473 | (font-lock-syntactic-face-function |
| 474 | . lisp-font-lock-syntactic-face-function))) | 474 | . lisp-font-lock-syntactic-face-function))) |
| 475 | (setq-local prettify-symbols-alist lisp--prettify-symbols-alist)) | 475 | (setq-local prettify-symbols-alist lisp--prettify-symbols-alist) |
| 476 | ;; electric | ||
| 477 | (when elisp | ||
| 478 | (setq-local electric-pair-text-pairs | ||
| 479 | (cons '(?\` . ?\') electric-pair-text-pairs))) | ||
| 480 | (setq-local electric-pair-skip-whitespace 'chomp) | ||
| 481 | (setq-local electric-pair-open-newline-between-pairs nil)) | ||
| 476 | 482 | ||
| 477 | (defun lisp-outline-level () | 483 | (defun lisp-outline-level () |
| 478 | "Lisp mode `outline-level' function." | 484 | "Lisp mode `outline-level' function." |
diff --git a/lisp/simple.el b/lisp/simple.el index a6543516a9c..624d87fd655 100644 --- a/lisp/simple.el +++ b/lisp/simple.el | |||
| @@ -610,7 +610,7 @@ In some text modes, where TAB inserts a tab, this command indents to the | |||
| 610 | column specified by the function `current-left-margin'." | 610 | column specified by the function `current-left-margin'." |
| 611 | (interactive "*") | 611 | (interactive "*") |
| 612 | (delete-horizontal-space t) | 612 | (delete-horizontal-space t) |
| 613 | (newline) | 613 | (newline 1 t) |
| 614 | (indent-according-to-mode)) | 614 | (indent-according-to-mode)) |
| 615 | 615 | ||
| 616 | (defun reindent-then-newline-and-indent () | 616 | (defun reindent-then-newline-and-indent () |
| @@ -6448,10 +6448,14 @@ More precisely, a char with closeparen syntax is self-inserted.") | |||
| 6448 | (point)))))) | 6448 | (point)))))) |
| 6449 | (funcall blink-paren-function))) | 6449 | (funcall blink-paren-function))) |
| 6450 | 6450 | ||
| 6451 | (put 'blink-paren-post-self-insert-function 'priority 100) | ||
| 6452 | |||
| 6451 | (add-hook 'post-self-insert-hook #'blink-paren-post-self-insert-function | 6453 | (add-hook 'post-self-insert-hook #'blink-paren-post-self-insert-function |
| 6452 | ;; Most likely, this hook is nil, so this arg doesn't matter, | 6454 | ;; Most likely, this hook is nil, so this arg doesn't matter, |
| 6453 | ;; but I use it as a reminder that this function usually | 6455 | ;; but I use it as a reminder that this function usually |
| 6454 | ;; likes to be run after others since it does `sit-for'. | 6456 | ;; likes to be run after others since it does |
| 6457 | ;; `sit-for'. That's also the reason it get a `priority' prop | ||
| 6458 | ;; of 100. | ||
| 6455 | 'append) | 6459 | 'append) |
| 6456 | 6460 | ||
| 6457 | ;; This executes C-g typed while Emacs is waiting for a command. | 6461 | ;; This executes C-g typed while Emacs is waiting for a command. |
diff --git a/test/ChangeLog b/test/ChangeLog index 1f4d5164e37..f45e4de11f5 100644 --- a/test/ChangeLog +++ b/test/ChangeLog | |||
| @@ -1,3 +1,6 @@ | |||
| 1 | 2013-12-26 João Távora <joaotavora@gmail.com> | ||
| 2 | * automated/electric-tests.el: Add tests for Electric Pair mode. | ||
| 3 | |||
| 1 | 2013-12-25 Fabián Ezequiel Gallina <fgallina@gnu.org> | 4 | 2013-12-25 Fabián Ezequiel Gallina <fgallina@gnu.org> |
| 2 | 5 | ||
| 3 | * automated/python-tests.el | 6 | * automated/python-tests.el |
diff --git a/test/automated/electric-tests.el b/test/automated/electric-tests.el new file mode 100644 index 00000000000..28a34752ecf --- /dev/null +++ b/test/automated/electric-tests.el | |||
| @@ -0,0 +1,510 @@ | |||
| 1 | ;;; electric-tests.el --- tests for electric.el | ||
| 2 | |||
| 3 | ;; Copyright (C) 2013 João Távora | ||
| 4 | |||
| 5 | ;; Author: João Távora <joaotavora@gmail.com> | ||
| 6 | ;; Keywords: | ||
| 7 | |||
| 8 | ;; This program is free software; you can redistribute it and/or modify | ||
| 9 | ;; it under the terms of the GNU General Public License as published by | ||
| 10 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 11 | ;; (at your option) any later version. | ||
| 12 | |||
| 13 | ;; This program is distributed in the hope that it will be useful, | ||
| 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | ;; GNU General Public License for more details. | ||
| 17 | |||
| 18 | ;; You should have received a copy of the GNU General Public License | ||
| 19 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 20 | |||
| 21 | ;;; Commentary: Tests for Electric Pair mode. | ||
| 22 | ;;; TODO: Add tests for other Electric-* functionality | ||
| 23 | |||
| 24 | ;; | ||
| 25 | |||
| 26 | ;;; Code: | ||
| 27 | (require 'ert) | ||
| 28 | (require 'ert-x) | ||
| 29 | (require 'electric) | ||
| 30 | (require 'cl-lib) | ||
| 31 | |||
| 32 | (defun call-with-saved-electric-modes (fn) | ||
| 33 | (let ((saved-electric (if electric-pair-mode 1 -1)) | ||
| 34 | (saved-layout (if electric-layout-mode 1 -1)) | ||
| 35 | (saved-indent (if electric-indent-mode 1 -1))) | ||
| 36 | (electric-pair-mode -1) | ||
| 37 | (electric-layout-mode -1) | ||
| 38 | (electric-indent-mode -1) | ||
| 39 | (unwind-protect | ||
| 40 | (funcall fn) | ||
| 41 | (electric-pair-mode saved-electric) | ||
| 42 | (electric-indent-mode saved-indent) | ||
| 43 | (electric-layout-mode saved-layout)))) | ||
| 44 | |||
| 45 | (defmacro save-electric-modes (&rest body) | ||
| 46 | (declare (indent defun) (debug t)) | ||
| 47 | `(call-with-saved-electric-modes #'(lambda () ,@body))) | ||
| 48 | |||
| 49 | (defun electric-pair-test-for (fixture where char expected-string | ||
| 50 | expected-point mode bindings fixture-fn) | ||
| 51 | (with-temp-buffer | ||
| 52 | (funcall mode) | ||
| 53 | (insert fixture) | ||
| 54 | (save-electric-modes | ||
| 55 | (let ((last-command-event char)) | ||
| 56 | (goto-char where) | ||
| 57 | (funcall fixture-fn) | ||
| 58 | (cl-progv | ||
| 59 | (mapcar #'car bindings) | ||
| 60 | (mapcar #'cdr bindings) | ||
| 61 | (self-insert-command 1)))) | ||
| 62 | (should (equal (buffer-substring-no-properties (point-min) (point-max)) | ||
| 63 | expected-string)) | ||
| 64 | (should (equal (point) | ||
| 65 | expected-point)))) | ||
| 66 | |||
| 67 | (eval-when-compile | ||
| 68 | (defun electric-pair-define-test-form (name fixture | ||
| 69 | char | ||
| 70 | pos | ||
| 71 | expected-string | ||
| 72 | expected-point | ||
| 73 | skip-pair-string | ||
| 74 | prefix | ||
| 75 | suffix | ||
| 76 | extra-desc | ||
| 77 | mode | ||
| 78 | bindings | ||
| 79 | fixture-fn) | ||
| 80 | (let* ((expected-string-and-point | ||
| 81 | (if skip-pair-string | ||
| 82 | (with-temp-buffer | ||
| 83 | (cl-progv | ||
| 84 | ;; FIXME: avoid `eval' | ||
| 85 | (mapcar #'car (eval bindings)) | ||
| 86 | (mapcar #'cdr (eval bindings)) | ||
| 87 | (funcall mode) | ||
| 88 | (insert fixture) | ||
| 89 | (goto-char (1+ pos)) | ||
| 90 | (insert char) | ||
| 91 | (cond ((eq (aref skip-pair-string pos) | ||
| 92 | ?p) | ||
| 93 | (insert (cadr (electric-pair-syntax-info char))) | ||
| 94 | (backward-char 1)) | ||
| 95 | ((eq (aref skip-pair-string pos) | ||
| 96 | ?s) | ||
| 97 | (delete-char -1) | ||
| 98 | (forward-char 1))) | ||
| 99 | (list | ||
| 100 | (buffer-substring-no-properties (point-min) (point-max)) | ||
| 101 | (point)))) | ||
| 102 | (list expected-string expected-point))) | ||
| 103 | (expected-string (car expected-string-and-point)) | ||
| 104 | (expected-point (cadr expected-string-and-point)) | ||
| 105 | (fixture (format "%s%s%s" prefix fixture suffix)) | ||
| 106 | (expected-string (format "%s%s%s" prefix expected-string suffix)) | ||
| 107 | (expected-point (+ (length prefix) expected-point)) | ||
| 108 | (pos (+ (length prefix) pos))) | ||
| 109 | `(ert-deftest ,(intern (format "electric-pair-%s-at-point-%s-in-%s%s" | ||
| 110 | name | ||
| 111 | (1+ pos) | ||
| 112 | mode | ||
| 113 | extra-desc)) | ||
| 114 | () | ||
| 115 | ,(format "With \"%s\", try input %c at point %d. \ | ||
| 116 | Should %s \"%s\" and point at %d" | ||
| 117 | fixture | ||
| 118 | char | ||
| 119 | (1+ pos) | ||
| 120 | (if (string= fixture expected-string) | ||
| 121 | "stay" | ||
| 122 | "become") | ||
| 123 | (replace-regexp-in-string "\n" "\\\\n" expected-string) | ||
| 124 | expected-point) | ||
| 125 | (electric-pair-test-for ,fixture | ||
| 126 | ,(1+ pos) | ||
| 127 | ,char | ||
| 128 | ,expected-string | ||
| 129 | ,expected-point | ||
| 130 | ',mode | ||
| 131 | ,bindings | ||
| 132 | ,fixture-fn))))) | ||
| 133 | |||
| 134 | (cl-defmacro define-electric-pair-test | ||
| 135 | (name fixture | ||
| 136 | input | ||
| 137 | &key | ||
| 138 | skip-pair-string | ||
| 139 | expected-string | ||
| 140 | expected-point | ||
| 141 | bindings | ||
| 142 | (modes '(quote (emacs-lisp-mode ruby-mode c++-mode))) | ||
| 143 | (test-in-comments t) | ||
| 144 | (test-in-strings t) | ||
| 145 | (test-in-code t) | ||
| 146 | (fixture-fn #'(lambda () | ||
| 147 | (electric-pair-mode 1)))) | ||
| 148 | `(progn | ||
| 149 | ,@(cl-loop | ||
| 150 | for mode in (eval modes) ;FIXME: avoid `eval' | ||
| 151 | append | ||
| 152 | (cl-loop | ||
| 153 | for (prefix suffix extra-desc) in | ||
| 154 | (append (if test-in-comments | ||
| 155 | `((,(with-temp-buffer | ||
| 156 | (funcall mode) | ||
| 157 | (insert "z") | ||
| 158 | (comment-region (point-min) (point-max)) | ||
| 159 | (buffer-substring-no-properties (point-min) | ||
| 160 | (1- (point-max)))) | ||
| 161 | "" | ||
| 162 | "-in-comments"))) | ||
| 163 | (if test-in-strings | ||
| 164 | `(("\"" "\"" "-in-strings"))) | ||
| 165 | (if test-in-code | ||
| 166 | `(("" "" "")))) | ||
| 167 | append | ||
| 168 | (cl-loop | ||
| 169 | for char across input | ||
| 170 | for pos from 0 | ||
| 171 | unless (eq char ?-) | ||
| 172 | collect (electric-pair-define-test-form | ||
| 173 | name | ||
| 174 | fixture | ||
| 175 | (aref input pos) | ||
| 176 | pos | ||
| 177 | expected-string | ||
| 178 | expected-point | ||
| 179 | skip-pair-string | ||
| 180 | prefix | ||
| 181 | suffix | ||
| 182 | extra-desc | ||
| 183 | mode | ||
| 184 | bindings | ||
| 185 | fixture-fn)))))) | ||
| 186 | |||
| 187 | ;;; Basic pairings and skippings | ||
| 188 | ;;; | ||
| 189 | (define-electric-pair-test balanced-situation | ||
| 190 | " (()) " "(((((((" :skip-pair-string "ppppppp" | ||
| 191 | :modes '(ruby-mode)) | ||
| 192 | |||
| 193 | (define-electric-pair-test too-many-openings | ||
| 194 | " ((()) " "(((((((" :skip-pair-string "ppppppp") | ||
| 195 | |||
| 196 | (define-electric-pair-test too-many-closings | ||
| 197 | " (())) " "(((((((" :skip-pair-string "------p") | ||
| 198 | |||
| 199 | (define-electric-pair-test too-many-closings-2 | ||
| 200 | "() ) " "---(---" :skip-pair-string "-------") | ||
| 201 | |||
| 202 | (define-electric-pair-test too-many-closings-3 | ||
| 203 | ")() " "(------" :skip-pair-string "-------") | ||
| 204 | |||
| 205 | (define-electric-pair-test balanced-autoskipping | ||
| 206 | " (()) " "---))--" :skip-pair-string "---ss--") | ||
| 207 | |||
| 208 | (define-electric-pair-test too-many-openings-autoskipping | ||
| 209 | " ((()) " "----))-" :skip-pair-string "-------") | ||
| 210 | |||
| 211 | (define-electric-pair-test too-many-closings-autoskipping | ||
| 212 | " (())) " "---)))-" :skip-pair-string "---sss-") | ||
| 213 | |||
| 214 | |||
| 215 | ;;; Mixed parens | ||
| 216 | ;;; | ||
| 217 | (define-electric-pair-test mixed-paren-1 | ||
| 218 | " ()] " "-(-(---" :skip-pair-string "-p-p---") | ||
| 219 | |||
| 220 | (define-electric-pair-test mixed-paren-2 | ||
| 221 | " [() " "-(-()--" :skip-pair-string "-p-ps--") | ||
| 222 | |||
| 223 | (define-electric-pair-test mixed-paren-3 | ||
| 224 | " (]) " "-(-()--" :skip-pair-string "---ps--") | ||
| 225 | |||
| 226 | (define-electric-pair-test mixed-paren-4 | ||
| 227 | " ()] " "---)]--" :skip-pair-string "---ss--") | ||
| 228 | |||
| 229 | (define-electric-pair-test mixed-paren-5 | ||
| 230 | " [() " "----(--" :skip-pair-string "----p--") | ||
| 231 | |||
| 232 | (define-electric-pair-test find-matching-different-paren-type | ||
| 233 | " ()] " "-[-----" :skip-pair-string "-------") | ||
| 234 | |||
| 235 | (define-electric-pair-test find-matching-different-paren-type-inside-list | ||
| 236 | "( ()]) " "-[-----" :skip-pair-string "-------") | ||
| 237 | |||
| 238 | (define-electric-pair-test ignore-different-unmatching-paren-type | ||
| 239 | "( ()]) " "-(-----" :skip-pair-string "-p-----") | ||
| 240 | |||
| 241 | (define-electric-pair-test autopair-keep-least-amount-of-mixed-unbalance | ||
| 242 | "( ()] " "-(-----" :skip-pair-string "-p-----") | ||
| 243 | |||
| 244 | (define-electric-pair-test dont-autopair-to-resolve-mixed-unbalance | ||
| 245 | "( ()] " "-[-----" :skip-pair-string "-------") | ||
| 246 | |||
| 247 | (define-electric-pair-test autopair-so-as-not-to-worsen-unbalance-situation | ||
| 248 | "( (]) " "-[-----" :skip-pair-string "-p-----") | ||
| 249 | |||
| 250 | (define-electric-pair-test skip-over-partially-balanced | ||
| 251 | " [([]) " "-----)---" :skip-pair-string "-----s---") | ||
| 252 | |||
| 253 | (define-electric-pair-test only-skip-over-at-least-partially-balanced-stuff | ||
| 254 | " [([()) " "-----))--" :skip-pair-string "-----s---") | ||
| 255 | |||
| 256 | |||
| 257 | |||
| 258 | |||
| 259 | ;;; Quotes | ||
| 260 | ;;; | ||
| 261 | (define-electric-pair-test pair-some-quotes-skip-others | ||
| 262 | " \"\" " "-\"\"-----" :skip-pair-string "-ps------" | ||
| 263 | :test-in-strings nil | ||
| 264 | :bindings `((electric-pair-text-syntax-table | ||
| 265 | . ,prog-mode-syntax-table))) | ||
| 266 | |||
| 267 | (define-electric-pair-test skip-single-quotes-in-ruby-mode | ||
| 268 | " '' " "--'-" :skip-pair-string "--s-" | ||
| 269 | :modes '(ruby-mode) | ||
| 270 | :test-in-comments nil | ||
| 271 | :test-in-strings nil | ||
| 272 | :bindings `((electric-pair-text-syntax-table | ||
| 273 | . ,prog-mode-syntax-table))) | ||
| 274 | |||
| 275 | (define-electric-pair-test leave-unbalanced-quotes-alone | ||
| 276 | " \"' " "-\"'-" :skip-pair-string "----" | ||
| 277 | :modes '(ruby-mode) | ||
| 278 | :test-in-strings nil | ||
| 279 | :bindings `((electric-pair-text-syntax-table | ||
| 280 | . ,prog-mode-syntax-table))) | ||
| 281 | |||
| 282 | (define-electric-pair-test leave-unbalanced-quotes-alone-2 | ||
| 283 | " \"\\\"' " "-\"--'-" :skip-pair-string "------" | ||
| 284 | :modes '(ruby-mode) | ||
| 285 | :test-in-strings nil | ||
| 286 | :bindings `((electric-pair-text-syntax-table | ||
| 287 | . ,prog-mode-syntax-table))) | ||
| 288 | |||
| 289 | (define-electric-pair-test leave-unbalanced-quotes-alone-3 | ||
| 290 | " foo\\''" "'------" :skip-pair-string "-------" | ||
| 291 | :modes '(ruby-mode) | ||
| 292 | :test-in-strings nil | ||
| 293 | :bindings `((electric-pair-text-syntax-table | ||
| 294 | . ,prog-mode-syntax-table))) | ||
| 295 | |||
| 296 | (define-electric-pair-test inhibit-only-if-next-is-mismatched | ||
| 297 | "\"foo\"\"bar" "\"" | ||
| 298 | :expected-string "\"\"\"foo\"\"bar" | ||
| 299 | :expected-point 2 | ||
| 300 | :test-in-strings nil | ||
| 301 | :bindings `((electric-pair-text-syntax-table | ||
| 302 | . ,prog-mode-syntax-table))) | ||
| 303 | |||
| 304 | |||
| 305 | ;;; More quotes, but now don't bind `electric-pair-text-syntax-table' | ||
| 306 | ;;; to `prog-mode-syntax-table'. Use the defaults for | ||
| 307 | ;;; `electric-pair-pairs' and `electric-pair-text-pairs'. | ||
| 308 | ;;; | ||
| 309 | (define-electric-pair-test pairing-skipping-quotes-in-code | ||
| 310 | " \"\" " "-\"\"-----" :skip-pair-string "-ps------" | ||
| 311 | :test-in-strings nil | ||
| 312 | :test-in-comments nil) | ||
| 313 | |||
| 314 | (define-electric-pair-test skipping-quotes-in-comments | ||
| 315 | " \"\" " "--\"-----" :skip-pair-string "--s------" | ||
| 316 | :test-in-strings nil) | ||
| 317 | |||
| 318 | |||
| 319 | ;;; Skipping over whitespace | ||
| 320 | ;;; | ||
| 321 | (define-electric-pair-test whitespace-jumping | ||
| 322 | " ( ) " "--))))---" :expected-string " ( ) " :expected-point 8 | ||
| 323 | :bindings '((electric-pair-skip-whitespace . t))) | ||
| 324 | |||
| 325 | (define-electric-pair-test whitespace-chomping | ||
| 326 | " ( ) " "--)------" :expected-string " () " :expected-point 4 | ||
| 327 | :bindings '((electric-pair-skip-whitespace . chomp))) | ||
| 328 | |||
| 329 | (define-electric-pair-test whitespace-chomping-2 | ||
| 330 | " ( \n\t\t\n ) " "--)------" :expected-string " () " :expected-point 4 | ||
| 331 | :bindings '((electric-pair-skip-whitespace . chomp)) | ||
| 332 | :test-in-comments nil) | ||
| 333 | |||
| 334 | (define-electric-pair-test whitespace-chomping-dont-cross-comments | ||
| 335 | " ( \n\t\t\n ) " "--)------" :expected-string " () \n\t\t\n ) " | ||
| 336 | :expected-point 4 | ||
| 337 | :bindings '((electric-pair-skip-whitespace . chomp)) | ||
| 338 | :test-in-strings nil | ||
| 339 | :test-in-code nil | ||
| 340 | :test-in-comments t) | ||
| 341 | |||
| 342 | |||
| 343 | ;;; Pairing arbitrary characters | ||
| 344 | ;;; | ||
| 345 | (define-electric-pair-test angle-brackets-everywhere | ||
| 346 | "<>" "<>" :skip-pair-string "ps" | ||
| 347 | :bindings '((electric-pair-pairs . ((?\< . ?\>))))) | ||
| 348 | |||
| 349 | (define-electric-pair-test angle-brackets-everywhere-2 | ||
| 350 | "(<>" "-<>" :skip-pair-string "-ps" | ||
| 351 | :bindings '((electric-pair-pairs . ((?\< . ?\>))))) | ||
| 352 | |||
| 353 | (defvar electric-pair-test-angle-brackets-table | ||
| 354 | (let ((table (make-syntax-table prog-mode-syntax-table))) | ||
| 355 | (modify-syntax-entry ?\< "(>" table) | ||
| 356 | (modify-syntax-entry ?\> ")<`" table) | ||
| 357 | table)) | ||
| 358 | |||
| 359 | (define-electric-pair-test angle-brackets-pair | ||
| 360 | "<>" "<" :expected-string "<><>" :expected-point 2 | ||
| 361 | :test-in-code nil | ||
| 362 | :bindings `((electric-pair-text-syntax-table | ||
| 363 | . ,electric-pair-test-angle-brackets-table))) | ||
| 364 | |||
| 365 | (define-electric-pair-test angle-brackets-skip | ||
| 366 | "<>" "->" :expected-string "<>" :expected-point 3 | ||
| 367 | :test-in-code nil | ||
| 368 | :bindings `((electric-pair-text-syntax-table | ||
| 369 | . ,electric-pair-test-angle-brackets-table))) | ||
| 370 | |||
| 371 | (define-electric-pair-test pair-backtick-and-quote-in-comments | ||
| 372 | ";; " "---`" :expected-string ";; `'" :expected-point 5 | ||
| 373 | :test-in-comments nil | ||
| 374 | :test-in-strings nil | ||
| 375 | :modes '(emacs-lisp-mode) | ||
| 376 | :bindings '((electric-pair-text-pairs . ((?\` . ?\'))))) | ||
| 377 | |||
| 378 | (define-electric-pair-test skip-backtick-and-quote-in-comments | ||
| 379 | ";; `foo'" "-------'" :expected-string ";; `foo'" :expected-point 9 | ||
| 380 | :test-in-comments nil | ||
| 381 | :test-in-strings nil | ||
| 382 | :modes '(emacs-lisp-mode) | ||
| 383 | :bindings '((electric-pair-text-pairs . ((?\` . ?\'))))) | ||
| 384 | |||
| 385 | (define-electric-pair-test pair-backtick-and-quote-in-strings | ||
| 386 | "\"\"" "-`" :expected-string "\"`'\"" :expected-point 3 | ||
| 387 | :test-in-comments nil | ||
| 388 | :test-in-strings nil | ||
| 389 | :modes '(emacs-lisp-mode) | ||
| 390 | :bindings '((electric-pair-text-pairs . ((?\` . ?\'))))) | ||
| 391 | |||
| 392 | (define-electric-pair-test skip-backtick-and-quote-in-strings | ||
| 393 | "\"`'\"" "--'" :expected-string "\"`'\"" :expected-point 4 | ||
| 394 | :test-in-comments nil | ||
| 395 | :test-in-strings nil | ||
| 396 | :modes '(emacs-lisp-mode) | ||
| 397 | :bindings '((electric-pair-text-pairs . ((?\` . ?\'))))) | ||
| 398 | |||
| 399 | (define-electric-pair-test skip-backtick-and-quote-in-strings-2 | ||
| 400 | " \"`'\"" "----'" :expected-string " \"`'\"" :expected-point 6 | ||
| 401 | :test-in-comments nil | ||
| 402 | :test-in-strings nil | ||
| 403 | :modes '(emacs-lisp-mode) | ||
| 404 | :bindings '((electric-pair-text-pairs . ((?\` . ?\'))))) | ||
| 405 | |||
| 406 | |||
| 407 | ;;; `js-mode' has `electric-layout-rules' for '{ and '} | ||
| 408 | ;;; | ||
| 409 | (define-electric-pair-test js-mode-braces | ||
| 410 | "" "{" :expected-string "{}" :expected-point 2 | ||
| 411 | :modes '(js-mode) | ||
| 412 | :fixture-fn #'(lambda () | ||
| 413 | (electric-pair-mode 1))) | ||
| 414 | |||
| 415 | (define-electric-pair-test js-mode-braces-with-layout | ||
| 416 | "" "{" :expected-string "{\n\n}" :expected-point 3 | ||
| 417 | :modes '(js-mode) | ||
| 418 | :test-in-comments nil | ||
| 419 | :test-in-strings nil | ||
| 420 | :fixture-fn #'(lambda () | ||
| 421 | (electric-layout-mode 1) | ||
| 422 | (electric-pair-mode 1))) | ||
| 423 | |||
| 424 | (define-electric-pair-test js-mode-braces-with-layout-and-indent | ||
| 425 | "" "{" :expected-string "{\n \n}" :expected-point 7 | ||
| 426 | :modes '(js-mode) | ||
| 427 | :test-in-comments nil | ||
| 428 | :test-in-strings nil | ||
| 429 | :fixture-fn #'(lambda () | ||
| 430 | (electric-pair-mode 1) | ||
| 431 | (electric-indent-mode 1) | ||
| 432 | (electric-layout-mode 1))) | ||
| 433 | |||
| 434 | |||
| 435 | ;;; Backspacing | ||
| 436 | ;;; TODO: better tests | ||
| 437 | ;;; | ||
| 438 | (ert-deftest electric-pair-backspace-1 () | ||
| 439 | (save-electric-modes | ||
| 440 | (with-temp-buffer | ||
| 441 | (insert "()") | ||
| 442 | (goto-char 2) | ||
| 443 | (electric-pair-backward-delete-char 1) | ||
| 444 | (should (equal "" (buffer-string)))))) | ||
| 445 | |||
| 446 | |||
| 447 | ;;; Electric newlines between pairs | ||
| 448 | ;;; TODO: better tests | ||
| 449 | (ert-deftest electric-pair-open-extra-newline () | ||
| 450 | (save-electric-modes | ||
| 451 | (with-temp-buffer | ||
| 452 | (c-mode) | ||
| 453 | (electric-pair-mode 1) | ||
| 454 | (electric-indent-mode 1) | ||
| 455 | (insert "int main {}") | ||
| 456 | (backward-char 1) | ||
| 457 | (let ((c-basic-offset 4)) | ||
| 458 | (newline 1 t) | ||
| 459 | (should (equal "int main {\n \n}" | ||
| 460 | (buffer-string))) | ||
| 461 | (should (equal (point) (- (point-max) 2))))))) | ||
| 462 | |||
| 463 | |||
| 464 | |||
| 465 | ;;; Autowrapping | ||
| 466 | ;;; | ||
| 467 | (define-electric-pair-test autowrapping-1 | ||
| 468 | "foo" "(" :expected-string "(foo)" :expected-point 2 | ||
| 469 | :fixture-fn #'(lambda () | ||
| 470 | (electric-pair-mode 1) | ||
| 471 | (mark-sexp 1))) | ||
| 472 | |||
| 473 | (define-electric-pair-test autowrapping-2 | ||
| 474 | "foo" ")" :expected-string "(foo)" :expected-point 6 | ||
| 475 | :fixture-fn #'(lambda () | ||
| 476 | (electric-pair-mode 1) | ||
| 477 | (mark-sexp 1))) | ||
| 478 | |||
| 479 | (define-electric-pair-test autowrapping-3 | ||
| 480 | "foo" ")" :expected-string "(foo)" :expected-point 6 | ||
| 481 | :fixture-fn #'(lambda () | ||
| 482 | (electric-pair-mode 1) | ||
| 483 | (goto-char (point-max)) | ||
| 484 | (skip-chars-backward "\"") | ||
| 485 | (mark-sexp -1))) | ||
| 486 | |||
| 487 | (define-electric-pair-test autowrapping-4 | ||
| 488 | "foo" "(" :expected-string "(foo)" :expected-point 2 | ||
| 489 | :fixture-fn #'(lambda () | ||
| 490 | (electric-pair-mode 1) | ||
| 491 | (goto-char (point-max)) | ||
| 492 | (skip-chars-backward "\"") | ||
| 493 | (mark-sexp -1))) | ||
| 494 | |||
| 495 | (define-electric-pair-test autowrapping-5 | ||
| 496 | "foo" "\"" :expected-string "\"foo\"" :expected-point 2 | ||
| 497 | :fixture-fn #'(lambda () | ||
| 498 | (electric-pair-mode 1) | ||
| 499 | (mark-sexp 1))) | ||
| 500 | |||
| 501 | (define-electric-pair-test autowrapping-6 | ||
| 502 | "foo" "\"" :expected-string "\"foo\"" :expected-point 6 | ||
| 503 | :fixture-fn #'(lambda () | ||
| 504 | (electric-pair-mode 1) | ||
| 505 | (goto-char (point-max)) | ||
| 506 | (skip-chars-backward "\"") | ||
| 507 | (mark-sexp -1))) | ||
| 508 | |||
| 509 | (provide 'electric-tests) | ||
| 510 | ;;; electric-tests.el ends here | ||