diff options
| author | João Távora | 2020-07-08 22:47:00 +0100 |
|---|---|---|
| committer | João Távora | 2020-07-08 22:47:10 +0100 |
| commit | 91041920c6f80d7a6b4ae53d27d844018710d7f3 (patch) | |
| tree | 20a9950c15d91995a142304c941d10a1b370a695 | |
| parent | 9a7aab2d9e5f5a8f15c6f60130cae6be32b11f48 (diff) | |
| download | emacs-91041920c6f80d7a6b4ae53d27d844018710d7f3.tar.gz emacs-91041920c6f80d7a6b4ae53d27d844018710d7f3.zip | |
Have Python mode cooperate asynchronously with Eldocscratch/python-eldoc-async
When combined with Flymake mode, which also adds a value to
eldoc-documentation-functions, Python-mode users can now experiment
with different eldoc-documentation-strategy values.
Also, this shoulda allow us to write automatic tests for this
particular Eldoc functionality.
* lisp/progmodes/python.el (inferior-python-mode): Set
coming-preoutput-filter-functions.
(python--shell-output-filter-in-progress)
(python--shell-output-filter-buffer): Rename from python- variant.
(python-shell-output-filter): Rework to support async operation.
(python-eldoc--get-doc-at-point): Rework to support async.
(python-eldoc-function): Use callback.
| -rw-r--r-- | lisp/progmodes/python.el | 188 |
1 files changed, 119 insertions, 69 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 165463aef59..e135ee76f60 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -2809,6 +2809,8 @@ variable. | |||
| 2809 | python-shell-comint-watch-for-first-prompt-output-filter | 2809 | python-shell-comint-watch-for-first-prompt-output-filter |
| 2810 | python-comint-postoutput-scroll-to-bottom | 2810 | python-comint-postoutput-scroll-to-bottom |
| 2811 | comint-watch-for-password-prompt)) | 2811 | comint-watch-for-password-prompt)) |
| 2812 | (setq comint-preoutput-filter-functions | ||
| 2813 | '(python-shell-output-filter)) | ||
| 2812 | (set (make-local-variable 'compilation-error-regexp-alist) | 2814 | (set (make-local-variable 'compilation-error-regexp-alist) |
| 2813 | python-shell-compilation-regexp-alist) | 2815 | python-shell-compilation-regexp-alist) |
| 2814 | (add-hook 'completion-at-point-functions | 2816 | (add-hook 'completion-at-point-functions |
| @@ -3021,8 +3023,10 @@ t when called interactively." | |||
| 3021 | (string-match "\n[ \t].*\n?\\'" string)) | 3023 | (string-match "\n[ \t].*\n?\\'" string)) |
| 3022 | (comint-send-string process "\n"))))) | 3024 | (comint-send-string process "\n"))))) |
| 3023 | 3025 | ||
| 3024 | (defvar python-shell-output-filter-in-progress nil) | 3026 | ;;; Three variables used by `python-shell-output-filter'. |
| 3025 | (defvar python-shell-output-filter-buffer nil) | 3027 | (defvar python--shell-output-filter-in-progress nil) |
| 3028 | (defvar python--shell-output-filter-buffer nil) | ||
| 3029 | (defvar python--shell-output-filter-callback nil) | ||
| 3026 | 3030 | ||
| 3027 | (defun python-shell-output-filter (string) | 3031 | (defun python-shell-output-filter (string) |
| 3028 | "Filter used in `python-shell-send-string-no-output' to grab output. | 3032 | "Filter used in `python-shell-send-string-no-output' to grab output. |
| @@ -3030,48 +3034,71 @@ STRING is the output received to this point from the process. | |||
| 3030 | This filter saves received output from the process in | 3034 | This filter saves received output from the process in |
| 3031 | `python-shell-output-filter-buffer' and stops receiving it after | 3035 | `python-shell-output-filter-buffer' and stops receiving it after |
| 3032 | detecting a prompt at the end of the buffer." | 3036 | detecting a prompt at the end of the buffer." |
| 3033 | (setq | 3037 | (cond (python--shell-output-filter-in-progress |
| 3034 | string (ansi-color-filter-apply string) | 3038 | (setq |
| 3035 | python-shell-output-filter-buffer | 3039 | string (ansi-color-filter-apply string) |
| 3036 | (concat python-shell-output-filter-buffer string)) | 3040 | python--shell-output-filter-buffer |
| 3037 | (when (python-shell-comint-end-of-output-p | 3041 | (concat python--shell-output-filter-buffer string)) |
| 3038 | python-shell-output-filter-buffer) | 3042 | (when (python-shell-comint-end-of-output-p |
| 3039 | ;; Output ends when `python-shell-output-filter-buffer' contains | 3043 | python--shell-output-filter-buffer) |
| 3040 | ;; the prompt attached at the end of it. | 3044 | ;; Output ends when `python-shell-output-filter-buffer' contains |
| 3041 | (setq python-shell-output-filter-in-progress nil | 3045 | ;; the prompt attached at the end of it. |
| 3042 | python-shell-output-filter-buffer | 3046 | (setq python--shell-output-filter-in-progress nil |
| 3043 | (substring python-shell-output-filter-buffer | 3047 | python--shell-output-filter-buffer |
| 3044 | 0 (match-beginning 0))) | 3048 | (substring python--shell-output-filter-buffer |
| 3045 | (when (string-match | 3049 | 0 (match-beginning 0))) |
| 3046 | python-shell--prompt-calculated-output-regexp | 3050 | (when (string-match |
| 3047 | python-shell-output-filter-buffer) | 3051 | python-shell--prompt-calculated-output-regexp |
| 3048 | ;; Some shells, like IPython might append a prompt before the | 3052 | python--shell-output-filter-buffer) |
| 3049 | ;; output, clean that. | 3053 | ;; Some shells, like IPython might append a prompt before the |
| 3050 | (setq python-shell-output-filter-buffer | 3054 | ;; output, clean that. |
| 3051 | (substring python-shell-output-filter-buffer (match-end 0))))) | 3055 | (setq python--shell-output-filter-buffer |
| 3052 | "") | 3056 | (substring python--shell-output-filter-buffer (match-end 0)))) |
| 3053 | 3057 | (when python--shell-output-filter-callback | |
| 3054 | (defun python-shell-send-string-no-output (string &optional process) | 3058 | (funcall python--shell-output-filter-callback |
| 3059 | python--shell-output-filter-buffer) | ||
| 3060 | (setq python--shell-output-filter-callback nil | ||
| 3061 | python--shell-output-filter-buffer nil))) | ||
| 3062 | "") | ||
| 3063 | (t string))) | ||
| 3064 | |||
| 3065 | (defvar python--async-comint-timeout 1 | ||
| 3066 | "Defaults to 1, but can be bound dynamically. | ||
| 3067 | Namely by callers of `python-shell-send-string-no-output'.") | ||
| 3068 | |||
| 3069 | (defun python-shell-send-string-no-output (string &optional process async) | ||
| 3055 | "Send STRING to PROCESS and inhibit output. | 3070 | "Send STRING to PROCESS and inhibit output. |
| 3056 | Return the output." | 3071 | Return the output string, unless ASYNC is non-nil, in which case |
| 3072 | nil is returned. If ASYNC is non-nil it should be a function of | ||
| 3073 | one argument. It is called with the output string if the comint | ||
| 3074 | subjob finished successfully or with nil if it didn't." | ||
| 3057 | (let ((process (or process (python-shell-get-process-or-error))) | 3075 | (let ((process (or process (python-shell-get-process-or-error))) |
| 3058 | (comint-preoutput-filter-functions | ||
| 3059 | '(python-shell-output-filter)) | ||
| 3060 | (python-shell-output-filter-in-progress t) | ||
| 3061 | (inhibit-quit t)) | 3076 | (inhibit-quit t)) |
| 3062 | (or | 3077 | (with-current-buffer (process-buffer process) |
| 3063 | (with-local-quit | 3078 | (setq python--shell-output-filter-in-progress t) |
| 3064 | (python-shell-send-string string process) | 3079 | (cond (async |
| 3065 | (while python-shell-output-filter-in-progress | 3080 | (python-shell-send-string string process) |
| 3066 | ;; `python-shell-output-filter' takes care of setting | 3081 | (let ((timer (run-with-timer |
| 3067 | ;; `python-shell-output-filter-in-progress' to NIL after it | 3082 | python--async-comint-timeout |
| 3068 | ;; detects end of output. | 3083 | nil async nil))) |
| 3069 | (accept-process-output process)) | 3084 | (setq python--shell-output-filter-callback |
| 3070 | (prog1 | 3085 | (lambda (string) |
| 3071 | python-shell-output-filter-buffer | 3086 | (funcall async string) |
| 3072 | (setq python-shell-output-filter-buffer nil))) | 3087 | (cancel-timer timer))))) |
| 3073 | (with-current-buffer (process-buffer process) | 3088 | (t |
| 3074 | (comint-interrupt-subjob))))) | 3089 | (or |
| 3090 | (with-local-quit | ||
| 3091 | (python-shell-send-string string process) | ||
| 3092 | (while python--shell-output-filter-in-progress | ||
| 3093 | ;; `python-shell-output-filter' takes care of | ||
| 3094 | ;; setting `python--shell-output-filter-in-progress' | ||
| 3095 | ;; to NIL after it detects end of output. | ||
| 3096 | (accept-process-output process)) | ||
| 3097 | (prog1 | ||
| 3098 | python--shell-output-filter-buffer | ||
| 3099 | (setq python--shell-output-filter-buffer nil))) | ||
| 3100 | (with-current-buffer (process-buffer process) | ||
| 3101 | (comint-interrupt-subjob)))))))) | ||
| 3075 | 3102 | ||
| 3076 | (defun python-shell-internal-send-string (string) | 3103 | (defun python-shell-internal-send-string (string) |
| 3077 | "Send STRING to the Internal Python interpreter. | 3104 | "Send STRING to the Internal Python interpreter. |
| @@ -4532,27 +4559,46 @@ Returns the current symbol handling point within arguments." | |||
| 4532 | (python-util-forward-comment -1))) | 4559 | (python-util-forward-comment -1))) |
| 4533 | (python-info-current-symbol t))) | 4560 | (python-info-current-symbol t))) |
| 4534 | 4561 | ||
| 4535 | (defun python-eldoc--get-doc-at-point (&optional force-input force-process) | 4562 | (defun python-eldoc--get-doc-at-point (&optional force-input force-process async) |
| 4536 | "Internal implementation to get documentation at point. | 4563 | "Internal implementation to get documentation at point. |
| 4537 | If not FORCE-INPUT is passed then what `python-eldoc--get-symbol-at-point' | 4564 | If not FORCE-INPUT is passed then what `python-eldoc--get-symbol-at-point' |
| 4538 | returns will be used. If not FORCE-PROCESS is passed what | 4565 | returns will be used. If not FORCE-PROCESS is passed what |
| 4539 | `python-shell-get-process' returns is used." | 4566 | `python-shell-get-process' returns is used. |
| 4567 | |||
| 4568 | If ASYNC is non nil, should be a function accepting the | ||
| 4569 | documentation one or nil if there's no such thing. In that case, | ||
| 4570 | the function returns t. | ||
| 4571 | " | ||
| 4540 | (let ((process (or force-process (python-shell-get-process)))) | 4572 | (let ((process (or force-process (python-shell-get-process)))) |
| 4541 | (when process | 4573 | (when process |
| 4542 | (let* ((input (or force-input | 4574 | (let* ((input (or force-input |
| 4543 | (python-eldoc--get-symbol-at-point))) | 4575 | (python-eldoc--get-symbol-at-point))) |
| 4544 | (docstring | 4576 | (command (and input |
| 4545 | (when input | 4577 | (concat |
| 4546 | ;; Prevent resizing the echo area when iPython is | 4578 | python-eldoc-setup-code |
| 4547 | ;; enabled. Bug#18794. | 4579 | "\nprint(" (format python-eldoc-string-code input) ")")))) |
| 4548 | (python-util-strip-string | 4580 | (when command |
| 4581 | (cond (async | ||
| 4549 | (python-shell-send-string-no-output | 4582 | (python-shell-send-string-no-output |
| 4550 | (concat | 4583 | command |
| 4551 | python-eldoc-setup-code | 4584 | process |
| 4552 | "\nprint(" (format python-eldoc-string-code input) ")") | 4585 | (lambda (string) |
| 4553 | process))))) | 4586 | ;; Prevent resizing the echo area when iPython is |
| 4554 | (unless (zerop (length docstring)) | 4587 | ;; enabled. Bug#18794. |
| 4555 | docstring))))) | 4588 | (when string |
| 4589 | (setq string (python-util-strip-string string))) | ||
| 4590 | (funcall async | ||
| 4591 | (and (not (zerop (length string))) | ||
| 4592 | string)))) | ||
| 4593 | t) | ||
| 4594 | (t | ||
| 4595 | (let ((res | ||
| 4596 | (python-util-strip-string | ||
| 4597 | (python-shell-send-string-no-output | ||
| 4598 | command | ||
| 4599 | process)))) | ||
| 4600 | (unless (zerop (length res)) | ||
| 4601 | res))))))))) | ||
| 4556 | 4602 | ||
| 4557 | (defvar-local python-eldoc-get-doc t | 4603 | (defvar-local python-eldoc-get-doc t |
| 4558 | "Non-nil means eldoc should fetch the documentation | 4604 | "Non-nil means eldoc should fetch the documentation |
| @@ -4573,28 +4619,32 @@ returns will be used. If not FORCE-PROCESS is passed what | |||
| 4573 | :type 'boolean | 4619 | :type 'boolean |
| 4574 | :version "25.1") | 4620 | :version "25.1") |
| 4575 | 4621 | ||
| 4576 | (defun python-eldoc-function (&rest _ignored) | 4622 | (defun python-eldoc-function (callback &rest _ignored) |
| 4577 | "`eldoc-documentation-function' for Python. | 4623 | "A member of `eldoc-documentation-functions' for Python. |
| 4578 | For this to work as best as possible you should call | 4624 | For this to work as best as possible you should call |
| 4579 | `python-shell-send-buffer' from time to time so context in | 4625 | `python-shell-send-buffer' from time to time so context in |
| 4580 | inferior Python process is updated properly. | 4626 | inferior Python process is updated properly. |
| 4581 | 4627 | ||
| 4582 | If `python-eldoc-function-timeout' seconds elapse before this | 4628 | In systems with `eldoc-documentation-functions', CALLBACK is |
| 4583 | function returns then if | 4629 | handled accordingly, otherwise this function is acceptable for |
| 4630 | `eldoc-documentation-function'. | ||
| 4631 | |||
| 4632 | In the latter case if `python-eldoc-function-timeout' seconds | ||
| 4633 | elapse before this -function returns then if | ||
| 4584 | `python-eldoc-function-timeout-permanent' is non-nil | 4634 | `python-eldoc-function-timeout-permanent' is non-nil |
| 4585 | `python-eldoc-get-doc' will be set to nil and eldoc will no | 4635 | `python-eldoc-get-doc' will be set to nil and eldoc will no |
| 4586 | longer return the documentation at the point automatically. | 4636 | longer return the documentation at the point automatically." |
| 4587 | |||
| 4588 | Set `python-eldoc-get-doc' to t to reenable eldoc documentation | ||
| 4589 | fetching." | ||
| 4590 | (when python-eldoc-get-doc | 4637 | (when python-eldoc-get-doc |
| 4591 | (with-timeout (python-eldoc-function-timeout | 4638 | (if (boundp 'eldoc-documentation-strategy) |
| 4592 | (if python-eldoc-function-timeout-permanent | 4639 | (python-eldoc--get-doc-at-point nil nil callback) |
| 4593 | (progn | 4640 | (with-timeout |
| 4594 | (message "Eldoc echo-area display muted in this buffer, see `python-eldoc-function'") | 4641 | (python-eldoc-function-timeout |
| 4595 | (setq python-eldoc-get-doc nil)) | 4642 | (if python-eldoc-function-timeout-permanent |
| 4596 | (message "`python-eldoc-function' timed out, see `python-eldoc-function-timeout'"))) | 4643 | (progn |
| 4597 | (python-eldoc--get-doc-at-point)))) | 4644 | (message "Eldoc echo-area display muted in this buffer, see `python-eldoc-function'") |
| 4645 | (setq python-eldoc-get-doc nil)) | ||
| 4646 | (message "`python-eldoc-function' timed out, see `python-eldoc-function-timeout'"))) | ||
| 4647 | (python-eldoc--get-doc-at-point))))) | ||
| 4598 | 4648 | ||
| 4599 | (defun python-eldoc-at-point (symbol) | 4649 | (defun python-eldoc-at-point (symbol) |
| 4600 | "Get help on SYMBOL using `help'. | 4650 | "Get help on SYMBOL using `help'. |