diff .elisp/ipython.el @ 19:b5d75594b356

Add support for the ipython-mode stuff and remove vestigial pymacs code.
author Augie Fackler <durin42@gmail.com>
date Mon, 08 Dec 2008 10:58:06 -0600
parents
children
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/.elisp/ipython.el
@@ -0,0 +1,483 @@
+;;; ipython.el --- Adds support for IPython to python-mode.el
+
+;; Copyright (C) 2002, 2003, 2004, 2005 Alexander Schmolck
+;; Author:        Alexander Schmolck
+;; Keywords:      ipython python languages oop
+;; URL:           http://ipython.scipy.org
+;; Compatibility: Emacs21, XEmacs21
+;; FIXME: #$@! INPUT RING
+(defconst ipython-version "$Revision: 2275 $"
+  "VC version number.")
+
+;;; Commentary 
+;; This library makes all the functionality python-mode has when running with
+;; the normal python-interpreter available for ipython, too. It also enables a
+;; persistent py-shell command history across sessions (if you exit python
+;; with C-d in py-shell) and defines the command `ipython-to-doctest', which
+;; can be used to convert bits of a ipython session into something that can be
+;; used for doctests. To install, put this file somewhere in your emacs
+;; `load-path' [1] and add the following line to your ~/.emacs file (the first
+;; line only needed if the default (``"ipython"``) is wrong)::
+;;
+;;   (setq ipython-command "/SOME-PATH/ipython")
+;;   (require 'ipython)
+;;
+;; Ipython will be set as the default python shell, but only if the ipython
+;; executable is in the path. For ipython sessions autocompletion with <tab>
+;; is also enabled (experimental feature!). Please also note that all the
+;; terminal functions in py-shell are handled by emacs's comint, **not** by
+;; (i)python, so importing readline etc. will have 0 effect.
+;;
+;; To start an interactive ipython session run `py-shell' with ``M-x py-shell``
+;; (or the default keybinding ``C-c C-!``).
+;;
+;; NOTE: This mode is currently somewhat alpha and although I hope that it
+;; will work fine for most cases, doing certain things (like the
+;; autocompletion and a decent scheme to switch between python interpreters)
+;; properly will also require changes to ipython that will likely have to wait
+;; for a larger rewrite scheduled some time in the future.
+;; 
+;; Also note that you currently NEED THE CVS VERSION OF PYTHON.EL.
+;;
+;; Further note that I don't know whether this runs under windows or not and
+;; that if it doesn't I can't really help much, not being afflicted myself.
+;;
+;;
+;; Hints for effective usage
+;; -------------------------
+;;
+;; - IMO the best feature by far of the ipython/emacs combo is how much easier it
+;;   makes it to find and fix bugs thanks to the ``%pdb on``/ pdbtrack combo. Try
+;;   it: first in the ipython to shell do ``%pdb on`` then do something that will
+;;   raise an exception (FIXME nice example) -- and be amazed how easy it is to
+;;   inspect the live objects in each stack frames and to jump to the
+;;   corresponding sourcecode locations as you walk up and down the stack trace
+;;   (even without ``%pdb on`` you can always use ``C-c -`` (`py-up-exception')
+;;   to jump to the corresponding source code locations).
+;;
+;; - emacs gives you much more powerful commandline editing and output searching
+;;   capabilities than ipython-standalone -- isearch is your friend if you
+;;   quickly want to print 'DEBUG ...' to stdout out etc.
+;;
+;; - This is not really specific to ipython, but for more convenient history
+;;   access you might want to add something like the following to *the beggining*
+;;   of your ``.emacs`` (if you want behavior that's more similar to stand-alone
+;;   ipython, you can change ``meta p`` etc. for ``control p``)::
+;;
+;;         (require 'comint)
+;;         (define-key comint-mode-map [(meta p)] 
+;;           'comint-previous-matching-input-from-input)
+;;         (define-key comint-mode-map [(meta n)] 
+;;           'comint-next-matching-input-from-input)
+;;         (define-key comint-mode-map [(control meta n)]
+;;            'comint-next-input)
+;;         (define-key comint-mode-map [(control meta p)]
+;;            'comint-previous-input)
+;;
+;; - Be aware that if you customize py-python-command previously, this value
+;;   will override what ipython.el does (because loading the customization
+;;   variables comes later).
+;;
+;; Please send comments and feedback to the ipython-list
+;; (<ipython-user@scipy.net>) where I (a.s.) or someone else will try to
+;; answer them (it helps if you specify your emacs version, OS etc; 
+;; familiarity with <http://www.catb.org/~esr/faqs/smart-questions.html> might
+;; speed up things further).
+;;
+;; Footnotes:
+;;
+;;     [1] If you don't know what `load-path' is, C-h v load-path will tell
+;;     you; if required you can also add a new directory. So assuming that
+;;     ipython.el resides in ~/el/, put this in your emacs:
+;;
+;;
+;;           (add-to-list 'load-path "~/el")
+;;           (setq ipython-command "/some-path/ipython")
+;;           (require 'ipython)
+;;
+;;
+;;
+;;
+;; TODO:
+;;      - do autocompletion properly
+;;      - implement a proper switching between python interpreters
+;;
+;; BUGS:
+;;      - neither::
+;;
+;;         (py-shell "-c print 'FOOBAR'")
+;;       
+;;        nor::
+;;       
+;;         (let ((py-python-command-args (append py-python-command-args 
+;;                                              '("-c" "print 'FOOBAR'"))))
+;;           (py-shell))
+;;
+;;        seem to print anything as they should
+;;
+;;      - look into init priority issues with `py-python-command' (if it's set
+;;        via custom)
+
+
+;;; Code
+(require 'cl)
+(require 'shell)
+(require 'executable)
+(require 'ansi-color)
+
+(defcustom ipython-command "ipython"
+  "*Shell command used to start ipython."
+  :type 'string 
+  :group 'python)
+
+;; Users can set this to nil
+(defvar py-shell-initial-switch-buffers t
+  "If nil, don't switch to the *Python* buffer on the first call to
+  `py-shell'.")
+
+(defvar ipython-backup-of-py-python-command nil
+  "HACK")
+
+  
+(defvar ipython-de-input-prompt-regexp "\\(?:
+In \\[[0-9]+\\]: *.*
+----+> \\(.*
+\\)[\n]?\\)\\|\\(?:
+In \\[[0-9]+\\]: *\\(.*
+\\)\\)\\|^[ ]\\{3\\}[.]\\{3,\\}: *\\(.*
+\\)"
+  "A regular expression to match the IPython input prompt and the python
+command after it. The first match group is for a command that is rewritten,
+the second for a 'normal' command, and the third for a multiline command.")
+(defvar ipython-de-output-prompt-regexp "^Out\\[[0-9]+\\]: "
+  "A regular expression to match the output prompt of IPython.")
+
+
+(if (not (executable-find ipython-command))
+    (message (format "Can't find executable %s - ipython.el *NOT* activated!!!"
+                     ipython-command))
+    ;; XXX load python-mode, so that we can screw around with its variables
+    ;; this has the disadvantage that python-mode is loaded even if no
+    ;; python-file is ever edited etc. but it means that `py-shell' works
+    ;; without loading a python-file first. Obviously screwing around with
+    ;; python-mode's variables like this is a mess, but well.
+    (require 'python-mode)
+    ;; turn on ansi colors for ipython and activate completion
+    (defun ipython-shell-hook ()
+      ;; the following is to synchronize dir-changes
+      (make-local-variable 'shell-dirstack)
+      (setq shell-dirstack nil)
+      (make-local-variable 'shell-last-dir)
+      (setq shell-last-dir nil)
+      (make-local-variable 'shell-dirtrackp)
+      (setq shell-dirtrackp t)
+      (add-hook 'comint-input-filter-functions 'shell-directory-tracker nil t)
+
+      (ansi-color-for-comint-mode-on)
+      (define-key py-shell-map [tab] 'ipython-complete)
+      ;; Add this so that tab-completion works both in X11 frames and inside
+      ;; terminals (such as when emacs is called with -nw).
+      (define-key py-shell-map "\t" 'ipython-complete)
+      ;;XXX this is really just a cheap hack, it only completes symbols in the
+      ;;interactive session -- useful nonetheless.
+      (define-key py-mode-map [(meta tab)] 'ipython-complete)
+      
+      )
+    (add-hook 'py-shell-hook 'ipython-shell-hook)
+    ;; Regular expression that describes tracebacks for IPython in context and
+    ;; verbose mode. 
+  
+    ;;Adapt python-mode settings for ipython.
+    ;; (this works for %xmode 'verbose' or 'context')
+
+    ;; XXX putative regexps for syntax errors; unfortunately the 
+    ;;     current python-mode traceback-line-re scheme is too primitive,
+    ;;     so it's either matching syntax errors, *or* everything else
+    ;;     (XXX: should ask Fernando for a change)
+    ;;"^   File \"\\(.*?\\)\", line \\([0-9]+\\).*\n.*\n.*\nSyntaxError:"
+    ;;^   File \"\\(.*?\\)\", line \\([0-9]+\\)"
+
+    (setq py-traceback-line-re
+          "\\(^[^\t >].+?\\.py\\).*\n   +[0-9]+[^\00]*?\n-+> \\([0-9]+\\)+")
+
+    
+    ;; Recognize the ipython pdb, whose prompt is 'ipdb>' or  'ipydb>'
+    ;;instead of '(Pdb)'
+    (setq py-pdbtrack-input-prompt "\n[(<]*[Ii]?[Pp]y?db[>)]+ ")
+    (setq pydb-pydbtrack-input-prompt "\n[(]*ipydb[>)]+ ")
+    
+    (setq py-shell-input-prompt-1-regexp "^In \\[[0-9]+\\]: *"
+          py-shell-input-prompt-2-regexp "^   [.][.][.]+: *" )
+    ;; select a suitable color-scheme
+    (unless (member "-colors" py-python-command-args)
+      (setq py-python-command-args 
+            (nconc py-python-command-args 
+                   (list "-colors"
+                         (cond  
+                           ((eq frame-background-mode 'dark)
+                            "Linux")
+                           ((eq frame-background-mode 'light)
+                            "LightBG")
+                           (t ; default (backg-mode isn't always set by XEmacs)
+                            "LightBG"))))))
+    (unless (equal ipython-backup-of-py-python-command py-python-command)
+      (setq ipython-backup-of-py-python-command py-python-command))
+    (setq py-python-command ipython-command))
+
+
+;; MODIFY py-shell so that it loads the editing history
+(defadvice py-shell (around py-shell-with-history)
+  "Add persistent command-history support (in
+$PYTHONHISTORY (or \"~/.ipython/history\", if we use IPython)). Also, if
+`py-shell-initial-switch-buffers' is nil, it only switches to *Python* if that
+buffer already exists."
+  (if (comint-check-proc "*Python*")
+      ad-do-it
+    (setq comint-input-ring-file-name
+          (if (string-equal py-python-command ipython-command)
+              (concat (or (getenv "IPYTHONDIR") "~/.ipython") "/history")
+            (or (getenv "PYTHONHISTORY") "~/.python-history.py")))
+    (comint-read-input-ring t)
+    (let ((buf (current-buffer)))
+      ad-do-it
+      (unless py-shell-initial-switch-buffers
+        (switch-to-buffer-other-window buf)))))
+(ad-activate 'py-shell)
+;; (defadvice py-execute-region (before py-execute-buffer-ensure-process)
+;;   "HACK: test that ipython is already running before executing something.
+;;   Doing this properly seems not worth the bother (unless people actually
+;;   request it)."
+;; (unless (comint-check-proc "*Python*")
+;;     (error "Sorry you have to first do M-x py-shell to send something to ipython.")))
+;; (ad-activate 'py-execute-region)
+
+(defadvice py-execute-region (around py-execute-buffer-ensure-process)
+  "HACK: if `py-shell' is not active or ASYNC is explicitly desired, fall back
+  to python instead of ipython." 
+  (let ((py-which-shell (if (and (comint-check-proc "*Python*") (not async))
+			    py-python-command
+			  ipython-backup-of-py-python-command)))
+    ad-do-it))
+(ad-activate 'py-execute-region)
+
+(defun ipython-to-doctest (start end)
+  "Transform a cut-and-pasted bit from an IPython session into something that
+looks like it came from a normal interactive python session, so that it can
+be used in doctests. Example:
+
+
+    In [1]: import sys
+    
+    In [2]: sys.stdout.write 'Hi!\n'
+    ------> sys.stdout.write ('Hi!\n')
+    Hi!
+    
+    In [3]: 3 + 4
+    Out[3]: 7
+    
+gets converted to:
+
+    >>> import sys
+    >>> sys.stdout.write ('Hi!\n')
+    Hi!
+    >>> 3 + 4
+    7
+
+"
+  (interactive "*r\n")
+  ;(message (format "###DEBUG s:%de:%d" start end))
+  (save-excursion
+    (save-match-data
+      ;; replace ``In [3]: bla`` with ``>>> bla`` and 
+      ;;         ``... :   bla`` with ``...    bla``
+      (goto-char start)
+      (while (re-search-forward ipython-de-input-prompt-regexp end t)
+        ;(message "finding 1")
+        (cond ((match-string 3)         ;continued
+               (replace-match "... \\3" t nil))
+              (t
+               (replace-match ">>> \\1\\2" t nil))))
+      ;; replace ``
+      (goto-char start)
+      (while (re-search-forward ipython-de-output-prompt-regexp end t)
+        (replace-match "" t nil)))))
+
+(defvar ipython-completion-command-string 
+  "print ';'.join(__IP.Completer.all_completions('%s')) #PYTHON-MODE SILENT\n"
+  "The string send to ipython to query for all possible completions")
+
+
+;; xemacs doesn't have `comint-preoutput-filter-functions' so we'll try the
+;; following wonderful hack to work around this case
+(if (featurep 'xemacs)
+    ;;xemacs
+    (defun ipython-complete ()
+      "Try to complete the python symbol before point. Only knows about the stuff
+in the current *Python* session."
+      (interactive)
+      (let* ((ugly-return nil)
+             (sep ";")
+             (python-process (or (get-buffer-process (current-buffer))
+                                 ;XXX hack for .py buffers
+                                 (get-process py-which-bufname)))
+             ;; XXX currently we go backwards to find the beginning of an
+             ;; expression part; a more powerful approach in the future might be
+             ;; to let ipython have the complete line, so that context can be used
+             ;; to do things like filename completion etc.
+             (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_." (point-at-bol))
+                                  (point)))
+             (end (point))
+             (pattern (buffer-substring-no-properties beg end))
+             (completions nil)
+             (completion-table nil)
+             completion
+             (comint-output-filter-functions
+              (append comint-output-filter-functions 
+                      '(ansi-color-filter-apply
+                        (lambda (string) 
+                                        ;(message (format "DEBUG filtering: %s" string))
+                          (setq ugly-return (concat ugly-return string))
+                          (delete-region comint-last-output-start 
+                                         (process-mark (get-buffer-process (current-buffer)))))))))
+                                        ;(message (format "#DEBUG pattern: '%s'" pattern))
+        (process-send-string python-process 
+                              (format ipython-completion-command-string pattern))
+        (accept-process-output python-process)
+                                        ;(message (format "DEBUG return: %s" ugly-return))
+        (setq completions 
+              (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
+        (setq completion-table (loop for str in completions
+                                     collect (list str nil)))
+        (setq completion (try-completion pattern completion-table))
+        (cond ((eq completion t))
+              ((null completion)
+               (message "Can't find completion for \"%s\"" pattern)
+               (ding))
+              ((not (string= pattern completion))
+               (delete-region beg end)
+               (insert completion))
+              (t
+               (message "Making completion list...")
+               (with-output-to-temp-buffer "*Python Completions*"
+                 (display-completion-list (all-completions pattern completion-table)))
+               (message "Making completion list...%s" "done")))))
+  ;; emacs
+  (defun ipython-complete ()
+    "Try to complete the python symbol before point. Only knows about the stuff
+in the current *Python* session."
+    (interactive)
+    (let* ((ugly-return nil)
+           (sep ";")
+           (python-process (or (get-buffer-process (current-buffer))
+                                        ;XXX hack for .py buffers
+                               (get-process py-which-bufname)))
+           ;; XXX currently we go backwards to find the beginning of an
+           ;; expression part; a more powerful approach in the future might be
+           ;; to let ipython have the complete line, so that context can be used
+           ;; to do things like filename completion etc.
+           (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_." (point-at-bol))
+                                (point))) 
+           (end (point))
+           (pattern (buffer-substring-no-properties beg end))
+           (completions nil)
+           (completion-table nil)
+           completion
+         (comint-preoutput-filter-functions
+          (append comint-preoutput-filter-functions 
+                  '(ansi-color-filter-apply
+                    (lambda (string) 
+                      (setq ugly-return (concat ugly-return string))
+                      "")))))
+      (process-send-string python-process 
+                            (format ipython-completion-command-string pattern))
+      (accept-process-output python-process)
+      (setq completions 
+            (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
+                                        ;(message (format "DEBUG completions: %S" completions))
+      (setq completion-table (loop for str in completions
+                                   collect (list str nil)))
+      (setq completion (try-completion pattern completion-table))
+      (cond ((eq completion t))
+            ((null completion)
+             (message "Can't find completion for \"%s\"" pattern)
+             (ding))
+            ((not (string= pattern completion))
+             (delete-region beg end)
+             (insert completion))
+            (t
+             (message "Making completion list...")
+             (with-output-to-temp-buffer "*IPython Completions*"
+               (display-completion-list (all-completions pattern completion-table)))
+             (message "Making completion list...%s" "done")))))
+)
+
+;;; autoindent support: patch sent in by Jin Liu <m.liu.jin@gmail.com>,
+;;; originally written by doxgen@newsmth.net
+;;; Minor modifications by fperez for xemacs compatibility.
+
+(defvar ipython-autoindent t
+ "If non-nil, enable autoindent for IPython shell through python-mode.")
+
+(defvar ipython-indenting-buffer-name "*IPython Indentation Calculation*"
+ "Temporary buffer for indenting multiline statement.")
+
+(defun ipython-get-indenting-buffer ()
+ "Return a temporary buffer set in python-mode. Create one if necessary."
+ (let ((buf (get-buffer-create ipython-indenting-buffer-name)))
+   (set-buffer buf)
+   (unless (eq major-mode 'python-mode)
+     (python-mode))
+   buf))
+
+(defvar ipython-indentation-string nil
+ "Indentation for the next line in a multiline statement.")
+
+(defun ipython-send-and-indent ()
+ "Send the current line to IPython, and calculate the indentation for
+the next line."
+ (interactive)
+ (if ipython-autoindent
+     (let ((line (buffer-substring (point-at-bol) (point)))
+           (after-prompt1)
+           (after-prompt2))
+       (save-excursion
+           (comint-bol t)
+           (if (looking-at py-shell-input-prompt-1-regexp)
+               (setq after-prompt1 t)
+             (setq after-prompt2 (looking-at py-shell-input-prompt-2-regexp)))
+           (with-current-buffer (ipython-get-indenting-buffer)
+             (when after-prompt1
+               (erase-buffer))
+             (when (or after-prompt1 after-prompt2)
+               (delete-region (point-at-bol) (point))
+               (insert line)
+               (newline-and-indent))))))
+ ;; send input line to ipython interpreter
+ (comint-send-input))
+
+(defun ipython-indentation-hook (string)
+ "Insert indentation string if py-shell-input-prompt-2-regexp
+matches last process output."
+ (let* ((start-marker (or comint-last-output-start
+                          (point-min-marker)))
+        (end-marker (process-mark (get-buffer-process (current-buffer))))
+        (text (ansi-color-filter-apply (buffer-substring start-marker end-marker))))
+   ;; XXX if `text' matches both pattern, it MUST be the last prompt-2
+   (when (and (string-match py-shell-input-prompt-2-regexp text)
+	      (not (string-match "\n$" text)))
+     (with-current-buffer (ipython-get-indenting-buffer)
+       (setq ipython-indentation-string
+	     (buffer-substring (point-at-bol) (point))))
+     (goto-char end-marker)
+     (insert ipython-indentation-string)
+     (setq ipython-indentation-string nil))))
+
+(add-hook 'py-shell-hook
+         (lambda ()
+           (add-hook 'comint-output-filter-functions
+                     'ipython-indentation-hook)))
+
+(define-key py-shell-map (kbd "RET") 'ipython-send-and-indent)
+;;; / end autoindent support
+
+(provide 'ipython)