aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el436
1 files changed, 363 insertions, 73 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index b498baec608..801432cd188 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -5,7 +5,7 @@
5;; Author: Fabián E. Gallina <fgallina@gnu.org> 5;; Author: Fabián E. Gallina <fgallina@gnu.org>
6;; URL: https://github.com/fgallina/python.el 6;; URL: https://github.com/fgallina/python.el
7;; Version: 0.28 7;; Version: 0.28
8;; Package-Requires: ((emacs "24.4") (cl-lib "1.0")) 8;; Package-Requires: ((emacs "24.4") (compat "28.1.2.1") (seq "2.23"))
9;; Maintainer: emacs-devel@gnu.org 9;; Maintainer: emacs-devel@gnu.org
10;; Created: Jul 2010 10;; Created: Jul 2010
11;; Keywords: languages 11;; Keywords: languages
@@ -34,7 +34,8 @@
34;; Implements Syntax highlighting, Indentation, Movement, Shell 34;; Implements Syntax highlighting, Indentation, Movement, Shell
35;; interaction, Shell completion, Shell virtualenv support, Shell 35;; interaction, Shell completion, Shell virtualenv support, Shell
36;; package support, Shell syntax highlighting, Pdb tracking, Symbol 36;; package support, Shell syntax highlighting, Pdb tracking, Symbol
37;; completion, Skeletons, FFAP, Code Check, ElDoc, Imenu. 37;; completion, Skeletons, FFAP, Code Check, ElDoc, Imenu, Flymake,
38;; Import management.
38 39
39;; Syntax highlighting: Fontification of code is provided and supports 40;; Syntax highlighting: Fontification of code is provided and supports
40;; python's triple quoted strings properly. 41;; python's triple quoted strings properly.
@@ -69,7 +70,7 @@
69;; variables. This example enables IPython globally: 70;; variables. This example enables IPython globally:
70 71
71;; (setq python-shell-interpreter "ipython" 72;; (setq python-shell-interpreter "ipython"
72;; python-shell-interpreter-args "-i") 73;; python-shell-interpreter-args "--simple-prompt")
73 74
74;; Using the "console" subcommand to start IPython in server-client 75;; Using the "console" subcommand to start IPython in server-client
75;; mode is known to fail intermittently due a bug on IPython itself 76;; mode is known to fail intermittently due a bug on IPython itself
@@ -240,6 +241,21 @@
240;; I'd recommend the first one since you'll get the same behavior for 241;; I'd recommend the first one since you'll get the same behavior for
241;; all modes out-of-the-box. 242;; all modes out-of-the-box.
242 243
244;; Flymake: A Flymake backend, using the pyflakes program by default,
245;; is provided. You can also use flake8 or pylint by customizing
246;; `python-flymake-command'.
247
248;; Import management: The commands `python-sort-imports',
249;; `python-add-import', `python-remove-import', and
250;; `python-fix-imports' automate the editing of import statements at
251;; the top of the buffer, which tend to be a tedious task in larger
252;; projects. These commands require that the isort library is
253;; available to the interpreter pointed at by `python-interpreter'.
254;; The last command also requires pyflakes. These dependencies can be
255;; installed, among other methods, with the following command:
256;;
257;; pip install isort pyflakes
258
243;;; Code: 259;;; Code:
244 260
245(require 'ansi-color) 261(require 'ansi-color)
@@ -248,6 +264,9 @@
248(eval-when-compile (require 'subr-x)) ;For `string-empty-p' and `string-join'. 264(eval-when-compile (require 'subr-x)) ;For `string-empty-p' and `string-join'.
249(require 'treesit) 265(require 'treesit)
250(require 'pcase) 266(require 'pcase)
267(require 'compat nil 'noerror)
268(require 'project nil 'noerror)
269(require 'seq)
251 270
252;; Avoid compiler warnings 271;; Avoid compiler warnings
253(defvar compilation-error-regexp-alist) 272(defvar compilation-error-regexp-alist)
@@ -273,6 +292,14 @@ Currently `python-mode' uses tree-sitter for font-locking, imenu,
273and movement functions." 292and movement functions."
274 :type 'boolean) 293 :type 'boolean)
275 294
295(defcustom python-interpreter "python"
296 "Python interpreter for noninteractive use.
297To customize the Python shell, modify `python-shell-interpreter'
298instead."
299 :version "29.1"
300 :type 'string)
301
302
276 303
277;;; Bindings 304;;; Bindings
278 305
@@ -282,6 +309,7 @@ and movement functions."
282 (define-key map [remap backward-sentence] #'python-nav-backward-block) 309 (define-key map [remap backward-sentence] #'python-nav-backward-block)
283 (define-key map [remap forward-sentence] #'python-nav-forward-block) 310 (define-key map [remap forward-sentence] #'python-nav-forward-block)
284 (define-key map [remap backward-up-list] #'python-nav-backward-up-list) 311 (define-key map [remap backward-up-list] #'python-nav-backward-up-list)
312 (define-key map [remap up-list] #'python-nav-up-list)
285 (define-key map [remap mark-defun] #'python-mark-defun) 313 (define-key map [remap mark-defun] #'python-mark-defun)
286 (define-key map "\C-c\C-j" #'imenu) 314 (define-key map "\C-c\C-j" #'imenu)
287 ;; Indent specific 315 ;; Indent specific
@@ -310,6 +338,11 @@ and movement functions."
310 (define-key map "\C-c\C-v" #'python-check) 338 (define-key map "\C-c\C-v" #'python-check)
311 (define-key map "\C-c\C-f" #'python-eldoc-at-point) 339 (define-key map "\C-c\C-f" #'python-eldoc-at-point)
312 (define-key map "\C-c\C-d" #'python-describe-at-point) 340 (define-key map "\C-c\C-d" #'python-describe-at-point)
341 ;; Import management
342 (define-key map "\C-c\C-ia" #'python-add-import)
343 (define-key map "\C-c\C-if" #'python-fix-imports)
344 (define-key map "\C-c\C-ir" #'python-remove-import)
345 (define-key map "\C-c\C-is" #'python-sort-imports)
313 ;; Utilities 346 ;; Utilities
314 (substitute-key-definition #'complete-symbol #'completion-at-point 347 (substitute-key-definition #'complete-symbol #'completion-at-point
315 map global-map) 348 map global-map)
@@ -355,7 +388,17 @@ and movement functions."
355 ["Help on symbol" python-eldoc-at-point 388 ["Help on symbol" python-eldoc-at-point
356 :help "Get help on symbol at point"] 389 :help "Get help on symbol at point"]
357 ["Complete symbol" completion-at-point 390 ["Complete symbol" completion-at-point
358 :help "Complete symbol before point"])) 391 :help "Complete symbol before point"]
392 "-----"
393 ["Add import" python-add-import
394 :help "Add an import statement to the top of this buffer"]
395 ["Remove import" python-remove-import
396 :help "Remove an import statement from the top of this buffer"]
397 ["Sort imports" python-sort-imports
398 :help "Sort the import statements at the top of this buffer"]
399 ["Fix imports" python-fix-imports
400 :help "Add missing imports and remove unused ones from the current buffer"]
401 ))
359 map) 402 map)
360 "Keymap for `python-mode'.") 403 "Keymap for `python-mode'.")
361 404
@@ -495,16 +538,6 @@ The type returned can be `comment', `string' or `paren'."
495 (eql (syntax-class (syntax-after (point))) 538 (eql (syntax-class (syntax-after (point)))
496 (syntax-class (string-to-syntax ")")))) 539 (syntax-class (string-to-syntax ")"))))
497 540
498(define-obsolete-function-alias
499 'python-info-ppss-context #'python-syntax-context "24.3")
500
501(define-obsolete-function-alias
502 'python-info-ppss-context-type #'python-syntax-context-type "24.3")
503
504(define-obsolete-function-alias
505 'python-info-ppss-comment-or-string-p
506 #'python-syntax-comment-or-string-p "24.3")
507
508(defun python-font-lock-syntactic-face-function (state) 541(defun python-font-lock-syntactic-face-function (state)
509 "Return syntactic face given STATE." 542 "Return syntactic face given STATE."
510 (if (nth 3 state) 543 (if (nth 3 state)
@@ -513,11 +546,22 @@ The type returned can be `comment', `string' or `paren'."
513 font-lock-string-face) 546 font-lock-string-face)
514 font-lock-comment-face)) 547 font-lock-comment-face))
515 548
549(defconst python--f-string-start-regexp
550 (rx bow
551 (or "f" "F" "fr" "Fr" "fR" "FR" "rf" "rF" "Rf" "RF")
552 (or "\"" "\"\"\"" "'" "'''"))
553 "A regular expression matching the beginning of an f-string.
554
555See URL `https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals'.")
556
516(defun python--f-string-p (ppss) 557(defun python--f-string-p (ppss)
517 "Return non-nil if the pos where PPSS was found is inside an f-string." 558 "Return non-nil if the pos where PPSS was found is inside an f-string."
518 (and (nth 3 ppss) 559 (and (nth 3 ppss)
519 (let ((spos (1- (nth 8 ppss)))) 560 (let* ((spos (1- (nth 8 ppss)))
520 (and (memq (char-after spos) '(?f ?F)) 561 (before-quote
562 (buffer-substring-no-properties (max (- spos 4) (point-min))
563 (min (+ spos 2) (point-max)))))
564 (and (string-match-p python--f-string-start-regexp before-quote)
521 (or (< (point-min) spos) 565 (or (< (point-min) spos)
522 (not (memq (char-syntax (char-before spos)) '(?w ?_)))))))) 566 (not (memq (char-syntax (char-before spos)) '(?w ?_))))))))
523 567
@@ -536,7 +580,7 @@ the {...} holes that appear within f-strings."
536 (while 580 (while
537 (progn 581 (progn
538 (while (and (not (python--f-string-p ppss)) 582 (while (and (not (python--f-string-p ppss))
539 (re-search-forward "\\<f['\"]" limit 'move)) 583 (re-search-forward python--f-string-start-regexp limit 'move))
540 (setq ppss (syntax-ppss))) 584 (setq ppss (syntax-ppss)))
541 (< (point) limit)) 585 (< (point) limit))
542 (cl-assert (python--f-string-p ppss)) 586 (cl-assert (python--f-string-p ppss))
@@ -1050,17 +1094,11 @@ Do not fontify the initial f for f-strings."
1050 1094
1051;;; Indentation 1095;;; Indentation
1052 1096
1053(define-obsolete-variable-alias
1054 'python-indent 'python-indent-offset "24.3")
1055
1056(defcustom python-indent-offset 4 1097(defcustom python-indent-offset 4
1057 "Default indentation offset for Python." 1098 "Default indentation offset for Python."
1058 :type 'integer 1099 :type 'integer
1059 :safe 'integerp) 1100 :safe 'integerp)
1060 1101
1061(define-obsolete-variable-alias
1062 'python-guess-indent 'python-indent-guess-indent-offset "24.3")
1063
1064(defcustom python-indent-guess-indent-offset t 1102(defcustom python-indent-guess-indent-offset t
1065 "Non-nil tells Python mode to guess `python-indent-offset' value." 1103 "Non-nil tells Python mode to guess `python-indent-offset' value."
1066 :type 'boolean 1104 :type 'boolean
@@ -2452,6 +2490,16 @@ virtualenv."
2452 "`compilation-error-regexp-alist' for inferior Python." 2490 "`compilation-error-regexp-alist' for inferior Python."
2453 :type '(alist regexp)) 2491 :type '(alist regexp))
2454 2492
2493(defcustom python-shell-dedicated nil
2494 "Whether to make Python shells dedicated by default.
2495This option influences `run-python' when called without a prefix
2496argument. If `buffer' or `project', create a Python shell
2497dedicated to the current buffer or its project (if one is found)."
2498 :version "29.1"
2499 :type '(choice (const :tag "To buffer" buffer)
2500 (const :tag "To project" project)
2501 (const :tag "Not dedicated" nil)))
2502
2455(defvar python-shell-output-filter-in-progress nil) 2503(defvar python-shell-output-filter-in-progress nil)
2456(defvar python-shell-output-filter-buffer nil) 2504(defvar python-shell-output-filter-buffer nil)
2457 2505
@@ -2814,12 +2862,19 @@ from `python-shell-prompt-regexp',
2814 2862
2815(defun python-shell-get-process-name (dedicated) 2863(defun python-shell-get-process-name (dedicated)
2816 "Calculate the appropriate process name for inferior Python process. 2864 "Calculate the appropriate process name for inferior Python process.
2817If DEDICATED is t returns a string with the form 2865If DEDICATED is nil, this is simply `python-shell-buffer-name'.
2818`python-shell-buffer-name'[`buffer-name'] else returns the value 2866If DEDICATED is `buffer' or `project', append the current buffer
2819of `python-shell-buffer-name'." 2867name respectively the current project name."
2820 (if dedicated 2868 (pcase dedicated
2821 (format "%s[%s]" python-shell-buffer-name (buffer-name)) 2869 ('nil python-shell-buffer-name)
2822 python-shell-buffer-name)) 2870 ('project
2871 (if-let ((proj (and (featurep 'project)
2872 (project-current))))
2873 (format "%s[%s]" python-shell-buffer-name (file-name-nondirectory
2874 (directory-file-name
2875 (project-root proj))))
2876 python-shell-buffer-name))
2877 (_ (format "%s[%s]" python-shell-buffer-name (buffer-name)))))
2823 2878
2824(defun python-shell-internal-get-process-name () 2879(defun python-shell-internal-get-process-name ()
2825 "Calculate the appropriate process name for Internal Python process. 2880 "Calculate the appropriate process name for Internal Python process.
@@ -3182,8 +3237,8 @@ interpreter is run. Variables
3182`python-shell-font-lock-enable', 3237`python-shell-font-lock-enable',
3183`python-shell-completion-setup-code', 3238`python-shell-completion-setup-code',
3184`python-shell-completion-string-code', 3239`python-shell-completion-string-code',
3185`python-eldoc-setup-code', `python-eldoc-string-code', 3240`python-eldoc-setup-code',
3186`python-ffap-setup-code' and `python-ffap-string-code' can 3241`python-ffap-setup-code' can
3187customize this mode for different Python interpreters. 3242customize this mode for different Python interpreters.
3188 3243
3189This mode resets `comint-output-filter-functions' locally, so you 3244This mode resets `comint-output-filter-functions' locally, so you
@@ -3277,8 +3332,8 @@ killed."
3277Argument CMD defaults to `python-shell-calculate-command' return 3332Argument CMD defaults to `python-shell-calculate-command' return
3278value. When called interactively with `prefix-arg', it allows 3333value. When called interactively with `prefix-arg', it allows
3279the user to edit such value and choose whether the interpreter 3334the user to edit such value and choose whether the interpreter
3280should be DEDICATED for the current buffer. When numeric prefix 3335should be DEDICATED to the current buffer or project. When
3281arg is other than 0 or 4 do not SHOW. 3336numeric prefix arg is other than 0 or 4 do not SHOW.
3282 3337
3283For a given buffer and same values of DEDICATED, if a process is 3338For a given buffer and same values of DEDICATED, if a process is
3284already running for it, it will do nothing. This means that if 3339already running for it, it will do nothing. This means that if
@@ -3292,15 +3347,47 @@ process buffer for a list of commands.)"
3292 (if current-prefix-arg 3347 (if current-prefix-arg
3293 (list 3348 (list
3294 (read-shell-command "Run Python: " (python-shell-calculate-command)) 3349 (read-shell-command "Run Python: " (python-shell-calculate-command))
3295 (y-or-n-p "Make dedicated process? ") 3350 (alist-get (car (read-multiple-choice "Make dedicated process?"
3351 '((?b "to buffer")
3352 (?p "to project")
3353 (?n "no"))))
3354 '((?b . buffer) (?p . project)))
3296 (= (prefix-numeric-value current-prefix-arg) 4)) 3355 (= (prefix-numeric-value current-prefix-arg) 4))
3297 (list (python-shell-calculate-command) nil t))) 3356 (list (python-shell-calculate-command)
3298 (let ((buffer 3357 python-shell-dedicated
3299 (python-shell-make-comint 3358 t)))
3300 (or cmd (python-shell-calculate-command)) 3359 (let* ((project (and (eq 'project dedicated)
3301 (python-shell-get-process-name dedicated) show))) 3360 (featurep 'project)
3361 (project-current t)))
3362 (default-directory (if project
3363 (project-root project)
3364 default-directory))
3365 (buffer (python-shell-make-comint
3366 (or cmd (python-shell-calculate-command))
3367 (python-shell-get-process-name dedicated)
3368 show)))
3302 (get-buffer-process buffer))) 3369 (get-buffer-process buffer)))
3303 3370
3371(defun python-shell-restart (&optional show)
3372 "Restart the Python shell.
3373Optional argument SHOW (interactively, the prefix argument), if
3374non-nil, means also display the Python shell buffer."
3375 (interactive "P")
3376 (with-current-buffer
3377 (or (and (derived-mode-p 'inferior-python-mode)
3378 (current-buffer))
3379 (seq-some (lambda (dedicated)
3380 (get-buffer (format "*%s*" (python-shell-get-process-name
3381 dedicated))))
3382 '(buffer project nil))
3383 (user-error "No Python shell"))
3384 (when-let ((proc (get-buffer-process (current-buffer))))
3385 (kill-process proc)
3386 (while (accept-process-output proc)))
3387 (python-shell-make-comint (python-shell-calculate-command)
3388 (string-trim (buffer-name) "\\*" "\\*")
3389 show)))
3390
3304(defun run-python-internal () 3391(defun run-python-internal ()
3305 "Run an inferior Internal Python process. 3392 "Run an inferior Internal Python process.
3306Input and output via buffer named after 3393Input and output via buffer named after
@@ -3328,15 +3415,13 @@ startup."
3328If current buffer is in `inferior-python-mode', return it." 3415If current buffer is in `inferior-python-mode', return it."
3329 (if (derived-mode-p 'inferior-python-mode) 3416 (if (derived-mode-p 'inferior-python-mode)
3330 (current-buffer) 3417 (current-buffer)
3331 (let* ((dedicated-proc-name (python-shell-get-process-name t)) 3418 (seq-some
3332 (dedicated-proc-buffer-name (format "*%s*" dedicated-proc-name)) 3419 (lambda (dedicated)
3333 (global-proc-name (python-shell-get-process-name nil)) 3420 (let* ((proc-name (python-shell-get-process-name dedicated))
3334 (global-proc-buffer-name (format "*%s*" global-proc-name)) 3421 (buffer-name (format "*%s*" proc-name)))
3335 (dedicated-running (comint-check-proc dedicated-proc-buffer-name)) 3422 (when (comint-check-proc buffer-name)
3336 (global-running (comint-check-proc global-proc-buffer-name))) 3423 buffer-name)))
3337 ;; Always prefer dedicated 3424 '(buffer project nil))))
3338 (or (and dedicated-running dedicated-proc-buffer-name)
3339 (and global-running global-proc-buffer-name)))))
3340 3425
3341(defun python-shell-get-process () 3426(defun python-shell-get-process ()
3342 "Return inferior Python process for current buffer." 3427 "Return inferior Python process for current buffer."
@@ -3377,17 +3462,11 @@ be asked for their values."
3377 "Instead call `python-shell-get-process' and create one if returns nil." 3462 "Instead call `python-shell-get-process' and create one if returns nil."
3378 "25.1") 3463 "25.1")
3379 3464
3380(define-obsolete-variable-alias
3381 'python-buffer 'python-shell-internal-buffer "24.3")
3382
3383(defvar python-shell-internal-buffer nil 3465(defvar python-shell-internal-buffer nil
3384 "Current internal shell buffer for the current buffer. 3466 "Current internal shell buffer for the current buffer.
3385This is really not necessary at all for the code to work but it's 3467This is really not necessary at all for the code to work but it's
3386there for compatibility with CEDET.") 3468there for compatibility with CEDET.")
3387 3469
3388(define-obsolete-variable-alias
3389 'python-preoutput-result 'python-shell-internal-last-output "24.3")
3390
3391(defvar python-shell-internal-last-output nil 3470(defvar python-shell-internal-last-output nil
3392 "Last output captured by the internal shell. 3471 "Last output captured by the internal shell.
3393This is really not necessary at all for the code to work but it's 3472This is really not necessary at all for the code to work but it's
@@ -3400,9 +3479,6 @@ there for compatibility with CEDET.")
3400 (get-process proc-name) 3479 (get-process proc-name)
3401 (run-python-internal)))) 3480 (run-python-internal))))
3402 3481
3403(define-obsolete-function-alias
3404 'python-proc #'python-shell-internal-get-or-create-process "24.3")
3405
3406(defun python-shell--save-temp-file (string) 3482(defun python-shell--save-temp-file (string)
3407 (let* ((temporary-file-directory 3483 (let* ((temporary-file-directory
3408 (if (file-remote-p default-directory) 3484 (if (file-remote-p default-directory)
@@ -3519,12 +3595,6 @@ Returns the output. See `python-shell-send-string-no-output'."
3519 (replace-regexp-in-string "_emacs_out +" "" string) 3595 (replace-regexp-in-string "_emacs_out +" "" string)
3520 (python-shell-internal-get-or-create-process)))) 3596 (python-shell-internal-get-or-create-process))))
3521 3597
3522(define-obsolete-function-alias
3523 'python-send-receive #'python-shell-internal-send-string "24.3")
3524
3525(define-obsolete-function-alias
3526 'python-send-string #'python-shell-internal-send-string "24.3")
3527
3528(defun python-shell-buffer-substring (start end &optional nomain no-cookie) 3598(defun python-shell-buffer-substring (start end &optional nomain no-cookie)
3529 "Send buffer substring from START to END formatted for shell. 3599 "Send buffer substring from START to END formatted for shell.
3530This is a wrapper over `buffer-substring' that takes care of 3600This is a wrapper over `buffer-substring' that takes care of
@@ -4690,9 +4760,6 @@ JUSTIFY should be used (if applicable) as in `fill-paragraph'."
4690 4760
4691;;; Skeletons 4761;;; Skeletons
4692 4762
4693(define-obsolete-variable-alias
4694 'python-use-skeletons 'python-skeleton-autoinsert "24.3")
4695
4696(defcustom python-skeleton-autoinsert nil 4763(defcustom python-skeleton-autoinsert nil
4697 "Non-nil means template skeletons will be automagically inserted. 4764 "Non-nil means template skeletons will be automagically inserted.
4698This happens when pressing \"if<SPACE>\", for example, to prompt for 4765This happens when pressing \"if<SPACE>\", for example, to prompt for
@@ -5696,11 +5763,11 @@ operator."
5696 "Check if point is at `beginning-of-defun' using SYNTAX-PPSS. 5763 "Check if point is at `beginning-of-defun' using SYNTAX-PPSS.
5697When CHECK-STATEMENT is non-nil, the current statement is checked 5764When CHECK-STATEMENT is non-nil, the current statement is checked
5698instead of the current physical line." 5765instead of the current physical line."
5699 (and (not (python-syntax-context-type (or syntax-ppss (syntax-ppss)))) 5766 (save-excursion
5700 (save-excursion 5767 (when check-statement
5701 (when check-statement 5768 (python-nav-beginning-of-statement))
5702 (python-nav-beginning-of-statement)) 5769 (beginning-of-line 1)
5703 (beginning-of-line 1) 5770 (and (not (python-syntax-context-type (or syntax-ppss (syntax-ppss))))
5704 (looking-at python-nav-beginning-of-defun-regexp)))) 5771 (looking-at python-nav-beginning-of-defun-regexp))))
5705 5772
5706(defun python-info-looking-at-beginning-of-block () 5773(defun python-info-looking-at-beginning-of-block ()
@@ -6078,6 +6145,225 @@ REPORT-FN is Flymake's callback function."
6078 (process-send-eof python--flymake-proc)))) 6145 (process-send-eof python--flymake-proc))))
6079 6146
6080 6147
6148;;; Import management
6149(defconst python--list-imports "\
6150from isort import find_imports_in_stream, find_imports_in_paths
6151from sys import argv, stdin
6152
6153query, files, result = argv[1] or None, argv[2:], {}
6154
6155if files:
6156 imports = find_imports_in_paths(files, top_only=True)
6157else:
6158 imports = find_imports_in_stream(stdin, top_only=True)
6159
6160for imp in imports:
6161 if query is None or query == (imp.alias or imp.attribute or imp.module):
6162 key = (imp.module, imp.attribute or '', imp.alias or '')
6163 if key not in result:
6164 result[key] = imp.statement()
6165
6166for key in sorted(result):
6167 print(result[key])
6168"
6169 "Script to list import statements in Python code.")
6170
6171(defvar python-import-history nil
6172 "History variable for `python-import' commands.")
6173
6174(defun python--import-sources ()
6175 "List files containing Python imports that may be useful in the current buffer."
6176 (if-let (((featurep 'project)) ;For compatibility with Emacs < 26
6177 (proj (project-current)))
6178 (seq-filter (lambda (s) (string-match-p "\\.py[ciw]?\\'" s))
6179 (project-files proj))
6180 (list default-directory)))
6181
6182(defun python--list-imports (name source)
6183 "List all Python imports matching NAME in SOURCE.
6184If NAME is nil, list all imports. SOURCE can be a buffer or a
6185list of file names or directories; the latter are searched
6186recursively."
6187 (let ((buffer (current-buffer)))
6188 (with-temp-buffer
6189 (let* ((temp (current-buffer))
6190 (status (if (bufferp source)
6191 (with-current-buffer source
6192 (call-process-region (point-min) (point-max)
6193 python-interpreter
6194 nil (list temp nil) nil
6195 "-c" python--list-imports
6196 (or name "")))
6197 (with-current-buffer buffer
6198 (apply #'call-process
6199 python-interpreter
6200 nil (list temp nil) nil
6201 "-c" python--list-imports
6202 (or name "")
6203 (mapcar #'file-local-name source)))))
6204 lines)
6205 (unless (eq 0 status)
6206 (error "%s exited with status %s (maybe isort is missing?)"
6207 python-interpreter status))
6208 (goto-char (point-min))
6209 (while (not (eobp))
6210 (push (buffer-substring-no-properties (point) (pos-eol))
6211 lines)
6212 (forward-line 1))
6213 (nreverse lines)))))
6214
6215(defun python--query-import (name source prompt)
6216 "Read a Python import statement defining NAME.
6217A list of candidates is produced by `python--list-imports' using
6218the NAME and SOURCE arguments. An interactive query, using the
6219PROMPT string, is made unless there is a single candidate."
6220 (let* ((cands (python--list-imports name source))
6221 ;; Don't use DEF argument of `completing-read', so it is able
6222 ;; to return the empty string.
6223 (minibuffer-default-add-function
6224 (lambda ()
6225 (setq minibuffer-default (with-minibuffer-selected-window
6226 (thing-at-point 'symbol)))))
6227 (statement (cond ((and name (length= cands 1))
6228 (car cands))
6229 (prompt
6230 (completing-read prompt
6231 (or cands python-import-history)
6232 nil nil nil
6233 'python-import-history)))))
6234 (unless (string-empty-p statement)
6235 statement)))
6236
6237(defun python--do-isort (&rest args)
6238 "Edit the current buffer using isort called with ARGS.
6239Return non-nil if the buffer was actually modified."
6240 (let ((buffer (current-buffer)))
6241 (with-temp-buffer
6242 (let ((temp (current-buffer)))
6243 (with-current-buffer buffer
6244 (let ((status (apply #'call-process-region
6245 (point-min) (point-max)
6246 python-interpreter
6247 nil (list temp nil) nil
6248 "-m" "isort" "-" args))
6249 (tick (buffer-chars-modified-tick)))
6250 (unless (eq 0 status)
6251 (error "%s exited with status %s (maybe isort is missing?)"
6252 python-interpreter status))
6253 (replace-buffer-contents temp)
6254 (not (eq tick (buffer-chars-modified-tick)))))))))
6255
6256;;;###autoload
6257(defun python-add-import (name)
6258 "Add an import statement to the current buffer.
6259
6260Interactively, ask for an import statement using all imports
6261found in the current project as suggestions. With a prefix
6262argument, restrict the suggestions to imports defining the symbol
6263at point. If there is only one such suggestion, act without
6264asking.
6265
6266When calling from Lisp, use a non-nil NAME to restrict the
6267suggestions to imports defining NAME."
6268 (interactive (list (when current-prefix-arg (thing-at-point 'symbol))))
6269 (when-let ((statement (python--query-import name
6270 (python--import-sources)
6271 "Add import: ")))
6272 (if (python--do-isort "--add" statement)
6273 (message "Added `%s'" statement)
6274 (message "(No changes in Python imports needed)"))))
6275
6276;;;###autoload
6277(defun python-import-symbol-at-point ()
6278 "Add an import statement for the symbol at point to the current buffer.
6279This works like `python-add-import', but with the opposite
6280behavior regarding the prefix argument."
6281 (interactive nil)
6282 (python-add-import (unless current-prefix-arg (thing-at-point 'symbol))))
6283
6284;;;###autoload
6285(defun python-remove-import (name)
6286 "Remove an import statement from the current buffer.
6287
6288Interactively, ask for an import statement to remove, displaying
6289the imports of the current buffer as suggestions. With a prefix
6290argument, restrict the suggestions to imports defining the symbol
6291at point. If there is only one such suggestion, act without
6292asking."
6293 (interactive (list (when current-prefix-arg (thing-at-point 'symbol))))
6294 (when-let ((statement (python--query-import name (current-buffer)
6295 "Remove import: ")))
6296 (if (python--do-isort "--rm" statement)
6297 (message "Removed `%s'" statement)
6298 (message "(No changes in Python imports needed)"))))
6299
6300;;;###autoload
6301(defun python-sort-imports ()
6302 "Sort Python imports in the current buffer."
6303 (interactive)
6304 (if (python--do-isort)
6305 (message "Sorted imports")
6306 (message "(No changes in Python imports needed)")))
6307
6308;;;###autoload
6309(defun python-fix-imports ()
6310 "Add missing imports and remove unused ones from the current buffer."
6311 (interactive)
6312 (let ((buffer (current-buffer))
6313 undefined unused add remove)
6314 ;; Compute list of undefined and unused names
6315 (with-temp-buffer
6316 (let ((temp (current-buffer)))
6317 (with-current-buffer buffer
6318 (call-process-region (point-min) (point-max)
6319 python-interpreter
6320 nil temp nil
6321 "-m" "pyflakes"))
6322 (goto-char (point-min))
6323 (when (looking-at-p ".* No module named pyflakes$")
6324 (error "%s couldn't find pyflakes" python-interpreter))
6325 (while (not (eobp))
6326 (cond ((looking-at ".* undefined name '\\([^']+\\)'$")
6327 (push (match-string 1) undefined))
6328 ((looking-at ".*'\\([^']+\\)' imported but unused$")
6329 (push (match-string 1) unused)))
6330 (forward-line 1))))
6331 ;; Compute imports to be added
6332 (dolist (name (seq-uniq undefined))
6333 (when-let ((statement (python--query-import name
6334 (python--import-sources)
6335 (format "\
6336Add import for undefined name `%s' (empty to skip): "
6337 name))))
6338 (push statement add)))
6339 ;; Compute imports to be removed
6340 (dolist (name (seq-uniq unused))
6341 ;; The unused imported names, as provided by pyflakes, are of
6342 ;; the form "module.var" or "module.var as alias", independently
6343 ;; of style of import statement used.
6344 (let* ((filter
6345 (lambda (statement)
6346 (string= name
6347 (thread-last
6348 statement
6349 (replace-regexp-in-string "^\\(from\\|import\\) " "")
6350 (replace-regexp-in-string " import " ".")))))
6351 (statements (seq-filter filter (python--list-imports nil buffer))))
6352 (when (length= statements 1)
6353 (push (car statements) remove))))
6354 ;; Edit buffer and say goodbye
6355 (if (not (or add remove))
6356 (message "(No changes in Python imports needed)")
6357 (apply #'python--do-isort
6358 (append (mapcan (lambda (x) (list "--add" x)) add)
6359 (mapcan (lambda (x) (list "--rm" x)) remove)))
6360 (message "%s" (concat (when add "Added ")
6361 (when add (string-join add ", "))
6362 (when remove (if add " and removed " "Removed "))
6363 (when remove (string-join remove ", " )))))))
6364
6365
6366;;; Major mode
6081(defun python-electric-pair-string-delimiter () 6367(defun python-electric-pair-string-delimiter ()
6082 (when (and electric-pair-mode 6368 (when (and electric-pair-mode
6083 (memq last-command-event '(?\" ?\')) 6369 (memq last-command-event '(?\" ?\'))
@@ -6214,8 +6500,10 @@ REPORT-FN is Flymake's callback function."
6214 6500
6215;;; Completion predicates for M-x 6501;;; Completion predicates for M-x
6216;; Commands that only make sense when editing Python code 6502;; Commands that only make sense when editing Python code
6217(dolist (sym '(python-check 6503(dolist (sym '(python-add-import
6504 python-check
6218 python-fill-paragraph 6505 python-fill-paragraph
6506 python-fix-imports
6219 python-indent-dedent-line 6507 python-indent-dedent-line
6220 python-indent-dedent-line-backspace 6508 python-indent-dedent-line-backspace
6221 python-indent-guess-indent-offset 6509 python-indent-guess-indent-offset
@@ -6240,9 +6528,11 @@ REPORT-FN is Flymake's callback function."
6240 python-nav-forward-statement 6528 python-nav-forward-statement
6241 python-nav-if-name-main 6529 python-nav-if-name-main
6242 python-nav-up-list 6530 python-nav-up-list
6531 python-remove-import
6243 python-shell-send-buffer 6532 python-shell-send-buffer
6244 python-shell-send-defun 6533 python-shell-send-defun
6245 python-shell-send-statement)) 6534 python-shell-send-statement
6535 python-sort-imports))
6246 (put sym 'completion-predicate #'python--completion-predicate)) 6536 (put sym 'completion-predicate #'python--completion-predicate))
6247 6537
6248(defun python-shell--completion-predicate (_ buffer) 6538(defun python-shell--completion-predicate (_ buffer)