From ea2de07164cee80a85226e68b83692d6eeda4f46 Mon Sep 17 00:00:00 2001 From: kobarity Date: Sun, 9 Apr 2023 20:48:00 +0900 Subject: [PATCH] Add a new user option in Python mode to improve the indentation * lisp/progmodes/python.el (python-indent-block-paren-deeper): New user option. (python-indent-context): Add a new context :inside-paren-from-block. (python-indent--calculate-indentation): Modify according to `python-indent-block-paren-deeper' and :inside-paren-from-block. * test/lisp/progmodes/python-tests.el (python-indent-inside-paren-block-1) (python-indent-inside-paren-block-2) (python-indent-inside-paren-block-3) (python-indent-inside-paren-block-4): New tests. (python-indent-inside-paren-5, python-indent-dedenters-8): Modify according to the new context. * etc/NEWS: Document the new user option. (Bug#62696) --- etc/NEWS | 8 ++ lisp/progmodes/python.el | 41 ++++++++-- test/lisp/progmodes/python-tests.el | 116 +++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 9 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 5e1fd76e99e..1fd97c35208 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -266,6 +266,14 @@ When non-nil, it will automatically register every package as a project, that you can quickly select using 'project-switch-project' ('C-x p p'). +** Python mode + +*** New user option 'python-indent-block-paren-deeper'. +Increase indentation inside parens of a block. +When this variable is set to non-nil and the contents of a block +inside parens are indented to the same level as outside the same +block, increase the indentation of the line. + * New Modes and Packages in Emacs 30.1 diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index bbabce80b4d..0868b77f201 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -1270,6 +1270,15 @@ python-indent-def-block-scale :type 'integer :safe 'natnump) +(defcustom python-indent-block-paren-deeper nil + "Increase indentation inside parens of a block. +When this variable is set to non-nil and the contents of a block +inside parens are indented to the same level as outside the same +block, increase the indentation of the line." + :version "30.1" + :type 'boolean + :safe 'booleanp) + (defvar python-indent-current-level 0 "Deprecated var available for compatibility.") @@ -1367,6 +1376,10 @@ python-indent-context - Point is inside a paren with items starting in their own line from a block start. - START is the position of the open paren. +:inside-paren-from-block + - Point is inside a paren from a block start followed by some + items on the same line. + - START is the first non space char position *after* the open paren. :after-backslash - Fallback case when point is after backslash. @@ -1450,12 +1463,16 @@ python-indent-context (starts-in-newline (cons :inside-paren-newline-start start)) ;; General case. - (t (cons :inside-paren - (save-excursion - (goto-char (1+ start)) - (skip-syntax-forward "(" 1) - (skip-syntax-forward " ") - (point)))))))) + (t (let ((after-start (save-excursion + (goto-char (1+ start)) + (skip-syntax-forward "(" 1) + (skip-syntax-forward " ") + (point)))) + (if (save-excursion + (python-nav-beginning-of-statement) + (python-info-looking-at-beginning-of-block)) + (cons :inside-paren-from-block after-start) + (cons :inside-paren after-start)))))))) ;; After backslash. ((let ((start (when (not (python-syntax-comment-or-string-p ppss)) (python-info-line-ends-backslash-p @@ -1603,7 +1620,17 @@ python-indent--calculate-indentation (`(,(or :inside-paren-newline-start-from-block) . ,start) (goto-char start) (+ (current-indentation) - (* python-indent-offset python-indent-def-block-scale)))))) + (* python-indent-offset python-indent-def-block-scale))) + (`(,:inside-paren-from-block . ,start) + (goto-char start) + (let ((column (current-column))) + (if (and python-indent-block-paren-deeper + (= column (+ (save-excursion + (python-nav-beginning-of-statement) + (current-indentation)) + python-indent-offset))) + (+ column python-indent-offset) + column)))))) (defun python-indent--calculate-levels (indentation) "Calculate levels list given INDENTATION. diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index 50153e66da5..60b11d572cf 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -1139,7 +1139,7 @@ python-indent-inside-paren-5 (should (eq (car (python-indent-context)) :no-indent)) (should (= (python-indent-calculate-indentation) 0)) (forward-line 1) - (should (eq (car (python-indent-context)) :inside-paren)) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) (should (= (python-indent-calculate-indentation) 7)) (forward-line 1) (should (eq (car (python-indent-context)) :after-block-start)) @@ -1174,6 +1174,118 @@ python-indent-inside-paren-7 ;; This signals an error if the test fails (should (eq (car (python-indent-context)) :inside-paren-newline-start)))) +(ert-deftest python-indent-inside-paren-block-1 () + "`python-indent-block-paren-deeper' set to nil (default). +See Bug#62696." + (python-tests-with-temp-buffer + " +if ('VALUE' in my_unnecessarily_long_dictionary and + some_other_long_condition_case): + do_something() +elif (some_case or + another_case): + do_another() +" + (python-tests-look-at "if") + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 4)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)) + (forward-line 1) + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 6)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-inside-paren-block-2 () + "`python-indent-block-paren-deeper' set to t. +See Bug#62696." + (python-tests-with-temp-buffer + " +if ('VALUE' in my_unnecessarily_long_dictionary and + some_other_long_condition_case): + do_something() +elif (some_case or + another_case): + do_another() +" + (let ((python-indent-block-paren-deeper t)) + (python-tests-look-at "if") + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 8)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)) + (forward-line 1) + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 6)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4))))) + +(ert-deftest python-indent-inside-paren-block-3 () + "With backslash. `python-indent-block-paren-deeper' set to nil (default). +See Bug#62696." + (python-tests-with-temp-buffer + " +if 'VALUE' in my_uncessarily_long_dictionary and\\ + (some_other_long_condition_case or + another_case): + do_something() +" + (python-tests-look-at "if") + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) + :after-backslash-block-continuation)) + (should (= (python-indent-calculate-indentation) 3)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 4)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-inside-paren-block-4 () + "With backslash. `python-indent-block-paren-deeper' set to t. +See Bug#62696." + (python-tests-with-temp-buffer + " +if 'VALUE' in my_uncessarily_long_dictionary and\\ + (some_other_long_condition_case or + another_case): + do_something() +" + (let ((python-indent-block-paren-deeper t)) + (python-tests-look-at "if") + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (forward-line 1) + (should (eq (car (python-indent-context)) + :after-backslash-block-continuation)) + (should (= (python-indent-calculate-indentation) 3)) + (forward-line 1) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) + (should (= (python-indent-calculate-indentation) 8)) + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4))))) + (ert-deftest python-indent-after-block-1 () "The most simple after-block case that shouldn't fail." (python-tests-with-temp-buffer @@ -1670,7 +1782,7 @@ python-indent-dedenters-8 (should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation t) 0)) (python-tests-look-at "a == 4):\n") - (should (eq (car (python-indent-context)) :inside-paren)) + (should (eq (car (python-indent-context)) :inside-paren-from-block)) (should (= (python-indent-calculate-indentation) 6)) (python-indent-line) (should (= (python-indent-calculate-indentation t) 4)) -- 2.25.1