aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkobarity2022-08-17 13:10:16 +0200
committerLars Ingebrigtsen2022-08-17 13:10:16 +0200
commit4915ca5dd4245a909c046e6691e8d4a1919890c8 (patch)
treea48242b42ff526acd35579ff0596e0add4887949
parent31e32212670f5774a6dbc0debac8854fa01d8f92 (diff)
downloademacs-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.el48
-rw-r--r--test/lisp/progmodes/python-tests.el113
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.
360This variant of `rx' supports common Python named REGEXPS." 360This 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.")
796Which one will be chosen depends on the value of 798Which 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.
113All 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'