diff options
| author | Yuan Fu | 2022-10-05 14:22:03 -0700 |
|---|---|---|
| committer | Yuan Fu | 2022-10-05 14:22:03 -0700 |
| commit | 7ebbd4efc3d45403cf845d35c36c21756baeeba8 (patch) | |
| tree | f53223ce7dbd64c079aced6e1a77964d1a8eaa3f /lisp/progmodes/python.el | |
| parent | cb183f6467401fb5ed2b7fc98ca75be9d943cbe3 (diff) | |
| parent | 95efafb72664049f8ac825047df3645656cf76f4 (diff) | |
| download | emacs-7ebbd4efc3d45403cf845d35c36c21756baeeba8.tar.gz emacs-7ebbd4efc3d45403cf845d35c36c21756baeeba8.zip | |
Merge branch 'master' into feature/tree-sitter
Diffstat (limited to 'lisp/progmodes/python.el')
| -rw-r--r-- | lisp/progmodes/python.el | 436 |
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, | |||
| 273 | and movement functions." | 292 | and movement functions." |
| 274 | :type 'boolean) | 293 | :type 'boolean) |
| 275 | 294 | ||
| 295 | (defcustom python-interpreter "python" | ||
| 296 | "Python interpreter for noninteractive use. | ||
| 297 | To customize the Python shell, modify `python-shell-interpreter' | ||
| 298 | instead." | ||
| 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 | |||
| 555 | See 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. | ||
| 2495 | This option influences `run-python' when called without a prefix | ||
| 2496 | argument. If `buffer' or `project', create a Python shell | ||
| 2497 | dedicated 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. |
| 2817 | If DEDICATED is t returns a string with the form | 2865 | If DEDICATED is nil, this is simply `python-shell-buffer-name'. |
| 2818 | `python-shell-buffer-name'[`buffer-name'] else returns the value | 2866 | If DEDICATED is `buffer' or `project', append the current buffer |
| 2819 | of `python-shell-buffer-name'." | 2867 | name 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 |
| 3187 | customize this mode for different Python interpreters. | 3242 | customize this mode for different Python interpreters. |
| 3188 | 3243 | ||
| 3189 | This mode resets `comint-output-filter-functions' locally, so you | 3244 | This mode resets `comint-output-filter-functions' locally, so you |
| @@ -3277,8 +3332,8 @@ killed." | |||
| 3277 | Argument CMD defaults to `python-shell-calculate-command' return | 3332 | Argument CMD defaults to `python-shell-calculate-command' return |
| 3278 | value. When called interactively with `prefix-arg', it allows | 3333 | value. When called interactively with `prefix-arg', it allows |
| 3279 | the user to edit such value and choose whether the interpreter | 3334 | the user to edit such value and choose whether the interpreter |
| 3280 | should be DEDICATED for the current buffer. When numeric prefix | 3335 | should be DEDICATED to the current buffer or project. When |
| 3281 | arg is other than 0 or 4 do not SHOW. | 3336 | numeric prefix arg is other than 0 or 4 do not SHOW. |
| 3282 | 3337 | ||
| 3283 | For a given buffer and same values of DEDICATED, if a process is | 3338 | For a given buffer and same values of DEDICATED, if a process is |
| 3284 | already running for it, it will do nothing. This means that if | 3339 | already 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. | ||
| 3373 | Optional argument SHOW (interactively, the prefix argument), if | ||
| 3374 | non-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. |
| 3306 | Input and output via buffer named after | 3393 | Input and output via buffer named after |
| @@ -3328,15 +3415,13 @@ startup." | |||
| 3328 | If current buffer is in `inferior-python-mode', return it." | 3415 | If 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. |
| 3385 | This is really not necessary at all for the code to work but it's | 3467 | This is really not necessary at all for the code to work but it's |
| 3386 | there for compatibility with CEDET.") | 3468 | there 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. |
| 3393 | This is really not necessary at all for the code to work but it's | 3472 | This 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. |
| 3530 | This is a wrapper over `buffer-substring' that takes care of | 3600 | This 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. |
| 4698 | This happens when pressing \"if<SPACE>\", for example, to prompt for | 4765 | This 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. |
| 5697 | When CHECK-STATEMENT is non-nil, the current statement is checked | 5764 | When CHECK-STATEMENT is non-nil, the current statement is checked |
| 5698 | instead of the current physical line." | 5765 | instead 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 "\ | ||
| 6150 | from isort import find_imports_in_stream, find_imports_in_paths | ||
| 6151 | from sys import argv, stdin | ||
| 6152 | |||
| 6153 | query, files, result = argv[1] or None, argv[2:], {} | ||
| 6154 | |||
| 6155 | if files: | ||
| 6156 | imports = find_imports_in_paths(files, top_only=True) | ||
| 6157 | else: | ||
| 6158 | imports = find_imports_in_stream(stdin, top_only=True) | ||
| 6159 | |||
| 6160 | for 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 | |||
| 6166 | for 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. | ||
| 6184 | If NAME is nil, list all imports. SOURCE can be a buffer or a | ||
| 6185 | list of file names or directories; the latter are searched | ||
| 6186 | recursively." | ||
| 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. | ||
| 6217 | A list of candidates is produced by `python--list-imports' using | ||
| 6218 | the NAME and SOURCE arguments. An interactive query, using the | ||
| 6219 | PROMPT 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. | ||
| 6239 | Return 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 | |||
| 6260 | Interactively, ask for an import statement using all imports | ||
| 6261 | found in the current project as suggestions. With a prefix | ||
| 6262 | argument, restrict the suggestions to imports defining the symbol | ||
| 6263 | at point. If there is only one such suggestion, act without | ||
| 6264 | asking. | ||
| 6265 | |||
| 6266 | When calling from Lisp, use a non-nil NAME to restrict the | ||
| 6267 | suggestions 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. | ||
| 6279 | This works like `python-add-import', but with the opposite | ||
| 6280 | behavior 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 | |||
| 6288 | Interactively, ask for an import statement to remove, displaying | ||
| 6289 | the imports of the current buffer as suggestions. With a prefix | ||
| 6290 | argument, restrict the suggestions to imports defining the symbol | ||
| 6291 | at point. If there is only one such suggestion, act without | ||
| 6292 | asking." | ||
| 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 "\ | ||
| 6336 | Add 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) |