From 5485e3e5b28f82b46d139c63b8ab77ed1d7d61c9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Tue, 27 Jan 2015 00:17:24 -0300 Subject: [PATCH] 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. --- lisp/ChangeLog | 20 + lisp/progmodes/python.el | 695 +++++++++++++++++---------------- test/ChangeLog | 22 ++ test/automated/python-tests.el | 565 ++++++++++++++++++++------- 4 files changed, 828 insertions(+), 474 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index a2467e6b00f..b3bedaaffa9 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,23 @@ +2015-01-26 Fabián Ezequiel Gallina + + 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 * calc/calc-units.el (math-units-in-expr-p) diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 833c3d9a9c5..d0a83087554 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -447,9 +447,14 @@ The type returned can be `comment', `string' or `paren'." ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string)) ((nth 1 ppss) 'paren)))) -(defsubst python-syntax-comment-or-string-p () - "Return non-nil if point is inside 'comment or 'string." - (nth 8 (syntax-ppss))) +(defsubst python-syntax-comment-or-string-p (&optional ppss) + "Return non-nil if PPSS is inside 'comment or 'string." + (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 '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") (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) - "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 () "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))))))) (defun python-indent-context () - "Get information on indentation context. -Context information is returned with a cons with the form: - (STATUS . START) - -Where status can be any of the following symbols: - - * after-comment: When current line might continue a comment block - * inside-paren: If point in between (), {} or [] - * inside-string: If point is inside a string - * 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." + "Get information about the current indentation context. +Context is returned in a cons with the form (STATUS . START). + +STATUS can be one of the following: + +keyword +------- + +:after-comment + - Point is after a comment line. + - START is the position of the \"#\" character. +:inside-string + - Point is inside string. + - 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 (widen) - (let ((ppss (save-excursion (beginning-of-line) (syntax-ppss))) - (start)) - (cons - (cond - ;; Beginning of buffer - ((save-excursion - (goto-char (line-beginning-position)) - (bobp)) - 'no-indent) - ;; Comment continuation - ((save-excursion - (when (and - (or - (python-info-current-line-comment-p) - (python-info-current-line-empty-p)) - (progn - (forward-comment -1) - (python-info-current-line-comment-p))) - (setq start (point)) - 'after-comment))) - ;; Inside string - ((setq start (python-syntax-context 'string ppss)) - 'inside-string) - ;; Inside a paren - ((setq start (python-syntax-context 'paren ppss)) - 'inside-paren) - ;; After backslash - ((setq start (when (not (or (python-syntax-context 'string ppss) - (python-syntax-context 'comment ppss))) - (let ((line-beg-pos (line-number-at-pos))) - (python-info-line-ends-backslash-p - (1- line-beg-pos))))) - 'after-backslash) - ;; After beginning of block - ((setq start (save-excursion - (when (progn - (back-to-indentation) - (python-util-forward-comment -1) - (equal (char-before) ?:)) - ;; Move to the first block start that's not in within - ;; a string, comment or paren and that's not a - ;; continuation line. - (while (and (re-search-backward - (python-rx block-start) nil t) - (or - (python-syntax-context-type) - (python-info-continuation-line-p)))) - (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 + (let ((ppss (save-excursion + (beginning-of-line) + (syntax-ppss)))) + (cond + ;; Beginning of buffer. + ((= (line-number-at-pos) 1) + (cons :no-indent 0)) + ;; Comment continuation (maybe). + ((save-excursion + (when (and + (or + (python-info-current-line-comment-p) + (python-info-current-line-empty-p)) + (forward-comment -1) + (python-info-current-line-comment-p)) + (cons :after-comment (point))))) + ;; Inside a string. + ((let ((start (python-syntax-context 'string ppss))) + (when start + (cons :inside-string start)))) + ;; Inside a paren. + ((let* ((start (python-syntax-context 'paren ppss)) + (starts-in-newline + (when start + (save-excursion + (goto-char start) + (forward-char) + (not + (= (line-number-at-pos) + (progn + (python-util-forward-comment) + (line-number-at-pos)))))))) + (when start + (cond + ;; Current line only holds the closing paren. + ((save-excursion + (skip-syntax-forward " ") + (when (and (python-syntax-closing-paren-p) + (progn + (forward-char 1) + (not (python-syntax-context 'paren)))) + (cons :inside-paren-at-closing-paren start)))) + ;; Current line only holds a closing paren for nested. + ((save-excursion + (back-to-indentation) + (python-syntax-closing-paren-p)) + (cons :inside-paren-at-closing-nested-paren start)) + ;; This line starts from a opening block in its own line. + ((save-excursion + (goto-char start) + (when (and + starts-in-newline + (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) + (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 (rx (or whitespace ?\n))) + (skip-chars-backward " \t\n") (python-nav-beginning-of-statement) - (point-marker))) - 'after-line) - ;; Do not indent - (t 'no-indent)) - start)))) - -(defun python-indent-calculate-indentation () - "Calculate correct indentation offset for the current line." - (let* ((indentation-context (python-indent-context)) - (context-status (car indentation-context)) - (context-start (cdr indentation-context))) - (save-restriction - (widen) - (save-excursion - (pcase context-status - (`no-indent 0) - (`after-comment - (goto-char context-start) - (current-indentation)) - ;; When point is after beginning of block just add one level - ;; of indentation relative to the context-start - (`after-beginning-of-block - (goto-char context-start) - (+ (current-indentation) python-indent-offset)) - ;; When after a simple line just use previous line - ;; indentation. - (`after-line - (let* ((pair (save-excursion - (goto-char context-start) - (cons - (current-indentation) - (python-info-beginning-of-block-p)))) - (context-indentation (car pair)) - ;; TODO: Separate block enders into its own case. - (adjustment - (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 - (`inside-string - (goto-char context-start) - (current-indentation)) - ;; After backslash we have several possibilities. - (`after-backslash - (cond - ;; Check if current line is a dot continuation. For this - ;; the current line must start with a dot and previous - ;; line must contain a dot too. - ((save-excursion - (back-to-indentation) - (when (looking-at "\\.") - ;; If after moving one line back point is inside a paren it - ;; needs to move back until it's not anymore - (while (prog2 - (forward-line -1) - (and (not (bobp)) - (python-syntax-context 'paren)))) - (goto-char (line-end-position)) - (while (and (re-search-backward - "\\." (line-beginning-position) t) - (python-syntax-context-type))) - (if (and (looking-at "\\.") - (not (python-syntax-context-type))) - ;; The indentation is the same column of the - ;; first matching dot that's not inside a - ;; comment, a string or a paren - (current-column) - ;; No dot found on previous line, just add another - ;; indentation level. - (+ (current-indentation) python-indent-offset))))) - ;; Check if prev line is a block continuation - ((let ((block-continuation-start - (python-info-block-continuation-line-p))) - (when block-continuation-start - ;; If block-continuation-start is set jump to that - ;; marker and use first column after the block start - ;; as indentation value. - (goto-char block-continuation-start) - (re-search-forward - (python-rx block-start (* space)) - (line-end-position) t) - (current-column)))) - ;; Check if current line is an assignment continuation - ((let ((assignment-continuation-start - (python-info-assignment-continuation-line-p))) - (when assignment-continuation-start - ;; If assignment-continuation is set jump to that - ;; marker and use first column after the assignment - ;; operator as indentation value. - (goto-char assignment-continuation-start) - (current-column)))) - (t - (forward-line -1) - (goto-char (python-info-beginning-of-backslash)) - (if (save-excursion - (and - (forward-line -1) - (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 () - "Calculate `python-indent-levels' and reset `python-indent-current-level'." - (if (or (python-info-continuation-line-p) - (not (python-info-dedenter-statement-p))) - ;; 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) + (point)))) + (when start + (if (save-excursion + (python-util-forward-comment -1) + (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 () + "Internal implementation of `python-indent-calculate-indentation'. +May return an integer for the maximum possible indentation at +current context or a list of integers. The latter case is only +happening for :at-dedenter-block-start context since the +possibilities can be narrowed to especific indentation points." + (save-restriction + (widen) + (save-excursion + (pcase (python-indent-context) + (`(:no-indent . ,_) 0) + (`(,(or :after-line + :after-comment + :inside-string + :after-backslash + :inside-paren-at-closing-paren + :inside-paren-at-closing-nested-paren) . ,start) + ;; Copy previous indentation. + (goto-char start) + (current-indentation)) + (`(,(or :after-block-start + :after-backslash-first-line + :inside-paren-newline-start) . ,start) + ;; Add one indentation level. + (goto-char start) + (+ (current-indentation) python-indent-offset)) + (`(,(or :inside-paren + :after-backslash-block-continuation + :after-backslash-assignment-continuation + :after-backslash-dotted-continuation) . ,start) + ;; Use the column given by the context. + (goto-char start) + (current-column)) + (`(:after-block-end . ,start) + ;; Subtract one indentation level. + (goto-char start) + (- (current-indentation) python-indent-offset)) + (`(:at-dedenter-block-start . ,_) + ;; List all possible indentation levels from opening blocks. + (let ((opening-block-start-points + (python-info-dedenter-opening-block-positions))) + (if (not opening-block-start-points) + 0 ; if not found default to first column + (mapcar (lambda (pos) + (save-excursion + (goto-char pos) + (current-indentation))) + opening-block-start-points)))) + (`(,(or :inside-paren-newline-start-from-block) . ,start) + ;; Add two indentation levels to make the suite stand out. + (goto-char start) + (+ (current-indentation) (* python-indent-offset 2))))))) + +(defun python-indent--calculate-levels (indentation) + "Calculate levels list given INDENTATION. +Argument INDENTATION can either be an integer or a list of +integers. Levels are returned in ascending order, and in the +case INDENTATION is a list, this order is enforced." + (if (listp indentation) + (sort (copy-sequence indentation) #'<) + (let* ((remainder (% indentation python-indent-offset)) + (steps (/ (- indentation remainder) python-indent-offset)) + (levels (mapcar (lambda (step) + (* python-indent-offset step)) + (number-sequence steps 0 -1)))) + (reverse + (if (not (zerop remainder)) + (cons indentation levels) + levels))))) + +(defun python-indent--previous-level (levels indentation) + "Return previous level from LEVELS relative to INDENTATION." + (let* ((levels (sort (copy-sequence levels) #'>)) + (default (car levels))) + (catch 'return + (dolist (level levels) + (when (funcall #'< level indentation) + (throw 'return level))) + default))) + +(defun python-indent-calculate-indentation (&optional previous) + "Calculate indentation. +Get indentation of PREVIOUS level when argument is non-nil. +Return the max level of the cycle when indentation reaches the +minimum." + (let* ((indentation (python-indent--calculate-indentation)) + (levels (python-indent--calculate-levels indentation))) + (if previous + (python-indent--previous-level levels (current-indentation)) + (apply #'max levels)))) + +(defun python-indent-line (&optional previous) "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. +Use the PREVIOUS level when argument is non-nil, otherwise indent +to the maxium available level. When indentation is the minimum +possible and PREVIOUS is non-nil, cycle back to the maximum +level." + (let ((follow-indentation-p + ;; Check if point is within indentation. + (and (<= (line-beginning-position) (point)) + (>= (+ (line-beginning-position) + (current-indentation)) + (point))))) + (save-excursion + (indent-line-to + (python-indent-calculate-indentation previous)) + (python-info-dedenter-opening-block-message)) + (when follow-indentation-p + (back-to-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-calculate-levels () + "Return possible indentation levels." + (python-indent--calculate-levels + (python-indent--calculate-indentation))) (defun python-indent-line-function () "`indent-line-function' for Python mode. -See `python-indent-line' for details." - (python-indent-line)) +When the variable `last-command' is equal to one of the symbols +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 () "De-indent current line." (interactive "*") - (when (and (not (python-syntax-comment-or-string-p)) - (<= (point-marker) (save-excursion - (back-to-indentation) - (point-marker))) - (> (current-column) 0)) - (python-indent-line t) - t)) + (when (and (not (bolp)) + (not (python-syntax-comment-or-string-p)) + (= (+ (line-beginning-position) + (current-indentation)) + (point))) + (python-indent-line t) + t)) (defun python-indent-dedent-line-backspace (arg) "De-indent current line. Argument ARG is passed to `backward-delete-char-untabify' when point is not in between the indentation." (interactive "*p") - (when (not (python-indent-dedent-line)) + (unless (python-indent-dedent-line) (backward-delete-char-untabify arg))) + (put 'python-indent-dedent-line-backspace 'delete-selection 'supersede) (defun python-indent-region (start end) diff --git a/test/ChangeLog b/test/ChangeLog index 11bf34d901c..62876e9ecae 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,25 @@ +2015-01-26 Fabián Ezequiel Gallina + + * 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 * automated/regexp-tests.el: Require regexp-opt, which is diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index 35a7b99e416..5b77a1db49a 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el @@ -174,13 +174,13 @@ aliqua." foo = long_function_name(var_one, var_two, 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)) (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)) (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)))) (ert-deftest python-indent-pep8-2 () @@ -192,19 +192,22 @@ def long_function_name( var_four): 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)) (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)) (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)) (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)) (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)))) (ert-deftest python-indent-pep8-3 () @@ -215,18 +218,34 @@ foo = long_function_name( var_one, var_two, 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)) (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)) (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)) (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)))) +(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 () "The most simple after-comment case that shouldn't fail." (python-tests-with-temp-buffer @@ -240,23 +259,23 @@ class Blag(object): # with the exception with which the first child failed. " (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)) (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)) (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 ;; that it was under-indented. (should (= (python-indent-calculate-indentation) 4)) (python-tests-look-at "# terminal state" -1) ;; 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) (should (= (current-indentation) 8)) (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. (should (= (python-indent-calculate-indentation) 8)))) @@ -275,33 +294,33 @@ now_we_do_mess_cause_this_is_not_a_comment = 1 # yeah, that. " (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)) (python-tests-look-at "return arg") ;; Comment here just gets ignored, this line is not a comment so ;; 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)) (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 ;; let's assume this comment was placed there because the user ;; wanted to (manually adding spaces or whatever). (should (= (python-indent-calculate-indentation) 0)) (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)) ;; Behave the same for blank lines: potentially a comment. (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)) (python-tests-look-at "now_we_do_mess") ;; Here is where comment indentation starts to get ignored and ;; 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)) (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)))) (ert-deftest python-indent-inside-paren-1 () @@ -325,49 +344,53 @@ 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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)))) (ert-deftest python-indent-inside-paren-2 () @@ -384,43 +407,121 @@ data = {'key': { }} " (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)))) +(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 () "The most simple after-block case that shouldn't fail." (python-tests-with-temp-buffer " 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)) (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)))) (ert-deftest python-indent-after-block-2 () @@ -432,9 +533,28 @@ def foo(a, b, c={ }): " (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)))) +(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 () "The most common case." (python-tests-with-temp-buffer @@ -444,16 +564,16 @@ from foo.bar.baz import something, something_1 \\\\ something_4, something_5 " (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)) (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)) (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)) (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)))) (ert-deftest python-indent-after-backslash-2 () @@ -471,40 +591,104 @@ objects = Thing.objects.all() \\\\ .values_list() " (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)) (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)))) +(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 () "Test de-indentation for pass keyword." (python-tests-with-temp-buffer - " + " Class foo(object): def bar(self): @@ -516,17 +700,18 @@ Class foo(object): else: pass " - (python-tests-look-at "3)") - (forward-line 1) - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "pass") - (forward-line 1) - (should (= (python-indent-calculate-indentation) 8)))) + (python-tests-look-at "3)") + (forward-line 1) + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "pass") + (forward-line 1) + (should (eq (car (python-indent-context)) :after-block-end)) + (should (= (python-indent-calculate-indentation) 8)))) (ert-deftest python-indent-block-enders-2 () "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 @@ -539,64 +724,68 @@ Class foo(object): 2, 3) " - (python-tests-look-at "def") - (should (= (python-indent-calculate-indentation) 4)) - (python-tests-look-at "if") - (should (= (python-indent-calculate-indentation) 8)) - (python-tests-look-at "return") - (should (= (python-indent-calculate-indentation) 12)) - (goto-char (point-max)) - (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 (eq (car (python-indent-context)) :after-block-end)) + (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)))) + (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 (eq (car (python-indent-context)) :after-block-end)) + (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)))) + (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 (eq (car (python-indent-context)) :after-block-end)) + (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)))) + (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 (eq (car (python-indent-context)) :after-block-end)) + (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) @@ -604,15 +793,15 @@ if save: 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))))) + (python-tests-look-at "elif\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 0)) + (should (= (python-indent-calculate-indentation t) 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) @@ -627,43 +816,50 @@ if save: 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))))) + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 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 () "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))))) + (python-tests-look-at "except\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 4)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 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))))) + (python-tests-look-at "finally\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 4)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation) 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) @@ -676,29 +872,31 @@ if save: 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))))) + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 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 () "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))))) + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 0)) + (should (= (python-indent-calculate-indentation t) 0)))) (ert-deftest python-indent-dedenters-7 () "Test indentation case from Bug#15163." (python-tests-with-temp-buffer - " + " if a: if b: pass @@ -706,10 +904,10 @@ if a: 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))))) + (python-tests-look-at "else:" 2) + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 0)) + (should (= (python-indent-calculate-indentation t) 0)))) (ert-deftest python-indent-dedenters-8 () "Test indentation for Bug#18432." @@ -721,10 +919,99 @@ if (a == 1 or elif (a == 3 or 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") - (should (eq (car (python-indent-context)) 'inside-paren)) + (should (eq (car (python-indent-context)) :inside-paren)) (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 () "Test indentation case from Bug#18228." -- 2.39.2