diff options
| author | Liu Hui | 2026-01-30 18:10:30 +0800 |
|---|---|---|
| committer | Eli Zaretskii | 2026-02-21 16:51:28 +0200 |
| commit | f2b99c4c0b1cca4360a6c516ca99e716e531fae5 (patch) | |
| tree | 206bb3eb550317a3b597c2cca5ac2f2c80e370bd /lisp/progmodes/python.el | |
| parent | 6f29a0ca7b179f2fa909bd27de69aa113ceab01e (diff) | |
| download | emacs-f2b99c4c0b1cca4360a6c516ca99e716e531fae5.tar.gz emacs-f2b99c4c0b1cca4360a6c516ca99e716e531fae5.zip | |
Use a custom Pdb class in the Python shell
The custom Pdb class enables native completion in pdb by
wrapping the pdb's native completer. It also makes necessary
function definitions like __PYTHON_EL_* available between pdb
frames, and enables non-native completion/ffap/eldoc
functionalities when debugging inside python modules.
* lisp/progmodes/python.el (python-shell-send-setup-code): Fix
the separator between python-shell-setup-codes.
(python-shell-completion-native-setup): Move common completion
setup code ...
(python-shell-completion-setup-code): ... here.
(python-shell-completion-at-point): Enable native completion for
pdb and respect the delimiter of pdb completer.
(python-shell-pdb-setup-code): New variable.
(python-shell-comint-watch-for-first-prompt-output-filter): Send
setup codes only once.
(python-ffap-module-path, python-eldoc--get-doc-at-point): Stop
sending setup code in every function call.
* test/lisp/progmodes/python-tests.el (python-tests--pdb-1)
(python-shell-pdb-1): New test.
* etc/NEWS: Mention the change. (bug#80182)
Diffstat (limited to 'lisp/progmodes/python.el')
| -rw-r--r-- | lisp/progmodes/python.el | 282 |
1 files changed, 155 insertions, 127 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index c544c845dff..0b4a773516a 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -3736,11 +3736,18 @@ It is used when sending file names to remote Python processes.") | |||
| 3736 | (format "exec(%s)\n" (python-shell--encode-string string)))))) | 3736 | (format "exec(%s)\n" (python-shell--encode-string string)))))) |
| 3737 | ;; Bootstrap: the normal definition of `python-shell-send-string' | 3737 | ;; Bootstrap: the normal definition of `python-shell-send-string' |
| 3738 | ;; depends on the Python code sent here. | 3738 | ;; depends on the Python code sent here. |
| 3739 | (python-shell-send-string-no-output python-shell-setup-code) | ||
| 3740 | (python-shell-send-string-no-output python-shell-eval-setup-code) | 3739 | (python-shell-send-string-no-output python-shell-eval-setup-code) |
| 3741 | (python-shell-send-string-no-output python-shell-eval-file-setup-code)) | 3740 | (python-shell-send-string-no-output python-shell-eval-file-setup-code)) |
| 3742 | (with-current-buffer (current-buffer) | 3741 | (with-current-buffer (current-buffer) |
| 3743 | (let ((inhibit-quit nil)) | 3742 | (let ((inhibit-quit nil)) |
| 3743 | (python-shell-send-string | ||
| 3744 | (mapconcat #'symbol-value '(python-shell-setup-code | ||
| 3745 | python-shell-completion-setup-code | ||
| 3746 | python-shell-pdb-setup-code | ||
| 3747 | python-ffap-setup-code | ||
| 3748 | python-eldoc-setup-code) | ||
| 3749 | "\n")) | ||
| 3750 | (python-shell-accept-process-output (python-shell-get-process)) | ||
| 3744 | (python-shell-readline-detect) | 3751 | (python-shell-readline-detect) |
| 3745 | (run-hooks 'python-shell-first-prompt-hook)))))) | 3752 | (run-hooks 'python-shell-first-prompt-hook)))))) |
| 3746 | output) | 3753 | output) |
| @@ -4432,7 +4439,8 @@ This function takes the list of setup code to send from the | |||
| 4432 | ((symbolp elt) (symbol-value elt)) | 4439 | ((symbolp elt) (symbol-value elt)) |
| 4433 | (t ""))) | 4440 | (t ""))) |
| 4434 | python-shell-setup-codes | 4441 | python-shell-setup-codes |
| 4435 | "\n\nprint ('python.el: sent setup code')")))) | 4442 | "\n") |
| 4443 | "\nprint ('python.el: sent setup code')"))) | ||
| 4436 | (python-shell-send-string code process) | 4444 | (python-shell-send-string code process) |
| 4437 | (python-shell-accept-process-output process)))) | 4445 | (python-shell-accept-process-output process)))) |
| 4438 | 4446 | ||
| @@ -4506,16 +4514,82 @@ def __PYTHON_EL_get_completions(text): | |||
| 4506 | finally: | 4514 | finally: |
| 4507 | if getattr(completer, 'PYTHON_EL_WRAPPED', False): | 4515 | if getattr(completer, 'PYTHON_EL_WRAPPED', False): |
| 4508 | completer.print_mode = True | 4516 | completer.print_mode = True |
| 4509 | return json.dumps(completions)" | 4517 | return json.dumps(completions) |
| 4510 | "Code used to setup completion in inferior Python processes." | 4518 | |
| 4511 | :type 'string) | 4519 | def __PYTHON_EL_wrap_completer(): |
| 4520 | import readline | ||
| 4521 | completer = readline.get_completer() | ||
| 4512 | 4522 | ||
| 4513 | (defun python-shell-completion-send-setup-code () | 4523 | if not completer: |
| 4514 | "Send `python-shell-completion-setup-code' to inferior Python process." | 4524 | # Used as last resort to avoid breaking customizations. |
| 4515 | (python-shell-send-string-no-output python-shell-completion-setup-code)) | 4525 | import rlcompleter |
| 4526 | completer = readline.get_completer() | ||
| 4516 | 4527 | ||
| 4517 | (add-hook 'python-shell-first-prompt-hook | 4528 | if completer and not getattr(completer, 'PYTHON_EL_WRAPPED', False): |
| 4518 | #'python-shell-completion-send-setup-code) | 4529 | class __PYTHON_EL_Completer: |
| 4530 | '''Completer wrapper that prints candidates to stdout. | ||
| 4531 | |||
| 4532 | It wraps an existing completer function and changes its behavior so | ||
| 4533 | that the user input is unchanged and real candidates are printed to | ||
| 4534 | stdout. | ||
| 4535 | |||
| 4536 | Returned candidates are '0__dummy_completion__' and | ||
| 4537 | '1__dummy_completion__' in that order ('0__dummy_completion__' is | ||
| 4538 | returned repeatedly until all possible candidates are consumed). | ||
| 4539 | |||
| 4540 | The real candidates are printed to stdout so that they can be | ||
| 4541 | easily retrieved through comint output redirect trickery. | ||
| 4542 | ''' | ||
| 4543 | |||
| 4544 | PYTHON_EL_WRAPPED = True | ||
| 4545 | |||
| 4546 | def __init__(self, completer): | ||
| 4547 | self.completer = completer | ||
| 4548 | self.last_completion = None | ||
| 4549 | self.print_mode = True | ||
| 4550 | |||
| 4551 | def __call__(self, text, state): | ||
| 4552 | if state == 0: | ||
| 4553 | # Set the first dummy completion. | ||
| 4554 | self.last_completion = None | ||
| 4555 | completion = '0__dummy_completion__' | ||
| 4556 | else: | ||
| 4557 | completion = self.completer(text, state - 1) | ||
| 4558 | |||
| 4559 | if not completion: | ||
| 4560 | if self.last_completion != '1__dummy_completion__': | ||
| 4561 | # When no more completions are available, returning a | ||
| 4562 | # dummy with non-sharing prefix allow ensuring output | ||
| 4563 | # while preventing changes to current input. | ||
| 4564 | # Coincidentally it's also the end of output. | ||
| 4565 | completion = '1__dummy_completion__' | ||
| 4566 | elif completion.endswith('('): | ||
| 4567 | # Remove parens on callables as it breaks completion on | ||
| 4568 | # arguments (e.g. str(Ari<tab>)). | ||
| 4569 | completion = completion[:-1] | ||
| 4570 | self.last_completion = completion | ||
| 4571 | |||
| 4572 | if completion in ( | ||
| 4573 | '0__dummy_completion__', '1__dummy_completion__'): | ||
| 4574 | return completion | ||
| 4575 | elif completion: | ||
| 4576 | # For every non-dummy completion, return a repeated dummy | ||
| 4577 | # one and print the real candidate so it can be retrieved | ||
| 4578 | # by comint output filters. | ||
| 4579 | if self.print_mode: | ||
| 4580 | print (completion) | ||
| 4581 | return '0__dummy_completion__' | ||
| 4582 | else: | ||
| 4583 | return completion | ||
| 4584 | else: | ||
| 4585 | return completion | ||
| 4586 | |||
| 4587 | # Wrap the existing completer function only once. | ||
| 4588 | new_completer = __PYTHON_EL_Completer(completer) | ||
| 4589 | readline.set_completer(new_completer)" | ||
| 4590 | "Code used to setup completion in inferior Python processes." | ||
| 4591 | :type 'string | ||
| 4592 | :version "31.1") | ||
| 4519 | 4593 | ||
| 4520 | (define-obsolete-variable-alias | 4594 | (define-obsolete-variable-alias |
| 4521 | 'python-shell-completion-module-string-code | 4595 | 'python-shell-completion-module-string-code |
| @@ -4601,103 +4675,16 @@ except: | |||
| 4601 | def __PYTHON_EL_native_completion_setup(): | 4675 | def __PYTHON_EL_native_completion_setup(): |
| 4602 | try: | 4676 | try: |
| 4603 | import readline | 4677 | import readline |
| 4678 | __PYTHON_EL_wrap_completer() | ||
| 4604 | 4679 | ||
| 4680 | # Ensure that rlcompleter.__main__ and __main__ are identical. | ||
| 4681 | # (Bug#76205) | ||
| 4682 | import sys | ||
| 4605 | try: | 4683 | try: |
| 4606 | import __builtin__ | 4684 | __IPYTHON__ |
| 4607 | except ImportError: | 4685 | sys.modules['rlcompleter'].__main__ = sys.modules['__main__'] |
| 4608 | # Python 3 | 4686 | except (NameError, KeyError): |
| 4609 | import builtins as __builtin__ | 4687 | pass |
| 4610 | |||
| 4611 | builtins = dir(__builtin__) | ||
| 4612 | is_ipython = ('__IPYTHON__' in builtins or | ||
| 4613 | '__IPYTHON__active' in builtins) | ||
| 4614 | |||
| 4615 | class __PYTHON_EL_Completer: | ||
| 4616 | '''Completer wrapper that prints candidates to stdout. | ||
| 4617 | |||
| 4618 | It wraps an existing completer function and changes its behavior so | ||
| 4619 | that the user input is unchanged and real candidates are printed to | ||
| 4620 | stdout. | ||
| 4621 | |||
| 4622 | Returned candidates are '0__dummy_completion__' and | ||
| 4623 | '1__dummy_completion__' in that order ('0__dummy_completion__' is | ||
| 4624 | returned repeatedly until all possible candidates are consumed). | ||
| 4625 | |||
| 4626 | The real candidates are printed to stdout so that they can be | ||
| 4627 | easily retrieved through comint output redirect trickery. | ||
| 4628 | ''' | ||
| 4629 | |||
| 4630 | PYTHON_EL_WRAPPED = True | ||
| 4631 | |||
| 4632 | def __init__(self, completer): | ||
| 4633 | self.completer = completer | ||
| 4634 | self.last_completion = None | ||
| 4635 | self.print_mode = True | ||
| 4636 | |||
| 4637 | def __call__(self, text, state): | ||
| 4638 | if state == 0: | ||
| 4639 | # Set the first dummy completion. | ||
| 4640 | self.last_completion = None | ||
| 4641 | completion = '0__dummy_completion__' | ||
| 4642 | else: | ||
| 4643 | completion = self.completer(text, state - 1) | ||
| 4644 | |||
| 4645 | if not completion: | ||
| 4646 | if self.last_completion != '1__dummy_completion__': | ||
| 4647 | # When no more completions are available, returning a | ||
| 4648 | # dummy with non-sharing prefix allow ensuring output | ||
| 4649 | # while preventing changes to current input. | ||
| 4650 | # Coincidentally it's also the end of output. | ||
| 4651 | completion = '1__dummy_completion__' | ||
| 4652 | elif completion.endswith('('): | ||
| 4653 | # Remove parens on callables as it breaks completion on | ||
| 4654 | # arguments (e.g. str(Ari<tab>)). | ||
| 4655 | completion = completion[:-1] | ||
| 4656 | self.last_completion = completion | ||
| 4657 | |||
| 4658 | if completion in ( | ||
| 4659 | '0__dummy_completion__', '1__dummy_completion__'): | ||
| 4660 | return completion | ||
| 4661 | elif completion: | ||
| 4662 | # For every non-dummy completion, return a repeated dummy | ||
| 4663 | # one and print the real candidate so it can be retrieved | ||
| 4664 | # by comint output filters. | ||
| 4665 | if self.print_mode: | ||
| 4666 | print (completion) | ||
| 4667 | return '0__dummy_completion__' | ||
| 4668 | else: | ||
| 4669 | return completion | ||
| 4670 | else: | ||
| 4671 | return completion | ||
| 4672 | |||
| 4673 | completer = readline.get_completer() | ||
| 4674 | |||
| 4675 | if not completer: | ||
| 4676 | # Used as last resort to avoid breaking customizations. | ||
| 4677 | import rlcompleter | ||
| 4678 | completer = readline.get_completer() | ||
| 4679 | |||
| 4680 | if completer and not getattr(completer, 'PYTHON_EL_WRAPPED', False): | ||
| 4681 | # Wrap the existing completer function only once. | ||
| 4682 | new_completer = __PYTHON_EL_Completer(completer) | ||
| 4683 | if not is_ipython: | ||
| 4684 | readline.set_completer(new_completer) | ||
| 4685 | else: | ||
| 4686 | # Ensure that rlcompleter.__main__ and __main__ are identical. | ||
| 4687 | # (Bug#76205) | ||
| 4688 | import sys | ||
| 4689 | try: | ||
| 4690 | sys.modules['rlcompleter'].__main__ = sys.modules['__main__'] | ||
| 4691 | except KeyError: | ||
| 4692 | pass | ||
| 4693 | # Try both initializations to cope with all IPython versions. | ||
| 4694 | # This works fine for IPython 3.x but not for earlier: | ||
| 4695 | readline.set_completer(new_completer) | ||
| 4696 | # IPython<3 hacks readline such that `readline.set_completer` | ||
| 4697 | # won't work. This workaround injects the new completer | ||
| 4698 | # function into the existing instance directly: | ||
| 4699 | instance = getattr(completer, 'im_self', completer.__self__) | ||
| 4700 | instance.rlcomplete = new_completer | ||
| 4701 | 4688 | ||
| 4702 | if readline.__doc__ and 'libedit' in readline.__doc__: | 4689 | if readline.__doc__ and 'libedit' in readline.__doc__: |
| 4703 | raise Exception('''libedit based readline is known not to work, | 4690 | raise Exception('''libedit based readline is known not to work, |
| @@ -4921,8 +4908,17 @@ using that one instead of current buffer's process." | |||
| 4921 | ;; Working on a shell buffer: use prompt end. | 4908 | ;; Working on a shell buffer: use prompt end. |
| 4922 | (cdr (python-util-comint-last-prompt)) | 4909 | (cdr (python-util-comint-last-prompt)) |
| 4923 | (line-beginning-position))) | 4910 | (line-beginning-position))) |
| 4911 | (prompt-boundaries | ||
| 4912 | (with-current-buffer (process-buffer process) | ||
| 4913 | (python-util-comint-last-prompt))) | ||
| 4914 | (prompt | ||
| 4915 | (with-current-buffer (process-buffer process) | ||
| 4916 | (when prompt-boundaries | ||
| 4917 | (buffer-substring-no-properties | ||
| 4918 | (car prompt-boundaries) (cdr prompt-boundaries))))) | ||
| 4924 | (no-delims | 4919 | (no-delims |
| 4925 | (and (not (if is-shell-buffer | 4920 | (and (not (string-match-p python-shell-prompt-pdb-regexp prompt)) |
| 4921 | (not (if is-shell-buffer | ||
| 4926 | (eq 'font-lock-comment-face | 4922 | (eq 'font-lock-comment-face |
| 4927 | (get-text-property (1- (point)) 'face)) | 4923 | (get-text-property (1- (point)) 'face)) |
| 4928 | (python-syntax-context 'comment))) | 4924 | (python-syntax-context 'comment))) |
| @@ -4946,14 +4942,6 @@ using that one instead of current buffer's process." | |||
| 4946 | (forward-char (length (match-string-no-properties 0))) | 4942 | (forward-char (length (match-string-no-properties 0))) |
| 4947 | (point))))) | 4943 | (point))))) |
| 4948 | (end (point)) | 4944 | (end (point)) |
| 4949 | (prompt-boundaries | ||
| 4950 | (with-current-buffer (process-buffer process) | ||
| 4951 | (python-util-comint-last-prompt))) | ||
| 4952 | (prompt | ||
| 4953 | (with-current-buffer (process-buffer process) | ||
| 4954 | (when prompt-boundaries | ||
| 4955 | (buffer-substring-no-properties | ||
| 4956 | (car prompt-boundaries) (cdr prompt-boundaries))))) | ||
| 4957 | (completion-fn | 4945 | (completion-fn |
| 4958 | (with-current-buffer (process-buffer process) | 4946 | (with-current-buffer (process-buffer process) |
| 4959 | (cond ((or (null prompt) | 4947 | (cond ((or (null prompt) |
| @@ -4963,13 +4951,7 @@ using that one instead of current buffer's process." | |||
| 4963 | (string-match-p | 4951 | (string-match-p |
| 4964 | python-shell-prompt-pdb-regexp prompt))) | 4952 | python-shell-prompt-pdb-regexp prompt))) |
| 4965 | #'ignore) | 4953 | #'ignore) |
| 4966 | ((or (not python-shell-completion-native-enable) | 4954 | ((not python-shell-completion-native-enable) |
| 4967 | ;; Even if native completion is enabled, for | ||
| 4968 | ;; pdb interaction always use the fallback | ||
| 4969 | ;; mechanism since the completer is changed. | ||
| 4970 | ;; Also, since pdb interaction is single-line | ||
| 4971 | ;; based, this is enough. | ||
| 4972 | (string-match-p python-shell-prompt-pdb-regexp prompt)) | ||
| 4973 | (if (or (equal python-shell--block-prompt prompt) | 4955 | (if (or (equal python-shell--block-prompt prompt) |
| 4974 | (string-match-p | 4956 | (string-match-p |
| 4975 | python-shell-prompt-block-regexp prompt)) | 4957 | python-shell-prompt-block-regexp prompt)) |
| @@ -5051,6 +5033,55 @@ If not try to complete." | |||
| 5051 | 5033 | ||
| 5052 | ;;; PDB Track integration | 5034 | ;;; PDB Track integration |
| 5053 | 5035 | ||
| 5036 | (defconst python-shell-pdb-setup-code | ||
| 5037 | "\ | ||
| 5038 | def __PYTHON_EL_Pdb_setup(): | ||
| 5039 | import pdb | ||
| 5040 | |||
| 5041 | class _PYTHON_EL_Pdb(pdb.Pdb, object): | ||
| 5042 | def __init__(self, *args, **kw): | ||
| 5043 | super(_PYTHON_EL_Pdb, self).__init__(*args, **kw) | ||
| 5044 | import re | ||
| 5045 | self._python_el_def_pattern = re.compile('__(PYTHON_EL|FFAP|PYDOC)_') | ||
| 5046 | self._python_el_defs = {} | ||
| 5047 | for k, v in globals().items(): | ||
| 5048 | if self._python_el_def_pattern.match(k): | ||
| 5049 | self._python_el_defs[k] = v | ||
| 5050 | |||
| 5051 | def _python_el_setup(self): | ||
| 5052 | if not hasattr(self, 'curframe') or self.curframe is None: | ||
| 5053 | return | ||
| 5054 | frame_globals = self.curframe.f_globals | ||
| 5055 | if '__PYTHON_EL_eval' not in frame_globals: | ||
| 5056 | for k, v in self._python_el_defs.items(): | ||
| 5057 | frame_globals[k] = v | ||
| 5058 | try: | ||
| 5059 | frame_globals['__PYTHON_EL_wrap_completer']() | ||
| 5060 | except Exception as e: | ||
| 5061 | print('failed to setup completer: {}'.format(str(e))) | ||
| 5062 | |||
| 5063 | def preloop(self): | ||
| 5064 | super(_PYTHON_EL_Pdb, self).preloop() | ||
| 5065 | # Trigger precmd/postcmd when entering pdb. | ||
| 5066 | self.cmdqueue.append('pass # __PYTHON_EL_') | ||
| 5067 | |||
| 5068 | def precmd(self, line): | ||
| 5069 | if self._python_el_def_pattern.search(line): | ||
| 5070 | self._real_lastcmd = self.lastcmd | ||
| 5071 | return super(_PYTHON_EL_Pdb, self).precmd(line) | ||
| 5072 | |||
| 5073 | def postcmd(self, stop, line): | ||
| 5074 | self._python_el_setup() | ||
| 5075 | if self._python_el_def_pattern.search(line): | ||
| 5076 | self.lastcmd = self._real_lastcmd | ||
| 5077 | return super(_PYTHON_EL_Pdb, self).postcmd(stop, line) | ||
| 5078 | |||
| 5079 | pdb.Pdb = _PYTHON_EL_Pdb | ||
| 5080 | |||
| 5081 | __PYTHON_EL_Pdb_setup() | ||
| 5082 | del __PYTHON_EL_Pdb_setup" | ||
| 5083 | "Code used to setup the debugger in inferior Python processes.") | ||
| 5084 | |||
| 5054 | (defcustom python-pdbtrack-activate t | 5085 | (defcustom python-pdbtrack-activate t |
| 5055 | "Non-nil makes Python shell enable pdbtracking. | 5086 | "Non-nil makes Python shell enable pdbtracking. |
| 5056 | Pdbtracking would open the file for current stack frame found in pdb output by | 5087 | Pdbtracking would open the file for current stack frame found in pdb output by |
| @@ -5691,8 +5722,7 @@ def __FFAP_get_module_path(objstr): | |||
| 5691 | (python-util-comint-end-of-output-p))) | 5722 | (python-util-comint-end-of-output-p))) |
| 5692 | (module-file | 5723 | (module-file |
| 5693 | (python-shell-send-string-no-output | 5724 | (python-shell-send-string-no-output |
| 5694 | (format "%s\nprint(__FFAP_get_module_path(%s))" | 5725 | (format "print(__FFAP_get_module_path(%s))" |
| 5695 | python-ffap-setup-code | ||
| 5696 | (python-shell--encode-string module))))) | 5726 | (python-shell--encode-string module))))) |
| 5697 | (unless (string-empty-p module-file) | 5727 | (unless (string-empty-p module-file) |
| 5698 | (python-util-strip-string module-file)))) | 5728 | (python-util-strip-string module-file)))) |
| @@ -5815,10 +5845,8 @@ returns will be used. If not FORCE-PROCESS is passed what | |||
| 5815 | ;; enabled. Bug#18794. | 5845 | ;; enabled. Bug#18794. |
| 5816 | (python-util-strip-string | 5846 | (python-util-strip-string |
| 5817 | (python-shell-send-string-no-output | 5847 | (python-shell-send-string-no-output |
| 5818 | (format | 5848 | (format "print(__PYDOC_get_help(%s))" |
| 5819 | "%s\nprint(__PYDOC_get_help(%s))" | 5849 | (python-shell--encode-string input)) |
| 5820 | python-eldoc-setup-code | ||
| 5821 | (python-shell--encode-string input)) | ||
| 5822 | process))))) | 5850 | process))))) |
| 5823 | (unless (string-empty-p docstring) | 5851 | (unless (string-empty-p docstring) |
| 5824 | docstring))))) | 5852 | docstring))))) |