Fix Python completion failure under certain conditions

* lisp/progmodes/python.el (python-shell-send-string-no-output):
Save and restore `comint-last-prompt-overlay' or `comint-last-prompt'.
* test/lisp/progmodes/python-tests.el (python-tests-shell-wait-for-prompt):
New helper function.
(python-tests-with-temp-buffer-with-shell): New helper macro.
(python-shell-completion-1, python-shell-completion-native-1)
(python-shell-completion-native-with-ffap-1)
(python-shell-completion-native-with-eldoc-1): New tests (bug#58389).
This commit is contained in:
kobarity 2022-10-10 22:24:17 +09:00 committed by Lars Ingebrigtsen
parent 36ab164496
commit b0585441a3
2 changed files with 111 additions and 8 deletions

View file

@ -3413,15 +3413,25 @@ detecting a prompt at the end of the buffer."
"Send STRING to PROCESS and inhibit output.
Return the output."
(or process (setq process (python-shell-get-process-or-error)))
(cl-letf (((process-filter process)
(lambda (_proc str)
(with-current-buffer (process-buffer process)
(python-shell-output-filter str))))
(python-shell-output-filter-in-progress t)
(inhibit-quit t))
(cl-letf* (((process-filter process)
(lambda (_proc str)
(with-current-buffer (process-buffer process)
(python-shell-output-filter str))))
(python-shell-output-filter-in-progress t)
(inhibit-quit t)
(buffer (process-buffer process))
(last-prompt (cond ((boundp 'comint-last-prompt-overlay)
'comint-last-prompt-overlay)
((boundp 'comint-last-prompt)
'comint-last-prompt)))
(last-prompt-value (buffer-local-value last-prompt buffer)))
(or
(with-local-quit
(python-shell-send-string string process)
(unwind-protect
(python-shell-send-string string process)
(when (not (null last-prompt))
(with-current-buffer buffer
(set last-prompt last-prompt-value))))
(while python-shell-output-filter-in-progress
;; `python-shell-output-filter' takes care of setting
;; `python-shell-output-filter-in-progress' to NIL after it
@ -3430,7 +3440,7 @@ Return the output."
(prog1
python-shell-output-filter-buffer
(setq python-shell-output-filter-buffer nil)))
(with-current-buffer (process-buffer process)
(with-current-buffer buffer
(comint-interrupt-subjob)))))
(defun python-shell-internal-send-string (string)

View file

@ -43,6 +43,37 @@ always located at the beginning of buffer."
(goto-char (point-min))
,@body)))
(defun python-tests-shell-wait-for-prompt ()
"Wait for the prompt in the shell buffer."
(python-shell-with-shell-buffer
(while (not (if-let ((prompt (python-util-comint-last-prompt)))
(python-shell-comint-end-of-output-p
(buffer-substring-no-properties
(car prompt) (cdr prompt)))))
(sit-for 0.1))))
(defmacro python-tests-with-temp-buffer-with-shell (contents &rest body)
"Create a `python-mode' enabled temp buffer with CONTENTS and `run-python'.
BODY is code to be executed within the temp buffer. Point is
always located at the beginning of buffer. Native completion is
turned off. Shell buffer will be killed on exit."
(declare (indent 1) (debug t))
`(with-temp-buffer
(let ((python-indent-guess-indent-offset nil)
(python-shell-completion-native-enable nil))
(python-mode)
(unwind-protect
(progn
(run-python nil t)
(insert ,contents)
(goto-char (point-min))
(python-tests-shell-wait-for-prompt)
,@body)
(when (python-shell-get-buffer)
(python-shell-with-shell-buffer
(let (kill-buffer-hook kill-buffer-query-functions)
(kill-buffer))))))))
(defmacro python-tests-with-temp-file (contents &rest body)
"Create a `python-mode' enabled file with CONTENTS.
BODY is code to be executed within the temp buffer. Point is
@ -4365,6 +4396,68 @@ def foo():
(python-shell-interpreter "/some/path/to/bin/pypy"))
(should (python-shell-completion-native-interpreter-disabled-p))))
(ert-deftest python-shell-completion-1 ()
(skip-unless (executable-find python-tests-shell-interpreter))
(python-tests-with-temp-buffer-with-shell
"
import abc
"
(let ((inhibit-message t))
(python-shell-send-buffer)
(python-tests-shell-wait-for-prompt)
(goto-char (point-max))
(insert "abc.")
(should (completion-at-point))
(insert "A")
(should (completion-at-point)))))
(ert-deftest python-shell-completion-native-1 ()
(skip-unless (executable-find python-tests-shell-interpreter))
(python-tests-with-temp-buffer-with-shell
"
import abc
"
(let ((inhibit-message t))
(python-shell-completion-native-turn-on)
(python-shell-send-buffer)
(python-tests-shell-wait-for-prompt)
(goto-char (point-max))
(insert "abc.")
(should (completion-at-point))
(insert "A")
(should (completion-at-point)))))
(ert-deftest python-shell-completion-native-with-ffap-1 ()
(skip-unless (executable-find python-tests-shell-interpreter))
(python-tests-with-temp-buffer-with-shell
"
import abc
"
(let ((inhibit-message t))
(python-shell-completion-native-turn-on)
(python-shell-send-buffer)
(python-tests-shell-wait-for-prompt)
(goto-char (point-max))
(insert "abc.")
;; This is called when FFAP is enabled and a find-file function is called.
(python-ffap-module-path "abc.")
(should (completion-at-point)))))
(ert-deftest python-shell-completion-native-with-eldoc-1 ()
(skip-unless (executable-find python-tests-shell-interpreter))
(python-tests-with-temp-buffer-with-shell
"
import abc
"
(let ((inhibit-message t))
(python-shell-completion-native-turn-on)
(python-shell-send-buffer)
(python-tests-shell-wait-for-prompt)
(goto-char (point-max))
(insert "abc.")
;; This is called by idle-timer when ElDoc is enabled.
(python-eldoc-function)
(should (completion-at-point)))))