;;; python.el --- Python's flying circus support for Emacs -*- lexical-binding: t -*-
-;; Copyright (C) 2003-2015 Free Software Foundation, Inc.
+;; Copyright (C) 2003-2016 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.5
+;; Version: 0.25.2
+;; Package-Requires: ((emacs "24.1") (cl-lib "1.0"))
;; Maintainer: emacs-devel@gnu.org
;; Created: Jul 2010
;; Keywords: languages
(autoload 'help-function-arglist "help-fns")
;;;###autoload
-(add-to-list 'auto-mode-alist (cons (purecopy "\\.py\\'") 'python-mode))
+(add-to-list 'auto-mode-alist (cons (purecopy "\\.pyw?\\'") 'python-mode))
;;;###autoload
(add-to-list 'interpreter-mode-alist (cons (purecopy "python[0-9.]*") 'python-mode))
;; Some util commands
(define-key map "\C-c\C-v" 'python-check)
(define-key map "\C-c\C-f" 'python-eldoc-at-point)
+ (define-key map "\C-c\C-d" 'python-describe-at-point)
;; Utilities
(substitute-key-definition 'complete-symbol 'completion-at-point
map global-map)
(defconst python-rx-constituents
`((block-start . ,(rx symbol-start
(or "def" "class" "if" "elif" "else" "try"
- "except" "finally" "for" "while" "with")
+ "except" "finally" "for" "while" "with"
+ ;; Python 3.5+ PEP492
+ (and "async" (+ space)
+ (or "def" "for" "with")))
symbol-end))
(dedenter . ,(rx symbol-start
(or "elif" "else" "except" "finally")
symbol-end))
(decorator . ,(rx line-start (* space) ?@ (any letter ?_)
(* (any word ?_))))
- (defun . ,(rx symbol-start (or "def" "class") symbol-end))
+ (defun . ,(rx symbol-start
+ (or "def" "class"
+ ;; Python 3.5+ PEP492
+ (and "async" (+ space) "def"))
+ symbol-end))
(if-name-main . ,(rx line-start "if" (+ space) "__name__"
(+ space) "==" (+ space)
(any ?' ?\") "__main__" (any ?' ?\")
((nth 1 ppss) 'paren))))
(defsubst python-syntax-comment-or-string-p (&optional ppss)
- "Return non-nil if PPSS is inside 'comment or 'string."
+ "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 ")"))))
+ (eql (syntax-class (syntax-after (point)))
+ (syntax-class (string-to-syntax ")"))))
(define-obsolete-function-alias
'python-info-ppss-context #'python-syntax-context "24.3")
;; fontified like that in order to keep font-lock consistent between
;; Python versions.
"nonlocal"
+ ;; Python 3.5+ PEP492
+ (and "async" (+ space) (or "def" "for" "with"))
+ "await"
;; Extra:
"self")
symbol-end)
;; Builtin Exceptions
(,(rx symbol-start
(or
+ ;; Python 2 and 3:
"ArithmeticError" "AssertionError" "AttributeError" "BaseException"
- "DeprecationWarning" "EOFError" "EnvironmentError" "Exception"
- "FloatingPointError" "FutureWarning" "GeneratorExit" "IOError"
- "ImportError" "ImportWarning" "IndexError" "KeyError"
- "KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
- "NotImplementedError" "OSError" "OverflowError"
- "PendingDeprecationWarning" "ReferenceError" "RuntimeError"
- "RuntimeWarning" "StopIteration" "SyntaxError" "SyntaxWarning"
- "SystemError" "SystemExit" "TypeError" "UnboundLocalError"
- "UnicodeDecodeError" "UnicodeEncodeError" "UnicodeError"
- "UnicodeTranslateError" "UnicodeWarning" "UserWarning" "VMSError"
- "ValueError" "Warning" "WindowsError" "ZeroDivisionError"
+ "BufferError" "BytesWarning" "DeprecationWarning" "EOFError"
+ "EnvironmentError" "Exception" "FloatingPointError" "FutureWarning"
+ "GeneratorExit" "IOError" "ImportError" "ImportWarning"
+ "IndentationError" "IndexError" "KeyError" "KeyboardInterrupt"
+ "LookupError" "MemoryError" "NameError" "NotImplementedError"
+ "OSError" "OverflowError" "PendingDeprecationWarning"
+ "ReferenceError" "RuntimeError" "RuntimeWarning" "StopIteration"
+ "SyntaxError" "SyntaxWarning" "SystemError" "SystemExit" "TabError"
+ "TypeError" "UnboundLocalError" "UnicodeDecodeError"
+ "UnicodeEncodeError" "UnicodeError" "UnicodeTranslateError"
+ "UnicodeWarning" "UserWarning" "ValueError" "Warning"
+ "ZeroDivisionError"
;; Python 2:
"StandardError"
;; Python 3:
- "BufferError" "BytesWarning" "IndentationError" "ResourceWarning"
- "TabError")
+ "BlockingIOError" "BrokenPipeError" "ChildProcessError"
+ "ConnectionAbortedError" "ConnectionError" "ConnectionRefusedError"
+ "ConnectionResetError" "FileExistsError" "FileNotFoundError"
+ "InterruptedError" "IsADirectoryError" "NotADirectoryError"
+ "PermissionError" "ProcessLookupError" "RecursionError"
+ "ResourceWarning" "StopAsyncIteration" "TimeoutError"
+ ;; OS specific
+ "VMSError" "WindowsError"
+ )
symbol-end) . font-lock-type-face)
;; Builtins
(,(rx symbol-start
((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
(defcustom python-indent-guess-indent-offset-verbose t
"Non-nil means to emit a warning when indentation guessing fails."
+ :version "25.1"
:type 'boolean
:group 'python
:safe' booleanp)
(defun python-nav-if-name-main ()
"Move point at the beginning the __main__ block.
-When \"if __name__ == '__main__':\" is found returns its
+When \"if __name__ == \\='__main__\\=':\" is found returns its
position, else returns nil."
(interactive)
(let ((point (point))
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."
+ :version "25.1"
:type '(repeat string)
:group 'python)
(define-obsolete-variable-alias
'python-shell-virtualenv-path 'python-shell-virtualenv-root "25.1")
-(defcustom python-shell-setup-codes '(python-shell-completion-setup-code
- python-ffap-setup-code
- python-eldoc-setup-code)
+(defcustom python-shell-setup-codes nil
"List of code run by `python-shell-send-setup-codes'."
:type '(repeat symbol)
:group 'python)
(defun python-shell-calculate-pythonpath ()
"Calculate the PYTHONPATH using `python-shell-extra-pythonpaths'."
(let ((pythonpath
- (tramp-compat-split-string
- (or (getenv "PYTHONPATH") "") path-separator)))
+ (split-string
+ (or (getenv "PYTHONPATH") "") path-separator 'omit)))
(python-shell--add-to-path-with-priority
pythonpath python-shell-extra-pythonpaths)
(mapconcat 'identity pythonpath path-separator)))
(tramp-set-connection-property vec "remote-path" remote-path)
(tramp-set-remote-path vec))))
-(defvar python-shell--with-environment-wrapped nil)
+(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 (split-string (car env) "=" 'omit))
+ (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.
(declare (indent 0) (debug (body)))
(let ((vec (make-symbol "vec")))
`(progn
- (if python-shell--with-environment-wrapped
- ,(macroexp-progn body)
- (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))
- (python-shell--with-environment-wrapped t))
- (when (tramp-get-connection-process ,vec)
- ;; For already existing connections, modified env vars must
- ;; be re-set again. This is a normal thing to happen when
- ;; remote dir-locals are read from remote and *then*
- ;; processes should be started within the same connection
- ;; with env vars calculated from them.
- (python-shell-tramp-refresh-remote-path
- ,vec (python-shell-calculate-exec-path)))
- ,(macroexp-progn body))))))
+ (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.
"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
(let ((code-file (python-shell--save-temp-file code)))
;; Use `process-file' as it is remote-host friendly.
(process-file
- python-shell-interpreter
+ interpreter
code-file
'(t nil)
nil
- python-shell-interpreter-interactive-arg)
+ interpreter-arg)
;; Try to cleanup
(delete-file code-file)))
(buffer-string)))
(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."
+ :version "25.1"
+ :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':
\(Type \\[describe-mode] in the process buffer for a list of commands.)"
(when python-shell--parent-buffer
(python-util-clone-local-variables python-shell--parent-buffer))
+ (set (make-local-variable 'indent-tabs-mode) nil)
;; 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
(setq mode-line-process '(":%s"))
(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)
(make-local-variable 'python-shell-internal-last-output)
(when python-shell-font-lock-enable
(python-shell-font-lock-turn-on))
- (compilation-shell-minor-mode 1)
- (python-shell-accept-process-output
- (get-buffer-process (current-buffer))))
+ (compilation-shell-minor-mode 1))
(defun python-shell-make-comint (cmd proc-name &optional show internal)
"Create a Python shell comint 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
+ ;; these allows having 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)
(or (python-shell-get-process)
(if interactivep
(user-error
- "Start a Python process first with ‘%s’ or ‘%s’."
+ "Start a Python process first with `%s' or `%s'."
(substitute-command-keys "\\[run-python]")
;; Get the binding.
(key-description
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.
+ \"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* ((substring (buffer-substring-no-properties start end))
+ (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))))
- (toplevel-block-p (save-excursion
- (goto-char start)
- (or (zerop (line-number-at-pos start))
- (progn
- (python-util-forward-comment 1)
- (zerop (current-indentation)))))))
+ (- (line-number-at-pos start) 2) ?\n)))))
(with-temp-buffer
(python-mode)
- (if fillstr (insert fillstr))
+ (when fillstr
+ (insert fillstr))
(insert substring)
(goto-char (point-min))
- (when (not toplevel-block-p)
+ (when (not toplevel-p)
(insert "if True:")
(delete-region (point) (line-end-position)))
(when nomain
(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__':\".
+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
(defun python-shell-send-buffer (&optional send-main msg)
"Send the entire buffer to inferior Python process.
When optional argument SEND-MAIN is non-nil, allow execution of
-code inside blocks delimited by \"if __name__== '__main__':\".
+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
"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 (python-shell-get-process))
- (code (concat
- (mapconcat
- (lambda (elt)
- (cond ((stringp elt) elt)
- ((symbolp elt) (symbol-value elt))
- (t "")))
- python-shell-setup-codes
- "\n\n")
- "\n\nprint ('python.el: sent setup code')")))
- (python-shell-send-string code process)
- (python-shell-accept-process-output 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)
\f
;;; Shell completion
(defcustom python-shell-completion-setup-code
- "try:
- import readline
-except:
- def __PYTHON_EL_get_completions(text):
- return []
-else:
- def __PYTHON_EL_get_completions(text):
+ "
+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__)
- completions = []
+
is_ipython = ('__IPYTHON__' in builtins or
'__IPYTHON__active' in builtins)
splits = text.split()
is_module = splits and splits[0] in ('from', 'import')
- try:
- 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.
+
+ 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 not completer:
- # importing rlcompleter sets the completer, use it as a
- # last resort to avoid breaking customizations.
- import rlcompleter
- completer = readline.get_completer()
- i = 0
- while True:
- completion = completer(text, i)
- if not completion:
- break
- i += 1
- completions.append(completion)
- except:
- pass
- return completions"
+ if getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ completer.print_mode = False
+ i = 0
+ while True:
+ completion = completer(text, i)
+ if not completion:
+ break
+ i += 1
+ 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(__PYTHON_EL_get_completions('''%s'''))\n"
+ "';'.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."
(list "pypy")
"List of disabled interpreters.
When a match is found, native completion is disabled."
+ :version "25.1"
:type '(repeat string))
(defcustom python-shell-completion-native-enable t
"Enable readline based native completion."
+ :version "25.1"
:type 'boolean)
(defcustom python-shell-completion-native-output-timeout 5.0
"Time in seconds to wait for completion output before giving up."
+ :version "25.1"
:type 'float)
(defcustom python-shell-completion-native-try-output-timeout 1.0
"Time in seconds to wait for *trying* native completion output."
+ :version "25.1"
:type 'float)
(defvar python-shell-completion-native-redirect-buffer
python-shell-completion-native-try-output-timeout))
(python-shell-completion-native-get-completions
(get-buffer-process (current-buffer))
- nil "int")))
+ nil "")))
(defun python-shell-completion-native-setup ()
"Try to setup native completion, return non-nil on success."
(let ((process (python-shell-get-process)))
- (python-shell-send-string "
+ (with-current-buffer (process-buffer process)
+ (python-shell-send-string "
def __PYTHON_EL_native_completion_setup():
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)
+
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:
- # The first completion is always a dummy completion. This
- # ensures proper output for sole completions and a current
- # input safeguard when no completions are available.
+ # Set the first dummy completion.
self.last_completion = None
completion = '0__dummy_completion__'
else:
completion = self.completer(text, state - 1)
+
if not completion:
- if state == 1:
- # When no completions are available, two non-sharing
- # prefix strings are returned just to ensure output
+ if self.last_completion != '1__dummy_completion__':
+ # When no more completions are available, returning a
+ # dummy with non-sharing prefix allow ensuring output
# while preventing changes to current input.
+ # Coincidentally it's also the end of output.
completion = '1__dummy_completion__'
- elif self.last_completion != '~~~~__dummy_completion__':
- # This marks the end of output.
- completion = '~~~~__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
- return 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)
# 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')
- print ('python.el: readline is available')
- except IOError:
- print ('python.el: readline not available')
-__PYTHON_EL_native_completion_setup()"
- process)
- (python-shell-accept-process-output process)
- (when (save-excursion
- (re-search-backward
- (regexp-quote "python.el: readline is available") nil t 1))
- (python-shell-completion-native-try))))
+
+ print ('python.el: native completion setup loaded')
+ except:
+ print ('python.el: native completion setup failed')
+
+__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.
"Like `python-shell-completion-native-turn-on-maybe' but force messages."
(python-shell-completion-native-turn-on-maybe t))
-(add-hook 'inferior-python-mode-hook
+(add-hook 'python-shell-first-prompt-hook
#'python-shell-completion-native-turn-on-maybe-with-msg)
(defun python-shell-completion-native-toggle (&optional msg)
When IMPORT is non-nil takes precedence over INPUT for
completion."
(with-current-buffer (process-buffer process)
- (when (and python-shell-completion-native-enable
- (python-util-comint-last-prompt)
- (>= (point) (cdr (python-util-comint-last-prompt))))
- (let* ((input (or import input))
- (original-filter-fn (process-filter process))
- (redirect-buffer (get-buffer-create
- python-shell-completion-native-redirect-buffer))
- (separators (python-rx (or whitespace open-paren close-paren)))
- (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
- (delete-region (point-min) (point-max))
- ;; 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-verbose nil)
- (comint-redirect-perform-sanity-check nil)
- (comint-redirect-insert-matching-regexp nil)
- ;; Feed it some regex that will never match.
- (comint-redirect-finished-regexp "^\\'$")
- (comint-redirect-output-buffer redirect-buffer)
- (current-time (float-time)))
- ;; 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. Output is accepted
- ;; *very* quickly to keep the shell super-responsive.
- (while (and (not (re-search-backward "~~~~__dummy_completion__" nil t))
- (< (- (float-time) current-time)
- python-shell-completion-native-output-timeout))
- (accept-process-output process 0.01))
+ (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
- (cl-remove-if
- (lambda (c)
- (string-match "__dummy_completion__" c))
- (split-string
- (buffer-substring-no-properties
- (point-min) (point-max))
- separators t))
- :test #'string=)))
- (set-process-filter process original-filter-fn))))))
+ (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* ((prompt
- (let ((prompt-boundaries (python-util-comint-last-prompt)))
- (buffer-substring-no-properties
- (car prompt-boundaries) (cdr prompt-boundaries))))
- (completion-code
- ;; 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 (string-match
- (concat "^" python-shell-prompt-pdb-regexp) prompt))
- ;; Since there are no guarantees the user will remain
- ;; in the same context where completion code was sent
- ;; (e.g. user steps into a function), safeguard
- ;; resending completion setup continuously.
- (concat python-shell-completion-setup-code
- "\nprint (" python-shell-completion-string-code ")"))
- ((string-match
- python-shell--prompt-calculated-input-regexp prompt)
- python-shell-completion-string-code)
- (t nil)))
- (subject (or import input)))
- (and completion-code
- (> (length input) 0)
- (let ((completions
- (python-util-strip-string
- (python-shell-send-string-no-output
- (format completion-code subject) process))))
- (and (> (length completions) 2)
- (split-string completions
- "^'\\|^\"\\|;\\|'$\\|\"$" t)))))))
+ (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'.
(forward-char (length (match-string-no-properties 0)))
(point))))
(end (point))
+ (prompt-boundaries
+ (with-current-buffer (process-buffer process)
+ (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
- (if python-shell-completion-native-enable
- #'python-shell-completion-native-get-completions
- #'python-shell-completion-get-completions)))
+ (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
"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
- (concat (file-remote-p default-directory)
- 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))
(declare (indent 2))
(let* ((name (symbol-name name))
(function-name (intern (concat "python-skeleton--" name)))
- (msg (format
- "Add '%s' clause? " name)))
+ (msg (funcall (if (fboundp 'format-message) #'format-message #'format)
+ "Add `%s' clause? " name)))
(when (not skel)
(setq skel
`(< ,(format "%s:" name) \n \n
;;; 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."
: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)
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)
doc = doc.splitlines()[0]
except:
doc = ''
- 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)
`python-shell-get-process' returns is used."
(let ((process (or force-process (python-shell-get-process))))
(when process
- (let ((input (or force-input
- (python-eldoc--get-symbol-at-point))))
- (and input
- ;; Prevent resizing the echo area when iPython is
- ;; enabled. Bug#18794.
- (python-util-strip-string
- (python-shell-send-string-no-output
- (format python-eldoc-string-code input)
- 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)))))
+
+(defvar-local python-eldoc-get-doc t
+ "Non-nil means eldoc should fetch the documentation
+ automatically. Set to nil by `python-eldoc-function' if
+ `python-eldoc-function-timeout-permanent' is non-nil and
+ `python-eldoc-function' times out.")
+
+(defcustom python-eldoc-function-timeout 1
+ "Timeout for `python-eldoc-function' in seconds."
+ :group 'python
+ :type 'integer
+ :version "25.1")
+
+(defcustom python-eldoc-function-timeout-permanent t
+ "Non-nil means that when `python-eldoc-function' times out
+`python-eldoc-get-doc' will be set to nil"
+ :group 'python
+ :type 'boolean
+ :version "25.1")
(defun python-eldoc-function ()
"`eldoc-documentation-function' for Python.
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."
- (python-eldoc--get-doc-at-point))
+inferior Python process is updated properly.
+
+If `python-eldoc-function-timeout' seconds elapse before this
+function returns then if
+`python-eldoc-function-timeout-permanent' is non-nil
+`python-eldoc-get-doc' will be set to nil and eldoc will no
+longer return the documentation at the point automatically.
+
+Set `python-eldoc-get-doc' to t to reenable eldoc documentation
+fetching"
+ (when python-eldoc-get-doc
+ (with-timeout (python-eldoc-function-timeout
+ (if python-eldoc-function-timeout-permanent
+ (progn
+ (message "Eldoc echo-area display muted in this buffer, see `python-eldoc-function'")
+ (setq python-eldoc-get-doc nil))
+ (message "`python-eldoc-function' timed out, see `python-eldoc-function-timeout'")))
+ (python-eldoc--get-doc-at-point))))
(defun python-eldoc-at-point (symbol)
"Get help on SYMBOL using `help'.
nil nil symbol))))
(message (python-eldoc--get-doc-at-point symbol)))
+(defun python-describe-at-point (symbol process)
+ (interactive (list (python-info-current-symbol)
+ (python-shell-get-process)))
+ (comint-send-string process (concat "help('" symbol "')\n")))
+
\f
;;; Hideshow
(current-column))))
(^ '(- (1+ (current-indentation))))))
- (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))
+ (if (boundp 'eldoc-documentation-functions)
+ (add-hook 'eldoc-documentation-functions #'python-eldoc-function nil 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
"`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)
(provide 'python)
;; Local Variables:
-;; coding: utf-8
;; indent-tabs-mode: nil
;; End: