diff options
-rw-r--r-- | lisp/progmodes/python.el | 35 | ||||
-rw-r--r-- | test/lisp/progmodes/python-tests.el | 119 |
2 files changed, 146 insertions, 8 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 8793fdc6458..2fe88323c35 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -511,19 +511,28 @@ This variant of `rx' supports common Python named REGEXPS." (''string `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) (and (nth 3 ppss) (nth 8 ppss)))) + (''single-quoted-string + `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) + (and (characterp (nth 3 ppss)) (nth 8 ppss)))) + (''triple-quoted-string + `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) + (and (eq t (nth 3 ppss)) (nth 8 ppss)))) (''paren `(nth 1 (or ,syntax-ppss (syntax-ppss)))) (_ form)))) (defun python-syntax-context (type &optional syntax-ppss) "Return non-nil if point is on TYPE using SYNTAX-PPSS. -TYPE can be `comment', `string' or `paren'. It returns the start +TYPE can be `comment', `string', `single-quoted-string', +`triple-quoted-string' or `paren'. It returns the start character address of the specified TYPE." (declare (compiler-macro python-syntax--context-compiler-macro)) (let ((ppss (or syntax-ppss (syntax-ppss)))) (pcase type ('comment (and (nth 4 ppss) (nth 8 ppss))) ('string (and (nth 3 ppss) (nth 8 ppss))) + ('single-quoted-string (and (characterp (nth 3 ppss)) (nth 8 ppss))) + ('triple-quoted-string (and (eq t (nth 3 ppss)) (nth 8 ppss))) ('paren (nth 1 ppss)) (_ nil)))) @@ -4805,9 +4814,7 @@ Optional argument JUSTIFY defines if the paragraph should be justified." ((python-syntax-context 'comment) (funcall python-fill-comment-function justify)) ;; Strings/Docstrings - ((save-excursion (or (python-syntax-context 'string) - (equal (string-to-syntax "|") - (syntax-after (point))))) + ((python-info-triple-quoted-string-p) (funcall python-fill-string-function justify)) ;; Decorators ((equal (char-after (save-excursion @@ -4833,10 +4840,7 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'." (let* ((str-start-pos (set-marker (make-marker) - (or (python-syntax-context 'string) - (and (equal (string-to-syntax "|") - (syntax-after (point))) - (point))))) + (python-info-triple-quoted-string-p))) ;; JT@2021-09-21: Since bug#49518's fix this will always be 1 (num-quotes (python-syntax-count-quotes (char-after str-start-pos) str-start-pos)) @@ -6043,6 +6047,21 @@ point's current `syntax-ppss'." ((python-info-looking-at-beginning-of-defun)) (t nil)))))) +(defun python-info-triple-quoted-string-p () + "Check if point is in a triple quoted string including quotes. +It returns the position of the third quote character of the start +of the string." + (save-excursion + (let ((pos (point))) + (cl-loop + for offset in '(0 3 -2 2 -1 1) + if (let ((check-pos (+ pos offset))) + (and (>= check-pos (point-min)) + (<= check-pos (point-max)) + (python-syntax-context + 'triple-quoted-string (syntax-ppss check-pos)))) + return it)))) + (defun python-info-encoding-from-cookie () "Detect current buffer's encoding from its coding cookie. Returns the encoding as a symbol." diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index e5a9d128bc5..ed4a08da6ab 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -255,6 +255,27 @@ aliqua." ;;; Font-lock and syntax +(ert-deftest python-syntax-context-1 () + (python-tests-with-temp-buffer + " +# Comment +s = 'Single Quoted String' +t = '''Triple Quoted String''' +p = (1 + 2) +" + (python-tests-look-at "Comment") + (should (= (python-syntax-context 'comment) (pos-bol))) + (python-tests-look-at "Single") + (should (= (python-syntax-context 'string) (1- (point)))) + (should (= (python-syntax-context 'single-quoted-string) (1- (point)))) + (should-not (python-syntax-context 'triple-quoted-string)) + (python-tests-look-at "Triple") + (should (= (python-syntax-context 'string) (1- (point)))) + (should-not (python-syntax-context 'single-quoted-string)) + (should (= (python-syntax-context 'triple-quoted-string) (1- (point)))) + (python-tests-look-at "1 + 2") + (should (= (python-syntax-context 'paren) (1- (point)))))) + (ert-deftest python-syntax-after-python-backspace () ;; `python-indent-dedent-line-backspace' garbles syntax (python-tests-with-temp-buffer @@ -2052,6 +2073,54 @@ this is a test this is a test this is a test this is a test this is a test this (fill-paragraph) (should (= (current-indentation) 0)))) +(ert-deftest python-fill-paragraph-single-quoted-string-1 () + "Single quoted string should not be filled." + (let ((contents " +s = 'abc def ghi jkl mno pqr stu vwx yz' +") + (fill-column 20)) + (python-tests-with-temp-buffer + contents + (python-tests-look-at "abc") + (fill-paragraph) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + contents))))) + +(ert-deftest python-fill-paragraph-single-quoted-string-2 () + "Ensure no fill is performed after the end of the single quoted string." + (let ((contents " +s1 = 'abc' +s2 = 'def' +")) + (python-tests-with-temp-buffer + contents + (python-tests-look-at "abc") + (fill-paragraph) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + contents))))) + +(ert-deftest python-fill-paragraph-triple-quoted-string-1 () + "Triple quoted string should be filled." + (let ((contents " +s = '''abc def ghi jkl mno pqr stu vwx yz''' +") + (expected " +s = '''abc def ghi +jkl mno pqr stu vwx +yz''' +") + (fill-column 20)) + (dolist (look-at '("'''abc" "z'''")) + (dolist (offset '(0 1 2 3)) + (python-tests-with-temp-buffer + contents + (python-tests-look-at look-at) + (forward-char offset) + (fill-paragraph) + (should (string= + (buffer-substring-no-properties (point-min) (point-max)) + expected))))))) + ;;; Mark @@ -6491,6 +6560,56 @@ class Class: (python-tests-look-at "'''Not a method docstring.'''") (should (not (python-info-docstring-p))))) +(ert-deftest python-info-triple-quoted-string-p-1 () + "Test triple quoted string." + (python-tests-with-temp-buffer + " +t = '''Triple''' +" + (python-tests-look-at " '''Triple") + (should-not + (python-tests-should-not-move + #'python-info-triple-quoted-string-p)) + (forward-char) + (let ((start-pos (+ (point) 2)) + (eol (pos-eol))) + (while (< (point) eol) + (should (= (python-tests-should-not-move + #'python-info-triple-quoted-string-p) + start-pos)) + (forward-char))) + (dolist (pos `(,(point) ,(point-min) ,(point-max))) + (goto-char pos) + (should-not + (python-tests-should-not-move + #'python-info-triple-quoted-string-p))))) + +(ert-deftest python-info-triple-quoted-string-p-2 () + "Test empty triple quoted string." + (python-tests-with-temp-buffer + " +e = '''''' +" + (python-tests-look-at "''''''") + (let ((start-pos (+ (point) 2)) + (eol (pos-eol))) + (while (< (point) eol) + (should (= (python-tests-should-not-move + #'python-info-triple-quoted-string-p) + start-pos)) + (forward-char))))) + +(ert-deftest python-info-triple-quoted-string-p-3 () + "Test single quoted string." + (python-tests-with-temp-buffer + " +s = 'Single' +" + (while (< (point) (point-max)) + (should-not (python-tests-should-not-move + #'python-info-triple-quoted-string-p)) + (forward-char)))) + (ert-deftest python-info-encoding-from-cookie-1 () "Should detect it on first line." (python-tests-with-temp-buffer |