From d8abff398bc45a791783c5c463838ba6fa3f030e Mon Sep 17 00:00:00 2001 From: lWarne Date: Sat, 6 Aug 2022 15:01:38 +0200 Subject: Fontify python escape sequences in literals * lisp/progmodes/python.el (python-rx): Add regular expressions matching escape codes in string and byte literals (python--string-bytes-literal-matcher): new function (python--not-raw-bytes-literal-start-regexp): new constant (python--not-raw-string-literal-start-regexp): new constant * test/lisp/progmodes/python-tests.el: Add tests for new fontification (bug#57004). --- lisp/progmodes/python.el | 56 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'lisp/progmodes/python.el') diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index b8fc7d4c546..27bdbae3113 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -427,7 +427,19 @@ This variant of `rx' supports common Python named REGEXPS." (: "vim:" (* space) "set" (+ space) "fileencoding" (* space) ?= (* space) (group-n 1 (+ (or word ?-))) - (* space) ":"))))) + (* space) ":")))) + (bytes-escape-sequence + (seq (not "\\") + (group (or "\\\\" "\\'" "\\a" "\\b" "\\f" + "\\n" "\\r" "\\t" "\\v" + (seq "\\" (= 3 (in "0-7"))) + (seq "\\x" hex hex))))) + (string-escape-sequence + (or bytes-escape-sequence + (seq (not "\\") + (or (group-n 1 "\\u" (= 4 hex)) + (group-n 1 "\\U" (= 8 hex)) + (group-n 1 "\\N{" (*? anychar) "}")))))) (rx ,@regexps))) @@ -539,6 +551,29 @@ the {...} holes that appear within f-strings." (goto-char (min limit (1+ send))) (setq ppss (syntax-ppss)))))) +(defconst python--not-raw-bytes-literal-start-regexp + (rx (or bos (not alnum)) (or "b" "B") (or "\"" "\"\"\"" "'" "'''") eos) + "A regular expression matching the start of a not-raw bytes literal.") + +(defconst python--not-raw-string-literal-start-regexp + (rx (or bos (not alnum)) (? (or "u" "U" "F" "f")) (or "\"" "\"\"\"" "'" "'''") eos) + "A regular expression matching the start of a not-raw string literal.") + +(defun python--string-bytes-literal-matcher (regexp start-regexp) + "Match REGEXP within a string or bytes literal whose start matches START-REGEXP." + (lambda (limit) + (cl-loop for result = (re-search-forward regexp limit t) + for result-valid = (and + result + (let* ((pos (nth 8 (syntax-ppss))) + (before-quote + (buffer-substring-no-properties + (max (- pos 5) (point-min)) + (min (+ pos 1) (point-max))))) + (string-match-p start-regexp before-quote))) + until (or (not result) result-valid) + finally return (and result-valid result)))) + (defvar python-font-lock-keywords-level-1 `((,(python-rx symbol-start "def" (1+ space) (group symbol-name)) (1 font-lock-function-name-face)) @@ -716,7 +751,24 @@ sign in chained assignment." grouped-assignment-target (* space) (or ")" "]") (* space) assignment-operator)) - (1 font-lock-variable-name-face))) + (1 font-lock-variable-name-face)) + ;; escape sequences within bytes literals + ;; "\\" "\'" "\a" "\b" "\f" "\n" "\r" "\t" "\v" + ;; "\ooo" character with octal value ooo + ;; "\xhh" character with hex value hh + (,(python--string-bytes-literal-matcher + (python-rx bytes-escape-sequence) + python--not-raw-bytes-literal-start-regexp) + (1 font-lock-constant-face t)) + ;; escape sequences within string literals, the same as appear in bytes + ;; literals in addition to: + ;; "\uxxxx" Character with 16-bit hex value xxxx + ;; "\Uxxxxxxxx" Character with 32-bit hex value xxxxxxxx + ;; "\N{name}" Character named name in the Unicode database + (,(python--string-bytes-literal-matcher + (python-rx string-escape-sequence) + python--not-raw-string-literal-start-regexp) + (1 'font-lock-constant-face t))) "Font lock keywords to use in `python-mode' for maximum decoration. This decoration level includes everything in -- cgit v1.2.1