python.el: New non-global state dependent indentation engine.

Fixes: debbugs:18319
Fixes: debbugs:19595

* lisp/progmodes/python.el (python-syntax-comment-or-string-p): Accept
PPSS as argument.
(python-syntax-closing-paren-p): New function.
(python-indent-current-level)
(python-indent-levels): Mark obsolete.
(python-indent-context): Return more context cases.
(python-indent--calculate-indentation)
(python-indent--calculate-levels): New functions.
(python-indent-calculate-levels): Use them.
(python-indent-calculate-indentation, python-indent-line):
(python-indent-line-function): Rewritten to use new API.
(python-indent-dedent-line): Simplify logic.
(python-indent-dedent-line-backspace): Use `unless`.
(python-indent-toggle-levels): Delete function.

* test/automated/python-tests.el (python-indent-pep8-1)
(python-indent-pep8-2, python-indent-pep8-3)
(python-indent-after-comment-1, python-indent-after-comment-2)
(python-indent-inside-paren-1, python-indent-inside-paren-2)
(python-indent-after-block-1, python-indent-after-block-2)
(python-indent-after-backslash-1, python-indent-after-backslash-2)
(python-indent-after-backslash-3, python-indent-block-enders-1)
(python-indent-block-enders-2, python-indent-block-enders-3)
(python-indent-block-enders-4, python-indent-block-enders-5)
(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-indent-dedenters-8): Fix tests.
(python-indent-base-case, python-indent-after-block-3)
(python-indent-after-backslash-5, python-indent-inside-paren-3)
(python-indent-inside-paren-4, python-indent-inside-paren-5)
(python-indent-inside-paren-6, python-indent-inside-string-1)
(python-indent-inside-string-2, python-indent-inside-string-3)
(python-indent-dedent-line-backspace-1): New Tests.
This commit is contained in:
Fabián Ezequiel Gallina 2015-01-27 00:17:24 -03:00
parent 3b23e6a702
commit 5485e3e5b2
4 changed files with 825 additions and 471 deletions

View file

@ -1,3 +1,23 @@
2015-01-26 Fabián Ezequiel Gallina <fgallina@gnu.org>
python.el: New non-global state dependent indentation engine.
(Bug#18319, Bug#19595)
* progmodes/python.el (python-syntax-comment-or-string-p): Accept
PPSS as argument.
(python-syntax-closing-paren-p): New function.
(python-indent-current-level)
(python-indent-levels): Mark obsolete.
(python-indent-context): Return more context cases.
(python-indent--calculate-indentation)
(python-indent--calculate-levels): New functions.
(python-indent-calculate-levels): Use them.
(python-indent-calculate-indentation, python-indent-line):
(python-indent-line-function): Rewritten to use new API.
(python-indent-dedent-line): Simplify logic.
(python-indent-dedent-line-backspace): Use `unless`.
(python-indent-toggle-levels): Delete function.
2015-01-22 Wolfgang Jenkner <wjenkner@inode.at> 2015-01-22 Wolfgang Jenkner <wjenkner@inode.at>
* calc/calc-units.el (math-units-in-expr-p) * calc/calc-units.el (math-units-in-expr-p)

View file

@ -447,9 +447,14 @@ The type returned can be `comment', `string' or `paren'."
((nth 8 ppss) (if (nth 4 ppss) 'comment 'string)) ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string))
((nth 1 ppss) 'paren)))) ((nth 1 ppss) 'paren))))
(defsubst python-syntax-comment-or-string-p () (defsubst python-syntax-comment-or-string-p (&optional ppss)
"Return non-nil if point is inside 'comment or 'string." "Return non-nil if PPSS is inside 'comment or 'string."
(nth 8 (syntax-ppss))) (nth 8 (or ppss (syntax-ppss))))
(defsubst python-syntax-closing-paren-p ()
"Return non-nil if char after point is a closing paren."
(= (syntax-class (syntax-after (point)))
(syntax-class (string-to-syntax ")"))))
(define-obsolete-function-alias (define-obsolete-function-alias
'python-info-ppss-context #'python-syntax-context "24.3") 'python-info-ppss-context #'python-syntax-context "24.3")
@ -671,10 +676,28 @@ It makes underscores and dots word constituent chars.")
'python-guess-indent 'python-indent-guess-indent-offset "24.3") 'python-guess-indent 'python-indent-guess-indent-offset "24.3")
(defvar python-indent-current-level 0 (defvar python-indent-current-level 0
"Current indentation level `python-indent-line-function' is using.") "Deprecated var available for compatibility.")
(defvar python-indent-levels '(0) (defvar python-indent-levels '(0)
"Levels of indentation available for `python-indent-line-function'.") "Deprecated var available for compatibility.")
(make-obsolete-variable
'python-indent-current-level
"The indentation API changed to avoid global state.
The function `python-indent-calculate-levels' does not use it
anymore. If you were defadvising it and or depended on this
variable for indentation customizations, refactor your code to
work on `python-indent-calculate-indentation' instead."
"24.5")
(make-obsolete-variable
'python-indent-levels
"The indentation API changed to avoid global state.
The function `python-indent-calculate-levels' does not use it
anymore. If you were defadvising it and or depended on this
variable for indentation customizations, refactor your code to
work on `python-indent-calculate-indentation' instead."
"24.5")
(defun python-indent-guess-indent-offset () (defun python-indent-guess-indent-offset ()
"Guess and set `python-indent-offset' for the current buffer." "Guess and set `python-indent-offset' for the current buffer."
@ -714,356 +737,358 @@ It makes underscores and dots word constituent chars.")
python-indent-offset))))))) python-indent-offset)))))))
(defun python-indent-context () (defun python-indent-context ()
"Get information on indentation context. "Get information about the current indentation context.
Context information is returned with a cons with the form: Context is returned in a cons with the form (STATUS . START).
(STATUS . START)
Where status can be any of the following symbols: STATUS can be one of the following:
* after-comment: When current line might continue a comment block keyword
* inside-paren: If point in between (), {} or [] -------
* inside-string: If point is inside a string
* after-backslash: Previous line ends in a backslash :after-comment
* after-beginning-of-block: Point is after beginning of block - Point is after a comment line.
* after-line: Point is after normal line - START is the position of the \"#\" character.
* dedenter-statement: Point is on a dedenter statement. :inside-string
* no-indent: Point is at beginning of buffer or other special case - Point is inside string.
START is the buffer position where the sexp starts." - START is the position of the first quote that starts it.
:no-indent
- No possible indentation case matches.
- START is always zero.
:inside-paren
- Fallback case when point is inside paren.
- START is the first non space char position *after* the open paren.
:inside-paren-at-closing-nested-paren
- Point is on a line that contains a nested paren closer.
- START is the position of the open paren it closes.
:inside-paren-at-closing-paren
- Point is on a line that contains a paren closer.
- START is the position of the open paren.
:inside-paren-newline-start
- Point is inside a paren with items starting in their own line.
- START is the position of the open paren.
:inside-paren-newline-start-from-block
- Point is inside a paren with items starting in their own line
from a block start.
- START is the position of the open paren.
:after-backslash
- Fallback case when point is after backslash.
- START is the char after the position of the backslash.
:after-backslash-assignment-continuation
- Point is after a backslashed assignment.
- START is the char after the position of the backslash.
:after-backslash-block-continuation
- Point is after a backslashed block continuation.
- START is the char after the position of the backslash.
:after-backslash-dotted-continuation
- Point is after a backslashed dotted continuation. Previous
line must contain a dot to align with.
- START is the char after the position of the backslash.
:after-backslash-first-line
- First line following a backslashed continuation.
- START is the char after the position of the backslash.
:after-block-end
- Point is after a line containing a block ender.
- START is the position where the ender starts.
:after-block-start
- Point is after a line starting a block.
- START is the position where the block starts.
:after-line
- Point is after a simple line.
- START is the position where the previous line starts.
:at-dedenter-block-start
- Point is on a line starting a dedenter block.
- START is the position where the dedenter block starts."
(save-restriction (save-restriction
(widen) (widen)
(let ((ppss (save-excursion (beginning-of-line) (syntax-ppss))) (let ((ppss (save-excursion
(start)) (beginning-of-line)
(cons (syntax-ppss))))
(cond (cond
;; Beginning of buffer ;; Beginning of buffer.
((save-excursion ((= (line-number-at-pos) 1)
(goto-char (line-beginning-position)) (cons :no-indent 0))
(bobp)) ;; Comment continuation (maybe).
'no-indent) ((save-excursion
;; Comment continuation (when (and
((save-excursion (or
(when (and (python-info-current-line-comment-p)
(or (python-info-current-line-empty-p))
(python-info-current-line-comment-p) (forward-comment -1)
(python-info-current-line-empty-p)) (python-info-current-line-comment-p))
(progn (cons :after-comment (point)))))
(forward-comment -1) ;; Inside a string.
(python-info-current-line-comment-p))) ((let ((start (python-syntax-context 'string ppss)))
(setq start (point)) (when start
'after-comment))) (cons :inside-string start))))
;; Inside string ;; Inside a paren.
((setq start (python-syntax-context 'string ppss)) ((let* ((start (python-syntax-context 'paren ppss))
'inside-string) (starts-in-newline
;; Inside a paren (when start
((setq start (python-syntax-context 'paren ppss)) (save-excursion
'inside-paren) (goto-char start)
;; After backslash (forward-char)
((setq start (when (not (or (python-syntax-context 'string ppss) (not
(python-syntax-context 'comment ppss))) (= (line-number-at-pos)
(let ((line-beg-pos (line-number-at-pos))) (progn
(python-info-line-ends-backslash-p (python-util-forward-comment)
(1- line-beg-pos))))) (line-number-at-pos))))))))
'after-backslash) (when start
;; After beginning of block (cond
((setq start (save-excursion ;; Current line only holds the closing paren.
(when (progn ((save-excursion
(back-to-indentation) (skip-syntax-forward " ")
(python-util-forward-comment -1) (when (and (python-syntax-closing-paren-p)
(equal (char-before) ?:)) (progn
;; Move to the first block start that's not in within (forward-char 1)
;; a string, comment or paren and that's not a (not (python-syntax-context 'paren))))
;; continuation line. (cons :inside-paren-at-closing-paren start))))
(while (and (re-search-backward ;; Current line only holds a closing paren for nested.
(python-rx block-start) nil t) ((save-excursion
(or (back-to-indentation)
(python-syntax-context-type) (python-syntax-closing-paren-p))
(python-info-continuation-line-p)))) (cons :inside-paren-at-closing-nested-paren start))
(when (looking-at (python-rx block-start)) ;; This line starts from a opening block in its own line.
(point-marker))))) ((save-excursion
'after-beginning-of-block) (goto-char start)
((when (setq start (python-info-dedenter-statement-p)) (when (and
'dedenter-statement)) starts-in-newline
;; After normal line (save-excursion
((setq start (save-excursion (back-to-indentation)
(looking-at (python-rx block-start))))
(cons
:inside-paren-newline-start-from-block start))))
(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))))))))
;; After backslash.
((let ((start (when (not (python-syntax-comment-or-string-p ppss))
(python-info-line-ends-backslash-p
(1- (line-number-at-pos))))))
(when start
(cond
;; Continuation of dotted expression.
((save-excursion
(back-to-indentation)
(when (eq (char-after) ?\.)
;; Move point back until it's not inside a paren.
(while (prog2
(forward-line -1)
(and (not (bobp))
(python-syntax-context 'paren))))
(goto-char (line-end-position))
(while (and (search-backward
"." (line-beginning-position) t)
(python-syntax-context-type)))
;; Ensure previous statement has dot to align with.
(when (and (eq (char-after) ?\.)
(not (python-syntax-context-type)))
(cons :after-backslash-dotted-continuation (point))))))
;; Continuation of block definition.
((let ((block-continuation-start
(python-info-block-continuation-line-p)))
(when block-continuation-start
(save-excursion
(goto-char block-continuation-start)
(re-search-forward
(python-rx block-start (* space))
(line-end-position) t)
(cons :after-backslash-block-continuation (point))))))
;; Continuation of assignment.
((let ((assignment-continuation-start
(python-info-assignment-continuation-line-p)))
(when assignment-continuation-start
(save-excursion
(goto-char assignment-continuation-start)
(cons :after-backslash-assignment-continuation (point))))))
;; First line after backslash continuation start.
((save-excursion
(goto-char start)
(when (or (= (line-number-at-pos) 1)
(not (python-info-beginning-of-backslash
(1- (line-number-at-pos)))))
(cons :after-backslash-first-line start))))
;; General case.
(t (cons :after-backslash start))))))
;; After beginning of block.
((let ((start (save-excursion
(back-to-indentation) (back-to-indentation)
(skip-chars-backward (rx (or whitespace ?\n))) (python-util-forward-comment -1)
(when (equal (char-before) ?:)
(python-nav-beginning-of-block)))))
(when start
(cons :after-block-start start))))
;; At dedenter statement.
((let ((start (python-info-dedenter-statement-p)))
(when start
(cons :at-dedenter-block-start start))))
;; After normal line.
((let ((start (save-excursion
(back-to-indentation)
(skip-chars-backward " \t\n")
(python-nav-beginning-of-statement) (python-nav-beginning-of-statement)
(point-marker))) (point))))
'after-line) (when start
;; Do not indent (if (save-excursion
(t 'no-indent)) (python-util-forward-comment -1)
start)))) (python-nav-beginning-of-statement)
(looking-at (python-rx block-ender)))
(cons :after-block-end start)
(cons :after-line start)))))
;; Default case: do not indent.
(t (cons :no-indent 0))))))
(defun python-indent-calculate-indentation () (defun python-indent--calculate-indentation ()
"Calculate correct indentation offset for the current line." "Internal implementation of `python-indent-calculate-indentation'.
(let* ((indentation-context (python-indent-context)) May return an integer for the maximum possible indentation at
(context-status (car indentation-context)) current context or a list of integers. The latter case is only
(context-start (cdr indentation-context))) happening for :at-dedenter-block-start context since the
(save-restriction possibilities can be narrowed to especific indentation points."
(widen) (save-restriction
(save-excursion (widen)
(pcase context-status (save-excursion
(`no-indent 0) (pcase (python-indent-context)
(`after-comment (`(:no-indent . ,_) 0)
(goto-char context-start) (`(,(or :after-line
(current-indentation)) :after-comment
;; When point is after beginning of block just add one level :inside-string
;; of indentation relative to the context-start :after-backslash
(`after-beginning-of-block :inside-paren-at-closing-paren
(goto-char context-start) :inside-paren-at-closing-nested-paren) . ,start)
(+ (current-indentation) python-indent-offset)) ;; Copy previous indentation.
;; When after a simple line just use previous line (goto-char start)
;; indentation. (current-indentation))
(`after-line (`(,(or :after-block-start
(let* ((pair (save-excursion :after-backslash-first-line
(goto-char context-start) :inside-paren-newline-start) . ,start)
(cons ;; Add one indentation level.
(current-indentation) (goto-char start)
(python-info-beginning-of-block-p)))) (+ (current-indentation) python-indent-offset))
(context-indentation (car pair)) (`(,(or :inside-paren
;; TODO: Separate block enders into its own case. :after-backslash-block-continuation
(adjustment :after-backslash-assignment-continuation
(if (save-excursion :after-backslash-dotted-continuation) . ,start)
(python-util-forward-comment -1) ;; Use the column given by the context.
(python-nav-beginning-of-statement) (goto-char start)
(looking-at (python-rx block-ender))) (current-column))
python-indent-offset (`(:after-block-end . ,start)
0))) ;; Subtract one indentation level.
(- context-indentation adjustment))) (goto-char start)
;; When point is on a dedenter statement, search for the (- (current-indentation) python-indent-offset))
;; opening block that corresponds to it and use its (`(:at-dedenter-block-start . ,_)
;; indentation. If no opening block is found just remove ;; List all possible indentation levels from opening blocks.
;; indentation as this is an invalid python file. (let ((opening-block-start-points
(`dedenter-statement (python-info-dedenter-opening-block-positions)))
(let ((block-start-point (if (not opening-block-start-points)
(python-info-dedenter-opening-block-position))) 0 ; if not found default to first column
(save-excursion (mapcar (lambda (pos)
(if (not block-start-point) (save-excursion
0 (goto-char pos)
(goto-char block-start-point) (current-indentation)))
(current-indentation))))) opening-block-start-points))))
;; When inside of a string, do nothing. just use the current (`(,(or :inside-paren-newline-start-from-block) . ,start)
;; indentation. XXX: perhaps it would be a good idea to ;; Add two indentation levels to make the suite stand out.
;; invoke standard text indentation here (goto-char start)
(`inside-string (+ (current-indentation) (* python-indent-offset 2)))))))
(goto-char context-start)
(current-indentation)) (defun python-indent--calculate-levels (indentation)
;; After backslash we have several possibilities. "Calculate levels list given INDENTATION.
(`after-backslash Argument INDENTATION can either be an integer or a list of
(cond integers. Levels are returned in ascending order, and in the
;; Check if current line is a dot continuation. For this case INDENTATION is a list, this order is enforced."
;; the current line must start with a dot and previous (if (listp indentation)
;; line must contain a dot too. (sort (copy-sequence indentation) #'<)
((save-excursion (let* ((remainder (% indentation python-indent-offset))
(back-to-indentation) (steps (/ (- indentation remainder) python-indent-offset))
(when (looking-at "\\.") (levels (mapcar (lambda (step)
;; If after moving one line back point is inside a paren it (* python-indent-offset step))
;; needs to move back until it's not anymore (number-sequence steps 0 -1))))
(while (prog2 (reverse
(forward-line -1) (if (not (zerop remainder))
(and (not (bobp)) (cons indentation levels)
(python-syntax-context 'paren)))) levels)))))
(goto-char (line-end-position))
(while (and (re-search-backward (defun python-indent--previous-level (levels indentation)
"\\." (line-beginning-position) t) "Return previous level from LEVELS relative to INDENTATION."
(python-syntax-context-type))) (let* ((levels (sort (copy-sequence levels) #'>))
(if (and (looking-at "\\.") (default (car levels)))
(not (python-syntax-context-type))) (catch 'return
;; The indentation is the same column of the (dolist (level levels)
;; first matching dot that's not inside a (when (funcall #'< level indentation)
;; comment, a string or a paren (throw 'return level)))
(current-column) default)))
;; No dot found on previous line, just add another
;; indentation level. (defun python-indent-calculate-indentation (&optional previous)
(+ (current-indentation) python-indent-offset))))) "Calculate indentation.
;; Check if prev line is a block continuation Get indentation of PREVIOUS level when argument is non-nil.
((let ((block-continuation-start Return the max level of the cycle when indentation reaches the
(python-info-block-continuation-line-p))) minimum."
(when block-continuation-start (let* ((indentation (python-indent--calculate-indentation))
;; If block-continuation-start is set jump to that (levels (python-indent--calculate-levels indentation)))
;; marker and use first column after the block start (if previous
;; as indentation value. (python-indent--previous-level levels (current-indentation))
(goto-char block-continuation-start) (apply #'max levels))))
(re-search-forward
(python-rx block-start (* space)) (defun python-indent-line (&optional previous)
(line-end-position) t) "Internal implementation of `python-indent-line-function'.
(current-column)))) Use the PREVIOUS level when argument is non-nil, otherwise indent
;; Check if current line is an assignment continuation to the maxium available level. When indentation is the minimum
((let ((assignment-continuation-start possible and PREVIOUS is non-nil, cycle back to the maximum
(python-info-assignment-continuation-line-p))) level."
(when assignment-continuation-start (let ((follow-indentation-p
;; If assignment-continuation is set jump to that ;; Check if point is within indentation.
;; marker and use first column after the assignment (and (<= (line-beginning-position) (point))
;; operator as indentation value. (>= (+ (line-beginning-position)
(goto-char assignment-continuation-start) (current-indentation))
(current-column)))) (point)))))
(t (save-excursion
(forward-line -1) (indent-line-to
(goto-char (python-info-beginning-of-backslash)) (python-indent-calculate-indentation previous))
(if (save-excursion (python-info-dedenter-opening-block-message))
(and (when follow-indentation-p
(forward-line -1) (back-to-indentation))))
(goto-char
(or (python-info-beginning-of-backslash) (point)))
(python-info-line-ends-backslash-p)))
;; The two previous lines ended in a backslash so we must
;; respect previous line indentation.
(current-indentation)
;; What happens here is that we are dealing with the second
;; line of a backslash continuation, in that case we just going
;; to add one indentation level.
(+ (current-indentation) python-indent-offset)))))
;; When inside a paren there's a need to handle nesting
;; correctly
(`inside-paren
(cond
;; If current line closes the outermost open paren use the
;; current indentation of the context-start line.
((save-excursion
(skip-syntax-forward "\s" (line-end-position))
(when (and (looking-at (regexp-opt '(")" "]" "}")))
(progn
(forward-char 1)
(not (python-syntax-context 'paren))))
(goto-char context-start)
(current-indentation))))
;; If open paren is contained on a line by itself add another
;; indentation level, else look for the first word after the
;; opening paren and use it's column position as indentation
;; level.
((let* ((content-starts-in-newline)
(indent
(save-excursion
(if (setq content-starts-in-newline
(progn
(goto-char context-start)
(forward-char)
(save-restriction
(narrow-to-region
(line-beginning-position)
(line-end-position))
(python-util-forward-comment))
(looking-at "$")))
(+ (current-indentation) python-indent-offset)
(current-column)))))
;; Adjustments
(cond
;; If current line closes a nested open paren de-indent one
;; level.
((progn
(back-to-indentation)
(looking-at (regexp-opt '(")" "]" "}"))))
(- indent python-indent-offset))
;; If the line of the opening paren that wraps the current
;; line starts a block add another level of indentation to
;; follow new pep8 recommendation. See: http://ur1.ca/5rojx
((save-excursion
(when (and content-starts-in-newline
(progn
(goto-char context-start)
(back-to-indentation)
(looking-at (python-rx block-start))))
(+ indent python-indent-offset))))
(t indent)))))))))))
(defun python-indent-calculate-levels () (defun python-indent-calculate-levels ()
"Calculate `python-indent-levels' and reset `python-indent-current-level'." "Return possible indentation levels."
(if (or (python-info-continuation-line-p) (python-indent--calculate-levels
(not (python-info-dedenter-statement-p))) (python-indent--calculate-indentation)))
;; XXX: This asks for a refactor. Even if point is on a
;; dedenter statement, it could be multiline and in that case
;; the continuation lines should be indented with normal rules.
(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'."
(setq python-indent-current-level (1- python-indent-current-level))
(when (< python-indent-current-level 0)
(setq python-indent-current-level (1- (length python-indent-levels)))))
(defun python-indent-line (&optional force-toggle)
"Internal implementation of `python-indent-line-function'.
Uses the offset calculated in
`python-indent-calculate-indentation' and available levels
indicated by the variable `python-indent-levels' to set the
current indentation.
When the variable `last-command' is equal to one of the symbols
inside `python-indent-trigger-commands' or FORCE-TOGGLE is
non-nil it cycles levels indicated in the variable
`python-indent-levels' by setting the current level in the
variable `python-indent-current-level'.
When the variable `last-command' is not equal to one of the
symbols inside `python-indent-trigger-commands' and FORCE-TOGGLE
is nil it calculates possible indentation levels and saves them
in the variable `python-indent-levels'. Afterwards it sets the
variable `python-indent-current-level' correctly so offset is
equal to
(nth python-indent-current-level python-indent-levels)"
(or
(and (or (and (memq this-command python-indent-trigger-commands)
(eq last-command this-command))
force-toggle)
(not (equal python-indent-levels '(0)))
(or (python-indent-toggle-levels) t))
(python-indent-calculate-levels))
(let* ((starting-pos (point-marker))
(indent-ending-position
(+ (line-beginning-position) (current-indentation)))
(follow-indentation-p
(or (bolp)
(and (<= (line-beginning-position) starting-pos)
(>= indent-ending-position starting-pos))))
(next-indent (nth python-indent-current-level python-indent-levels)))
(unless (= next-indent (current-indentation))
(beginning-of-line)
(delete-horizontal-space)
(indent-to next-indent)
(goto-char starting-pos))
(and follow-indentation-p (back-to-indentation)))
(python-info-dedenter-opening-block-message))
(defun python-indent-line-function () (defun python-indent-line-function ()
"`indent-line-function' for Python mode. "`indent-line-function' for Python mode.
See `python-indent-line' for details." When the variable `last-command' is equal to one of the symbols
(python-indent-line)) inside `python-indent-trigger-commands' it cycles possible
indentation levels from right to left."
(python-indent-line
(and (memq this-command python-indent-trigger-commands)
(eq last-command this-command))))
(defun python-indent-dedent-line () (defun python-indent-dedent-line ()
"De-indent current line." "De-indent current line."
(interactive "*") (interactive "*")
(when (and (not (python-syntax-comment-or-string-p)) (when (and (not (bolp))
(<= (point-marker) (save-excursion (not (python-syntax-comment-or-string-p))
(back-to-indentation) (= (+ (line-beginning-position)
(point-marker))) (current-indentation))
(> (current-column) 0)) (point)))
(python-indent-line t) (python-indent-line t)
t)) t))
(defun python-indent-dedent-line-backspace (arg) (defun python-indent-dedent-line-backspace (arg)
"De-indent current line. "De-indent current line.
Argument ARG is passed to `backward-delete-char-untabify' when Argument ARG is passed to `backward-delete-char-untabify' when
point is not in between the indentation." point is not in between the indentation."
(interactive "*p") (interactive "*p")
(when (not (python-indent-dedent-line)) (unless (python-indent-dedent-line)
(backward-delete-char-untabify arg))) (backward-delete-char-untabify arg)))
(put 'python-indent-dedent-line-backspace 'delete-selection 'supersede) (put 'python-indent-dedent-line-backspace 'delete-selection 'supersede)
(defun python-indent-region (start end) (defun python-indent-region (start end)

View file

@ -1,3 +1,25 @@
2015-01-26 Fabián Ezequiel Gallina <fgallina@gnu.org>
* automated/python-tests.el (python-indent-pep8-1)
(python-indent-pep8-2, python-indent-pep8-3)
(python-indent-after-comment-1, python-indent-after-comment-2)
(python-indent-inside-paren-1, python-indent-inside-paren-2)
(python-indent-after-block-1, python-indent-after-block-2)
(python-indent-after-backslash-1, python-indent-after-backslash-2)
(python-indent-after-backslash-3, python-indent-block-enders-1)
(python-indent-block-enders-2, python-indent-block-enders-3)
(python-indent-block-enders-4, python-indent-block-enders-5)
(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-indent-dedenters-8): Fix tests.
(python-indent-base-case, python-indent-after-block-3)
(python-indent-after-backslash-5, python-indent-inside-paren-3)
(python-indent-inside-paren-4, python-indent-inside-paren-5)
(python-indent-inside-paren-6, python-indent-inside-string-1)
(python-indent-inside-string-2, python-indent-inside-string-3)
(python-indent-dedent-line-backspace-1): New Tests.
2015-01-24 Glenn Morris <rgm@gnu.org> 2015-01-24 Glenn Morris <rgm@gnu.org>
* automated/regexp-tests.el: Require regexp-opt, which is * automated/regexp-tests.el: Require regexp-opt, which is

View file

@ -174,13 +174,13 @@ aliqua."
foo = long_function_name(var_one, var_two, foo = long_function_name(var_one, var_two,
var_three, var_four) var_three, var_four)
" "
(should (eq (car (python-indent-context)) 'no-indent)) (should (eq (car (python-indent-context)) :no-indent))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "foo = long_function_name(var_one, var_two,") (python-tests-look-at "foo = long_function_name(var_one, var_two,")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "var_three, var_four)") (python-tests-look-at "var_three, var_four)")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 25)))) (should (= (python-indent-calculate-indentation) 25))))
(ert-deftest python-indent-pep8-2 () (ert-deftest python-indent-pep8-2 ()
@ -192,19 +192,22 @@ def long_function_name(
var_four): var_four):
print (var_one) print (var_one)
" "
(should (eq (car (python-indent-context)) 'no-indent)) (should (eq (car (python-indent-context)) :no-indent))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "def long_function_name(") (python-tests-look-at "def long_function_name(")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "var_one, var_two, var_three,") (python-tests-look-at "var_one, var_two, var_three,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-newline-start-from-block))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "var_four):") (python-tests-look-at "var_four):")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-newline-start-from-block))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "print (var_one)") (python-tests-look-at "print (var_one)")
(should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (eq (car (python-indent-context))
:after-block-start))
(should (= (python-indent-calculate-indentation) 4)))) (should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-pep8-3 () (ert-deftest python-indent-pep8-3 ()
@ -215,18 +218,34 @@ foo = long_function_name(
var_one, var_two, var_one, var_two,
var_three, var_four) var_three, var_four)
" "
(should (eq (car (python-indent-context)) 'no-indent)) (should (eq (car (python-indent-context)) :no-indent))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "foo = long_function_name(") (python-tests-look-at "foo = long_function_name(")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "var_one, var_two,") (python-tests-look-at "var_one, var_two,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "var_three, var_four)") (python-tests-look-at "var_three, var_four)")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 4)))) (should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-base-case ()
"Check base case does not trigger errors."
(python-tests-with-temp-buffer
"
"
(goto-char (point-min))
(should (eq (car (python-indent-context)) :no-indent))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-after-comment-1 () (ert-deftest python-indent-after-comment-1 ()
"The most simple after-comment case that shouldn't fail." "The most simple after-comment case that shouldn't fail."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
@ -240,23 +259,23 @@ class Blag(object):
# with the exception with which the first child failed. # with the exception with which the first child failed.
" "
(python-tests-look-at "# We only complete") (python-tests-look-at "# We only complete")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "# terminal state") (python-tests-look-at "# terminal state")
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "# with the exception") (python-tests-look-at "# with the exception")
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
;; This one indents relative to previous block, even given the fact ;; This one indents relative to previous block, even given the fact
;; that it was under-indented. ;; that it was under-indented.
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "# terminal state" -1) (python-tests-look-at "# terminal state" -1)
;; It doesn't hurt to check again. ;; It doesn't hurt to check again.
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
(python-indent-line) (python-indent-line)
(should (= (current-indentation) 8)) (should (= (current-indentation) 8))
(python-tests-look-at "# with the exception") (python-tests-look-at "# with the exception")
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
;; Now everything should be lined up. ;; Now everything should be lined up.
(should (= (python-indent-calculate-indentation) 8)))) (should (= (python-indent-calculate-indentation) 8))))
@ -275,33 +294,33 @@ now_we_do_mess_cause_this_is_not_a_comment = 1
# yeah, that. # yeah, that.
" "
(python-tests-look-at "# I don't do much") (python-tests-look-at "# I don't do much")
(should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "return arg") (python-tests-look-at "return arg")
;; Comment here just gets ignored, this line is not a comment so ;; Comment here just gets ignored, this line is not a comment so
;; the rules won't apply here. ;; the rules won't apply here.
(should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "# This comment is badly") (python-tests-look-at "# This comment is badly")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-block-end))
;; The return keyword moves indentation backwards 4 spaces, but ;; The return keyword moves indentation backwards 4 spaces, but
;; let's assume this comment was placed there because the user ;; let's assume this comment was placed there because the user
;; wanted to (manually adding spaces or whatever). ;; wanted to (manually adding spaces or whatever).
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "# but we won't mess") (python-tests-look-at "# but we won't mess")
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
;; Behave the same for blank lines: potentially a comment. ;; Behave the same for blank lines: potentially a comment.
(forward-line 1) (forward-line 1)
(should (eq (car (python-indent-context)) 'after-comment)) (should (eq (car (python-indent-context)) :after-comment))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "now_we_do_mess") (python-tests-look-at "now_we_do_mess")
;; Here is where comment indentation starts to get ignored and ;; Here is where comment indentation starts to get ignored and
;; where the user can't freely indent anymore. ;; where the user can't freely indent anymore.
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "# yeah, that.") (python-tests-look-at "# yeah, that.")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)))) (should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-inside-paren-1 () (ert-deftest python-indent-inside-paren-1 ()
@ -325,49 +344,53 @@ data = {
} }
" "
(python-tests-look-at "data = {") (python-tests-look-at "data = {")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "'key':") (python-tests-look-at "'key':")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "{") (python-tests-look-at "{")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "'objlist': [") (python-tests-look-at "'objlist': [")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "{") (python-tests-look-at "{")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 12)) (should (= (python-indent-calculate-indentation) 12))
(python-tests-look-at "'pk': 1,") (python-tests-look-at "'pk': 1,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 16)) (should (= (python-indent-calculate-indentation) 16))
(python-tests-look-at "'name': 'first',") (python-tests-look-at "'name': 'first',")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 16)) (should (= (python-indent-calculate-indentation) 16))
(python-tests-look-at "},") (python-tests-look-at "},")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 12)) (should (= (python-indent-calculate-indentation) 12))
(python-tests-look-at "{") (python-tests-look-at "{")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 12)) (should (= (python-indent-calculate-indentation) 12))
(python-tests-look-at "'pk': 2,") (python-tests-look-at "'pk': 2,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 16)) (should (= (python-indent-calculate-indentation) 16))
(python-tests-look-at "'name': 'second',") (python-tests-look-at "'name': 'second',")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 16)) (should (= (python-indent-calculate-indentation) 16))
(python-tests-look-at "}") (python-tests-look-at "}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 12)) (should (= (python-indent-calculate-indentation) 12))
(python-tests-look-at "]") (python-tests-look-at "]")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "}") (python-tests-look-at "}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "}") (python-tests-look-at "}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
(should (= (python-indent-calculate-indentation) 0)))) (should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-inside-paren-2 () (ert-deftest python-indent-inside-paren-2 ()
@ -384,43 +407,121 @@ data = {'key': {
}} }}
" "
(python-tests-look-at "data = {") (python-tests-look-at "data = {")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "'objlist': [") (python-tests-look-at "'objlist': [")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "{'pk': 1,") (python-tests-look-at "{'pk': 1,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "'name': 'first'},") (python-tests-look-at "'name': 'first'},")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 9)) (should (= (python-indent-calculate-indentation) 9))
(python-tests-look-at "{'pk': 2,") (python-tests-look-at "{'pk': 2,")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "'name': 'second'}") (python-tests-look-at "'name': 'second'}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 9)) (should (= (python-indent-calculate-indentation) 9))
(python-tests-look-at "]") (python-tests-look-at "]")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "}}") (python-tests-look-at "}}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context))
:inside-paren-at-closing-nested-paren))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "}") (python-tests-look-at "}")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
(should (= (python-indent-calculate-indentation) 0)))) (should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-inside-paren-3 ()
"The simplest case possible."
(python-tests-with-temp-buffer
"
data = ('these',
'are',
'the',
'tokens')
"
(python-tests-look-at "data = ('these',")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 8))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 8))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 8))))
(ert-deftest python-indent-inside-paren-4 ()
"Respect indentation of first column."
(python-tests-with-temp-buffer
"
data = [ [ 'these', 'are'],
['the', 'tokens' ] ]
"
(python-tests-look-at "data = [ [ 'these', 'are'],")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 9))))
(ert-deftest python-indent-inside-paren-5 ()
"Test when :inside-paren initial parens are skipped in context start."
(python-tests-with-temp-buffer
"
while ((not some_condition) and
another_condition):
do_something_interesting(
with_some_arg)
"
(python-tests-look-at "while ((not some_condition) and")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 7))
(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)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 8))))
(ert-deftest python-indent-inside-paren-6 ()
"This should be aligned.."
(python-tests-with-temp-buffer
"
CHOICES = (('some', 'choice'),
('another', 'choice'),
('more', 'choices'))
"
(python-tests-look-at "CHOICES = (('some', 'choice'),")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 11))
(forward-line 1)
(should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 11))))
(ert-deftest python-indent-after-block-1 () (ert-deftest python-indent-after-block-1 ()
"The most simple after-block case that shouldn't fail." "The most simple after-block case that shouldn't fail."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
def foo(a, b, c=True): def foo(a, b, c=True):
" "
(should (eq (car (python-indent-context)) 'no-indent)) (should (eq (car (python-indent-context)) :no-indent))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(goto-char (point-max)) (goto-char (point-max))
(should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4)))) (should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-after-block-2 () (ert-deftest python-indent-after-block-2 ()
@ -432,9 +533,28 @@ def foo(a, b, c={
}): }):
" "
(goto-char (point-max)) (goto-char (point-max))
(should (eq (car (python-indent-context)) 'after-beginning-of-block)) (should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4)))) (should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-after-block-3 ()
"A weird (malformed) sample, usually found in python shells."
(python-tests-with-temp-buffer
"
In [1]:
def func():
pass
In [2]:
something
"
(python-tests-look-at "pass")
(should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "something")
(end-of-line)
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-after-backslash-1 () (ert-deftest python-indent-after-backslash-1 ()
"The most common case." "The most common case."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
@ -444,16 +564,16 @@ from foo.bar.baz import something, something_1 \\\\
something_4, something_5 something_4, something_5
" "
(python-tests-look-at "from foo.bar.baz import something, something_1") (python-tests-look-at "from foo.bar.baz import something, something_1")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "something_2 something_3,") (python-tests-look-at "something_2 something_3,")
(should (eq (car (python-indent-context)) 'after-backslash)) (should (eq (car (python-indent-context)) :after-backslash-first-line))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "something_4, something_5") (python-tests-look-at "something_4, something_5")
(should (eq (car (python-indent-context)) 'after-backslash)) (should (eq (car (python-indent-context)) :after-backslash))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(goto-char (point-max)) (goto-char (point-max))
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)))) (should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-after-backslash-2 () (ert-deftest python-indent-after-backslash-2 ()
@ -471,40 +591,104 @@ objects = Thing.objects.all() \\\\
.values_list() .values_list()
" "
(python-tests-look-at "objects = Thing.objects.all()") (python-tests-look-at "objects = Thing.objects.all()")
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at ".filter(") (python-tests-look-at ".filter(")
(should (eq (car (python-indent-context)) 'after-backslash)) (should (eq (car (python-indent-context))
:after-backslash-dotted-continuation))
(should (= (python-indent-calculate-indentation) 23)) (should (= (python-indent-calculate-indentation) 23))
(python-tests-look-at "type='toy',") (python-tests-look-at "type='toy',")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 27)) (should (= (python-indent-calculate-indentation) 27))
(python-tests-look-at "status='bought'") (python-tests-look-at "status='bought'")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 27)) (should (= (python-indent-calculate-indentation) 27))
(python-tests-look-at ") \\\\") (python-tests-look-at ") \\\\")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
(should (= (python-indent-calculate-indentation) 23)) (should (= (python-indent-calculate-indentation) 23))
(python-tests-look-at ".aggregate(") (python-tests-look-at ".aggregate(")
(should (eq (car (python-indent-context)) 'after-backslash)) (should (eq (car (python-indent-context))
:after-backslash-dotted-continuation))
(should (= (python-indent-calculate-indentation) 23)) (should (= (python-indent-calculate-indentation) 23))
(python-tests-look-at "Sum('amount')") (python-tests-look-at "Sum('amount')")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-newline-start))
(should (= (python-indent-calculate-indentation) 27)) (should (= (python-indent-calculate-indentation) 27))
(python-tests-look-at ") \\\\") (python-tests-look-at ") \\\\")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
(should (= (python-indent-calculate-indentation) 23)) (should (= (python-indent-calculate-indentation) 23))
(python-tests-look-at ".values_list()") (python-tests-look-at ".values_list()")
(should (eq (car (python-indent-context)) 'after-backslash)) (should (eq (car (python-indent-context))
:after-backslash-dotted-continuation))
(should (= (python-indent-calculate-indentation) 23)) (should (= (python-indent-calculate-indentation) 23))
(forward-line 1) (forward-line 1)
(should (eq (car (python-indent-context)) 'after-line)) (should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0)))) (should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-after-backslash-3 ()
"Backslash continuation from block start."
(python-tests-with-temp-buffer
"
with open('/path/to/some/file/you/want/to/read') as file_1, \\\\
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())
"
(python-tests-look-at
"with open('/path/to/some/file/you/want/to/read') as file_1, \\\\")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at
"open('/path/to/some/file/being/written', 'w') as file_2")
(should (eq (car (python-indent-context))
:after-backslash-block-continuation))
(should (= (python-indent-calculate-indentation) 5))
(python-tests-look-at "file_2.write(file_1.read())")
(should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-after-backslash-4 ()
"Backslash continuation from assignment."
(python-tests-with-temp-buffer
"
super_awful_assignment = some_calculation() and \\\\
another_calculation() and \\\\
some_final_calculation()
"
(python-tests-look-at
"super_awful_assignment = some_calculation() and \\\\")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "another_calculation() and \\\\")
(should (eq (car (python-indent-context))
:after-backslash-assignment-continuation))
(should (= (python-indent-calculate-indentation) 25))
(python-tests-look-at "some_final_calculation()")
(should (eq (car (python-indent-context)) :after-backslash))
(should (= (python-indent-calculate-indentation) 25))))
(ert-deftest python-indent-after-backslash-5 ()
"Dotted continuation bizarre example."
(python-tests-with-temp-buffer
"
def delete_all_things():
Thing \\\\
.objects.all() \\\\
.delete()
"
(python-tests-look-at "Thing \\\\")
(should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at ".objects.all() \\\\")
(should (eq (car (python-indent-context)) :after-backslash-first-line))
(should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at ".delete()")
(should (eq (car (python-indent-context))
:after-backslash-dotted-continuation))
(should (= (python-indent-calculate-indentation) 16))))
(ert-deftest python-indent-block-enders-1 () (ert-deftest python-indent-block-enders-1 ()
"Test de-indentation for pass keyword." "Test de-indentation for pass keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
Class foo(object): Class foo(object):
def bar(self): def bar(self):
@ -516,17 +700,18 @@ Class foo(object):
else: else:
pass pass
" "
(python-tests-look-at "3)") (python-tests-look-at "3)")
(forward-line 1) (forward-line 1)
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "pass") (python-tests-look-at "pass")
(forward-line 1) (forward-line 1)
(should (= (python-indent-calculate-indentation) 8)))) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 8))))
(ert-deftest python-indent-block-enders-2 () (ert-deftest python-indent-block-enders-2 ()
"Test de-indentation for return keyword." "Test de-indentation for return keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
Class foo(object): Class foo(object):
'''raise lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do '''raise lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
@ -539,64 +724,68 @@ Class foo(object):
2, 2,
3) 3)
" "
(python-tests-look-at "def") (python-tests-look-at "def")
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "if") (python-tests-look-at "if")
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(python-tests-look-at "return") (python-tests-look-at "return")
(should (= (python-indent-calculate-indentation) 12)) (should (= (python-indent-calculate-indentation) 12))
(goto-char (point-max)) (goto-char (point-max))
(should (= (python-indent-calculate-indentation) 8)))) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 8))))
(ert-deftest python-indent-block-enders-3 () (ert-deftest python-indent-block-enders-3 ()
"Test de-indentation for continue keyword." "Test de-indentation for continue keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
for element in lst: for element in lst:
if element is None: if element is None:
continue continue
" "
(python-tests-look-at "if") (python-tests-look-at "if")
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "continue") (python-tests-look-at "continue")
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(forward-line 1) (forward-line 1)
(should (= (python-indent-calculate-indentation) 4)))) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-block-enders-4 () (ert-deftest python-indent-block-enders-4 ()
"Test de-indentation for break keyword." "Test de-indentation for break keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
for element in lst: for element in lst:
if element is None: if element is None:
break break
" "
(python-tests-look-at "if") (python-tests-look-at "if")
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "break") (python-tests-look-at "break")
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(forward-line 1) (forward-line 1)
(should (= (python-indent-calculate-indentation) 4)))) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-block-enders-5 () (ert-deftest python-indent-block-enders-5 ()
"Test de-indentation for raise keyword." "Test de-indentation for raise keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
for element in lst: for element in lst:
if element is None: if element is None:
raise ValueError('Element cannot be None') raise ValueError('Element cannot be None')
" "
(python-tests-look-at "if") (python-tests-look-at "if")
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "raise") (python-tests-look-at "raise")
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(forward-line 1) (forward-line 1)
(should (= (python-indent-calculate-indentation) 4)))) (should (eq (car (python-indent-context)) :after-block-end))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-dedenters-1 () (ert-deftest python-indent-dedenters-1 ()
"Test de-indentation for the elif keyword." "Test de-indentation for the elif keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if save: if save:
try: try:
write_to_disk(data) write_to_disk(data)
@ -604,15 +793,15 @@ if save:
cleanup() cleanup()
elif elif
" "
(python-tests-look-at "elif\n") (python-tests-look-at "elif\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(should (equal (python-indent-calculate-levels) '(0))))) (should (= (python-indent-calculate-indentation t) 0))))
(ert-deftest python-indent-dedenters-2 () (ert-deftest python-indent-dedenters-2 ()
"Test de-indentation for the else keyword." "Test de-indentation for the else keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if save: if save:
try: try:
write_to_disk(data) write_to_disk(data)
@ -627,43 +816,50 @@ if save:
finally: finally:
data.free() data.free()
" "
(python-tests-look-at "else\n") (python-tests-look-at "else\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(should (equal (python-indent-calculate-levels) '(0 4 8))))) (python-indent-line t)
(should (= (python-indent-calculate-indentation t) 4))
(python-indent-line t)
(should (= (python-indent-calculate-indentation t) 0))
(python-indent-line t)
(should (= (python-indent-calculate-indentation t) 8))))
(ert-deftest python-indent-dedenters-3 () (ert-deftest python-indent-dedenters-3 ()
"Test de-indentation for the except keyword." "Test de-indentation for the except keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if save: if save:
try: try:
write_to_disk(data) write_to_disk(data)
except except
" "
(python-tests-look-at "except\n") (python-tests-look-at "except\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(should (equal (python-indent-calculate-levels) '(4))))) (python-indent-line t)
(should (= (python-indent-calculate-indentation t) 4))))
(ert-deftest python-indent-dedenters-4 () (ert-deftest python-indent-dedenters-4 ()
"Test de-indentation for the finally keyword." "Test de-indentation for the finally keyword."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if save: if save:
try: try:
write_to_disk(data) write_to_disk(data)
finally finally
" "
(python-tests-look-at "finally\n") (python-tests-look-at "finally\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 4)) (should (= (python-indent-calculate-indentation) 4))
(should (equal (python-indent-calculate-levels) '(4))))) (python-indent-line t)
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-dedenters-5 () (ert-deftest python-indent-dedenters-5 ()
"Test invalid levels are skipped in a complex example." "Test invalid levels are skipped in a complex example."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if save: if save:
try: try:
write_to_disk(data) write_to_disk(data)
@ -676,29 +872,31 @@ if save:
do_cleanup() do_cleanup()
else else
" "
(python-tests-look-at "else\n") (python-tests-look-at "else\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 8)) (should (= (python-indent-calculate-indentation) 8))
(should (equal (python-indent-calculate-levels) '(0 8))))) (should (= (python-indent-calculate-indentation t) 0))
(python-indent-line t)
(should (= (python-indent-calculate-indentation t) 8))))
(ert-deftest python-indent-dedenters-6 () (ert-deftest python-indent-dedenters-6 ()
"Test indentation is zero when no opening block for dedenter." "Test indentation is zero when no opening block for dedenter."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
try: try:
# if save: # if save:
write_to_disk(data) write_to_disk(data)
else else
" "
(python-tests-look-at "else\n") (python-tests-look-at "else\n")
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(should (equal (python-indent-calculate-levels) '(0))))) (should (= (python-indent-calculate-indentation t) 0))))
(ert-deftest python-indent-dedenters-7 () (ert-deftest python-indent-dedenters-7 ()
"Test indentation case from Bug#15163." "Test indentation case from Bug#15163."
(python-tests-with-temp-buffer (python-tests-with-temp-buffer
" "
if a: if a:
if b: if b:
pass pass
@ -706,10 +904,10 @@ if a:
pass pass
else: else:
" "
(python-tests-look-at "else:" 2) (python-tests-look-at "else:" 2)
(should (eq (car (python-indent-context)) 'dedenter-statement)) (should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 0)) (should (= (python-indent-calculate-indentation) 0))
(should (equal (python-indent-calculate-levels) '(0))))) (should (= (python-indent-calculate-indentation t) 0))))
(ert-deftest python-indent-dedenters-8 () (ert-deftest python-indent-dedenters-8 ()
"Test indentation for Bug#18432." "Test indentation for Bug#18432."
@ -721,10 +919,99 @@ if (a == 1 or
elif (a == 3 or elif (a == 3 or
a == 4): a == 4):
" "
(python-tests-look-at "elif (a == 3 or")
(should (eq (car (python-indent-context)) :at-dedenter-block-start))
(should (= (python-indent-calculate-indentation) 0))
(should (= (python-indent-calculate-indentation t) 0))
(python-tests-look-at "a == 4):\n") (python-tests-look-at "a == 4):\n")
(should (eq (car (python-indent-context)) 'inside-paren)) (should (eq (car (python-indent-context)) :inside-paren))
(should (= (python-indent-calculate-indentation) 6)) (should (= (python-indent-calculate-indentation) 6))
(should (equal (python-indent-calculate-levels) '(0 4 6))))) (python-indent-line)
(should (= (python-indent-calculate-indentation t) 4))
(python-indent-line t)
(should (= (python-indent-calculate-indentation t) 0))
(python-indent-line t)
(should (= (python-indent-calculate-indentation t) 6))))
(ert-deftest python-indent-inside-string-1 ()
"Test indentation for strings."
(python-tests-with-temp-buffer
"
multiline = '''
bunch
of
lines
'''
"
(python-tests-look-at "multiline = '''")
(should (eq (car (python-indent-context)) :after-line))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "bunch")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "of")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "lines")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 0))
(python-tests-look-at "'''")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 0))))
(ert-deftest python-indent-inside-string-2 ()
"Test indentation for docstrings."
(python-tests-with-temp-buffer
"
def fn(a, b, c=True):
'''docstring
bunch
of
lines
'''
"
(python-tests-look-at "'''docstring")
(should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "bunch")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "of")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "lines")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "'''")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-inside-string-3 ()
"Test indentation for nested strings."
(python-tests-with-temp-buffer
"
def fn(a, b, c=True):
some_var = '''
bunch
of
lines
'''
"
(python-tests-look-at "some_var = '''")
(should (eq (car (python-indent-context)) :after-block-start))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "bunch")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "of")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "lines")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))
(python-tests-look-at "'''")
(should (eq (car (python-indent-context)) :inside-string))
(should (= (python-indent-calculate-indentation) 4))))
(ert-deftest python-indent-electric-colon-1 () (ert-deftest python-indent-electric-colon-1 ()
"Test indentation case from Bug#18228." "Test indentation case from Bug#18228."