From: Fabián Ezequiel Gallina Date: Wed, 9 Jul 2014 03:55:53 +0000 (-0300) Subject: Fix dedenters and electric colon handling. X-Git-Tag: emacs-24.3.93~79 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=fded0b4a15d4ec7f92a64f03e9aa78f19a9b6031;p=emacs.git Fix dedenters and electric colon handling. * lisp/progmodes/python.el (python-rx-constituents): Add dedenter and block-ender. (python-indent-dedenters, python-indent-block-enders): Delete. (python-indent-context): Return new case for dedenter-statement. (python-indent-calculate-indentation): Handle new case. (python-indent-calculate-levels): Fix levels calculation for dedenter statements. (python-indent-post-self-insert-function): Fix colon handling. (python-info-dedenter-opening-block-message): New function. (python-indent-line): Use it. (python-info-closing-block) (python-info-closing-block-message): Remove. (python-info-dedenter-opening-block-position) (python-info-dedenter-opening-block-positions) (python-info-dedenter-statement-p): New functions. * test/automated/python-tests.el (python-indent-block-enders-1) (python-indent-block-enders-2): Fix tests. (python-indent-block-enders-3) (python-indent-block-enders-4) (python-indent-block-enders-5) (python-indent-dedenters-1) (python-indent-dedenters-2): Remove tests. (python-indent-dedenters-1) (python-indent-dedenters-2) (python-indent-dedenters-3) (python-indent-dedenters-4) (python-indent-dedenters-5) (python-indent-dedenters-6) (python-indent-dedenters-7) (python-info-dedenter-opening-block-position-1) (python-info-dedenter-opening-block-position-2) (python-info-dedenter-opening-block-position-3) (python-info-dedenter-opening-block-positions-1) (python-info-dedenter-opening-block-positions-2) (python-info-dedenter-opening-block-positions-3) (python-info-dedenter-opening-block-positions-4) (python-info-dedenter-opening-block-positions-5) (python-info-dedenter-opening-block-message-1) (python-info-dedenter-opening-block-message-2) (python-info-dedenter-opening-block-message-3) (python-info-dedenter-opening-block-message-4) (python-info-dedenter-opening-block-message-5) (python-info-dedenter-statement-p-1) (python-info-dedenter-statement-p-2) (python-info-dedenter-statement-p-3) (python-info-dedenter-statement-p-4) (python-info-dedenter-statement-p-5): New tests. Fixes: debbugs:15163 --- diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 0333e366beb..5bf133af77a 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,23 @@ +2014-07-09 Fabián Ezequiel Gallina + + Fix dedenters and electric colon handling. (Bug#15163) + + * progmodes/python.el + (python-rx-constituents): Add dedenter and block-ender. + (python-indent-dedenters, python-indent-block-enders): Delete. + (python-indent-context): Return new case for dedenter-statement. + (python-indent-calculate-indentation): Handle new case. + (python-indent-calculate-levels): Fix levels calculation for + dedenter statements. + (python-indent-post-self-insert-function): Fix colon handling. + (python-info-dedenter-opening-block-message): New function. + (python-indent-line): Use it. + (python-info-closing-block) + (python-info-closing-block-message): Remove. + (python-info-dedenter-opening-block-position) + (python-info-dedenter-opening-block-positions) + (python-info-dedenter-statement-p): New functions. + 2014-07-08 Stefan Monnier * progmodes/sh-script.el (sh-smie-sh-rules): Don't align with a && in diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 2301db8ecf6..237302f0530 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -322,6 +322,13 @@ (or "def" "class" "if" "elif" "else" "try" "except" "finally" "for" "while" "with") symbol-end)) + (dedenter . ,(rx symbol-start + (or "elif" "else" "except" "finally") + symbol-end)) + (block-ender . ,(rx symbol-start + (or + "break" "continue" "pass" "raise" "return") + symbol-end)) (decorator . ,(rx line-start (* space) ?@ (any letter ?_) (* (any word ?_)))) (defun . ,(rx symbol-start (or "def" "class") symbol-end)) @@ -631,18 +638,6 @@ It makes underscores and dots word constituent chars.") (defvar python-indent-levels '(0) "Levels of indentation available for `python-indent-line-function'.") -(defvar python-indent-dedenters '("else" "elif" "except" "finally") - "List of words that should be dedented. -These make `python-indent-calculate-indentation' subtract the value of -`python-indent-offset'.") - -(defvar python-indent-block-enders - '("break" "continue" "pass" "raise" "return") - "List of words that mark the end of a block. -These make `python-indent-calculate-indentation' subtract the -value of `python-indent-offset' when `python-indent-context' is -AFTER-LINE.") - (defun python-indent-guess-indent-offset () "Guess and set `python-indent-offset' for the current buffer." (interactive) @@ -693,6 +688,7 @@ Where status can be any of the following symbols: * after-backslash: Previous line ends in a backslash * after-beginning-of-block: Point is after beginning of block * after-line: Point is after normal line + * dedenter-statement: Point is on a dedenter statement. * no-indent: Point is at beginning of buffer or other special case START is the buffer position where the sexp starts." (save-restriction @@ -747,6 +743,8 @@ START is the buffer position where the sexp starts." (when (looking-at (python-rx block-start)) (point-marker))))) 'after-beginning-of-block) + ((when (setq start (python-info-dedenter-statement-p)) + 'dedenter-statement)) ;; After normal line ((setq start (save-excursion (back-to-indentation) @@ -777,8 +775,7 @@ START is the buffer position where the sexp starts." (goto-char context-start) (+ (current-indentation) python-indent-offset)) ;; When after a simple line just use previous line - ;; indentation, in the case current line starts with a - ;; `python-indent-dedenters' de-indent one level. + ;; indentation. (`after-line (let* ((pair (save-excursion (goto-char context-start) @@ -786,25 +783,27 @@ START is the buffer position where the sexp starts." (current-indentation) (python-info-beginning-of-block-p)))) (context-indentation (car pair)) - (after-block-start-p (cdr pair)) + ;; TODO: Separate block enders into its own case. (adjustment - (if (or (save-excursion - (back-to-indentation) - (and - ;; De-indent only when dedenters are not - ;; next to a block start. This allows - ;; one-liner constructs such as: - ;; if condition: print "yay" - ;; else: print "wry" - (not after-block-start-p) - (looking-at (regexp-opt python-indent-dedenters)))) - (save-excursion - (python-util-forward-comment -1) - (python-nav-beginning-of-statement) - (looking-at (regexp-opt python-indent-block-enders)))) + (if (save-excursion + (python-util-forward-comment -1) + (python-nav-beginning-of-statement) + (looking-at (python-rx block-ender))) python-indent-offset 0))) (- context-indentation adjustment))) + ;; When point is on a dedenter statement, search for the + ;; opening block that corresponds to it and use its + ;; indentation. If no opening block is found just remove + ;; indentation as this is an invalid python file. + (`dedenter-statement + (let ((block-start-point + (python-info-dedenter-opening-block-position))) + (save-excursion + (if (not block-start-point) + 0 + (goto-char block-start-point) + (current-indentation))))) ;; When inside of a string, do nothing. just use the current ;; indentation. XXX: perhaps it would be a good idea to ;; invoke standard text indentation here @@ -931,16 +930,25 @@ START is the buffer position where the sexp starts." (defun python-indent-calculate-levels () "Calculate `python-indent-levels' and reset `python-indent-current-level'." - (let* ((indentation (python-indent-calculate-indentation)) - (remainder (% indentation python-indent-offset)) - (steps (/ (- indentation remainder) python-indent-offset))) - (setq python-indent-levels (list 0)) - (dotimes (step steps) - (push (* python-indent-offset (1+ step)) python-indent-levels)) - (when (not (eq 0 remainder)) - (push (+ (* python-indent-offset steps) remainder) python-indent-levels)) - (setq python-indent-levels (nreverse python-indent-levels)) - (setq python-indent-current-level (1- (length python-indent-levels))))) + (if (not (python-info-dedenter-statement-p)) + (let* ((indentation (python-indent-calculate-indentation)) + (remainder (% indentation python-indent-offset)) + (steps (/ (- indentation remainder) python-indent-offset))) + (setq python-indent-levels (list 0)) + (dotimes (step steps) + (push (* python-indent-offset (1+ step)) python-indent-levels)) + (when (not (eq 0 remainder)) + (push (+ (* python-indent-offset steps) remainder) python-indent-levels))) + (setq python-indent-levels + (or + (mapcar (lambda (pos) + (save-excursion + (goto-char pos) + (current-indentation))) + (python-info-dedenter-opening-block-positions)) + (list 0)))) + (setq python-indent-current-level (1- (length python-indent-levels)) + python-indent-levels (nreverse python-indent-levels))) (defun python-indent-toggle-levels () "Toggle `python-indent-current-level' over `python-indent-levels'." @@ -989,7 +997,7 @@ equal to (indent-to next-indent) (goto-char starting-pos)) (and follow-indentation-p (back-to-indentation))) - (python-info-closing-block-message)) + (python-info-dedenter-opening-block-message)) (defun python-indent-line-function () "`indent-line-function' for Python mode. @@ -1125,14 +1133,7 @@ the line will be re-indented automatically if needed." (eolp) (not (equal ?: (char-before (1- (point))))) (not (python-syntax-comment-or-string-p))) - (let ((indentation (current-indentation)) - (calculated-indentation (python-indent-calculate-indentation))) - (python-info-closing-block-message) - (when (> indentation calculated-indentation) - (save-excursion - (indent-line-to calculated-indentation) - (when (not (python-info-closing-block-message)) - (indent-line-to indentation))))))))) + (python-indent-line))))) ;;; Navigation @@ -3450,49 +3451,88 @@ parent defun name." (and (python-info-end-of-statement-p) (python-info-statement-ends-block-p))) -(defun python-info-closing-block () - "Return the point of the block the current line closes." - (let ((closing-word (save-excursion - (back-to-indentation) - (current-word))) - (indentation (current-indentation))) - (when (member closing-word python-indent-dedenters) +(define-obsolete-function-alias + 'python-info-closing-block + 'python-info-dedenter-opening-block-position "24.4") + +(defun python-info-dedenter-opening-block-position () + "Return the point of the closest block the current line closes. +Returns nil if point is not on a dedenter statement or no opening +block can be detected. The latter case meaning current file is +likely an invalid python file." + (let ((positions (python-info-dedenter-opening-block-positions)) + (indentation (current-indentation)) + (position)) + (while (and (not position) + positions) (save-excursion - (forward-line -1) - (while (and (> (current-indentation) indentation) - (not (bobp)) - (not (back-to-indentation)) - (forward-line -1))) - (back-to-indentation) - (cond - ((not (equal indentation (current-indentation))) nil) - ((string= closing-word "elif") - (when (member (current-word) '("if" "elif")) - (point-marker))) - ((string= closing-word "else") - (when (member (current-word) '("if" "elif" "except" "for" "while")) - (point-marker))) - ((string= closing-word "except") - (when (member (current-word) '("try")) - (point-marker))) - ((string= closing-word "finally") - (when (member (current-word) '("except" "else")) - (point-marker)))))))) - -(defun python-info-closing-block-message (&optional closing-block-point) - "Message the contents of the block the current line closes. -With optional argument CLOSING-BLOCK-POINT use that instead of -recalculating it calling `python-info-closing-block'." - (let ((point (or closing-block-point (python-info-closing-block)))) + (goto-char (car positions)) + (if (<= (current-indentation) indentation) + (setq position (car positions)) + (setq positions (cdr positions))))) + position)) + +(defun python-info-dedenter-opening-block-positions () + "Return points of blocks the current line may close sorted by closer. +Returns nil if point is not on a dedenter statement or no opening +block can be detected. The latter case meaning current file is +likely an invalid python file." + (save-excursion + (let ((dedenter-pos (python-info-dedenter-statement-p))) + (when dedenter-pos + (goto-char dedenter-pos) + (let* ((pairs '(("elif" "elif" "if") + ("else" "if" "elif" "except" "for" "while") + ("except" "except" "try") + ("finally" "else" "except" "try"))) + (dedenter (match-string-no-properties 0)) + (possible-opening-blocks (cdr (assoc-string dedenter pairs))) + (collected-indentations) + (opening-blocks)) + (catch 'exit + (while (python-nav--syntactically + (lambda () + (re-search-backward (python-rx block-start) nil t)) + #'<) + (let ((indentation (current-indentation))) + (when (and (not (memq indentation collected-indentations)) + (or (not collected-indentations) + (< indentation (apply #'min collected-indentations)))) + (setq collected-indentations + (cons indentation collected-indentations)) + (when (member (match-string-no-properties 0) + possible-opening-blocks) + (setq opening-blocks (cons (point) opening-blocks)))) + (when (zerop indentation) + (throw 'exit nil))))) + ;; sort by closer + (nreverse opening-blocks)))))) + +(define-obsolete-function-alias + 'python-info-closing-block-message + 'python-info-dedenter-opening-block-message "24.4") + +(defun python-info-dedenter-opening-block-message () + "Message the first line of the block the current statement closes." + (let ((point (python-info-dedenter-opening-block-position))) (when point (save-restriction (widen) (message "Closes %s" (save-excursion (goto-char point) - (back-to-indentation) (buffer-substring (point) (line-end-position)))))))) +(defun python-info-dedenter-statement-p () + "Return point if current statement is a dedenter. +Sets `match-data' to the keyword that starts the dedenter +statement." + (save-excursion + (python-nav-beginning-of-statement) + (when (and (not (python-syntax-context-type)) + (looking-at (python-rx dedenter))) + (point)))) + (defun python-info-line-ends-backslash-p (&optional line-number) "Return non-nil if current line ends with backslash. With optional argument LINE-NUMBER, check that line instead." diff --git a/test/ChangeLog b/test/ChangeLog index f93b553f341..cf4ddc83544 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,40 @@ +2014-07-09 Fabián Ezequiel Gallina + + * automated/python-tests.el + (python-indent-block-enders-1) + (python-indent-block-enders-2): Fix tests. + (python-indent-block-enders-3) + (python-indent-block-enders-4) + (python-indent-block-enders-5) + (python-indent-dedenters-1) + (python-indent-dedenters-2): Remove tests. + (python-indent-dedenters-1) + (python-indent-dedenters-2) + (python-indent-dedenters-3) + (python-indent-dedenters-4) + (python-indent-dedenters-5) + (python-indent-dedenters-6) + (python-indent-dedenters-7) + (python-info-dedenter-opening-block-position-1) + (python-info-dedenter-opening-block-position-2) + (python-info-dedenter-opening-block-position-3) + (python-info-dedenter-opening-block-positions-1) + (python-info-dedenter-opening-block-positions-2) + (python-info-dedenter-opening-block-positions-3) + (python-info-dedenter-opening-block-positions-4) + (python-info-dedenter-opening-block-positions-5) + (python-info-dedenter-opening-block-message-1) + (python-info-dedenter-opening-block-message-2) + (python-info-dedenter-opening-block-message-3) + (python-info-dedenter-opening-block-message-4) + (python-info-dedenter-opening-block-message-5) + (python-info-dedenter-statement-p-1) + (python-info-dedenter-statement-p-2) + (python-info-dedenter-statement-p-3) + (python-info-dedenter-statement-p-4) + (python-info-dedenter-statement-p-5): New tests. + + 2014-07-01 Fabián Ezequiel Gallina * automated/python-tests.el diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index a35242fe882..3a4eda36bfe 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el @@ -435,79 +435,6 @@ def foo(a, b, c={ (should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (= (python-indent-calculate-indentation) 4)))) -(ert-deftest python-indent-dedenters-1 () - "Check all dedenters." - (python-tests-with-temp-buffer - " -def foo(a, b, c): - if a: - print (a) - elif b: - print (b) - else: - try: - print (c.pop()) - except (IndexError, AttributeError): - print (c) - finally: - print ('nor a, nor b are true') -" - (python-tests-look-at "if a:") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "print (a)") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "elif b:") - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "print (b)") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "else:") - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "try:") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "print (c.pop())") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 12)) - (python-tests-look-at "except (IndexError, AttributeError):") - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "print (c)") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 12)) - (python-tests-look-at "finally:") - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "print ('nor a, nor b are true')") - (should (eq (car (python-indent-context)) 'after-beginning-of-block)) - (should (= (python-indent-calculate-indentation) 12)))) - -(ert-deftest python-indent-dedenters-2 () - "Check one-liner block special case.." - (python-tests-with-temp-buffer - " -cond = True -if cond: - - if cond: print 'True' -else: print 'False' - -else: - return -" - (python-tests-look-at "else: print 'False'") - ;; When a block has code after ":" it's just considered a simple - ;; line as that's a common thing to happen in one-liners. - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "else:") - (should (eq (car (python-indent-context)) 'after-line)) - (should (= (python-indent-calculate-indentation) 0)))) - (ert-deftest python-indent-after-backslash-1 () "The most common case." (python-tests-with-temp-buffer @@ -575,9 +502,9 @@ objects = Thing.objects.all() \\\\ (should (= (python-indent-calculate-indentation) 0)))) (ert-deftest python-indent-block-enders-1 () - "Test `python-indent-block-enders' value honoring." + "Test de-indentation for pass keyword." (python-tests-with-temp-buffer - " + " Class foo(object): def bar(self): @@ -589,17 +516,17 @@ Class foo(object): else: pass " - (python-tests-look-at "3)") - (forward-line 1) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "pass") - (forward-line 1) - (should (= (python-indent-calculate-indentation) 8)))) + (python-tests-look-at "3)") + (forward-line 1) + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "pass") + (forward-line 1) + (should (= (python-indent-calculate-indentation) 8)))) (ert-deftest python-indent-block-enders-2 () - "Test `python-indent-block-enders' value honoring." + "Test de-indentation for return keyword." (python-tests-with-temp-buffer - " + " Class foo(object): '''raise lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do @@ -612,10 +539,177 @@ Class foo(object): 2, 3) " - (python-tests-look-at "def") - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "if") - (should (= (python-indent-calculate-indentation) 8)))) + (python-tests-look-at "def") + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "if") + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "return") + (should (= (python-indent-calculate-indentation) 12)) + (goto-char (point-max)) + (should (= (python-indent-calculate-indentation) 8)))) + +(ert-deftest python-indent-block-enders-3 () + "Test de-indentation for continue keyword." + (python-tests-with-temp-buffer + " +for element in lst: + if element is None: + continue +" + (python-tests-look-at "if") + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "continue") + (should (= (python-indent-calculate-indentation) 8)) + (forward-line 1) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-block-enders-4 () + "Test de-indentation for break keyword." + (python-tests-with-temp-buffer + " +for element in lst: + if element is None: + break +" + (python-tests-look-at "if") + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "break") + (should (= (python-indent-calculate-indentation) 8)) + (forward-line 1) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-block-enders-5 () + "Test de-indentation for raise keyword." + (python-tests-with-temp-buffer + " +for element in lst: + if element is None: + raise ValueError('Element cannot be None') +" + (python-tests-look-at "if") + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "raise") + (should (= (python-indent-calculate-indentation) 8)) + (forward-line 1) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-dedenters-1 () + "Test de-indentation for the elif keyword." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + finally: + cleanup() + elif +" + (python-tests-look-at "elif\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 0)) + (should (equal (python-indent-calculate-levels) '(0))))) + +(ert-deftest python-indent-dedenters-2 () + "Test de-indentation for the else keyword." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 8)) + (should (equal (python-indent-calculate-levels) '(0 4 8))))) + +(ert-deftest python-indent-dedenters-3 () + "Test de-indentation for the except keyword." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except +" + (python-tests-look-at "except\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 4)) + (should (equal (python-indent-calculate-levels) '(4))))) + +(ert-deftest python-indent-dedenters-4 () + "Test de-indentation for the finally keyword." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + finally +" + (python-tests-look-at "finally\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 4)) + (should (equal (python-indent-calculate-levels) '(4))))) + +(ert-deftest python-indent-dedenters-5 () + "Test invalid levels are skipped in a complex example." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + finally: + if cleanup: + do_cleanup() + else +" + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 8)) + (should (equal (python-indent-calculate-levels) '(0 8))))) + +(ert-deftest python-indent-dedenters-6 () + "Test indentation is zero when no opening block for dedenter." + (python-tests-with-temp-buffer + " +try: + # if save: + write_to_disk(data) + else +" + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 0)) + (should (equal (python-indent-calculate-levels) '(0))))) + +(ert-deftest python-indent-dedenters-7 () + "Test indentation case from Bug#15163." + (python-tests-with-temp-buffer + " +if a: + if b: + pass + else: + pass + else: +" + (python-tests-look-at "else:" 2) + (should (eq (car (python-indent-context)) 'dedenter-statement)) + (should (= (python-indent-calculate-indentation) 0)) + (should (equal (python-indent-calculate-levels) '(0))))) ;;; Navigation @@ -2428,9 +2522,9 @@ if width == 0 and height == 0 and \\\\ (python-util-forward-comment -1) (should (python-info-end-of-block-p)))) -(ert-deftest python-info-closing-block-1 () +(ert-deftest python-info-dedenter-opening-block-position-1 () (python-tests-with-temp-buffer - " + " if request.user.is_authenticated(): try: profile = request.user.get_profile() @@ -2445,26 +2539,26 @@ if request.user.is_authenticated(): profile.views += 1 profile.save() " - (python-tests-look-at "try:") - (should (not (python-info-closing-block))) - (python-tests-look-at "except Profile.DoesNotExist:") - (should (= (python-tests-look-at "try:" -1 t) - (python-info-closing-block))) - (python-tests-look-at "else:") - (should (= (python-tests-look-at "except Profile.DoesNotExist:" -1 t) - (python-info-closing-block))) - (python-tests-look-at "if profile.stats:") - (should (not (python-info-closing-block))) - (python-tests-look-at "else:") - (should (= (python-tests-look-at "if profile.stats:" -1 t) - (python-info-closing-block))) - (python-tests-look-at "finally:") - (should (= (python-tests-look-at "else:" -2 t) - (python-info-closing-block))))) - -(ert-deftest python-info-closing-block-2 () - (python-tests-with-temp-buffer - " + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "except Profile.DoesNotExist:") + (should (= (python-tests-look-at "try:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "else:") + (should (= (python-tests-look-at "except Profile.DoesNotExist:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "if profile.stats:") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "else:") + (should (= (python-tests-look-at "if profile.stats:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "finally:") + (should (= (python-tests-look-at "else:" -2 t) + (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-position-2 () + (python-tests-with-temp-buffer + " if request.user.is_authenticated(): profile = Profile.objects.get_or_create(user=request.user) if profile.stats: @@ -2475,10 +2569,440 @@ data = { } 'else' " - (python-tests-look-at "'else': 'do it'") - (should (not (python-info-closing-block))) - (python-tests-look-at "'else'") - (should (not (python-info-closing-block))))) + (python-tests-look-at "'else': 'do it'") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "'else'") + (should (not (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-position-3 () + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "except IOError:") + (should (= (python-tests-look-at "try:" -1 t) + (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "except Exception:") + (should (= (python-tests-look-at "except IOError:" -1 t) + (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "if hide_details:") + (should (not (python-info-dedenter-opening-block-position))) + + ;; check indentation modifies the detected opening block + (python-tests-look-at "else") + (should (= (python-tests-look-at "if hide_details:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 8) + (should (= (python-tests-look-at "if hide_details:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 4) + (should (= (python-tests-look-at "except Exception:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 0) + (should (= (python-tests-look-at "if save:" -1 t) + (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-positions-1 () + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except IOError:") + (should + (equal (list + (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except Exception:") + (should + (equal (list + (python-tests-look-at "except IOError:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "if hide_details:") + (should (not (python-info-dedenter-opening-block-positions))) + + ;; check indentation does not modify the detected opening blocks + (python-tests-look-at "else") + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 8) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 4) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 0) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-2 () + "Test detection of opening blocks for elif." + (python-tests-with-temp-buffer + " +if var: + if var2: + something() + elif var3: + something_else() + elif +" + (python-tests-look-at "elif var3:") + (should + (equal (list + (python-tests-look-at "if var2:" -1 t) + (python-tests-look-at "if var:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "elif\n") + (should + (equal (list + (python-tests-look-at "elif var3:" -1 t) + (python-tests-look-at "if var:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-3 () + "Test detection of opening blocks for else." + (python-tests-with-temp-buffer + " +try: + something() +except: + if var: + if var2: + something() + elif var3: + something_else() + else + +if var4: + while var5: + var4.pop() + else + + for value in var6: + if value > 0: + print value + else +" + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "elif var3:" -1 t) + (python-tests-look-at "if var:" -1 t) + (python-tests-look-at "except:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "while var5:" -1 t) + (python-tests-look-at "if var4:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "if value > 0:" -1 t) + (python-tests-look-at "for value in var6:" -1 t) + (python-tests-look-at "if var4:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-4 () + "Test detection of opening blocks for except." + (python-tests-with-temp-buffer + " +try: + something() +except ValueError: + something_else() + except +" + (python-tests-look-at "except ValueError:") + (should + (equal (list (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except\n") + (should + (equal (list (python-tests-look-at "except ValueError:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-5 () + "Test detection of opening blocks for finally." + (python-tests-with-temp-buffer + " +try: + something() + finally + +try: + something_else() +except: + logger.exception('something went wrong') + finally + +try: + something_else_else() +except Exception: + logger.exception('something else went wrong') +else: + print ('all good') + finally +" + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "except:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "else:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-message-1 () + "Test dedenters inside strings are ignored." + (python-tests-with-temp-buffer + "''' +try: + something() +except: + logger.exception('something went wrong') +''' +" + (python-tests-look-at "except\n") + (should (not (python-info-dedenter-opening-block-message))))) + +(ert-deftest python-info-dedenter-opening-block-message-2 () + "Test except keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +" + (python-tests-look-at "except:") + (should (string= + "Closes try:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes try:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-3 () + "Test else keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +" + (python-tests-look-at "else:") + (should (string= + "Closes except:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes except:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-4 () + "Test finally keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +finally: + clean() +" + (python-tests-look-at "finally:") + (should (string= + "Closes else:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes else:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-5 () + "Test elif keyword." + (python-tests-with-temp-buffer + " +if a: + something() +elif b: +" + (python-tests-look-at "elif b:") + (should (string= + "Closes if a:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes if a:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + + +(ert-deftest python-info-dedenter-statement-p-1 () + "Test dedenters inside strings are ignored." + (python-tests-with-temp-buffer + "''' +try: + something() +except: + logger.exception('something went wrong') +''' +" + (python-tests-look-at "except\n") + (should (not (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-2 () + "Test except keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +" + (python-tests-look-at "except:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-3 () + "Test else keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +" + (python-tests-look-at "else:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-4 () + "Test finally keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +finally: + clean() +" + (python-tests-look-at "finally:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-5 () + "Test elif keyword." + (python-tests-with-temp-buffer + " +if a: + something() +elif b: +" + (python-tests-look-at "elif b:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) (ert-deftest python-info-line-ends-backslash-p-1 () (python-tests-with-temp-buffer