From c44908c0593a2c3aab6468144643ccd003084afa Mon Sep 17 00:00:00 2001 From: kobarity Date: Mon, 16 May 2022 15:40:17 +0200 Subject: [PATCH] Fix Python highlighting of some assignment statements * lisp/progmodes/python.el (python-rx): Limit not-simple-operator to a single line (bug#51362). --- lisp/progmodes/python.el | 18 ++-- test/lisp/progmodes/python-tests.el | 160 ++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 9 deletions(-) diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index cb4be10f5cc..ca744b1cfde 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -394,7 +394,7 @@ This variant of `rx' supports common Python named REGEXPS." (open-paren (or "{" "[" "(")) (close-paren (or "}" "]" ")")) (simple-operator (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)) - (not-simple-operator (not simple-operator)) + (not-simple-operator (not (or simple-operator ?\n))) (operator (or "==" ">=" "is" "not" "**" "//" "<<" ">>" "<=" "!=" "+" "-" "/" "&" "^" "~" "|" "*" "<" ">" @@ -603,15 +603,15 @@ builtins.") (defun python-font-lock-assignment-matcher (regexp) "Font lock matcher for assignments based on REGEXP. -Return nil if REGEXP matched within a `paren' context (to avoid, -e.g., default values for arguments or passing arguments by name -being treated as assignments) or is followed by an '=' sign (to -avoid '==' being treated as an assignment." +Search for next occurrence if REGEXP matched within a `paren' +context (to avoid, e.g., default values for arguments or passing +arguments by name being treated as assignments) or is followed by +an '=' sign (to avoid '==' being treated as an assignment." (lambda (limit) - (let ((res (re-search-forward regexp limit t))) - (unless (or (python-syntax-context 'paren) - (equal (char-after (point)) ?=)) - res)))) + (cl-loop while (re-search-forward regexp limit t) + unless (or (python-syntax-context 'paren) + (equal (char-after) ?=)) + return t))) (defvar python-font-lock-keywords-maximum-decoration `((python--font-lock-f-strings) diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index 01b233cc425..98b55a5f8bc 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -95,6 +95,19 @@ STRING, it is skipped so the next STRING occurrence is selected." found-point (and restore-point (goto-char starting-point))))) +(defun python-tests-assert-faces (content faces) + "Assert that font faces for CONTENT are equal to FACES." + (python-tests-with-temp-buffer content + (font-lock-ensure nil nil) + (should (equal faces (python-tests-get-buffer-faces))))) + +(defun python-tests-get-buffer-faces () + "Return a list of (position . face) for each face change positions." + (cl-loop for pos = (point-min) + then (next-single-property-change pos 'face) + while pos + collect (cons pos (get-text-property pos 'face)))) + (defun python-tests-self-insert (char-or-str) "Call `self-insert-command' for chars in CHAR-OR-STR." (let ((chars @@ -201,6 +214,153 @@ aliqua." (should (string= (buffer-string) "\"\"")) (should (null (nth 3 (syntax-ppss)))))) +(ert-deftest python-font-lock-assignment-statement-1 () + (python-tests-assert-faces + "a, b, c = 1, 2, 3" + '((1 . font-lock-variable-name-face) (2) + (4 . font-lock-variable-name-face) (5) + (7 . font-lock-variable-name-face) (8)))) + +(ert-deftest python-font-lock-assignment-statement-2 () + (python-tests-assert-faces + "a, *b, c = 1, 2, 3, 4, 5" + '((1 . font-lock-variable-name-face) (2) + (5 . font-lock-variable-name-face) (6) + (8 . font-lock-variable-name-face) (9)))) + +(ert-deftest python-font-lock-assignment-statement-3 () + (python-tests-assert-faces + "[a, b] = (1, 2)" + '((1) + (2 . font-lock-variable-name-face) (3) + (5 . font-lock-variable-name-face) (6)))) + +(ert-deftest python-font-lock-assignment-statement-4 () + (python-tests-assert-faces + "(l[1], l[2]) = (10, 11)" + '((1) + (2 . font-lock-variable-name-face) (3) + (8 . font-lock-variable-name-face) (9)))) + +(ert-deftest python-font-lock-assignment-statement-5 () + (python-tests-assert-faces + "(a, b, c, *d) = *x, y = 5, 6, 7, 8, 9" + '((1) + (2 . font-lock-variable-name-face) (3) + (5 . font-lock-variable-name-face) (6) + (8 . font-lock-variable-name-face) (9) + (12 . font-lock-variable-name-face) (13) + (18 . font-lock-variable-name-face) (19) + (21 . font-lock-variable-name-face) (22)))) + +(ert-deftest python-font-lock-assignment-statement-6 () + (python-tests-assert-faces + "(a,) = 'foo'," + '((1) + (2 . font-lock-variable-name-face) (3) + (8 . font-lock-string-face) (13)))) + +(ert-deftest python-font-lock-assignment-statement-7 () + (python-tests-assert-faces + "(*a,) = ['foo', 'bar', 'baz']" + '((1) + (3 . font-lock-variable-name-face) (4) + (10 . font-lock-string-face) (15) + (17 . font-lock-string-face) (22) + (24 . font-lock-string-face) (29)))) + +(ert-deftest python-font-lock-assignment-statement-8 () + (python-tests-assert-faces + "d = D('a', ['b'], 'c')" + '((1 . font-lock-variable-name-face) (2) + (7 . font-lock-string-face) (10) + (13 . font-lock-string-face) (16) + (19 . font-lock-string-face) (22)))) + +(ert-deftest python-font-lock-assignment-statement-9 () + (python-tests-assert-faces + "d.x, d.y[0], *d.z = 'a', 'b', 'c', 'd', 'e'" + '((1) + (3 . font-lock-variable-name-face) (4) + (8 . font-lock-variable-name-face) (9) + (17 . font-lock-variable-name-face) (18) + (21 . font-lock-string-face) (24) + (26 . font-lock-string-face) (29) + (31 . font-lock-string-face) (34) + (36 . font-lock-string-face) (39) + (41 . font-lock-string-face)))) + +(ert-deftest python-font-lock-assignment-statement-10 () + (python-tests-assert-faces + "a: int = 5" + '((1 . font-lock-variable-name-face) (2) + (4 . font-lock-builtin-face) (7)))) + +(ert-deftest python-font-lock-assignment-statement-11 () + (python-tests-assert-faces + "b: Tuple[Optional[int], Union[Sequence[str], str]] = (None, 'foo')" + '((1 . font-lock-variable-name-face) (2) + (19 . font-lock-builtin-face) (22) + (40 . font-lock-builtin-face) (43) + (46 . font-lock-builtin-face) (49) + (55 . font-lock-constant-face) (59) + (61 . font-lock-string-face) (66)))) + +(ert-deftest python-font-lock-assignment-statement-12 () + (python-tests-assert-faces + "c: Collection = {1, 2, 3}" + '((1 . font-lock-variable-name-face) (2)))) + +(ert-deftest python-font-lock-assignment-statement-13 () + (python-tests-assert-faces + "d: Mapping[int, str] = {1: 'bar', 2: 'baz'}" + '((1 . font-lock-variable-name-face) (2) + (12 . font-lock-builtin-face) (15) + (17 . font-lock-builtin-face) (20) + (28 . font-lock-string-face) (33) + (38 . font-lock-string-face) (43)))) + +(ert-deftest python-font-lock-assignment-statement-14 () + (python-tests-assert-faces + "(a) = 5; (b) = 6" + '((1) + (2 . font-lock-variable-name-face) (3) + (11 . font-lock-variable-name-face) (12)))) + +(ert-deftest python-font-lock-assignment-statement-15 () + (python-tests-assert-faces + "[a] = 5,; [b] = 6," + '((1) + (2 . font-lock-variable-name-face) (3) + (12 . font-lock-variable-name-face) (13)))) + +(ert-deftest python-font-lock-assignment-statement-16 () + (python-tests-assert-faces + "[*a] = 5, 6; [*b] = 7, 8" + '((1) + (3 . font-lock-variable-name-face) (4) + (16 . font-lock-variable-name-face) (17)))) + +(ert-deftest python-font-lock-assignment-statement-17 () + (python-tests-assert-faces + "CustomInt = int + +def f(x: CustomInt) -> CustomInt: + y = x + 1 + ys: Sequence[CustomInt] = [y, y + 1] + res: CustomInt = sum(ys) + 1 + return res +" + '((1 . font-lock-variable-name-face) (10) + (13 . font-lock-builtin-face) (16) + (18 . font-lock-keyword-face) (21) + (22 . font-lock-function-name-face) (23) + (56 . font-lock-variable-name-face) (57) + (70 . font-lock-variable-name-face) (72) + (111 . font-lock-variable-name-face) (114) + (128 . font-lock-builtin-face) (131) + (144 . font-lock-keyword-face) (150)))) + ;;; Indentation -- 2.39.2