diff options
| author | kobarity | 2022-08-17 13:10:16 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-08-17 13:10:16 +0200 |
| commit | 4915ca5dd4245a909c046e6691e8d4a1919890c8 (patch) | |
| tree | a48242b42ff526acd35579ff0596e0add4887949 | |
| parent | 31e32212670f5774a6dbc0debac8854fa01d8f92 (diff) | |
| download | emacs-4915ca5dd4245a909c046e6691e8d4a1919890c8.tar.gz emacs-4915ca5dd4245a909c046e6691e8d4a1919890c8.zip | |
Enhance Python font-lock to support multilines
* test/lisp/progmodes/python-tests.el
(python-tests-assert-faces-after-change): New helper function.
(python-font-lock-keywords-level-1-3)
(python-font-lock-assignment-statement-multiline-*): New tests.
* lisp/progmodes/python.el (python-rx): Add `sp-nl' to represent
space or newline (with/without backslash).
(python-font-lock-keywords-level-1)
(python-font-lock-keywords-maximum-decoration): Allow newlines
where appropriate.
(python-font-lock-extend-region): New function.
(python-mode): Set `python-font-lock-extend-region' to
`font-lock-extend-after-change-region-function'.
| -rw-r--r-- | lisp/progmodes/python.el | 48 | ||||
| -rw-r--r-- | test/lisp/progmodes/python-tests.el | 113 |
2 files changed, 145 insertions, 16 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 44df3186b27..e1350391994 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -359,6 +359,7 @@ | |||
| 359 | "Python mode specialized rx macro. | 359 | "Python mode specialized rx macro. |
| 360 | This variant of `rx' supports common Python named REGEXPS." | 360 | This variant of `rx' supports common Python named REGEXPS." |
| 361 | `(rx-let ((sp-bsnl (or space (and ?\\ ?\n))) | 361 | `(rx-let ((sp-bsnl (or space (and ?\\ ?\n))) |
| 362 | (sp-nl (or space (and (? ?\\) ?\n))) | ||
| 362 | (block-start (seq symbol-start | 363 | (block-start (seq symbol-start |
| 363 | (or "def" "class" "if" "elif" "else" "try" | 364 | (or "def" "class" "if" "elif" "else" "try" |
| 364 | "except" "finally" "for" "while" "with" | 365 | "except" "finally" "for" "while" "with" |
| @@ -583,9 +584,9 @@ the {...} holes that appear within f-strings." | |||
| 583 | finally return (and result-valid result)))) | 584 | finally return (and result-valid result)))) |
| 584 | 585 | ||
| 585 | (defvar python-font-lock-keywords-level-1 | 586 | (defvar python-font-lock-keywords-level-1 |
| 586 | `((,(python-rx symbol-start "def" (1+ space) (group symbol-name)) | 587 | `((,(python-rx symbol-start "def" (1+ sp-bsnl) (group symbol-name)) |
| 587 | (1 font-lock-function-name-face)) | 588 | (1 font-lock-function-name-face)) |
| 588 | (,(python-rx symbol-start "class" (1+ space) (group symbol-name)) | 589 | (,(python-rx symbol-start "class" (1+ sp-bsnl) (group symbol-name)) |
| 589 | (1 font-lock-type-face))) | 590 | (1 font-lock-type-face))) |
| 590 | "Font lock keywords to use in `python-mode' for level 1 decoration. | 591 | "Font lock keywords to use in `python-mode' for level 1 decoration. |
| 591 | 592 | ||
| @@ -725,12 +726,12 @@ sign in chained assignment." | |||
| 725 | ;; [*a] = 5, 6 | 726 | ;; [*a] = 5, 6 |
| 726 | ;; are handled separately below | 727 | ;; are handled separately below |
| 727 | (,(python-font-lock-assignment-matcher | 728 | (,(python-font-lock-assignment-matcher |
| 728 | (python-rx (? (or "[" "(") (* space)) | 729 | (python-rx (? (or "[" "(") (* sp-nl)) |
| 729 | grouped-assignment-target (* space) ?, (* space) | 730 | grouped-assignment-target (* sp-nl) ?, (* sp-nl) |
| 730 | (* assignment-target (* space) ?, (* space)) | 731 | (* assignment-target (* sp-nl) ?, (* sp-nl)) |
| 731 | (? assignment-target (* space)) | 732 | (? assignment-target (* sp-nl)) |
| 732 | (? ?, (* space)) | 733 | (? ?, (* sp-nl)) |
| 733 | (? (or ")" "]") (* space)) | 734 | (? (or ")" "]") (* sp-bsnl)) |
| 734 | (group assignment-operator))) | 735 | (group assignment-operator))) |
| 735 | (1 font-lock-variable-name-face) | 736 | (1 font-lock-variable-name-face) |
| 736 | (,(python-rx grouped-assignment-target) | 737 | (,(python-rx grouped-assignment-target) |
| @@ -745,19 +746,20 @@ sign in chained assignment." | |||
| 745 | ;; c: Collection = {1, 2, 3} | 746 | ;; c: Collection = {1, 2, 3} |
| 746 | ;; d: Mapping[int, str] = {1: 'bar', 2: 'baz'} | 747 | ;; d: Mapping[int, str] = {1: 'bar', 2: 'baz'} |
| 747 | (,(python-font-lock-assignment-matcher | 748 | (,(python-font-lock-assignment-matcher |
| 748 | (python-rx grouped-assignment-target (* space) | 749 | (python-rx (or line-start ?\;) (* sp-bsnl) |
| 749 | (? ?: (* space) (+ not-simple-operator) (* space)) | 750 | grouped-assignment-target (* sp-bsnl) |
| 750 | assignment-operator)) | 751 | (? ?: (* sp-bsnl) (+ not-simple-operator) (* sp-bsnl)) |
| 752 | assignment-operator)) | ||
| 751 | (1 font-lock-variable-name-face)) | 753 | (1 font-lock-variable-name-face)) |
| 752 | ;; special cases | 754 | ;; special cases |
| 753 | ;; (a) = 5 | 755 | ;; (a) = 5 |
| 754 | ;; [a] = 5, | 756 | ;; [a] = 5, |
| 755 | ;; [*a] = 5, 6 | 757 | ;; [*a] = 5, 6 |
| 756 | (,(python-font-lock-assignment-matcher | 758 | (,(python-font-lock-assignment-matcher |
| 757 | (python-rx (or line-start ?\; ?=) (* space) | 759 | (python-rx (or line-start ?\; ?=) (* sp-bsnl) |
| 758 | (or "[" "(") (* space) | 760 | (or "[" "(") (* sp-nl) |
| 759 | grouped-assignment-target (* space) | 761 | grouped-assignment-target (* sp-nl) |
| 760 | (or ")" "]") (* space) | 762 | (or ")" "]") (* sp-bsnl) |
| 761 | assignment-operator)) | 763 | assignment-operator)) |
| 762 | (1 font-lock-variable-name-face)) | 764 | (1 font-lock-variable-name-face)) |
| 763 | ;; escape sequences within bytes literals | 765 | ;; escape sequences within bytes literals |
| @@ -796,6 +798,18 @@ decorators, exceptions, and assignments.") | |||
| 796 | Which one will be chosen depends on the value of | 798 | Which one will be chosen depends on the value of |
| 797 | `font-lock-maximum-decoration'.") | 799 | `font-lock-maximum-decoration'.") |
| 798 | 800 | ||
| 801 | (defun python-font-lock-extend-region (beg end _old-len) | ||
| 802 | "Extend font-lock region given by BEG and END to statement boundaries." | ||
| 803 | (save-excursion | ||
| 804 | (save-match-data | ||
| 805 | (goto-char beg) | ||
| 806 | (python-nav-beginning-of-statement) | ||
| 807 | (setq beg (point)) | ||
| 808 | (goto-char end) | ||
| 809 | (python-nav-end-of-statement) | ||
| 810 | (setq end (point)) | ||
| 811 | (cons beg end)))) | ||
| 812 | |||
| 799 | 813 | ||
| 800 | (defconst python-syntax-propertize-function | 814 | (defconst python-syntax-propertize-function |
| 801 | (syntax-propertize-rules | 815 | (syntax-propertize-rules |
| @@ -5780,7 +5794,9 @@ REPORT-FN is Flymake's callback function." | |||
| 5780 | `(,python-font-lock-keywords | 5794 | `(,python-font-lock-keywords |
| 5781 | nil nil nil nil | 5795 | nil nil nil nil |
| 5782 | (font-lock-syntactic-face-function | 5796 | (font-lock-syntactic-face-function |
| 5783 | . python-font-lock-syntactic-face-function))) | 5797 | . python-font-lock-syntactic-face-function) |
| 5798 | (font-lock-extend-after-change-region-function | ||
| 5799 | . python-font-lock-extend-region))) | ||
| 5784 | 5800 | ||
| 5785 | (setq-local syntax-propertize-function | 5801 | (setq-local syntax-propertize-function |
| 5786 | python-syntax-propertize-function) | 5802 | python-syntax-propertize-function) |
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index 9e8fa7f5520..875c92573ef 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el | |||
| @@ -108,6 +108,20 @@ STRING, it is skipped so the next STRING occurrence is selected." | |||
| 108 | while pos | 108 | while pos |
| 109 | collect (cons pos (get-text-property pos 'face)))) | 109 | collect (cons pos (get-text-property pos 'face)))) |
| 110 | 110 | ||
| 111 | (defun python-tests-assert-faces-after-change (content faces search replace) | ||
| 112 | "Assert that font faces for CONTENT are equal to FACES after change. | ||
| 113 | All occurrences of SEARCH are changed to REPLACE." | ||
| 114 | (python-tests-with-temp-buffer | ||
| 115 | content | ||
| 116 | ;; Force enable font-lock mode without jit-lock. | ||
| 117 | (rename-buffer "*python-font-lock-test*" t) | ||
| 118 | (let (noninteractive font-lock-support-mode) | ||
| 119 | (font-lock-mode)) | ||
| 120 | (while | ||
| 121 | (re-search-forward search nil t) | ||
| 122 | (replace-match replace)) | ||
| 123 | (should (equal faces (python-tests-get-buffer-faces))))) | ||
| 124 | |||
| 111 | (defun python-tests-self-insert (char-or-str) | 125 | (defun python-tests-self-insert (char-or-str) |
| 112 | "Call `self-insert-command' for chars in CHAR-OR-STR." | 126 | "Call `self-insert-command' for chars in CHAR-OR-STR." |
| 113 | (let ((chars | 127 | (let ((chars |
| @@ -226,6 +240,13 @@ aliqua." | |||
| 226 | "def 1func():" | 240 | "def 1func():" |
| 227 | '((1 . font-lock-keyword-face) (4)))) | 241 | '((1 . font-lock-keyword-face) (4)))) |
| 228 | 242 | ||
| 243 | (ert-deftest python-font-lock-keywords-level-1-3 () | ||
| 244 | (python-tests-assert-faces | ||
| 245 | "def \\ | ||
| 246 | func():" | ||
| 247 | '((1 . font-lock-keyword-face) (4) | ||
| 248 | (15 . font-lock-function-name-face) (19)))) | ||
| 249 | |||
| 229 | (ert-deftest python-font-lock-assignment-statement-1 () | 250 | (ert-deftest python-font-lock-assignment-statement-1 () |
| 230 | (python-tests-assert-faces | 251 | (python-tests-assert-faces |
| 231 | "a, b, c = 1, 2, 3" | 252 | "a, b, c = 1, 2, 3" |
| @@ -380,6 +401,98 @@ def f(x: CustomInt) -> CustomInt: | |||
| 380 | (128 . font-lock-builtin-face) (131) | 401 | (128 . font-lock-builtin-face) (131) |
| 381 | (144 . font-lock-keyword-face) (150)))) | 402 | (144 . font-lock-keyword-face) (150)))) |
| 382 | 403 | ||
| 404 | (ert-deftest python-font-lock-assignment-statement-multiline-1 () | ||
| 405 | (python-tests-assert-faces-after-change | ||
| 406 | " | ||
| 407 | [ | ||
| 408 | a, | ||
| 409 | b | ||
| 410 | ] # ( | ||
| 411 | 1, | ||
| 412 | 2 | ||
| 413 | ) | ||
| 414 | " | ||
| 415 | '((1) | ||
| 416 | (8 . font-lock-variable-name-face) (9) | ||
| 417 | (15 . font-lock-variable-name-face) (16)) | ||
| 418 | "#" "=")) | ||
| 419 | |||
| 420 | (ert-deftest python-font-lock-assignment-statement-multiline-2 () | ||
| 421 | (python-tests-assert-faces-after-change | ||
| 422 | " | ||
| 423 | [ | ||
| 424 | *a | ||
| 425 | ] # 5, 6 | ||
| 426 | " | ||
| 427 | '((1) | ||
| 428 | (9 . font-lock-variable-name-face) (10)) | ||
| 429 | "#" "=")) | ||
| 430 | |||
| 431 | (ert-deftest python-font-lock-assignment-statement-multiline-3 () | ||
| 432 | (python-tests-assert-faces-after-change | ||
| 433 | "a\\ | ||
| 434 | ,\\ | ||
| 435 | b\\ | ||
| 436 | ,\\ | ||
| 437 | c\\ | ||
| 438 | #\\ | ||
| 439 | 1\\ | ||
| 440 | ,\\ | ||
| 441 | 2\\ | ||
| 442 | ,\\ | ||
| 443 | 3" | ||
| 444 | '((1 . font-lock-variable-name-face) (2) | ||
| 445 | (15 . font-lock-variable-name-face) (16) | ||
| 446 | (29 . font-lock-variable-name-face) (30)) | ||
| 447 | "#" "=")) | ||
| 448 | |||
| 449 | (ert-deftest python-font-lock-assignment-statement-multiline-4 () | ||
| 450 | (python-tests-assert-faces-after-change | ||
| 451 | "a\\ | ||
| 452 | :\\ | ||
| 453 | int\\ | ||
| 454 | #\\ | ||
| 455 | 5" | ||
| 456 | '((1 . font-lock-variable-name-face) (2) | ||
| 457 | (15 . font-lock-builtin-face) (18)) | ||
| 458 | "#" "=")) | ||
| 459 | |||
| 460 | (ert-deftest python-font-lock-assignment-statement-multiline-5 () | ||
| 461 | (python-tests-assert-faces-after-change | ||
| 462 | "(\\ | ||
| 463 | a\\ | ||
| 464 | )\\ | ||
| 465 | #\\ | ||
| 466 | 5\\ | ||
| 467 | ;\\ | ||
| 468 | (\\ | ||
| 469 | b\\ | ||
| 470 | )\\ | ||
| 471 | #\\ | ||
| 472 | 6" | ||
| 473 | '((1) | ||
| 474 | (8 . font-lock-variable-name-face) (9) | ||
| 475 | (46 . font-lock-variable-name-face) (47)) | ||
| 476 | "#" "=")) | ||
| 477 | |||
| 478 | (ert-deftest python-font-lock-assignment-statement-multiline-6 () | ||
| 479 | (python-tests-assert-faces-after-change | ||
| 480 | "( | ||
| 481 | a | ||
| 482 | )\\ | ||
| 483 | #\\ | ||
| 484 | 5\\ | ||
| 485 | ;\\ | ||
| 486 | ( | ||
| 487 | b | ||
| 488 | )\\ | ||
| 489 | #\\ | ||
| 490 | 6" | ||
| 491 | '((1) | ||
| 492 | (7 . font-lock-variable-name-face) (8) | ||
| 493 | (43 . font-lock-variable-name-face) (44)) | ||
| 494 | "#" "=")) | ||
| 495 | |||
| 383 | (ert-deftest python-font-lock-escape-sequence-string-newline () | 496 | (ert-deftest python-font-lock-escape-sequence-string-newline () |
| 384 | (python-tests-assert-faces | 497 | (python-tests-assert-faces |
| 385 | "'\\n' | 498 | "'\\n' |