Switch to recommended form of GPLv3 permissions notice.
[bpt/emacs.git] / lisp / progmodes / python.el
index 46d78c8..259b095 100644 (file)
@@ -1,6 +1,6 @@
-;;; python.el --- silly walks for Python
+;;; python.el --- silly walks for Python  -*- coding: iso-8859-1 -*-
 
-;; Copyright (C) 2003, 2004, 2005, 2006, 2007  Free Software Foundation, Inc.
+;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
 
 ;; Author: Dave Love <fx@gnu.org>
 ;; Maintainer: FSF
 
 ;; TODO: See various Fixmes below.
 
+;; Fixme: This doesn't support (the nascent) Python 3 .
+
 ;;; Code:
 
+(require 'comint)
+
 (eval-when-compile
-  (require 'cl)
   (require 'compile)
-  (require 'comint)
   (require 'hippie-exp))
 
+(require 'sym-comp)
 (autoload 'comint-mode "comint")
 
 (defgroup python nil
 (add-to-list 'interpreter-mode-alist '("python" . python-mode))
 ;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))
+(add-to-list 'same-window-buffer-names "*Python*")
 \f
 ;;;; Font lock
 
 (defvar python-font-lock-keywords
   `(,(rx symbol-start
-        ;; From v 2.4 reference.
+        ;; From v 2.5 reference, § keywords.
         ;; def and class dealt with separately below
-        (or "and" "assert" "break" "continue" "del" "elif" "else"
+        (or "and" "as" "assert" "break" "continue" "del" "elif" "else"
             "except" "exec" "finally" "for" "from" "global" "if"
             "import" "in" "is" "lambda" "not" "or" "pass" "print"
-            "raise" "return" "try" "while" "yield"
-            ;; Future keywords
-            "as" "None" "with"
+            "raise" "return" "try" "while" "with" "yield"
              ;; Not real keywords, but close enough to be fontified as such
              "self" "True" "False")
         symbol-end)
+    (,(rx symbol-start "None" symbol-end)      ; see § Keywords in 2.5 manual
+     . font-lock-constant-face)
     ;; Definitions
     (,(rx symbol-start (group "class") (1+ space) (group (1+ (or word ?_))))
      (1 font-lock-keyword-face) (2 font-lock-type-face))
     ;; Top-level assignments are worth highlighting.
     (,(rx line-start (group (1+ (or word ?_))) (0+ space) "=")
      (1 font-lock-variable-name-face))
-    (,(rx "@" (1+ (or word ?_))) ; decorators
-    (0 font-lock-preprocessor-face))))
+    (,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_)))) ; decorators
+     (1 font-lock-type-face))
+    ;; Built-ins.  (The next three blocks are from
+    ;; `__builtin__.__dict__.keys()' in Python 2.5.1.)  These patterns
+    ;; are debateable, but they at least help to spot possible
+    ;; shadowing of builtins.
+    (,(rx symbol-start (or
+         ;; exceptions
+         "ArithmeticError" "AssertionError" "AttributeError"
+         "BaseException" "DeprecationWarning" "EOFError"
+         "EnvironmentError" "Exception" "FloatingPointError"
+         "FutureWarning" "GeneratorExit" "IOError" "ImportError"
+         "ImportWarning" "IndentationError" "IndexError" "KeyError"
+         "KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
+         "NotImplemented" "NotImplementedError" "OSError"
+         "OverflowError" "PendingDeprecationWarning" "ReferenceError"
+         "RuntimeError" "RuntimeWarning" "StandardError"
+         "StopIteration" "SyntaxError" "SyntaxWarning" "SystemError"
+         "SystemExit" "TabError" "TypeError" "UnboundLocalError"
+         "UnicodeDecodeError" "UnicodeEncodeError" "UnicodeError"
+         "UnicodeTranslateError" "UnicodeWarning" "UserWarning"
+         "ValueError" "Warning" "ZeroDivisionError") symbol-end)
+     . font-lock-type-face)
+    (,(rx (or line-start (not (any ". \t"))) (* (any " \t")) symbol-start
+         (group (or
+         ;; callable built-ins, fontified when not appearing as
+         ;; object attributes
+         "abs" "all" "any" "apply" "basestring" "bool" "buffer" "callable"
+         "chr" "classmethod" "cmp" "coerce" "compile" "complex"
+         "copyright" "credits" "delattr" "dict" "dir" "divmod"
+         "enumerate" "eval" "execfile" "exit" "file" "filter" "float"
+         "frozenset" "getattr" "globals" "hasattr" "hash" "help"
+         "hex" "id" "input" "int" "intern" "isinstance" "issubclass"
+         "iter" "len" "license" "list" "locals" "long" "map" "max"
+         "min" "object" "oct" "open" "ord" "pow" "property" "quit"
+         "range" "raw_input" "reduce" "reload" "repr" "reversed"
+         "round" "set" "setattr" "slice" "sorted" "staticmethod"
+         "str" "sum" "super" "tuple" "type" "unichr" "unicode" "vars"
+         "xrange" "zip")) symbol-end)
+     (1 font-lock-builtin-face))
+    (,(rx symbol-start (or
+         ;; other built-ins
+         "True" "False" "None" "Ellipsis"
+         "_" "__debug__" "__doc__" "__import__" "__name__") symbol-end)
+     . font-lock-builtin-face)))
 
 (defconst python-font-lock-syntactic-keywords
   ;; Make outer chars of matching triple-quote sequences into generic
   ;; string delimiters.  Fixme: Is there a better way?
-  `((,(rx (or line-start buffer-start
-             (not (syntax escape)))    ; avoid escaped leading quote
-         (group (optional (any "uUrR"))) ; prefix gets syntax property
+  ;; First avoid a sequence preceded by an odd number of backslashes.
+  `((,(rx (not (any ?\\))
+         ?\\ (* (and ?\\ ?\\))
+         (group (syntax string-quote))
+         (backref 1)
+         (group (backref 1)))
+     (2 ,(string-to-syntax "\"")))     ; dummy
+    (,(rx (group (optional (any "uUrR"))) ; prefix gets syntax property
          (optional (any "rR"))           ; possible second prefix
          (group (syntax string-quote))   ; maybe gets property
          (backref 2)                     ; per first quote
@@ -144,14 +196,13 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
   ;; x = ''' """ ' a
   ;; '''
   ;; x '"""' x """ \"""" x
-  ;; Fixme:  """""" goes wrong (due to syntax-ppss not getting the string
-  ;; fence context).
   (save-excursion
     (goto-char (match-beginning 0))
     (cond
      ;; Consider property for the last char if in a fenced string.
      ((= n 3)
-      (let ((syntax (syntax-ppss)))
+      (let* ((font-lock-syntactic-keywords nil)
+            (syntax (syntax-ppss)))
        (when (eq t (nth 3 syntax))     ; after unclosed fence
          (goto-char (nth 8 syntax))    ; fence position
          (skip-chars-forward "uUrR")   ; skip any prefix
@@ -163,8 +214,9 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
               (= (match-beginning 1) (match-end 1))) ; prefix is null
          (and (= n 1)                  ; prefix
               (/= (match-beginning 1) (match-end 1)))) ; non-empty
-      (unless (nth 3 (syntax-ppss))
-        (eval-when-compile (string-to-syntax "|"))))
+      (let ((font-lock-syntactic-keywords nil))
+       (unless (eq 'string (syntax-ppss-context (syntax-ppss)))
+         (eval-when-compile (string-to-syntax "|")))))
      ;; Otherwise (we're in a non-matching string) the property is
      ;; nil, which is OK.
      )))
@@ -200,6 +252,7 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
     (define-key map "\C-c<" 'python-shift-left)
     (define-key map "\C-c>" 'python-shift-right)
     (define-key map "\C-c\C-k" 'python-mark-block)
+    (define-key map "\C-c\C-d" 'python-pdbtrack-toggle-stack-tracking)
     (define-key map "\C-c\C-n" 'python-next-statement)
     (define-key map "\C-c\C-p" 'python-previous-statement)
     (define-key map "\C-c\C-u" 'python-beginning-of-block)
@@ -214,7 +267,7 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
     (define-key map "\C-c\C-z" 'python-switch-to-python)
     (define-key map "\C-c\C-m" 'python-load-file)
     (define-key map "\C-c\C-l" 'python-load-file) ; a la cmuscheme
-    (substitute-key-definition 'complete-symbol 'python-complete-symbol
+    (substitute-key-definition 'complete-symbol 'symbol-complete
                               map global-map)
     (define-key map "\C-c\C-i" 'python-find-imports)
     (define-key map "\C-c\C-t" 'python-expand-template)
@@ -243,11 +296,9 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
        ("Templates..."
         :help "Expand templates for compound statements"
         :filter (lambda (&rest junk)
-                  (mapcar (lambda (elt)
-                            (vector (car elt) (cdr elt) t))
-                          python-skeletons))) ; defined later
+                   (abbrev-table-menu python-mode-abbrev-table)))
        "-"
-       ["Start interpreter" run-python
+       ["Start interpreter" python-shell
         :help "Run `inferior' Python in separate buffer"]
        ["Import/reload file" python-load-file
         :help "Load into inferior Python session"]
@@ -267,8 +318,10 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
        "-"
        ["Help on symbol" python-describe-symbol
         :help "Use pydoc on symbol at point"]
-       ["Complete symbol" python-complete-symbol
+       ["Complete symbol" symbol-complete
         :help "Complete (qualified) symbol before point"]
+       ["Find function" python-find-function
+        :help "Try to find source definition of function at point"]
        ["Update imports" python-find-imports
         :help "Update list of top-level imports for completion"]))
     map))
@@ -277,6 +330,14 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
 ;; eric has items including: (un)indent, (un)comment, restart script,
 ;; run script, debug script; also things for profiling, unit testing.
 
+(defvar python-shell-map
+  (let ((map (copy-keymap comint-mode-map)))
+    (define-key map [tab]   'tab-to-tab-stop)
+    (define-key map "\C-c-" 'py-up-exception)
+    (define-key map "\C-c=" 'py-down-exception)
+    map)
+  "Keymap used in *Python* shell buffers.")
+
 (defvar python-mode-syntax-table
   (let ((table (make-syntax-table)))
     ;; Give punctuation syntax to ASCII that normally has symbol
@@ -348,7 +409,7 @@ comments and strings, or that point is within brackets/parens."
                    (error nil))))))))
 
 (defun python-comment-line-p ()
-  "Return non-nil if current line has only a comment."
+  "Return non-nil iff current line has only a comment."
   (save-excursion
     (end-of-line)
     (when (eq 'comment (syntax-ppss-context (syntax-ppss)))
@@ -356,7 +417,7 @@ comments and strings, or that point is within brackets/parens."
       (looking-at (rx (or (syntax comment-start) line-end))))))
 
 (defun python-blank-line-p ()
-  "Return non-nil if current line is blank."
+  "Return non-nil iff current line is blank."
   (save-excursion
     (beginning-of-line)
     (looking-at "\\s-*$")))
@@ -441,6 +502,87 @@ statement."
   :group 'python
   :type 'integer)
 
+
+(defcustom python-default-interpreter 'cpython
+  "*Which Python interpreter is used by default.
+The value for this variable can be either `cpython' or `jpython'.
+
+When the value is `cpython', the variables `python-python-command' and
+`python-python-command-args' are consulted to determine the interpreter
+and arguments to use.
+
+When the value is `jpython', the variables `python-jpython-command' and
+`python-jpython-command-args' are consulted to determine the interpreter
+and arguments to use.
+
+Note that this variable is consulted only the first time that a Python
+mode buffer is visited during an Emacs session.  After that, use
+\\[python-toggle-shells] to change the interpreter shell."
+  :type '(choice (const :tag "Python (a.k.a. CPython)" cpython)
+                (const :tag "JPython" jpython))
+  :group 'python)
+
+(defcustom python-python-command-args '("-i")
+  "*List of string arguments to be used when starting a Python shell."
+  :type '(repeat string)
+  :group 'python)
+
+(defcustom python-jython-command-args '("-i")
+  "*List of string arguments to be used when starting a Jython shell."
+  :type '(repeat string)
+  :group 'python
+  :tag "JPython Command Args")
+
+;; for toggling between CPython and JPython
+(defvar python-which-shell nil)
+(defvar python-which-args  python-python-command-args)
+(defvar python-which-bufname "Python")
+(make-variable-buffer-local 'python-which-shell)
+(make-variable-buffer-local 'python-which-args)
+(make-variable-buffer-local 'python-which-bufname)
+
+(defcustom python-pdbtrack-do-tracking-p t
+  "*Controls whether the pdbtrack feature is enabled or not.
+
+When non-nil, pdbtrack is enabled in all comint-based buffers,
+e.g. shell interaction buffers and the *Python* buffer.
+
+When using pdb to debug a Python program, pdbtrack notices the
+pdb prompt and presents the line in the source file where the
+program is stopped in a pop-up buffer.  It's similar to what
+gud-mode does for debugging C programs with gdb, but without
+having to restart the program."
+  :type 'boolean
+  :group 'python)
+(make-variable-buffer-local 'python-pdbtrack-do-tracking-p)
+
+(defcustom python-pdbtrack-minor-mode-string " PDB"
+  "*Minor-mode sign to be displayed when pdbtrack is active."
+  :type 'string
+  :group 'python)
+
+;; Add a designator to the minor mode strings
+(or (assq 'python-pdbtrack-is-tracking-p minor-mode-alist)
+    (push '(python-pdbtrack-is-tracking-p python-pdbtrack-minor-mode-string)
+         minor-mode-alist))
+
+;; Bind python-file-queue before installing the kill-emacs-hook.
+(defvar python-file-queue nil
+  "Queue of Python temp files awaiting execution.
+Currently-active file is at the head of the list.")
+
+(defvar python-pdbtrack-is-tracking-p nil)
+
+(defconst python-pdbtrack-stack-entry-regexp
+  "^> \\(.*\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_]+\\)()"
+  "Regular expression pdbtrack uses to find a stack trace entry.")
+
+(defconst python-pdbtrack-input-prompt "\n[(<]*[Pp]db[>)]+ "
+  "Regular expression pdbtrack uses to recognize a pdb prompt.")
+
+(defconst python-pdbtrack-track-range 10000
+  "Max number of characters from end of buffer to search for stack entry.")
+
 (defun python-guess-indent ()
   "Guess step for indentation of current buffer.
 Set `python-indent' locally to the value guessed."
@@ -552,39 +694,38 @@ Set `python-indent' locally to the value guessed."
        ((looking-at (rx (0+ space) (syntax comment-start)
                        (not (any " \t\n")))) ; non-indentable comment
        (current-indentation))
-       (t (if python-honour-comment-indentation
-              ;; Back over whitespace, newlines, non-indentable comments.
-              (catch 'done
-                (while t
-                  (if (cond ((bobp))
-                            ;; not at comment start
-                            ((not (forward-comment -1))
-                             (python-beginning-of-statement)
-                             t)
-                            ;; trailing comment
-                            ((/= (current-column) (current-indentation))
-                             (python-beginning-of-statement)
-                             t)
-                            ;; indentable comment like python-mode.el
-                            ((and (looking-at (rx (syntax comment-start)
-                                                  (or space line-end)))
-                                  (/= 0 (current-column)))))
-                      (throw 'done t)))))
-          (python-indentation-levels)
-          ;; Prefer to indent comments with an immediately-following
-          ;; statement, e.g.
-          ;;       ...
-          ;;   # ...
-          ;;   def ...
-          (when (and (> python-indent-list-length 1)
-                     (python-comment-line-p))
-            (forward-line)
-            (unless (python-comment-line-p)
-              (let ((elt (assq (current-indentation) python-indent-list)))
-                (setq python-indent-list
-                      (nconc (delete elt python-indent-list)
-                             (list elt))))))
-          (caar (last python-indent-list)))))))
+       ((and python-honour-comment-indentation
+            ;; Back over whitespace, newlines, non-indentable comments.
+            (catch 'done
+              (while (cond ((bobp) nil)
+                           ((not (forward-comment -1))
+                            nil)       ; not at comment start
+                           ;; Now at start of comment -- trailing one?
+                           ((/= (current-column) (current-indentation))
+                            nil)
+                           ;; Indentable comment, like python-mode.el?
+                           ((and (looking-at (rx (syntax comment-start)
+                                                 (or space line-end)))
+                                 (/= 0 (current-column)))
+                            (throw 'done (current-column)))
+                           ;; Else skip it (loop).
+                           (t))))))
+       (t
+       (python-indentation-levels)
+       ;; Prefer to indent comments with an immediately-following
+       ;; statement, e.g.
+       ;;       ...
+       ;;   # ...
+       ;;   def ...
+       (when (and (> python-indent-list-length 1)
+                  (python-comment-line-p))
+         (forward-line)
+         (unless (python-comment-line-p)
+           (let ((elt (assq (current-indentation) python-indent-list)))
+             (setq python-indent-list
+                   (nconc (delete elt python-indent-list)
+                          (list elt))))))
+       (caar (last python-indent-list)))))))
 
 ;;;; Cycling through the possible indentations with successive TABs.
 
@@ -606,7 +747,7 @@ Set `python-indent' locally to the value guessed."
   '(("else" "if" "elif" "while" "for" "try" "except")
     ("elif" "if" "elif")
     ("except" "try" "except")
-    ("finally" "try"))
+    ("finally" "try" "except"))
   "Alist of keyword matches.
 The car of an element is a keyword introducing a statement which
 can close a block opened by a keyword in the cdr.")
@@ -777,11 +918,12 @@ reached start of buffer."
                     ;; Not sure why it was like this -- fails in case of
                     ;; last internal function followed by first
                     ;; non-def statement of the main body.
-                     ;;(and def-line (= in ci))
+;;                  (and def-line (= in ci))
                     (= in ci)
                     (< in ci)))
               (not (python-in-string/comment)))
-         (setq found t)))))
+         (setq found t)))
+    found))
 
 (defun python-end-of-defun ()
   "`end-of-defun-function' for Python.
@@ -835,22 +977,27 @@ Accounts for continuation lines, multi-line strings, and
 multi-line bracketed expressions."
   (beginning-of-line)
   (python-beginning-of-string)
-  (while (python-continuation-line-p)
-    (beginning-of-line)
-    (if (python-backslash-continuation-line-p)
-       (progn
-         (forward-line -1)
-         (while (python-backslash-continuation-line-p)
-           (forward-line -1)))
-      (python-beginning-of-string)
-      (python-skip-out)))
+  (let (point)
+    (while (and (python-continuation-line-p)
+               (if point
+                   (< (point) point)
+                 t))
+      (beginning-of-line)
+      (if (python-backslash-continuation-line-p)
+         (progn
+           (forward-line -1)
+           (while (python-backslash-continuation-line-p)
+             (forward-line -1)))
+       (python-beginning-of-string)
+       (python-skip-out))
+      (setq point (point))))
   (back-to-indentation))
 
 (defun python-skip-out (&optional forward syntax)
   "Skip out of any nested brackets.
 Skip forward if FORWARD is non-nil, else backward.
 If SYNTAX is non-nil it is the state returned by `syntax-ppss' at point.
-Return non-nil if skipping was done."
+Return non-nil iff skipping was done."
   (let ((depth (syntax-ppss-depth (or syntax (syntax-ppss))))
        (forward (if forward -1 1)))
     (unless (zerop depth)
@@ -921,11 +1068,14 @@ Return count of statements left to move."
   (if (< count 0)
       (python-previous-statement (- count))
     (beginning-of-line)
-    (while (and (> count 0) (not (eobp)))
-      (python-end-of-statement)
-      (python-skip-comments/blanks)
-      (unless (eobp)
-       (setq count (1- count))))
+    (let (bogus)
+      (while (and (> count 0) (not (eobp)) (not bogus))
+       (python-end-of-statement)
+       (python-skip-comments/blanks)
+       (if (eq 'string (syntax-ppss-context (syntax-ppss)))
+           (setq bogus t)
+         (unless (eobp)
+           (setq count (1- count))))))
     count))
 
 (defun python-beginning-of-block (&optional arg)
@@ -988,7 +1138,7 @@ don't move and return nil.  Otherwise return t."
                  (if (and (zerop ci) (not open))
                      (not (goto-char point))
                    (catch 'done
-                      (while (zerop (python-next-statement))
+                     (while (zerop (python-next-statement))
                        (when (or (and open (<= (current-indentation) ci))
                                  (< (current-indentation) ci))
                          (python-skip-comments/blanks t)
@@ -1005,9 +1155,21 @@ don't move and return nil.  Otherwise return t."
     (set-text-properties 0 (length function-name) nil function-name)
     function-name))
 
-
+\f
 ;;;; Imenu.
 
+;; For possibily speeding this up, here's the top of the ELP profile
+;; for rescanning pydoc.py (2.2k lines, 90kb):
+;; Function Name                         Call Count  Elapsed Time  Average Time
+;; ====================================  ==========  =============  ============
+;; python-imenu-create-index             156         2.430906      0.0155827307
+;; python-end-of-defun                   155         1.2718260000  0.0082053290
+;; python-end-of-block                   155         1.1898689999  0.0076765741
+;; python-next-statement                 2970        1.024717      0.0003450225
+;; python-end-of-statement               2970        0.4332190000  0.0001458649
+;; python-beginning-of-defun             265         0.0918479999  0.0003465962
+;; python-skip-comments/blanks           3125        0.0753319999  2.410...e-05
+
 (defvar python-recursing)
 (defun python-imenu-create-index ()
   "`imenu-create-index-function' for Python.
@@ -1083,13 +1245,15 @@ just insert a single colon."
 
 (defun python-backspace (arg)
   "Maybe delete a level of indentation on the current line.
-Do so if point is at the end of the line's indentation.
+Do so if point is at the end of the line's indentation outside
+strings and comments.
 Otherwise just call `backward-delete-char-untabify'.
 Repeat ARG times."
   (interactive "*p")
   (if (or (/= (current-indentation) (current-column))
          (bolp)
-         (python-continuation-line-p))
+         (python-continuation-line-p)
+         (python-in-string/comment))
       (backward-delete-char-untabify arg)
     ;; Look for the largest valid indentation which is smaller than
     ;; the current indentation.
@@ -1190,6 +1354,10 @@ local value.")
      1 2)
     (,(rx " in file " (group (1+ not-newline)) " on line "
          (group (1+ digit)))
+     1 2)
+    ;; pdb stack trace
+    (,(rx line-start "> " (group (1+ (not (any "(\"<"))))
+         "(" (group (1+ digit)) ")" (1+ (not (any "("))) "()")
      1 2))
   "`compilation-error-regexp-alist' for inferior Python.")
 
@@ -1199,7 +1367,7 @@ local value.")
     (define-key map "\C-c\C-l" 'python-load-file)
     (define-key map "\C-c\C-v" 'python-check)
     ;; Note that we _can_ still use these commands which send to the
-    ;; Python process even at the prompt provided we have a normal prompt,
+    ;; Python process even at the prompt iff we have a normal prompt,
     ;; i.e. '>>> ' and not '... '.  See the comment before
     ;; python-send-region.  Fixme: uncomment these if we address that.
 
@@ -1216,6 +1384,9 @@ local value.")
     ;; (modify-syntax-entry ?\" "." st)
     st))
 
+;; Autoloaded.
+(declare-function compilation-shell-minor-mode "compile" (&optional arg))
+
 ;; Fixme: This should inherit some stuff from `python-mode', but I'm
 ;; not sure how much: at least some keybindings, like C-c C-f;
 ;; syntax?; font-locking, e.g. for triple-quoted strings?
@@ -1245,7 +1416,7 @@ For running multiple processes in multiple buffers, see `run-python' and
   ;; 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 (repeat 3 (any ">.")) " "))))
+       (rx line-start (1+ (and (or (repeat 3 (any ">.")) "(Pdb)") " "))))
   (set (make-local-variable 'compilation-error-regexp-alist)
        python-compilation-regexp-alist)
   (compilation-shell-minor-mode 1))
@@ -1274,11 +1445,16 @@ Don't save anything for STR matching `inferior-python-filter-regexp'."
 (defvar python-preoutput-result nil
   "Data from last `_emacs_out' line seen by the preoutput filter.")
 
+(defvar python-preoutput-continuation nil
+  "If non-nil, funcall this when `python-preoutput-filter' sees `_emacs_ok'.")
+
 (defvar python-preoutput-leftover nil)
 (defvar python-preoutput-skip-next-prompt nil)
 
 ;; Using this stops us getting lines in the buffer like
 ;; >>> ... ... >>>
+;; Also look for (and delete) an `_emacs_ok' string and call
+;; `python-preoutput-continuation' if we get it.
 (defun python-preoutput-filter (s)
   "`comint-preoutput-filter-functions' function: ignore prompts not at bol."
   (when python-preoutput-leftover
@@ -1328,6 +1504,23 @@ Don't save anything for STR matching `inferior-python-filter-regexp'."
 
 (autoload 'comint-check-proc "comint")
 
+(defvar python-version-checked nil)
+(defun python-check-version (cmd)
+  "Check that CMD runs a suitable version of Python."
+  ;; Fixme:  Check on Jython.
+  (unless (or python-version-checked
+             (equal 0 (string-match (regexp-quote python-python-command)
+                                    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))
+       (error "Only Python versions >= 2.2 and < 3.0 supported")))
+    (setq python-version-checked t)))
+
 ;;;###autoload
 (defun run-python (&optional cmd noshow new)
   "Run an inferior Python process, input and output via buffer *Python*.
@@ -1348,22 +1541,26 @@ buffer for a list of commands.)"
   (interactive (if current-prefix-arg
                   (list (read-string "Run Python: " python-command) nil t)
                 (list python-command)))
-  (unless cmd (setq cmd python-python-command))
+  (unless cmd (setq cmd python-command))
+  (python-check-version cmd)
   (setq python-command cmd)
   ;; Fixme: Consider making `python-buffer' buffer-local as a buffer
   ;; (not a name) in Python buffers from which `run-python' &c is
   ;; invoked.  Would support multiple processes better.
   (when (or new (not (comint-check-proc python-buffer)))
     (with-current-buffer
-        (let* ((cmdlist (append (python-args-to-list cmd) '("-i")))
-               (path (getenv "PYTHONPATH"))
-               (process-environment    ; to import emacs.py
-                (cons (concat "PYTHONPATH=" data-directory
-                              (if path (concat path-separator path)))
-                      process-environment)))
-          (apply 'make-comint-in-buffer "Python"
-                 (if new (generate-new-buffer "*Python*") "*Python*")
-                 (car cmdlist) nil (cdr cmdlist)))
+       (let* ((cmdlist (append (python-args-to-list cmd) '("-i")))
+              (path (getenv "PYTHONPATH"))
+              (process-environment     ; to import emacs.py
+               (cons (concat "PYTHONPATH="
+                             (if path (concat path path-separator))
+                             data-directory)
+                     process-environment))
+              ;; Suppress use of pager for help output:
+              (process-connection-type nil))
+         (apply 'make-comint-in-buffer "Python"
+                (generate-new-buffer "*Python*")
+                (car cmdlist) nil (cdr cmdlist)))
       (setq-default python-buffer (current-buffer))
       (setq python-buffer (current-buffer))
       (accept-process-output (get-buffer-process python-buffer) 5)
@@ -1375,7 +1572,13 @@ buffer for a list of commands.)"
       ;; seems worth putting in a separate file, and it's probably cleaner
       ;; to put it in a module.
       ;; Ensure we're at a prompt before doing anything else.
-      (python-send-string "import emacs")))
+      (python-send-string "import emacs")
+      ;; The following line was meant to ensure that we're at a prompt
+      ;; before doing anything else.  However, this can cause Emacs to
+      ;; hang waiting for a response, if that Python function fails
+      ;; (i.e. raises an exception).
+      ;; (python-send-receive "print '_emacs_out ()'")
+      ))
   (if (derived-mode-p 'python-mode)
       (setq python-buffer (default-value 'python-buffer))) ; buffer-local
   ;; Without this, help output goes into the inferior python buffer if
@@ -1383,30 +1586,14 @@ buffer for a list of commands.)"
   (sit-for 1 t)        ;Should we use accept-process-output instead?  --Stef
   (unless noshow (pop-to-buffer python-buffer t)))
 
-;; Fixme: We typically lose if the inferior isn't in the normal REPL,
-;; e.g. prompt is `help> '.  Probably raise an error if the form of
-;; the prompt is unexpected.  Actually, it needs to be `>>> ', not
-;; `... ', i.e. we're not inputting a block &c.  However, this may not
-;; be the place to check it, e.g. we might actually want to send
-;; commands having set up such a state.
-
 (defun python-send-command (command)
-  "Like `python-send-string' but resets `compilation-shell-minor-mode'.
-COMMAND should be a single statement."
-  ;; (assert (not (string-match "\n" command)))
-  ;; (let ((end (marker-position (process-mark (python-proc)))))
-  (with-current-buffer (process-buffer (python-proc))
-    (goto-char (point-max))
-    (compilation-forget-errors)
-    (python-send-string command)
-    (setq compilation-last-buffer (current-buffer)))
-    ;; No idea what this is for but it breaks the call to
-    ;; compilation-fake-loc in python-send-region.  -- Stef
-    ;; Must wait until this has completed before re-setting variables below.
-    ;; (python-send-receive "print '_emacs_out ()'")
-    ;; (with-current-buffer python-buffer
-    ;;   (set-marker compilation-parsing-end end))
-    ) ;;)
+  "Like `python-send-string' but resets `compilation-shell-minor-mode'."
+  (when (python-check-comint-prompt)
+    (with-current-buffer (process-buffer (python-proc))
+      (goto-char (point-max))
+      (compilation-forget-errors)
+      (python-send-string command)
+      (setq compilation-last-buffer (current-buffer)))))
 
 (defun python-send-region (start end)
   "Send the region to the inferior Python process."
@@ -1530,8 +1717,8 @@ See variable `python-buffer'.  Starts a new process if necessary."
   (unless (comint-check-proc python-buffer)
     (run-python nil t))
   (get-buffer-process (if (derived-mode-p 'inferior-python-mode)
-                          (current-buffer)
-                        python-buffer)))
+                         (current-buffer)
+                       python-buffer)))
 
 (defun python-set-proc ()
   "Set the default value of `python-buffer' to correspond to this buffer.
@@ -1606,16 +1793,27 @@ will."
 
 (defun python-send-receive (string)
   "Send STRING to inferior Python (if any) and return result.
-The result is what follows `_emacs_out' in the output."
+The result is what follows `_emacs_out' in the output.
+This is a no-op if `python-check-comint-prompt' returns nil."
   (python-send-string string)
   (let ((proc (python-proc)))
     (with-current-buffer (process-buffer proc)
-      (set (make-local-variable 'python-preoutput-result) nil)
-      (while (progn
-               (accept-process-output proc 5)
-               (null python-preoutput-result)))
-      (prog1 python-preoutput-result
-        (kill-local-variable 'python-preoutput-result)))))
+      (when (python-check-comint-prompt proc)
+       (set (make-local-variable 'python-preoutput-result) nil)
+       (while (progn
+                (accept-process-output proc 5)
+                (null python-preoutput-result)))
+       (prog1 python-preoutput-result
+         (kill-local-variable 'python-preoutput-result))))))
+
+(defun python-check-comint-prompt (&optional proc)
+  "Return non-nil iff there's a normal prompt in the inferior buffer.
+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)))))
 
 ;; Fixme:  Is there anything reasonable we can do with random methods?
 ;; (Currently only works with functions.)
@@ -1629,25 +1827,27 @@ instance.  Assumes an inferior Python is running."
     (with-local-quit
       ;; First try the symbol we're on.
       (or (and symbol
-               (python-send-receive (format "emacs.eargs(%S, %s)"
-                                            symbol python-imports)))
-          ;; Try moving to symbol before enclosing parens.
-          (let ((s (syntax-ppss)))
-            (unless (zerop (car s))
-              (when (eq ?\( (char-after (nth 1 s)))
-                (save-excursion
-                  (goto-char (nth 1 s))
-                  (skip-syntax-backward "-")
-                  (let ((point (point)))
-                    (skip-chars-backward "a-zA-Z._")
-                    (if (< (point) point)
-                        (python-send-receive
-                         (format "emacs.eargs(%S, %s)"
-                                 (buffer-substring-no-properties (point) point)
-                                 python-imports))))))))))))
+              (python-send-receive (format "emacs.eargs(%S, %s)"
+                                           symbol python-imports)))
+         ;; Try moving to symbol before enclosing parens.
+         (let ((s (syntax-ppss)))
+           (unless (zerop (car s))
+             (when (eq ?\( (char-after (nth 1 s)))
+               (save-excursion
+                 (goto-char (nth 1 s))
+                 (skip-syntax-backward "-")
+                 (let ((point (point)))
+                   (skip-chars-backward "a-zA-Z._")
+                   (if (< (point) point)
+                       (python-send-receive
+                        (format "emacs.eargs(%S, %s)"
+                                (buffer-substring-no-properties (point) point)
+                                python-imports))))))))))))
 \f
 ;;;; Info-look functionality.
 
+(declare-function info-lookup-maybe-add-help "info-look" (&rest arg))
+
 (defun python-after-info-look ()
   "Set up info-look for Python.
 Used with `eval-after-load'."
@@ -1662,9 +1862,9 @@ Used with `eval-after-load'."
            (condition-case ()
                ;; Don't use `info' because it would pop-up a *info* buffer.
                (with-no-warnings
-                (Info-goto-node (format "(python%s-lib)Miscellaneous Index"
-                                        version))
-                t)
+                 (Info-goto-node (format "(python%s-lib)Miscellaneous Index"
+                                         version))
+                 t)
              (error nil)))))
     (info-lookup-maybe-add-help
      :mode 'python-mode
@@ -1737,47 +1937,68 @@ The criterion is either a match for `jython-mode' via
              (jython-mode)))))))
 
 (defun python-fill-paragraph (&optional justify)
-  "`fill-paragraph-function' handling comments and multi-line strings.
-If any of the current line is a comment, fill the comment or the
-paragraph of it that point is in, preserving the comment's
-indentation and initial comment characters.  Similarly if the end
-of the current line is in or at the end of a multi-line string.
-Otherwise, do nothing."
+  "`fill-paragraph-function' handling multi-line strings and possibly comments.
+If any of the current line is in or at the end of a multi-line string,
+fill the string or the paragraph of it that point is in, preserving
+the string's indentation."
   (interactive "P")
   (or (fill-comment-paragraph justify)
-      ;; The `paragraph-start' and `paragraph-separate' variables
-      ;; don't allow us to delimit the last paragraph in a multi-line
-      ;; string properly, so narrow to the string and then fill around
-      ;; (the end of) the current line.
       (save-excursion
        (end-of-line)
        (let* ((syntax (syntax-ppss))
               (orig (point))
-              (start (nth 8 syntax))
-              end)
-         (cond ((eq t (nth 3 syntax))        ; in fenced string
-                (goto-char (nth 8 syntax))   ; string start
+              start end)
+         (cond ((nth 4 syntax) ; comment.   fixme: loses with trailing one
+                (let (fill-paragraph-function)
+                  (fill-paragraph justify)))
+               ;; The `paragraph-start' and `paragraph-separate'
+               ;; variables don't allow us to delimit the last
+               ;; paragraph in a multi-line string properly, so narrow
+               ;; to the string and then fill around (the end of) the
+               ;; current line.
+               ((eq t (nth 3 syntax))  ; in fenced string
+                (goto-char (nth 8 syntax)) ; string start
+                (setq start (line-beginning-position))
                 (setq end (condition-case () ; for unbalanced quotes
-                               (progn (forward-sexp) (point))
+                               (progn (forward-sexp)
+                                      (- (point) 3))
                              (error (point-max)))))
-               ((re-search-backward "\\s|\\s-*\\=" nil t) ; end of fenced
-                                                          ; string
+               ((re-search-backward "\\s|\\s-*\\=" nil t) ; end of fenced string
                 (forward-char)
                 (setq end (point))
                 (condition-case ()
                     (progn (backward-sexp)
-                           (setq start (point)))
-                  (error (setq end nil)))))
+                           (setq start (line-beginning-position)))
+                  (error nil))))
          (when end
            (save-restriction
              (narrow-to-region start end)
              (goto-char orig)
-              (let ((paragraph-separate
-                     ;; Make sure that fenced-string delimiters that stand
-                     ;; on their own line stay there.
-                     (concat "[ \t]*['\"]+[ \t]*$\\|" paragraph-separate)))
-                (fill-paragraph justify))))))
-      t))
+             ;; Avoid losing leading and trailing newlines in doc
+             ;; strings written like:
+             ;;   """
+             ;;   ...
+             ;;   """
+             (let ((paragraph-separate
+                    ;; Note that the string could be part of an
+                    ;; expression, so it can have preceding and
+                    ;; trailing non-whitespace.
+                    (concat
+                     (rx (or
+                          ;; Opening triple quote without following text.
+                          (and (* nonl)
+                               (group (syntax string-delimiter))
+                               (repeat 2 (backref 1))
+                               ;; Fixme:  Not sure about including
+                               ;; trailing whitespace.
+                               (* (any " \t"))
+                               eol)
+                          ;; Closing trailing quote without preceding text.
+                          (and (group (any ?\" ?')) (backref 2)
+                               (syntax string-delimiter))))
+                     "\\(?:" paragraph-separate "\\)"))
+                   fill-paragraph-function)
+               (fill-paragraph justify))))))) t)
 
 (defun python-shift-left (start end &optional count)
   "Shift lines in region COUNT (the prefix arg) columns to the left.
@@ -1834,7 +2055,6 @@ of current line."
        (while (or (null length-limit)
                   (null (cdr accum))
                   (< length length-limit))
-         (setq start nil)
          (let ((started-from (point)))
            (python-beginning-of-block)
            (end-of-line)
@@ -1868,7 +2088,8 @@ Uses `python-beginning-of-block', `python-end-of-block'."
 \f
 ;;;; Completion.
 
-(defvar python-imports nil
+;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2008-01/msg00076.html
+(defvar python-imports "None"
   "String of top-level import statements updated by `python-find-imports'.")
 (make-variable-buffer-local 'python-imports)
 
@@ -1886,9 +2107,13 @@ Uses `python-beginning-of-block', `python-end-of-block'."
        (goto-char (point-min))
        (while (re-search-forward "^import\\>\\|^from\\>" nil t)
          (unless (syntax-ppss-context (syntax-ppss))
-           (push (buffer-substring (line-beginning-position)
-                                   (line-beginning-position 2))
-                 lines)))
+           (let ((start (line-beginning-position)))
+             ;; Skip over continued lines.
+             (while (and (eq ?\\ (char-before (line-end-position)))
+                         (= 0 (forward-line 1)))
+               t)
+             (push (buffer-substring start (line-beginning-position 2))
+                   lines))))
        (setq python-imports
              (if lines
                  (apply #'concat
@@ -1939,60 +2164,6 @@ Uses `python-imports' to load modules against which to complete."
                       nil t)
                      (match-beginning 1)))))
     (if start (buffer-substring-no-properties start end))))
-
-(defun python-complete-symbol ()
-  "Perform completion on the Python symbol preceding point.
-Repeating the command scrolls the completion window."
-  (interactive)
-  (let ((window (get-buffer-window "*Completions*")))
-    (if (and (eq last-command this-command)
-            (window-live-p window) (window-buffer window)
-            (buffer-name (window-buffer window)))
-       (with-current-buffer (window-buffer window)
-         (if (pos-visible-in-window-p (point-max) window)
-             (set-window-start window (point-min))
-           (save-selected-window
-             (select-window window)
-             (scroll-up))))
-      ;; Do completion.
-      (let* ((end (point))
-            (symbol (python-partial-symbol))
-            (completions (python-symbol-completions symbol))
-            (completion (if completions
-                            (try-completion symbol completions))))
-       (when symbol
-         (cond ((eq completion t))
-               ((null completion)
-                (message "Can't find completion for \"%s\"" symbol)
-                (ding))
-               ((not (string= symbol completion))
-                (delete-region (- end (length symbol)) end)
-                (insert completion))
-               (t
-                (message "Making completion list...")
-                (with-output-to-temp-buffer "*Completions*"
-                  (display-completion-list completions symbol))
-                (message "Making completion list...%s" "done"))))))))
-
-(defun python-try-complete (old)
-  "Completion function for Python for use with `hippie-expand'."
-  (when (derived-mode-p 'python-mode)  ; though we only add it locally
-    (unless old
-      (let ((symbol (python-partial-symbol)))
-       (he-init-string (- (point) (length symbol)) (point))
-       (if (not (he-string-member he-search-string he-tried-table))
-           (push he-search-string he-tried-table))
-       (setq he-expand-list
-             (and symbol (python-symbol-completions symbol)))))
-    (while (and he-expand-list
-               (he-string-member (car he-expand-list) he-tried-table))
-      (pop he-expand-list))
-    (if he-expand-list
-       (progn
-         (he-substitute-string (pop he-expand-list))
-         t)
-      (if old (he-reset-string))
-      nil)))
 \f
 ;;;; FFAP support
 
@@ -2003,54 +2174,75 @@ Repeating the command scrolls the completion window."
 (eval-after-load "ffap"
   '(push '(python-mode . python-module-path) ffap-alist))
 \f
-;;;; Skeletons
+;;;; Find-function support
 
-(defcustom python-use-skeletons nil
-  "Non-nil means template skeletons will be automagically inserted.
-This happens when pressing \"if<SPACE>\", for example, to prompt for
-the if condition."
-  :type 'boolean
-  :group 'python)
+;; Fixme: key binding?
 
-(defvar python-skeletons nil
-  "Alist of named skeletons for Python mode.
-Elements are of the form (NAME . EXPANDER-FUNCTION).")
+(defun python-find-function (name)
+  "Find source of definition of function NAME.
+Interactively, prompt for name."
+  (interactive
+   (let ((symbol (with-syntax-table python-dotty-syntax-table
+                  (current-word)))
+        (enable-recursive-minibuffers t))
+     (list (read-string (if symbol
+                           (format "Find location of (default %s): " symbol)
+                         "Find location of: ")
+                       nil nil symbol))))
+  (unless python-imports
+    (error "Not called from buffer visiting Python file"))
+  (let* ((loc (python-send-receive (format "emacs.location_of (%S, %s)"
+                                          name python-imports)))
+        (loc (car (read-from-string loc)))
+        (file (car loc))
+        (line (cdr loc)))
+    (unless file (error "Don't know where `%s' is defined" name))
+    (pop-to-buffer (find-file-noselect file))
+    (when (integerp line)
+      (goto-line line))))
+\f
+;;;; Skeletons
 
-(defvar python-mode-abbrev-table nil
-  "Abbrev table for Python mode.
-The default contents correspond to the elements of `python-skeletons'.")
-(define-abbrev-table 'python-mode-abbrev-table ())
+(define-abbrev-table 'python-mode-abbrev-table ()
+  "Abbrev table for Python mode."
+  :case-fixed t
+  ;; Allow / inside abbrevs.
+  :regexp "\\(?:^\\|[^/]\\)\\<\\([[:word:]/]+\\)\\W*"
+  ;; Only expand in code.
+  :enable-function (lambda () (not (python-in-string/comment))))
 
 (eval-when-compile
-  ;; Define a user-level skeleton and add it to `python-skeletons' and
-  ;; the abbrev table.
+  ;; Define a user-level skeleton and add it to the abbrev table.
 (defmacro def-python-skeleton (name &rest elements)
   (let* ((name (symbol-name name))
         (function (intern (concat "python-insert-" name))))
     `(progn
-       (add-to-list 'python-skeletons ',(cons name function))
-       (if python-use-skeletons
-          (define-abbrev python-mode-abbrev-table ,name "" ',function nil t))
+       ;; Usual technique for inserting a skeleton, but expand
+       ;; to the original abbrev instead if in a comment or string.
+       (define-abbrev python-mode-abbrev-table ,name ""
+        ',function
+        nil t)                         ; system abbrev
        (define-skeleton ,function
         ,(format "Insert Python \"%s\" template." name)
         ,@elements)))))
 (put 'def-python-skeleton 'lisp-indent-function 2)
 
-;; From `skeleton-further-elements':
+;; From `skeleton-further-elements' set below:
 ;;  `<': outdent a level;
 ;;  `^': delete indentation on current line and also previous newline.
-;;       Not quote like `delete-indentation'.  Assumes point is at
+;;       Not quite like `delete-indentation'.  Assumes point is at
 ;;       beginning of indentation.
 
 (def-python-skeleton if
   "Condition: "
   "if " str ":" \n
-  > _ \n
+  > -1    ; Fixme: I don't understand the spurious space this removes.
+  _ \n
   ("other condition, %s: "
    <                   ; Avoid wrong indentation after block opening.
    "elif " str ":" \n
    > _ \n nil)
-  (python-else) | ^)
+  '(python-else) | ^)
 
 (define-skeleton python-else
   "Auxiliary skeleton."
@@ -2063,25 +2255,25 @@ The default contents correspond to the elements of `python-skeletons'.")
 (def-python-skeleton while
   "Condition: "
   "while " str ":" \n
-  > _ \n
-  (python-else) | ^)
+  > -1 _ \n
+  '(python-else) | ^)
 
 (def-python-skeleton for
   "Target, %s: "
   "for " str " in " (skeleton-read "Expression, %s: ") ":" \n
-  > _ \n
-  (python-else) | ^)
+  > -1 _ \n
+  '(python-else) | ^)
 
 (def-python-skeleton try/except
   nil
   "try:" \n
-  > _ \n
+  > -1 _ \n
   ("Exception, %s: "
-   < "except " str (python-target) ":" \n
+   < "except " str '(python-target) ":" \n
    > _ \n nil)
   < "except:" \n
   > _ \n
-  (python-else) | ^)
+  '(python-else) | ^)
 
 (define-skeleton python-target
   "Auxiliary skeleton."
@@ -2090,7 +2282,7 @@ The default contents correspond to the elements of `python-skeletons'.")
 (def-python-skeleton try/finally
   nil
   "try:" \n
-  > _ \n
+  > -1 _ \n
   < "finally:" \n
   > _ \n)
 
@@ -2098,7 +2290,7 @@ The default contents correspond to the elements of `python-skeletons'.")
   "Name: "
   "def " str " (" ("Parameter, %s: " (unless (equal ?\( (char-before)) ", ")
                     str) "):" \n
-  "\"\"\"" @ " \"\"\"" \n          ; Fixme: syntaxification wrong for """"""
+  "\"\"\"" - "\"\"\"" \n     ; Fixme:  extra space inserted -- why?).
   > _ \n)
 
 (def-python-skeleton class
@@ -2108,7 +2300,7 @@ The default contents correspond to the elements of `python-skeletons'.")
                     str)
   & ")" | -2                           ; close list or remove opening
   ":" \n
-  "\"\"\"" @ " \"\"\"" \n
+  "\"\"\"" - "\"\"\"" \n
   > _ \n)
 
 (defvar python-default-template "if"
@@ -2121,13 +2313,14 @@ Interactively, prompt for the name with completion."
   (interactive
    (list (completing-read (format "Template to expand (default %s): "
                                  python-default-template)
-                         python-skeletons nil t)))
+                         python-mode-abbrev-table nil t nil nil
+                          python-default-template)))
   (if (equal "" name)
       (setq name python-default-template)
     (setq python-default-template name))
-  (let ((func (cdr (assoc name python-skeletons))))
-    (if func
-       (funcall func)
+  (let ((sym (abbrev-symbol name python-mode-abbrev-table)))
+    (if sym
+        (abbrev-insert sym)
       (error "Undefined template: %s" name))))
 \f
 ;;;; Bicycle Repair Man support
@@ -2151,63 +2344,51 @@ without confirmation."
        (pymacs-load "bikeemacs" "brm-") ; first line of normal recipe
        (let ((py-mode-map (make-sparse-keymap)) ; it assumes this
              (features (cons 'python-mode features))) ; and requires this
-         (brm-init))                   ; second line of normal recipe
-        (remove-hook 'python-mode-hook ; undo this from `brm-init'
-                     '(lambda () (easy-menu-add brm-menu)))
-        (easy-menu-define
-          python-brm-menu python-mode-map
-          "Bicycle Repair Man"
-          '("BicycleRepairMan"
-            :help "Interface to navigation and refactoring tool"
-            "Queries"
-            ["Find References" brm-find-references
-             :help "Find references to name at point in compilation buffer"]
-            ["Find Definition" brm-find-definition
-             :help "Find definition of name at point"]
-            "-"
-            "Refactoring"
-            ["Rename" brm-rename
-             :help "Replace name at point with a new name everywhere"]
-            ["Extract Method" brm-extract-method
-             :active (and mark-active (not buffer-read-only))
-             :help "Replace statements in region with a method"]
-            ["Extract Local Variable" brm-extract-local-variable
-             :active (and mark-active (not buffer-read-only))
-             :help "Replace expression in region with an assignment"]
-            ["Inline Local Variable" brm-inline-local-variable
-             :help
-             "Substitute uses of variable at point with its definition"]
-            ;; Fixme:  Should check for anything to revert.
-            ["Undo Last Refactoring" brm-undo :help ""])))
-    (error (error "Bicyclerepairman setup failed: %s" data))))
+         (brm-init)                    ; second line of normal recipe
+         (remove-hook 'python-mode-hook ; undo this from `brm-init'
+                      '(lambda () (easy-menu-add brm-menu)))
+         (easy-menu-define
+           python-brm-menu python-mode-map
+           "Bicycle Repair Man"
+           '("BicycleRepairMan"
+             :help "Interface to navigation and refactoring tool"
+             "Queries"
+             ["Find References" brm-find-references
+              :help "Find references to name at point in compilation buffer"]
+             ["Find Definition" brm-find-definition
+              :help "Find definition of name at point"]
+             "-"
+             "Refactoring"
+             ["Rename" brm-rename
+              :help "Replace name at point with a new name everywhere"]
+             ["Extract Method" brm-extract-method
+              :active (and mark-active (not buffer-read-only))
+              :help "Replace statements in region with a method"]
+             ["Extract Local Variable" brm-extract-local-variable
+              :active (and mark-active (not buffer-read-only))
+              :help "Replace expression in region with an assignment"]
+             ["Inline Local Variable" brm-inline-local-variable
+              :help
+              "Substitute uses of variable at point with its definition"]
+             ;; Fixme:  Should check for anything to revert.
+             ["Undo Last Refactoring" brm-undo :help ""]))))
+    (error (error "BicycleRepairMan setup failed: %s" data))))
 \f
 ;;;; Modes.
 
+;; pdb tracking is alert once this file is loaded, but takes no action if
+;; `python-pdbtrack-do-tracking-p' is nil.
+(add-hook 'comint-output-filter-functions 'python-pdbtrack-track-stack-file)
+
 (defvar outline-heading-end-regexp)
 (defvar eldoc-documentation-function)
-
-;; Stuff to allow expanding abbrevs with non-word constituents.
-(defun python-abbrev-pc-hook ()
-  "Set the syntax table before possibly expanding abbrevs."
-  (remove-hook 'post-command-hook 'python-abbrev-pc-hook t)
-  (set-syntax-table python-mode-syntax-table))
-
-(defvar python-abbrev-syntax-table
-  (copy-syntax-table python-mode-syntax-table)
-  "Syntax table used when expanding abbrevs.")
-
-(defun python-pea-hook ()
-  "Reset the syntax table after possibly expanding abbrevs."
-  (set-syntax-table python-abbrev-syntax-table)
-  (add-hook 'post-command-hook 'python-abbrev-pc-hook nil t))
-(modify-syntax-entry ?/ "w" python-abbrev-syntax-table)
-
 (defvar python-mode-running)            ;Dynamically scoped var.
 
 ;;;###autoload
 (define-derived-mode python-mode fundamental-mode "Python"
   "Major mode for editing Python files.
-Font Lock mode is currently required for correct parsing of the source.
+Turns on Font Lock mode unconditionally since it is currently required
+for correct parsing of the source.
 See also `jython-mode', which is actually invoked if the buffer appears to
 contain Jython code.  See also `run-python' and associated Python mode
 commands for running Python under Emacs.
@@ -2276,25 +2457,30 @@ with skeleton expansions for compound statement templates.
   (add-hook 'eldoc-mode-hook
            (lambda () (run-python nil t)) ; need it running
            nil t)
+  (set (make-local-variable 'symbol-completion-symbol-function)
+       'python-partial-symbol)
+  (set (make-local-variable 'symbol-completion-completions-function)
+       'python-symbol-completions)
   ;; Fixme: should be in hideshow.  This seems to be of limited use
   ;; since it isn't (can't be) indentation-based.  Also hide-level
   ;; doesn't seem to work properly.
   (add-to-list 'hs-special-modes-alist
-              `(python-mode "^\\s-*def\\>" nil "#"
+              `(python-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#"
                 ,(lambda (arg)
-                  (python-end-of-defun)
-                  (skip-chars-backward " \t\n"))
+                   (python-end-of-defun)
+                   (skip-chars-backward " \t\n"))
                 nil))
   (set (make-local-variable 'skeleton-further-elements)
        '((< '(backward-delete-char-untabify (min python-indent
                                                 (current-column))))
         (^ '(- (1+ (current-indentation))))))
-  (add-hook 'pre-abbrev-expand-hook 'python-pea-hook nil t)
   (if (featurep 'hippie-exp)
       (set (make-local-variable 'hippie-expand-try-functions-list)
-          (cons 'python-try-complete hippie-expand-try-functions-list)))
+          (cons 'symbol-completion-try-complete
+                hippie-expand-try-functions-list)))
   ;; Python defines TABs as being 8-char wide.
   (set (make-local-variable 'tab-width) 8)
+  (unless font-lock-mode (font-lock-mode 1))
   (when python-guess-indent (python-guess-indent))
   ;; Let's make it harder for the user to shoot himself in the foot.
   (unless (= tab-width python-indent)
@@ -2305,11 +2491,16 @@ with skeleton expansions for compound statement templates.
     (let ((python-mode-running t))
       (python-maybe-jython))))
 
+;; Not done automatically in Emacs 21 or 22.
+(defcustom python-mode-hook nil
+  "Hook run when entering Python mode."
+  :group 'python
+  :type 'hook)
 (custom-add-option 'python-mode-hook 'imenu-add-menubar-index)
 (custom-add-option 'python-mode-hook
                   (lambda ()
-                     "Turn off Indent Tabs mode."
-                     (set (make-local-variable 'indent-tabs-mode) nil)))
+                    "Turn off Indent Tabs mode."
+                    (setq indent-tabs-mode nil)))
 (custom-add-option 'python-mode-hook 'turn-on-eldoc-mode)
 (custom-add-option 'python-mode-hook 'abbrev-mode)
 (custom-add-option 'python-mode-hook 'python-setup-brm)
@@ -2322,7 +2513,320 @@ Runs `jython-mode-hook' after `python-mode-hook'."
   :group 'python
   (set (make-local-variable 'python-command) python-jython-command))
 
+\f
+
+;; pdbtrack features
+
+(defun python-comint-output-filter-function (string)
+  "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>>> ")))
+            python-file-queue)
+    (condition-case nil
+        (delete-file (car python-file-queue))
+      (error nil))
+    (setq python-file-queue (cdr python-file-queue))
+    (if python-file-queue
+       (let ((pyproc (get-buffer-process (current-buffer))))
+         (python-execute-file pyproc (car python-file-queue))))))
+
+(defun python-pdbtrack-overlay-arrow (activation)
+  "Activate or deactivate arrow at beginning-of-line in current buffer."
+  (if activation
+      (progn
+        (setq overlay-arrow-position (make-marker)
+              overlay-arrow-string "=>"
+              python-pdbtrack-is-tracking-p t)
+        (set-marker overlay-arrow-position
+                    (save-excursion (beginning-of-line) (point))
+                    (current-buffer)))
+    (setq overlay-arrow-position nil
+          python-pdbtrack-is-tracking-p nil)))
+
+(defun python-pdbtrack-track-stack-file (text)
+  "Show the file indicated by the pdb stack entry line, in a separate window.
+
+Activity is disabled if the buffer-local variable
+`python-pdbtrack-do-tracking-p' is nil.
+
+We depend on the pdb input prompt being a match for
+`python-pdbtrack-input-prompt'.
+
+If the traceback target file path is invalid, we look for the
+most recently visited python-mode buffer which either has the
+name of the current function or class, or which defines the
+function or class.  This is to provide for scripts not in the
+local filesytem (e.g., Zope's 'Script \(Python)', but it's not
+Zope specific).  If you put a copy of the script in a buffer
+named for the script and activate python-mode, then pdbtrack will
+find it."
+  ;; Instead of trying to piece things together from partial text
+  ;; (which can be almost useless depending on Emacs version), we
+  ;; monitor to the point where we have the next pdb prompt, and then
+  ;; check all text from comint-last-input-end to process-mark.
+  ;;
+  ;; Also, we're very conservative about clearing the overlay arrow,
+  ;; to minimize residue.  This means, for instance, that executing
+  ;; other pdb commands wipe out the highlight.  You can always do a
+  ;; 'where' (aka 'w') PDB command to reveal the overlay arrow.
+
+  (let* ((origbuf (current-buffer))
+        (currproc (get-buffer-process origbuf)))
+
+    (if (not (and currproc python-pdbtrack-do-tracking-p))
+        (python-pdbtrack-overlay-arrow nil)
+
+      (let* ((procmark (process-mark currproc))
+             (block (buffer-substring (max comint-last-input-end
+                                           (- procmark
+                                              python-pdbtrack-track-range))
+                                      procmark))
+             target target_fname target_lineno target_buffer)
+
+        (if (not (string-match (concat python-pdbtrack-input-prompt "$") block))
+            (python-pdbtrack-overlay-arrow nil)
+
+          (setq target (python-pdbtrack-get-source-buffer block))
+
+          (if (stringp target)
+              (progn
+                (python-pdbtrack-overlay-arrow nil)
+                (message "pdbtrack: %s" target))
+
+            (setq target_lineno (car target)
+                  target_buffer (cadr target)
+                  target_fname (buffer-file-name target_buffer))
+            (switch-to-buffer-other-window target_buffer)
+            (goto-line target_lineno)
+            (message "pdbtrack: line %s, file %s" target_lineno target_fname)
+            (python-pdbtrack-overlay-arrow t)
+            (pop-to-buffer origbuf t)
+            ;; in large shell buffers, above stuff may cause point to lag output
+            (goto-char procmark)
+            )))))
+  )
+
+(defun python-pdbtrack-get-source-buffer (block)
+  "Return line number and buffer of code indicated by block's traceback text.
+
+We look first to visit the file indicated in the trace.
+
+Failing that, we look for the most recently visited python-mode buffer
+with the same name or having the named function.
+
+If we're unable find the source code we return a string describing the
+problem."
+
+  (if (not (string-match python-pdbtrack-stack-entry-regexp block))
+
+      "Traceback cue not found"
+
+    (let* ((filename (match-string 1 block))
+           (lineno (string-to-number (match-string 2 block)))
+           (funcname (match-string 3 block))
+           funcbuffer)
+
+      (cond ((file-exists-p filename)
+             (list lineno (find-file-noselect filename)))
+
+            ((setq funcbuffer (python-pdbtrack-grub-for-buffer funcname lineno))
+             (if (string-match "/Script (Python)$" filename)
+                 ;; Add in number of lines for leading '##' comments:
+                 (setq lineno
+                       (+ lineno
+                          (save-excursion
+                            (set-buffer funcbuffer)
+                            (if (equal (point-min)(point-max))
+                                0
+                              (count-lines
+                               (point-min)
+                               (max (point-min)
+                                    (string-match "^\\([^#]\\|#[^#]\\|#$\\)"
+                                                  (buffer-substring
+                                                   (point-min) (point-max)))
+                                    )))))))
+               (list lineno funcbuffer))
+
+            ((= (elt filename 0) ?\<)
+             (format "(Non-file source: '%s')" filename))
+
+            (t (format "Not found: %s(), %s" funcname filename)))
+      )
+    )
+  )
+
+(defun python-pdbtrack-grub-for-buffer (funcname lineno)
+  "Find recent python-mode buffer named, or having function named funcname."
+  (let ((buffers (buffer-list))
+        buf
+        got)
+    (while (and buffers (not got))
+      (setq buf (car buffers)
+            buffers (cdr buffers))
+      (if (and (save-excursion (set-buffer buf)
+                               (string= major-mode "python-mode"))
+               (or (string-match funcname (buffer-name buf))
+                   (string-match (concat "^\\s-*\\(def\\|class\\)\\s-+"
+                                         funcname "\\s-*(")
+                                 (save-excursion
+                                   (set-buffer buf)
+                                   (buffer-substring (point-min)
+                                                     (point-max))))))
+          (setq got buf)))
+    got))
+
+(defun python-toggle-shells (arg)
+  "Toggles between the CPython and JPython shells.
+
+With positive argument ARG (interactively \\[universal-argument]),
+uses the CPython shell, with negative ARG uses the JPython shell, and
+with a zero argument, toggles the shell.
+
+Programmatically, ARG can also be one of the symbols `cpython' or
+`jpython', equivalent to positive arg and negative arg respectively."
+  (interactive "P")
+  ;; default is to toggle
+  (if (null arg)
+      (setq arg 0))
+  ;; preprocess arg
+  (cond
+   ((equal arg 0)
+    ;; toggle
+    (if (string-equal python-which-bufname "Python")
+       (setq arg -1)
+      (setq arg 1)))
+   ((equal arg 'cpython) (setq arg 1))
+   ((equal arg 'jpython) (setq arg -1)))
+  (let (msg)
+    (cond
+     ((< 0 arg)
+      ;; set to CPython
+      (setq python-which-shell python-python-command
+           python-which-args python-python-command-args
+           python-which-bufname "Python"
+           msg "CPython"
+           mode-name "Python"))
+     ((> 0 arg)
+      (setq python-which-shell python-jython-command
+           python-which-args python-jython-command-args
+           python-which-bufname "JPython"
+           msg "JPython"
+           mode-name "JPython")))
+    (message "Using the %s shell" msg)))
+
+;; Python subprocess utilities and filters
+(defun python-execute-file (proc filename)
+  "Send to Python interpreter process PROC \"execfile('FILENAME')\".
+Make that process's buffer visible and force display.  Also make
+comint believe the user typed this string so that
+`kill-output-from-shell' does The Right Thing."
+  (let ((curbuf (current-buffer))
+       (procbuf (process-buffer proc))
+;      (comint-scroll-to-bottom-on-output t)
+       (msg (format "## working on region in file %s...\n" filename))
+        ;; add some comment, so that we can filter it out of history
+       (cmd (format "execfile(r'%s') # PYTHON-MODE\n" filename)))
+    (unwind-protect
+       (save-excursion
+         (set-buffer procbuf)
+         (goto-char (point-max))
+         (move-marker (process-mark proc) (point))
+         (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.
+This is like Shell mode, except that Python is running in the window
+instead of a shell.  See the `Interactive Shell' and `Shell Mode'
+sections of the Emacs manual for details, especially for the key
+bindings active in the `*Python*' buffer.
+
+With optional \\[universal-argument], the user is prompted for the
+flags to pass to the Python interpreter.  This has no effect when this
+command is used to switch to an existing process, only when a new
+process is started.  If you use this, you will probably want to ensure
+that the current arguments are retained (they will be included in the
+prompt).  This argument is ignored when this function is called
+programmatically, or when running in Emacs 19.34 or older.
+
+Note: You can toggle between using the CPython interpreter and the
+JPython interpreter by hitting \\[python-toggle-shells].  This toggles
+buffer local variables which control whether all your subshell
+interactions happen to the `*JPython*' or `*Python*' buffers (the
+latter is the name used for the CPython buffer).
+
+Warning: Don't use an interactive Python if you change sys.ps1 or
+sys.ps2 from their default values, or if you're running code that
+prints `>>> ' or `... ' at the start of a line.  `python-mode' can't
+distinguish your output from Python's output, and assumes that `>>> '
+at the start of a line is a prompt from Python.  Similarly, the Emacs
+Shell mode code assumes that both `>>> ' and `... ' at the start of a
+line are Python prompts.  Bad things can happen if you fool either
+mode.
+
+Warning:  If you do any editing *in* the process buffer *while* the
+buffer is accepting output from Python, do NOT attempt to `undo' the
+changes.  Some of the output (nowhere near the parts you changed!) may
+be lost if you do.  This appears to be an Emacs bug, an unfortunate
+interaction between undo and process filters; the same problem exists in
+non-Python process buffers using the default (Emacs-supplied) process
+filter."
+  (interactive "P")
+  ;; Set the default shell if not already set
+  (when (null python-which-shell)
+    (python-toggle-shells python-default-interpreter))
+  (let ((args python-which-args))
+    (when (and argprompt
+              (interactive-p)
+              (fboundp 'split-string))
+      ;; TBD: Perhaps force "-i" in the final list?
+      (setq args (split-string
+                 (read-string (concat python-which-bufname
+                                      " arguments: ")
+                              (concat
+                               (mapconcat 'identity python-which-args " ") " ")
+                              ))))
+    (switch-to-buffer-other-window
+     (apply 'make-comint python-which-bufname python-which-shell nil args))
+    (make-local-variable 'comint-prompt-regexp)
+    (set-process-sentinel (get-buffer-process (current-buffer))
+                          'python-sentinel)
+    (setq comint-prompt-regexp "^>>> \\|^[.][.][.] \\|^(pdb) ")
+    (add-hook 'comint-output-filter-functions
+             'python-comint-output-filter-function nil t)
+    ;; pdbtrack
+    (set-syntax-table python-mode-syntax-table)
+    (use-local-map python-shell-map)))
+
+(defun python-pdbtrack-toggle-stack-tracking (arg)
+  (interactive "P")
+  (if (not (get-buffer-process (current-buffer)))
+      (error "No process associated with buffer '%s'" (current-buffer)))
+  ;; missing or 0 is toggle, >0 turn on, <0 turn off
+  (if (or (not arg)
+         (zerop (setq arg (prefix-numeric-value arg))))
+      (setq python-pdbtrack-do-tracking-p (not python-pdbtrack-do-tracking-p))
+    (setq python-pdbtrack-do-tracking-p (> arg 0)))
+  (message "%sabled Python's pdbtrack"
+           (if python-pdbtrack-do-tracking-p "En" "Dis")))
+
+(defun turn-on-pdbtrack ()
+  (interactive)
+  (python-pdbtrack-toggle-stack-tracking 1))
+
+(defun turn-off-pdbtrack ()
+  (interactive)
+  (python-pdbtrack-toggle-stack-tracking 0))
+
+(defun python-sentinel (proc msg)
+  (setq overlay-arrow-position nil))
+
 (provide 'python)
 (provide 'python-21)
+
 ;; arch-tag: 6fce1d99-a704-4de9-ba19-c6e4912b0554
 ;;; python.el ends here