Subject: changes in python.el to support IPython

From: Fabian Ezequiel Gallina <[email-masked]>
Date: Mon, 28 Dec 2009 02:05:36 -0300

Package: emacs,python
Severity: wishlist
Tags: patch

[ resent from
  http://lists.gnu.org/archive/html/emacs-devel/2009-12/msg00817.html ]

Hi everybody,

I made several changes to python.el to support the IPython console.

I always preferred the standard Emacs version of python.el to this one
https://launchpad.net/python-mode but the IPython support was always
making me use the other.

Also, I really think that python's menu item "Start interpreter"
should execute `run-python' instead of `python-shell', mainly because
all the items below use that interpreter to work. With that said I
really don't understand why `python-shell' could be useful at all, my
guess is that it could be set as a defalias to `run-python'.

Regards,
-- 
Fabián E. Gallina
http://www.from-the-cloud.com
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 104ea26..bcef353 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -298,7 +298,7 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
 	 :filter (lambda (&rest junk)
                    (abbrev-table-menu python-mode-abbrev-table)))
 	"-"
-	["Start interpreter" python-shell
+	["Start interpreter" run-python
 	 :help "Run `inferior' Python in separate buffer"]
 	["Import/reload file" python-load-file
 	 :help "Load into inferior Python session"]
@@ -571,10 +571,21 @@ having to restart the program."
   "Queue of Python temp files awaiting execution.
 Currently-active file is at the head of the list.")
 
+(defcustom python-shell-input-prompt-1-regexp "^>>> "
+  "*A regular expression to match the input prompt of the shell."
+  :type 'string
+  :group 'python)
+
+(defcustom python-shell-input-prompt-2-regexp "^[.][.][.] "
+  "*A regular expression to match the input prompt of the shell after the
+  first line of input."
+  :type 'string
+  :group 'python)
+
 (defvar python-pdbtrack-is-tracking-p nil)
 
 (defconst python-pdbtrack-stack-entry-regexp
-  "^> \\(.*\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_]+\\)()"
+  "^> \\(.*\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
   "Regular expression pdbtrack uses to find a stack trace entry.")
 
 (defconst python-pdbtrack-input-prompt "\n[(<]*[Pp]db[>)]+ "
@@ -1303,13 +1314,22 @@ See `python-check-command' for the default."
 
 ;;;; Inferior mode stuff (following cmuscheme).
 
-;; Fixme: Make sure we can work with IPython.
-
 (defcustom python-python-command "python"
   "Shell command to run Python interpreter.
 Any arguments can't contain whitespace.
-Note that IPython may not work properly; it must at least be used
-with the `-cl' flag, i.e. use `ipython -cl'."
+
+Note that IPython may not work properly; you must especify the
+correct `python-shell-input-prompt-1-regexp' and
+`python-shell-input-prompt-2-regexp' regular expressions so the
+prompt is identified correctly. 
+
+Normally you would set them as follows in your .emacs: 
+
+\(setq python-shell-input-prompt-1-regexp \"^In \\[[0-9]+\\]: *\"
+       python-shell-input-prompt-2-regexp \"^   [.][.][.]+: *\")
+
+Alternatively you could just add the `-cl' flag to the command so
+prompts remain the same as the standard python console."
   :group 'python
   :type 'string)
 
@@ -1411,12 +1431,17 @@ For running multiple processes in multiple buffers, see `run-python' and
   :group 'python
   (setq mode-line-process '(":%s"))
   (set (make-local-variable 'comint-input-filter) 'python-input-filter)
-  (add-hook 'comint-preoutput-filter-functions #'python-preoutput-filter
-	    nil t)
+  (add-hook 'comint-preoutput-filter-functions #'python-preoutput-filter 
+            nil t)
   ;; Still required by `comint-redirect-send-command', for instance
   ;; (and we need to match things like `>>> ... >>> '):
-  (set (make-local-variable 'comint-prompt-regexp)
-       (rx line-start (1+ (and (or (repeat 3 (any ">.")) "(Pdb)") " "))))
+  ;; (set (make-local-variable 'comint-prompt-regexp)
+  ;;      (rx line-start (1+ (and (or (repeat 3 (any ">.")) "(Pdb)") " "))))
+  (make-local-variable 'comint-prompt-regexp)
+  (setq comint-prompt-regexp (format "%s\\|%s\\|%s"
+                                     python-shell-input-prompt-1-regexp
+                                     python-shell-input-prompt-2-regexp
+                                     "^([Pp]db) "))
   (set (make-local-variable 'compilation-error-regexp-alist)
        python-compilation-regexp-alist)
   (compilation-shell-minor-mode 1))
@@ -1513,11 +1538,10 @@ Don't save anything for STR matching `inferior-python-filter-regexp'."
 				     cmd)))
     (unless (shell-command-to-string cmd)
       (error "Can't run Python command `%s'" cmd))
-    (let* ((res (shell-command-to-string (concat cmd " --version"))))
-      (string-match "Python \\([0-9]\\)\\.\\([0-9]\\)" res)
-      (unless (and (equal "2" (match-string 1 res))
-		   (match-beginning 2)
-		   (>= (string-to-number (match-string 2 res)) 2))
+    (let* ((res (shell-command-to-string
+                 (concat cmd
+                         " -c \"from sys import version_info; print version_info >= (2, 2) and version_info < (3, 0)\""))))
+      (unless (string-match "True" res)
 	(error "Only Python versions >= 2.2 and < 3.0 supported")))
     (setq python-version-checked t)))
 
@@ -1558,8 +1582,12 @@ buffer for a list of commands.)"
 			      (if path (concat path path-separator))
 			      data-directory)
 		      process-environment))
-	       ;; Suppress use of pager for help output:
-	       (process-connection-type nil))
+               ;; If we set process-connection-type to nil for some
+               ;; reason the IPython console will not work at all.                
+               ;; My guess is that it redirects stdout and stderr in
+               ;; special ways the standard python console doesn't
+               ;; -- fg
+	       (process-connection-type t))
 	  (apply 'make-comint-in-buffer "Python"
 		 (generate-new-buffer "*Python*")
 		 (car cmdlist) nil (cdr cmdlist)))
