-;;; python.el --- Python's flying circus support for Emacs
+;;; python.el --- Python's flying circus support for Emacs -*- coding: utf-8 -*-
-;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;; Copyright (C) 2003-2012 Free Software Foundation, Inc.
;; Author: Fabián E. Gallina <fabian@anue.biz>
;; URL: https://github.com/fgallina/python.el
-;; Version: 0.23.1
+;; Version: 0.24.2
;; Maintainer: FSF
;; Created: Jul 2010
;; Keywords: languages
-;; This file is NOT part of GNU Emacs.
+;; This file is part of GNU Emacs.
-;; python.el is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
-;; python.el is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
+;; GNU Emacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with python.el. If not, see <http://www.gnu.org/licenses/>.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; indentation bits extracted from original Dave Love's python.el
;; found in GNU/Emacs.
-;; While it probably has less features than Dave Love's python.el and
-;; PSF's python-mode.el it provides the main stuff you'll need while
-;; keeping it simple :)
-
;; Implements Syntax highlighting, Indentation, Movement, Shell
;; interaction, Shell completion, Shell virtualenv support, Pdb
;; tracking, Symbol completion, Skeletons, FFAP, Code Check, Eldoc,
;; Movement: `beginning-of-defun' and `end-of-defun' functions are
;; properly implemented. There are also specialized
-;; `forward-sentence' and `backward-sentence' replacements
-;; (`python-nav-forward-sentence', `python-nav-backward-sentence'
-;; respectively). Extra functions `python-nav-sentence-start' and
-;; `python-nav-sentence-end' are included to move to the beginning and
-;; to the end of a setence while taking care of multiline definitions.
+;; `forward-sentence' and `backward-sentence' replacements called
+;; `python-nav-forward-block', `python-nav-backward-block'
+;; respectively which navigate between beginning of blocks of code.
+;; Extra functions `python-nav-forward-statement',
+;; `python-nav-backward-statement', `python-nav-statement-start',
+;; `python-nav-statement-end', `python-nav-block-start' and
+;; `python-nav-block-end' are included but no bound to any key.
;; `python-nav-jump-to-defun' is provided and allows jumping to a
;; function or class definition quickly in the current buffer.
;; python-shell-interpreter-args ""
;; python-shell-prompt-regexp "In \\[[0-9]+\\]: "
;; python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
-;; python-shell-completion-setup-code ""
+;; python-shell-completion-setup-code
+;; "from IPython.core.completerlib import module_completion"
+;; python-shell-completion-module-string-code
+;; "';'.join(module_completion('''%s'''))\n"
;; python-shell-completion-string-code
-;; "';'.join(get_ipython().Completer.all_completions('''%s'''))\n")
+;; "';'.join(get_ipython().Completer.all_completions('''%s'''))\n")
;; For iPython 0.10 everything would be the same except for
-;; `python-shell-completion-string-code':
+;; `python-shell-completion-string-code' and
+;; `python-shell-completion-module-string-code':
;; (setq python-shell-completion-string-code
-;; "';'.join(__IP.complete('''%s'''))\n")
+;; "';'.join(__IP.complete('''%s'''))\n"
+;; python-shell-completion-module-string-code "")
+
+;; Unfortunately running iPython on Windows needs some more tweaking.
+;; The way you must set `python-shell-interpreter' and
+;; `python-shell-interpreter-args' is as follows:
+
+;; (setq
+;; python-shell-interpreter "C:\\Python27\\python.exe"
+;; python-shell-interpreter-args
+;; "-i C:\\Python27\\Scripts\\ipython-script.py")
+
+;; That will spawn the iPython process correctly (Of course you need
+;; to modify the paths according to your system).
;; Please note that the default completion system depends on the
;; readline module, so if you are using some Operating System that
;; "VIRTUAL_ENV=/path/to/env/"))
;; (python-shell-exec-path . ("/path/to/env/bin/"))
-;; Since the above is cumbersome and can be programatically
+;; Since the above is cumbersome and can be programmatically
;; calculated, the variable `python-shell-virtualenv-path' is
;; provided. When this variable is set with the path of the
;; virtualenv to use, `process-environment' and `exec-path' get proper
;; (setq python-shell-virtualenv-path "/path/to/env/")
+;; Also the `python-shell-extra-pythonpaths' variable have been
+;; introduced as simple way of adding paths to the PYTHONPATH without
+;; affecting existing values.
+
;; Pdb tracking: when you execute a block of code that contains some
;; call to pdb (or ipdb) it will prompt the block of code and will
;; follow the execution of pdb marking the current line with an arrow.
(let ((map (make-sparse-keymap)))
;; Movement
(substitute-key-definition 'backward-sentence
- 'python-nav-backward-sentence
+ 'python-nav-backward-block
map global-map)
(substitute-key-definition 'forward-sentence
- 'python-nav-forward-sentence
+ 'python-nav-forward-block
map global-map)
(define-key map "\C-c\C-j" 'python-nav-jump-to-defun)
;; Indent specific
(define-key map "\C-c\C-f" 'python-eldoc-at-point)
;; Utilities
(substitute-key-definition 'complete-symbol 'completion-at-point
- map global-map)
+ map global-map)
(easy-menu-define python-menu map "Python Mode menu"
`("Python"
- :help "Python-specific Features"
- ["Shift region left" python-indent-shift-left :active mark-active
- :help "Shift region left by a single indentation step"]
- ["Shift region right" python-indent-shift-right :active mark-active
- :help "Shift region right by a single indentation step"]
- "-"
- ["Start of def/class" beginning-of-defun
- :help "Go to start of outermost definition around point"]
- ["End of def/class" end-of-defun
- :help "Go to end of definition around point"]
- ["Mark def/class" mark-defun
- :help "Mark outermost definition around point"]
- ["Jump to def/class" python-nav-jump-to-defun
- :help "Jump to a class or function definition"]
+ :help "Python-specific Features"
+ ["Shift region left" python-indent-shift-left :active mark-active
+ :help "Shift region left by a single indentation step"]
+ ["Shift region right" python-indent-shift-right :active mark-active
+ :help "Shift region right by a single indentation step"]
+ "-"
+ ["Start of def/class" beginning-of-defun
+ :help "Go to start of outermost definition around point"]
+ ["End of def/class" end-of-defun
+ :help "Go to end of definition around point"]
+ ["Mark def/class" mark-defun
+ :help "Mark outermost definition around point"]
+ ["Jump to def/class" python-nav-jump-to-defun
+ :help "Jump to a class or function definition"]
"--"
- ("Skeletons")
+ ("Skeletons")
"---"
- ["Start interpreter" run-python
- :help "Run inferior Python process in a separate buffer"]
- ["Switch to shell" python-shell-switch-to-shell
- :help "Switch to running inferior Python process"]
- ["Eval string" python-shell-send-string
- :help "Eval string in inferior Python session"]
- ["Eval buffer" python-shell-send-buffer
- :help "Eval buffer in inferior Python session"]
- ["Eval region" python-shell-send-region
- :help "Eval region in inferior Python session"]
- ["Eval defun" python-shell-send-defun
- :help "Eval defun in inferior Python session"]
- ["Eval file" python-shell-send-file
- :help "Eval file in inferior Python session"]
- ["Debugger" pdb :help "Run pdb under GUD"]
+ ["Start interpreter" run-python
+ :help "Run inferior Python process in a separate buffer"]
+ ["Switch to shell" python-shell-switch-to-shell
+ :help "Switch to running inferior Python process"]
+ ["Eval string" python-shell-send-string
+ :help "Eval string in inferior Python session"]
+ ["Eval buffer" python-shell-send-buffer
+ :help "Eval buffer in inferior Python session"]
+ ["Eval region" python-shell-send-region
+ :help "Eval region in inferior Python session"]
+ ["Eval defun" python-shell-send-defun
+ :help "Eval defun in inferior Python session"]
+ ["Eval file" python-shell-send-file
+ :help "Eval file in inferior Python session"]
+ ["Debugger" pdb :help "Run pdb under GUD"]
"----"
- ["Check file" python-check
- :help "Check file for errors"]
- ["Help on symbol" python-eldoc-at-point
- :help "Get help on symbol at point"]
- ["Complete symbol" completion-at-point
- :help "Complete symbol before point"]))
+ ["Check file" python-check
+ :help "Check file for errors"]
+ ["Help on symbol" python-eldoc-at-point
+ :help "Get help on symbol at point"]
+ ["Complete symbol" completion-at-point
+ :help "Complete symbol before point"]))
map)
"Keymap for `python-mode'.")
(eval-when-compile
(defconst python-rx-constituents
- (list
- `(block-start . ,(rx symbol-start
+ `((block-start . ,(rx symbol-start
(or "def" "class" "if" "elif" "else" "try"
"except" "finally" "for" "while" "with")
symbol-end))
- `(decorator . ,(rx line-start (* space) ?@ (any letter ?_)
- (* (any word ?_))))
- `(defun . ,(rx symbol-start (or "def" "class") symbol-end))
- `(symbol-name . ,(rx (any letter ?_) (* (any word ?_))))
- `(open-paren . ,(rx (or "{" "[" "(")))
- `(close-paren . ,(rx (or "}" "]" ")")))
- `(simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
- `(not-simple-operator . ,(rx (not (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%))))
- `(operator . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">"
+ (decorator . ,(rx line-start (* space) ?@ (any letter ?_)
+ (* (any word ?_))))
+ (defun . ,(rx symbol-start (or "def" "class") symbol-end))
+ (if-name-main . ,(rx line-start "if" (+ space) "__name__"
+ (+ space) "==" (+ space)
+ (any ?' ?\") "__main__" (any ?' ?\")
+ (* space) ?:))
+ (symbol-name . ,(rx (any letter ?_) (* (any word ?_))))
+ (open-paren . ,(rx (or "{" "[" "(")))
+ (close-paren . ,(rx (or "}" "]" ")")))
+ (simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
+ ;; FIXME: rx should support (not simple-operator).
+ (not-simple-operator . ,(rx
+ (not
+ (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%))))
+ ;; FIXME: Use regexp-opt.
+ (operator . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">"
"=" "%" "**" "//" "<<" ">>" "<=" "!="
"==" ">=" "is" "not")))
- `(assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
+ ;; FIXME: Use regexp-opt.
+ (assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
">>=" "<<=" "&=" "^=" "|="))))
"Additional Python specific sexps for `python-rx'"))
(defmacro python-rx (&rest regexps)
- "Python mode specialized rx macro which supports common python named REGEXPS."
- (let ((rx-constituents (append python-rx-constituents rx-constituents)))
- (cond ((null regexps)
- (error "No regexp"))
- ((cdr regexps)
- (rx-to-string `(and ,@regexps) t))
- (t
- (rx-to-string (car regexps) t)))))
+ "Python mode specialized rx macro.
+This variant of `rx' supports common python named REGEXPS."
+ (let ((rx-constituents (append python-rx-constituents rx-constituents)))
+ (cond ((null regexps)
+ (error "No regexp"))
+ ((cdr regexps)
+ (rx-to-string `(and ,@regexps) t))
+ (t
+ (rx-to-string (car regexps) t)))))
\f
;;; Font-lock and syntax
-
(defvar python-font-lock-keywords
;; Keywords
`(,(rx symbol-start
- (or "and" "del" "from" "not" "while" "as" "elif" "global" "or" "with"
- "assert" "else" "if" "pass" "yield" "break" "except" "import"
- "print" "class" "exec" "in" "raise" "continue" "finally" "is"
- "return" "def" "for" "lambda" "try" "self")
+ (or
+ "and" "del" "from" "not" "while" "as" "elif" "global" "or" "with"
+ "assert" "else" "if" "pass" "yield" "break" "except" "import" "class"
+ "in" "raise" "continue" "finally" "is" "return" "def" "for" "lambda"
+ "try"
+ ;; Python 2:
+ "print" "exec"
+ ;; Python 3:
+ ;; False, None, and True are listed as keywords on the Python 3
+ ;; documentation, but since they also qualify as constants they are
+ ;; fontified like that in order to keep font-lock consistent between
+ ;; Python versions.
+ "nonlocal"
+ ;; Extra:
+ "self")
symbol-end)
;; functions
(,(rx symbol-start "def" (1+ space) (group (1+ (or word ?_))))
(1 font-lock-type-face))
;; Constants
(,(rx symbol-start
- ;; copyright, license, credits, quit, exit are added by the
- ;; site module and since they are not intended to be used in
- ;; programs they are not added here either.
- (or "None" "True" "False" "Ellipsis" "__debug__" "NotImplemented")
+ (or
+ "Ellipsis" "False" "None" "NotImplemented" "True" "__debug__"
+ ;; copyright, license, credits, quit and exit are added by the site
+ ;; module and they are not intended to be used in programs
+ "copyright" "credits" "exit" "license" "quit")
symbol-end) . font-lock-constant-face)
;; Decorators.
(,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_))
(1 font-lock-type-face))
;; Builtin Exceptions
(,(rx symbol-start
- (or "ArithmeticError" "AssertionError" "AttributeError"
- "BaseException" "BufferError" "BytesWarning" "DeprecationWarning"
- "EOFError" "EnvironmentError" "Exception" "FloatingPointError"
- "FutureWarning" "GeneratorExit" "IOError" "ImportError"
- "ImportWarning" "IndentationError" "IndexError" "KeyError"
- "KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
- "NotImplementedError" "OSError" "OverflowError"
- "PendingDeprecationWarning" "ReferenceError" "RuntimeError"
- "RuntimeWarning" "StandardError" "StopIteration" "SyntaxError"
- "SyntaxWarning" "SystemError" "SystemExit" "TabError" "TypeError"
- "UnboundLocalError" "UnicodeDecodeError" "UnicodeEncodeError"
- "UnicodeError" "UnicodeTranslateError" "UnicodeWarning"
- "UserWarning" "ValueError" "Warning" "ZeroDivisionError")
+ (or
+ "ArithmeticError" "AssertionError" "AttributeError" "BaseException"
+ "DeprecationWarning" "EOFError" "EnvironmentError" "Exception"
+ "FloatingPointError" "FutureWarning" "GeneratorExit" "IOError"
+ "ImportError" "ImportWarning" "IndexError" "KeyError"
+ "KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
+ "NotImplementedError" "OSError" "OverflowError"
+ "PendingDeprecationWarning" "ReferenceError" "RuntimeError"
+ "RuntimeWarning" "StopIteration" "SyntaxError" "SyntaxWarning"
+ "SystemError" "SystemExit" "TypeError" "UnboundLocalError"
+ "UnicodeDecodeError" "UnicodeEncodeError" "UnicodeError"
+ "UnicodeTranslateError" "UnicodeWarning" "UserWarning" "VMSError"
+ "ValueError" "Warning" "WindowsError" "ZeroDivisionError"
+ ;; Python 2:
+ "StandardError"
+ ;; Python 3:
+ "BufferError" "BytesWarning" "IndentationError" "ResourceWarning"
+ "TabError")
symbol-end) . font-lock-type-face)
;; Builtins
(,(rx symbol-start
- (or "_" "__doc__" "__import__" "__name__" "__package__" "abs" "all"
- "any" "apply" "basestring" "bin" "bool" "buffer" "bytearray"
- "bytes" "callable" "chr" "classmethod" "cmp" "coerce" "compile"
- "complex" "delattr" "dict" "dir" "divmod" "enumerate" "eval"
- "execfile" "file" "filter" "float" "format" "frozenset"
- "getattr" "globals" "hasattr" "hash" "help" "hex" "id" "input"
- "int" "intern" "isinstance" "issubclass" "iter" "len" "list"
- "locals" "long" "map" "max" "min" "next" "object" "oct" "open"
- "ord" "pow" "print" "property" "range" "raw_input" "reduce"
- "reload" "repr" "reversed" "round" "set" "setattr" "slice"
- "sorted" "staticmethod" "str" "sum" "super" "tuple" "type"
- "unichr" "unicode" "vars" "xrange" "zip")
+ (or
+ "abs" "all" "any" "bin" "bool" "callable" "chr" "classmethod"
+ "compile" "complex" "delattr" "dict" "dir" "divmod" "enumerate"
+ "eval" "filter" "float" "format" "frozenset" "getattr" "globals"
+ "hasattr" "hash" "help" "hex" "id" "input" "int" "isinstance"
+ "issubclass" "iter" "len" "list" "locals" "map" "max" "memoryview"
+ "min" "next" "object" "oct" "open" "ord" "pow" "print" "property"
+ "range" "repr" "reversed" "round" "set" "setattr" "slice" "sorted"
+ "staticmethod" "str" "sum" "super" "tuple" "type" "vars" "zip"
+ "__import__"
+ ;; Python 2:
+ "basestring" "cmp" "execfile" "file" "long" "raw_input" "reduce"
+ "reload" "unichr" "unicode" "xrange" "apply" "buffer" "coerce"
+ "intern"
+ ;; Python 3:
+ "ascii" "bytearray" "bytes" "exec"
+ ;; Extra:
+ "__all__" "__doc__" "__name__" "__package__")
symbol-end) . font-lock-builtin-face)
- ;; asignations
+ ;; assignments
;; support for a = b = c = 5
(,(lambda (limit)
(let ((re (python-rx (group (+ (any word ?. ?_)))
(set-match-data nil)))))
(1 font-lock-variable-name-face nil nil))))
-(defconst python-font-lock-syntactic-keywords
+(defconst python-syntax-propertize-function
;; Make outer chars of matching triple-quote sequences into generic
;; string delimiters. Fixme: Is there a better way?
;; First avoid a sequence preceded by an odd number of backslashes.
- `((,(concat "\\(?:\\([RUru]\\)[Rr]?\\|^\\|[^\\]\\(?:\\\\.\\)*\\)" ;Prefix.
+ (syntax-propertize-rules
+ (;; ¡Backrefs don't work in syntax-propertize-rules!
+ (concat "\\(?:\\([RUru]\\)[Rr]?\\|^\\|[^\\]\\(?:\\\\.\\)*\\)" ;Prefix.
"\\(?:\\('\\)'\\('\\)\\|\\(?2:\"\\)\"\\(?3:\"\\)\\)")
- (3 (python-quote-syntax)))))
+ (3 (ignore (python-quote-syntax))))))
(defun python-quote-syntax ()
"Put `syntax-table' property correctly on triple quote.
(cond
((eq t (nth 3 syntax)) ; after unclosed fence
;; Consider property for the last char if in a fenced string.
- (goto-char (nth 8 syntax)) ; fence position
- (skip-chars-forward "uUrR") ; skip any prefix
+ (goto-char (nth 8 syntax)) ; fence position
+ (skip-chars-forward "uUrR") ; skip any prefix
;; Is it a matching sequence?
(if (eq (char-after) (char-after (match-beginning 2)))
(put-text-property (match-beginning 3) (match-end 3)
;; Give punctuation syntax to ASCII that normally has symbol
;; syntax or has word syntax and isn't a letter.
(let ((symbol (string-to-syntax "_"))
- (sst (standard-syntax-table)))
+ (sst (standard-syntax-table)))
(dotimes (i 128)
- (unless (= i ?_)
- (if (equal symbol (aref sst i))
- (modify-syntax-entry i "." table)))))
+ (unless (= i ?_)
+ (if (equal symbol (aref sst i))
+ (modify-syntax-entry i "." table)))))
(modify-syntax-entry ?$ "." table)
(modify-syntax-entry ?% "." table)
;; exceptions
:group 'python
:safe 'booleanp)
+(define-obsolete-variable-alias
+ 'python-indent 'python-indent-offset "24.2")
+
+(define-obsolete-variable-alias
+ 'python-guess-indent 'python-indent-guess-indent-offset "24.2")
+
(defvar python-indent-current-level 0
"Current indentation level `python-indent-line-function' is using.")
(defun python-indent-guess-indent-offset ()
"Guess and set `python-indent-offset' for the current buffer."
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (let ((found-block))
- (while (and (not found-block)
- (re-search-forward
- (python-rx line-start block-start) nil t))
- (when (and (not (python-info-ppss-context 'string))
- (not (python-info-ppss-context 'comment))
- (progn
- (goto-char (line-end-position))
- (forward-comment -9999)
- (eq ?: (char-before))))
- (setq found-block t)))
- (if (not found-block)
- (message "Can't guess python-indent-offset, using defaults: %s"
- python-indent-offset)
- (while (and (progn
- (goto-char (line-end-position))
- (python-info-continuation-line-p))
- (not (eobp)))
- (forward-line 1))
- (forward-line 1)
- (forward-comment 9999)
- (let ((indent-offset (current-indentation)))
- (when (> indent-offset 0)
- (setq python-indent-offset indent-offset))))))))
+ (interactive)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (let ((block-end))
+ (while (and (not block-end)
+ (re-search-forward
+ (python-rx line-start block-start) nil t))
+ (when (and
+ (not (python-info-ppss-context-type))
+ (progn
+ (goto-char (line-end-position))
+ (python-util-forward-comment -1)
+ (if (equal (char-before) ?:)
+ t
+ (forward-line 1)
+ (when (python-info-block-continuation-line-p)
+ (while (and (python-info-continuation-line-p)
+ (not (eobp)))
+ (forward-line 1))
+ (python-util-forward-comment -1)
+ (when (equal (char-before) ?:)
+ t)))))
+ (setq block-end (point-marker))))
+ (let ((indentation
+ (when block-end
+ (goto-char block-end)
+ (python-util-forward-comment)
+ (current-indentation))))
+ (if indentation
+ (setq python-indent-offset indentation)
+ (message "Can't guess python-indent-offset, using defaults: %s"
+ python-indent-offset)))))))
(defun python-indent-context ()
"Get information on indentation context.
((setq start (when (not (or (python-info-ppss-context 'string ppss)
(python-info-ppss-context 'comment ppss)))
(let ((line-beg-pos (line-beginning-position)))
- (when (eq ?\\ (char-before (1- line-beg-pos)))
+ (when (python-info-line-ends-backslash-p
+ (1- line-beg-pos))
(- line-beg-pos 2)))))
'after-backslash)
;; After beginning of block
((setq start (save-excursion
- (let ((block-regexp (python-rx block-start))
- (block-start-line-end ":[[:space:]]*$"))
- (back-to-indentation)
- (forward-comment -9999)
- (back-to-indentation)
- (when (or (python-info-continuation-line-p)
- (and (not (looking-at block-regexp))
- (save-excursion
- (re-search-forward
- block-start-line-end
- (line-end-position) t))))
- (while (and (forward-line -1)
- (python-info-continuation-line-p)
- (not (bobp))))
- (back-to-indentation)
- (when (not (looking-at block-regexp))
- (forward-line 1)))
- (back-to-indentation)
- (when (and (looking-at block-regexp)
- (or (re-search-forward
- block-start-line-end
- (line-end-position) t)
- (save-excursion
- (goto-char (line-end-position))
- (python-info-continuation-line-p))))
+ (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-info-ppss-context 'string)
+ (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'paren)
+ (python-info-continuation-line-p))))
+ (when (looking-at (python-rx block-start))
(point-marker)))))
'after-beginning-of-block)
;; After normal line
((setq start (save-excursion
(back-to-indentation)
- (forward-comment -9999)
- (python-nav-sentence-start)
+ (python-util-forward-comment -1)
+ (python-nav-statement-start)
(point-marker)))
'after-line)
;; Do not indent
(save-excursion
(case context-status
('no-indent 0)
+ ;; 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, in the case current line starts with a
+ ;; `python-indent-dedenters' de-indent one level.
('after-line
(-
(save-excursion
(looking-at (regexp-opt python-indent-dedenters)))
python-indent-offset
0)))
+ ;; 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
- (let* ((block-continuation
- (save-excursion
- (forward-line -1)
- (python-info-block-continuation-line-p)))
- (assignment-continuation
- (save-excursion
- (forward-line -1)
- (python-info-assignment-continuation-line-p)))
- (dot-continuation
- (save-excursion
- (back-to-indentation)
- (when (looking-at "\\.")
- (forward-line -1)
- (goto-char (line-end-position))
- (while (and (re-search-backward "\\." (line-beginning-position) t)
- (or (python-info-ppss-context 'comment)
- (python-info-ppss-context 'string)
- (python-info-ppss-context 'paren))))
- (if (and (looking-at "\\.")
- (not (or (python-info-ppss-context 'comment)
- (python-info-ppss-context 'string)
- (python-info-ppss-context 'paren))))
- (current-column)
- (+ (current-indentation) python-indent-offset)))))
- (indentation (cond
- (dot-continuation
- dot-continuation)
- (block-continuation
- (goto-char block-continuation)
- (re-search-forward
- (python-rx block-start (* space))
- (line-end-position) t)
- (current-column))
- (assignment-continuation
- (goto-char assignment-continuation)
- (re-search-forward
- (python-rx simple-operator)
- (line-end-position) t)
- (forward-char 1)
- (re-search-forward
- (python-rx (* space))
- (line-end-position) t)
- (current-column))
- (t
- (goto-char context-start)
- (if (not
- (save-excursion
- (back-to-indentation)
- (looking-at
- "\\(?:return\\|from\\|import\\)\s+")))
- (current-indentation)
- (+ (current-indentation)
- (length
- (match-string-no-properties 0))))))))
- indentation))
+ (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-info-ppss-context 'paren))))
+ (goto-char (line-end-position))
+ (while (and (re-search-backward
+ "\\." (line-beginning-position) t)
+ (or (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))))
+ (if (and (looking-at "\\.")
+ (not (or (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))))
+ ;; 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
- (or (save-excursion
- (skip-syntax-forward "\s" (line-end-position))
- (when (and (looking-at (regexp-opt '(")" "]" "}")))
- (not (forward-char 1))
- (not (python-info-ppss-context 'paren)))
- (goto-char context-start)
+ (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-info-ppss-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)
- (current-column)))
- (-
- (save-excursion
- (goto-char context-start)
- (forward-char)
- (save-restriction
- (narrow-to-region
- (line-beginning-position)
- (line-end-position))
- (forward-comment 9999))
- (if (looking-at "$")
- (+ (current-indentation) python-indent-offset)
- (forward-comment 9999)
- (current-column)))
- (if (progn
- (back-to-indentation)
- (looking-at (regexp-opt '(")" "]" "}"))))
- python-indent-offset
- 0)))))))))
+ (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'."
(beginning-of-line)
(delete-horizontal-space)
(indent-to (nth python-indent-current-level python-indent-levels))
- (save-restriction
- (widen)
- (let ((closing-block-point (python-info-closing-block)))
- (when closing-block-point
- (message "Closes %s" (buffer-substring
- closing-block-point
- (save-excursion
- (goto-char closing-block-point)
- (line-end-position))))))))
+ (python-info-closing-block-message))
(defun python-indent-line-function ()
"`indent-line-function' for Python mode.
(python-info-ppss-context 'comment))))
(let ((indentation (current-indentation))
(calculated-indentation (python-indent-calculate-indentation)))
+ (python-info-closing-block-message)
(when (> indentation calculated-indentation)
(save-excursion
(indent-line-to calculated-indentation)
- (when (not (python-info-closing-block))
+ (when (not (python-info-closing-block-message))
(indent-line-to indentation)))))))
(put 'python-indent-electric-colon 'delete-selection t)
+(defun python-indent-post-self-insert-function ()
+ "Adjust closing paren line indentation after a char is added.
+This function is intended to be added to the
+`post-self-insert-hook.' If a line renders a paren alone, after
+adding a char before it, the line will be re-indented
+automatically if needed."
+ (when (and (eq (char-before) last-command-event)
+ (not (bolp))
+ (memq (char-after) '(?\) ?\] ?\})))
+ (save-excursion
+ (goto-char (line-beginning-position))
+ ;; If after going to the beginning of line the point
+ ;; is still inside a paren it's ok to do the trick
+ (when (python-info-ppss-context 'paren)
+ (let ((indentation (python-indent-calculate-indentation)))
+ (when (< (current-indentation) indentation)
+ (indent-line-to indentation)))))))
+
\f
;;; Navigation
The name of the defun should be grouped so it can be retrieved
via `match-string'.")
-(defun python-nav-beginning-of-defun (&optional nodecorators)
+(defun python-nav-beginning-of-defun (&optional arg)
"Move point to `beginning-of-defun'.
-When NODECORATORS is non-nil decorators are not included. This
-is the main part of`python-beginning-of-defun-function'
-implementation. Return non-nil if point is moved to the
-`beginning-of-defun'."
- (let ((indent-pos (save-excursion
- (back-to-indentation)
- (point-marker)))
- (found)
- (include-decorators
- (lambda ()
- (when (not nodecorators)
- (when (save-excursion
- (forward-line -1)
- (looking-at (python-rx decorator)))
- (while (and (not (bobp))
- (forward-line -1)
- (looking-at (python-rx decorator))))
- (when (not (bobp)) (forward-line 1)))))))
- (if (and (> (point) indent-pos)
- (save-excursion
- (goto-char (line-beginning-position))
- (looking-at python-nav-beginning-of-defun-regexp)))
- (progn
- (goto-char (line-beginning-position))
- (funcall include-decorators)
- (setq found t))
- (goto-char (line-beginning-position))
- (when (re-search-backward python-nav-beginning-of-defun-regexp nil t)
- (setq found t))
- (goto-char (or (python-info-ppss-context 'string) (point)))
- (funcall include-decorators))
- found))
-
-(defun python-beginning-of-defun-function (&optional arg nodecorators)
+With positive ARG move search backwards. With negative do the
+same but forward. When ARG is nil or 0 defaults to 1. This is
+the main part of `python-beginning-of-defun-function'. Return
+non-nil if point is moved to `beginning-of-defun'."
+ (when (or (null arg) (= arg 0)) (setq arg 1))
+ (let* ((re-search-fn (if (> arg 0)
+ #'re-search-backward
+ #'re-search-forward))
+ (line-beg-pos (line-beginning-position))
+ (line-content-start (+ line-beg-pos (current-indentation)))
+ (pos (point-marker))
+ (found
+ (progn
+ (when (and (< arg 0)
+ (python-info-looking-at-beginning-of-defun))
+ (end-of-line 1))
+ (while (and (funcall re-search-fn
+ python-nav-beginning-of-defun-regexp nil t)
+ (python-info-ppss-context-type)))
+ (and (python-info-looking-at-beginning-of-defun)
+ (or (not (= (line-number-at-pos pos)
+ (line-number-at-pos)))
+ (and (>= (point) line-beg-pos)
+ (<= (point) line-content-start)
+ (> pos line-content-start)))))))
+ (if found
+ (or (beginning-of-line 1) t)
+ (and (goto-char pos) nil))))
+
+(defun python-beginning-of-defun-function (&optional arg)
"Move point to the beginning of def or class.
-With positive ARG move that number of functions forward. With
-negative do the same but backwards. When NODECORATORS is non-nil
-decorators are not included. Return non-nil if point is moved to the
-`beginning-of-defun'."
+With positive ARG move that number of functions backwards. With
+negative do the same but forward. When ARG is nil or 0 defaults
+to 1. Return non-nil if point is moved to `beginning-of-defun'."
(when (or (null arg) (= arg 0)) (setq arg 1))
- (if (> arg 0)
- (dotimes (i arg (python-nav-beginning-of-defun nodecorators)))
- (let ((found))
- (dotimes (i (- arg) found)
- (python-end-of-defun-function)
- (forward-comment 9999)
- (goto-char (line-end-position))
- (when (not (eobp))
- (setq found
- (python-nav-beginning-of-defun nodecorators)))))))
+ (let ((found))
+ (cond ((and (eq this-command 'mark-defun)
+ (python-info-looking-at-beginning-of-defun)))
+ (t
+ (dotimes (i (if (> arg 0) arg (- arg)))
+ (when (and (python-nav-beginning-of-defun arg)
+ (not found))
+ (setq found t)))))
+ found))
(defun python-end-of-defun-function ()
"Move point to the end of def or class.
Returns nil if point is not in a def or class."
(interactive)
- (let ((beg-defun-indent)
- (decorator-regexp "[[:space:]]*@"))
- (when (looking-at decorator-regexp)
- (while (and (not (eobp))
- (forward-line 1)
- (looking-at decorator-regexp))))
- (when (not (looking-at python-nav-beginning-of-defun-regexp))
- (python-beginning-of-defun-function))
- (setq beg-defun-indent (current-indentation))
- (forward-line 1)
- (while (and (forward-line 1)
- (not (eobp))
- (or (not (current-word))
- (> (current-indentation) beg-defun-indent))))
- (forward-comment 9999)
- (goto-char (line-beginning-position))))
-
-(defun python-nav-sentence-start ()
- "Move to start of current sentence."
+ (let ((beg-defun-indent))
+ (when (or (python-info-looking-at-beginning-of-defun)
+ (python-beginning-of-defun-function 1)
+ (python-beginning-of-defun-function -1))
+ (setq beg-defun-indent (current-indentation))
+ (forward-line 1)
+ ;; Go as forward as possible
+ (while (and (or
+ (python-nav-beginning-of-defun -1)
+ (and (goto-char (point-max)) nil))
+ (> (current-indentation) beg-defun-indent)))
+ (beginning-of-line 1)
+ ;; Go as backwards as possible
+ (while (and (forward-line -1)
+ (not (bobp))
+ (or (not (current-word))
+ (equal (char-after (+ (point) (current-indentation))) ?#)
+ (<= (current-indentation) beg-defun-indent)
+ (looking-at (python-rx decorator))
+ (python-info-ppss-context-type))))
+ (forward-line 1)
+ ;; If point falls inside a paren or string context the point is
+ ;; forwarded at the end of it (or end of buffer if its not closed)
+ (let ((context-type (python-info-ppss-context-type)))
+ (when (memq context-type '(paren string))
+ ;; Slow but safe.
+ (while (and (not (eobp))
+ (python-info-ppss-context-type))
+ (forward-line 1)))))))
+
+(defun python-nav-statement-start ()
+ "Move to start of current statement."
(interactive "^")
- (while (and (not (back-to-indentation))
+ (while (and (or (back-to-indentation) t)
(not (bobp))
(when (or
(save-excursion
(python-info-line-ends-backslash-p))
(python-info-ppss-context 'string)
(python-info-ppss-context 'paren))
- (forward-line -1)))))
+ (forward-line -1)))))
-(defun python-nav-sentence-end ()
- "Move to end of current sentence."
+(defun python-nav-statement-end ()
+ "Move to end of current statement."
(interactive "^")
(while (and (goto-char (line-end-position))
(not (eobp))
(python-info-line-ends-backslash-p)
(python-info-ppss-context 'string)
(python-info-ppss-context 'paren))
- (forward-line 1)))))
+ (forward-line 1)))))
-(defun python-nav-backward-sentence (&optional arg)
- "Move backward to start of sentence. With ARG, do it arg times.
-See `python-nav-forward-sentence' for more information."
+(defun python-nav-backward-statement (&optional arg)
+ "Move backward to previous statement.
+With ARG, repeat. See `python-nav-forward-statement'."
(interactive "^p")
(or arg (setq arg 1))
- (python-nav-forward-sentence (- arg)))
+ (python-nav-forward-statement (- arg)))
-(defun python-nav-forward-sentence (&optional arg)
- "Move forward to next end of sentence. With ARG, repeat.
-With negative argument, move backward repeatedly to start of sentence."
+(defun python-nav-forward-statement (&optional arg)
+ "Move forward to next statement.
+With ARG, repeat. With negative argument, move ARG times
+backward to previous statement."
(interactive "^p")
(or arg (setq arg 1))
(while (> arg 0)
- (forward-comment 9999)
- (python-nav-sentence-end)
- (forward-line 1)
+ (python-nav-statement-end)
+ (python-util-forward-comment)
+ (python-nav-statement-start)
(setq arg (1- arg)))
(while (< arg 0)
- (python-nav-sentence-end)
- (forward-comment -9999)
- (python-nav-sentence-start)
- (forward-line -1)
+ (python-nav-statement-start)
+ (python-util-forward-comment -1)
+ (python-nav-statement-start)
(setq arg (1+ arg))))
-(defun python-nav-list-defun-positions (&optional include-type)
+(defun python-nav-block-start ()
+ "Move to start of current block."
+ (interactive "^")
+ (let ((starting-pos (point))
+ (block-regexp (python-rx
+ line-start (* whitespace) block-start)))
+ (if (progn
+ (python-nav-statement-start)
+ (looking-at (python-rx block-start)))
+ (point-marker)
+ ;; Go to first line beginning a statement
+ (while (and (not (bobp))
+ (or (and (python-nav-statement-start) nil)
+ (python-info-current-line-comment-p)
+ (python-info-current-line-empty-p)))
+ (forward-line -1))
+ (let ((block-matching-indent
+ (- (current-indentation) python-indent-offset)))
+ (while
+ (and (python-nav-backward-block)
+ (> (current-indentation) block-matching-indent)))
+ (if (and (looking-at (python-rx block-start))
+ (= (current-indentation) block-matching-indent))
+ (point-marker)
+ (and (goto-char starting-pos) nil))))))
+
+(defun python-nav-block-end ()
+ "Move to end of current block."
+ (interactive "^")
+ (when (python-nav-block-start)
+ (let ((block-indentation (current-indentation)))
+ (python-nav-statement-end)
+ (while (and (forward-line 1)
+ (not (eobp))
+ (or (and (> (current-indentation) block-indentation)
+ (or (python-nav-statement-end) t))
+ (python-info-current-line-comment-p)
+ (python-info-current-line-empty-p))))
+ (python-util-forward-comment -1)
+ (point-marker))))
+
+(defun python-nav-backward-block (&optional arg)
+ "Move backward to previous block of code.
+With ARG, repeat. See `python-nav-forward-block'."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (python-nav-forward-block (- arg)))
+
+(defun python-nav-forward-block (&optional arg)
+ "Move forward to next block of code.
+With ARG, repeat. With negative argument, move ARG times
+backward to previous block."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (let ((block-start-regexp
+ (python-rx line-start (* whitespace) block-start))
+ (starting-pos (point)))
+ (while (> arg 0)
+ (python-nav-statement-end)
+ (while (and
+ (re-search-forward block-start-regexp nil t)
+ (or (python-info-ppss-context 'string)
+ (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'paren))))
+ (setq arg (1- arg)))
+ (while (< arg 0)
+ (python-nav-statement-start)
+ (while (and
+ (re-search-backward block-start-regexp nil t)
+ (or (python-info-ppss-context 'string)
+ (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'paren))))
+ (setq arg (1+ arg)))
+ (python-nav-statement-start)
+ (if (not (looking-at (python-rx block-start)))
+ (and (goto-char starting-pos) nil)
+ (and (not (= (point) starting-pos)) (point-marker)))))
+
+(defun python-nav-forward-sexp-function (&optional arg)
+ "Move forward across one block of code.
+With ARG, do it that many times. Negative arg -N means
+move backward N times."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (while (> arg 0)
+ (let ((block-starting-pos
+ (save-excursion (python-nav-block-start)))
+ (block-ending-pos
+ (save-excursion (python-nav-block-end)))
+ (next-block-starting-pos
+ (save-excursion (python-nav-forward-block))))
+ (cond ((not block-starting-pos)
+ (python-nav-forward-block))
+ ((= (point) block-starting-pos)
+ (if (or (not next-block-starting-pos)
+ (< block-ending-pos next-block-starting-pos))
+ (python-nav-block-end)
+ (python-nav-forward-block)))
+ ((= block-ending-pos (point))
+ (let ((parent-block-end-pos
+ (save-excursion
+ (python-util-forward-comment)
+ (python-nav-block-start)
+ (python-nav-block-end))))
+ (if (and parent-block-end-pos
+ (or (not next-block-starting-pos)
+ (> next-block-starting-pos parent-block-end-pos)))
+ (goto-char parent-block-end-pos)
+ (python-nav-forward-block))))
+ (t (python-nav-block-end))))
+ (setq arg (1- arg)))
+ (while (< arg 0)
+ (let* ((block-starting-pos
+ (save-excursion (python-nav-block-start)))
+ (block-ending-pos
+ (save-excursion (python-nav-block-end)))
+ (prev-block-ending-pos
+ (save-excursion (when (python-nav-backward-block)
+ (python-nav-block-end))))
+ (prev-block-parent-ending-pos
+ (save-excursion
+ (when prev-block-ending-pos
+ (goto-char prev-block-ending-pos)
+ (python-util-forward-comment)
+ (python-nav-block-start)
+ (python-nav-block-end)))))
+ (cond ((not block-ending-pos)
+ (and (python-nav-backward-block)
+ (python-nav-block-end)))
+ ((= (point) block-ending-pos)
+ (let ((candidates))
+ (dolist (name
+ '(prev-block-parent-ending-pos
+ prev-block-ending-pos
+ block-ending-pos
+ block-starting-pos))
+ (when (and (symbol-value name)
+ (< (symbol-value name) (point)))
+ (add-to-list 'candidates (symbol-value name))))
+ (goto-char (apply 'max candidates))))
+ ((> (point) block-ending-pos)
+ (python-nav-block-end))
+ ((= (point) block-starting-pos)
+ (if (not (> (point) (or prev-block-ending-pos (point))))
+ (python-nav-backward-block)
+ (goto-char prev-block-ending-pos)
+ (let ((parent-block-ending-pos
+ (save-excursion
+ (python-nav-forward-sexp-function)
+ (and (not (looking-at (python-rx block-start)))
+ (point)))))
+ (when (and parent-block-ending-pos
+ (> parent-block-ending-pos prev-block-ending-pos))
+ (goto-char parent-block-ending-pos)))))
+ (t (python-nav-block-start))))
+ (setq arg (1+ arg))))
+
+(defvar python-nav-list-defun-positions-cache nil)
+(make-variable-buffer-local 'python-nav-list-defun-positions-cache)
+
+(defun python-nav-list-defun-positions (&optional include-type rescan)
"Make an Alist of defun names and point markers for current buffer.
When optional argument INCLUDE-TYPE is non-nil the type is
-included the defun name."
- (let ((defs))
- (save-restriction
- (widen)
- (save-excursion
- (goto-char (point-max))
- (while (re-search-backward python-nav-beginning-of-defun-regexp nil t)
- (when (and (not (python-info-ppss-context 'string))
- (not (python-info-ppss-context 'comment))
- (not (python-info-ppss-context 'parent)))
- (add-to-list
- 'defs (cons
- (python-info-current-defun include-type)
- (point-marker)))))
- defs))))
-
-(defun python-nav-read-defun ()
+included the defun name. With optional argument RESCAN the
+`python-nav-list-defun-positions-cache' is invalidated and the
+list of defun is regenerated again."
+ (if (and python-nav-list-defun-positions-cache (not rescan))
+ python-nav-list-defun-positions-cache
+ (let ((defs))
+ (save-restriction
+ (widen)
+ (save-excursion
+ (goto-char (point-max))
+ (while (re-search-backward python-nav-beginning-of-defun-regexp nil t)
+ (when (and (not (python-info-ppss-context 'string))
+ (not (python-info-ppss-context 'comment))
+ (not (python-info-ppss-context 'parent)))
+ (add-to-list
+ 'defs (cons
+ (python-info-current-defun include-type)
+ (point-marker)))))
+ (setq python-nav-list-defun-positions-cache defs))))))
+
+(defun python-nav-read-defun (&optional rescan)
"Read a defun name of current buffer and return its point marker.
A cons cell with the form (DEFUN-NAME . POINT-MARKER) is returned
-when defun is completed, else nil."
- (let ((defs (python-nav-list-defun-positions)))
+when defun is completed, else nil. With optional argument RESCAN
+forces `python-nav-list-defun-positions' to invalidate its
+cache."
+ (let ((defs (python-nav-list-defun-positions nil rescan)))
(minibuffer-with-setup-hook
(lambda ()
(setq minibuffer-completion-table (mapcar 'car defs)))
(assoc-string stringdef defs))))))
(defun python-nav-jump-to-defun (def)
- "Jump to the definition of DEF in current file."
+ "Jump to the definition of DEF in current file.
+Locations are cached; use a `C-u' prefix argument to force a
+rescan."
(interactive
- (list (python-nav-read-defun)))
+ (list (python-nav-read-defun current-prefix-arg)))
(when (not (called-interactively-p 'interactive))
(setq def (assoc-string def (python-nav-list-defun-positions))))
(let ((def-marker (cdr def)))
(defcustom python-shell-interpreter "python"
"Default Python interpreter for shell."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defcustom python-shell-internal-buffer-name "Python Internal"
"Default buffer name for the Internal Python interpreter."
(defcustom python-shell-interpreter-args "-i"
"Default arguments for the Python interpreter."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defcustom python-shell-prompt-regexp ">>> "
"Regular Expression matching top\-level input prompt of python shell.
:group 'python
:safe 'stringp)
+(defcustom python-shell-enable-font-lock t
+ "Should syntax highlighting be enabled in the python shell buffer?
+Restart the python shell after changing this variable for it to take effect."
+ :type 'boolean
+ :group 'python
+ :safe 'booleanp)
+
(defcustom python-shell-send-setup-max-wait 5
"Seconds to wait for process output before code setup.
-If output is received before the especified time then control is
+If output is received before the specified time then control is
returned in that moment and not after waiting."
:type 'integer
:group 'python
:group 'python
:safe 'listp)
+(defcustom python-shell-extra-pythonpaths nil
+ "List of extra pythonpaths for Python shell.
+The values of this variable are added to the existing value of
+PYTHONPATH in the `process-environment' variable."
+ :type '(repeat string)
+ :group 'python
+ :safe 'listp)
+
(defcustom python-shell-exec-path nil
"List of path to search for binaries.
This variable follows the same rules as `exec-path' since it
(defcustom python-shell-setup-codes '(python-shell-completion-setup-code
python-ffap-setup-code
python-eldoc-setup-code)
- "List of code run by `python-shell-send-setup-codes'.
-Each variable can contain either a simple string with the code to
-execute or a cons with the form (CODE . DESCRIPTION), where CODE
-is a string with the code to execute and DESCRIPTION is the
-description of it."
+ "List of code run by `python-shell-send-setup-codes'."
:type '(repeat symbol)
:group 'python
:safe 'listp)
(defcustom python-shell-compilation-regexp-alist
`((,(rx line-start (1+ (any " \t")) "File \""
- (group (1+ (not (any "\"<")))) ; avoid `<stdin>' &c
- "\", line " (group (1+ digit)))
+ (group (1+ (not (any "\"<")))) ; avoid `<stdin>' &c
+ "\", line " (group (1+ digit)))
1 2)
(,(rx " in file " (group (1+ not-newline)) " on line "
- (group (1+ digit)))
+ (group (1+ digit)))
1 2)
(,(rx line-start "> " (group (1+ (not (any "(\"<"))))
- "(" (group (1+ digit)) ")" (1+ (not (any "("))) "()")
+ "(" (group (1+ digit)) ")" (1+ (not (any "("))) "()")
1 2))
"`compilation-error-regexp-alist' for inferior Python."
:type '(alist string)
:group 'python)
(defun python-shell-get-process-name (dedicated)
- "Calculate the appropiate process name for inferior Python process.
+ "Calculate the appropriate process name for inferior Python process.
If DEDICATED is t and the variable `buffer-file-name' is non-nil
returns a string with the form
`python-shell-buffer-name'[variable `buffer-file-name'] else
process-name))
(defun python-shell-internal-get-process-name ()
- "Calculate the appropiate process name for Internal Python process.
+ "Calculate the appropriate process name for Internal Python process.
The name is calculated from `python-shell-global-buffer-name' and
a hash of all relevant global shell settings in order to ensure
uniqueness for different types of configurations."
(md5
(concat
(python-shell-parse-command)
+ python-shell-prompt-regexp
+ python-shell-prompt-block-regexp
+ python-shell-prompt-output-regexp
(mapconcat #'symbol-value python-shell-setup-codes "")
- (mapconcat #'indentity python-shell-process-environment "")
+ (mapconcat #'identity python-shell-process-environment "")
+ (mapconcat #'identity python-shell-extra-pythonpaths "")
+ (mapconcat #'identity python-shell-exec-path "")
(or python-shell-virtualenv-path "")
- (mapconcat #'indentity python-shell-exec-path "")))))
+ (mapconcat #'identity python-shell-exec-path "")))))
(defun python-shell-parse-command ()
"Calculate the string used to execute the inferior Python process."
(virtualenv (if python-shell-virtualenv-path
(directory-file-name python-shell-virtualenv-path)
nil)))
+ (when python-shell-extra-pythonpaths
+ (setenv "PYTHONPATH"
+ (format "%s%s%s"
+ (mapconcat 'identity
+ python-shell-extra-pythonpaths
+ path-separator)
+ path-separator
+ (or (getenv "PYTHONPATH") ""))))
(if (not virtualenv)
process-environment
(setenv "PYTHONHOME" nil)
(setenv "PATH" (format "%s/bin%s%s"
- virtualenv path-separator (getenv "PATH")))
+ virtualenv path-separator
+ (or (getenv "PATH") "")))
(setenv "VIRTUAL_ENV" virtualenv))
process-environment))
OUTPUT is a string with the contents of the buffer."
(ansi-color-filter-apply output))
-(defvar inferior-python-mode-current-file nil
- "Current file from which a region was sent.")
-(make-variable-buffer-local 'inferior-python-mode-current-file)
-
(define-derived-mode inferior-python-mode comint-mode "Inferior Python"
"Major mode for Python inferior process.
Runs a Python interpreter as a subprocess of Emacs, with Python
`python-shell-prompt-regexp',
`python-shell-prompt-output-regexp',
`python-shell-prompt-block-regexp',
+`python-shell-enable-font-lock',
`python-shell-completion-setup-code',
-`python-shell-completion-string-code', `python-eldoc-setup-code',
-`python-eldoc-string-code', `python-ffap-setup-code' and
-`python-ffap-string-code' can customize this mode for different
-Python interpreters.
+`python-shell-completion-string-code',
+`python-shell-completion-module-string-code',
+`python-eldoc-setup-code', `python-eldoc-string-code',
+`python-ffap-setup-code' and `python-ffap-string-code' can
+customize this mode for different Python interpreters.
You can also add additional setup code to be run at
initialization of the interpreter via `python-shell-setup-codes'
'python-shell-completion-complete-at-point)
(define-key inferior-python-mode-map (kbd "<tab>")
'python-shell-completion-complete-or-indent)
+ (when python-shell-enable-font-lock
+ (set (make-local-variable 'font-lock-defaults)
+ '(python-font-lock-keywords nil nil nil nil))
+ (set (make-local-variable 'syntax-propertize-function)
+ python-syntax-propertize-function))
(compilation-shell-minor-mode 1))
(defun python-shell-make-comint (cmd proc-name &optional pop)
(y-or-n-p "Make dedicated process? ")
(read-string "Run Python: " (python-shell-parse-command)))
(list nil (python-shell-parse-command))))
- (python-shell-make-comint cmd (python-shell-get-process-name dedicated) t)
+ (python-shell-make-comint cmd (python-shell-get-process-name dedicated))
dedicated)
(defun run-python-internal ()
(defun python-shell-get-or-create-process ()
"Get or create an inferior Python process for current buffer and return it."
- (let* ((old-buffer (current-buffer))
- (dedicated-proc-name (python-shell-get-process-name t))
+ (let* ((dedicated-proc-name (python-shell-get-process-name t))
(dedicated-proc-buffer-name (format "*%s*" dedicated-proc-name))
(global-proc-name (python-shell-get-process-name nil))
(global-proc-buffer-name (format "*%s*" global-proc-name))
(setq dedicated-running t)
(setq global-running t)))
;; Always prefer dedicated
- (switch-to-buffer old-buffer)
(get-buffer-process (if dedicated-running
dedicated-proc-buffer-name
global-proc-buffer-name))))
+(defvar python-shell-internal-buffer nil
+ "Current internal shell buffer for the current buffer.
+This is really not necessary at all for the code to work but it's
+there for compatibility with CEDET.")
+(make-variable-buffer-local 'python-shell-internal-buffer)
+
(defun python-shell-internal-get-or-create-process ()
"Get or create an inferior Internal Python process."
(let* ((proc-name (python-shell-internal-get-process-name))
(proc-buffer-name (format "*%s*" proc-name)))
(run-python-internal)
+ (setq python-shell-internal-buffer proc-buffer-name)
(get-buffer-process proc-buffer-name)))
+(define-obsolete-function-alias
+ 'python-proc 'python-shell-internal-get-or-create-process "24.2")
+
+(define-obsolete-variable-alias
+ 'python-buffer 'python-shell-internal-buffer "24.2")
+
(defun python-shell-send-string (string &optional process msg)
"Send STRING to inferior Python PROCESS.
When MSG is non-nil messages the first line of STRING."
"")))))
(python-shell-send-string string process msg)
(accept-process-output process)
- (mapconcat
- (lambda (string) string)
- (split-string
- output-buffer
- (if (> (length python-shell-prompt-output-regexp) 0)
- (format "\n*%s$\\|^%s"
- python-shell-prompt-regexp
- (or python-shell-prompt-output-regexp ""))
- (format "\n$\\|^%s"
- python-shell-prompt-regexp)) t) "\n")))
+ (replace-regexp-in-string
+ (if (> (length python-shell-prompt-output-regexp) 0)
+ (format "\n*%s$\\|^%s\\|\n$"
+ python-shell-prompt-regexp
+ (or python-shell-prompt-output-regexp ""))
+ (format "\n*$\\|^%s\\|\n$"
+ python-shell-prompt-regexp))
+ "" output-buffer)))
(defun python-shell-internal-send-string (string)
"Send STRING to the Internal Python interpreter.
(python-shell-internal-get-or-create-process) nil))
(define-obsolete-function-alias
- 'python-send-receive 'python-shell-internal-send-string "23.3"
- "Send STRING to inferior Python (if any) and return result.
-The result is what follows `_emacs_out' in the output.
-This is a no-op if `python-check-comint-prompt' returns nil.")
+ 'python-send-receive 'python-shell-internal-send-string "24.2")
+
+(define-obsolete-function-alias
+ 'python-send-string 'python-shell-internal-send-string "24.2")
(defun python-shell-send-region (start end)
"Send the region delimited by START and END to inferior Python process."
(interactive "r")
- (let ((deactivate-mark nil))
- (python-shell-send-string (buffer-substring start end) nil t)))
+ (python-shell-send-string (buffer-substring start end) nil t))
-(defun python-shell-send-buffer ()
- "Send the entire buffer to inferior Python process."
- (interactive)
+(defun python-shell-send-buffer (&optional arg)
+ "Send the entire buffer to inferior Python process.
+
+With prefix ARG include lines surrounded by \"if __name__ == '__main__':\""
+ (interactive "P")
(save-restriction
(widen)
- (python-shell-send-region (point-min) (point-max))))
+ (python-shell-send-region
+ (point-min)
+ (or (and
+ (not arg)
+ (save-excursion
+ (re-search-forward (python-rx if-name-main) nil t))
+ (match-beginning 0))
+ (point-max)))))
(defun python-shell-send-defun (arg)
"Send the current defun to inferior Python process.
-When argument ARG is non-nil sends the innermost defun."
+When argument ARG is non-nil do not include decorators."
(interactive "P")
(save-excursion
(python-shell-send-region
(progn
- (or (python-beginning-of-defun-function)
- (progn (beginning-of-line) (point-marker))))
+ (end-of-line 1)
+ (while (and (or (python-beginning-of-defun-function)
+ (beginning-of-line 1))
+ (> (current-indentation) 0)))
+ (when (not arg)
+ (while (and (forward-line -1)
+ (looking-at (python-rx decorator))))
+ (forward-line 1))
+ (point-marker))
(progn
(or (python-end-of-defun-function)
- (progn (end-of-line) (point-marker)))))))
+ (end-of-line 1))
+ (point-marker)))))
(defun python-shell-send-file (file-name &optional process temp-file-name)
"Send FILE-NAME to inferior Python PROCESS.
(file-name (or (expand-file-name file-name) temp-file-name)))
(when (not file-name)
(error "If FILE-NAME is nil then TEMP-FILE-NAME must be non-nil"))
- (with-current-buffer (process-buffer process)
- (setq inferior-python-mode-current-file
- (convert-standard-filename file-name)))
(python-shell-send-string
(format
(concat "__pyfile = open('''%s''');"
(accept-process-output process python-shell-send-setup-max-wait)
(dolist (code python-shell-setup-codes)
(when code
- (when (consp code)
- (setq msg (cdr code)))
(message (format msg code))
- (python-shell-send-string-no-output
+ (python-shell-send-string
(symbol-value code) process)))))
(add-hook 'inferior-python-mode-hook
return completions"
"Code used to setup completion in inferior Python processes."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defcustom python-shell-completion-string-code
"';'.join(__COMPLETER_all_completions('''%s'''))\n"
"Python code used to get a string of completions separated by semicolons."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
+
+(defcustom python-shell-completion-module-string-code ""
+ "Python code used to get completions separated by semicolons for imports.
+
+For IPython v0.11, add the following line to
+`python-shell-completion-setup-code':
+
+from IPython.core.completerlib import module_completion
-(defun python-shell-completion--get-completions (input process)
- "Retrieve available completions for INPUT using PROCESS."
+and use the following as the value of this variable:
+
+';'.join(module_completion('''%s'''))\n"
+ :type 'string
+ :group 'python)
+
+(defcustom python-shell-completion-pdb-string-code
+ "';'.join(globals().keys() + locals().keys())"
+ "Python code used to get completions separated by semicolons for [i]pdb."
+ :type 'string
+ :group 'python)
+
+(defun python-shell-completion--get-completions (input process completion-code)
+ "Retrieve available completions for INPUT using PROCESS.
+Argument COMPLETION-CODE is the python code used to get
+completions on the current context."
(with-current-buffer (process-buffer process)
(let ((completions (python-shell-send-string-no-output
- (format python-shell-completion-string-code input)
- process)))
+ (format completion-code input) process)))
(when (> (length completions) 2)
(split-string completions "^'\\|^\"\\|;\\|'$\\|\"$" t)))))
-(defun python-shell-completion--get-completion (input completions)
- "Get completion for INPUT using COMPLETIONS."
- (let ((completion (when completions
- (try-completion input completions))))
- (cond ((eq completion t)
- input)
- ((null completion)
- (message "Can't find completion for \"%s\"" input)
- (ding)
- input)
- ((not (string= input completion))
- completion)
- (t
- (message "Making completion list...")
- (with-output-to-temp-buffer "*Python Completions*"
- (display-completion-list
- (all-completions input completions)))
- input))))
+(defun python-shell-completion--do-completion-at-point (process)
+ "Do completion at point for PROCESS."
+ (with-syntax-table python-dotty-syntax-table
+ (let* ((beg
+ (save-excursion
+ (let* ((paren-depth (car (syntax-ppss)))
+ (syntax-string "w_")
+ (syntax-list (string-to-syntax syntax-string)))
+ ;; Stop scanning for the beginning of the completion subject
+ ;; after the char before point matches a delimiter
+ (while (member (car (syntax-after (1- (point)))) syntax-list)
+ (skip-syntax-backward syntax-string)
+ (when (or (equal (char-before) ?\))
+ (equal (char-before) ?\"))
+ (forward-char -1))
+ (while (or
+ ;; honor initial paren depth
+ (> (car (syntax-ppss)) paren-depth)
+ (python-info-ppss-context 'string))
+ (forward-char -1))))
+ (point)))
+ (end (point))
+ (line (buffer-substring-no-properties (point-at-bol) end))
+ (input (buffer-substring-no-properties beg end))
+ ;; Get the last prompt for the inferior process buffer. This is
+ ;; used for the completion code selection heuristic.
+ (prompt
+ (with-current-buffer (process-buffer process)
+ (buffer-substring-no-properties
+ (overlay-start comint-last-prompt-overlay)
+ (overlay-end comint-last-prompt-overlay))))
+ (completion-context
+ ;; Check whether a prompt matches a pdb string, an import statement
+ ;; or just the standard prompt and use the correct
+ ;; python-shell-completion-*-code string
+ (cond ((and (> (length python-shell-completion-pdb-string-code) 0)
+ (string-match
+ (concat "^" python-shell-prompt-pdb-regexp) prompt))
+ 'pdb)
+ ((and (>
+ (length python-shell-completion-module-string-code) 0)
+ (string-match
+ (concat "^" python-shell-prompt-regexp) prompt)
+ (string-match "^[ \t]*\\(from\\|import\\)[ \t]" line))
+ 'import)
+ ((string-match
+ (concat "^" python-shell-prompt-regexp) prompt)
+ 'default)
+ (t nil)))
+ (completion-code
+ (case completion-context
+ ('pdb python-shell-completion-pdb-string-code)
+ ('import python-shell-completion-module-string-code)
+ ('default python-shell-completion-string-code)
+ (t nil)))
+ (input
+ (if (eq completion-context 'import)
+ (replace-regexp-in-string "^[ \t]+" "" line)
+ input))
+ (completions
+ (and completion-code (> (length input) 0)
+ (python-shell-completion--get-completions
+ input process completion-code))))
+ (list beg end completions))))
(defun python-shell-completion-complete-at-point ()
"Perform completion at point in inferior Python process."
(interactive)
- (with-syntax-table python-dotty-syntax-table
- (when (and comint-last-prompt-overlay
- (> (point-marker) (overlay-end comint-last-prompt-overlay)))
- (let* ((process (get-buffer-process (current-buffer)))
- (input (substring-no-properties
- (or (comint-word (current-word)) "") nil nil)))
- (delete-char (- (length input)))
- (insert
- (python-shell-completion--get-completion
- input (python-shell-completion--get-completions input process)))))))
+ (and comint-last-prompt-overlay
+ (> (point-marker) (overlay-end comint-last-prompt-overlay))
+ (python-shell-completion--do-completion-at-point
+ (get-buffer-process (current-buffer)))))
(defun python-shell-completion-complete-or-indent ()
"Complete or indent depending on the context.
(buffer-substring (comint-line-beginning-position)
(point-marker)))
(indent-for-tab-command)
- (comint-dynamic-complete)))
+ (completion-at-point)))
\f
;;; PDB Track integration
+(defcustom python-pdbtrack-activate t
+ "Non-nil makes python shell enable pdbtracking."
+ :type 'boolean
+ :group 'python
+ :safe 'booleanp)
+
(defcustom python-pdbtrack-stacktrace-info-regexp
- "> %s(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
+ "^> \\([^\"(<]+\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
"Regular Expression matching stacktrace information.
-Used to extract the current line and module being inspected. The
-regexp should not start with a caret (^) and can contain a string
-placeholder (\%s) which is replaced with the filename beign
-inspected (so other files in the debugging process are not
-opened)"
+Used to extract the current line and module being inspected."
:type 'string
:group 'python
:safe 'stringp)
-(defvar python-pdbtrack-tracking-buffers '()
- "Alist containing elements of form (#<buffer> . #<buffer>).
-The car of each element of the alist is the tracking buffer and
-the cdr is the tracked buffer.")
-
-(defun python-pdbtrack-get-or-add-tracking-buffers ()
- "Get/Add a tracked buffer for the current buffer.
-Internally it uses the `python-pdbtrack-tracking-buffers' alist.
-Returns a cons with the form:
- * (#<tracking buffer> . #< tracked buffer>)."
- (or
- (assq (current-buffer) python-pdbtrack-tracking-buffers)
- (let* ((file (with-current-buffer (current-buffer)
- inferior-python-mode-current-file))
- (tracking-buffers
- `(,(current-buffer) .
- ,(or (get-file-buffer file)
- (find-file-noselect file)))))
- (set-buffer (cdr tracking-buffers))
- (python-mode)
- (set-buffer (car tracking-buffers))
- (setq python-pdbtrack-tracking-buffers
- (cons tracking-buffers python-pdbtrack-tracking-buffers))
- tracking-buffers)))
+(defvar python-pdbtrack-tracked-buffer nil
+ "Variable containing the value of the current tracked buffer.
+Never set this variable directly, use
+`python-pdbtrack-set-tracked-buffer' instead.")
+(make-variable-buffer-local 'python-pdbtrack-tracked-buffer)
+
+(defvar python-pdbtrack-buffers-to-kill nil
+ "List of buffers to be deleted after tracking finishes.")
+(make-variable-buffer-local 'python-pdbtrack-buffers-to-kill)
+
+(defun python-pdbtrack-set-tracked-buffer (file-name)
+ "Set the buffer for FILE-NAME as the tracked buffer.
+Internally it uses the `python-pdbtrack-tracked-buffer' variable.
+Returns the tracked buffer."
+ (let ((file-buffer (get-file-buffer file-name)))
+ (if file-buffer
+ (setq python-pdbtrack-tracked-buffer file-buffer)
+ (setq file-buffer (find-file-noselect file-name))
+ (when (not (member file-buffer python-pdbtrack-buffers-to-kill))
+ (add-to-list 'python-pdbtrack-buffers-to-kill file-buffer)))
+ file-buffer))
(defun python-pdbtrack-comint-output-filter-function (output)
"Move overlay arrow to current pdb line in tracked buffer.
Argument OUTPUT is a string with the output from the comint process."
- (when (not (string= output ""))
- (let ((full-output (ansi-color-filter-apply
- (buffer-substring comint-last-input-end
- (point-max)))))
- (if (string-match python-shell-prompt-pdb-regexp full-output)
- (let* ((tracking-buffers (python-pdbtrack-get-or-add-tracking-buffers))
- (line-num
- (save-excursion
- (string-match
- (format python-pdbtrack-stacktrace-info-regexp
- (regexp-quote
- inferior-python-mode-current-file))
- full-output)
- (string-to-number (or (match-string-no-properties 1 full-output) ""))))
- (tracked-buffer-window (get-buffer-window (cdr tracking-buffers)))
+ (when (and python-pdbtrack-activate (not (string= output "")))
+ (let* ((full-output (ansi-color-filter-apply
+ (buffer-substring comint-last-input-end (point-max))))
+ (line-number)
+ (file-name
+ (with-temp-buffer
+ (insert full-output)
+ (goto-char (point-min))
+ ;; OK, this sucked but now it became a cool hack. The
+ ;; stacktrace information normally is on the first line
+ ;; but in some cases (like when doing a step-in) it is
+ ;; on the second.
+ (when (or (looking-at python-pdbtrack-stacktrace-info-regexp)
+ (and
+ (forward-line)
+ (looking-at python-pdbtrack-stacktrace-info-regexp)))
+ (setq line-number (string-to-number
+ (match-string-no-properties 2)))
+ (match-string-no-properties 1)))))
+ (if (and file-name line-number)
+ (let* ((tracked-buffer
+ (python-pdbtrack-set-tracked-buffer file-name))
+ (shell-buffer (current-buffer))
+ (tracked-buffer-window (get-buffer-window tracked-buffer))
(tracked-buffer-line-pos))
- (when line-num
- (with-current-buffer (cdr tracking-buffers)
- (set (make-local-variable 'overlay-arrow-string) "=>")
- (set (make-local-variable 'overlay-arrow-position) (make-marker))
- (setq tracked-buffer-line-pos (progn
- (goto-char (point-min))
- (forward-line (1- line-num))
- (point-marker)))
- (when tracked-buffer-window
- (set-window-point tracked-buffer-window tracked-buffer-line-pos))
- (set-marker overlay-arrow-position tracked-buffer-line-pos)))
- (pop-to-buffer (cdr tracking-buffers))
- (switch-to-buffer-other-window (car tracking-buffers)))
- (let ((tracking-buffers (assq (current-buffer)
- python-pdbtrack-tracking-buffers)))
- (when tracking-buffers
- (if inferior-python-mode-current-file
- (with-current-buffer (cdr tracking-buffers)
- (set-marker overlay-arrow-position nil))
- (kill-buffer (cdr tracking-buffers)))
- (setq python-pdbtrack-tracking-buffers
- (assq-delete-all (current-buffer)
- python-pdbtrack-tracking-buffers)))))))
+ (with-current-buffer tracked-buffer
+ (set (make-local-variable 'overlay-arrow-string) "=>")
+ (set (make-local-variable 'overlay-arrow-position) (make-marker))
+ (setq tracked-buffer-line-pos (progn
+ (goto-char (point-min))
+ (forward-line (1- line-number))
+ (point-marker)))
+ (when tracked-buffer-window
+ (set-window-point
+ tracked-buffer-window tracked-buffer-line-pos))
+ (set-marker overlay-arrow-position tracked-buffer-line-pos))
+ (pop-to-buffer tracked-buffer)
+ (switch-to-buffer-other-window shell-buffer))
+ (when python-pdbtrack-tracked-buffer
+ (with-current-buffer python-pdbtrack-tracked-buffer
+ (set-marker overlay-arrow-position nil))
+ (mapc #'(lambda (buffer)
+ (ignore-errors (kill-buffer buffer)))
+ python-pdbtrack-buffers-to-kill)
+ (setq python-pdbtrack-tracked-buffer nil
+ python-pdbtrack-buffers-to-kill nil)))))
output)
\f
(let ((process (python-shell-get-process)))
(if (not process)
(error "Completion needs an inferior Python process running")
- (with-syntax-table python-dotty-syntax-table
- (let* ((input (substring-no-properties
- (or (comint-word (current-word)) "") nil nil))
- (completions (python-shell-completion--get-completions
- input process)))
- (delete-char (- (length input)))
- (insert
- (python-shell-completion--get-completion
- input completions)))))))
-
-(add-to-list 'debug-ignored-errors "^Completion needs an inferior Python process running.")
+ (python-shell-completion--do-completion-at-point process))))
+
+(add-to-list 'debug-ignored-errors
+ "^Completion needs an inferior Python process running.")
\f
;;; Fill paragraph
:group 'python
:safe 'booleanp)
+(define-obsolete-variable-alias
+ 'python-use-skeletons 'python-skeleton-autoinsert "24.2")
+
(defvar python-skeleton-available '()
"Internal list of available skeletons.")
"Define a `python-mode' skeleton using NAME DOC and SKEL.
The skeleton will be bound to python-skeleton-NAME and will
be added to `python-mode-abbrev-table'."
+ (declare (indent 2))
(let* ((name (symbol-name name))
- (function-name (intern (concat "python-skeleton-" name))))
+ (function-name (intern (concat "python-skeleton-" name))))
`(progn
- (define-abbrev python-mode-abbrev-table ,name "" ',function-name)
+ (define-abbrev python-mode-abbrev-table ,name "" ',function-name
+ :system t)
(setq python-skeleton-available
(cons ',function-name python-skeleton-available))
(define-skeleton ,function-name
,(or doc
(format "Insert %s statement." name))
,@skel))))
-(put 'python-skeleton-define 'lisp-indent-function 2)
(defmacro python-define-auxiliary-skeleton (name doc &optional &rest skel)
"Define a `python-mode' auxiliary skeleton using NAME DOC and SKEL.
The skeleton will be bound to python-skeleton-NAME."
+ (declare (indent 2))
(let* ((name (symbol-name name))
- (function-name (intern (concat "python-skeleton--" name)))
+ (function-name (intern (concat "python-skeleton--" name)))
(msg (format
"Add '%s' clause? " name)))
(when (not skel)
(unless (y-or-n-p ,msg)
(signal 'quit t))
,@skel)))
-(put 'python-define-auxiliary-skeleton 'lisp-indent-function 2)
(python-define-auxiliary-skeleton else nil)
"Function name: "
"def " str " (" ("Parameter, %s: "
(unless (equal ?\( (char-before)) ", ")
- str) "):" \n
- "\"\"\"" - "\"\"\"" \n
- > _ \n)
+ str) "):" \n
+ "\"\"\"" - "\"\"\"" \n
+ > _ \n)
(python-skeleton-define class nil
"Class name: "
"class " str " (" ("Inheritance, %s: "
- (unless (equal ?\( (char-before)) ", ")
- str)
+ (unless (equal ?\( (char-before)) ", ")
+ str)
& ")" | -2
":" \n
"\"\"\"" - "\"\"\"" \n
return ''"
"Python code to get a module path."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defcustom python-ffap-string-code
"__FFAP_get_module_path('''%s''')\n"
"Python code used to get a string with the path of a module."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defun python-ffap-module-path (module)
"Function for `ffap-alist' to return path for MODULE."
(python-shell-send-string-no-output
(format python-ffap-string-code module) process)))
(when module-file
- (substring-no-properties module-file 1 -1))))))
+ (substring-no-properties module-file 1 -1))))))
(eval-after-load "ffap"
'(progn
;;; Code check
(defcustom python-check-command
- "pychecker --stdlib"
+ "pyflakes"
"Command used to check a Python file."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
+
+(defcustom python-check-buffer-name
+ "*Python check: %s*"
+ "Buffer name used for check commands."
+ :type 'string
+ :group 'python)
(defvar python-check-custom-command nil
"Internal use.")
`python-check-command' for the default."
(interactive
(list (read-string "Check command: "
- (or python-check-custom-command
- (concat python-check-command " "
+ (or python-check-custom-command
+ (concat python-check-command " "
(shell-quote-argument
(or
(let ((name (buffer-file-name)))
"")))))))
(setq python-check-custom-command command)
(save-some-buffers (not compilation-ask-about-save) nil)
- (compilation-start command))
+ (let ((process-environment (python-shell-calculate-process-environment))
+ (exec-path (python-shell-calculate-exec-path)))
+ (compilation-start command nil
+ (lambda (mode-name)
+ (format python-check-buffer-name command)))))
\f
;;; Eldoc
print(doc)"
"Python code to setup documentation retrieval."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defcustom python-eldoc-string-code
"__PYDOC_get_help('''%s''')\n"
"Python code used to get a string with the documentation of an object."
:type 'string
- :group 'python
- :safe 'stringp)
+ :group 'python)
(defun python-eldoc--get-doc-at-point (&optional force-input force-process)
"Internal implementation to get documentation at point.
(current-word)
(concat current-defun "." (current-word))))))
(ppss (syntax-ppss))
- (help (when (and input
- (not (string= input (concat current-defun ".")))
- (not (or (python-info-ppss-context 'string ppss)
- (python-info-ppss-context 'comment ppss))))
- (when (string-match (concat
- (regexp-quote (concat current-defun "."))
- "self\\.") input)
+ (help (when (and
+ input
+ (not (string= input (concat current-defun ".")))
+ (not (or (python-info-ppss-context 'string ppss)
+ (python-info-ppss-context 'comment ppss))))
+ (when (string-match
+ (concat
+ (regexp-quote (concat current-defun "."))
+ "self\\.") input)
(with-temp-buffer
(insert input)
(goto-char (point-min))
(forward-word)
(forward-char)
- (delete-region (point-marker) (search-forward "self."))
- (setq input (buffer-substring (point-min) (point-max)))))
+ (delete-region
+ (point-marker) (search-forward "self."))
+ (setq input (buffer-substring
+ (point-min) (point-max)))))
(python-shell-send-string-no-output
(format python-eldoc-string-code input) process))))
(with-current-buffer (process-buffer process)
(defun python-eldoc-at-point (symbol)
"Get help on SYMBOL using `help'.
Interactively, prompt for symbol."
- (interactive
- (let ((symbol (with-syntax-table python-dotty-syntax-table
- (current-word)))
- (enable-recursive-minibuffers t))
- (list (read-string (if symbol
- (format "Describe symbol (default %s): " symbol)
- "Describe symbol: ")
- nil nil symbol))))
- (let ((process (python-shell-get-process)))
- (if (not process)
- (message "Eldoc needs an inferior Python process running.")
- (message (python-eldoc--get-doc-at-point symbol process)))))
+ (interactive
+ (let ((symbol (with-syntax-table python-dotty-syntax-table
+ (current-word)))
+ (enable-recursive-minibuffers t))
+ (list (read-string (if symbol
+ (format "Describe symbol (default %s): " symbol)
+ "Describe symbol: ")
+ nil nil symbol))))
+ (let ((process (python-shell-get-process)))
+ (if (not process)
+ (message "Eldoc needs an inferior Python process running.")
+ (message (python-eldoc--get-doc-at-point symbol process)))))
\f
;;; Imenu
(defun python-imenu-make-element-tree (element-list full-element plain-index)
"Make a tree from plain alist of module names.
-ELEMENT-LIST is the defun name splitted by \".\" and FULL-ELEMENT
+ELEMENT-LIST is the defun name split by \".\" and FULL-ELEMENT
is the same thing, the difference is that FULL-ELEMENT remains
untouched in all recursive calls.
Argument PLAIN-INDEX is the calculated plain index used to build the tree."
(push (cons subelement-name subelement-point)
python-imenu-index-alist)
(when (not (listp (cdr path-ref)))
- ;; Modifiy root cdr to be a list
+ ;; Modify root cdr to be a list.
(setcdr path-ref
(list (cons (format python-imenu-subtree-root-label
(car path-ref))
(save-restriction
(widen)
(save-excursion
- (goto-char (line-end-position))
- (forward-comment -9999)
+ (end-of-line 1)
(setq min-indent (current-indentation))
- (while (python-beginning-of-defun-function 1 t)
+ (while (python-beginning-of-defun-function 1)
(when (or (< (current-indentation) min-indent)
first-run)
(setq first-run nil)
(when (member (current-word) '("except" "else"))
(point-marker))))))))
-(defun python-info-line-ends-backslash-p ()
- "Return non-nil if current line ends with backslash."
- (string= (or (ignore-errors
- (buffer-substring
- (line-end-position)
- (- (line-end-position) 1))) "") "\\"))
+(defun python-info-closing-block-message (&optional closing-block-point)
+ "Message the contents of the block the current line closes.
+With optional argument CLOSING-BLOCK-POINT use that instead of
+recalculating it calling `python-info-closing-block'."
+ (let ((point (or closing-block-point (python-info-closing-block))))
+ (when point
+ (save-restriction
+ (widen)
+ (message "Closes %s" (save-excursion
+ (goto-char point)
+ (back-to-indentation)
+ (buffer-substring
+ (point) (line-end-position))))))))
+
+(defun python-info-line-ends-backslash-p (&optional line-number)
+ "Return non-nil if current line ends with backslash.
+With optional argument LINE-NUMBER, check that line instead."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (when line-number
+ (goto-char line-number))
+ (while (and (not (eobp))
+ (goto-char (line-end-position))
+ (python-info-ppss-context 'paren)
+ (not (equal (char-before (point)) ?\\)))
+ (forward-line 1))
+ (when (equal (char-before) ?\\)
+ (point-marker)))))
+
+(defun python-info-beginning-of-backslash (&optional line-number)
+ "Return the point where the backslashed line start.
+Optional argument LINE-NUMBER forces the line number to check against."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (when line-number
+ (goto-char line-number))
+ (when (python-info-line-ends-backslash-p)
+ (while (save-excursion
+ (goto-char (line-beginning-position))
+ (python-info-ppss-context 'paren))
+ (forward-line -1))
+ (back-to-indentation)
+ (point-marker)))))
(defun python-info-continuation-line-p ()
- "Return non-nil if current line is continuation of another."
- (let ((current-ppss-context-type (python-info-ppss-context-type)))
- (and
- (equal (save-excursion
- (goto-char (line-end-position))
- (forward-comment 9999)
- (python-info-ppss-context-type))
- current-ppss-context-type)
- (or (python-info-line-ends-backslash-p)
- (string-match ",[[:space:]]*$" (buffer-substring
- (line-beginning-position)
- (line-end-position)))
- (save-excursion
- (let ((innermost-paren (progn
- (goto-char (line-end-position))
- (python-info-ppss-context 'paren))))
- (when (and innermost-paren
- (and (<= (line-beginning-position) innermost-paren)
- (>= (line-end-position) innermost-paren)))
- (goto-char innermost-paren)
- (looking-at (python-rx open-paren (* space) line-end)))))
- (save-excursion
- (back-to-indentation)
- (python-info-ppss-context 'paren))))))
+ "Check if current line is continuation of another.
+When current line is continuation of another return the point
+where the continued line ends."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let* ((context-type (progn
+ (back-to-indentation)
+ (python-info-ppss-context-type)))
+ (line-start (line-number-at-pos))
+ (context-start (when context-type
+ (python-info-ppss-context context-type))))
+ (cond ((equal context-type 'paren)
+ ;; Lines inside a paren are always a continuation line
+ ;; (except the first one).
+ (when (equal (python-info-ppss-context-type) 'paren)
+ (python-util-forward-comment -1)
+ (python-util-forward-comment -1)
+ (point-marker)))
+ ((or (equal context-type 'comment)
+ (equal context-type 'string))
+ ;; move forward an roll again
+ (goto-char context-start)
+ (python-util-forward-comment)
+ (python-info-continuation-line-p))
+ (t
+ ;; Not within a paren, string or comment, the only way we are
+ ;; dealing with a continuation line is that previous line
+ ;; contains a backslash, and this can only be the previous line
+ ;; from current
+ (back-to-indentation)
+ (python-util-forward-comment -1)
+ (python-util-forward-comment -1)
+ (when (and (equal (1- line-start) (line-number-at-pos))
+ (python-info-line-ends-backslash-p))
+ (point-marker))))))))
(defun python-info-block-continuation-line-p ()
"Return non-nil if current line is a continuation of a block."
(save-excursion
- (while (and (not (bobp))
- (python-info-continuation-line-p))
- (forward-line -1))
- (forward-line 1)
- (back-to-indentation)
- (when (looking-at (python-rx block-start))
- (point-marker))))
+ (when (python-info-continuation-line-p)
+ (forward-line -1)
+ (back-to-indentation)
+ (when (looking-at (python-rx block-start))
+ (point-marker)))))
(defun python-info-assignment-continuation-line-p ()
- "Return non-nil if current line is a continuation of an assignment."
+ "Check if current line is a continuation of an assignment.
+When current line is continuation of another with an assignment
+return the point of the first non-blank character after the
+operator."
(save-excursion
- (while (and (not (bobp))
- (python-info-continuation-line-p))
- (forward-line -1))
- (forward-line 1)
- (back-to-indentation)
- (when (and (not (looking-at (python-rx block-start)))
- (save-excursion
+ (when (python-info-continuation-line-p)
+ (forward-line -1)
+ (back-to-indentation)
+ (when (and (not (looking-at (python-rx block-start)))
(and (re-search-forward (python-rx not-simple-operator
assignment-operator
not-simple-operator)
(line-end-position) t)
(not (or (python-info-ppss-context 'string)
(python-info-ppss-context 'paren)
- (python-info-ppss-context 'comment))))))
- (point-marker))))
+ (python-info-ppss-context 'comment)))))
+ (skip-syntax-forward "\s")
+ (point-marker)))))
(defun python-info-ppss-context (type &optional syntax-ppss)
"Return non-nil if point is on TYPE using SYNTAX-PPSS.
'paren)
(t nil))))
+(defun python-info-looking-at-beginning-of-defun (&optional syntax-ppss)
+ "Check if point is at `beginning-of-defun' using SYNTAX-PPSS."
+ (and (not (python-info-ppss-context-type (or syntax-ppss (syntax-ppss))))
+ (save-excursion
+ (beginning-of-line 1)
+ (looking-at python-nav-beginning-of-defun-regexp))))
+
+(defun python-info-current-line-comment-p ()
+ "Check if current line is a comment line."
+ (char-equal (or (char-after (+ (point) (current-indentation))) ?_) ?#))
+
+(defun python-info-current-line-empty-p ()
+ "Check if current line is empty, ignoring whitespace."
+ (save-excursion
+ (beginning-of-line 1)
+ (looking-at
+ (python-rx line-start (* whitespace)
+ (group (* not-newline))
+ (* whitespace) line-end))
+ (string-equal "" (match-string-no-properties 1))))
+
\f
;;; Utility functions
(and (symbolp (car pair))
(string-match (or regexp "^python-")
(symbol-name (car pair)))
- (set (make-local-variable (car pair))
- (cdr pair))))
+ (set (make-local-variable (car pair))
+ (cdr pair))))
(buffer-local-variables from-buffer)))
+(defun python-util-forward-comment (&optional direction)
+ "Python mode specific version of `forward-comment'.
+Optional argument DIRECTION defines the direction to move to."
+ (let ((comment-start (python-info-ppss-context 'comment))
+ (factor (if (< (or direction 0) 0)
+ -99999
+ 99999)))
+ (when comment-start
+ (goto-char comment-start))
+ (forward-comment factor)))
+
\f
;;;###autoload
-(define-derived-mode python-mode fundamental-mode "Python"
+(define-derived-mode python-mode prog-mode "Python"
"Major mode for editing Python files.
\\{python-mode-map}
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(set (make-local-variable 'parse-sexp-ignore-comments) t)
+ (set (make-local-variable 'forward-sexp-function)
+ 'python-nav-forward-sexp-function)
+
(set (make-local-variable 'font-lock-defaults)
- '(python-font-lock-keywords
- nil nil nil nil
- (font-lock-syntactic-keywords . python-font-lock-syntactic-keywords)))
+ '(python-font-lock-keywords nil nil nil nil))
+
+ (set (make-local-variable 'syntax-propertize-function)
+ python-syntax-propertize-function)
- (set (make-local-variable 'indent-line-function) #'python-indent-line-function)
+ (set (make-local-variable 'indent-line-function)
+ #'python-indent-line-function)
(set (make-local-variable 'indent-region-function) #'python-indent-region)
(set (make-local-variable 'paragraph-start) "\\s-*$")
- (set (make-local-variable 'fill-paragraph-function) 'python-fill-paragraph-function)
+ (set (make-local-variable 'fill-paragraph-function)
+ 'python-fill-paragraph-function)
(set (make-local-variable 'beginning-of-defun-function)
#'python-beginning-of-defun-function)
(add-hook 'completion-at-point-functions
'python-completion-complete-at-point nil 'local)
+ (add-hook 'post-self-insert-hook
+ 'python-indent-post-self-insert-function nil 'local)
+
(setq imenu-create-index-function #'python-imenu-create-index)
(set (make-local-variable 'add-log-current-defun-function)
#'python-info-current-defun)
+ (add-hook 'which-func-functions #'python-info-current-defun nil t)
+
(set (make-local-variable 'skeleton-further-elements)
'((abbrev-mode nil)
(< '(backward-delete-char-untabify (min python-indent-offset
- (current-column))))
- (^ '(- (1+ (current-indentation))))))
+ (current-column))))
+ (^ '(- (1+ (current-indentation))))))
(set (make-local-variable 'eldoc-documentation-function)
#'python-eldoc-function)
(add-to-list 'hs-special-modes-alist
- `(python-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#"
+ `(python-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#"
,(lambda (arg)
(python-end-of-defun-function)) nil))