From 56968aa61ccf3933dfcd083dd03f4be559a94efd Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 16 Jul 2014 21:39:35 +0300 Subject: [PATCH 01/15] Fix bug #18036 with infloop in redisplay with huge fringes. src/xdisp.c (move_it_vertically_backward, move_it_by_lines): Prevent infinite looping in redisplay when display lines don't have enough space to display even a single character. --- src/ChangeLog | 6 ++++++ src/xdisp.c | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ChangeLog b/src/ChangeLog index ca31b10fb6a..a664b12fe6e 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,9 @@ +2014-07-16 Eli Zaretskii + + * xdisp.c (move_it_vertically_backward, move_it_by_lines): Prevent + infinite looping in redisplay when display lines don't have enough + space to display even a single character. (Bug#18036) + 2014-07-13 Eli Zaretskii * xdisp.c (decode_mode_spec): Call file-remote-p on the current diff --git a/src/xdisp.c b/src/xdisp.c index 2f0683294fa..263f499dbfa 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -9351,7 +9351,7 @@ move_it_vertically_backward (struct it *it, int dy) /* Estimate how many newlines we must move back. */ nlines = max (1, dy / default_line_pixel_height (it->w)); - if (it->line_wrap == TRUNCATE) + if (it->line_wrap == TRUNCATE || nchars_per_row == 0) pos_limit = BEGV; else pos_limit = max (start_pos - nlines * nchars_per_row, BEGV); @@ -9606,7 +9606,7 @@ move_it_by_lines (struct it *it, ptrdiff_t dvpos) /* Go back -DVPOS buffer lines, but no farther than -DVPOS full screen lines, and reseat the iterator there. */ start_charpos = IT_CHARPOS (*it); - if (it->line_wrap == TRUNCATE) + if (it->line_wrap == TRUNCATE || nchars_per_row == 0) pos_limit = BEGV; else pos_limit = max (start_charpos + dvpos * nchars_per_row, BEGV); From 137448c0939848661fdf6e89075c1ac706d30c5e Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 17 Jul 2014 18:17:52 +0300 Subject: [PATCH 02/15] Fix part of bug #18035 with overlay strings on invisible text. src/xdisp.c (init_iterator): Initialize it->stop_charpos to the buffer position where we are to start the iteration. (handle_invisible_prop): Record in it->stop_charpos the position where the invisible text ends. --- src/ChangeLog | 7 +++++++ src/xdisp.c | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ChangeLog b/src/ChangeLog index a664b12fe6e..463c895e5da 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,10 @@ +2014-07-17 Eli Zaretskii + + * xdisp.c (init_iterator): Initialize it->stop_charpos to the + buffer position where we are to start the iteration. + (handle_invisible_prop): Record in it->stop_charpos the position + where the invisible text ends. (Bug#18035) + 2014-07-16 Eli Zaretskii * xdisp.c (move_it_vertically_backward, move_it_by_lines): Prevent diff --git a/src/xdisp.c b/src/xdisp.c index 263f499dbfa..3ab1dd76654 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -3034,6 +3034,7 @@ init_iterator (struct it *it, struct window *w, getting overlays and face properties from that position. */ if (charpos >= BUF_BEG (current_buffer)) { + it->stop_charpos = charpos; it->end_charpos = ZV; eassert (charpos == BYTE_TO_CHAR (bytepos)); IT_CHARPOS (*it) = charpos; @@ -4546,7 +4547,24 @@ handle_invisible_prop (struct it *it) && get_overlay_strings (it, it->stop_charpos)) { handled = HANDLED_RECOMPUTE_PROPS; - it->stack[it->sp - 1].display_ellipsis_p = display_ellipsis_p; + if (it->sp > 0) + { + it->stack[it->sp - 1].display_ellipsis_p = display_ellipsis_p; + /* The call to get_overlay_strings above recomputes + it->stop_charpos, but it only considers changes + in properties and overlays beyond iterator's + current position. This causes us to miss changes + that happen exactly where the invisible property + ended. So we play it safe here and force the + iterator to check for potential stop positions + immediately after the invisible text. Note that + if get_overlay_strings returns non-zero, it + normally also pushed the iterator stack, so we + need to update the stop position in the slot + below the current one. */ + it->stack[it->sp - 1].stop_charpos + = CHARPOS (it->stack[it->sp - 1].current.pos); + } } else if (display_ellipsis_p) { From 9180cde1125adced11ca5125ebed4e988d776539 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 17 Jul 2014 18:25:21 +0300 Subject: [PATCH 03/15] Fix assertion violation when restoring hscrolled window configurations. src/xdisp.c (hscroll_window_tree): Don't try hscrolling windows whose cursor row has zero buffer position as their start position. Reported by Martin Rudalics . --- src/ChangeLog | 3 +++ src/xdisp.c | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/ChangeLog b/src/ChangeLog index 463c895e5da..737a5078b24 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -4,6 +4,9 @@ buffer position where we are to start the iteration. (handle_invisible_prop): Record in it->stop_charpos the position where the invisible text ends. (Bug#18035) + (hscroll_window_tree): Don't try hscrolling windows whose cursor + row has zero buffer position as their start position. Reported by + martin rudalics . 2014-07-16 Eli Zaretskii diff --git a/src/xdisp.c b/src/xdisp.c index 3ab1dd76654..d1016b6b8d7 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -12880,6 +12880,13 @@ hscroll_window_tree (Lisp_Object window) h_margin = hscroll_margin * WINDOW_FRAME_COLUMN_WIDTH (w); if (!NILP (Fbuffer_local_value (Qauto_hscroll_mode, w->contents)) + /* In some pathological cases, like restoring a window + configuration into a frame that is much smaller than + the one from which the configuration was saved, we + get glyph rows whose start and end have zero buffer + positions, which we cannot handle below. Just skip + such windows. */ + && CHARPOS (cursor_row->start.pos) >= BUF_BEG (w->contents) /* For left-to-right rows, hscroll when cursor is either (i) inside the right hscroll margin, or (ii) if it is inside the left margin and the window is already From e8bd4b948b82fd47359e0cf9a0a9ebb802c01e78 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 17 Jul 2014 18:37:48 +0300 Subject: [PATCH 04/15] Fix setting up coding-systems for clipboard access on MS-Windows and MS-DOS. src/w32select.c (setup_windows_coding_system): Apply CODING_ANNOTATION_MASK to the common_flags member of struct coding_system. Reported by Martin Rudalics . src/w16select.c (Fw16_get_clipboard_data): Apply CODING_ANNOTATION_MASK to the common_flags member of struct coding_system. --- src/ChangeLog | 9 +++++++++ src/w16select.c | 2 +- src/w32select.c | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ChangeLog b/src/ChangeLog index 737a5078b24..d395e97246d 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,5 +1,14 @@ 2014-07-17 Eli Zaretskii + * w32select.c (setup_windows_coding_system): Apply + CODING_ANNOTATION_MASK to the common_flags member of struct + coding_system. Reported by martin rudalics . + + * w16select.c (Fw16_get_clipboard_data): Apply + CODING_ANNOTATION_MASK to the common_flags member of struct + coding_system. + + * xdisp.c (init_iterator): Initialize it->stop_charpos to the buffer position where we are to start the iteration. (handle_invisible_prop): Record in it->stop_charpos the position diff --git a/src/w16select.c b/src/w16select.c index 1e4d35b721b..4f88741f5c1 100644 --- a/src/w16select.c +++ b/src/w16select.c @@ -600,7 +600,7 @@ DEFUN ("w16-get-clipboard-data", Fw16_get_clipboard_data, Sw16_get_clipboard_dat coding.mode |= CODING_MODE_LAST_BLOCK; /* We explicitly disable composition handling because selection data should not contain any composition sequence. */ - coding.mode &= CODING_ANNOTATION_MASK; + coding.common_flags &= CODING_ANNOTATION_MASK; decode_coding_object (&coding, Qnil, 0, 0, truelen, truelen, Qt); ret = coding.dst_object; Vlast_coding_system_used = CODING_ID_NAME (coding.id); diff --git a/src/w32select.c b/src/w32select.c index 2ae25858bd2..7c21dde01a5 100644 --- a/src/w32select.c +++ b/src/w32select.c @@ -670,7 +670,7 @@ setup_windows_coding_system (Lisp_Object coding_system, which both apply to ISO6429 only. We don't know if these really need to be unset on Windows, but it probably doesn't hurt either. */ - coding->mode &= ~CODING_ANNOTATION_MASK; + coding->common_flags &= ~CODING_ANNOTATION_MASK; coding->mode |= CODING_MODE_LAST_BLOCK | CODING_MODE_SAFE_ENCODING; } From 449c428491f7f6cce7c576d9b9a057f38b86c50f Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 17 Jul 2014 18:39:07 +0300 Subject: [PATCH 05/15] Fix src/ChangeLog entry of last commit. --- src/ChangeLog | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ChangeLog b/src/ChangeLog index d395e97246d..8dda7baa6a4 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -8,7 +8,6 @@ CODING_ANNOTATION_MASK to the common_flags member of struct coding_system. - * xdisp.c (init_iterator): Initialize it->stop_charpos to the buffer position where we are to start the iteration. (handle_invisible_prop): Record in it->stop_charpos the position From 64384ca4ded41824f30a0a2470a65160dd486207 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 17 Jul 2014 18:40:18 +0300 Subject: [PATCH 06/15] Fix last commit of src/w16select.c. --- src/w16select.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/w16select.c b/src/w16select.c index 4f88741f5c1..c229ba5816a 100644 --- a/src/w16select.c +++ b/src/w16select.c @@ -600,7 +600,7 @@ DEFUN ("w16-get-clipboard-data", Fw16_get_clipboard_data, Sw16_get_clipboard_dat coding.mode |= CODING_MODE_LAST_BLOCK; /* We explicitly disable composition handling because selection data should not contain any composition sequence. */ - coding.common_flags &= CODING_ANNOTATION_MASK; + coding.common_flags &= ~CODING_ANNOTATION_MASK; decode_coding_object (&coding, Qnil, 0, 0, truelen, truelen, Qt); ret = coding.dst_object; Vlast_coding_system_used = CODING_ID_NAME (coding.id); From d949ade3c101981d015b3d78d061bdff584df13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Sat, 19 Jul 2014 10:13:07 -0300 Subject: [PATCH 07/15] Autodetect Python shell prompts. * lisp/progmodes/python.el: (python-shell-interpreter-interactive-arg) (python-shell-prompt-detect-enabled) (python-shell-prompt-detect-failure-warning) (python-shell-prompt-input-regexps) (python-shell-prompt-output-regexps): New vars. (python-shell-prompt-calculated-input-regexp) (python-shell-prompt-calculated-output-regexp): New vars. (python-shell-get-process-name) (python-shell-internal-get-process-name) (python-shell-output-filter) (python-shell-completion-get-completions): Use them. (python-shell-prompt-detect) (python-shell-prompt-validate-regexps): New functions. (python-shell-prompt-set-calculated-regexps): New function. (inferior-python-mode): Use it. Also honor overriden python-shell-interpreter and python-shell-interpreter-args. (python-shell-make-comint): Honor overriden python-shell-interpreter and python-shell-interpreter-args. (python-shell-get-or-create-process): Make it testable by allowing to call run-python non-interactively. (python-util-valid-regexp-p): New function. (python-shell-prompt-regexp, python-shell-prompt-block-regexp) (python-shell-prompt-output-regexp) (python-shell-prompt-pdb-regexp): Use it as defcustom :safe. * test/automated/python-tests.el (python-shell-make-comint-1): (python-shell-make-comint-2): Fix indentation. (python-shell-make-comint-3) (python-shell-make-comint-4): New tests. (python-shell-get-or-create-process-1): Fix test. (python-shell-get-or-create-process-2) (python-shell-get-or-create-process-3): New tests. (python-shell-internal-get-or-create-process-1): Fix test. (python-shell-prompt-detect-1): New test. (python-shell-prompt-detect-2): New test. (Bug#17370) (python-shell-prompt-detect-3) (python-shell-prompt-detect-4) (python-shell-prompt-detect-5) (python-shell-prompt-detect-6) (python-shell-prompt-validate-regexps-1) (python-shell-prompt-validate-regexps-2) (python-shell-prompt-validate-regexps-3) (python-shell-prompt-validate-regexps-4) (python-shell-prompt-validate-regexps-5) (python-shell-prompt-validate-regexps-6) (python-shell-prompt-validate-regexps-7) (python-shell-prompt-set-calculated-regexps-1) (python-shell-prompt-set-calculated-regexps-2) (python-shell-prompt-set-calculated-regexps-3) (python-shell-prompt-set-calculated-regexps-4) (python-shell-prompt-set-calculated-regexps-5) (python-shell-prompt-set-calculated-regexps-6) (python-util-valid-regexp-p-1): New tests. --- lisp/ChangeLog | 29 ++ lisp/progmodes/python.el | 318 +++++++++++++++++---- test/ChangeLog | 34 ++- test/automated/python-tests.el | 486 +++++++++++++++++++++++++++++---- 4 files changed, 773 insertions(+), 94 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 0e82c4bbc46..2a1266a6031 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,32 @@ +2014-07-17 Fabián Ezequiel Gallina + + Autodetect Python shell prompts. (Bug#17370) + * progmodes/python.el: + (python-shell-interpreter-interactive-arg) + (python-shell-prompt-detect-enabled) + (python-shell-prompt-detect-failure-warning) + (python-shell-prompt-input-regexps) + (python-shell-prompt-output-regexps): New vars. + (python-shell-prompt-calculated-input-regexp) + (python-shell-prompt-calculated-output-regexp): New vars. + (python-shell-get-process-name) + (python-shell-internal-get-process-name) + (python-shell-output-filter) + (python-shell-completion-get-completions): Use them. + (python-shell-prompt-detect) + (python-shell-prompt-validate-regexps): New functions. + (python-shell-prompt-set-calculated-regexps): New function. + (inferior-python-mode): Use it. Also honor overriden + python-shell-interpreter and python-shell-interpreter-args. + (python-shell-make-comint): Honor overriden + python-shell-interpreter and python-shell-interpreter-args. + (python-shell-get-or-create-process): Make it testable by allowing + to call run-python non-interactively. + (python-util-valid-regexp-p): New function. + (python-shell-prompt-regexp, python-shell-prompt-block-regexp) + (python-shell-prompt-output-regexp) + (python-shell-prompt-pdb-regexp): Use it as defcustom :safe. + 2014-07-16 Glenn Morris * desktop.el (after-init-hook): Disable startup frame restoration diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 237302f0530..e49d5882a61 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -4,7 +4,7 @@ ;; Author: Fabián E. Gallina ;; URL: https://github.com/fgallina/python.el -;; Version: 0.24.2 +;; Version: 0.24.4 ;; Maintainer: emacs-devel@gnu.org ;; Created: Jul 2010 ;; Keywords: languages @@ -62,13 +62,28 @@ ;; (add-hook 'python-mode-hook ;; (lambda () (setq forward-sexp-function nil))) -;; Shell interaction: is provided and allows you to execute easily any +;; Shell interaction: is provided and allows you to easily execute any ;; block of code of your current buffer in an inferior Python process. +;; This relies upon having prompts for input (e.g. ">>> " and "... " +;; in standard Python shell) and output (e.g. "Out[1]: " in iPython) +;; detected properly. Failing that Emacs may hang but, in the case +;; that happens, you can recover with \\[keyboard-quit]. To avoid +;; this issue, a two-step prompt autodetection mechanism is provided: +;; the first step is manual and consists of a collection of regular +;; expressions matching common prompts for Python shells stored in +;; `python-shell-prompt-input-regexps' and +;; `python-shell-prompt-output-regexps', and dir-local friendly vars +;; `python-shell-prompt-regexp', `python-shell-prompt-block-regexp', +;; `python-shell-prompt-output-regexp' which are appended to the +;; former automatically when a shell spawns; the second step is +;; automatic and depends on the `python-shell-prompt-detect' helper +;; function. See its docstring for details on global variables that +;; modify its behavior. ;; Shell completion: hitting tab will try to complete the current -;; word. Shell completion is implemented in a manner that if you -;; change the `python-shell-interpreter' to any other (for example -;; IPython) it should be easy to integrate another way to calculate +;; word. Shell completion is implemented in a way that if you change +;; the `python-shell-interpreter' to any other (for example IPython) +;; it should be easy to integrate another way to calculate ;; completions. You just need to specify your custom ;; `python-shell-completion-setup-code' and ;; `python-shell-completion-string-code'. @@ -79,8 +94,6 @@ ;; (setq ;; python-shell-interpreter "ipython" ;; python-shell-interpreter-args "" -;; python-shell-prompt-regexp "In \\[[0-9]+\\]: " -;; python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: " ;; python-shell-completion-setup-code ;; "from IPython.core.completerlib import module_completion" ;; python-shell-completion-module-string-code @@ -213,7 +226,9 @@ ;;; Code: (require 'ansi-color) +(require 'cl-lib) (require 'comint) +(require 'json) ;; Avoid compiler warnings (defvar view-return-to-alist) @@ -1706,33 +1721,56 @@ position, else returns nil." :type 'string :group 'python) -(defcustom python-shell-prompt-regexp ">>> " - "Regular expression matching top-level input prompt of Python shell. -It should not contain a caret (^) at the beginning." +(defcustom python-shell-interpreter-interactive-arg "-i" + "Interpreter argument to force it to run interactively." :type 'string - :group 'python - :safe 'stringp) + :version "24.4") -(defcustom python-shell-prompt-block-regexp "[.][.][.] " +(defcustom python-shell-prompt-detect-enabled t + "Non-nil enables autodetection of interpreter prompts." + :type 'boolean + :safe 'booleanp + :version "24.4") + +(defcustom python-shell-prompt-detect-failure-warning t + "Non-nil enables warnings when detection of prompts fail." + :type 'boolean + :safe 'booleanp + :version "24.4") + +(defcustom python-shell-prompt-input-regexps + '(">>> " "\\.\\.\\. " ; Python + "In \\[[0-9]+\\]: ") ; iPython + "List of regular expressions matching input prompts." + :type '(repeat string) + :version "24.4") + +(defcustom python-shell-prompt-output-regexps + '("" ; Python + "Out\\[[0-9]+\\]: ") ; iPython + "List of regular expressions matching output prompts." + :type '(repeat string) + :version "24.4") + +(defcustom python-shell-prompt-regexp ">>> " + "Regular expression matching top level input prompt of Python shell. +It should not contain a caret (^) at the beginning." + :type 'string) + +(defcustom python-shell-prompt-block-regexp "\\.\\.\\. " "Regular expression matching block input prompt of Python shell. It should not contain a caret (^) at the beginning." - :type 'string - :group 'python - :safe 'stringp) + :type 'string) (defcustom python-shell-prompt-output-regexp "" "Regular expression matching output prompt of Python shell. It should not contain a caret (^) at the beginning." - :type 'string - :group 'python - :safe 'stringp) + :type 'string) (defcustom python-shell-prompt-pdb-regexp "[(<]*[Ii]?[Pp]db[>)]+ " "Regular expression matching pdb input prompt of Python shell. It should not contain a caret (^) at the beginning." - :type 'string - :group 'python - :safe 'stringp) + :type 'string) (defcustom python-shell-enable-font-lock t "Should syntax highlighting be enabled in the Python shell buffer? @@ -1802,6 +1840,162 @@ virtualenv." :type '(alist string) :group 'python) +(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)) + (python-code-file + (python-shell--save-temp-file + (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 + (call-process + (executable-find python-shell-interpreter) + python-code-file + '(t nil) + nil + python-shell-interpreter-interactive-arg) + (ignore-errors (delete-file python-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) + (warn + (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))))) + (defun python-shell-get-process-name (dedicated) "Calculate the appropriate process name for inferior Python process. If DEDICATED is t and the variable `buffer-file-name' is non-nil @@ -1824,10 +2018,10 @@ uniqueness for different types of configurations." python-shell-internal-buffer-name (md5 (concat - (python-shell-parse-command) - python-shell-prompt-regexp - python-shell-prompt-block-regexp - python-shell-prompt-output-regexp + python-shell-interpreter + python-shell-interpreter-args + python-shell--prompt-calculated-input-regexp + python-shell--prompt-calculated-output-regexp (mapconcat #'symbol-value python-shell-setup-codes "") (mapconcat #'identity python-shell-process-environment "") (mapconcat #'identity python-shell-extra-pythonpaths "") @@ -1921,12 +2115,19 @@ initialization of the interpreter via `python-shell-setup-codes' variable. \(Type \\[describe-mode] in the process buffer for a list of commands.)" - (and python-shell--parent-buffer - (python-util-clone-local-variables python-shell--parent-buffer)) - (setq comint-prompt-regexp (format "^\\(?:%s\\|%s\\|%s\\)" - python-shell-prompt-regexp - python-shell-prompt-block-regexp - python-shell-prompt-pdb-regexp)) + (let ((interpreter python-shell-interpreter) + (args python-shell-interpreter-args)) + (when python-shell--parent-buffer + (python-util-clone-local-variables python-shell--parent-buffer)) + ;; Users can override default values for these vars when calling + ;; `run-python'. This ensures new values let-bound in + ;; `python-shell-make-comint' are locally set. + (set (make-local-variable 'python-shell-interpreter) interpreter) + (set (make-local-variable 'python-shell-interpreter-args) args)) + (set (make-local-variable 'python-shell--prompt-calculated-input-regexp) nil) + (set (make-local-variable 'python-shell--prompt-calculated-output-regexp) nil) + (python-shell-prompt-set-calculated-regexps) + (setq comint-prompt-regexp python-shell--prompt-calculated-input-regexp) (setq mode-line-process '(":%s")) (make-local-variable 'comint-output-filter-functions) (add-hook 'comint-output-filter-functions @@ -1989,10 +2190,20 @@ killed." (exec-path (python-shell-calculate-exec-path))) (when (not (comint-check-proc proc-buffer-name)) (let* ((cmdlist (split-string-and-unquote cmd)) + (interpreter (car cmdlist)) + (args (cdr cmdlist)) (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name - (car cmdlist) nil (cdr cmdlist))) + interpreter nil args)) (python-shell--parent-buffer (current-buffer)) - (process (get-buffer-process buffer))) + (process (get-buffer-process buffer)) + ;; As the user may have overriden default values for + ;; these vars on `run-python', let-binding them allows + ;; to have the new right values in all setup code + ;; that's is done in `inferior-python-mode', which is + ;; important, especially for prompt detection. + (python-shell-interpreter interpreter) + (python-shell-interpreter-args + (mapconcat #'identity args " "))) (with-current-buffer buffer (inferior-python-mode)) (accept-process-output process) @@ -2064,8 +2275,12 @@ startup." "Return inferior Python process for current buffer." (get-buffer-process (python-shell-get-buffer))) -(defun python-shell-get-or-create-process () - "Get or create an inferior Python process for current buffer and return it." +(defun python-shell-get-or-create-process (&optional cmd dedicated show) + "Get or create an inferior Python process for current buffer and return it. +Arguments CMD, DEDICATED and SHOW are those of `run-python' and +are used to start the shell. If those arguments are not +provided, `run-python' is called interactively and the user will +be asked for their values." (let* ((dedicated-proc-name (python-shell-get-process-name t)) (dedicated-proc-buffer-name (format "*%s*" dedicated-proc-name)) (global-proc-name (python-shell-get-process-name nil)) @@ -2074,7 +2289,11 @@ startup." (global-running (comint-check-proc global-proc-buffer-name)) (current-prefix-arg 16)) (when (and (not dedicated-running) (not global-running)) - (if (call-interactively 'run-python) + (if (if (not cmd) + ;; XXX: Refactor code such that calling `run-python' + ;; interactively is not needed anymore. + (call-interactively 'run-python) + (run-python cmd dedicated show)) (setq dedicated-running t) (setq global-running t))) ;; Always prefer dedicated @@ -2157,10 +2376,13 @@ detecting a prompt at the end of the buffer." (when (string-match ;; XXX: It seems on OSX an extra carriage return is attached ;; at the end of output, this handles that too. - (format "\r?\n\\(?:%s\\|%s\\|%s\\)$" - python-shell-prompt-regexp - python-shell-prompt-block-regexp - python-shell-prompt-pdb-regexp) + (concat + "\r?\n" + ;; Remove initial caret from calculated regexp + (replace-regexp-in-string + (rx string-start ?^) "" + python-shell--prompt-calculated-input-regexp) + "$") python-shell-output-filter-buffer) ;; Output ends when `python-shell-output-filter-buffer' contains ;; the prompt attached at the end of it. @@ -2168,9 +2390,9 @@ detecting a prompt at the end of the buffer." python-shell-output-filter-buffer (substring python-shell-output-filter-buffer 0 (match-beginning 0))) - (when (and (> (length python-shell-prompt-output-regexp) 0) - (string-match (concat "^" python-shell-prompt-output-regexp) - python-shell-output-filter-buffer)) + (when (string-match + python-shell--prompt-calculated-output-regexp + python-shell-output-filter-buffer) ;; Some shells, like iPython might append a prompt before the ;; output, clean that. (setq python-shell-output-filter-buffer @@ -2456,11 +2678,11 @@ LINE is used to detect the context on how to complete given INPUT." ((and (> (length python-shell-completion-module-string-code) 0) (string-match - (concat "^" python-shell-prompt-regexp) prompt) + python-shell--prompt-calculated-input-regexp prompt) (string-match "^[ \t]*\\(from\\|import\\)[ \t]" line)) 'import) ((string-match - (concat "^" python-shell-prompt-regexp) prompt) + python-shell--prompt-calculated-input-regexp prompt) 'default) (t nil))) (completion-code @@ -3706,6 +3928,10 @@ returned as is." "" string)) +(defun python-util-valid-regexp-p (regexp) + "Return non-nil if REGEXP is valid." + (ignore-errors (string-match regexp "") t)) + (defun python-electric-pair-string-delimiter () (when (and electric-pair-mode diff --git a/test/ChangeLog b/test/ChangeLog index cf4ddc83544..b4b3bedcbdc 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,34 @@ +2014-07-17 Fabián Ezequiel Gallina + + * automated/python-tests.el (python-shell-make-comint-1): + (python-shell-make-comint-2): Fix indentation. + (python-shell-make-comint-3) + (python-shell-make-comint-4): New tests. + (python-shell-get-or-create-process-1): Fix test. + (python-shell-get-or-create-process-2) + (python-shell-get-or-create-process-3): New tests. + (python-shell-internal-get-or-create-process-1): Fix test. + (python-shell-prompt-detect-1): New test. + (python-shell-prompt-detect-2): New test. (Bug#17370) + (python-shell-prompt-detect-3) + (python-shell-prompt-detect-4) + (python-shell-prompt-detect-5) + (python-shell-prompt-detect-6) + (python-shell-prompt-validate-regexps-1) + (python-shell-prompt-validate-regexps-2) + (python-shell-prompt-validate-regexps-3) + (python-shell-prompt-validate-regexps-4) + (python-shell-prompt-validate-regexps-5) + (python-shell-prompt-validate-regexps-6) + (python-shell-prompt-validate-regexps-7) + (python-shell-prompt-set-calculated-regexps-1) + (python-shell-prompt-set-calculated-regexps-2) + (python-shell-prompt-set-calculated-regexps-3) + (python-shell-prompt-set-calculated-regexps-4) + (python-shell-prompt-set-calculated-regexps-5) + (python-shell-prompt-set-calculated-regexps-6) + (python-util-valid-regexp-p-1): New tests. + 2014-07-09 Fabián Ezequiel Gallina * automated/python-tests.el @@ -34,13 +65,12 @@ (python-info-dedenter-statement-p-4) (python-info-dedenter-statement-p-5): New tests. - 2014-07-01 Fabián Ezequiel Gallina * automated/python-tests.el (python-tests-self-insert): New function. (python-triple-quote-pairing): Use it. - (python-util-forward-comment-1): New test. (Bug#17658) + (python-parens-electric-indent-1): New test. (Bug#17658) 2014-06-28 Leo Liu diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index 3a4eda36bfe..a60da31e44c 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el @@ -1773,8 +1773,8 @@ Using `python-shell-interpreter' and (proc-name (python-shell-get-process-name nil)) (shell-buffer (python-tests-with-temp-buffer - "" (python-shell-make-comint - (python-shell-parse-command) proc-name))) + "" (python-shell-make-comint + (python-shell-parse-command) proc-name))) (process (get-buffer-process shell-buffer))) (unwind-protect (progn @@ -1794,8 +1794,8 @@ Using `python-shell-interpreter' and (proc-name (python-shell-internal-get-process-name)) (shell-buffer (python-tests-with-temp-buffer - "" (python-shell-make-comint - (python-shell-parse-command) proc-name nil t))) + "" (python-shell-make-comint + (python-shell-parse-command) proc-name nil t))) (process (get-buffer-process shell-buffer))) (unwind-protect (progn @@ -1806,6 +1806,79 @@ Using `python-shell-interpreter' and (should (string= (buffer-name) (format " *%s*" proc-name))))) (kill-buffer shell-buffer)))) +(ert-deftest python-shell-make-comint-3 () + "Check comint creation with overriden python interpreter and args. +The command passed to `python-shell-make-comint' as argument must +locally override global values set in `python-shell-interpreter' +and `python-shell-interpreter-args' in the new shell buffer." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((python-shell-setup-codes nil) + (python-shell-interpreter "interpreter") + (python-shell-interpreter-args "--some-args") + (proc-name (python-shell-get-process-name nil)) + (interpreter-override + (concat (executable-find python-tests-shell-interpreter) " " "-i")) + (shell-buffer + (python-tests-with-temp-buffer + "" (python-shell-make-comint interpreter-override proc-name nil))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (string= python-shell-interpreter + (executable-find python-tests-shell-interpreter))) + (should (string= python-shell-interpreter-args "-i")))) + (kill-buffer shell-buffer)))) + +(ert-deftest python-shell-make-comint-4 () + "Check shell calculated prompts regexps are set." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (python-shell-setup-codes nil) + (python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (python-shell-interpreter-args "-i") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled t) + (python-shell-prompt-input-regexps '("extralargeinputprompt" "sml")) + (python-shell-prompt-output-regexps '("extralargeoutputprompt" "sml")) + (python-shell-prompt-regexp "in") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdf") + (python-shell-prompt-output-regexp "output") + (startup-code (concat "import sys\n" + "sys.ps1 = 'py> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'out '\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (proc-name (python-shell-get-process-name nil)) + (shell-buffer + (progn + (setenv "PYTHONSTARTUP" startup-file) + (python-tests-with-temp-buffer + "" (python-shell-make-comint + (python-shell-parse-command) proc-name nil)))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (string= + python-shell--prompt-calculated-input-regexp + (concat "^\\(extralargeinputprompt\\|\\.\\.> \\|" + "block\\|py> \\|pdf\\|sml\\|in\\)"))) + (should (string= + python-shell--prompt-calculated-output-regexp + "^\\(extralargeoutputprompt\\|output\\|out \\|sml\\)")))) + (delete-file startup-file) + (kill-buffer shell-buffer)))) + (ert-deftest python-shell-get-process-1 () "Check dedicated shell process preference over global." (skip-unless (executable-find python-tests-shell-interpreter)) @@ -1840,54 +1913,370 @@ Using `python-shell-interpreter' and (ignore-errors (kill-buffer dedicated-shell-buffer)))))) (ert-deftest python-shell-get-or-create-process-1 () - "Check shell process creation fallback." - :expected-result :failed + "Check shell dedicated process creation." + (skip-unless (executable-find python-tests-shell-interpreter)) (python-tests-with-temp-file - "" - ;; XXX: Break early until we can skip stuff. We need to mimic - ;; user interaction because `python-shell-get-or-create-process' - ;; asks for all arguments interactively when a shell process - ;; doesn't exist. - (should nil) - (let* ((python-shell-interpreter - (executable-find python-tests-shell-interpreter)) - (use-dialog-box) - (dedicated-process-name (python-shell-get-process-name t)) - (dedicated-process (python-shell-get-or-create-process)) - (dedicated-shell-buffer (process-buffer dedicated-process))) - (unwind-protect - (progn - (set-process-query-on-exit-flag dedicated-process nil) - ;; Prefer dedicated if not buffer exist. - (should (equal (process-name dedicated-process) - dedicated-process-name)) - (kill-buffer dedicated-shell-buffer) - ;; No buffer available. - (should (not (python-shell-get-process)))) - (ignore-errors (kill-buffer dedicated-shell-buffer)))))) + "" + (let* ((python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (use-dialog-box) + (dedicated-process-name (python-shell-get-process-name t)) + (dedicated-process + (python-shell-get-or-create-process python-shell-interpreter t)) + (dedicated-shell-buffer (process-buffer dedicated-process))) + (unwind-protect + (progn + (set-process-query-on-exit-flag dedicated-process nil) + ;; should be dedicated. + (should (equal (process-name dedicated-process) + dedicated-process-name)) + (kill-buffer dedicated-shell-buffer) + ;; Check there are no processes for current buffer. + (should (not (python-shell-get-process)))) + (ignore-errors (kill-buffer dedicated-shell-buffer)))))) + +(ert-deftest python-shell-get-or-create-process-2 () + "Check shell global process creation." + (skip-unless (executable-find python-tests-shell-interpreter)) + (python-tests-with-temp-file + "" + (let* ((python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (use-dialog-box) + (process-name (python-shell-get-process-name nil)) + (process + (python-shell-get-or-create-process python-shell-interpreter)) + (shell-buffer (process-buffer process))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + ;; should be global. + (should (equal (process-name process) process-name)) + (kill-buffer shell-buffer) + ;; Check there are no processes for current buffer. + (should (not (python-shell-get-process)))) + (ignore-errors (kill-buffer dedicated-shell-buffer)))))) + +(ert-deftest python-shell-get-or-create-process-3 () + "Check shell dedicated/global process preference." + (skip-unless (executable-find python-tests-shell-interpreter)) + (python-tests-with-temp-file + "" + (let* ((python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (use-dialog-box) + (dedicated-process-name (python-shell-get-process-name t)) + (global-process) + (dedicated-process)) + (unwind-protect + (progn + ;; Create global process + (run-python python-shell-interpreter nil) + (setq global-process (get-buffer-process "*Python*")) + (should global-process) + (set-process-query-on-exit-flag global-process nil) + ;; Create dedicated process + (run-python python-shell-interpreter t) + (setq dedicated-process (get-process dedicated-process-name)) + (should dedicated-process) + (set-process-query-on-exit-flag dedicated-process nil) + ;; Prefer dedicated. + (should (equal (python-shell-get-or-create-process) + dedicated-process)) + ;; Kill the dedicated so the global takes over. + (kill-buffer (process-buffer dedicated-process)) + ;; Detect global. + (should (equal (python-shell-get-or-create-process) global-process)) + ;; Kill the global. + (kill-buffer (process-buffer global-process)) + ;; Check there are no processes for current buffer. + (should (not (python-shell-get-process)))) + (ignore-errors (kill-buffer dedicated-shell-buffer)))))) (ert-deftest python-shell-internal-get-or-create-process-1 () "Check internal shell process creation fallback." (skip-unless (executable-find python-tests-shell-interpreter)) (python-tests-with-temp-file - "" - (should (not (process-live-p (python-shell-internal-get-process-name)))) - (let* ((python-shell-interpreter - (executable-find python-tests-shell-interpreter)) - (internal-process-name (python-shell-internal-get-process-name)) - (internal-process (python-shell-internal-get-or-create-process)) - (internal-shell-buffer (process-buffer internal-process))) - (unwind-protect - (progn - (set-process-query-on-exit-flag internal-process nil) - (should (equal (process-name internal-process) - internal-process-name)) - (should (equal internal-process - (python-shell-internal-get-or-create-process))) - ;; No user buffer available. - (should (not (python-shell-get-process))) - (kill-buffer internal-shell-buffer)) - (ignore-errors (kill-buffer internal-shell-buffer)))))) + "" + (should (not (process-live-p (python-shell-internal-get-process-name)))) + (let* ((python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (internal-process-name (python-shell-internal-get-process-name)) + (internal-process (python-shell-internal-get-or-create-process)) + (internal-shell-buffer (process-buffer internal-process))) + (unwind-protect + (progn + (set-process-query-on-exit-flag internal-process nil) + (should (equal (process-name internal-process) + internal-process-name)) + (should (equal internal-process + (python-shell-internal-get-or-create-process))) + ;; Assert the internal process is not a user process + (should (not (python-shell-get-process))) + (kill-buffer internal-shell-buffer)) + (ignore-errors (kill-buffer internal-shell-buffer)))))) + +(ert-deftest python-shell-prompt-detect-1 () + "Check prompt autodetection." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let ((process-environment process-environment)) + ;; Ensure no startup file is enabled + (setenv "PYTHONSTARTUP" "") + (should python-shell-prompt-detect-enabled) + (should (equal (python-shell-prompt-detect) '(">>> " "... " ""))))) + +(ert-deftest python-shell-prompt-detect-2 () + "Check prompt autodetection with startup file. Bug#17370." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = 'py> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'out '\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + ;; Ensure startup file is enabled + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-enabled) + (should (equal (python-shell-prompt-detect) '("py> " "..> " "out ")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-3 () + "Check prompts are not autodetected when feature is disabled." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let ((process-environment process-environment) + (python-shell-prompt-detect-enabled nil)) + ;; Ensure no startup file is enabled + (should (not python-shell-prompt-detect-enabled)) + (should (not (python-shell-prompt-detect))))) + +(ert-deftest python-shell-prompt-detect-4 () + "Check warning is shown when detection fails." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + ;; Trigger failure by removing prompts in the startup file + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-failure-warning) + (should python-shell-prompt-detect-enabled) + (should (not (python-shell-prompt-detect))) + (should (get-buffer "*Warnings*"))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-5 () + "Check disabled warnings are not shown when detection fails." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (python-shell-prompt-detect-failure-warning nil)) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should (not python-shell-prompt-detect-failure-warning)) + (should python-shell-prompt-detect-enabled) + (should (not (python-shell-prompt-detect))) + (should (not (get-buffer "*Warnings*")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-6 () + "Warnings are not shown when detection is disabled." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (python-shell-prompt-detect-failure-warning t) + (python-shell-prompt-detect-enabled nil)) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-failure-warning) + (should (not python-shell-prompt-detect-enabled)) + (should (not (python-shell-prompt-detect))) + (should (not (get-buffer "*Warnings*")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-validate-regexps-1 () + "Check `python-shell-prompt-input-regexps' are validated." + (let* ((python-shell-prompt-input-regexps '("\\(")) + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-input-regexps'")))) + +(ert-deftest python-shell-prompt-validate-regexps-2 () + "Check `python-shell-prompt-output-regexps' are validated." + (let* ((python-shell-prompt-output-regexps '("\\(")) + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-output-regexps'")))) + +(ert-deftest python-shell-prompt-validate-regexps-3 () + "Check `python-shell-prompt-regexp' is validated." + (let* ((python-shell-prompt-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-regexp'")))) + +(ert-deftest python-shell-prompt-validate-regexps-4 () + "Check `python-shell-prompt-block-regexp' is validated." + (let* ((python-shell-prompt-block-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-block-regexp'")))) + +(ert-deftest python-shell-prompt-validate-regexps-5 () + "Check `python-shell-prompt-pdb-regexp' is validated." + (let* ((python-shell-prompt-pdb-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-pdb-regexp'")))) + +(ert-deftest python-shell-prompt-validate-regexps-6 () + "Check `python-shell-prompt-output-regexp' is validated." + (let* ((python-shell-prompt-output-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-output-regexp'")))) + +(ert-deftest python-shell-prompt-validate-regexps-7 () + "Check default regexps are valid." + ;; should not signal error + (python-shell-prompt-validate-regexps)) + +(ert-deftest python-shell-prompt-set-calculated-regexps-1 () + "Check regexps are validated." + (let* ((python-shell-prompt-output-regexp '("\\(")) + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil) + (error-data (should-error (python-shell-prompt-set-calculated-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + "Invalid regexp \\( in `python-shell-prompt-output-regexp'")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-2 () + "Check `python-shell-prompt-input-regexps' are set." + (let* ((python-shell-prompt-input-regexps '("my" "prompt")) + (python-shell-prompt-output-regexps '("")) + (python-shell-prompt-regexp "") + (python-shell-prompt-block-regexp "") + (python-shell-prompt-pdb-regexp "") + (python-shell-prompt-output-regexp "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(prompt\\|my\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-3 () + "Check `python-shell-prompt-output-regexps' are set." + (let* ((python-shell-prompt-input-regexps '("")) + (python-shell-prompt-output-regexps '("my" "prompt")) + (python-shell-prompt-regexp "") + (python-shell-prompt-block-regexp "") + (python-shell-prompt-pdb-regexp "") + (python-shell-prompt-output-regexp "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(prompt\\|my\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-4 () + "Check user defined prompts are set." + (let* ((python-shell-prompt-input-regexps '("")) + (python-shell-prompt-output-regexps '("")) + (python-shell-prompt-regexp "prompt") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdb") + (python-shell-prompt-output-regexp "output") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(prompt\\|block\\|pdb\\|\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(output\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-5 () + "Check order of regexps (larger first)." + (let* ((python-shell-prompt-input-regexps '("extralargeinputprompt" "sml")) + (python-shell-prompt-output-regexps '("extralargeoutputprompt" "sml")) + (python-shell-prompt-regexp "in") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdf") + (python-shell-prompt-output-regexp "output") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(extralargeinputprompt\\|block\\|pdf\\|sml\\|in\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(extralargeoutputprompt\\|output\\|sml\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-6 () + "Check detected prompts are included `regexp-quote'd." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((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 "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled t) + (process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = 'p.> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'o.t '\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + (setenv "PYTHONSTARTUP" startup-file) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(\\.\\.> \\|p\\.> \\|\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(o\\.t \\|\\)"))) + (ignore-errors (delete-file startup-file))))) ;;; Shell completion @@ -3269,6 +3658,11 @@ def foo(a, b, c): (python-util-forward-comment -1) (should (= (point) (point-min))))) +(ert-deftest python-util-valid-regexp-p-1 () + (should (python-util-valid-regexp-p "")) + (should (python-util-valid-regexp-p python-shell-prompt-regexp)) + (should (not (python-util-valid-regexp-p "\\(")))) + ;;; Electricity From eb8cb39e8930b476e20bb8434e28a350e46ece34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Sat, 19 Jul 2014 16:19:47 -0300 Subject: [PATCH 08/15] Fix Python shell prompts detection for remote hosts. * lisp/progmodes/python.el (python-shell-prompt-detect): Replace call-process with process-file and make it more robust. --- lisp/ChangeLog | 6 ++++++ lisp/progmodes/python.el | 37 +++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 2a1266a6031..437b2225ce1 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,9 @@ +2014-07-19 Fabián Ezequiel Gallina + + Fix Python shell prompts detection for remote hosts. + * progmodes/python.el (python-shell-prompt-detect): Replace + call-process with process-file and make it more robust. + 2014-07-17 Fabián Ezequiel Gallina Autodetect Python shell prompts. (Bug#17370) diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index e49d5882a61..870640e4c55 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -1864,24 +1864,29 @@ 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)) - (python-code-file - (python-shell--save-temp-file - (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"))) + (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 - (call-process - (executable-find python-shell-interpreter) - python-code-file - '(t nil) - nil - python-shell-interpreter-interactive-arg) - (ignore-errors (delete-file python-code-file)) + ;; 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 + (executable-find 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 From 880b716696b4d8737e7199d6487f17b7e6825dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dj=C3=A4rv?= Date: Sun, 20 Jul 2014 15:18:47 +0200 Subject: [PATCH 09/15] * macfont.h (macfont_update_antialias_threshold): Declare. * macfont.m (macfont_update_antialias_threshold): Remove static. * nsterm.h (EmacsApp): Add antialiasThresholdDidChange. * nsterm.m (applicationDidFinishLaunching:): Call antialiasThresholdDidChange, register for antialias changes. (antialiasThresholdDidChange:): New method for EmacsApp. Fixes: debbugs:17534 --- src/ChangeLog | 12 ++++++++++++ src/macfont.h | 1 + src/macfont.m | 2 +- src/nsterm.h | 1 + src/nsterm.m | 21 +++++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/ChangeLog b/src/ChangeLog index 8dda7baa6a4..f39526f4f4b 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,15 @@ +2014-07-20 Jan Djärv + + * nsterm.m (applicationDidFinishLaunching:): Call + antialiasThresholdDidChange, register for antialias changes (Bug#17534). + (antialiasThresholdDidChange:): New method for EmacsApp. + + * nsterm.h (EmacsApp): Add antialiasThresholdDidChange. + + * macfont.m (macfont_update_antialias_threshold): Remove static. + + * macfont.h (macfont_update_antialias_threshold): Declare. + 2014-07-17 Eli Zaretskii * w32select.c (setup_windows_coding_system): Apply diff --git a/src/macfont.h b/src/macfont.h index 8b451357e42..7421cd63a79 100644 --- a/src/macfont.h +++ b/src/macfont.h @@ -144,4 +144,5 @@ typedef const struct _EmacsScreenFont *ScreenFontRef; /* opaque */ extern void mac_register_font_driver (struct frame *f); extern void *macfont_get_nsctfont (struct font *font); +extern void macfont_update_antialias_threshold (void); diff --git a/src/macfont.m b/src/macfont.m index 075b512e686..1bb3fb14134 100644 --- a/src/macfont.m +++ b/src/macfont.m @@ -729,7 +729,7 @@ static const struct static CGFloat macfont_antialias_threshold; -static void +void macfont_update_antialias_threshold (void) { int threshold; diff --git a/src/nsterm.h b/src/nsterm.h index 74789634a90..8c3e4e5c314 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -111,6 +111,7 @@ typedef float EmacsCGFloat; #endif } - (void)logNotification: (NSNotification *)notification; +- (void)antialiasThresholdDidChange:(NSNotification *)notification; - (void)sendEvent: (NSEvent *)theEvent; - (void)showPreferencesWindow: (id)sender; - (BOOL) openFile: (NSString *)fileName; diff --git a/src/nsterm.m b/src/nsterm.m index c7cb4faa3b7..4b1ebb2b516 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -4668,9 +4668,30 @@ ns_term_shutdown (int sig) ((EmacsApp *)self)->applicationDidFinishLaunchingCalled = YES; #endif [NSApp setServicesProvider: NSApp]; + + [self antialiasThresholdDidChange:nil]; +#ifdef NS_IMPL_COCOA +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(antialiasThresholdDidChange:) + name:NSAntialiasThresholdChangedNotification + object:nil]; +#endif +#endif + ns_send_appdefined (-2); } +- (void)antialiasThresholdDidChange:(NSNotification *)notification +{ +#ifdef NS_IMPL_COCOA +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + macfont_update_antialias_threshold (); +#endif +#endif +} + /* Termination sequences: C-x C-c: From b06a0dff84b2ec6b32e5740c17d595f6874e884f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Sun, 20 Jul 2014 15:12:30 -0300 Subject: [PATCH 10/15] Make python.el work with IPython automatically. * lisp/progmodes/python.el: (python-shell-completion-setup-code): New value supporting iPython. (python-shell-completion-string-code): New value supporting iPython. (python-shell-completion-get-completions): Use them. (python-shell-completion-module-string-code): Make obsolete. (python-shell-prompt-input-regexps) (python-shell-prompt-output-regexps): Add safeguard for ipdb. (python-shell-output-filter): Fix comment typo. * test/automated/python-tests.el: (python-util-clone-local-variables-1): Fix test. Fixes: debbugs:15510 --- etc/NEWS | 17 +++ lisp/ChangeLog | 12 ++ lisp/progmodes/python.el | 206 +++++++++++++++++---------------- test/ChangeLog | 5 + test/automated/python-tests.el | 2 - 5 files changed, 138 insertions(+), 104 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 2f3a3d0e38e..19356f7473b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -670,6 +670,23 @@ display a "Homepage" header.) ** In Prolog mode, `prolog-use-smie' has been removed, along with the non-SMIE indentation code. +** Python mode + +*** Out of the box support for CPython, iPython and readline based shells. +**** `python-shell-completion-module-string-code` is no longer used. + +*** Automatic shell prompt detection. +**** New user options: +***** `python-shell-interpreter-interactive-arg`. +***** `python-shell-prompt-detect-enabled`. +***** `python-shell-prompt-detect-failure-warning`. +***** `python-shell-prompt-input-regexps`. +***** `python-shell-prompt-output-regexps`. + +*** Python shell support for remote hosts via tramp. + +*** Correct display of line numbers for code sent to the Python shell. + ** Remember *** The new command `remember-notes' creates a buffer that is saved on exit. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 437b2225ce1..100d57f3893 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,15 @@ +2014-07-20 Fabián Ezequiel Gallina + + Make python.el work with IPython automatically. (Bug#15510) + * progmodes/python.el: + (python-shell-completion-setup-code): New value supporting iPython. + (python-shell-completion-string-code): New value supporting iPython. + (python-shell-completion-get-completions): Use them. + (python-shell-completion-module-string-code): Make obsolete. + (python-shell-prompt-input-regexps) + (python-shell-prompt-output-regexps): Add safeguard for ipdb. + (python-shell-output-filter): Fix comment typo. + 2014-07-19 Fabián Ezequiel Gallina Fix Python shell prompts detection for remote hosts. diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 870640e4c55..229874d3d51 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -62,16 +62,46 @@ ;; (add-hook 'python-mode-hook ;; (lambda () (setq forward-sexp-function nil))) -;; Shell interaction: is provided and allows you to easily execute any -;; block of code of your current buffer in an inferior Python process. -;; This relies upon having prompts for input (e.g. ">>> " and "... " -;; in standard Python shell) and output (e.g. "Out[1]: " in iPython) -;; detected properly. Failing that Emacs may hang but, in the case -;; that happens, you can recover with \\[keyboard-quit]. To avoid -;; this issue, a two-step prompt autodetection mechanism is provided: -;; the first step is manual and consists of a collection of regular -;; expressions matching common prompts for Python shells stored in -;; `python-shell-prompt-input-regexps' and +;; Shell interaction: is provided and allows opening Python shells +;; inside Emacs and executing any block of code of your current buffer +;; in that inferior Python process. + +;; Besides that only the standard CPython (2.x and 3.x) shell and +;; IPython are officially supported out of the box, the interaction +;; should support any other readline based Python shells as well +;; (e.g. Jython and Pypy have been reported to work). You can change +;; your default interpreter and commandline arguments by setting the +;; `python-shell-interpreter' and `python-shell-interpreter-args' +;; variables. This example enables IPython globally: + +;; (setq python-shell-interpreter "ipython" +;; python-shell-interpreter-args "-i") + +;; Using the "console" subcommand to start IPython in server-client +;; mode is known to fail intermittently due a bug on IPython itself +;; (see URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=18052#27'). +;; There seems to be a race condition in the IPython server (A.K.A +;; kernel) when code is sent while it is still initializing, sometimes +;; causing the shell to get stalled. With that said, if an IPython +;; kernel is already running, "console --existing" seems to work fine. + +;; Running IPython on Windows needs more tweaking. The way you should +;; set `python-shell-interpreter' and `python-shell-interpreter-args' +;; is as follows (of course you need to modify the paths according to +;; your system): + +;; (setq python-shell-interpreter "C:\\Python27\\python.exe" +;; python-shell-interpreter-args +;; "-i C:\\Python27\\Scripts\\ipython-script.py") + +;; The interaction relies upon having prompts for input (e.g. ">>> " +;; and "... " in standard Python shell) and output (e.g. "Out[1]: " in +;; IPython) detected properly. Failing that Emacs may hang but, in +;; the case that happens, you can recover with \\[keyboard-quit]. To +;; avoid this issue, a two-step prompt autodetection mechanism is +;; provided: the first step is manual and consists of a collection of +;; regular expressions matching common prompts for Python shells +;; stored in `python-shell-prompt-input-regexps' and ;; `python-shell-prompt-output-regexps', and dir-local friendly vars ;; `python-shell-prompt-regexp', `python-shell-prompt-block-regexp', ;; `python-shell-prompt-output-regexp' which are appended to the @@ -81,51 +111,23 @@ ;; modify its behavior. ;; Shell completion: hitting tab will try to complete the current -;; word. Shell completion is implemented in a way that if you change -;; the `python-shell-interpreter' to any other (for example IPython) -;; it should be easy to integrate another way to calculate -;; completions. You just need to specify your custom -;; `python-shell-completion-setup-code' and -;; `python-shell-completion-string-code'. - -;; Here is a complete example of the settings you would use for -;; iPython 0.11: - -;; (setq -;; python-shell-interpreter "ipython" -;; python-shell-interpreter-args "" -;; python-shell-completion-setup-code -;; "from IPython.core.completerlib import module_completion" -;; python-shell-completion-module-string-code -;; "';'.join(module_completion('''%s'''))\n" -;; python-shell-completion-string-code -;; "';'.join(get_ipython().Completer.all_completions('''%s'''))\n") - -;; For iPython 0.10 everything would be the same except for -;; `python-shell-completion-string-code' and -;; `python-shell-completion-module-string-code': - -;; (setq python-shell-completion-string-code -;; "';'.join(__IP.complete('''%s'''))\n" -;; python-shell-completion-module-string-code "") - -;; Unfortunately running iPython on Windows needs some more tweaking. -;; The way you must set `python-shell-interpreter' and -;; `python-shell-interpreter-args' is as follows: - -;; (setq -;; python-shell-interpreter "C:\\Python27\\python.exe" -;; python-shell-interpreter-args -;; "-i C:\\Python27\\Scripts\\ipython-script.py") - -;; That will spawn the iPython process correctly (Of course you need -;; to modify the paths according to your system). - -;; Please note that the default completion system depends on the +;; word. Shell completion is implemented in such way that if you +;; change the `python-shell-interpreter' it should be possible to +;; integrate custom logic to calculate completions. To achieve this +;; you just need to set `python-shell-completion-setup-code' and +;; `python-shell-completion-string-code'. The default provided code, +;; enables autocompletion for both CPython and IPython (and ideally +;; any readline based Python shell). This code depends on the ;; readline module, so if you are using some Operating System that -;; bundles Python without it (like Windows) just install the -;; pyreadline from http://ipython.scipy.org/moin/PyReadline/Intro and -;; you should be good to go. +;; bundles Python without it (like Windows), installing pyreadline +;; from URL `http://ipython.scipy.org/moin/PyReadline/Intro' should +;; suffice. To troubleshoot why you are not getting any completions +;; you can try the following in your Python shell: + +;; >>> import readline, rlcompleter + +;; If you see an error, then you need to either install pyreadline or +;; setup custom code that avoids that dependency. ;; Shell virtualenv support: The shell also contains support for ;; virtualenvs and other special environment modifications thanks to @@ -1740,14 +1742,18 @@ position, else returns nil." (defcustom python-shell-prompt-input-regexps '(">>> " "\\.\\.\\. " ; Python - "In \\[[0-9]+\\]: ") ; iPython + "In \\[[0-9]+\\]: " ; IPython + ;; Using ipdb outside IPython may fail to cleanup and leave static + ;; IPython prompts activated, this adds some safeguard for that. + "In : " "\\.\\.\\.: ") "List of regular expressions matching input prompts." :type '(repeat string) :version "24.4") (defcustom python-shell-prompt-output-regexps '("" ; Python - "Out\\[[0-9]+\\]: ") ; iPython + "Out\\[[0-9]+\\]: " ; IPython + "Out :") ; ipdb safeguard "List of regular expressions matching output prompts." :type '(repeat string) :version "24.4") @@ -2398,7 +2404,7 @@ detecting a prompt at the end of the buffer." (when (string-match python-shell--prompt-calculated-output-regexp python-shell-output-filter-buffer) - ;; Some shells, like iPython might append a prompt before the + ;; Some shells, like IPython might append a prompt before the ;; output, clean that. (setq python-shell-output-filter-buffer (substring python-shell-output-filter-buffer (match-end 0))))) @@ -2608,23 +2614,35 @@ This function takes the list of setup code to send from the (defcustom python-shell-completion-setup-code "try: - import readline + import readline, rlcompleter except ImportError: - def __COMPLETER_all_completions(text): [] + def __PYTHON_EL_get_completions(text): + return [] else: - import rlcompleter - readline.set_completer(rlcompleter.Completer().complete) - def __COMPLETER_all_completions(text): - import sys + def __PYTHON_EL_get_completions(text): completions = [] try: - i = 0 - while True: - res = readline.get_completer()(text, i) - if not res: break - i += 1 - completions.append(res) - except NameError: + splits = text.split() + is_module = splits and splits[0] in ('from', 'import') + is_ipython = getattr( + __builtins__, '__IPYTHON__', + getattr(__builtins__, '__IPYTHON__active', False)) + if is_module: + from IPython.core.completerlib import module_completion + completions = module_completion(text.strip()) + elif is_ipython and getattr(__builtins__, '__IP', None): + completions = __IP.complete(text) + elif is_ipython and getattr(__builtins__, 'get_ipython', None): + completions = get_ipython().Completer.all_completions(text) + else: + i = 0 + while True: + res = readline.get_completer()(text, i) + if not res: + break + i += 1 + completions.append(res) + except: pass return completions" "Code used to setup completion in inferior Python processes." @@ -2632,24 +2650,18 @@ else: :group 'python) (defcustom python-shell-completion-string-code - "';'.join(__COMPLETER_all_completions('''%s'''))\n" - "Python code used to get a string of completions separated by semicolons." + "';'.join(__PYTHON_EL_get_completions('''%s'''))\n" + "Python code used to get a string of completions separated by semicolons. +The string passed to the function is the current python name or +the full statement in the case of imports." :type 'string :group 'python) -(defcustom python-shell-completion-module-string-code "" - "Python code used to get completions separated by semicolons for imports. - -For IPython v0.11, add the following line to -`python-shell-completion-setup-code': - -from IPython.core.completerlib import module_completion - -and use the following as the value of this variable: - -';'.join(module_completion('''%s'''))\n" - :type 'string - :group 'python) +(define-obsolete-variable-alias + 'python-shell-completion-module-string-code + 'python-shell-completion-string-code + "24.4" + "Completion string code must also autocomplete modules.") (defcustom python-shell-completion-pdb-string-code "';'.join(globals().keys() + locals().keys())" @@ -2672,33 +2684,23 @@ LINE is used to detect the context on how to complete given INPUT." (re-search-backward "^") (python-util-forward-comment) (point)))))) - (completion-context + (completion-code ;; Check whether a prompt matches a pdb string, an import ;; statement or just the standard prompt and use the ;; correct python-shell-completion-*-code string (cond ((and (> (length python-shell-completion-pdb-string-code) 0) (string-match (concat "^" python-shell-prompt-pdb-regexp) prompt)) - 'pdb) - ((and (> - (length python-shell-completion-module-string-code) 0) - (string-match - python-shell--prompt-calculated-input-regexp prompt) - (string-match "^[ \t]*\\(from\\|import\\)[ \t]" line)) - 'import) + python-shell-completion-pdb-string-code) ((string-match python-shell--prompt-calculated-input-regexp prompt) - 'default) + python-shell-completion-string-code) (t nil))) - (completion-code - (pcase completion-context - (`pdb python-shell-completion-pdb-string-code) - (`import python-shell-completion-module-string-code) - (`default python-shell-completion-string-code) - (_ nil))) (input - (if (eq completion-context 'import) - (replace-regexp-in-string "^[ \t]+" "" line) + (if (string-match + (python-rx (+ space) (or "from" "import") space) + line) + line input))) (and completion-code (> (length input) 0) diff --git a/test/ChangeLog b/test/ChangeLog index b4b3bedcbdc..451fa91548f 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,8 @@ +2014-07-20 Fabián Ezequiel Gallina + + * automated/python-tests.el: + (python-util-clone-local-variables-1): Fix test. + 2014-07-17 Fabián Ezequiel Gallina * automated/python-tests.el (python-shell-make-comint-1): diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index a60da31e44c..81f4567091e 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el @@ -3619,8 +3619,6 @@ def foo(a, b, c): (python-shell-extra-pythonpaths "/home/user/pylib/") (python-shell-completion-setup-code . "from IPython.core.completerlib import module_completion") - (python-shell-completion-module-string-code - . "';'.join(module_completion('''%s'''))\n") (python-shell-completion-string-code . "';'.join(get_ipython().Completer.all_completions('''%s'''))\n") (python-shell-virtualenv-path From f72d7fa132538ed66048797759a9d59f99c5b8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Sun, 20 Jul 2014 15:28:50 -0300 Subject: [PATCH 11/15] * lisp/progmodes/python.el (python-mode): Don't set mode-require-final-newline. Fixes: debbugs:17990 --- lisp/ChangeLog | 5 +++++ lisp/progmodes/python.el | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 100d57f3893..9c41c6f7ade 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,8 @@ +2014-07-20 Fabián Ezequiel Gallina + + * progmodes/python.el (python-mode): Don't set + mode-require-final-newline. (Bug#17990) + 2014-07-20 Fabián Ezequiel Gallina Make python.el work with IPython automatically. (Bug#15510) diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 229874d3d51..f4ff03e2405 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -4023,8 +4023,6 @@ returned as is." ,(lambda (_arg) (python-nav-end-of-defun)) nil)) - (set (make-local-variable 'mode-require-final-newline) t) - (set (make-local-variable 'outline-regexp) (python-rx (* space) block-start)) (set (make-local-variable 'outline-heading-end-regexp) ":[^\n]*\n") From 1651cf2ed5f7aa8935b8bb3dc1a7813479b99044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Ezequiel=20Gallina?= Date: Sun, 20 Jul 2014 16:51:16 -0300 Subject: [PATCH 12/15] * lisp/progmodes/python.el: Add comment about pipe buffering and solutions for missing/delayed output in inferior Python shells. Fixes: debbugs:17304 --- lisp/ChangeLog | 6 ++++++ lisp/progmodes/python.el | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 9c41c6f7ade..2ee5ee808c1 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,9 @@ +2014-07-20 Fabián Ezequiel Gallina + + * progmodes/python.el: Add comment about pipe buffering and + solutions for missing/delayed output in inferior Python shells. + (Bug#17304) + 2014-07-20 Fabián Ezequiel Gallina * progmodes/python.el (python-mode): Don't set diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index f4ff03e2405..86970b8a0c7 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -94,6 +94,14 @@ ;; python-shell-interpreter-args ;; "-i C:\\Python27\\Scripts\\ipython-script.py") +;; If you are experiencing missing or delayed output in your shells, +;; that's likely caused by your Operating System's pipe buffering +;; (e.g. this is known to happen running CPython 3.3.4 in Windows 7. +;; See URL `http://debbugs.gnu.org/cgi/bugreport.cgi?bug=17304'). To +;; fix this, using CPython's "-u" commandline argument or setting the +;; "PYTHONUNBUFFERED" environment variable should help: See URL +;; `https://docs.python.org/3/using/cmdline.html#cmdoption-u'. + ;; The interaction relies upon having prompts for input (e.g. ">>> " ;; and "... " in standard Python shell) and output (e.g. "Out[1]: " in ;; IPython) detected properly. Failing that Emacs may hang but, in From c255615a5defff663a8b0f7a18f4a0438a3f6693 Mon Sep 17 00:00:00 2001 From: Glenn Morris Date: Sun, 20 Jul 2014 23:31:36 -0400 Subject: [PATCH 13/15] * doc/emacs/emacs.texi (Intro): Workaround makeinfo 4 @acronym bug. Fixes: debbugs:18040 --- doc/emacs/ChangeLog | 4 ++++ doc/emacs/emacs.texi | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/emacs/ChangeLog b/doc/emacs/ChangeLog index e2a6b627a19..3a4f67bf4b2 100644 --- a/doc/emacs/ChangeLog +++ b/doc/emacs/ChangeLog @@ -1,3 +1,7 @@ +2014-07-21 Glenn Morris + + * emacs.texi (Intro): Workaround makeinfo 4 @acronym bug. (Bug#18040) + 2014-07-03 Juri Linkov * search.texi (Regexp Search): Update lax space matching that is diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index 023974fec84..81a75807511 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -1477,7 +1477,16 @@ Neal Ziring, Teodor Zlatanov, and Detlev Zundel. You are reading about GNU Emacs, the GNU incarnation of the advanced, self-documenting, customizable, extensible editor Emacs. -(The @samp{G} in @acronym{GNU, @acronym{GNU}'s Not Unix} is not silent.) +(The @samp{G} in +@c Workaround makeinfo 4 bug. +@c http://lists.gnu.org/archive/html/bug-texinfo/2004-08/msg00009.html +@iftex +@acronym{GNU, @acronym{GNU}'s Not Unix} +@end iftex +@ifnottex +@acronym{GNU, GNU's Not Unix} +@end ifnottex +is not silent.) We call Emacs @dfn{advanced} because it can do much more than simple insertion and deletion of text. It can control subprocesses, indent From f6c951e5d2ce79fe673965a1cee55a34048de612 Mon Sep 17 00:00:00 2001 From: Glenn Morris Date: Mon, 21 Jul 2014 01:38:17 -0400 Subject: [PATCH 14/15] Small fixes re toggle-read-only, mainly doc * progmodes/hideif.el (hide-ifdef-mode-submap): Also substitute read-only-mode. * bindings.el (mode-line-toggle-read-only): * bs.el (bs-toggle-readonly): * buff-menu.el (Buffer-menu-toggle-read-only): * dired.el (dired-toggle-read-only): * files.el (view-read-only, find-file-read-only) (find-file-read-only-other-window) (find-file-read-only-other-frame): * progmodes/hideif.el (hide-ifdef-toggle-outside-read-only): Doc fixes re toggle-read-only. * view.el: Comment updates. --- lisp/ChangeLog | 14 ++++++++++++++ lisp/bindings.el | 2 +- lisp/bs.el | 2 +- lisp/buff-menu.el | 2 +- lisp/dired.el | 2 +- lisp/files.el | 8 ++++---- lisp/progmodes/hideif.el | 5 ++++- lisp/view.el | 6 +++--- 8 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 2ee5ee808c1..6c6e7543c29 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,17 @@ +2014-07-21 Glenn Morris + + * progmodes/hideif.el (hide-ifdef-mode-submap): + Also substitute read-only-mode. + * bindings.el (mode-line-toggle-read-only): + * bs.el (bs-toggle-readonly): + * buff-menu.el (Buffer-menu-toggle-read-only): + * dired.el (dired-toggle-read-only): + * files.el (view-read-only, find-file-read-only) + (find-file-read-only-other-window) + (find-file-read-only-other-frame): + * progmodes/hideif.el (hide-ifdef-toggle-outside-read-only): + Doc fixes re toggle-read-only. + 2014-07-20 Fabián Ezequiel Gallina * progmodes/python.el: Add comment about pipe buffering and diff --git a/lisp/bindings.el b/lisp/bindings.el index 911ab63e8d0..ac31723e02d 100644 --- a/lisp/bindings.el +++ b/lisp/bindings.el @@ -36,7 +36,7 @@ corresponding to the mode line clicked." (defun mode-line-toggle-read-only (event) - "Like `toggle-read-only', for the mode-line." + "Like toggling `read-only-mode', for the mode-line." (interactive "e") (with-selected-window (posn-window (event-start event)) (read-only-mode 'toggle))) diff --git a/lisp/bs.el b/lisp/bs.el index f0f8b1dc351..0348b95a1ef 100644 --- a/lisp/bs.el +++ b/lisp/bs.el @@ -954,7 +954,7 @@ Default is `bs--current-sort-function'." (defun bs-toggle-readonly () "Toggle read-only status for buffer on current line. -Uses function `toggle-read-only'." +Uses function `read-only-mode'." (interactive) (with-current-buffer (bs--current-buffer) (read-only-mode 'toggle)) diff --git a/lisp/buff-menu.el b/lisp/buff-menu.el index d85caca6216..f1772b3d20c 100644 --- a/lisp/buff-menu.el +++ b/lisp/buff-menu.el @@ -539,7 +539,7 @@ The current window remains selected." (defun Buffer-menu-toggle-read-only () "Toggle read-only status of buffer on this line. -This behaves like invoking \\[toggle-read-only] in that buffer." +This behaves like invoking \\[read-only-mode] in that buffer." (interactive) (let ((read-only (with-current-buffer (Buffer-menu-buffer t) diff --git a/lisp/dired.el b/lisp/dired.el index e4f297e8cc8..60f8c1e9dfc 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -2015,7 +2015,7 @@ Actual changes in files cannot be undone by Emacs.")) "Edit Dired buffer with Wdired, or make it read-only. If the current buffer can be edited with Wdired, (i.e. the major mode is `dired-mode'), call `wdired-change-to-wdired-mode'. -Otherwise, call `toggle-read-only'." +Otherwise, toggle `read-only-mode'." (interactive) (if (derived-mode-p 'dired-mode) (wdired-change-to-wdired-mode) diff --git a/lisp/files.el b/lisp/files.el index 718421b1899..a5cee47ee8a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -572,7 +572,7 @@ A value of nil means ignore them; anything else means query." In fact, this means that all read-only buffers normally have View mode enabled, including buffers that are read-only because you visit a file you cannot alter, and buffers you make read-only -using \\[toggle-read-only]." +using \\[read-only-mode]." :type 'boolean :group 'view) @@ -1517,7 +1517,7 @@ file names with wildcards." (defun find-file-read-only (filename &optional wildcards) "Edit file FILENAME but don't allow changes. Like \\[find-file], but marks buffer as read-only. -Use \\[toggle-read-only] to permit editing." +Use \\[read-only-mode] to permit editing." (interactive (find-file-read-args "Find file read-only: " (confirm-nonexistent-file-or-buffer))) @@ -1526,7 +1526,7 @@ Use \\[toggle-read-only] to permit editing." (defun find-file-read-only-other-window (filename &optional wildcards) "Edit file FILENAME in another window but don't allow changes. Like \\[find-file-other-window], but marks buffer as read-only. -Use \\[toggle-read-only] to permit editing." +Use \\[read-only-mode] to permit editing." (interactive (find-file-read-args "Find file read-only other window: " (confirm-nonexistent-file-or-buffer))) @@ -1535,7 +1535,7 @@ Use \\[toggle-read-only] to permit editing." (defun find-file-read-only-other-frame (filename &optional wildcards) "Edit file FILENAME in another frame but don't allow changes. Like \\[find-file-other-frame], but marks buffer as read-only. -Use \\[toggle-read-only] to permit editing." +Use \\[read-only-mode] to permit editing." (interactive (find-file-read-args "Find file read-only other frame: " (confirm-nonexistent-file-or-buffer))) diff --git a/lisp/progmodes/hideif.el b/lisp/progmodes/hideif.el index 39ad676f593..506f2c2364e 100644 --- a/lisp/progmodes/hideif.el +++ b/lisp/progmodes/hideif.el @@ -149,6 +149,9 @@ (define-key map "\C-q" 'hide-ifdef-toggle-read-only) (define-key map "\C-w" 'hide-ifdef-toggle-shadowing) + (substitute-key-definition + 'read-only-mode 'hide-ifdef-toggle-outside-read-only map) + ;; `toggle-read-only' is obsoleted by `read-only-mode'. (substitute-key-definition 'toggle-read-only 'hide-ifdef-toggle-outside-read-only map) map) @@ -1049,7 +1052,7 @@ It does not do the work that's pointless to redo on a recursive entry." (force-mode-line-update)) (defun hide-ifdef-toggle-outside-read-only () - "Replacement for `toggle-read-only' within Hide-Ifdef mode." + "Replacement for `read-only-mode' within Hide-Ifdef mode." (interactive) (setq hif-outside-read-only (not hif-outside-read-only)) (message "Read only %s" diff --git a/lisp/view.el b/lisp/view.el index a5992ccb62a..9f914b03f56 100644 --- a/lisp/view.el +++ b/lisp/view.el @@ -491,12 +491,12 @@ Entry to view-mode runs the normal hook `view-mode-hook'." (defun view--disable () (remove-hook 'change-major-mode-hook 'view--disable t) (and view-overlay (delete-overlay view-overlay)) - ;; Calling toggle-read-only while View mode is enabled + ;; Calling read-only-mode while View mode is enabled ;; sets view-read-only to t as a buffer-local variable - ;; after exiting View mode. That arranges that the next toggle-read-only + ;; after exiting View mode. That arranges that the next read-only-mode ;; will reenable View mode. ;; Canceling View mode in any other way should cancel that, too, - ;; so that View mode stays off if toggle-read-only is called. + ;; so that View mode stays off if read-only-mode is called. (if (local-variable-p 'view-read-only) (kill-local-variable 'view-read-only)) (if (boundp 'Helper-return-blurb) From ef3b7194838482c4bcb55beb951f3a8e2e0f44ac Mon Sep 17 00:00:00 2001 From: Glenn Morris Date: Mon, 21 Jul 2014 01:58:27 -0400 Subject: [PATCH 15/15] NEWS tweaks --- etc/NEWS | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 19356f7473b..b1524151cc8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -675,13 +675,12 @@ along with the non-SMIE indentation code. *** Out of the box support for CPython, iPython and readline based shells. **** `python-shell-completion-module-string-code` is no longer used. -*** Automatic shell prompt detection. -**** New user options: -***** `python-shell-interpreter-interactive-arg`. -***** `python-shell-prompt-detect-enabled`. -***** `python-shell-prompt-detect-failure-warning`. -***** `python-shell-prompt-input-regexps`. -***** `python-shell-prompt-output-regexps`. +*** Automatic shell prompt detection. New user options: +**** `python-shell-interpreter-interactive-arg'. +**** `python-shell-prompt-detect-enabled'. +**** `python-shell-prompt-detect-failure-warning'. +**** `python-shell-prompt-input-regexps'. +**** `python-shell-prompt-output-regexps'. *** Python shell support for remote hosts via tramp.