+(defvar python-shell--prompt-calculated-input-regexp nil
+ "Calculated input prompt regexp for inferior python shell.
+Do not set this variable directly, instead use
+`python-shell-prompt-set-calculated-regexps'.")
+
+(defvar python-shell--prompt-calculated-output-regexp nil
+ "Calculated output prompt regexp for inferior python shell.
+Do not set this variable directly, instead use
+`python-shell-set-prompt-regexp'.")
+
+(defun python-shell-prompt-detect ()
+ "Detect prompts for the current `python-shell-interpreter'.
+When prompts can be retrieved successfully from the
+`python-shell-interpreter' run with
+`python-shell-interpreter-interactive-arg', returns a list of
+three elements, where the first two are input prompts and the
+last one is an output prompt. When no prompts can be detected
+and `python-shell-prompt-detect-failure-warning' is non-nil,
+shows a warning with instructions to avoid hangs and returns nil.
+When `python-shell-prompt-detect-enabled' is nil avoids any
+detection and just returns nil."
+ (when python-shell-prompt-detect-enabled
+ (let* ((process-environment (python-shell-calculate-process-environment))
+ (exec-path (python-shell-calculate-exec-path))
+ (code (concat
+ "import sys\n"
+ "ps = [getattr(sys, 'ps%s' % i, '') for i in range(1,4)]\n"
+ ;; JSON is built manually for compatibility
+ "ps_json = '\\n[\"%s\", \"%s\", \"%s\"]\\n' % tuple(ps)\n"
+ "print (ps_json)\n"
+ "sys.exit(0)\n"))
+ (output
+ (with-temp-buffer
+ ;; TODO: improve error handling by using
+ ;; `condition-case' and displaying the error message to
+ ;; the user in the no-prompts warning.
+ (ignore-errors
+ (let ((code-file (python-shell--save-temp-file code)))
+ ;; Use `process-file' as it is remote-host friendly.
+ (process-file
+ python-shell-interpreter
+ code-file
+ '(t nil)
+ nil
+ python-shell-interpreter-interactive-arg)
+ ;; Try to cleanup
+ (delete-file code-file)))
+ (buffer-string)))
+ (prompts
+ (catch 'prompts
+ (dolist (line (split-string output "\n" t))
+ (let ((res
+ ;; Check if current line is a valid JSON array
+ (and (string= (substring line 0 2) "[\"")
+ (ignore-errors
+ ;; Return prompts as a list, not vector
+ (append (json-read-from-string line) nil)))))
+ ;; The list must contain 3 strings, where the first
+ ;; is the input prompt, the second is the block
+ ;; prompt and the last one is the output prompt. The
+ ;; input prompt is the only one that can't be empty.
+ (when (and (= (length res) 3)
+ (cl-every #'stringp res)
+ (not (string= (car res) "")))
+ (throw 'prompts res))))
+ nil)))
+ (when (and (not prompts)
+ python-shell-prompt-detect-failure-warning)
+ (lwarn
+ '(python python-shell-prompt-regexp)
+ :warning
+ (concat
+ "Python shell prompts cannot be detected.\n"
+ "If your emacs session hangs when starting python shells\n"
+ "recover with `keyboard-quit' and then try fixing the\n"
+ "interactive flag for your interpreter by adjusting the\n"
+ "`python-shell-interpreter-interactive-arg' or add regexps\n"
+ "matching shell prompts in the directory-local friendly vars:\n"
+ " + `python-shell-prompt-regexp'\n"
+ " + `python-shell-prompt-block-regexp'\n"
+ " + `python-shell-prompt-output-regexp'\n"
+ "Or alternatively in:\n"
+ " + `python-shell-prompt-input-regexps'\n"
+ " + `python-shell-prompt-output-regexps'")))
+ prompts)))
+
+(defun python-shell-prompt-validate-regexps ()
+ "Validate all user provided regexps for prompts.
+Signals `user-error' if any of these vars contain invalid
+regexps: `python-shell-prompt-regexp',
+`python-shell-prompt-block-regexp',
+`python-shell-prompt-pdb-regexp',
+`python-shell-prompt-output-regexp',
+`python-shell-prompt-input-regexps',
+`python-shell-prompt-output-regexps'."
+ (dolist (symbol (list 'python-shell-prompt-input-regexps
+ 'python-shell-prompt-output-regexps
+ 'python-shell-prompt-regexp
+ 'python-shell-prompt-block-regexp
+ 'python-shell-prompt-pdb-regexp
+ 'python-shell-prompt-output-regexp))
+ (dolist (regexp (let ((regexps (symbol-value symbol)))
+ (if (listp regexps)
+ regexps
+ (list regexps))))
+ (when (not (python-util-valid-regexp-p regexp))
+ (user-error "Invalid regexp %s in `%s'"
+ regexp symbol)))))
+
+(defun python-shell-prompt-set-calculated-regexps ()
+ "Detect and set input and output prompt regexps.
+Build and set the values for `python-shell-input-prompt-regexp'
+and `python-shell-output-prompt-regexp' using the values from
+`python-shell-prompt-regexp', `python-shell-prompt-block-regexp',
+`python-shell-prompt-pdb-regexp',
+`python-shell-prompt-output-regexp',
+`python-shell-prompt-input-regexps',
+`python-shell-prompt-output-regexps' and detected prompts from
+`python-shell-prompt-detect'."
+ (when (not (and python-shell--prompt-calculated-input-regexp
+ python-shell--prompt-calculated-output-regexp))
+ (let* ((detected-prompts (python-shell-prompt-detect))
+ (input-prompts nil)
+ (output-prompts nil)
+ (build-regexp
+ (lambda (prompts)
+ (concat "^\\("
+ (mapconcat #'identity
+ (sort prompts
+ (lambda (a b)
+ (let ((length-a (length a))
+ (length-b (length b)))
+ (if (= length-a length-b)
+ (string< a b)
+ (> (length a) (length b))))))
+ "\\|")
+ "\\)"))))
+ ;; Validate ALL regexps
+ (python-shell-prompt-validate-regexps)
+ ;; Collect all user defined input prompts
+ (dolist (prompt (append python-shell-prompt-input-regexps
+ (list python-shell-prompt-regexp
+ python-shell-prompt-block-regexp
+ python-shell-prompt-pdb-regexp)))
+ (cl-pushnew prompt input-prompts :test #'string=))
+ ;; Collect all user defined output prompts
+ (dolist (prompt (cons python-shell-prompt-output-regexp
+ python-shell-prompt-output-regexps))
+ (cl-pushnew prompt output-prompts :test #'string=))
+ ;; Collect detected prompts if any
+ (when detected-prompts
+ (dolist (prompt (butlast detected-prompts))
+ (setq prompt (regexp-quote prompt))
+ (cl-pushnew prompt input-prompts :test #'string=))
+ (cl-pushnew (regexp-quote
+ (car (last detected-prompts)))
+ output-prompts :test #'string=))
+ ;; Set input and output prompt regexps from collected prompts
+ (setq python-shell--prompt-calculated-input-regexp
+ (funcall build-regexp input-prompts)
+ python-shell--prompt-calculated-output-regexp
+ (funcall build-regexp output-prompts)))))
+