summaryrefslogtreecommitdiff
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el286
1 files changed, 170 insertions, 116 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 2d47cdc4068..091456aa89a 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -4,7 +4,7 @@
;; Author: Fabián E. Gallina <fgallina@gnu.org>
;; URL: https://github.com/fgallina/python.el
-;; Version: 0.26.1
+;; Version: 0.27
;; Package-Requires: ((emacs "24.1") (cl-lib "1.0"))
;; Maintainer: emacs-devel@gnu.org
;; Created: Jul 2010
@@ -29,7 +29,7 @@
;; Major mode for editing Python files with some fontification and
;; indentation bits extracted from original Dave Love's python.el
-;; found in GNU/Emacs.
+;; found in GNU Emacs.
;; Implements Syntax highlighting, Indentation, Movement, Shell
;; interaction, Shell completion, Shell virtualenv support, Shell
@@ -135,7 +135,7 @@
;; 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')
+;; installing pyreadline (URL `https://ipython.org/pyreadline.html')
;; should suffice. To troubleshoot why you are not getting any
;; completions, you can try the following in your Python shell:
@@ -247,13 +247,6 @@
;; I'd recommend the first one since you'll get the same behavior for
;; all modes out-of-the-box.
-;;; Installation:
-
-;; Add this to your .emacs:
-
-;; (add-to-list 'load-path "/folder/containing/file")
-;; (require 'python)
-
;;; TODO:
;;; Code:
@@ -261,7 +254,6 @@
(require 'ansi-color)
(require 'cl-lib)
(require 'comint)
-(require 'json)
(require 'tramp-sh)
;; Avoid compiler warnings
@@ -284,24 +276,6 @@
:link '(emacs-commentary-link "python"))
-;;; 24.x Compat
-
-
-(eval-and-compile
- (unless (fboundp 'prog-first-column)
- (defun prog-first-column ()
- 0))
- (unless (fboundp 'file-local-name)
- (defun file-local-name (file)
- "Return the local name component of FILE.
-It returns a file name which can be used directly as argument of
-`process-file', `start-file-process', or `shell-command'."
- (or (file-remote-p file 'localname) file))))
-
-;; In Emacs 24.3 and earlier, `define-derived-mode' does not define
-;; the hook variable, it only puts documentation on the symbol.
-(defvar inferior-python-mode-hook)
-
;;; Bindings
@@ -520,6 +494,52 @@ The type returned can be `comment', `string' or `paren'."
font-lock-string-face)
font-lock-comment-face))
+(defun python--f-string-p (ppss)
+ "Return non-nil if the pos where PPSS was found is inside an f-string."
+ (and (nth 3 ppss)
+ (let ((spos (1- (nth 8 ppss))))
+ (and (memq (char-after spos) '(?f ?F))
+ (or (< (point-min) spos)
+ (not (memq (char-syntax (char-before spos)) '(?w ?_))))))))
+
+(defun python--font-lock-f-strings (limit)
+ "Mark {...} holes as being code.
+Remove the (presumably `font-lock-string-face') `face' property from
+the {...} holes that appear within f-strings."
+ ;; FIXME: This will fail to properly highlight strings appearing
+ ;; within the {...} of an f-string.
+ ;; We could presumably fix it by running
+ ;; `font-lock-fontify-syntactically-region' (as is done in
+ ;; `sm-c--cpp-fontify-syntactically', for example) after removing
+ ;; the `face' property, but I'm not sure it's worth the effort and
+ ;; the risks.
+ (let ((ppss (syntax-ppss)))
+ (while
+ (progn
+ (while (and (not (python--f-string-p ppss))
+ (re-search-forward "\\<f['\"]" limit 'move))
+ (setq ppss (syntax-ppss)))
+ (< (point) limit))
+ (cl-assert (python--f-string-p ppss))
+ (let ((send (save-excursion
+ (goto-char (nth 8 ppss))
+ (condition-case nil
+ (progn (let ((forward-sexp-function nil))
+ (forward-sexp 1))
+ (min limit (1- (point))))
+ (scan-error limit)))))
+ (while (re-search-forward "{" send t)
+ (if (eq ?\{ (char-after))
+ (forward-char 1) ;Just skip over {{
+ (let ((beg (match-beginning 0))
+ (end (condition-case nil
+ (progn (up-list 1) (min send (point)))
+ (scan-error send))))
+ (goto-char end)
+ (put-text-property beg end 'face nil))))
+ (goto-char (min limit (1+ send)))
+ (setq ppss (syntax-ppss))))))
+
(defvar python-font-lock-keywords-level-1
`((,(rx symbol-start "def" (1+ space) (group (1+ (or word ?_))))
(1 font-lock-function-name-face))
@@ -586,7 +606,8 @@ This is the medium decoration level, including everything in
builtins.")
(defvar python-font-lock-keywords-maximum-decoration
- `(,@python-font-lock-keywords-level-2
+ `((python--font-lock-f-strings)
+ ,@python-font-lock-keywords-level-2
;; Constants
(,(rx symbol-start
(or
@@ -594,7 +615,8 @@ builtins.")
;; copyright, license, credits, quit and exit are added by the site
;; module and they are not intended to be used in programs
"copyright" "credits" "exit" "license" "quit")
- symbol-end) . font-lock-constant-face)
+ symbol-end)
+ . font-lock-constant-face)
;; Decorators.
(,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_))
(0+ "." (1+ (or word ?_)))))
@@ -628,12 +650,16 @@ builtins.")
;; OS specific
"VMSError" "WindowsError"
)
- symbol-end) . font-lock-type-face)
+ symbol-end)
+ . font-lock-type-face)
;; assignments
;; support for a = b = c = 5
(,(lambda (limit)
- (let ((re (python-rx (group (+ (any word ?. ?_)))
- (? ?\[ (+ (not (any ?\]))) ?\]) (* space)
+ (let ((re (python-rx (group symbol-name)
+ ;; subscript, like "[5]"
+ (? ?\[ (+ (not ?\])) ?\]) (* space)
+ ;; type hint, like ": int" or ": Mapping[int, str]"
+ (? ?: (* space) (+ not-simple-operator) (* space))
assignment-operator))
(res nil))
(while (and (setq res (re-search-forward re limit t))
@@ -643,9 +669,9 @@ builtins.")
(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)
+ (let ((re (python-rx (group symbol-name) (* space)
+ (* ?, (* space) symbol-name (* space))
+ ?, (* space) symbol-name (* space)
assignment-operator))
(res nil))
(while (and (setq res (re-search-forward re limit t))
@@ -1993,7 +2019,7 @@ position, else returns nil."
;; IPython prompts activated, this adds some safeguard for that.
"In : " "\\.\\.\\.: ")
"List of regular expressions matching input prompts."
- :type '(repeat string)
+ :type '(repeat regexp)
:version "24.4")
(defcustom python-shell-prompt-output-regexps
@@ -2001,28 +2027,28 @@ position, else returns nil."
"Out\\[[0-9]+\\]: " ; IPython
"Out :") ; ipdb safeguard
"List of regular expressions matching output prompts."
- :type '(repeat string)
+ :type '(repeat regexp)
:version "24.4")
(defcustom python-shell-prompt-regexp ">>> "
"Regular expression matching top level input prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string)
+ :type 'regexp)
(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)
+ :type 'regexp)
(defcustom python-shell-prompt-output-regexp ""
"Regular expression matching output prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string)
+ :type 'regexp)
(defcustom python-shell-prompt-pdb-regexp "[(<]*[Ii]?[Pp]db[>)]+ "
"Regular expression matching pdb input prompt of Python shell.
It should not contain a caret (^) at the beginning."
- :type 'string)
+ :type 'regexp)
(define-obsolete-variable-alias
'python-shell-enable-font-lock 'python-shell-font-lock-enable "25.1")
@@ -2076,7 +2102,7 @@ that they are prioritized when looking for 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
+here. Normally you won't 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"
@@ -2091,7 +2117,7 @@ executed through tramp connections."
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)
+ :type '(choice (const nil) directory)
:group 'python)
(defcustom python-shell-setup-codes nil
@@ -2111,7 +2137,7 @@ virtualenv."
"(" (group (1+ digit)) ")" (1+ (not (any "("))) "()")
1 2))
"`compilation-error-regexp-alist' for inferior Python."
- :type '(alist string)
+ :type '(alist regexp)
:group 'python)
(defmacro python-shell--add-to-path-with-priority (pathvar paths)
@@ -2276,6 +2302,18 @@ Do not set this variable directly, instead use
Do not set this variable directly, instead use
`python-shell-prompt-set-calculated-regexps'.")
+(defalias 'python--parse-json-array
+ (if (fboundp 'json-parse-string)
+ (lambda (string)
+ (json-parse-string string :array-type 'list))
+ (require 'json)
+ (defvar json-array-type)
+ (declare-function json-read-from-string "json" (string))
+ (lambda (string)
+ (let ((json-array-type 'list))
+ (json-read-from-string string))))
+ "Parse the JSON array in STRING into a Lisp list.")
+
(defun python-shell-prompt-detect ()
"Detect prompts for the current `python-shell-interpreter'.
When prompts can be retrieved successfully from the
@@ -2324,11 +2362,11 @@ detection and just returns nil."
(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) "[\"")
+ ;; Check if current line is a valid JSON array.
+ (and (string-prefix-p "[\"" line)
(ignore-errors
- ;; Return prompts as a list, not vector
- (append (json-read-from-string line) nil)))))
+ ;; Return prompts as a list.
+ (python--parse-json-array line)))))
;; 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
@@ -2798,6 +2836,7 @@ variable.
python-shell-comint-watch-for-first-prompt-output-filter
python-comint-postoutput-scroll-to-bottom
comint-watch-for-password-prompt))
+ (setq-local comint-highlight-input nil)
(set (make-local-variable 'compilation-error-regexp-alist)
python-shell-compilation-regexp-alist)
(add-hook 'completion-at-point-functions
@@ -2876,7 +2915,7 @@ process buffer for a list of commands.)"
(python-shell-make-comint
(or cmd (python-shell-calculate-command))
(python-shell-get-process-name dedicated) show)))
- (pop-to-buffer buffer)
+ (set-buffer buffer)
(get-buffer-process buffer)))
(defun run-python-internal ()
@@ -3080,7 +3119,7 @@ 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-buffer-substring (start end &optional nomain)
+(defun python-shell-buffer-substring (start end &optional nomain no-cookie)
"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
@@ -3094,9 +3133,16 @@ the python shell:
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.
+ ;; If we're at the start of the expression, and
+ ;; there's just blank space ahead of it, then expand
+ ;; the region to include the start of the line.
+ ;; This makes things work better with the rest of
+ ;; the data we're sending over.
(goto-char start)
- (line-beginning-position)))
+ (if (string-blank-p
+ (buffer-substring (line-beginning-position) start))
+ (line-beginning-position)
+ start)))
(substring (buffer-substring-no-properties start end))
(starts-at-point-min-p (save-restriction
(widen)
@@ -3106,12 +3152,13 @@ the python shell:
(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)))))
+ (fillstr (and (not no-cookie)
+ (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
@@ -3150,7 +3197,8 @@ the python shell:
(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)
+(defun python-shell-send-region (start end &optional send-main msg
+ no-cookie)
"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__\\=':\".
@@ -3160,7 +3208,8 @@ 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)))
+ (let* ((string (python-shell-buffer-substring start end (not send-main)
+ no-cookie))
(process (python-shell-get-process-or-error msg))
(original-string (buffer-substring-no-properties start end))
(_ (string-match "\\`\n*\\(.*\\)" original-string)))
@@ -3184,7 +3233,7 @@ interactively."
(python-shell-send-region
(save-excursion (python-nav-beginning-of-statement))
(save-excursion (python-nav-end-of-statement))
- send-main msg)))
+ send-main msg t)))
(defun python-shell-send-buffer (&optional send-main msg)
"Send the entire buffer to inferior Python process.
@@ -3206,27 +3255,29 @@ 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
- (end-of-line 1)
- (while (and (or (python-nav-beginning-of-defun)
- (beginning-of-line 1))
- (> (current-indentation) 0)))
- (when (not arg)
- (while (and
- (eq (forward-line -1) 0)
- (if (looking-at (python-rx decorator))
- t
- (forward-line 1)
- nil))))
- (point-marker))
- (progn
- (or (python-nav-end-of-defun)
- (end-of-line 1))
- (point-marker))
- nil ;; noop
- msg)))
+ (let ((starting-pos (point)))
+ (save-excursion
+ (python-shell-send-region
+ (progn
+ (end-of-line 1)
+ (while (and (or (python-nav-beginning-of-defun)
+ (beginning-of-line 1))
+ (> (current-indentation) 0)))
+ (when (not arg)
+ (while (and
+ (eq (forward-line -1) 0)
+ (if (looking-at (python-rx decorator))
+ t
+ (forward-line 1)
+ nil))))
+ (point-marker))
+ (progn
+ (goto-char starting-pos)
+ (or (python-nav-end-of-defun)
+ (end-of-line 1))
+ (point-marker))
+ nil ;; noop
+ msg))))
(defun python-shell-send-file (file-name &optional process temp-file-name
delete msg)
@@ -3787,7 +3838,7 @@ the top stack frame has been reached.
Filename is expected in the first parenthesized expression.
Line number is expected in the second parenthesized expression."
- :type 'string
+ :type 'regexp
:version "27.1"
:safe 'stringp)
@@ -3802,7 +3853,7 @@ was `continue'. This behavior slightly differentiates the `continue' command
from the `exit' command listed in `python-pdbtrack-exit-command'.
See `python-pdbtrack-activate' for pdbtracking session overview."
- :type 'list
+ :type '(repeat string)
:version "27.1")
(defcustom python-pdbtrack-exit-command '("q" "quit" "exit")
@@ -3811,7 +3862,7 @@ After one of this commands is sent to pdb, pdbtracking session is
considered over.
See `python-pdbtrack-activate' for pdbtracking session overview."
- :type 'list
+ :type '(repeat string)
:version "27.1")
(defcustom python-pdbtrack-kill-buffers t
@@ -3954,8 +4005,8 @@ Argument OUTPUT is a string with the output from the comint process."
"Setup pdb tracking in current buffer."
(make-local-variable 'python-pdbtrack-buffers-to-kill)
(make-local-variable 'python-pdbtrack-tracked-buffer)
- (add-to-list (make-local-variable 'comint-input-filter-functions)
- #'python-pdbtrack-comint-input-filter-function)
+ (add-hook 'comint-input-filter-functions
+ #'python-pdbtrack-comint-input-filter-function nil t)
(add-to-list (make-local-variable 'comint-output-filter-functions)
#'python-pdbtrack-comint-output-filter-function)
(add-function :before (process-sentinel (get-buffer-process (current-buffer)))
@@ -4136,7 +4187,7 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
(goto-char (point-max)))
(point-marker)))
(multi-line-p
- ;; Docstring styles may vary for oneliners and multi-liners.
+ ;; Docstring styles may vary for one-liners and multi-liners.
(> (count-matches "\n" str-start-pos str-end-pos) 0))
(delimiters-style
(pcase python-fill-docstring-style
@@ -4562,7 +4613,7 @@ returns will be used. If not FORCE-PROCESS is passed what
:type 'boolean
:version "25.1")
-(defun python-eldoc-function ()
+(defun python-eldoc-function (&rest _ignored)
"`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
@@ -4591,9 +4642,7 @@ Interactively, prompt for symbol."
(interactive
(let ((symbol (python-eldoc--get-symbol-at-point))
(enable-recursive-minibuffers t))
- (list (read-string (if symbol
- (format "Describe symbol (default %s): " symbol)
- "Describe symbol: ")
+ (list (read-string (format-prompt "Describe symbol" symbol)
nil nil symbol))))
(message (python-eldoc--get-doc-at-point symbol)))
@@ -5137,21 +5186,22 @@ point's current `syntax-ppss'."
(>=
2
(let (last-backward-sexp-point)
- (while (save-excursion
- (python-nav-backward-sexp)
- (setq backward-sexp-point (point))
- (and (= indentation (current-indentation))
- ;; Make sure we're always moving point.
- ;; If we get stuck in the same position
- ;; on consecutive loop iterations,
- ;; bail out.
- (prog1 (not (eql last-backward-sexp-point
- backward-sexp-point))
- (setq last-backward-sexp-point
- backward-sexp-point))
- (looking-at-p
- (concat "[uU]?[rR]?"
- (python-rx string-delimiter)))))
+ (while (and (<= counter 2)
+ (save-excursion
+ (python-nav-backward-sexp)
+ (setq backward-sexp-point (point))
+ (and (= indentation (current-indentation))
+ ;; Make sure we're always moving point.
+ ;; If we get stuck in the same position
+ ;; on consecutive loop iterations,
+ ;; bail out.
+ (prog1 (not (eql last-backward-sexp-point
+ backward-sexp-point))
+ (setq last-backward-sexp-point
+ backward-sexp-point))
+ (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))
@@ -5343,7 +5393,7 @@ To use `flake8' you would set this to (\"flake8\" \"-\")."
:group 'python-flymake
:type '(repeat string))
-;; The default regexp accomodates for older pyflakes, which did not
+;; The default regexp accommodates for older pyflakes, which did not
;; report the column number, and at the same time it's compatible with
;; flake8 output, although it may be redefined to explicitly match the
;; TYPE
@@ -5542,12 +5592,16 @@ REPORT-FN is Flymake's callback function."
(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))
+ (with-no-warnings
+ ;; suppress warnings about eldoc-documentation-function being obsolete
+ (if (null eldoc-documentation-function)
+ ;; Emacs<25
+ (set (make-local-variable 'eldoc-documentation-function)
+ #'python-eldoc-function)
+ (if (boundp 'eldoc-documentation-functions)
+ (add-hook 'eldoc-documentation-functions #'python-eldoc-function nil t)
+ (add-function :before-until (local 'eldoc-documentation-function)
+ #'python-eldoc-function))))
(add-to-list
'hs-special-modes-alist