summaryrefslogtreecommitdiff
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
authorKen Raeburn <raeburn@raeburn.org>2015-11-01 01:42:21 -0400
committerKen Raeburn <raeburn@raeburn.org>2015-11-01 01:42:21 -0400
commit39372e1a1032521be74575bb06f95a3898fbae30 (patch)
tree754bd242a23d2358ea116126fcb0a629947bd9ec /lisp/progmodes/python.el
parent6a3121904d76e3b2f63007341d48c5c1af55de80 (diff)
parente11aaee266da52937a3a031cb108fe13f68958c3 (diff)
downloademacs-39372e1a1032521be74575bb06f95a3898fbae30.tar.gz
merge from trunk
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el3853
1 files changed, 2698 insertions, 1155 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 01833ffd70b..6ff12b54976 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -1,11 +1,12 @@
-;;; python.el --- Python's flying circus support for Emacs
+;;; python.el --- Python's flying circus support for Emacs -*- lexical-binding: t -*-
-;; Copyright (C) 2003-2013 Free Software Foundation, Inc.
+;; Copyright (C) 2003-2015 Free Software Foundation, Inc.
-;; Author: Fabián E. Gallina <fabian@anue.biz>
+;; Author: Fabián E. Gallina <fgallina@gnu.org>
;; URL: https://github.com/fgallina/python.el
-;; Version: 0.24.2
-;; Maintainer: FSF
+;; Version: 0.25.1
+;; Package-Requires: ((emacs "24.1") (cl-lib "1.0"))
+;; Maintainer: emacs-devel@gnu.org
;; Created: Jul 2010
;; Keywords: languages
@@ -31,18 +32,18 @@
;; found in GNU/Emacs.
;; Implements Syntax highlighting, Indentation, Movement, Shell
-;; interaction, Shell completion, Shell virtualenv support, Pdb
-;; tracking, Symbol completion, Skeletons, FFAP, Code Check, Eldoc,
-;; Imenu.
+;; interaction, Shell completion, Shell virtualenv support, Shell
+;; package support, Shell syntax highlighting, Pdb tracking, Symbol
+;; completion, Skeletons, FFAP, Code Check, Eldoc, Imenu.
;; Syntax highlighting: Fontification of code is provided and supports
;; python's triple quoted strings properly.
;; Indentation: Automatic indentation with indentation cycling is
;; provided, it allows you to navigate different available levels of
-;; indentation by hitting <tab> several times. Also when inserting a
-;; colon the `python-indent-electric-colon' command is invoked and
-;; causes the current line to be dedented automatically if needed.
+;; indentation by hitting <tab> several times. Also electric-indent-mode
+;; is supported such that when inserting a colon the current line is
+;; dedented automatically if needed.
;; Movement: `beginning-of-defun' and `end-of-defun' functions are
;; properly implemented. There are also specialized
@@ -52,67 +53,96 @@
;; Extra functions `python-nav-forward-statement',
;; `python-nav-backward-statement',
;; `python-nav-beginning-of-statement', `python-nav-end-of-statement',
-;; `python-nav-beginning-of-block' and `python-nav-end-of-block' are
-;; included but no bound to any key. At last but not least the
-;; specialized `python-nav-forward-sexp' allows easy navigation
-;; between code blocks. If you prefer `cc-mode'-like `forward-sexp'
-;; movement, setting `forward-sexp-function' to nil is enough, You can
-;; do that using the `python-mode-hook':
+;; `python-nav-beginning-of-block', `python-nav-end-of-block' and
+;; `python-nav-if-name-main' are included but no bound to any key. At
+;; last but not least the specialized `python-nav-forward-sexp' allows
+;; easy navigation between code blocks. If you prefer `cc-mode'-like
+;; `forward-sexp' movement, setting `forward-sexp-function' to nil is
+;; enough, You can do that using the `python-mode-hook':
;; (add-hook 'python-mode-hook
;; (lambda () (setq forward-sexp-function nil)))
-;; Shell interaction: is provided and allows you to execute easily any
-;; block of code of your current buffer in an inferior Python process.
+;; Shell interaction: is provided and allows opening Python shells
+;; inside Emacs and executing any block of code of your current buffer
+;; in that inferior Python process.
+
+;; Besides that only the standard CPython (2.x and 3.x) shell and
+;; IPython are officially supported out of the box, the interaction
+;; should support any other readline based Python shells as well
+;; (e.g. Jython and PyPy have been reported to work). You can change
+;; your default interpreter and commandline arguments by setting the
+;; `python-shell-interpreter' and `python-shell-interpreter-args'
+;; variables. This example enables IPython globally:
+
+;; (setq python-shell-interpreter "ipython"
+;; python-shell-interpreter-args "-i")
+
+;; Using the "console" subcommand to start IPython in server-client
+;; mode is known to fail intermittently due a bug on IPython itself
+;; (see URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=18052#27').
+;; There seems to be a race condition in the IPython server (A.K.A
+;; kernel) when code is sent while it is still initializing, sometimes
+;; causing the shell to get stalled. With that said, if an IPython
+;; kernel is already running, "console --existing" seems to work fine.
+
+;; Running IPython on Windows needs more tweaking. The way you should
+;; set `python-shell-interpreter' and `python-shell-interpreter-args'
+;; is as follows (of course you need to modify the paths according to
+;; your system):
+
+;; (setq python-shell-interpreter "C:\\Python27\\python.exe"
+;; python-shell-interpreter-args
+;; "-i C:\\Python27\\Scripts\\ipython-script.py")
+
+;; Missing or delayed output used to happen due to differences between
+;; Operating Systems' pipe buffering (e.g. CPython 3.3.4 in Windows 7.
+;; See URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=17304'). To
+;; avoid this, the `python-shell-unbuffered' defaults to non-nil and
+;; controls whether `python-shell-calculate-process-environment'
+;; should set the "PYTHONUNBUFFERED" environment variable on startup:
+;; See URL `https://docs.python.org/3/using/cmdline.html#cmdoption-u'.
+
+;; The interaction relies upon having prompts for input (e.g. ">>> "
+;; and "... " in standard Python shell) and output (e.g. "Out[1]: " in
+;; IPython) detected properly. Failing that Emacs may hang but, in
+;; the case that happens, you can recover with \\[keyboard-quit]. To
+;; avoid this issue, a two-step prompt autodetection mechanism is
+;; provided: the first step is manual and consists of a collection of
+;; regular expressions matching common prompts for Python shells
+;; stored in `python-shell-prompt-input-regexps' and
+;; `python-shell-prompt-output-regexps', and dir-local friendly vars
+;; `python-shell-prompt-regexp', `python-shell-prompt-block-regexp',
+;; `python-shell-prompt-output-regexp' which are appended to the
+;; former automatically when a shell spawns; the second step is
+;; automatic and depends on the `python-shell-prompt-detect' helper
+;; function. See its docstring for details on global variables that
+;; modify its behavior.
;; Shell completion: hitting tab will try to complete the current
-;; word. Shell completion is implemented in a manner that if you
-;; change the `python-shell-interpreter' to any other (for example
-;; IPython) it should be easy to integrate another way to calculate
-;; completions. You just need to specify your custom
-;; `python-shell-completion-setup-code' and
-;; `python-shell-completion-string-code'.
-
-;; Here is a complete example of the settings you would use for
-;; iPython 0.11:
-
-;; (setq
-;; python-shell-interpreter "ipython"
-;; python-shell-interpreter-args ""
-;; python-shell-prompt-regexp "In \\[[0-9]+\\]: "
-;; python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
-;; 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")
-
-;; For iPython 0.10 everything would be the same except for
-;; `python-shell-completion-string-code' and
-;; `python-shell-completion-module-string-code':
-
-;; (setq python-shell-completion-string-code
-;; "';'.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
-;; bundles Python without it (like Windows) just install the
-;; pyreadline from http://ipython.scipy.org/moin/PyReadline/Intro and
-;; you should be good to go.
+;; word. The two built-in mechanisms depend on Python's readline
+;; module: the "native" completion is tried first and is activated
+;; when `python-shell-completion-native-enable' is non-nil, the
+;; current `python-shell-interpreter' is not a member of the
+;; `python-shell-completion-native-disabled-interpreters' variable and
+;; `python-shell-completion-native-setup' succeeds; the "fallback" or
+;; "legacy" mechanism works by executing Python code in the background
+;; and enables auto-completion for shells that do not support
+;; receiving escape sequences (with some limitations, i.e. completion
+;; in blocks does not work). The code executed for the "fallback"
+;; completion can be found in `python-shell-completion-setup-code' and
+;; `python-shell-completion-string-code' variables. Their default
+;; values enable completion for both CPython and IPython, and probably
+;; any readline based shell (it's known to work with PyPy). If your
+;; Python installation lacks readline (like CPython for Windows),
+;; installing pyreadline (URL `http://ipython.org/pyreadline.html')
+;; should suffice. To troubleshoot why you are not getting any
+;; completions, you can try the following in your Python shell:
+
+;; >>> import readline, rlcompleter
+
+;; If you see an error, then you need to either install pyreadline or
+;; setup custom code that avoids that dependency.
;; Shell virtualenv support: The shell also contains support for
;; virtualenvs and other special environment modifications thanks to
@@ -135,18 +165,34 @@
;; (python-shell-exec-path . ("/path/to/env/bin/"))
;; Since the above is cumbersome and can be programmatically
-;; calculated, the variable `python-shell-virtualenv-path' is
+;; calculated, the variable `python-shell-virtualenv-root' is
;; provided. When this variable is set with the path of the
;; virtualenv to use, `process-environment' and `exec-path' get proper
;; values in order to run shells inside the specified virtualenv. So
;; the following will achieve the same as the previous example:
-;; (setq python-shell-virtualenv-path "/path/to/env/")
+;; (setq python-shell-virtualenv-root "/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.
+;; Shell package support: you can enable a package in the current
+;; shell so that relative imports work properly using the
+;; `python-shell-package-enable' command.
+
+;; Shell remote support: remote Python shells are started with the
+;; correct environment for files opened remotely through tramp, also
+;; respecting dir-local variables provided `enable-remote-dir-locals'
+;; is non-nil. The logic for this is transparently handled by the
+;; `python-shell-with-environment' macro.
+
+;; Shell syntax highlighting: when enabled current input in shell is
+;; highlighted. The variable `python-shell-font-lock-enable' controls
+;; activation of this feature globally when shells are started.
+;; Activation/deactivation can be also controlled on the fly via the
+;; `python-shell-font-lock-toggle' command.
+
;; 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.
@@ -155,15 +201,13 @@
;; the shell completion in background so you should run
;; `python-shell-send-buffer' from time to time to get better results.
-;; Skeletons: 6 skeletons are provided for simple inserting of class,
-;; def, for, if, try and while. These skeletons are integrated with
-;; abbrev. If you have `abbrev-mode' activated and
+;; Skeletons: skeletons are provided for simple inserting of things like class,
+;; def, for, import, if, try, and while. These skeletons are
+;; integrated with abbrev. If you have `abbrev-mode' activated and
;; `python-skeleton-autoinsert' is set to t, then whenever you type
;; the name of any of those defined and hit SPC, they will be
;; automatically expanded. As an alternative you can use the defined
-;; skeleton commands: `python-skeleton-class', `python-skeleton-def'
-;; `python-skeleton-for', `python-skeleton-if', `python-skeleton-try'
-;; and `python-skeleton-while'.
+;; skeleton commands: `python-skeleton-<foo>'.
;; FFAP: You can find the filename for a given module when using ffap
;; out of the box. This feature needs an inferior python shell
@@ -180,17 +224,19 @@
;; Imenu: There are two index building functions to be used as
;; `imenu-create-index-function': `python-imenu-create-index' (the
;; default one, builds the alist in form of a tree) and
-;; `python-imenu-create-flat-index'. See also
+;; `python-imenu-create-flat-index'. See also
;; `python-imenu-format-item-label-function',
;; `python-imenu-format-parent-item-label-function',
;; `python-imenu-format-parent-item-jump-label-function' variables for
;; changing the way labels are formatted in the tree version.
-;; If you used python-mode.el you probably will miss auto-indentation
-;; when inserting newlines. To achieve the same behavior you have
-;; two options:
+;; If you used python-mode.el you may miss auto-indentation when
+;; inserting newlines. To achieve the same behavior you have two
+;; options:
-;; 1) Use GNU/Emacs' standard binding for `newline-and-indent': C-j.
+;; 1) Enable the minor-mode `electric-indent-mode' (enabled by
+;; default) and use RET. If this mode is disabled use
+;; `newline-and-indent', bound to C-j.
;; 2) Add the following hook in your .emacs:
@@ -213,7 +259,10 @@
;;; Code:
(require 'ansi-color)
+(require 'cl-lib)
(require 'comint)
+(require 'json)
+(require 'tramp-sh)
;; Avoid compiler warnings
(defvar view-return-to-alist)
@@ -221,11 +270,12 @@
(defvar outline-heading-end-regexp)
(autoload 'comint-mode "comint")
+(autoload 'help-function-arglist "help-fns")
;;;###autoload
(add-to-list 'auto-mode-alist (cons (purecopy "\\.py\\'") 'python-mode))
;;;###autoload
-(add-to-list 'interpreter-mode-alist (cons (purecopy "python") 'python-mode))
+(add-to-list 'interpreter-mode-alist (cons (purecopy "python[0-9.]*") 'python-mode))
(defgroup python nil
"Python Language's flying circus support for Emacs."
@@ -233,6 +283,18 @@
:version "24.3"
:link '(emacs-commentary-link "python"))
+
+;;; 24.x Compat
+
+
+(unless (fboundp 'prog-widen)
+ (defun prog-widen ()
+ (widen)))
+
+(unless (fboundp 'prog-first-column)
+ (defun prog-first-column ()
+ 0))
+
;;; Bindings
@@ -242,18 +304,19 @@
(define-key map [remap backward-sentence] 'python-nav-backward-block)
(define-key map [remap forward-sentence] 'python-nav-forward-block)
(define-key map [remap backward-up-list] 'python-nav-backward-up-list)
+ (define-key map [remap mark-defun] 'python-mark-defun)
(define-key map "\C-c\C-j" 'imenu)
;; Indent specific
(define-key map "\177" 'python-indent-dedent-line-backspace)
(define-key map (kbd "<backtab>") 'python-indent-dedent-line)
(define-key map "\C-c<" 'python-indent-shift-left)
(define-key map "\C-c>" 'python-indent-shift-right)
- (define-key map ":" 'python-indent-electric-colon)
;; Skeletons
(define-key map "\C-c\C-tc" 'python-skeleton-class)
(define-key map "\C-c\C-td" 'python-skeleton-def)
(define-key map "\C-c\C-tf" 'python-skeleton-for)
(define-key map "\C-c\C-ti" 'python-skeleton-if)
+ (define-key map "\C-c\C-tm" 'python-skeleton-import)
(define-key map "\C-c\C-tt" 'python-skeleton-try)
(define-key map "\C-c\C-tw" 'python-skeleton-while)
;; Shell interaction
@@ -317,12 +380,19 @@
;;; Python specialized rx
-(eval-when-compile
+(eval-and-compile
(defconst python-rx-constituents
`((block-start . ,(rx symbol-start
(or "def" "class" "if" "elif" "else" "try"
"except" "finally" "for" "while" "with")
symbol-end))
+ (dedenter . ,(rx symbol-start
+ (or "elif" "else" "except" "finally")
+ symbol-end))
+ (block-ender . ,(rx symbol-start
+ (or
+ "break" "continue" "pass" "raise" "return")
+ symbol-end))
(decorator . ,(rx line-start (* space) ?@ (any letter ?_)
(* (any word ?_))))
(defun . ,(rx symbol-start (or "def" "class") symbol-end))
@@ -353,12 +423,23 @@
(* ?\\ ?\\) (any ?\' ?\")))
(* ?\\ ?\\)
;; Match single or triple quotes of any kind.
- (group (or "\"" "\"\"\"" "'" "'''"))))))
+ (group (or "\"" "\"\"\"" "'" "'''")))))
+ (coding-cookie . ,(rx line-start ?# (* space)
+ (or
+ ;; # coding=<encoding name>
+ (: "coding" (or ?: ?=) (* space) (group-n 1 (+ (or word ?-))))
+ ;; # -*- coding: <encoding name> -*-
+ (: "-*-" (* space) "coding:" (* space)
+ (group-n 1 (+ (or word ?-))) (* space) "-*-")
+ ;; # vim: set fileencoding=<encoding name> :
+ (: "vim:" (* space) "set" (+ space)
+ "fileencoding" (* space) ?= (* space)
+ (group-n 1 (+ (or word ?-))) (* space) ":")))))
"Additional Python specific sexps for `python-rx'")
(defmacro python-rx (&rest regexps)
"Python mode specialized rx macro.
-This variant of `rx' supports common python named REGEXPS."
+This variant of `rx' supports common Python named REGEXPS."
(let ((rx-constituents (append python-rx-constituents rx-constituents)))
(cond ((null regexps)
(error "No regexp"))
@@ -370,7 +451,7 @@ This variant of `rx' supports common python named REGEXPS."
;;; Font-lock and syntax
-(eval-when-compile
+(eval-and-compile
(defun python-syntax--context-compiler-macro (form type &optional syntax-ppss)
(pcase type
(`'comment
@@ -403,9 +484,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")
@@ -417,6 +503,14 @@ The type returned can be `comment', `string' or `paren'."
'python-info-ppss-comment-or-string-p
#'python-syntax-comment-or-string-p "24.3")
+(defun python-font-lock-syntactic-face-function (state)
+ "Return syntactic face given STATE."
+ (if (nth 3 state)
+ (if (python-info-docstring-p state)
+ font-lock-doc-face
+ font-lock-string-face)
+ font-lock-comment-face))
+
(defvar python-font-lock-keywords
;; Keywords
`(,(rx symbol-start
@@ -501,29 +595,24 @@ The type returned can be `comment', `string' or `paren'."
(,(lambda (limit)
(let ((re (python-rx (group (+ (any word ?. ?_)))
(? ?\[ (+ (not (any ?\]))) ?\]) (* space)
- assignment-operator)))
- (when (re-search-forward re limit t)
- (while (and (python-syntax-context 'paren)
- (re-search-forward re limit t)))
- (if (not (or (python-syntax-context 'paren)
- (equal (char-after (point-marker)) ?=)))
- t
- (set-match-data nil)))))
+ assignment-operator))
+ (res nil))
+ (while (and (setq res (re-search-forward re limit t))
+ (or (python-syntax-context 'paren)
+ (equal (char-after (point)) ?=))))
+ res))
(1 font-lock-variable-name-face nil nil))
;; support for a, b, c = (1, 2, 3)
(,(lambda (limit)
(let ((re (python-rx (group (+ (any word ?. ?_))) (* space)
(* ?, (* space) (+ (any word ?. ?_)) (* space))
?, (* space) (+ (any word ?. ?_)) (* space)
- assignment-operator)))
- (when (and (re-search-forward re limit t)
- (goto-char (nth 3 (match-data))))
- (while (and (python-syntax-context 'paren)
- (re-search-forward re limit t))
- (goto-char (nth 3 (match-data))))
- (if (not (python-syntax-context 'paren))
- t
- (set-match-data nil)))))
+ assignment-operator))
+ (res nil))
+ (while (and (setq res (re-search-forward re limit t))
+ (goto-char (match-end 1))
+ (python-syntax-context 'paren)))
+ res))
(1 font-lock-variable-name-face nil nil))))
(defconst python-syntax-propertize-function
@@ -531,10 +620,15 @@ The type returned can be `comment', `string' or `paren'."
((python-rx string-delimiter)
(0 (ignore (python-syntax-stringify))))))
+(defconst python--prettify-symbols-alist
+ '(("lambda" . ?λ)
+ ("and" . ?∧)
+ ("or" . ?∨)))
+
(defsubst python-syntax-count-quotes (quote-char &optional point limit)
"Count number of quotes around point (max is 3).
QUOTE-CHAR is the quote char to count. Optional argument POINT is
-the point where scan starts (defaults to current point) and LIMIT
+the point where scan starts (defaults to current point), and LIMIT
is used to limit the scan."
(let ((i 0))
(while (and (< i 3)
@@ -619,6 +713,12 @@ It makes underscores and dots word constituent chars.")
:group 'python
:safe 'booleanp)
+(defcustom python-indent-guess-indent-offset-verbose t
+ "Non-nil means to emit a warning when indentation guessing fails."
+ :type 'boolean
+ :group 'python
+ :safe' booleanp)
+
(defcustom python-indent-trigger-commands
'(indent-for-tab-command yas-expand yas/expand)
"Commands that might trigger a `python-indent-line' call."
@@ -632,29 +732,35 @@ 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'.")
-
-(defvar python-indent-dedenters '("else" "elif" "except" "finally")
- "List of words that should be dedented.
-These make `python-indent-calculate-indentation' subtract the value of
-`python-indent-offset'.")
-
-(defvar python-indent-block-enders
- '("break" "continue" "pass" "raise" "return")
- "List of words that mark the end of a block.
-These make `python-indent-calculate-indentation' subtract the
-value of `python-indent-offset' when `python-indent-context' is
-AFTER-LINE.")
+ "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."
(interactive)
(save-excursion
(save-restriction
- (widen)
+ (prog-widen)
(goto-char (point-min))
(let ((block-end))
(while (and (not block-end)
@@ -681,321 +787,365 @@ AFTER-LINE.")
(goto-char block-end)
(python-util-forward-comment)
(current-indentation))))
- (if indentation
+ (if (and indentation (not (zerop indentation)))
(set (make-local-variable 'python-indent-offset) indentation)
- (message "Can't guess python-indent-offset, using defaults: %s"
- python-indent-offset)))))))
+ (when python-indent-guess-indent-offset-verbose
+ (message "Can't guess python-indent-offset, using defaults: %s"
+ 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:
- * 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
- * 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)
- ;; 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)
- ;; After normal line
- ((setq start (save-excursion
+ (prog-widen)
+ (let ((ppss (save-excursion
+ (beginning-of-line)
+ (syntax-ppss))))
+ (cond
+ ;; Beginning of buffer.
+ ((= (line-number-at-pos) 1)
+ (cons :no-indent 0))
+ ;; Inside a string.
+ ((let ((start (python-syntax-context 'string ppss)))
+ (when start
+ (cons (if (python-info-docstring-p)
+ :inside-docstring
+ :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)
- (skip-chars-backward (rx (or whitespace ?\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)
- ;; 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
- (goto-char context-start)
- (current-indentation))
- (if (or (save-excursion
- (back-to-indentation)
- (looking-at (regexp-opt python-indent-dedenters)))
- (save-excursion
+ (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, comment or ender (default case).
+ ((save-excursion
+ (back-to-indentation)
+ (skip-chars-backward " \t\n")
+ (if (bobp)
+ (cons :no-indent 0)
+ (python-nav-beginning-of-statement)
+ (cons
+ (cond ((python-info-current-line-comment-p)
+ :after-comment)
+ ((save-excursion
+ (goto-char (line-end-position))
(python-util-forward-comment -1)
(python-nav-beginning-of-statement)
- (member (current-word) python-indent-block-enders)))
- 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
- (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'."
- (let* ((indentation (python-indent-calculate-indentation))
- (remainder (% indentation python-indent-offset))
- (steps (/ (- indentation remainder) python-indent-offset)))
- (setq python-indent-levels (list 0))
- (dotimes (step steps)
- (push (* python-indent-offset (1+ step)) python-indent-levels))
- (when (not (eq 0 remainder))
- (push (+ (* python-indent-offset steps) remainder) python-indent-levels))
- (setq python-indent-levels (nreverse python-indent-levels))
- (setq python-indent-current-level (1- (length python-indent-levels)))))
-
-(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)
+ (looking-at (python-rx block-ender)))
+ :after-block-end)
+ (t :after-line))
+ (point)))))))))
+
+(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 specific indentation points."
+ (save-restriction
+ (prog-widen)
+ (save-excursion
+ (pcase (python-indent-context)
+ (`(:no-indent . ,_) (prog-first-column)) ; usually 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))
+ (`(:inside-docstring . ,start)
+ (let* ((line-indentation (current-indentation))
+ (base-indent (progn
+ (goto-char start)
+ (current-indentation))))
+ (max line-indentation base-indent)))
+ (`(,(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)
+ (prog-first-column) ; 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) #'<)
+ (nconc (number-sequence (prog-first-column) (1- indentation)
+ python-indent-offset)
+ (list indentation))))
+
+(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))
+ (if levels
+ (apply #'max levels)
+ (prog-first-column)))))
+
+(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 maximum 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 it 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-closing-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))
+ (= (current-indentation) (current-column)))
+ (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."
+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)
- "Indent a python region automagically.
+ "Indent a Python region automagically.
Called from a program, START and END specify the region to indent."
(let ((deactivate-mark nil))
@@ -1006,24 +1156,35 @@ Called from a program, START and END specify the region to indent."
(or (bolp) (forward-line 1))
(while (< (point) end)
(or (and (bolp) (eolp))
- (let (word)
- (forward-line -1)
- (back-to-indentation)
- (setq word (current-word))
- (forward-line 1)
- (when (and word
- ;; Don't mess with strings, unless it's the
- ;; enclosing set of quotes.
- (or (not (python-syntax-context 'string))
- (eq
- (syntax-after
- (+ (1- (point))
- (current-indentation)
- (python-syntax-count-quotes (char-after) (point))))
- (string-to-syntax "|"))))
- (beginning-of-line)
- (delete-horizontal-space)
- (indent-to (python-indent-calculate-indentation)))))
+ (when (and
+ ;; Skip if previous line is empty or a comment.
+ (save-excursion
+ (let ((line-is-comment-p
+ (python-info-current-line-comment-p)))
+ (forward-line -1)
+ (not
+ (or (and (python-info-current-line-comment-p)
+ ;; Unless this line is a comment too.
+ (not line-is-comment-p))
+ (python-info-current-line-empty-p)))))
+ ;; Don't mess with strings, unless it's the
+ ;; enclosing set of quotes or a docstring.
+ (or (not (python-syntax-context 'string))
+ (eq
+ (syntax-after
+ (+ (1- (point))
+ (current-indentation)
+ (python-syntax-count-quotes (char-after) (point))))
+ (string-to-syntax "|"))
+ (python-info-docstring-p))
+ ;; Skip if current line is a block start, a
+ ;; dedenter or block ender.
+ (save-excursion
+ (back-to-indentation)
+ (not (looking-at
+ (python-rx
+ (or block-start dedenter block-ender))))))
+ (python-indent-line)))
(forward-line 1))
(move-marker end nil))))
@@ -1047,14 +1208,12 @@ any lines in the region are indented less than COUNT columns."
(while (< (point) end)
(if (and (< (current-indentation) count)
(not (looking-at "[ \t]*$")))
- (error "Can't shift all lines enough"))
+ (user-error "Can't shift all lines enough"))
(forward-line))
(indent-rigidly start end (- count))))))
-(add-to-list 'debug-ignored-errors "^Can't shift all lines enough")
-
(defun python-indent-shift-right (start end &optional count)
- "Shift lines contained in region START END by COUNT columns to the left.
+ "Shift lines contained in region START END by COUNT columns to the right.
COUNT defaults to `python-indent-offset'. If region isn't
active, the current line is shifted. The shifted region includes
the lines in which START and END lie."
@@ -1063,48 +1222,74 @@ the lines in which START and END lie."
(list (region-beginning) (region-end) current-prefix-arg)
(list (line-beginning-position) (line-end-position) current-prefix-arg)))
(let ((deactivate-mark nil))
- (if count
- (setq count (prefix-numeric-value count))
- (setq count python-indent-offset))
+ (setq count (if count (prefix-numeric-value count)
+ python-indent-offset))
(indent-rigidly start end count)))
-(defun python-indent-electric-colon (arg)
- "Insert a colon and maybe de-indent the current line.
-With numeric ARG, just insert that many colons. With
-\\[universal-argument], just insert a single colon."
- (interactive "*P")
- (self-insert-command (if (not (integerp arg)) 1 arg))
- (when (and (not arg)
- (eolp)
- (not (equal ?: (char-after (- (point-marker) 2))))
- (not (python-syntax-comment-or-string-p)))
- (let ((indentation (current-indentation))
- (calculated-indentation (python-indent-calculate-indentation)))
- (python-info-closing-block-message)
- (when (> indentation calculated-indentation)
- (save-excursion
- (indent-line-to calculated-indentation)
- (when (not (python-info-closing-block-message))
- (indent-line-to indentation)))))))
-(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-syntax-context 'paren)
+ "Adjust indentation after insertion of some characters.
+This function is intended to be added to `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 electric-indent-mode
+ (eq (char-before) last-command-event))
+ (cond
+ ;; Electric indent inside parens
+ ((and
+ (not (bolp))
+ (let ((paren-start (python-syntax-context 'paren)))
+ ;; Check that point is inside parens.
+ (when paren-start
+ (not
+ ;; Filter the case where input is happening in the same
+ ;; line where the open paren is.
+ (= (line-number-at-pos)
+ (line-number-at-pos paren-start)))))
+ ;; When content has been added before the closing paren or a
+ ;; comma has been inserted, it's ok to do the trick.
+ (or
+ (memq (char-after) '(?\) ?\] ?\}))
+ (eq (char-before) ?,)))
+ (save-excursion
+ (goto-char (line-beginning-position))
(let ((indentation (python-indent-calculate-indentation)))
- (when (< (current-indentation) indentation)
- (indent-line-to indentation)))))))
+ (when (and (numberp indentation) (< (current-indentation) indentation))
+ (indent-line-to indentation)))))
+ ;; Electric colon
+ ((and (eq ?: last-command-event)
+ (memq ?: electric-indent-chars)
+ (not current-prefix-arg)
+ ;; Trigger electric colon only at end of line
+ (eolp)
+ ;; Avoid re-indenting on extra colon
+ (not (equal ?: (char-before (1- (point)))))
+ (not (python-syntax-comment-or-string-p)))
+ ;; Just re-indent dedenters
+ (let ((dedenter-pos (python-info-dedenter-statement-p))
+ (current-pos (point)))
+ (when dedenter-pos
+ (save-excursion
+ (goto-char dedenter-pos)
+ (python-indent-line)
+ (unless (= (line-number-at-pos dedenter-pos)
+ (line-number-at-pos current-pos))
+ ;; Reindent region if this is a multiline statement
+ (python-indent-region dedenter-pos current-pos)))))))))
+
+
+;;; Mark
+
+(defun python-mark-defun (&optional allow-extend)
+ "Put mark at end of this defun, point at beginning.
+The defun marked is the one that contains point or follows point.
+
+Interactively (or with ALLOW-EXTEND non-nil), if this command is
+repeated or (in Transient Mark mode) if the mark is active, it
+marks the next defun after the ones already marked."
+ (interactive "p")
+ (when (python-info-looking-at-beginning-of-defun)
+ (end-of-line 1))
+ (mark-defun allow-extend))
;;; Navigation
@@ -1159,20 +1344,20 @@ With positive ARG search backwards, else search forwards."
(defun python-nav-beginning-of-defun (&optional arg)
"Move point to `beginning-of-defun'.
-With positive ARG search backwards else search forward. When ARG
-is nil or 0 defaults to 1. When searching backwards nested
-defuns are handled with care depending on current point
-position. Return non-nil if point is moved to
+With positive ARG search backwards else search forward.
+ARG nil or 0 defaults to 1. When searching backwards,
+nested defuns are handled with care depending on current
+point position. Return non-nil if point is moved to
`beginning-of-defun'."
(when (or (null arg) (= arg 0)) (setq arg 1))
(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)))))
+ (while (and (not (= arg 0))
+ (let ((keep-searching-p
+ (python-nav--beginning-of-defun arg)))
+ (when (and keep-searching-p (null found))
+ (setq found t))
+ keep-searching-p))
+ (setq arg (if (> arg 0) (1- arg) (1+ arg))))
found))
(defun python-nav-end-of-defun ()
@@ -1262,15 +1447,21 @@ nested definitions."
(defun python-nav-beginning-of-statement ()
"Move to start of current statement."
(interactive "^")
- (while (and (or (back-to-indentation) t)
- (not (bobp))
- (when (or
- (save-excursion
- (forward-line -1)
- (python-info-line-ends-backslash-p))
- (python-syntax-context 'string)
- (python-syntax-context 'paren))
- (forward-line -1))))
+ (back-to-indentation)
+ (let* ((ppss (syntax-ppss))
+ (context-point
+ (or
+ (python-syntax-context 'paren ppss)
+ (python-syntax-context 'string ppss))))
+ (cond ((bobp))
+ (context-point
+ (goto-char context-point)
+ (python-nav-beginning-of-statement))
+ ((save-excursion
+ (forward-line -1)
+ (python-info-line-ends-backslash-p))
+ (forward-line -1)
+ (python-nav-beginning-of-statement))))
(point-marker))
(defun python-nav-end-of-statement (&optional noend)
@@ -1332,9 +1523,7 @@ backward to previous statement."
(defun python-nav-beginning-of-block ()
"Move to start of current block."
(interactive "^")
- (let ((starting-pos (point))
- (block-regexp (python-rx
- line-start (* whitespace) block-start)))
+ (let ((starting-pos (point)))
(if (progn
(python-nav-beginning-of-statement)
(looking-at (python-rx block-start)))
@@ -1403,46 +1592,60 @@ backward to previous block."
(and (goto-char starting-pos) nil)
(and (not (= (point) starting-pos)) (point-marker)))))
-(defun python-nav-lisp-forward-sexp-safe (&optional arg)
- "Safe version of standard `forward-sexp'.
-When ARG > 0 move forward, else if ARG is < 0."
- (or arg (setq arg 1))
+(defun python-nav--lisp-forward-sexp (&optional arg)
+ "Standard version `forward-sexp'.
+It ignores completely the value of `forward-sexp-function' by
+setting it to nil before calling `forward-sexp'. With positive
+ARG move forward only one sexp, else move backwards."
(let ((forward-sexp-function)
- (paren-regexp
- (if (> arg 0) (python-rx close-paren) (python-rx open-paren)))
- (search-fn
- (if (> arg 0) #'re-search-forward #'re-search-backward)))
+ (arg (if (or (not arg) (> arg 0)) 1 -1)))
+ (forward-sexp arg)))
+
+(defun python-nav--lisp-forward-sexp-safe (&optional arg)
+ "Safe version of standard `forward-sexp'.
+When at end of sexp (i.e. looking at a opening/closing paren)
+skips it instead of throwing an error. With positive ARG move
+forward only one sexp, else move backwards."
+ (let* ((arg (if (or (not arg) (> arg 0)) 1 -1))
+ (paren-regexp
+ (if (> arg 0) (python-rx close-paren) (python-rx open-paren)))
+ (search-fn
+ (if (> arg 0) #'re-search-forward #'re-search-backward)))
(condition-case nil
- (forward-sexp arg)
+ (python-nav--lisp-forward-sexp arg)
(error
(while (and (funcall search-fn paren-regexp nil t)
(python-syntax-context 'paren)))))))
-(defun python-nav--forward-sexp (&optional dir)
+(defun python-nav--forward-sexp (&optional dir safe skip-parens-p)
"Move to forward sexp.
-With positive Optional argument DIR direction move forward, else
-backwards."
+With positive optional argument DIR direction move forward, else
+backwards. When optional argument SAFE is non-nil do not throw
+errors when at end of sexp, skip it instead. With optional
+argument SKIP-PARENS-P force sexp motion to ignore parenthesized
+expressions when looking at them in either direction."
(setq dir (or dir 1))
(unless (= dir 0)
(let* ((forward-p (if (> dir 0)
(and (setq dir 1) t)
(and (setq dir -1) nil)))
- (re-search-fn (if forward-p
- 're-search-forward
- 're-search-backward))
(context-type (python-syntax-context-type)))
(cond
((memq context-type '(string comment))
;; Inside of a string, get out of it.
(let ((forward-sexp-function))
(forward-sexp dir)))
- ((or (eq context-type 'paren)
- (and forward-p (looking-at (python-rx open-paren)))
- (and (not forward-p)
- (eq (syntax-class (syntax-after (1- (point))))
- (car (string-to-syntax ")")))))
+ ((and (not skip-parens-p)
+ (or (eq context-type 'paren)
+ (if forward-p
+ (eq (syntax-class (syntax-after (point)))
+ (car (string-to-syntax "(")))
+ (eq (syntax-class (syntax-after (1- (point))))
+ (car (string-to-syntax ")"))))))
;; Inside a paren or looking at it, lisp knows what to do.
- (python-nav-lisp-forward-sexp-safe dir))
+ (if safe
+ (python-nav--lisp-forward-sexp-safe dir)
+ (python-nav--lisp-forward-sexp dir)))
(t
;; This part handles the lispy feel of
;; `python-nav-forward-sexp'. Knowing everything about the
@@ -1456,7 +1659,9 @@ backwards."
((python-info-end-of-statement-p) 'statement-end)))
(next-sexp-pos
(save-excursion
- (python-nav-lisp-forward-sexp-safe dir)
+ (if safe
+ (python-nav--lisp-forward-sexp-safe dir)
+ (python-nav--lisp-forward-sexp dir))
(point)))
(next-sexp-context
(save-excursion
@@ -1472,7 +1677,7 @@ backwards."
(cond ((and (not (eobp))
(python-info-current-line-empty-p))
(python-util-forward-comment dir)
- (python-nav--forward-sexp dir))
+ (python-nav--forward-sexp dir safe skip-parens-p))
((eq context 'block-start)
(python-nav-end-of-block))
((eq context 'statement-start)
@@ -1492,7 +1697,7 @@ backwards."
(cond ((and (not (bobp))
(python-info-current-line-empty-p))
(python-util-forward-comment dir)
- (python-nav--forward-sexp dir))
+ (python-nav--forward-sexp dir safe skip-parens-p))
((eq context 'block-end)
(python-nav-beginning-of-block))
((eq context 'statement-end)
@@ -1510,23 +1715,70 @@ backwards."
(python-nav-beginning-of-statement))
(t (goto-char next-sexp-pos))))))))))
-(defun python-nav--backward-sexp ()
- "Move to backward sexp."
- (python-nav--forward-sexp -1))
-
-(defun python-nav-forward-sexp (&optional arg)
- "Move forward across one block of code.
-With ARG, do it that many times. Negative arg -N means
-move backward N times."
+(defun python-nav-forward-sexp (&optional arg safe skip-parens-p)
+ "Move forward across expressions.
+With ARG, do it that many times. Negative arg -N means move
+backward N times. When optional argument SAFE is non-nil do not
+throw errors when at end of sexp, skip it instead. With optional
+argument SKIP-PARENS-P force sexp motion to ignore parenthesized
+expressions when looking at them in either direction (forced to t
+in interactive calls)."
(interactive "^p")
(or arg (setq arg 1))
+ ;; Do not follow parens on interactive calls. This hack to detect
+ ;; if the function was called interactively copes with the way
+ ;; `forward-sexp' works by calling `forward-sexp-function', losing
+ ;; interactive detection by checking `current-prefix-arg'. The
+ ;; reason to make this distinction is that lisp functions like
+ ;; `blink-matching-open' get confused causing issues like the one in
+ ;; Bug#16191. With this approach the user gets a symmetric behavior
+ ;; when working interactively while called functions expecting
+ ;; paren-based sexp motion work just fine.
+ (or
+ skip-parens-p
+ (setq skip-parens-p
+ (memq real-this-command
+ (list
+ #'forward-sexp #'backward-sexp
+ #'python-nav-forward-sexp #'python-nav-backward-sexp
+ #'python-nav-forward-sexp-safe #'python-nav-backward-sexp))))
(while (> arg 0)
- (python-nav--forward-sexp)
+ (python-nav--forward-sexp 1 safe skip-parens-p)
(setq arg (1- arg)))
(while (< arg 0)
- (python-nav--backward-sexp)
+ (python-nav--forward-sexp -1 safe skip-parens-p)
(setq arg (1+ arg))))
+(defun python-nav-backward-sexp (&optional arg safe skip-parens-p)
+ "Move backward across expressions.
+With ARG, do it that many times. Negative arg -N means move
+forward N times. When optional argument SAFE is non-nil do not
+throw errors when at end of sexp, skip it instead. With optional
+argument SKIP-PARENS-P force sexp motion to ignore parenthesized
+expressions when looking at them in either direction (forced to t
+in interactive calls)."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (python-nav-forward-sexp (- arg) safe skip-parens-p))
+
+(defun python-nav-forward-sexp-safe (&optional arg skip-parens-p)
+ "Move forward safely across expressions.
+With ARG, do it that many times. Negative arg -N means move
+backward N times. With optional argument SKIP-PARENS-P force
+sexp motion to ignore parenthesized expressions when looking at
+them in either direction (forced to t in interactive calls)."
+ (interactive "^p")
+ (python-nav-forward-sexp arg t skip-parens-p))
+
+(defun python-nav-backward-sexp-safe (&optional arg skip-parens-p)
+ "Move backward safely across expressions.
+With ARG, do it that many times. Negative arg -N means move
+forward N times. With optional argument SKIP-PARENS-P force sexp
+motion to ignore parenthesized expressions when looking at them in
+either direction (forced to t in interactive calls)."
+ (interactive "^p")
+ (python-nav-backward-sexp arg t skip-parens-p))
+
(defun python-nav--up-list (&optional dir)
"Internal implementation of `python-nav-up-list'.
DIR is always 1 or -1 and comes sanitized from
@@ -1582,12 +1834,35 @@ This command assumes point is not in a string or comment."
(defun python-nav-backward-up-list (&optional arg)
"Move backward out of one level of parentheses (or blocks).
With ARG, do this that many times.
-A negative argument means move backward but still to a less deep spot.
+A negative argument means move forward but still to a less deep spot.
This command assumes point is not in a string or comment."
(interactive "^p")
(or arg (setq arg 1))
(python-nav-up-list (- arg)))
+(defun python-nav-if-name-main ()
+ "Move point at the beginning the __main__ block.
+When \"if __name__ == '__main__':\" is found returns its
+position, else returns nil."
+ (interactive)
+ (let ((point (point))
+ (found (catch 'found
+ (goto-char (point-min))
+ (while (re-search-forward
+ (python-rx line-start
+ "if" (+ space)
+ "__name__" (+ space)
+ "==" (+ space)
+ (group-n 1 (or ?\" ?\'))
+ "__main__" (backref 1) (* space) ":")
+ nil t)
+ (when (not (python-syntax-context-type))
+ (beginning-of-line)
+ (throw 'found t))))))
+ (if found
+ (point)
+ (ignore (goto-char point)))))
+
;;; Shell integration
@@ -1613,86 +1888,135 @@ This command assumes point is not in a string or comment."
:type 'string
:group 'python)
+(defcustom python-shell-interpreter-interactive-arg "-i"
+ "Interpreter argument to force it to run interactively."
+ :type 'string
+ :version "24.4")
+
+(defcustom python-shell-prompt-detect-enabled t
+ "Non-nil enables autodetection of interpreter prompts."
+ :type 'boolean
+ :safe 'booleanp
+ :version "24.4")
+
+(defcustom python-shell-prompt-detect-failure-warning t
+ "Non-nil enables warnings when detection of prompts fail."
+ :type 'boolean
+ :safe 'booleanp
+ :version "24.4")
+
+(defcustom python-shell-prompt-input-regexps
+ '(">>> " "\\.\\.\\. " ; Python
+ "In \\[[0-9]+\\]: " ; IPython
+ " \\.\\.\\.: " ; IPython
+ ;; Using ipdb outside IPython may fail to cleanup and leave static
+ ;; IPython prompts activated, this adds some safeguard for that.
+ "In : " "\\.\\.\\.: ")
+ "List of regular expressions matching input prompts."
+ :type '(repeat string)
+ :version "24.4")
+
+(defcustom python-shell-prompt-output-regexps
+ '("" ; Python
+ "Out\\[[0-9]+\\]: " ; IPython
+ "Out :") ; ipdb safeguard
+ "List of regular expressions matching output prompts."
+ :type '(repeat string)
+ :version "24.4")
+
(defcustom python-shell-prompt-regexp ">>> "
- "Regular Expression matching top\-level input prompt of python shell.
+ "Regular expression matching top level input prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string
- :group 'python
- :safe 'stringp)
+ :type 'string)
-(defcustom python-shell-prompt-block-regexp "[.][.][.] "
- "Regular Expression matching block input prompt of python shell.
+(defcustom python-shell-prompt-block-regexp "\\.\\.\\. "
+ "Regular expression matching block input prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string
- :group 'python
- :safe 'stringp)
+ :type 'string)
(defcustom python-shell-prompt-output-regexp ""
- "Regular Expression matching output prompt of python shell.
+ "Regular expression matching output prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string
- :group 'python
- :safe 'stringp)
+ :type 'string)
(defcustom python-shell-prompt-pdb-regexp "[(<]*[Ii]?[Pp]db[>)]+ "
- "Regular Expression matching pdb input prompt of python shell.
+ "Regular expression matching pdb input prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string
+ :type 'string)
+
+(define-obsolete-variable-alias
+ 'python-shell-enable-font-lock 'python-shell-font-lock-enable "25.1")
+
+(defcustom python-shell-font-lock-enable 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 'stringp)
+ :safe 'booleanp)
-(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."
+(defcustom python-shell-unbuffered t
+ "Should shell output be unbuffered?.
+When non-nil, this may prevent delayed and missing output in the
+Python shell. See commentary for details."
:type 'boolean
:group 'python
:safe 'booleanp)
(defcustom python-shell-process-environment nil
- "List of environment variables for Python shell.
-This variable follows the same rules as `process-environment'
-since it merges with it before the process creation routines are
-called. When this variable is nil, the Python shell is run with
-the default `process-environment'."
+ "List of overridden environment variables for subprocesses to inherit.
+Each element should be a string of the form ENVVARNAME=VALUE.
+When this variable is non-nil, values are exported into the
+process environment before starting it. Any variables already
+present in the current environment are superseded by variables
+set here."
:type '(repeat string)
- :group 'python
- :safe 'listp)
+ :group 'python)
(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."
+When this variable is non-nil, values added at the beginning of
+the PYTHONPATH before starting processes. Any values present
+here that already exists in PYTHONPATH are moved to the beginning
+of the list so that they are prioritized when looking for
+modules."
:type '(repeat string)
- :group 'python
- :safe 'listp)
+ :group 'python)
(defcustom python-shell-exec-path nil
- "List of path to search for binaries.
-This variable follows the same rules as `exec-path' since it
-merges with it before the process creation routines are called.
-When this variable is nil, the Python shell is run with the
-default `exec-path'."
+ "List of paths for searching executables.
+When this variable is non-nil, values added at the beginning of
+the PATH before starting processes. Any values present here that
+already exists in PATH are moved to the beginning of the list so
+that they are prioritized when looking for executables."
:type '(repeat string)
- :group 'python
- :safe 'listp)
+ :group 'python)
+
+(defcustom python-shell-remote-exec-path nil
+ "List of paths to be ensured remotely for searching executables.
+When this variable is non-nil, values are exported into remote
+hosts PATH before starting processes. Values defined in
+`python-shell-exec-path' will take precedence to paths defined
+here. Normally you wont use this variable directly unless you
+plan to ensure a particular set of paths to all Python shell
+executed through tramp connections."
+ :type '(repeat string)
+ :group 'python)
-(defcustom python-shell-virtualenv-path nil
+(defcustom python-shell-virtualenv-root nil
"Path to virtualenv root.
-This variable, when set to a string, makes the values stored in
-`python-shell-process-environment' and `python-shell-exec-path'
-to be modified properly so shells are started with the specified
+This variable, when set to a string, makes the environment to be
+modified such that shells are started within the specified
virtualenv."
:type '(choice (const nil) string)
- :group 'python
- :safe 'stringp)
+ :group 'python)
-(defcustom python-shell-setup-codes '(python-shell-completion-setup-code
- python-ffap-setup-code
- python-eldoc-setup-code)
+(define-obsolete-variable-alias
+ 'python-shell-virtualenv-path 'python-shell-virtualenv-root "25.1")
+
+(defcustom python-shell-setup-codes nil
"List of code run by `python-shell-send-setup-codes'."
:type '(repeat symbol)
- :group 'python
- :safe 'listp)
+ :group 'python)
(defcustom python-shell-compilation-regexp-alist
`((,(rx line-start (1+ (any " \t")) "File \""
@@ -1709,229 +2033,748 @@ virtualenv."
:type '(alist string)
:group 'python)
-(defun python-shell-get-process-name (dedicated)
- "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
-returns the value of `python-shell-buffer-name'."
- (let ((process-name
- (if (and dedicated
- buffer-file-name)
- (format "%s[%s]" python-shell-buffer-name buffer-file-name)
- (format "%s" python-shell-buffer-name))))
- process-name))
-
-(defun python-shell-internal-get-process-name ()
- "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."
- (format "%s [%s]"
- python-shell-internal-buffer-name
- (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 #'identity python-shell-process-environment "")
- (mapconcat #'identity python-shell-extra-pythonpaths "")
- (mapconcat #'identity python-shell-exec-path "")
- (or python-shell-virtualenv-path "")
- (mapconcat #'identity python-shell-exec-path "")))))
-
-(defun python-shell-parse-command ()
- "Calculate the string used to execute the inferior Python process."
- (let ((process-environment (python-shell-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (format "%s %s"
- (executable-find python-shell-interpreter)
- python-shell-interpreter-args)))
+(defmacro python-shell--add-to-path-with-priority (pathvar paths)
+ "Modify PATHVAR and ensure PATHS are added only once at beginning."
+ `(dolist (path (reverse ,paths))
+ (cl-delete path ,pathvar :test #'string=)
+ (cl-pushnew path ,pathvar :test #'string=)))
+
+(defun python-shell-calculate-pythonpath ()
+ "Calculate the PYTHONPATH using `python-shell-extra-pythonpaths'."
+ (let ((pythonpath
+ (tramp-compat-split-string
+ (or (getenv "PYTHONPATH") "") path-separator)))
+ (python-shell--add-to-path-with-priority
+ pythonpath python-shell-extra-pythonpaths)
+ (mapconcat 'identity pythonpath path-separator)))
(defun python-shell-calculate-process-environment ()
- "Calculate process environment given `python-shell-virtualenv-path'."
- (let ((process-environment (append
- python-shell-process-environment
- process-environment nil))
- (virtualenv (if python-shell-virtualenv-path
- (directory-file-name python-shell-virtualenv-path)
- nil)))
+ "Calculate `process-environment' or `tramp-remote-process-environment'.
+Prepends `python-shell-process-environment', sets extra
+pythonpaths from `python-shell-extra-pythonpaths' and sets a few
+virtualenv related vars. If `default-directory' points to a
+remote host, the returned value is intended for
+`tramp-remote-process-environment'."
+ (let* ((remote-p (file-remote-p default-directory))
+ (process-environment (if remote-p
+ tramp-remote-process-environment
+ process-environment))
+ (virtualenv (when python-shell-virtualenv-root
+ (directory-file-name python-shell-virtualenv-root))))
+ (dolist (env python-shell-process-environment)
+ (pcase-let ((`(,key ,value) (split-string env "=")))
+ (setenv key value)))
+ (when python-shell-unbuffered
+ (setenv "PYTHONUNBUFFERED" "1"))
(when python-shell-extra-pythonpaths
- (setenv "PYTHONPATH"
- (format "%s%s%s"
- (mapconcat 'identity
- python-shell-extra-pythonpaths
- path-separator)
- path-separator
- (or (getenv "PYTHONPATH") ""))))
+ (setenv "PYTHONPATH" (python-shell-calculate-pythonpath)))
(if (not virtualenv)
process-environment
(setenv "PYTHONHOME" nil)
- (setenv "PATH" (format "%s/bin%s%s"
- virtualenv path-separator
- (or (getenv "PATH") "")))
(setenv "VIRTUAL_ENV" virtualenv))
process-environment))
(defun python-shell-calculate-exec-path ()
- "Calculate exec path given `python-shell-virtualenv-path'."
- (let ((path (append python-shell-exec-path
- exec-path nil)))
- (if (not python-shell-virtualenv-path)
- path
- (cons (format "%s/bin"
- (directory-file-name python-shell-virtualenv-path))
- path))))
-
-(defun python-comint-output-filter-function (output)
- "Hook run after content is put into comint buffer.
-OUTPUT is a string with the contents of the buffer."
- (ansi-color-filter-apply output))
+ "Calculate `exec-path'.
+Prepends `python-shell-exec-path' and adds the binary directory
+for virtualenv if `python-shell-virtualenv-root' is set. If
+`default-directory' points to a remote host, the returned value
+appends `python-shell-remote-exec-path' instead of `exec-path'."
+ (let ((new-path (copy-sequence
+ (if (file-remote-p default-directory)
+ python-shell-remote-exec-path
+ exec-path))))
+ (python-shell--add-to-path-with-priority
+ new-path python-shell-exec-path)
+ (if (not python-shell-virtualenv-root)
+ new-path
+ (python-shell--add-to-path-with-priority
+ new-path
+ (list (expand-file-name "bin" python-shell-virtualenv-root)))
+ new-path)))
+
+(defun python-shell-tramp-refresh-remote-path (vec paths)
+ "Update VEC's remote-path giving PATHS priority."
+ (let ((remote-path (tramp-get-connection-property vec "remote-path" nil)))
+ (when remote-path
+ (python-shell--add-to-path-with-priority remote-path paths)
+ (tramp-set-connection-property vec "remote-path" remote-path)
+ (tramp-set-remote-path vec))))
+
+(defun python-shell-tramp-refresh-process-environment (vec env)
+ "Update VEC's process environment with ENV."
+ ;; Stolen from `tramp-open-connection-setup-interactive-shell'.
+ (let ((env (append (when (fboundp #'tramp-get-remote-locale)
+ ;; Emacs<24.4 compat.
+ (list (tramp-get-remote-locale vec)))
+ (copy-sequence env)))
+ (tramp-end-of-heredoc
+ (if (boundp 'tramp-end-of-heredoc)
+ tramp-end-of-heredoc
+ (md5 tramp-end-of-output)))
+ unset vars item)
+ (while env
+ (setq item (tramp-compat-split-string (car env) "="))
+ (setcdr item (mapconcat 'identity (cdr item) "="))
+ (if (and (stringp (cdr item)) (not (string-equal (cdr item) "")))
+ (push (format "%s %s" (car item) (cdr item)) vars)
+ (push (car item) unset))
+ (setq env (cdr env)))
+ (when vars
+ (tramp-send-command
+ vec
+ (format "while read var val; do export $var=$val; done <<'%s'\n%s\n%s"
+ tramp-end-of-heredoc
+ (mapconcat 'identity vars "\n")
+ tramp-end-of-heredoc)
+ t))
+ (when unset
+ (tramp-send-command
+ vec (format "unset %s" (mapconcat 'identity unset " ")) t))))
+
+(defmacro python-shell-with-environment (&rest body)
+ "Modify shell environment during execution of BODY.
+Temporarily sets `process-environment' and `exec-path' during
+execution of body. If `default-directory' points to a remote
+machine then modifies `tramp-remote-process-environment' and
+`python-shell-remote-exec-path' instead."
+ (declare (indent 0) (debug (body)))
+ (let ((vec (make-symbol "vec")))
+ `(progn
+ (let* ((,vec
+ (when (file-remote-p default-directory)
+ (ignore-errors
+ (tramp-dissect-file-name default-directory 'noexpand))))
+ (process-environment
+ (if ,vec
+ process-environment
+ (python-shell-calculate-process-environment)))
+ (exec-path
+ (if ,vec
+ exec-path
+ (python-shell-calculate-exec-path)))
+ (tramp-remote-process-environment
+ (if ,vec
+ (python-shell-calculate-process-environment)
+ tramp-remote-process-environment)))
+ (when (tramp-get-connection-process ,vec)
+ ;; For already existing connections, the new exec path must
+ ;; be re-set, otherwise it won't take effect. One example
+ ;; of such case is when remote dir-locals are read and
+ ;; *then* subprocesses are triggered within the same
+ ;; connection.
+ (python-shell-tramp-refresh-remote-path
+ ,vec (python-shell-calculate-exec-path))
+ ;; The `tramp-remote-process-environment' variable is only
+ ;; effective when the started process is an interactive
+ ;; shell, otherwise (like in the case of processes started
+ ;; with `process-file') the environment is not changed.
+ ;; This makes environment modifications effective
+ ;; unconditionally.
+ (python-shell-tramp-refresh-process-environment
+ ,vec tramp-remote-process-environment))
+ ,(macroexp-progn body)))))
+
+(defvar python-shell--prompt-calculated-input-regexp nil
+ "Calculated input prompt regexp for inferior python shell.
+Do not set this variable directly, instead use
+`python-shell-prompt-set-calculated-regexps'.")
+
+(defvar python-shell--prompt-calculated-output-regexp nil
+ "Calculated output prompt regexp for inferior python shell.
+Do not set this variable directly, instead use
+`python-shell-set-prompt-regexp'.")
+
+(defun python-shell-prompt-detect ()
+ "Detect prompts for the current `python-shell-interpreter'.
+When prompts can be retrieved successfully from the
+`python-shell-interpreter' run with
+`python-shell-interpreter-interactive-arg', returns a list of
+three elements, where the first two are input prompts and the
+last one is an output prompt. When no prompts can be detected
+and `python-shell-prompt-detect-failure-warning' is non-nil,
+shows a warning with instructions to avoid hangs and returns nil.
+When `python-shell-prompt-detect-enabled' is nil avoids any
+detection and just returns nil."
+ (when python-shell-prompt-detect-enabled
+ (python-shell-with-environment
+ (let* ((code (concat
+ "import sys\n"
+ "ps = [getattr(sys, 'ps%s' % i, '') for i in range(1,4)]\n"
+ ;; JSON is built manually for compatibility
+ "ps_json = '\\n[\"%s\", \"%s\", \"%s\"]\\n' % tuple(ps)\n"
+ "print (ps_json)\n"
+ "sys.exit(0)\n"))
+ (interpreter python-shell-interpreter)
+ (interpreter-arg python-shell-interpreter-interactive-arg)
+ (output
+ (with-temp-buffer
+ ;; TODO: improve error handling by using
+ ;; `condition-case' and displaying the error message to
+ ;; the user in the no-prompts warning.
+ (ignore-errors
+ (let ((code-file (python-shell--save-temp-file code)))
+ ;; Use `process-file' as it is remote-host friendly.
+ (process-file
+ interpreter
+ code-file
+ '(t nil)
+ nil
+ interpreter-arg)
+ ;; Try to cleanup
+ (delete-file code-file)))
+ (buffer-string)))
+ (prompts
+ (catch 'prompts
+ (dolist (line (split-string output "\n" t))
+ (let ((res
+ ;; Check if current line is a valid JSON array
+ (and (string= (substring line 0 2) "[\"")
+ (ignore-errors
+ ;; Return prompts as a list, not vector
+ (append (json-read-from-string line) nil)))))
+ ;; The list must contain 3 strings, where the first
+ ;; is the input prompt, the second is the block
+ ;; prompt and the last one is the output prompt. The
+ ;; input prompt is the only one that can't be empty.
+ (when (and (= (length res) 3)
+ (cl-every #'stringp res)
+ (not (string= (car res) "")))
+ (throw 'prompts res))))
+ nil)))
+ (when (and (not prompts)
+ python-shell-prompt-detect-failure-warning)
+ (lwarn
+ '(python python-shell-prompt-regexp)
+ :warning
+ (concat
+ "Python shell prompts cannot be detected.\n"
+ "If your emacs session hangs when starting python shells\n"
+ "recover with `keyboard-quit' and then try fixing the\n"
+ "interactive flag for your interpreter by adjusting the\n"
+ "`python-shell-interpreter-interactive-arg' or add regexps\n"
+ "matching shell prompts in the directory-local friendly vars:\n"
+ " + `python-shell-prompt-regexp'\n"
+ " + `python-shell-prompt-block-regexp'\n"
+ " + `python-shell-prompt-output-regexp'\n"
+ "Or alternatively in:\n"
+ " + `python-shell-prompt-input-regexps'\n"
+ " + `python-shell-prompt-output-regexps'")))
+ prompts))))
+
+(defun python-shell-prompt-validate-regexps ()
+ "Validate all user provided regexps for prompts.
+Signals `user-error' if any of these vars contain invalid
+regexps: `python-shell-prompt-regexp',
+`python-shell-prompt-block-regexp',
+`python-shell-prompt-pdb-regexp',
+`python-shell-prompt-output-regexp',
+`python-shell-prompt-input-regexps',
+`python-shell-prompt-output-regexps'."
+ (dolist (symbol (list 'python-shell-prompt-input-regexps
+ 'python-shell-prompt-output-regexps
+ 'python-shell-prompt-regexp
+ 'python-shell-prompt-block-regexp
+ 'python-shell-prompt-pdb-regexp
+ 'python-shell-prompt-output-regexp))
+ (dolist (regexp (let ((regexps (symbol-value symbol)))
+ (if (listp regexps)
+ regexps
+ (list regexps))))
+ (when (not (python-util-valid-regexp-p regexp))
+ (user-error "Invalid regexp %s in `%s'"
+ regexp symbol)))))
+
+(defun python-shell-prompt-set-calculated-regexps ()
+ "Detect and set input and output prompt regexps.
+Build and set the values for `python-shell-input-prompt-regexp'
+and `python-shell-output-prompt-regexp' using the values from
+`python-shell-prompt-regexp', `python-shell-prompt-block-regexp',
+`python-shell-prompt-pdb-regexp',
+`python-shell-prompt-output-regexp',
+`python-shell-prompt-input-regexps',
+`python-shell-prompt-output-regexps' and detected prompts from
+`python-shell-prompt-detect'."
+ (when (not (and python-shell--prompt-calculated-input-regexp
+ python-shell--prompt-calculated-output-regexp))
+ (let* ((detected-prompts (python-shell-prompt-detect))
+ (input-prompts nil)
+ (output-prompts nil)
+ (build-regexp
+ (lambda (prompts)
+ (concat "^\\("
+ (mapconcat #'identity
+ (sort prompts
+ (lambda (a b)
+ (let ((length-a (length a))
+ (length-b (length b)))
+ (if (= length-a length-b)
+ (string< a b)
+ (> (length a) (length b))))))
+ "\\|")
+ "\\)"))))
+ ;; Validate ALL regexps
+ (python-shell-prompt-validate-regexps)
+ ;; Collect all user defined input prompts
+ (dolist (prompt (append python-shell-prompt-input-regexps
+ (list python-shell-prompt-regexp
+ python-shell-prompt-block-regexp
+ python-shell-prompt-pdb-regexp)))
+ (cl-pushnew prompt input-prompts :test #'string=))
+ ;; Collect all user defined output prompts
+ (dolist (prompt (cons python-shell-prompt-output-regexp
+ python-shell-prompt-output-regexps))
+ (cl-pushnew prompt output-prompts :test #'string=))
+ ;; Collect detected prompts if any
+ (when detected-prompts
+ (dolist (prompt (butlast detected-prompts))
+ (setq prompt (regexp-quote prompt))
+ (cl-pushnew prompt input-prompts :test #'string=))
+ (cl-pushnew (regexp-quote
+ (car (last detected-prompts)))
+ output-prompts :test #'string=))
+ ;; Set input and output prompt regexps from collected prompts
+ (setq python-shell--prompt-calculated-input-regexp
+ (funcall build-regexp input-prompts)
+ python-shell--prompt-calculated-output-regexp
+ (funcall build-regexp output-prompts)))))
+
+(defun python-shell-get-process-name (dedicated)
+ "Calculate the appropriate process name for inferior Python process.
+If DEDICATED is t returns a string with the form
+`python-shell-buffer-name'[`buffer-name'] else returns the value
+of `python-shell-buffer-name'."
+ (if dedicated
+ (format "%s[%s]" python-shell-buffer-name (buffer-name))
+ python-shell-buffer-name))
+
+(defun python-shell-internal-get-process-name ()
+ "Calculate the appropriate process name for Internal Python process.
+The name is calculated from `python-shell-global-buffer-name' and
+the `buffer-name'."
+ (format "%s[%s]" python-shell-internal-buffer-name (buffer-name)))
+
+(defun python-shell-calculate-command ()
+ "Calculate the string used to execute the inferior Python process."
+ (format "%s %s"
+ (shell-quote-argument python-shell-interpreter)
+ python-shell-interpreter-args))
+
+(define-obsolete-function-alias
+ 'python-shell-parse-command
+ #'python-shell-calculate-command "25.1")
+
+(defvar python-shell--package-depth 10)
+
+(defun python-shell-package-enable (directory package)
+ "Add DIRECTORY parent to $PYTHONPATH and enable PACKAGE."
+ (interactive
+ (let* ((dir (expand-file-name
+ (read-directory-name
+ "Package root: "
+ (file-name-directory
+ (or (buffer-file-name) default-directory)))))
+ (name (completing-read
+ "Package: "
+ (python-util-list-packages
+ dir python-shell--package-depth))))
+ (list dir name)))
+ (python-shell-send-string
+ (format
+ (concat
+ "import os.path;import sys;"
+ "sys.path.append(os.path.dirname(os.path.dirname('''%s''')));"
+ "__package__ = '''%s''';"
+ "import %s")
+ directory package package)
+ (python-shell-get-process)))
+
+(defun python-shell-accept-process-output (process &optional timeout regexp)
+ "Accept PROCESS output with TIMEOUT until REGEXP is found.
+Optional argument TIMEOUT is the timeout argument to
+`accept-process-output' calls. Optional argument REGEXP
+overrides the regexp to match the end of output, defaults to
+`comint-prompt-regexp.'. Returns non-nil when output was
+properly captured.
+
+This utility is useful in situations where the output may be
+received in chunks, since `accept-process-output' gives no
+guarantees they will be grabbed in a single call. An example use
+case for this would be the CPython shell start-up, where the
+banner and the initial prompt are received separately."
+ (let ((regexp (or regexp comint-prompt-regexp)))
+ (catch 'found
+ (while t
+ (when (not (accept-process-output process timeout))
+ (throw 'found nil))
+ (when (looking-back
+ regexp (car (python-util-comint-last-prompt)))
+ (throw 'found t))))))
+
+(defun python-shell-comint-end-of-output-p (output)
+ "Return non-nil if OUTPUT is ends with input prompt."
+ (string-match
+ ;; XXX: It seems on OSX an extra carriage return is attached
+ ;; at the end of output, this handles that too.
+ (concat
+ "\r?\n?"
+ ;; Remove initial caret from calculated regexp
+ (replace-regexp-in-string
+ (rx string-start ?^) ""
+ python-shell--prompt-calculated-input-regexp)
+ (rx eos))
+ output))
+
+(define-obsolete-function-alias
+ 'python-comint-output-filter-function
+ 'ansi-color-filter-apply
+ "25.1")
+
+(defun python-comint-postoutput-scroll-to-bottom (output)
+ "Faster version of `comint-postoutput-scroll-to-bottom'.
+Avoids `recenter' calls until OUTPUT is completely sent."
+ (when (and (not (string= "" output))
+ (python-shell-comint-end-of-output-p
+ (ansi-color-filter-apply output)))
+ (comint-postoutput-scroll-to-bottom output))
+ output)
(defvar python-shell--parent-buffer nil)
-(defvar python-shell-output-syntax-table
- (let ((table (make-syntax-table python-dotty-syntax-table)))
- (modify-syntax-entry ?\' "." table)
- (modify-syntax-entry ?\" "." table)
- (modify-syntax-entry ?\( "." table)
- (modify-syntax-entry ?\[ "." table)
- (modify-syntax-entry ?\{ "." table)
- (modify-syntax-entry ?\) "." table)
- (modify-syntax-entry ?\] "." table)
- (modify-syntax-entry ?\} "." table)
- table)
- "Syntax table for shell output.
-It makes parens and quotes be treated as punctuation chars.")
+(defmacro python-shell-with-shell-buffer (&rest body)
+ "Execute the forms in BODY with the shell buffer temporarily current.
+Signals an error if no shell buffer is available for current buffer."
+ (declare (indent 0) (debug t))
+ (let ((shell-process (make-symbol "shell-process")))
+ `(let ((,shell-process (python-shell-get-process-or-error)))
+ (with-current-buffer (process-buffer ,shell-process)
+ ,@body))))
+
+(defvar python-shell--font-lock-buffer nil)
+
+(defun python-shell-font-lock-get-or-create-buffer ()
+ "Get or create a font-lock buffer for current inferior process."
+ (python-shell-with-shell-buffer
+ (if python-shell--font-lock-buffer
+ python-shell--font-lock-buffer
+ (let ((process-name
+ (process-name (get-buffer-process (current-buffer)))))
+ (generate-new-buffer
+ (format " *%s-font-lock*" process-name))))))
+
+(defun python-shell-font-lock-kill-buffer ()
+ "Kill the font-lock buffer safely."
+ (when (and python-shell--font-lock-buffer
+ (buffer-live-p python-shell--font-lock-buffer))
+ (kill-buffer python-shell--font-lock-buffer)
+ (when (derived-mode-p 'inferior-python-mode)
+ (setq python-shell--font-lock-buffer nil))))
+
+(defmacro python-shell-font-lock-with-font-lock-buffer (&rest body)
+ "Execute the forms in BODY in the font-lock buffer.
+The value returned is the value of the last form in BODY. See
+also `with-current-buffer'."
+ (declare (indent 0) (debug t))
+ `(python-shell-with-shell-buffer
+ (save-current-buffer
+ (when (not (and python-shell--font-lock-buffer
+ (get-buffer python-shell--font-lock-buffer)))
+ (setq python-shell--font-lock-buffer
+ (python-shell-font-lock-get-or-create-buffer)))
+ (set-buffer python-shell--font-lock-buffer)
+ (when (not font-lock-mode)
+ (font-lock-mode 1))
+ (set (make-local-variable 'delay-mode-hooks) t)
+ (let ((python-indent-guess-indent-offset nil))
+ (when (not (derived-mode-p 'python-mode))
+ (python-mode))
+ ,@body))))
+
+(defun python-shell-font-lock-cleanup-buffer ()
+ "Cleanup the font-lock buffer.
+Provided as a command because this might be handy if something
+goes wrong and syntax highlighting in the shell gets messed up."
+ (interactive)
+ (python-shell-with-shell-buffer
+ (python-shell-font-lock-with-font-lock-buffer
+ (erase-buffer))))
+
+(defun python-shell-font-lock-comint-output-filter-function (output)
+ "Clean up the font-lock buffer after any OUTPUT."
+ (if (and (not (string= "" output))
+ ;; Is end of output and is not just a prompt.
+ (not (member
+ (python-shell-comint-end-of-output-p
+ (ansi-color-filter-apply output))
+ '(nil 0))))
+ ;; If output is other than an input prompt then "real" output has
+ ;; been received and the font-lock buffer must be cleaned up.
+ (python-shell-font-lock-cleanup-buffer)
+ ;; Otherwise just add a newline.
+ (python-shell-font-lock-with-font-lock-buffer
+ (goto-char (point-max))
+ (newline)))
+ output)
+
+(defun python-shell-font-lock-post-command-hook ()
+ "Fontifies current line in shell buffer."
+ (let ((prompt-end (cdr (python-util-comint-last-prompt))))
+ (when (and prompt-end (> (point) prompt-end)
+ (process-live-p (get-buffer-process (current-buffer))))
+ (let* ((input (buffer-substring-no-properties
+ prompt-end (point-max)))
+ (deactivate-mark nil)
+ (start-pos prompt-end)
+ (buffer-undo-list t)
+ (font-lock-buffer-pos nil)
+ (replacement
+ (python-shell-font-lock-with-font-lock-buffer
+ (delete-region (line-beginning-position)
+ (point-max))
+ (setq font-lock-buffer-pos (point))
+ (insert input)
+ ;; Ensure buffer is fontified, keeping it
+ ;; compatible with Emacs < 24.4.
+ (if (fboundp 'font-lock-ensure)
+ (funcall 'font-lock-ensure)
+ (font-lock-default-fontify-buffer))
+ (buffer-substring font-lock-buffer-pos
+ (point-max))))
+ (replacement-length (length replacement))
+ (i 0))
+ ;; Inject text properties to get input fontified.
+ (while (not (= i replacement-length))
+ (let* ((plist (text-properties-at i replacement))
+ (next-change (or (next-property-change i replacement)
+ replacement-length))
+ (plist (let ((face (plist-get plist 'face)))
+ (if (not face)
+ plist
+ ;; Replace FACE text properties with
+ ;; FONT-LOCK-FACE so input is fontified.
+ (plist-put plist 'face nil)
+ (plist-put plist 'font-lock-face face)))))
+ (set-text-properties
+ (+ start-pos i) (+ start-pos next-change) plist)
+ (setq i next-change)))))))
+
+(defun python-shell-font-lock-turn-on (&optional msg)
+ "Turn on shell font-lock.
+With argument MSG show activation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (python-shell-font-lock-kill-buffer)
+ (set (make-local-variable 'python-shell--font-lock-buffer) nil)
+ (add-hook 'post-command-hook
+ #'python-shell-font-lock-post-command-hook nil 'local)
+ (add-hook 'kill-buffer-hook
+ #'python-shell-font-lock-kill-buffer nil 'local)
+ (add-hook 'comint-output-filter-functions
+ #'python-shell-font-lock-comint-output-filter-function
+ 'append 'local)
+ (when msg
+ (message "Shell font-lock is enabled"))))
+
+(defun python-shell-font-lock-turn-off (&optional msg)
+ "Turn off shell font-lock.
+With argument MSG show deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (python-shell-font-lock-kill-buffer)
+ (when (python-util-comint-last-prompt)
+ ;; Cleanup current fontification
+ (remove-text-properties
+ (cdr (python-util-comint-last-prompt))
+ (line-end-position)
+ '(face nil font-lock-face nil)))
+ (set (make-local-variable 'python-shell--font-lock-buffer) nil)
+ (remove-hook 'post-command-hook
+ #'python-shell-font-lock-post-command-hook 'local)
+ (remove-hook 'kill-buffer-hook
+ #'python-shell-font-lock-kill-buffer 'local)
+ (remove-hook 'comint-output-filter-functions
+ #'python-shell-font-lock-comint-output-filter-function
+ 'local)
+ (when msg
+ (message "Shell font-lock is disabled"))))
+
+(defun python-shell-font-lock-toggle (&optional msg)
+ "Toggle font-lock for shell.
+With argument MSG show activation/deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (set (make-local-variable 'python-shell-font-lock-enable)
+ (not python-shell-font-lock-enable))
+ (if python-shell-font-lock-enable
+ (python-shell-font-lock-turn-on msg)
+ (python-shell-font-lock-turn-off msg))
+ python-shell-font-lock-enable))
+
+(defvar python-shell--first-prompt-received-output-buffer nil)
+(defvar python-shell--first-prompt-received nil)
+
+(defcustom python-shell-first-prompt-hook nil
+ "Hook run upon first (non-pdb) shell prompt detection.
+This is the place for shell setup functions that need to wait for
+output. Since the first prompt is ensured, this helps the
+current process to not hang waiting for output by safeguarding
+interactive actions can be performed. This is useful to safely
+attach setup code for long-running processes that eventually
+provide a shell."
+ :type 'hook
+ :group 'python)
+
+(defun python-shell-comint-watch-for-first-prompt-output-filter (output)
+ "Run `python-shell-first-prompt-hook' when first prompt is found in OUTPUT."
+ (when (not python-shell--first-prompt-received)
+ (set (make-local-variable 'python-shell--first-prompt-received-output-buffer)
+ (concat python-shell--first-prompt-received-output-buffer
+ (ansi-color-filter-apply output)))
+ (when (python-shell-comint-end-of-output-p
+ python-shell--first-prompt-received-output-buffer)
+ (if (string-match-p
+ (concat python-shell-prompt-pdb-regexp (rx eos))
+ (or python-shell--first-prompt-received-output-buffer ""))
+ ;; Skip pdb prompts and reset the buffer.
+ (setq python-shell--first-prompt-received-output-buffer nil)
+ (set (make-local-variable 'python-shell--first-prompt-received) t)
+ (setq python-shell--first-prompt-received-output-buffer nil)
+ (with-current-buffer (current-buffer)
+ (let ((inhibit-quit nil))
+ (run-hooks 'python-shell-first-prompt-hook))))))
+ output)
+
+;; Used to hold user interactive overrides to
+;; `python-shell-interpreter' and `python-shell-interpreter-args' that
+;; will be made buffer-local by `inferior-python-mode':
+(defvar python-shell--interpreter)
+(defvar python-shell--interpreter-args)
(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
-I/O through an Emacs buffer. Variables
-`python-shell-interpreter' and `python-shell-interpreter-args'
-controls which Python interpreter is run. Variables
+I/O through an Emacs buffer. Variables `python-shell-interpreter'
+and `python-shell-interpreter-args' control which Python
+interpreter is run. Variables
`python-shell-prompt-regexp',
`python-shell-prompt-output-regexp',
`python-shell-prompt-block-regexp',
-`python-shell-enable-font-lock',
+`python-shell-font-lock-enable',
`python-shell-completion-setup-code',
`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.
+This mode resets `comint-output-filter-functions' locally, so you
+may want to re-add custom functions to it using the
+`inferior-python-mode-hook'.
+
You can also add additional setup code to be run at
initialization of the interpreter via `python-shell-setup-codes'
variable.
\(Type \\[describe-mode] in the process buffer for a list of commands.)"
- (and python-shell--parent-buffer
- (python-util-clone-local-variables python-shell--parent-buffer))
- (setq comint-prompt-regexp (format "^\\(?:%s\\|%s\\|%s\\)"
- python-shell-prompt-regexp
- python-shell-prompt-block-regexp
- python-shell-prompt-pdb-regexp))
+ (when python-shell--parent-buffer
+ (python-util-clone-local-variables python-shell--parent-buffer))
+ ;; Users can interactively override default values for
+ ;; `python-shell-interpreter' and `python-shell-interpreter-args'
+ ;; when calling `run-python'. This ensures values let-bound in
+ ;; `python-shell-make-comint' are locally set if needed.
+ (set (make-local-variable 'python-shell-interpreter)
+ (or python-shell--interpreter python-shell-interpreter))
+ (set (make-local-variable 'python-shell-interpreter-args)
+ (or python-shell--interpreter-args python-shell-interpreter-args))
+ (set (make-local-variable 'python-shell--prompt-calculated-input-regexp) nil)
+ (set (make-local-variable 'python-shell--prompt-calculated-output-regexp) nil)
+ (python-shell-prompt-set-calculated-regexps)
+ (setq comint-prompt-regexp python-shell--prompt-calculated-input-regexp)
+ (set (make-local-variable 'comint-prompt-read-only) t)
(setq mode-line-process '(":%s"))
- (make-local-variable 'comint-output-filter-functions)
- (add-hook 'comint-output-filter-functions
- 'python-comint-output-filter-function)
- (add-hook 'comint-output-filter-functions
- 'python-pdbtrack-comint-output-filter-function)
+ (set (make-local-variable 'comint-output-filter-functions)
+ '(ansi-color-process-output
+ python-shell-comint-watch-for-first-prompt-output-filter
+ python-pdbtrack-comint-output-filter-function
+ python-comint-postoutput-scroll-to-bottom))
(set (make-local-variable 'compilation-error-regexp-alist)
python-shell-compilation-regexp-alist)
- (define-key inferior-python-mode-map [remap complete-symbol]
- 'completion-at-point)
(add-hook 'completion-at-point-functions
- 'python-shell-completion-complete-at-point nil 'local)
- (add-to-list (make-local-variable 'comint-dynamic-complete-functions)
- 'python-shell-completion-complete-at-point)
+ #'python-shell-completion-at-point nil 'local)
(define-key inferior-python-mode-map "\t"
'python-shell-completion-complete-or-indent)
(make-local-variable 'python-pdbtrack-buffers-to-kill)
(make-local-variable 'python-pdbtrack-tracked-buffer)
(make-local-variable 'python-shell-internal-last-output)
- (when python-shell-enable-font-lock
- (set-syntax-table python-mode-syntax-table)
- (set (make-local-variable 'font-lock-defaults)
- '(python-font-lock-keywords nil nil nil nil))
- (set (make-local-variable 'syntax-propertize-function)
- (eval
- ;; XXX: Unfortunately eval is needed here to make use of the
- ;; dynamic value of `comint-prompt-regexp'.
- `(syntax-propertize-rules
- (,comint-prompt-regexp
- (0 (ignore
- (put-text-property
- comint-last-input-start end 'syntax-table
- python-shell-output-syntax-table)
- ;; XXX: This might look weird, but it is the easiest
- ;; way to ensure font lock gets cleaned up before the
- ;; current prompt, which is needed for unclosed
- ;; strings to not mess up with current input.
- (font-lock-unfontify-region comint-last-input-start end))))
- (,(python-rx string-delimiter)
- (0 (ignore
- (and (not (eq (get-text-property start 'field) 'output))
- (python-syntax-stringify)))))))))
+ (when python-shell-font-lock-enable
+ (python-shell-font-lock-turn-on))
(compilation-shell-minor-mode 1))
-(defun python-shell-make-comint (cmd proc-name &optional pop internal)
- "Create a python shell comint buffer.
-CMD is the python command to be executed and PROC-NAME is the
+(defun python-shell-make-comint (cmd proc-name &optional show internal)
+ "Create a Python shell comint buffer.
+CMD is the Python command to be executed and PROC-NAME is the
process name the comint buffer will get. After the comint buffer
is created the `inferior-python-mode' is activated. When
-optional argument POP is non-nil the buffer is shown. When
+optional argument SHOW is non-nil the buffer is shown. When
optional argument INTERNAL is non-nil this process is run on a
buffer with a name that starts with a space, following the Emacs
convention for temporary/internal buffers, and also makes sure
the user is not queried for confirmation when the process is
killed."
(save-excursion
- (let* ((proc-buffer-name
- (format (if (not internal) "*%s*" " *%s*") proc-name))
- (process-environment (python-shell-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
- (when (not (comint-check-proc proc-buffer-name))
- (let* ((cmdlist (split-string-and-unquote cmd))
- (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name
- (car cmdlist) nil (cdr cmdlist)))
- (python-shell--parent-buffer (current-buffer))
- (process (get-buffer-process buffer)))
- (with-current-buffer buffer
- (inferior-python-mode))
- (accept-process-output process)
- (and pop (pop-to-buffer buffer t))
- (and internal (set-process-query-on-exit-flag process nil))))
- proc-buffer-name)))
+ (python-shell-with-environment
+ (let* ((proc-buffer-name
+ (format (if (not internal) "*%s*" " *%s*") proc-name)))
+ (when (not (comint-check-proc proc-buffer-name))
+ (let* ((cmdlist (split-string-and-unquote cmd))
+ (interpreter (car cmdlist))
+ (args (cdr cmdlist))
+ (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name
+ interpreter nil args))
+ (python-shell--parent-buffer (current-buffer))
+ (process (get-buffer-process buffer))
+ ;; Users can override the interpreter and args
+ ;; interactively when calling `run-python', let-binding
+ ;; these allows to have the new right values in all
+ ;; setup code that is done in `inferior-python-mode',
+ ;; which is important, especially for prompt detection.
+ (python-shell--interpreter interpreter)
+ (python-shell--interpreter-args
+ (mapconcat #'identity args " ")))
+ (with-current-buffer buffer
+ (inferior-python-mode))
+ (when show (display-buffer buffer))
+ (and internal (set-process-query-on-exit-flag process nil))))
+ proc-buffer-name))))
;;;###autoload
-(defun run-python (cmd &optional dedicated show)
+(defun run-python (&optional cmd dedicated show)
"Run an inferior Python process.
-Input and output via buffer named after
-`python-shell-buffer-name'. If there is a process already
-running in that buffer, just switch to it.
-With argument, allows you to define CMD so you can edit the
-command used to call the interpreter and define DEDICATED, so a
-dedicated process for the current buffer is open. When numeric
-prefix arg is other than 0 or 4 do not SHOW.
+Argument CMD defaults to `python-shell-calculate-command' return
+value. When called interactively with `prefix-arg', it allows
+the user to edit such value and choose whether the interpreter
+should be DEDICATED for the current buffer. When numeric prefix
+arg is other than 0 or 4 do not SHOW.
-Runs the hook `inferior-python-mode-hook' (after the
-`comint-mode-hook' is run). \(Type \\[describe-mode] in the
+For a given buffer and same values of DEDICATED, if a process is
+already running for it, it will do nothing. This means that if
+the current buffer is using a global process, the user is still
+able to switch it to use a dedicated one.
+
+Runs the hook `inferior-python-mode-hook' after
+`comint-mode-hook' is run. (Type \\[describe-mode] in the
process buffer for a list of commands.)"
(interactive
(if current-prefix-arg
(list
- (read-string "Run Python: " (python-shell-parse-command))
+ (read-shell-command "Run Python: " (python-shell-calculate-command))
(y-or-n-p "Make dedicated process? ")
(= (prefix-numeric-value current-prefix-arg) 4))
- (list (python-shell-parse-command) nil t)))
- (python-shell-make-comint
- cmd (python-shell-get-process-name dedicated) show)
- dedicated)
+ (list (python-shell-calculate-command) nil t)))
+ (get-buffer-process
+ (python-shell-make-comint
+ (or cmd (python-shell-calculate-command))
+ (python-shell-get-process-name dedicated) show)))
(defun run-python-internal ()
"Run an inferior Internal Python process.
@@ -1940,50 +2783,75 @@ Input and output via buffer named after
`python-shell-internal-get-process-name' returns.
This new kind of shell is intended to be used for generic
-communication related to defined configurations, the main
+communication related to defined configurations; the main
difference with global or dedicated shells is that these ones are
attached to a configuration, not a buffer. This means that can
be used for example to retrieve the sys.path and other stuff,
without messing with user shells. Note that
-`python-shell-enable-font-lock' and `inferior-python-mode-hook'
+`python-shell-font-lock-enable' and `inferior-python-mode-hook'
are set to nil for these shells, so setup codes are not sent at
startup."
- (let ((python-shell-enable-font-lock nil)
+ (let ((python-shell-font-lock-enable nil)
(inferior-python-mode-hook nil))
(get-buffer-process
(python-shell-make-comint
- (python-shell-parse-command)
+ (python-shell-calculate-command)
(python-shell-internal-get-process-name) nil t))))
+(defun python-shell-get-buffer ()
+ "Return inferior Python buffer for current buffer.
+If current buffer is in `inferior-python-mode', return it."
+ (if (derived-mode-p 'inferior-python-mode)
+ (current-buffer)
+ (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))
+ (dedicated-running (comint-check-proc dedicated-proc-buffer-name))
+ (global-running (comint-check-proc global-proc-buffer-name)))
+ ;; Always prefer dedicated
+ (or (and dedicated-running dedicated-proc-buffer-name)
+ (and global-running global-proc-buffer-name)))))
+
(defun python-shell-get-process ()
- "Get inferior Python process for current buffer and return it."
- (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))
- (dedicated-running (comint-check-proc dedicated-proc-buffer-name))
- (global-running (comint-check-proc global-proc-buffer-name)))
- ;; Always prefer dedicated
- (get-buffer-process (or (and dedicated-running dedicated-proc-buffer-name)
- (and global-running global-proc-buffer-name)))))
-
-(defun python-shell-get-or-create-process ()
- "Get or create an inferior Python process for current buffer and return it."
- (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))
- (dedicated-running (comint-check-proc dedicated-proc-buffer-name))
- (global-running (comint-check-proc global-proc-buffer-name))
- (current-prefix-arg 16))
- (when (and (not dedicated-running) (not global-running))
- (if (call-interactively 'run-python)
- (setq dedicated-running t)
- (setq global-running t)))
- ;; Always prefer dedicated
- (get-buffer-process (if dedicated-running
- dedicated-proc-buffer-name
- global-proc-buffer-name))))
+ "Return inferior Python process for current buffer."
+ (get-buffer-process (python-shell-get-buffer)))
+
+(defun python-shell-get-process-or-error (&optional interactivep)
+ "Return inferior Python process for current buffer or signal error.
+When argument INTERACTIVEP is non-nil, use `user-error' instead
+of `error' with a user-friendly message."
+ (or (python-shell-get-process)
+ (if interactivep
+ (user-error
+ "Start a Python process first with `%s' or `%s'."
+ (substitute-command-keys "\\[run-python]")
+ ;; Get the binding.
+ (key-description
+ (where-is-internal
+ #'run-python overriding-local-map t)))
+ (error
+ "No inferior Python process running."))))
+
+(defun python-shell-get-or-create-process (&optional cmd dedicated show)
+ "Get or create an inferior Python process for current buffer and return it.
+Arguments CMD, DEDICATED and SHOW are those of `run-python' and
+are used to start the shell. If those arguments are not
+provided, `run-python' is called interactively and the user will
+be asked for their values."
+ (let ((shell-process (python-shell-get-process)))
+ (when (not shell-process)
+ (if (not cmd)
+ ;; XXX: Refactor code such that calling `run-python'
+ ;; interactively is not needed anymore.
+ (call-interactively 'run-python)
+ (run-python cmd dedicated show)))
+ (or shell-process (python-shell-get-process))))
+
+(make-obsolete
+ #'python-shell-get-or-create-process
+ "Instead call `python-shell-get-process' and create one if returns nil."
+ "25.1")
(defvar python-shell-internal-buffer nil
"Current internal shell buffer for the current buffer.
@@ -1997,18 +2865,10 @@ there for compatibility with CEDET.")
(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)))
- (when (not (process-live-p proc-name))
- (run-python-internal)
- (setq python-shell-internal-buffer proc-buffer-name)
- ;; XXX: Why is this `sit-for' needed?
- ;; `python-shell-make-comint' calls `accept-process-output'
- ;; already but it is not helping to get proper output on
- ;; 'gnu/linux when the internal shell process is not running and
- ;; a call to `python-shell-internal-send-string' is issued.
- (sit-for 0.1 t))
- (get-buffer-process proc-buffer-name)))
+ (let ((proc-name (python-shell-internal-get-process-name)))
+ (if (process-live-p proc-name)
+ (get-process proc-name)
+ (run-python-internal))))
(define-obsolete-function-alias
'python-proc 'python-shell-internal-get-or-create-process "24.3")
@@ -2019,27 +2879,33 @@ there for compatibility with CEDET.")
(define-obsolete-variable-alias
'python-preoutput-result 'python-shell-internal-last-output "24.3")
+(defun python-shell--save-temp-file (string)
+ (let* ((temporary-file-directory
+ (if (file-remote-p default-directory)
+ (concat (file-remote-p default-directory) "/tmp")
+ temporary-file-directory))
+ (temp-file-name (make-temp-file "py"))
+ (coding-system-for-write (python-info-encoding)))
+ (with-temp-file temp-file-name
+ (insert string)
+ (delete-trailing-whitespace))
+ temp-file-name))
+
(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."
- (interactive "sPython command: ")
- (let ((process (or process (python-shell-get-or-create-process)))
- (lines (split-string string "\n" t)))
- (and msg (message "Sent: %s..." (nth 0 lines)))
- (if (> (length lines) 1)
- (let* ((temporary-file-directory
- (if (file-remote-p default-directory)
- (concat (file-remote-p default-directory) "/tmp")
- temporary-file-directory))
- (temp-file-name (make-temp-file "py"))
+When optional argument MSG is non-nil, forces display of a
+user-friendly message if there's no process running; defaults to
+t when called interactively."
+ (interactive
+ (list (read-string "Python command: ") nil t))
+ (let ((process (or process (python-shell-get-process-or-error msg))))
+ (if (string-match ".\n+." string) ;Multiline.
+ (let* ((temp-file-name (python-shell--save-temp-file string))
(file-name (or (buffer-file-name) temp-file-name)))
- (with-temp-file temp-file-name
- (insert string)
- (delete-trailing-whitespace))
- (python-shell-send-file file-name process temp-file-name))
+ (python-shell-send-file file-name process temp-file-name t))
(comint-send-string process string)
- (when (or (not (string-match "\n$" string))
- (string-match "\n[ \t].*\n?$" string))
+ (when (or (not (string-match "\n\\'" string))
+ (string-match "\n[ \t].*\n?\\'" string))
(comint-send-string process "\n")))))
(defvar python-shell-output-filter-in-progress nil)
@@ -2055,13 +2921,7 @@ detecting a prompt at the end of the buffer."
string (ansi-color-filter-apply string)
python-shell-output-filter-buffer
(concat python-shell-output-filter-buffer string))
- (when (string-match
- ;; XXX: It seems on OSX an extra carriage return is attached
- ;; at the end of output, this handles that too.
- (format "\r?\n\\(?:%s\\|%s\\|%s\\)$"
- python-shell-prompt-regexp
- python-shell-prompt-block-regexp
- python-shell-prompt-pdb-regexp)
+ (when (python-shell-comint-end-of-output-p
python-shell-output-filter-buffer)
;; Output ends when `python-shell-output-filter-buffer' contains
;; the prompt attached at the end of it.
@@ -2069,27 +2929,26 @@ detecting a prompt at the end of the buffer."
python-shell-output-filter-buffer
(substring python-shell-output-filter-buffer
0 (match-beginning 0)))
- (when (and (> (length python-shell-prompt-output-regexp) 0)
- (string-match (concat "^" python-shell-prompt-output-regexp)
- python-shell-output-filter-buffer))
- ;; Some shells, like iPython might append a prompt before the
+ (when (string-match
+ python-shell--prompt-calculated-output-regexp
+ python-shell-output-filter-buffer)
+ ;; Some shells, like IPython might append a prompt before the
;; output, clean that.
(setq python-shell-output-filter-buffer
(substring python-shell-output-filter-buffer (match-end 0)))))
"")
-(defun python-shell-send-string-no-output (string &optional process msg)
+(defun python-shell-send-string-no-output (string &optional process)
"Send STRING to PROCESS and inhibit output.
-When MSG is non-nil messages the first line of STRING. Return
-the output."
- (let ((process (or process (python-shell-get-or-create-process)))
+Return the output."
+ (let ((process (or process (python-shell-get-process-or-error)))
(comint-preoutput-filter-functions
'(python-shell-output-filter))
(python-shell-output-filter-in-progress t)
(inhibit-quit t))
(or
(with-local-quit
- (python-shell-send-string string process msg)
+ (python-shell-send-string string process)
(while python-shell-output-filter-in-progress
;; `python-shell-output-filter' takes care of setting
;; `python-shell-output-filter-in-progress' to NIL after it
@@ -2111,7 +2970,7 @@ Returns the output. See `python-shell-send-string-no-output'."
;; Makes this function compatible with the old
;; python-send-receive. (At least for CEDET).
(replace-regexp-in-string "_emacs_out +" "" string)
- (python-shell-internal-get-or-create-process) nil)))
+ (python-shell-internal-get-or-create-process))))
(define-obsolete-function-alias
'python-send-receive 'python-shell-internal-send-string "24.3")
@@ -2119,37 +2978,113 @@ Returns the output. See `python-shell-send-string-no-output'."
(define-obsolete-function-alias
'python-send-string 'python-shell-internal-send-string "24.3")
-(defun python-shell-send-region (start end)
- "Send the region delimited by START and END to inferior Python process."
- (interactive "r")
- (python-shell-send-string
- (concat
- (let ((line-num (line-number-at-pos start)))
- ;; When sending a region, add blank lines for non sent code so
- ;; backtraces remain correct.
- (make-string (1- line-num) ?\n))
- (buffer-substring start end))
- nil t))
-
-(defun python-shell-send-buffer (&optional arg)
+(defun python-shell-buffer-substring (start end &optional nomain)
+ "Send buffer substring from START to END formatted for shell.
+This is a wrapper over `buffer-substring' that takes care of
+different transformations for the code sent to be evaluated in
+the python shell:
+ 1. When optional argument NOMAIN is non-nil everything under an
+ \"if __name__ == \\='__main__\\='\" block will be removed.
+ 2. When a subregion of the buffer is sent, it takes care of
+ appending extra empty lines so tracebacks are correct.
+ 3. When the region sent is a substring of the current buffer, a
+ coding cookie is added.
+ 4. Wraps indented regions under an \"if True:\" block so the
+ interpreter evaluates them correctly."
+ (let* ((start (save-excursion
+ ;; Normalize start to the line beginning position.
+ (goto-char start)
+ (line-beginning-position)))
+ (substring (buffer-substring-no-properties start end))
+ (starts-at-point-min-p (save-restriction
+ (widen)
+ (= (point-min) start)))
+ (encoding (python-info-encoding))
+ (toplevel-p (zerop (save-excursion
+ (goto-char start)
+ (python-util-forward-comment 1)
+ (current-indentation))))
+ (fillstr (when (not starts-at-point-min-p)
+ (concat
+ (format "# -*- coding: %s -*-\n" encoding)
+ (make-string
+ ;; Subtract 2 because of the coding cookie.
+ (- (line-number-at-pos start) 2) ?\n)))))
+ (with-temp-buffer
+ (python-mode)
+ (when fillstr
+ (insert fillstr))
+ (insert substring)
+ (goto-char (point-min))
+ (when (not toplevel-p)
+ (insert "if True:")
+ (delete-region (point) (line-end-position)))
+ (when nomain
+ (let* ((if-name-main-start-end
+ (and nomain
+ (save-excursion
+ (when (python-nav-if-name-main)
+ (cons (point)
+ (progn (python-nav-forward-sexp-safe)
+ ;; Include ending newline
+ (forward-line 1)
+ (point)))))))
+ ;; Oh destructuring bind, how I miss you.
+ (if-name-main-start (car if-name-main-start-end))
+ (if-name-main-end (cdr if-name-main-start-end))
+ (fillstr (make-string
+ (- (line-number-at-pos if-name-main-end)
+ (line-number-at-pos if-name-main-start)) ?\n)))
+ (when if-name-main-start-end
+ (goto-char if-name-main-start)
+ (delete-region if-name-main-start if-name-main-end)
+ (insert fillstr))))
+ ;; Ensure there's only one coding cookie in the generated string.
+ (goto-char (point-min))
+ (when (looking-at-p (python-rx coding-cookie))
+ (forward-line 1)
+ (when (looking-at-p (python-rx coding-cookie))
+ (delete-region
+ (line-beginning-position) (line-end-position))))
+ (buffer-substring-no-properties (point-min) (point-max)))))
+
+(defun python-shell-send-region (start end &optional send-main msg)
+ "Send the region delimited by START and END to inferior Python process.
+When optional argument SEND-MAIN is non-nil, allow execution of
+code inside blocks delimited by \"if __name__== '__main__':\".
+When called interactively SEND-MAIN defaults to nil, unless it's
+called with prefix argument. When optional argument MSG is
+non-nil, forces display of a user-friendly message if there's no
+process running; defaults to t when called interactively."
+ (interactive
+ (list (region-beginning) (region-end) current-prefix-arg t))
+ (let* ((string (python-shell-buffer-substring start end (not send-main)))
+ (process (python-shell-get-process-or-error msg))
+ (original-string (buffer-substring-no-properties start end))
+ (_ (string-match "\\`\n*\\(.*\\)" original-string)))
+ (message "Sent: %s..." (match-string 1 original-string))
+ (python-shell-send-string string process)))
+
+(defun python-shell-send-buffer (&optional send-main msg)
"Send the entire buffer to inferior Python process.
-With prefix ARG allow execution of code inside blocks delimited
-by \"if __name__== '__main__':\""
- (interactive "P")
+When optional argument SEND-MAIN is non-nil, allow execution of
+code inside blocks delimited by \"if __name__== '__main__':\".
+When called interactively SEND-MAIN defaults to nil, unless it's
+called with prefix argument. When optional argument MSG is
+non-nil, forces display of a user-friendly message if there's no
+process running; defaults to t when called interactively."
+ (interactive (list current-prefix-arg t))
(save-restriction
(widen)
- (let ((str (buffer-substring (point-min) (point-max))))
- (and
- (not arg)
- (setq str (replace-regexp-in-string
- (python-rx if-name-main)
- "if __name__ == '__main__ ':" str)))
- (python-shell-send-string str))))
-
-(defun python-shell-send-defun (arg)
+ (python-shell-send-region (point-min) (point-max) send-main msg)))
+
+(defun python-shell-send-defun (&optional arg msg)
"Send the current defun to inferior Python process.
-When argument ARG is non-nil do not include decorators."
- (interactive "P")
+When argument ARG is non-nil do not include decorators. When
+optional argument MSG is non-nil, forces display of a
+user-friendly message if there's no process running; defaults to
+t when called interactively."
+ (interactive (list current-prefix-arg t))
(save-excursion
(python-shell-send-region
(progn
@@ -2165,198 +3100,514 @@ When argument ARG is non-nil do not include decorators."
(progn
(or (python-nav-end-of-defun)
(end-of-line 1))
- (point-marker)))))
+ (point-marker))
+ nil ;; noop
+ msg)))
-(defun python-shell-send-file (file-name &optional process temp-file-name)
+(defun python-shell-send-file (file-name &optional process temp-file-name
+ delete msg)
"Send FILE-NAME to inferior Python PROCESS.
If TEMP-FILE-NAME is passed then that file is used for processing
instead, while internally the shell will continue to use
-FILE-NAME."
- (interactive "fFile to send: ")
- (let* ((process (or process (python-shell-get-or-create-process)))
+FILE-NAME. If TEMP-FILE-NAME and DELETE are non-nil, then
+TEMP-FILE-NAME is deleted after evaluation is performed. When
+optional argument MSG is non-nil, forces display of a
+user-friendly message if there's no process running; defaults to
+t when called interactively."
+ (interactive
+ (list
+ (read-file-name "File to send: ") ; file-name
+ nil ; process
+ nil ; temp-file-name
+ nil ; delete
+ t)) ; msg
+ (let* ((process (or process (python-shell-get-process-or-error msg)))
+ (encoding (with-temp-buffer
+ (insert-file-contents
+ (or temp-file-name file-name))
+ (python-info-encoding)))
+ (file-name (expand-file-name
+ (or (file-remote-p file-name 'localname)
+ file-name)))
(temp-file-name (when temp-file-name
(expand-file-name
(or (file-remote-p temp-file-name 'localname)
- temp-file-name))))
- (file-name (or (when file-name
- (expand-file-name
- (or (file-remote-p file-name 'localname)
- file-name)))
- temp-file-name)))
- (when (not file-name)
- (error "If FILE-NAME is nil then TEMP-FILE-NAME must be non-nil"))
+ temp-file-name)))))
(python-shell-send-string
(format
- (concat "__pyfile = open('''%s''');"
- "exec(compile(__pyfile.read(), '''%s''', 'exec'));"
- "__pyfile.close()")
- (or temp-file-name file-name) file-name)
+ (concat
+ "import codecs, os;"
+ "__pyfile = codecs.open('''%s''', encoding='''%s''');"
+ "__code = __pyfile.read().encode('''%s''');"
+ "__pyfile.close();"
+ (when (and delete temp-file-name)
+ (format "os.remove('''%s''');" temp-file-name))
+ "exec(compile(__code, '''%s''', 'exec'));")
+ (or temp-file-name file-name) encoding encoding file-name)
process)))
-(defun python-shell-switch-to-shell ()
- "Switch to inferior Python process buffer."
- (interactive)
- (pop-to-buffer (process-buffer (python-shell-get-or-create-process)) t))
+(defun python-shell-switch-to-shell (&optional msg)
+ "Switch to inferior Python process buffer.
+When optional argument MSG is non-nil, forces display of a
+user-friendly message if there's no process running; defaults to
+t when called interactively."
+ (interactive "p")
+ (pop-to-buffer
+ (process-buffer (python-shell-get-process-or-error msg)) nil t))
(defun python-shell-send-setup-code ()
"Send all setup code for shell.
This function takes the list of setup code to send from the
`python-shell-setup-codes' list."
- (let ((process (get-buffer-process (current-buffer))))
- (dolist (code python-shell-setup-codes)
- (when code
- (message "Sent %s" code)
- (python-shell-send-string
- (symbol-value code) process)))))
-
-(add-hook 'inferior-python-mode-hook
+ (when python-shell-setup-codes
+ (let ((process (python-shell-get-process))
+ (code (concat
+ (mapconcat
+ (lambda (elt)
+ (cond ((stringp elt) elt)
+ ((symbolp elt) (symbol-value elt))
+ (t "")))
+ python-shell-setup-codes
+ "\n\nprint ('python.el: sent setup code')"))))
+ (python-shell-send-string code process)
+ (python-shell-accept-process-output process))))
+
+(add-hook 'python-shell-first-prompt-hook
#'python-shell-send-setup-code)
;;; Shell completion
(defcustom python-shell-completion-setup-code
- "try:
- import readline
-except ImportError:
- def __COMPLETER_all_completions(text): []
-else:
- import rlcompleter
- readline.set_completer(rlcompleter.Completer().complete)
- def __COMPLETER_all_completions(text):
- import sys
- completions = []
+ "
+def __PYTHON_EL_get_completions(text):
+ completions = []
+ completer = None
+
+ try:
+ import readline
+
try:
+ import __builtin__
+ except ImportError:
+ # Python 3
+ import builtins as __builtin__
+ builtins = dir(__builtin__)
+
+ is_ipython = ('__IPYTHON__' in builtins or
+ '__IPYTHON__active' in builtins)
+ splits = text.split()
+ is_module = splits and splits[0] in ('from', 'import')
+
+ if is_ipython and is_module:
+ from IPython.core.completerlib import module_completion
+ completions = module_completion(text.strip())
+ elif is_ipython and '__IP' in builtins:
+ completions = __IP.complete(text)
+ elif is_ipython and 'get_ipython' in builtins:
+ completions = get_ipython().Completer.all_completions(text)
+ else:
+ # Try to reuse current completer.
+ completer = readline.get_completer()
+ if not completer:
+ # importing rlcompleter sets the completer, use it as a
+ # last resort to avoid breaking customizations.
+ import rlcompleter
+ completer = readline.get_completer()
+ if getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ completer.print_mode = False
i = 0
while True:
- res = readline.get_completer()(text, i)
- if not res: break
+ completion = completer(text, i)
+ if not completion:
+ break
i += 1
- completions.append(res)
- except NameError:
- pass
- return completions"
+ completions.append(completion)
+ except:
+ pass
+ finally:
+ if getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ completer.print_mode = True
+ return completions"
"Code used to setup completion in inferior Python processes."
:type 'string
: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."
+ "';'.join(__PYTHON_EL_get_completions('''%s'''))"
+ "Python code used to get a string of completions separated by semicolons.
+The string passed to the function is the current python name or
+the full statement in the case of imports."
:type 'string
: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
+(define-obsolete-variable-alias
+ 'python-shell-completion-module-string-code
+ 'python-shell-completion-string-code
+ "24.4"
+ "Completion string code must also autocomplete modules.")
-and use the following as the value of this variable:
+(define-obsolete-variable-alias
+ 'python-shell-completion-pdb-string-code
+ 'python-shell-completion-string-code
+ "25.1"
+ "Completion string code must work for (i)pdb.")
+
+(defcustom python-shell-completion-native-disabled-interpreters
+ ;; PyPy's readline cannot handle some escape sequences yet.
+ (list "pypy")
+ "List of disabled interpreters.
+When a match is found, native completion is disabled."
+ :type '(repeat string))
+
+(defcustom python-shell-completion-native-enable t
+ "Enable readline based native completion."
+ :type 'boolean)
+
+(defcustom python-shell-completion-native-output-timeout 5.0
+ "Time in seconds to wait for completion output before giving up."
+ :type 'float)
+
+(defcustom python-shell-completion-native-try-output-timeout 1.0
+ "Time in seconds to wait for *trying* native completion output."
+ :type 'float)
+
+(defvar python-shell-completion-native-redirect-buffer
+ " *Python completions redirect*"
+ "Buffer to be used to redirect output of readline commands.")
+
+(defun python-shell-completion-native-interpreter-disabled-p ()
+ "Return non-nil if interpreter has native completion disabled."
+ (when python-shell-completion-native-disabled-interpreters
+ (string-match
+ (regexp-opt python-shell-completion-native-disabled-interpreters)
+ (file-name-nondirectory python-shell-interpreter))))
+
+(defun python-shell-completion-native-try ()
+ "Return non-nil if can trigger native completion."
+ (let ((python-shell-completion-native-enable t)
+ (python-shell-completion-native-output-timeout
+ python-shell-completion-native-try-output-timeout))
+ (python-shell-completion-native-get-completions
+ (get-buffer-process (current-buffer))
+ nil "")))
+
+(defun python-shell-completion-native-setup ()
+ "Try to setup native completion, return non-nil on success."
+ (let ((process (python-shell-get-process)))
+ (with-current-buffer (process-buffer process)
+ (python-shell-send-string "
+def __PYTHON_EL_native_completion_setup():
+ try:
+ import readline
-';'.join(module_completion('''%s'''))\n"
- :type 'string
- :group 'python)
+ try:
+ import __builtin__
+ except ImportError:
+ # Python 3
+ import builtins as __builtin__
+
+ builtins = dir(__builtin__)
+ is_ipython = ('__IPYTHON__' in builtins or
+ '__IPYTHON__active' in builtins)
+
+ class __PYTHON_EL_Completer:
+ '''Completer wrapper that prints candidates to stdout.
+
+ It wraps an existing completer function and changes its behavior so
+ that the user input is unchanged and real candidates are printed to
+ stdout.
+
+ Returned candidates are '0__dummy_completion__' and
+ '1__dummy_completion__' in that order ('0__dummy_completion__' is
+ returned repeatedly until all possible candidates are consumed).
+
+ The real candidates are printed to stdout so that they can be
+ easily retrieved through comint output redirect trickery.
+ '''
+
+ PYTHON_EL_WRAPPED = True
+
+ def __init__(self, completer):
+ self.completer = completer
+ self.last_completion = None
+ self.print_mode = True
+
+ def __call__(self, text, state):
+ if state == 0:
+ # Set the first dummy completion.
+ self.last_completion = None
+ completion = '0__dummy_completion__'
+ else:
+ completion = self.completer(text, state - 1)
+
+ if not completion:
+ if self.last_completion != '1__dummy_completion__':
+ # When no more completions are available, returning a
+ # dummy with non-sharing prefix allow to ensure output
+ # while preventing changes to current input.
+ # Coincidentally it's also the end of output.
+ completion = '1__dummy_completion__'
+ elif completion.endswith('('):
+ # Remove parens on callables as it breaks completion on
+ # arguments (e.g. str(Ari<tab>)).
+ completion = completion[:-1]
+ self.last_completion = completion
+
+ if completion in (
+ '0__dummy_completion__', '1__dummy_completion__'):
+ return completion
+ elif completion:
+ # For every non-dummy completion, return a repeated dummy
+ # one and print the real candidate so it can be retrieved
+ # by comint output filters.
+ if self.print_mode:
+ print (completion)
+ return '0__dummy_completion__'
+ else:
+ return completion
+ else:
+ return completion
+
+ completer = readline.get_completer()
+
+ if not completer:
+ # Used as last resort to avoid breaking customizations.
+ import rlcompleter
+ completer = readline.get_completer()
+
+ if completer and not getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ # Wrap the existing completer function only once.
+ new_completer = __PYTHON_EL_Completer(completer)
+ if not is_ipython:
+ readline.set_completer(new_completer)
+ else:
+ # Try both initializations to cope with all IPython versions.
+ # This works fine for IPython 3.x but not for earlier:
+ readline.set_completer(new_completer)
+ # IPython<3 hacks readline such that `readline.set_completer`
+ # won't work. This workaround injects the new completer
+ # function into the existing instance directly:
+ instance = getattr(completer, 'im_self', completer.__self__)
+ instance.rlcomplete = new_completer
+
+ if readline.__doc__ and 'libedit' in readline.__doc__:
+ readline.parse_and_bind('bind ^I rl_complete')
+ else:
+ readline.parse_and_bind('tab: complete')
+ # Require just one tab to send output.
+ readline.parse_and_bind('set show-all-if-ambiguous on')
-(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)
+ print ('python.el: native completion setup loaded')
+ except:
+ print ('python.el: native completion setup failed')
-(defun python-shell-completion-get-completions (process line input)
- "Do completion at point for PROCESS.
-LINE is used to detect the context on how to complete given
-INPUT."
- (let* ((prompt
- ;; Get the last prompt for the inferior process
- ;; buffer. This is used for the completion code selection
- ;; heuristic.
- (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
- (pcase completion-context
- (`pdb python-shell-completion-pdb-string-code)
- (`import python-shell-completion-module-string-code)
- (`default python-shell-completion-string-code)
- (_ nil)))
- (input
- (if (eq completion-context 'import)
- (replace-regexp-in-string "^[ \t]+" "" line)
- input)))
- (and completion-code
- (> (length input) 0)
- (with-current-buffer (process-buffer process)
- (let ((completions (python-shell-send-string-no-output
- (format completion-code input) process)))
- (and (> (length completions) 2)
- (split-string completions
- "^'\\|^\"\\|;\\|'$\\|\"$" t)))))))
-
-(defun python-shell-completion-complete-at-point (&optional process)
- "Perform completion at point in inferior Python.
+__PYTHON_EL_native_completion_setup()" process)
+ (when (and
+ (python-shell-accept-process-output
+ process python-shell-completion-native-try-output-timeout)
+ (save-excursion
+ (re-search-backward
+ (regexp-quote "python.el: native completion setup loaded") nil t 1)))
+ (python-shell-completion-native-try)))))
+
+(defun python-shell-completion-native-turn-off (&optional msg)
+ "Turn off shell native completions.
+With argument MSG show deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (set (make-local-variable 'python-shell-completion-native-enable) nil)
+ (when msg
+ (message "Shell native completion is disabled, using fallback"))))
+
+(defun python-shell-completion-native-turn-on (&optional msg)
+ "Turn on shell native completions.
+With argument MSG show deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (set (make-local-variable 'python-shell-completion-native-enable) t)
+ (python-shell-completion-native-turn-on-maybe msg)))
+
+(defun python-shell-completion-native-turn-on-maybe (&optional msg)
+ "Turn on native completions if enabled and available.
+With argument MSG show activation/deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (when python-shell-completion-native-enable
+ (cond
+ ((python-shell-completion-native-interpreter-disabled-p)
+ (python-shell-completion-native-turn-off msg))
+ ((python-shell-completion-native-setup)
+ (when msg
+ (message "Shell native completion is enabled.")))
+ (t (lwarn
+ '(python python-shell-completion-native-turn-on-maybe)
+ :warning
+ (concat
+ "Your `python-shell-interpreter' doesn't seem to "
+ "support readline, yet `python-shell-completion-native' "
+ (format "was t and %S is not part of the "
+ (file-name-nondirectory python-shell-interpreter))
+ "`python-shell-completion-native-disabled-interpreters' "
+ "list. Native completions have been disabled locally. "))
+ (python-shell-completion-native-turn-off msg))))))
+
+(defun python-shell-completion-native-turn-on-maybe-with-msg ()
+ "Like `python-shell-completion-native-turn-on-maybe' but force messages."
+ (python-shell-completion-native-turn-on-maybe t))
+
+(add-hook 'python-shell-first-prompt-hook
+ #'python-shell-completion-native-turn-on-maybe-with-msg)
+
+(defun python-shell-completion-native-toggle (&optional msg)
+ "Toggle shell native completion.
+With argument MSG show activation/deactivation message."
+ (interactive "p")
+ (python-shell-with-shell-buffer
+ (if python-shell-completion-native-enable
+ (python-shell-completion-native-turn-off msg)
+ (python-shell-completion-native-turn-on msg))
+ python-shell-completion-native-enable))
+
+(defun python-shell-completion-native-get-completions (process import input)
+ "Get completions using native readline for PROCESS.
+When IMPORT is non-nil takes precedence over INPUT for
+completion."
+ (with-current-buffer (process-buffer process)
+ (let* ((input (or import input))
+ (original-filter-fn (process-filter process))
+ (redirect-buffer (get-buffer-create
+ python-shell-completion-native-redirect-buffer))
+ (trigger "\t")
+ (new-input (concat input trigger))
+ (input-length
+ (save-excursion
+ (+ (- (point-max) (comint-bol)) (length new-input))))
+ (delete-line-command (make-string input-length ?\b))
+ (input-to-send (concat new-input delete-line-command)))
+ ;; Ensure restoring the process filter, even if the user quits
+ ;; or there's some other error.
+ (unwind-protect
+ (with-current-buffer redirect-buffer
+ ;; Cleanup the redirect buffer
+ (erase-buffer)
+ ;; Mimic `comint-redirect-send-command', unfortunately it
+ ;; can't be used here because it expects a newline in the
+ ;; command and that's exactly what we are trying to avoid.
+ (let ((comint-redirect-echo-input nil)
+ (comint-redirect-completed nil)
+ (comint-redirect-perform-sanity-check nil)
+ (comint-redirect-insert-matching-regexp t)
+ (comint-redirect-finished-regexp
+ "1__dummy_completion__[[:space:]]*\n")
+ (comint-redirect-output-buffer redirect-buffer))
+ ;; Compatibility with Emacs 24.x. Comint changed and
+ ;; now `comint-redirect-filter' gets 3 args. This
+ ;; checks which version of `comint-redirect-filter' is
+ ;; in use based on its args and uses `apply-partially'
+ ;; to make it up for the 3 args case.
+ (if (= (length
+ (help-function-arglist 'comint-redirect-filter)) 3)
+ (set-process-filter
+ process (apply-partially
+ #'comint-redirect-filter original-filter-fn))
+ (set-process-filter process #'comint-redirect-filter))
+ (process-send-string process input-to-send)
+ ;; Grab output until our dummy completion used as
+ ;; output end marker is found.
+ (when (python-shell-accept-process-output
+ process python-shell-completion-native-output-timeout
+ comint-redirect-finished-regexp)
+ (re-search-backward "0__dummy_completion__" nil t)
+ (cl-remove-duplicates
+ (split-string
+ (buffer-substring-no-properties
+ (line-beginning-position) (point-min))
+ "[ \f\t\n\r\v()]+" t)
+ :test #'string=))))
+ (set-process-filter process original-filter-fn)))))
+
+(defun python-shell-completion-get-completions (process import input)
+ "Do completion at point using PROCESS for IMPORT or INPUT.
+When IMPORT is non-nil takes precedence over INPUT for
+completion."
+ (setq input (or import input))
+ (with-current-buffer (process-buffer process)
+ (let ((completions
+ (python-util-strip-string
+ (python-shell-send-string-no-output
+ (format
+ (concat python-shell-completion-setup-code
+ "\nprint (" python-shell-completion-string-code ")")
+ input) process))))
+ (when (> (length completions) 2)
+ (split-string completions
+ "^'\\|^\"\\|;\\|'$\\|\"$" t)))))
+
+(defun python-shell-completion-at-point (&optional process)
+ "Function for `completion-at-point-functions' in `inferior-python-mode'.
Optional argument PROCESS forces completions to be retrieved
using that one instead of current buffer's process."
(setq process (or process (get-buffer-process (current-buffer))))
- (let* ((start
+ (let* ((line-start (if (derived-mode-p 'inferior-python-mode)
+ ;; Working on a shell buffer: use prompt end.
+ (cdr (python-util-comint-last-prompt))
+ (line-beginning-position)))
+ (import-statement
+ (when (string-match-p
+ (rx (* space) word-start (or "from" "import") word-end space)
+ (buffer-substring-no-properties line-start (point)))
+ (buffer-substring-no-properties line-start (point))))
+ (start
(save-excursion
- (with-syntax-table python-dotty-syntax-table
- (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-syntax-context 'string))
- (forward-char -1)))
- (point)))))
- (end (point)))
+ (if (not (re-search-backward
+ (python-rx
+ (or whitespace open-paren close-paren string-delimiter))
+ line-start
+ t 1))
+ line-start
+ (forward-char (length (match-string-no-properties 0)))
+ (point))))
+ (end (point))
+ (prompt-boundaries (python-util-comint-last-prompt))
+ (prompt
+ (with-current-buffer (process-buffer process)
+ (when prompt-boundaries
+ (buffer-substring-no-properties
+ (car prompt-boundaries) (cdr prompt-boundaries)))))
+ (completion-fn
+ (with-current-buffer (process-buffer process)
+ (cond ((or (null prompt)
+ (< (point) (cdr prompt-boundaries)))
+ #'ignore)
+ ((or (not python-shell-completion-native-enable)
+ ;; Even if native completion is enabled, for
+ ;; pdb interaction always use the fallback
+ ;; mechanism since the completer is changed.
+ ;; Also, since pdb interaction is single-line
+ ;; based, this is enough.
+ (string-match-p python-shell-prompt-pdb-regexp prompt))
+ #'python-shell-completion-get-completions)
+ (t #'python-shell-completion-native-get-completions)))))
(list start end
(completion-table-dynamic
(apply-partially
- #'python-shell-completion-get-completions
- process (buffer-substring-no-properties
- (line-beginning-position) end))))))
+ completion-fn
+ process import-statement)))))
+
+(define-obsolete-function-alias
+ 'python-shell-completion-complete-at-point
+ 'python-shell-completion-at-point
+ "25.1")
(defun python-shell-completion-complete-or-indent ()
"Complete or indent depending on the context.
-If content before pointer is all whitespace indent. If not try
-to complete."
+If content before pointer is all whitespace, indent.
+If not try to complete."
(interactive)
(if (string-match "^[[:space:]]*$"
(buffer-substring (comint-line-beginning-position)
- (point-marker)))
+ (point)))
(indent-for-tab-command)
(completion-at-point)))
@@ -2364,14 +3615,14 @@ to complete."
;;; PDB Track integration
(defcustom python-pdbtrack-activate t
- "Non-nil makes python shell enable pdbtracking."
+ "Non-nil makes Python shell enable pdbtracking."
:type 'boolean
:group 'python
:safe 'booleanp)
(defcustom python-pdbtrack-stacktrace-info-regexp
- "^> \\([^\"(<]+\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
- "Regular Expression matching stacktrace information.
+ "> \\([^\"(<]+\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
+ "Regular expression matching stacktrace information.
Used to extract the current line and module being inspected."
:type 'string
:group 'python
@@ -2389,10 +3640,18 @@ Never set this variable directly, use
"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)))
+ (let* ((file-name-prospect (concat (file-remote-p default-directory)
+ file-name))
+ (file-buffer (get-file-buffer file-name-prospect)))
(if file-buffer
(setq python-pdbtrack-tracked-buffer file-buffer)
- (setq file-buffer (find-file-noselect file-name))
+ (cond
+ ((file-exists-p file-name-prospect)
+ (setq file-buffer (find-file-noselect file-name-prospect)))
+ ((and (not (equal file-name file-name-prospect))
+ (file-exists-p file-name))
+ ;; Fallback to a locally available copy of the file.
+ (setq file-buffer (find-file-noselect file-name-prospect))))
(when (not (member file-buffer python-pdbtrack-buffers-to-kill))
(add-to-list 'python-pdbtrack-buffers-to-kill file-buffer)))
file-buffer))
@@ -2453,18 +3712,19 @@ Argument OUTPUT is a string with the output from the comint process."
;;; Symbol completion
-(defun python-completion-complete-at-point ()
- "Complete current symbol at point.
-For this to work the best as possible you should call
+(defun python-completion-at-point ()
+ "Function for `completion-at-point-functions' in `python-mode'.
+For this to work as best as possible you should call
`python-shell-send-buffer' from time to time so context in
-inferior python process is updated properly."
+inferior Python process is updated properly."
(let ((process (python-shell-get-process)))
- (if (not process)
- (error "Completion needs an inferior Python process running")
- (python-shell-completion-complete-at-point process))))
+ (when process
+ (python-shell-completion-at-point process))))
-(add-to-list 'debug-ignored-errors
- "^Completion needs an inferior Python process running.")
+(define-obsolete-function-alias
+ 'python-completion-complete-at-point
+ 'python-completion-at-point
+ "25.1")
;;; Fill paragraph
@@ -2502,12 +3762,12 @@ fill parens."
This affects `python-fill-string' behavior with regards to
triple quotes positioning.
-Possible values are DJANGO, ONETWO, PEP-257, PEP-257-NN,
-SYMMETRIC, and NIL. A value of NIL won't care about quotes
+Possible values are `django', `onetwo', `pep-257', `pep-257-nn',
+`symmetric', and nil. A value of nil won't care about quotes
position and will treat docstrings a normal string, any other
value may result in one of the following docstring styles:
-DJANGO:
+`django':
\"\"\"
Process foo, return bar.
@@ -2519,7 +3779,7 @@ DJANGO:
If processing fails throw ProcessingError.
\"\"\"
-ONETWO:
+`onetwo':
\"\"\"Process foo, return bar.\"\"\"
@@ -2530,7 +3790,7 @@ ONETWO:
\"\"\"
-PEP-257:
+`pep-257':
\"\"\"Process foo, return bar.\"\"\"
@@ -2540,7 +3800,7 @@ PEP-257:
\"\"\"
-PEP-257-NN:
+`pep-257-nn':
\"\"\"Process foo, return bar.\"\"\"
@@ -2549,7 +3809,7 @@ PEP-257-NN:
If processing fails throw ProcessingError.
\"\"\"
-SYMMETRIC:
+`symmetric':
\"\"\"Process foo, return bar.\"\"\"
@@ -2607,8 +3867,7 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
(defun python-fill-string (&optional justify)
"String fill function for `python-fill-paragraph'.
JUSTIFY should be used (if applicable) as in `fill-paragraph'."
- (let* ((marker (point-marker))
- (str-start-pos
+ (let* ((str-start-pos
(set-marker
(make-marker)
(or (python-syntax-context 'string)
@@ -2638,17 +3897,12 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
(`pep-257 (and multi-line-p (cons nil 2)))
(`pep-257-nn (and multi-line-p (cons nil 1)))
(`symmetric (and multi-line-p (cons 1 1)))))
- (docstring-p (save-excursion
- ;; Consider docstrings those strings which
- ;; start on a line by themselves.
- (python-nav-beginning-of-statement)
- (and (= (point) str-start-pos))))
(fill-paragraph-function))
(save-restriction
(narrow-to-region str-start-pos str-end-pos)
(fill-paragraph justify))
(save-excursion
- (when (and docstring-p python-fill-docstring-style)
+ (when (and (python-info-docstring-p) python-fill-docstring-style)
;; Add the number of newlines indicated by the selected style
;; at the start of the docstring.
(goto-char (+ str-start-pos num-quotes))
@@ -2674,7 +3928,7 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
;; Again indent only if a newline is added.
(indent-according-to-mode))))) t)
-(defun python-fill-decorator (&optional justify)
+(defun python-fill-decorator (&optional _justify)
"Decorator fill function for `python-fill-paragraph'.
JUSTIFY should be used (if applicable) as in `fill-paragraph'."
t)
@@ -2685,17 +3939,17 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
(save-restriction
(narrow-to-region (progn
(while (python-syntax-context 'paren)
- (goto-char (1- (point-marker))))
- (point-marker)
+ (goto-char (1- (point))))
(line-beginning-position))
(progn
(when (not (python-syntax-context 'paren))
(end-of-line)
(when (not (python-syntax-context 'paren))
(skip-syntax-backward "^)")))
- (while (python-syntax-context 'paren)
- (goto-char (1+ (point-marker))))
- (point-marker)))
+ (while (and (python-syntax-context 'paren)
+ (not (eobp)))
+ (goto-char (1+ (point))))
+ (point)))
(let ((paragraph-start "\f\\|[ \t]*$")
(paragraph-separate ",")
(fill-paragraph-function))
@@ -2704,7 +3958,8 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
(while (not (eobp))
(forward-line 1)
(python-indent-line)
- (goto-char (line-end-position)))) t)
+ (goto-char (line-end-position))))
+ t)
;;; Skeletons
@@ -2761,8 +4016,8 @@ The skeleton will be bound to python-skeleton-NAME."
(declare (indent 2))
(let* ((name (symbol-name name))
(function-name (intern (concat "python-skeleton--" name)))
- (msg (format
- "Add '%s' clause? " name)))
+ (msg (format-message
+ "Add `%s' clause? " name)))
(when (not skel)
(setq skel
`(< ,(format "%s:" name) \n \n
@@ -2803,6 +4058,12 @@ The skeleton will be bound to python-skeleton-NAME."
> _ \n
'(python-skeleton--else) | ^)
+(python-skeleton-define import nil
+ "Import from module: "
+ "from " str & " " | -5
+ "import "
+ ("Identifier: " str ", ") -2 \n _)
+
(python-skeleton-define try nil
nil
"try:" \n
@@ -2829,15 +4090,14 @@ The skeleton will be bound to python-skeleton-NAME."
"class " str "(" ("Inheritance, %s: "
(unless (equal ?\( (char-before)) ", ")
str)
- & ")" | -2
+ & ")" | -1
":" \n
"\"\"\"" - "\"\"\"" \n
> _ \n)
(defun python-skeleton-add-menu-items ()
"Add menu items to Python->Skeletons menu."
- (let ((skeletons (sort python-skeleton-available 'string<))
- (items))
+ (let ((skeletons (sort python-skeleton-available 'string<)))
(dolist (skeleton skeletons)
(easy-menu-add-item
nil '("Python" "Skeletons")
@@ -2848,13 +4108,22 @@ The skeleton will be bound to python-skeleton-NAME."
;;; FFAP
(defcustom python-ffap-setup-code
- "def __FFAP_get_module_path(module):
+ "
+def __FFAP_get_module_path(objstr):
try:
- import os
- path = __import__(module).__file__
- if path[-4:] == '.pyc' and os.path.exists(path[0:-1]):
- path = path[:-1]
- return path
+ import inspect
+ import os.path
+ # NameError exceptions are delayed until this point.
+ obj = eval(objstr)
+ module = inspect.getmodule(obj)
+ filename = module.__file__
+ ext = os.path.splitext(filename)[1]
+ if ext in ('.pyc', '.pyo'):
+ # Point to the source file.
+ filename = filename[:-1]
+ if os.path.exists(filename):
+ return filename
+ return ''
except:
return ''"
"Python code to get a module path."
@@ -2862,7 +4131,7 @@ The skeleton will be bound to python-skeleton-NAME."
:group 'python)
(defcustom python-ffap-string-code
- "__FFAP_get_module_path('''%s''')\n"
+ "__FFAP_get_module_path('''%s''')"
"Python code used to get a string with the path of a module."
:type 'string
:group 'python)
@@ -2870,16 +4139,19 @@ The skeleton will be bound to python-skeleton-NAME."
(defun python-ffap-module-path (module)
"Function for `ffap-alist' to return path for MODULE."
(let ((process (or
- (and (eq major-mode 'inferior-python-mode)
+ (and (derived-mode-p 'inferior-python-mode)
(get-buffer-process (current-buffer)))
(python-shell-get-process))))
(if (not process)
nil
(let ((module-file
(python-shell-send-string-no-output
- (format python-ffap-string-code module) process)))
- (when module-file
- (substring-no-properties module-file 1 -1))))))
+ (concat
+ python-ffap-setup-code
+ "\nprint (" (format python-ffap-string-code module) ")")
+ process)))
+ (unless (zerop (length module-file))
+ (python-util-strip-string module-file))))))
(defvar ffap-alist)
@@ -2892,7 +4164,9 @@ The skeleton will be bound to python-skeleton-NAME."
;;; Code check
(defcustom python-check-command
- "pyflakes"
+ (or (executable-find "pyflakes")
+ (executable-find "epylint")
+ "install pyflakes, pylint or something else")
"Command used to check a Python file."
:type 'string
:group 'python)
@@ -2905,11 +4179,13 @@ The skeleton will be bound to python-skeleton-NAME."
(defvar python-check-custom-command nil
"Internal use.")
+;; XXX: Avoid `defvar-local' for compat with Emacs<24.3
+(make-variable-buffer-local 'python-check-custom-command)
(defun python-check (command)
"Check a Python file (default current buffer's file).
-Runs COMMAND, a shell command, as if by `compile'. See
-`python-check-command' for the default."
+Runs COMMAND, a shell command, as if by `compile'.
+See `python-check-command' for the default."
(interactive
(list (read-string "Check command: "
(or python-check-custom-command
@@ -2922,10 +4198,9 @@ Runs COMMAND, a shell command, as if by `compile'. See
"")))))))
(setq python-check-custom-command command)
(save-some-buffers (not compilation-ask-about-save) nil)
- (let ((process-environment (python-shell-calculate-process-environment))
- (exec-path (python-shell-calculate-exec-path)))
+ (python-shell-with-environment
(compilation-start command nil
- (lambda (mode-name)
+ (lambda (_modename)
(format python-check-buffer-name command)))))
@@ -2935,7 +4210,11 @@ Runs COMMAND, a shell command, as if by `compile'. See
"def __PYDOC_get_help(obj):
try:
import inspect
- if hasattr(obj, 'startswith'):
+ try:
+ str_type = basestring
+ except NameError:
+ str_type = str
+ if isinstance(obj, str_type):
obj = eval(obj, globals())
doc = inspect.getdoc(obj)
if not doc and callable(obj):
@@ -2958,48 +4237,65 @@ Runs COMMAND, a shell command, as if by `compile'. See
doc = doc.splitlines()[0]
except:
doc = ''
- try:
- exec('print doc')
- except SyntaxError:
- print(doc)"
+ return doc"
"Python code to setup documentation retrieval."
:type 'string
:group 'python)
(defcustom python-eldoc-string-code
- "__PYDOC_get_help('''%s''')\n"
+ "__PYDOC_get_help('''%s''')"
"Python code used to get a string with the documentation of an object."
:type 'string
:group 'python)
+(defun python-eldoc--get-symbol-at-point ()
+ "Get the current symbol for eldoc.
+Returns the current symbol handling point within arguments."
+ (save-excursion
+ (let ((start (python-syntax-context 'paren)))
+ (when start
+ (goto-char start))
+ (when (or start
+ (eobp)
+ (memq (char-syntax (char-after)) '(?\ ?-)))
+ ;; Try to adjust to closest symbol if not in one.
+ (python-util-forward-comment -1)))
+ (python-info-current-symbol t)))
+
(defun python-eldoc--get-doc-at-point (&optional force-input force-process)
"Internal implementation to get documentation at point.
-If not FORCE-INPUT is passed then what
-`python-info-current-symbol' returns will be used. If not
-FORCE-PROCESS is passed what `python-shell-get-process' returns
-is used."
+If not FORCE-INPUT is passed then what `python-eldoc--get-symbol-at-point'
+returns will be used. If not FORCE-PROCESS is passed what
+`python-shell-get-process' returns is used."
(let ((process (or force-process (python-shell-get-process))))
- (if (not process)
- (error "Eldoc needs an inferior Python process running")
- (let ((input (or force-input
- (python-info-current-symbol t))))
- (and input
- (python-shell-send-string-no-output
- (format python-eldoc-string-code input)
- process))))))
+ (when process
+ (let* ((input (or force-input
+ (python-eldoc--get-symbol-at-point)))
+ (docstring
+ (when input
+ ;; Prevent resizing the echo area when iPython is
+ ;; enabled. Bug#18794.
+ (python-util-strip-string
+ (python-shell-send-string-no-output
+ (concat
+ python-eldoc-setup-code
+ "\nprint(" (format python-eldoc-string-code input) ")")
+ process)))))
+ (unless (zerop (length docstring))
+ docstring)))))
(defun python-eldoc-function ()
"`eldoc-documentation-function' for Python.
-For this to work the best as possible you should call
+For this to work as best as possible you should call
`python-shell-send-buffer' from time to time so context in
-inferior python process is updated properly."
+inferior Python process is updated properly."
(python-eldoc--get-doc-at-point))
(defun python-eldoc-at-point (symbol)
"Get help on SYMBOL using `help'.
Interactively, prompt for symbol."
(interactive
- (let ((symbol (python-info-current-symbol t))
+ (let ((symbol (python-eldoc--get-symbol-at-point))
(enable-recursive-minibuffers t))
(list (read-string (if symbol
(format "Describe symbol (default %s): " symbol)
@@ -3007,8 +4303,16 @@ Interactively, prompt for symbol."
nil nil symbol))))
(message (python-eldoc--get-doc-at-point symbol)))
-(add-to-list 'debug-ignored-errors
- "^Eldoc needs an inferior Python process running.")
+
+;;; Hideshow
+
+(defun python-hideshow-forward-sexp-function (arg)
+ "Python specific `forward-sexp' function for `hs-minor-mode'.
+Argument ARG is ignored."
+ arg ; Shut up, byte compiler.
+ (python-nav-end-of-defun)
+ (unless (python-info-current-line-empty-p)
+ (backward-char)))
;;; Imenu
@@ -3029,15 +4333,15 @@ It must be a function with two arguments: TYPE and NAME.")
It must be a function with two arguments: TYPE and NAME.")
(defun python-imenu-format-item-label (type name)
- "Return imenu label for single node using TYPE and NAME."
+ "Return Imenu label for single node using TYPE and NAME."
(format "%s (%s)" name type))
(defun python-imenu-format-parent-item-label (type name)
- "Return imenu label for parent node using TYPE and NAME."
+ "Return Imenu label for parent node using TYPE and NAME."
(format "%s..." (python-imenu-format-item-label type name)))
-(defun python-imenu-format-parent-item-jump-label (type name)
- "Return imenu label for parent node jump using TYPE and NAME."
+(defun python-imenu-format-parent-item-jump-label (type _name)
+ "Return Imenu label for parent node jump using TYPE and NAME."
(if (string= type "class")
"*class definition*"
"*function definition*"))
@@ -3054,7 +4358,7 @@ It must be a function with two arguments: TYPE and NAME.")
(defun python-imenu--build-tree (&optional min-indent prev-indent tree)
"Recursively build the tree of nested definitions of a node.
-Arguments MIN-INDENT PREV-INDENT and TREE are internal and should
+Arguments MIN-INDENT, PREV-INDENT and TREE are internal and should
not be passed explicitly unless you know what you are doing."
(setq min-indent (or min-indent 0)
prev-indent (or prev-indent python-indent-offset))
@@ -3095,7 +4399,7 @@ not be passed explicitly unless you know what you are doing."
tree)))))))
(defun python-imenu-create-index ()
- "Return tree Imenu alist for the current python buffer.
+ "Return tree Imenu alist for the current Python buffer.
Change `python-imenu-format-item-label-function',
`python-imenu-format-parent-item-label-function',
`python-imenu-format-parent-item-jump-label-function' to
@@ -3108,18 +4412,19 @@ customize how labels are formatted."
index))
(defun python-imenu-create-flat-index (&optional alist prefix)
- "Return flat outline of the current python buffer for Imenu.
-Optional Argument ALIST is the tree to be flattened, when nil
+ "Return flat outline of the current Python buffer for Imenu.
+Optional argument ALIST is the tree to be flattened; when nil
`python-imenu-build-index' is used with
`python-imenu-format-parent-item-jump-label-function'
`python-imenu-format-parent-item-label-function'
-`python-imenu-format-item-label-function' set to (lambda (type
-name) name). Optional Argument PREFIX is used in recursive calls
-and should not be passed explicitly.
+`python-imenu-format-item-label-function' set to
+ (lambda (type name) name)
+Optional argument PREFIX is used in recursive calls and should
+not be passed explicitly.
Converts this:
- \((\"Foo\" . 103)
+ ((\"Foo\" . 103)
(\"Bar\" . 138)
(\"decorator\"
(\"decorator\" . 173)
@@ -3129,7 +4434,7 @@ Converts this:
To this:
- \((\"Foo\" . 103)
+ ((\"Foo\" . 103)
(\"Bar\" . 138)
(\"decorator\" . 173)
(\"decorator.wrap\" . 353)
@@ -3150,7 +4455,7 @@ To this:
(cons name (cdar pos))
(python-imenu-create-flat-index (cddr item) name))))))
(or alist
- (let* ((fn (lambda (type name) name))
+ (let* ((fn (lambda (_type name) name))
(python-imenu-format-item-label-function fn)
(python-imenu-format-parent-item-label-function fn)
(python-imenu-format-parent-item-jump-label-function fn))
@@ -3162,11 +4467,10 @@ To this:
(defun python-info-current-defun (&optional include-type)
"Return name of surrounding function with Python compatible dotty syntax.
Optional argument INCLUDE-TYPE indicates to include the type of the defun.
-This function is compatible to be used as
-`add-log-current-defun-function' since it returns nil if point is
-not inside a defun."
+This function can be used as the value of `add-log-current-defun-function'
+since it returns nil if point is not inside a defun."
(save-restriction
- (widen)
+ (prog-widen)
(save-excursion
(end-of-line 1)
(let ((names)
@@ -3283,55 +4587,94 @@ parent defun name."
(and (python-info-end-of-statement-p)
(python-info-statement-ends-block-p)))
-(defun python-info-closing-block ()
- "Return the point of the block the current line closes."
- (let ((closing-word (save-excursion
- (back-to-indentation)
- (current-word)))
- (indentation (current-indentation)))
- (when (member closing-word python-indent-dedenters)
+(define-obsolete-function-alias
+ 'python-info-closing-block
+ 'python-info-dedenter-opening-block-position "24.4")
+
+(defun python-info-dedenter-opening-block-position ()
+ "Return the point of the closest block the current line closes.
+Returns nil if point is not on a dedenter statement or no opening
+block can be detected. The latter case meaning current file is
+likely an invalid python file."
+ (let ((positions (python-info-dedenter-opening-block-positions))
+ (indentation (current-indentation))
+ (position))
+ (while (and (not position)
+ positions)
(save-excursion
- (forward-line -1)
- (while (and (> (current-indentation) indentation)
- (not (bobp))
- (not (back-to-indentation))
- (forward-line -1)))
- (back-to-indentation)
- (cond
- ((not (equal indentation (current-indentation))) nil)
- ((string= closing-word "elif")
- (when (member (current-word) '("if" "elif"))
- (point-marker)))
- ((string= closing-word "else")
- (when (member (current-word) '("if" "elif" "except" "for" "while"))
- (point-marker)))
- ((string= closing-word "except")
- (when (member (current-word) '("try"))
- (point-marker)))
- ((string= closing-word "finally")
- (when (member (current-word) '("except" "else"))
- (point-marker))))))))
-
-(defun python-info-closing-block-message (&optional closing-block-point)
- "Message the contents of the block the current line closes.
-With optional argument CLOSING-BLOCK-POINT use that instead of
-recalculating it calling `python-info-closing-block'."
- (let ((point (or closing-block-point (python-info-closing-block))))
+ (goto-char (car positions))
+ (if (<= (current-indentation) indentation)
+ (setq position (car positions))
+ (setq positions (cdr positions)))))
+ position))
+
+(defun python-info-dedenter-opening-block-positions ()
+ "Return points of blocks the current line may close sorted by closer.
+Returns nil if point is not on a dedenter statement or no opening
+block can be detected. The latter case meaning current file is
+likely an invalid python file."
+ (save-excursion
+ (let ((dedenter-pos (python-info-dedenter-statement-p)))
+ (when dedenter-pos
+ (goto-char dedenter-pos)
+ (let* ((pairs '(("elif" "elif" "if")
+ ("else" "if" "elif" "except" "for" "while")
+ ("except" "except" "try")
+ ("finally" "else" "except" "try")))
+ (dedenter (match-string-no-properties 0))
+ (possible-opening-blocks (cdr (assoc-string dedenter pairs)))
+ (collected-indentations)
+ (opening-blocks))
+ (catch 'exit
+ (while (python-nav--syntactically
+ (lambda ()
+ (re-search-backward (python-rx block-start) nil t))
+ #'<)
+ (let ((indentation (current-indentation)))
+ (when (and (not (memq indentation collected-indentations))
+ (or (not collected-indentations)
+ (< indentation (apply #'min collected-indentations))))
+ (setq collected-indentations
+ (cons indentation collected-indentations))
+ (when (member (match-string-no-properties 0)
+ possible-opening-blocks)
+ (setq opening-blocks (cons (point) opening-blocks))))
+ (when (zerop indentation)
+ (throw 'exit nil)))))
+ ;; sort by closer
+ (nreverse opening-blocks))))))
+
+(define-obsolete-function-alias
+ 'python-info-closing-block-message
+ 'python-info-dedenter-opening-block-message "24.4")
+
+(defun python-info-dedenter-opening-block-message ()
+ "Message the first line of the block the current statement closes."
+ (let ((point (python-info-dedenter-opening-block-position)))
(when point
(save-restriction
- (widen)
+ (prog-widen)
(message "Closes %s" (save-excursion
(goto-char point)
- (back-to-indentation)
(buffer-substring
(point) (line-end-position))))))))
+(defun python-info-dedenter-statement-p ()
+ "Return point if current statement is a dedenter.
+Sets `match-data' to the keyword that starts the dedenter
+statement."
+ (save-excursion
+ (python-nav-beginning-of-statement)
+ (when (and (not (python-syntax-context-type))
+ (looking-at (python-rx dedenter)))
+ (point))))
+
(defun python-info-line-ends-backslash-p (&optional line-number)
"Return non-nil if current line ends with backslash.
With optional argument LINE-NUMBER, check that line instead."
(save-excursion
(save-restriction
- (widen)
+ (prog-widen)
(when line-number
(python-util-goto-line line-number))
(while (and (not (eobp))
@@ -3347,7 +4690,7 @@ With optional argument LINE-NUMBER, check that line instead."
Optional argument LINE-NUMBER forces the line number to check against."
(save-excursion
(save-restriction
- (widen)
+ (prog-widen)
(when line-number
(python-util-goto-line line-number))
(when (python-info-line-ends-backslash-p)
@@ -3364,7 +4707,7 @@ When current line is continuation of another return the point
where the continued line ends."
(save-excursion
(save-restriction
- (widen)
+ (prog-widen)
(let* ((context-type (progn
(back-to-indentation)
(python-syntax-context-type)))
@@ -3401,23 +4744,40 @@ where the continued line ends."
(when (looking-at (python-rx block-start))
(point-marker)))))
+(defun python-info-assignment-statement-p (&optional current-line-only)
+ "Check if current line is an assignment.
+With argument CURRENT-LINE-ONLY is non-nil, don't follow any
+continuations, just check the if current line is an assignment."
+ (save-excursion
+ (let ((found nil))
+ (if current-line-only
+ (back-to-indentation)
+ (python-nav-beginning-of-statement))
+ (while (and
+ (re-search-forward (python-rx not-simple-operator
+ assignment-operator
+ (group not-simple-operator))
+ (line-end-position) t)
+ (not found))
+ (save-excursion
+ ;; The assignment operator should not be inside a string.
+ (backward-char (length (match-string-no-properties 1)))
+ (setq found (not (python-syntax-context-type)))))
+ (when found
+ (skip-syntax-forward " ")
+ (point-marker)))))
+
+;; TODO: rename to clarify this is only for the first continuation
+;; line or remove it and move its body to `python-indent-context'.
(defun python-info-assignment-continuation-line-p ()
- "Check if current line is a continuation of an assignment.
+ "Check if current line is the first 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
(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 (python-syntax-context-type))))
- (skip-syntax-forward "\s")
- (point-marker)))))
+ (python-info-assignment-statement-p t))))
(defun python-info-looking-at-beginning-of-defun (&optional syntax-ppss)
"Check if point is at `beginning-of-defun' using SYNTAX-PPSS."
@@ -3427,13 +4787,13 @@ operator."
(looking-at python-nav-beginning-of-defun-regexp))))
(defun python-info-current-line-comment-p ()
- "Check if current line is a comment line."
+ "Return non-nil if current line is a comment line."
(char-equal
(or (char-after (+ (line-beginning-position) (current-indentation))) ?_)
?#))
(defun python-info-current-line-empty-p ()
- "Check if current line is empty, ignoring whitespace."
+ "Return non-nil if current line is empty, ignoring whitespace."
(save-excursion
(beginning-of-line 1)
(looking-at
@@ -3442,6 +4802,72 @@ operator."
(* whitespace) line-end))
(string-equal "" (match-string-no-properties 1))))
+(defun python-info-docstring-p (&optional syntax-ppss)
+ "Return non-nil if point is in a docstring.
+When optional argument SYNTAX-PPSS is given, use that instead of
+point's current `syntax-ppss'."
+ ;;; https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring
+ (save-excursion
+ (when (and syntax-ppss (python-syntax-context 'string syntax-ppss))
+ (goto-char (nth 8 syntax-ppss)))
+ (python-nav-beginning-of-statement)
+ (let ((counter 1)
+ (indentation (current-indentation))
+ (backward-sexp-point)
+ (re (concat "[uU]?[rR]?"
+ (python-rx string-delimiter))))
+ (when (and
+ (not (python-info-assignment-statement-p))
+ (looking-at-p re)
+ ;; Allow up to two consecutive docstrings only.
+ (>=
+ 2
+ (progn
+ (while (save-excursion
+ (python-nav-backward-sexp)
+ (setq backward-sexp-point (point))
+ (and (= indentation (current-indentation))
+ (not (bobp)) ; Prevent infloop.
+ (looking-at-p
+ (concat "[uU]?[rR]?"
+ (python-rx string-delimiter)))))
+ ;; Previous sexp was a string, restore point.
+ (goto-char backward-sexp-point)
+ (cl-incf counter))
+ counter)))
+ (python-util-forward-comment -1)
+ (python-nav-beginning-of-statement)
+ (cond ((bobp))
+ ((python-info-assignment-statement-p) t)
+ ((python-info-looking-at-beginning-of-defun))
+ (t nil))))))
+
+(defun python-info-encoding-from-cookie ()
+ "Detect current buffer's encoding from its coding cookie.
+Returns the encoding as a symbol."
+ (let ((first-two-lines
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (forward-line 2)
+ (buffer-substring-no-properties
+ (point)
+ (point-min))))))
+ (when (string-match (python-rx coding-cookie) first-two-lines)
+ (intern (match-string-no-properties 1 first-two-lines)))))
+
+(defun python-info-encoding ()
+ "Return encoding for file.
+Try `python-info-encoding-from-cookie', if none is found then
+default to utf-8."
+ ;; If no encoding is defined, then it's safe to use UTF-8: Python 2
+ ;; uses ASCII as default while Python 3 uses UTF-8. This means that
+ ;; in the worst case scenario python.el will make things work for
+ ;; Python 2 files with unicode data and no encoding defined.
+ (or (python-info-encoding-from-cookie)
+ 'utf-8))
+
;;; Utility functions
@@ -3464,6 +4890,18 @@ to \"^python-\"."
(cdr pair))))
(buffer-local-variables from-buffer)))
+(defvar comint-last-prompt-overlay) ; Shut up, byte compiler.
+
+(defun python-util-comint-last-prompt ()
+ "Return comint last prompt overlay start and end.
+This is for compatibility with Emacs < 24.4."
+ (cond ((bound-and-true-p comint-last-prompt-overlay)
+ (cons (overlay-start comint-last-prompt-overlay)
+ (overlay-end comint-last-prompt-overlay)))
+ ((bound-and-true-p comint-last-prompt)
+ comint-last-prompt)
+ (t nil)))
+
(defun python-util-forward-comment (&optional direction)
"Python mode specific version of `forward-comment'.
Optional argument DIRECTION defines the direction to move to."
@@ -3475,11 +4913,73 @@ Optional argument DIRECTION defines the direction to move to."
(goto-char comment-start))
(forward-comment factor)))
+(defun python-util-list-directories (directory &optional predicate max-depth)
+ "List DIRECTORY subdirs, filtered by PREDICATE and limited by MAX-DEPTH.
+Argument PREDICATE defaults to `identity' and must be a function
+that takes one argument (a full path) and returns non-nil for
+allowed files. When optional argument MAX-DEPTH is non-nil, stop
+searching when depth is reached, else don't limit."
+ (let* ((dir (expand-file-name directory))
+ (dir-length (length dir))
+ (predicate (or predicate #'identity))
+ (to-scan (list dir))
+ (tally nil))
+ (while to-scan
+ (let ((current-dir (car to-scan)))
+ (when (funcall predicate current-dir)
+ (setq tally (cons current-dir tally)))
+ (setq to-scan (append (cdr to-scan)
+ (python-util-list-files
+ current-dir #'file-directory-p)
+ nil))
+ (when (and max-depth
+ (<= max-depth
+ (length (split-string
+ (substring current-dir dir-length)
+ "/\\|\\\\" t))))
+ (setq to-scan nil))))
+ (nreverse tally)))
+
+(defun python-util-list-files (dir &optional predicate)
+ "List files in DIR, filtering with PREDICATE.
+Argument PREDICATE defaults to `identity' and must be a function
+that takes one argument (a full path) and returns non-nil for
+allowed files."
+ (let ((dir-name (file-name-as-directory dir)))
+ (apply #'nconc
+ (mapcar (lambda (file-name)
+ (let ((full-file-name (expand-file-name file-name dir-name)))
+ (when (and
+ (not (member file-name '("." "..")))
+ (funcall (or predicate #'identity) full-file-name))
+ (list full-file-name))))
+ (directory-files dir-name)))))
+
+(defun python-util-list-packages (dir &optional max-depth)
+ "List packages in DIR, limited by MAX-DEPTH.
+When optional argument MAX-DEPTH is non-nil, stop searching when
+depth is reached, else don't limit."
+ (let* ((dir (expand-file-name dir))
+ (parent-dir (file-name-directory
+ (directory-file-name
+ (file-name-directory
+ (file-name-as-directory dir)))))
+ (subpath-length (length parent-dir)))
+ (mapcar
+ (lambda (file-name)
+ (replace-regexp-in-string
+ (rx (or ?\\ ?/)) "." (substring file-name subpath-length)))
+ (python-util-list-directories
+ (directory-file-name dir)
+ (lambda (dir)
+ (file-exists-p (expand-file-name "__init__.py" dir)))
+ max-depth))))
+
(defun python-util-popn (lst n)
"Return LST first N elements.
-N should be an integer, when it's a natural negative number its
-opposite is used. When N is bigger than the length of LST, the
-list is returned as is."
+N should be an integer, when negative its opposite is used.
+When N is bigger than the length of LST, the list is
+returned as is."
(let* ((n (min (abs n)))
(len (length lst))
(acc))
@@ -3491,14 +4991,36 @@ list is returned as is."
n (1- n)))
(reverse acc))))
+(defun python-util-strip-string (string)
+ "Strip STRING whitespace and newlines from end and beginning."
+ (replace-regexp-in-string
+ (rx (or (: string-start (* (any whitespace ?\r ?\n)))
+ (: (* (any whitespace ?\r ?\n)) string-end)))
+ ""
+ string))
+
+(defun python-util-valid-regexp-p (regexp)
+ "Return non-nil if REGEXP is valid."
+ (ignore-errors (string-match regexp "") t))
+
+(defun python-electric-pair-string-delimiter ()
+ (when (and electric-pair-mode
+ (memq last-command-event '(?\" ?\'))
+ (let ((count 0))
+ (while (eq (char-before (- (point) count)) last-command-event)
+ (cl-incf count))
+ (= count 3))
+ (eq (char-after) last-command-event))
+ (save-excursion (insert (make-string 2 last-command-event)))))
+
+(defvar electric-indent-inhibit)
+
;;;###autoload
(define-derived-mode python-mode prog-mode "Python"
"Major mode for editing Python files.
-\\{python-mode-map}
-Entry to this mode calls the value of `python-mode-hook'
-if that value is non-nil."
+\\{python-mode-map}"
(set (make-local-variable 'tab-width) 8)
(set (make-local-variable 'indent-tabs-mode) nil)
@@ -3512,7 +5034,10 @@ if that value is non-nil."
'python-nav-forward-sexp)
(set (make-local-variable 'font-lock-defaults)
- '(python-font-lock-keywords nil nil nil nil))
+ '(python-font-lock-keywords
+ nil nil nil nil
+ (font-lock-syntactic-face-function
+ . python-font-lock-syntactic-face-function)))
(set (make-local-variable 'syntax-propertize-function)
python-syntax-propertize-function)
@@ -3520,10 +5045,18 @@ if that value is non-nil."
(set (make-local-variable 'indent-line-function)
#'python-indent-line-function)
(set (make-local-variable 'indent-region-function) #'python-indent-region)
+ ;; Because indentation is not redundant, we cannot safely reindent code.
+ (set (make-local-variable 'electric-indent-inhibit) t)
+ (set (make-local-variable 'electric-indent-chars)
+ (cons ?: electric-indent-chars))
+
+ ;; Add """ ... """ pairing to electric-pair-mode.
+ (add-hook 'post-self-insert-hook
+ #'python-electric-pair-string-delimiter 'append t)
(set (make-local-variable 'paragraph-start) "\\s-*$")
(set (make-local-variable 'fill-paragraph-function)
- 'python-fill-paragraph)
+ #'python-fill-paragraph)
(set (make-local-variable 'beginning-of-defun-function)
#'python-nav-beginning-of-defun)
@@ -3531,10 +5064,10 @@ if that value is non-nil."
#'python-nav-end-of-defun)
(add-hook 'completion-at-point-functions
- 'python-completion-complete-at-point nil 'local)
+ #'python-completion-at-point nil 'local)
(add-hook 'post-self-insert-hook
- 'python-indent-post-self-insert-function nil 'local)
+ #'python-indent-post-self-insert-function 'append 'local)
(set (make-local-variable 'imenu-create-index-function)
#'python-imenu-create-index)
@@ -3550,24 +5083,35 @@ if that value is non-nil."
(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 "#"
- ,(lambda (arg)
- (python-nav-end-of-defun)) nil))
-
- (set (make-local-variable 'mode-require-final-newline) t)
+ (if (null eldoc-documentation-function)
+ ;; Emacs<25
+ (set (make-local-variable 'eldoc-documentation-function)
+ #'python-eldoc-function)
+ (add-function :before-until (local 'eldoc-documentation-function)
+ #'python-eldoc-function))
+
+ (add-to-list
+ 'hs-special-modes-alist
+ `(python-mode
+ "\\s-*\\(?:def\\|class\\)\\>"
+ ;; Use the empty string as end regexp so it doesn't default to
+ ;; "\\s)". This way parens at end of defun are properly hidden.
+ ""
+ "#"
+ python-hideshow-forward-sexp-function
+ nil))
(set (make-local-variable 'outline-regexp)
(python-rx (* space) block-start))
- (set (make-local-variable 'outline-heading-end-regexp) ":\\s-*\n")
+ (set (make-local-variable 'outline-heading-end-regexp) ":[^\n]*\n")
(set (make-local-variable 'outline-level)
#'(lambda ()
"`outline-level' function for Python mode."
(1+ (/ (current-indentation) python-indent-offset))))
+ (set (make-local-variable 'prettify-symbols-alist)
+ python--prettify-symbols-alist)
+
(python-skeleton-add-menu-items)
(make-local-variable 'python-shell-internal-buffer)
@@ -3579,7 +5123,6 @@ if that value is non-nil."
(provide 'python)
;; Local Variables:
-;; coding: utf-8
;; indent-tabs-mode: nil
;; End: