(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))
(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)
* 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
(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)
(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)
(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
(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'."
(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.
(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)))))
\f
;;; Navigation
(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."
(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
(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):
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
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)))))
\f
;;; Navigation
(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()
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:
}
'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