@@ -1615,7 +1643,12 @@ buffer for a list of commands.)"
   ;; non-ASCII.
   (interactive "r")
   (let* ((f (make-temp-file "py"))
-	 (command (format "emacs.eexecfile(%S)" f))
+	 (command
+          ;; Unfortunately IPython puts the FakeModule module into
+          ;; __main__ so emacs.eexecfile becomes useless.
+          (if (string-match "^ipython" python-command)
+              (format "execfile %S" f)
+            (format "emacs.eexecfile(%S)" f)))
 	 (orig-start (copy-marker start)))
     (when (save-excursion
 	    (goto-char start)
@@ -1815,7 +1848,10 @@ If there isn't, it's probably not appropriate to send input to return Eldoc
 information etc.  If PROC is non-nil, check the buffer for that process."
   (with-current-buffer (process-buffer (or proc (python-proc)))
     (save-excursion
-      (save-match-data (re-search-backward ">>> \\=" nil t)))))
+      (save-match-data (re-search-backward
+                        (format "%s *\\="
+                                python-shell-input-prompt-1-regexp)
+                        nil t)))))
 
 ;; Fixme:  Is there anything reasonable we can do with random methods?
 ;; (Currently only works with functions.)
@@ -2531,9 +2567,7 @@ Runs `jython-mode-hook' after `python-mode-hook'."
   "Watch output for Python prompt and exec next file waiting in queue.
 This function is appropriate for `comint-output-filter-functions'."
   ;; TBD: this should probably use split-string
-  (when (and (or (string-equal string ">>> ")
-		 (and (>= (length string) 5)
-		      (string-equal (substring string -5) "\n>>> ")))
+  (when (and (string-match python-shell-input-prompt-1-regexp string)
 	     python-file-queue)
     (condition-case nil
         (delete-file (car python-file-queue))
@@ -2745,6 +2779,7 @@ comint believe the user typed this string so that
 	  (funcall (process-filter proc) proc msg))
       (set-buffer curbuf))
     (process-send-string proc cmd)))
+
 ;;;###autoload
 (defun python-shell (&optional argprompt)
   "Start an interactive Python interpreter in another window.
@@ -2803,7 +2838,10 @@ filter."
     (make-local-variable 'comint-prompt-regexp)
     (set-process-sentinel (get-buffer-process (current-buffer))
                           'python-sentinel)
-    (setq comint-prompt-regexp "^>>> \\|^[.][.][.] \\|^(pdb) ")
+    (setq comint-prompt-regexp (format "%s\\|%s\\|%s"
+                                       python-shell-input-prompt-1-regexp
+                                       python-shell-input-prompt-2-regexp
+                                       "^([Pp]db) "))
     (add-hook 'comint-output-filter-functions
 	      'python-comint-output-filter-function nil t)
     ;; pdbtrack