]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/python.el
Revision: miles@gnu.org--gnu-2005/emacs--unicode--0--patch-25
[gnu-emacs] / lisp / progmodes / python.el
index 5ca1277a9948a3440be8a0426968af906bcca53d..745bc57a9b07715d92cb8fd9364a8a592874b2b1 100644 (file)
@@ -1,8 +1,9 @@
 ;;; python.el --- silly walks for Python
 
-;; Copyright (C) 2003, 04  Free Software Foundation, Inc.
+;; Copyright (C) 2003, 2004  Free Software Foundation, Inc.
 
 ;; Author: Dave Love <fx@gnu.org>
+;; Maintainer: FSF
 ;; Created: Nov 2003
 ;; Keywords: languages
 
 ;; I've installed a minor mode to do the job properly in Emacs 22.
 ;; Other things seem more natural or canonical here, e.g. the
 ;; {beginning,end}-of-defun implementation dealing with nested
-;; definitions, and the inferior mode following `cmuscheme'.  (The
-;; inferior mode should be able to find the source of errors from
-;; `python-send-region' & al via `compilation-minor-mode', but I can't
-;; make that work with the current (March '04) compile.el.)
-;; Successive TABs cycle between possible indentations for the line.
+;; definitions, and the inferior mode following `cmuscheme'.  The
+;; inferior mode can find the source of errors from
+;; `python-send-region' & al via `compilation-minor-mode'.  Successive
+;; TABs cycle between possible indentations for the line.  There is
+;; symbol completion using lookup in Python.
 
 ;; Even where it has similar facilities, this is incompatible with
 ;; python-mode.el in various respects.  For instance, various key
 ;; bindings are changed to obey Emacs conventions, and things like
 ;; marking blocks and `beginning-of-defun' behave differently.
 
-;; TODO: See various Fixmes below.  It should be possible to arrange
-;; some sort of completion using the inferior interpreter.
+;; TODO: See various Fixmes below.
 
 ;;; Code:
 
 (require 'comint)
 (eval-when-compile
   (require 'compile)
-  (autoload 'Info-last "info")
-  (autoload 'Info-exit "info")
   (autoload 'info-lookup-maybe-add-help "info-look"))
-(autoload 'compilation-start "compile")        ; spurious compiler warning anyway
+(autoload 'compilation-start "compile")
 
 (defgroup python nil
   "Silly walks in the Python language"
   :group 'languages
-  :version "21.4"
+  :version "22.1"
   :link '(emacs-commentary-link "python"))
 \f
 ;;;###autoload
 (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 (and (group (optional (any "uUrR"))) ; prefix gets syntax property
+  `((,(rx (and (or line-start buffer-start (not (syntax escape))) ; avoid escaped
+                                                      ; leading quote
+              (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
@@ -132,32 +132,31 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
   ;;  ur"""ar""" x='"' # """
   ;; x = ''' """ ' a
   ;; '''
-  ;; x '"""' x
+  ;; x '"""' x """ \"""" x
   (save-excursion
     (goto-char (match-beginning 0))
-    (unless (eq ?\\ (char-before))
-      (cond
-       ;; Consider property for the last char if in a fenced string.
-       ((= n 3)
-       (let ((syntax (syntax-ppss)))
-         (when (eq t (nth 3 syntax))    ; after unclosed fence
-           (goto-char (nth 8 syntax))   ; fence position
-           ;; Skip any prefix.
-           (if (memq (char-after) '(?u ?U ?R ?r))
-               (skip-chars-forward "uUrR"))
-           ;; Is it a matching sequence?
-           (if (eq (char-after) (char-after (match-beginning 2)))
-               (eval-when-compile (string-to-syntax "|"))))))
-       ;; Consider property for initial char, accounting for prefixes.
-       ((or (and (= n 2)                               ; not prefix
-                (= (match-beginning 1) (match-end 1))) ; prefix is null
-           (and (= n 1)                                ; prefix
-                (/= (match-beginning 1) (match-end 1)))) ; non-empty
-       (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.
-      )))
+    (cond
+     ;; Consider property for the last char if in a fenced string.
+     ((= n 3)
+      (let ((syntax (syntax-ppss)))
+       (when (eq t (nth 3 syntax))     ; after unclosed fence
+         (goto-char (nth 8 syntax))    ; fence position
+         ;; Skip any prefix.
+         (if (memq (char-after) '(?u ?U ?R ?r))
+             (skip-chars-forward "uUrR"))
+         ;; Is it a matching sequence?
+         (if (eq (char-after) (char-after (match-beginning 2)))
+             (eval-when-compile (string-to-syntax "|"))))))
+     ;; Consider property for initial char, accounting for prefixes.
+     ((or (and (= n 2)                 ; not prefix
+              (= (match-beginning 1) (match-end 1))) ; prefix is null
+         (and (= n 1)                  ; prefix
+              (/= (match-beginning 1) (match-end 1)))) ; non-empty
+      (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.
+     )))
 
 ;; This isn't currently in `font-lock-defaults' as probably not worth
 ;; it -- we basically only mess with a few normally-symbol characters.
@@ -204,6 +203,8 @@ 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
+                              map global-map)
     ;; Fixme: Add :help to menu.
     (easy-menu-define python-menu map "Python Mode menu"
       '("Python"
@@ -262,9 +263,7 @@ Used for syntactic keywords.  N is the match number (1, 2 or 3)."
 ;;;; Utility stuff
 
 (defsubst python-in-string/comment ()
-  "Return non-nil if point is in a Python literal (a comment or string).
-Optional argument LIM indicates the beginning of the containing form,
-i.e. the limit on how far back to scan."
+  "Return non-nil if point is in a Python literal (a comment or string)."
   (syntax-ppss-context (syntax-ppss)))
 
 (defconst python-space-backslash-table
@@ -299,16 +298,18 @@ comments and strings, or that the bracket/paren nesting depth is nonzero."
               (syntax-ppss (line-beginning-position)))))))
 
 (defun python-comment-line-p ()
-  "Return non-nil if current line has only a comment or is blank."
+  "Return non-nil iff current line has only a comment."
   (save-excursion
-    (back-to-indentation)
-    (looking-at (rx (or (syntax comment-start) line-end)))))
+    (end-of-line)
+    (when (eq 'comment (syntax-ppss-context (syntax-ppss)))
+      (back-to-indentation)
+      (looking-at (rx (or (syntax comment-start) line-end))))))
 
 (defun python-beginning-of-string ()
   "Go to beginning of string around point.
 Do nothing if not in string."
   (let ((state (syntax-ppss)))
-    (when (nth 3 state)
+    (when (eq 'string (syntax-ppss-context state))
       (goto-char (nth 8 state)))))
 
 (defun python-open-block-statement-p (&optional bos)
@@ -323,7 +324,8 @@ BOS non-nil means point is known to be at beginning of statement."
                                     line-end))
                            (save-excursion (python-end-of-statement))
                            t)
-        (not (python-in-string/comment)))))
+        (not (progn (goto-char (match-beginning 0))
+                    (python-in-string/comment))))))
 
 (defun python-close-block-statement-p (&optional bos)
   "Return non-nil if current line is a statement closing a block.
@@ -384,7 +386,8 @@ Otherwise indent them to column zero."
 (defcustom python-honour-comment-indentation nil
   "Non-nil means indent relative to preceding comment line.
 Only do this for comments where the leading comment character is followed
-by space."
+by space.  This doesn't apply to comment lines, which are always indented
+in lines with preceding comments."
   :type 'boolean
   :group 'python)
 
@@ -514,6 +517,16 @@ Set `python-indent' locally to the value guessed."
                                     (- python-indent)))
                          0)))))))))
 
+(defun python-comment-indent ()
+  "`comment-indent-function' for Python."
+  ;; If previous non-blank line was a comment, use its indentation.
+  ;; FIXME: This seems unnecessary since the default code delegates to
+  ;; indent-according-to-mode.  --Stef
+  (unless (bobp)
+    (save-excursion
+      (forward-comment -1)
+      (if (eq ?# (char-after)) (current-column)))))
+
 ;;;; Cycling through the possible indentations with successive TABs.
 
 ;; These don't need to be buffer-local since they're only relevant
@@ -538,11 +551,17 @@ Set `python-indent' locally to the value guessed."
                      (point))))
 
 (defun python-indentation-levels ()
-  "Return a list of possible indentations for this statement.
+  "Return a list of possible indentations for this line.
 Includes the default indentation and those which would close all
-enclosing blocks."
+enclosing blocks.  Assumes the line has already been indented per
+`python-indent-line'.  Elements of the list are actually pairs:
+\(INDENTATION . TEXT), where TEXT is the initial text of the
+corresponding block opening (or nil)."
   (save-excursion
-    (let ((levels (list (cons (current-indentation) nil))))
+    (let ((levels (list (cons (current-indentation)
+                             (save-excursion
+                               (if (python-beginning-of-block)
+                                   (python-initial-text)))))))
       ;; Only one possibility if we immediately follow a block open or
       ;; are in a continuation line.
       (unless (or (python-continuation-line-p)
@@ -568,8 +587,7 @@ enclosing blocks."
       (if (> (- (point-max) pos) (point))
          (goto-char (- (point-max) pos))))))
 
-;; Fixme: Is the arg necessary?
-(defun python-indent-line (&optional arg)
+(defun python-indent-line ()
   "Indent current line as Python code.
 When invoked via `indent-for-tab-command', cycle through possible
 indentations for current line.  The cycle is broken by a command different
@@ -586,13 +604,30 @@ from `indent-for-tab-command', i.e. successive TABs do the cycling."
                 (beginning-of-line)
                 (delete-horizontal-space)
                 (indent-to (car (nth python-indent-index python-indent-list)))
-                (let ((text (cdr (nth python-indent-index
-                                      python-indent-list))))
-                  (if text (message "Closes: %s" text)))))
+                (if (python-block-end-p)
+                    (let ((text (cdr (nth python-indent-index
+                                          python-indent-list))))
+                      (if text
+                          (message "Closes: %s" text))))))
       (python-indent-line-1)
       (setq python-indent-list (python-indentation-levels)
            python-indent-list-length (length python-indent-list)
            python-indent-index (1- python-indent-list-length)))))
+
+(defun python-block-end-p ()
+  "Non-nil if this is a line in a statement closing a block,
+or a blank line indented to where it would close a block."
+  (and (not (python-comment-line-p))
+       (or (python-close-block-statement-p t)
+          (< (current-indentation)
+             (save-excursion
+               (python-previous-statement)
+               (current-indentation))))))
+
+;; Fixme: Define an indent-region-function.  It should probably leave
+;; lines alone if the indentation is already at one of the allowed
+;; levels.  Otherwise, M-C-\ typically keeps indenting more deeply
+;; down a function.
 \f
 ;;;; Movement.
 
@@ -605,7 +640,7 @@ start of buffer."
        (def-re (rx (and line-start (0+ space) (or "def" "class")
                         (1+ space)
                         (group (1+ (or word (syntax symbol)))))))
-       point found lep def-line)
+       found lep def-line)
     (if (python-comment-line-p)
        (setq ci most-positive-fixnum))
     (while (and (not (bobp)) (not found))
@@ -629,8 +664,7 @@ start of buffer."
   "`end-of-defun-function' for Python.
 Finds end of innermost nested class or method definition."
   (let ((orig (point))
-       (pattern (rx (and line-start (0+ space)
-                         (or "def" "class") space))))
+       (pattern (rx (and line-start (0+ space) (or "def" "class") space))))
     ;; Go to start of current block and check whether it's at top
     ;; level.  If it is, and not a block start, look forward for
     ;; definition statement.
@@ -677,16 +711,17 @@ 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)
-       (while (python-backslash-continuation-line-p)
-         (forward-line -1))
-      (python-beginning-of-string)
-      ;; Skip forward out of nested brackets.
-      (condition-case ()               ; beware invalid syntax
-         (progn (backward-up-list (syntax-ppss-depth (syntax-ppss))) t)
-       (error (end-of-line)))))
+  (catch 'foo
+    (while (python-continuation-line-p)
+      (beginning-of-line)
+      (if (python-backslash-continuation-line-p)
+         (while (python-backslash-continuation-line-p)
+           (forward-line -1))
+       (python-beginning-of-string)
+       ;; Skip forward out of nested brackets.
+       (condition-case ()              ; beware invalid syntax
+           (progn (backward-up-list (syntax-ppss-depth (syntax-ppss))) t)
+         (error (throw 'foo nil))))))
   (back-to-indentation))
 
 (defun python-end-of-statement ()
@@ -822,17 +857,19 @@ move and return nil.  Otherwise return t."
 \f
 ;;;; Imenu.
 
+(defvar python-recursing)
 (defun python-imenu-create-index ()
   "`imenu-create-index-function' for Python.
 
 Makes nested Imenu menus from nested `class' and `def' statements.
 The nested menus are headed by an item referencing the outer
 definition; it has a space prepended to the name so that it sorts
-first with `imenu--sort-by-name'."
-  (unless (boundp 'recursing)          ; dynamically bound below
+first with `imenu--sort-by-name' (though, unfortunately, sub-menus
+precede it)."
+  (unless (boundp 'python-recursing)           ; dynamically bound below
     (goto-char (point-min)))           ; normal call from Imenu
   (let (index-alist                    ; accumulated value to return
-       name is-class pos)
+       name)
     (while (re-search-forward
            (rx (and line-start (0+ space) ; leading space
                     (or (group "def") (group "class"))     ; type
@@ -845,7 +882,7 @@ first with `imenu--sort-by-name'."
              (setq name (concat "class " name)))
          (save-restriction
            (narrow-to-defun)
-           (let* ((recursing t)
+           (let* ((python-recursing t)
                   (sublist (python-imenu-create-index)))
              (if sublist
                  (progn (push (cons (concat " " name) pos) sublist)
@@ -913,13 +950,20 @@ See `python-check-command' for the default."
                                        (file-name-nondirectory name))))))))
   (setq python-saved-check-command command)
   (save-some-buffers (not compilation-ask-about-save) nil)
-  (compilation-start command))
+  (let ((compilation-error-regexp-alist
+        (cons '("(\\([^,]+\\), line \\([0-9]+\\))" 1 2)
+              compilation-error-regexp-alist)))
+    (compilation-start command)))
 \f
 ;;;; Inferior mode stuff (following cmuscheme).
 
+;; Fixme: Make sure we can work with IPython.
+
 (defcustom python-python-command "python"
   "*Shell command to run Python interpreter.
-Any arguments can't contain whitespace."
+Any arguments can't contain whitespace.
+Note that IPython may not work properly; it must at least be used with the
+`-cl' flag, i.e. use `ipython -cl'."
   :group 'python
   :type 'string)
 
@@ -936,40 +980,66 @@ Additional arguments are added when the command is used by `run-python'
 et al.")
 
 (defvar python-buffer nil
-  "*The current python process buffer.
-To run multiple Python processes, start the first with \\[run-python].
-It will be in a buffer named *Python*.  Rename that with
-\\[rename-buffer].  Now start a new process with \\[run-python].  It
-will be in a new buffer, named *Python*.  Switch between the different
-process buffers with \\[switch-to-buffer].
-
-Commands that send text from source buffers to Python processes have
-to choose a process to send to.  This is determined by global variable
-`python-buffer'.  Suppose you have three inferior Pythons running:
-    Buffer     Process
-    foo                python
-    bar                python<2>
-    *Python*    python<3>
-If you do a \\[python-send-region-and-go] command on some Python source
-code, what process does it go to?
-
-- In a process buffer (foo, bar, or *Python*), send it to that process.
-- In some other buffer (e.g. a source file), send it to the process
-  attached to `python-buffer'.
-Process selection is done by function `python-proc'.
-
-Whenever \\[run-python] starts a new process, it resets `python-buffer'
-to be the new process's buffer.  If you only run one process, this will
-do the right thing.  If you run multiple processes, you can change
-`python-buffer' to another process buffer with \\[set-variable].")
+  "The current python process buffer."
+  ;; Fixme: a single process is currently assumed, so that this doc
+  ;; is misleading.
+
+;;   "*The current python process buffer.
+;; To run multiple Python processes, start the first with \\[run-python].
+;; It will be in a buffer named *Python*.  Rename that with
+;; \\[rename-buffer].  Now start a new process with \\[run-python].  It
+;; will be in a new buffer, named *Python*.  Switch between the different
+;; process buffers with \\[switch-to-buffer].
+
+;; Commands that send text from source buffers to Python processes have
+;; to choose a process to send to.  This is determined by global variable
+;; `python-buffer'.  Suppose you have three inferior Pythons running:
+;;     Buffer  Process
+;;     foo             python
+;;     bar             python<2>
+;;     *Python*    python<3>
+;; If you do a \\[python-send-region-and-go] command on some Python source
+;; code, what process does it go to?
+
+;; - In a process buffer (foo, bar, or *Python*), send it to that process.
+;; - In some other buffer (e.g. a source file), send it to the process
+;;   attached to `python-buffer'.
+;; Process selection is done by function `python-proc'.
+
+;; Whenever \\[run-python] starts a new process, it resets `python-buffer'
+;; to be the new process's buffer.  If you only run one process, this will
+;; do the right thing.  If you run multiple processes, you can change
+;; `python-buffer' to another process buffer with \\[set-variable]."
+  )
 
 (defconst python-compilation-regexp-alist
+  ;; FIXME: maybe these should move to compilation-error-regexp-alist-alist.
   `((,(rx (and line-start (1+ (any " \t")) "File \""
               (group (1+ (not (any "\"<")))) ; avoid `<stdin>' &c
               "\", line " (group (1+ digit))))
-     1 python-compilation-line-number))
+     1 2)
+    (,(rx (and " in file " (group (1+ not-newline)) " on line "
+              (group (1+ digit))))
+     1 2))
   "`compilation-error-regexp-alist' for inferior Python.")
 
+(defvar inferior-python-mode-map
+  (let ((map (make-sparse-keymap)))
+    ;; This will inherit from comint-mode-map.
+    (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 iff we have a normal prompt,
+    ;; i.e. '>>> ' and not '... '.  See the comment before
+    ;; python-send-region.  Fixme: uncomment these if we address that.
+
+    ;; (define-key map [(meta ?\t)] 'python-complete-symbol)
+    ;; (define-key map "\C-c\C-f" 'python-describe-symbol)
+    map))
+
+;; 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?
 (define-derived-mode inferior-python-mode comint-mode "Inferior Python"
   "Major mode for interacting with an inferior Python process.
 A Python process can be started with \\[run-python].
@@ -990,14 +1060,13 @@ For running multiple processes in multiple buffers, see `python-buffer'.
   :group 'python
   (set-syntax-table python-mode-syntax-table)
   (setq mode-line-process '(":%s"))
-  ;; Fixme: Maybe install some python-mode bindings too.
-  (define-key inferior-python-mode-map "\C-c\C-l" 'python-load-file)
-  (define-key inferior-python-mode-map "\C-c\C-z" 'python-switch-to-python)
-  (add-hook 'comint-input-filter-functions 'python-input-filter nil t)
+  (set (make-local-variable 'comint-input-filter) 'python-input-filter)
   (add-hook 'comint-preoutput-filter-functions #'python-preoutput-filter
            nil t)
-  ;; Still required by `comint-redirect-send-command', for instance:
-  (set (make-local-variable 'comint-prompt-regexp) "^\\([>.]\\{3\\} \\)+")
+  ;; Still required by `comint-redirect-send-command', for instance
+  ;; (and we need to match things like `>>> ... >>> '):
+  (set (make-local-variable 'comint-prompt-regexp)
+       (rx (and line-start (1+ (and (repeat 3 (any ">.")) ?\ )))))
   (set (make-local-variable 'compilation-error-regexp-alist)
        python-compilation-regexp-alist)
   (compilation-shell-minor-mode 1))
@@ -1008,18 +1077,9 @@ Default ignores all inputs of 0, 1, or 2 non-blank characters."
   :type 'regexp
   :group 'python)
 
-(defvar python-orig-start-line nil
-  "Line number at start of region sent to inferior Python.")
-
-(defvar python-orig-file nil
-  "File name to associate with errors found in inferior Python.")
-
 (defun python-input-filter (str)
   "`comint-input-filter' function for inferior Python.
-Don't save anything for STR matching `inferior-python-filter-regexp'.
-Also resets variables for adjusting error messages."
-  (setq python-orig-file nil
-       python-orig-start-line 1)
+Don't save anything for STR matching `inferior-python-filter-regexp'."
   (not (string-match inferior-python-filter-regexp str)))
 
 ;; Fixme: Loses with quoted whitespace.
@@ -1032,20 +1092,8 @@ Also resets variables for adjusting error messages."
          (t (let ((pos (string-match "[^ \t]" string)))
               (if pos (python-args-to-list (substring string pos))))))))
 
-(defun python-compilation-line-number (file col)
-  "Return error descriptor of error found for FILE, column COL.
-Used as line-number hook function in `python-compilation-regexp-alist'."
-  (let ((line (save-excursion
-               (goto-char (match-beginning 2))
-               (read (current-buffer)))))
-    (list (point-marker) (if python-orig-file
-                            (list python-orig-file default-directory)
-                          file)
-         (+ line (1- python-orig-start-line))
-         nil)))
-
 (defvar python-preoutput-result nil
-  "Data from output line last `_emacs_out' line seen by the preoutput filter.")
+  "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'.")
@@ -1056,7 +1104,9 @@ Used as line-number hook function in `python-compilation-regexp-alist'."
 ;; `python-preoutput-continuation' if we get it.
 (defun python-preoutput-filter (s)
   "`comint-preoutput-filter-functions' function: ignore prompts not at bol."
-  (cond ((and (string-match "\\`[.>]\\{3\\} \\'" s)
+  (cond ((and (string-match (rx (and string-start (repeat 3 (any ".>"))
+                                    " " string-end))
+                           s)
              (/= (let ((inhibit-field-text-motion t))
                    (line-beginning-position))
                  (point)))
@@ -1077,10 +1127,10 @@ Used as line-number hook function in `python-compilation-regexp-alist'."
 CMD is the Python command to run.  NOSHOW non-nil means don't show the
 buffer automatically.
 If there is a process already running in `*Python*', switch to
-that buffer.  Interactively a prefix arg, allows you to edit the initial
-command line (default is the value of `python-command'); `-i' etc. args
-will be added to this as appropriate.  Runs the hooks
-`inferior-python-mode-hook' (after the `comint-mode-hook' is run).
+that buffer.  Interactively, a prefix arg allows you to edit the initial
+command line (default is `python-command'); `-i' etc.  args will be added
+to this as appropriate.  Runs the hook `inferior-python-mode-hook'
+\(after the `comint-mode-hook' is run).
 \(Type \\[describe-mode] in the process buffer for a list of commands.)"
   (interactive (list (if current-prefix-arg
                         (read-string "Run Python: " python-command)
@@ -1090,86 +1140,77 @@ will be added to this as appropriate.  Runs the hooks
   ;; 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.
-  (unless (comint-check-proc "*Python*")
-    (let ((cmdlist (append (python-args-to-list cmd) '("-i"))))
+  (unless (comint-check-proc python-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)))
+                 process-environment)))
       (set-buffer (apply 'make-comint "Python" (car cmdlist) nil
-                        (cdr cmdlist))))
+                        (cdr cmdlist)))
+      (setq python-buffer (buffer-name)))
     (inferior-python-mode)
     ;; Load function defintions we need.
     ;; Before the preoutput function was used, this was done via -c in
     ;; cmdlist, but that loses the banner and doesn't run the startup
-    ;; file.
-    (python-send-string "\
-def _emacs_execfile (file):  # execute file and remove it
-    from os import remove
-    try: execfile (file, globals (), globals ())
-    finally: remove (file)
-
-def _emacs_args (name):  # get arglist of name for eldoc &c
-    import inspect
-    parts = name.split ('.')
-    if len (parts) > 1:
-        try: exec 'import ' + parts[0]
-        except: return None
-    try: exec 'func='+name # lose if name is keyword or undefined
-    except: return None
-    if inspect.isbuiltin (func):
-        doc = func.__doc__
-        if doc.find (' ->') != -1:
-            print '_emacs_out', doc.split (' ->')[0]
-        elif doc.find ('\n') != -1:
-            print '_emacs_out', doc.split ('\n')[0]
-        return None
-    if inspect.ismethod (func): func = func.im_func
-    if not inspect.isfunction (func):
-        return None
-    (args, varargs, varkw, defaults) = inspect.getargspec (func)
-    print '_emacs_out', func.__name__+inspect.formatargspec (args, varargs, varkw, defaults)
-
-print '_emacs_ok'"))
-  (unless noshow (pop-to-buffer (setq python-buffer "*Python*"))))
+    ;; file.  The code might be inline here, but there's enough that it
+    ;; seems worth putting in a separate file, and it's probably cleaner
+    ;; to put it in a module.
+    (python-send-string "import emacs"))
+  (unless noshow (pop-to-buffer python-buffer)))
+
+;; 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 do 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-minor-mode'."
+  (goto-char (point-max))
+  (let ((end (marker-position (process-mark (python-proc)))))
+    (compilation-forget-errors)
+    (python-send-string command)
+    (set-marker compilation-parsing-end end)
+    (setq compilation-last-buffer (current-buffer))))
 
 (defun python-send-region (start end)
   "Send the region to the inferior Python process."
   ;; The region is evaluated from a temporary file.  This avoids
   ;; problems with blank lines, which have different semantics
   ;; interactively and in files.  It also saves the inferior process
-  ;; buffer filling up with interpreter prompts.  We need a function
-  ;; to remove the temporary file when it has been evaluated, which
-  ;; unfortunately means using a not-quite pristine interpreter
-  ;; initially.  Unfortunately we also get tracebacks which look like:
-  ;; 
-  ;; >>> Traceback (most recent call last):
-  ;;   File "<stdin>", line 1, in ?
-  ;;   File "<string>", line 4, in _emacs_execfile
-  ;;   File "/tmp/py7734RSB", line 11
+  ;; buffer filling up with interpreter prompts.  We need a Python
+  ;; function to remove the temporary file when it has been evaluated
+  ;; (though we could probably do it in Lisp with a Comint output
+  ;; filter).  This function also catches exceptions and truncates
+  ;; tracebacks not to mention the frame of the function itself.
   ;;
   ;; The compilation-minor-mode parsing takes care of relating the
-  ;; reference to the temporary file to the source.  Fixme:
-  ;; comint-filter the first two lines of the traceback?
+  ;; reference to the temporary file to the source.
+  ;;
+  ;; Fixme: Write a `coding' header to the temp file if the region is
+  ;; non-ASCII.
   (interactive "r")
   (let* ((f (make-temp-file "py"))
-        (command (format "_emacs_execfile(%S)" f))
-        (orig-file (buffer-file-name))
-        (orig-line (save-restriction
-                     (widen)
-                     (line-number-at-pos start))))
-    (if (save-excursion
-         (goto-char start)
-         (/= 0 (current-indentation))) ; need dummy block
-       (write-region "if True:\n" nil f nil 'nomsg))
+        (command (format "emacs.eexecfile(%S)" f))
+        (orig-start (copy-marker start)))
+    (when (save-excursion
+           (goto-char start)
+           (/= 0 (current-indentation))) ; need dummy block
+      (save-excursion
+       (goto-char orig-start)
+       ;; Wrong if we had indented code at buffer start.
+       (set-marker orig-start (line-beginning-position 0)))
+      (write-region "if True:\n" nil f nil 'nomsg))
     (write-region start end f t 'nomsg)
-    (when python-buffer
-      (with-current-buffer python-buffer
-       (let ((end (marker-position (process-mark (python-proc)))))
-         (set (make-local-variable 'python-orig-file) orig-file)
-         (set (make-local-variable 'python-orig-start-line) orig-line)
-         (set (make-local-variable 'compilation-error-list) nil)
-         (let ((comint-input-filter-functions
-                (delete 'python-input-filter comint-input-filter-functions)))
-           (python-send-string command))
-         (set-marker compilation-parsing-end end)
-         (setq compilation-last-buffer (current-buffer)))))))
+    (with-current-buffer (process-buffer (python-proc))        ;Runs python if needed.
+      (python-send-command command)
+      ;; Tell compile.el to redirect error locations in file `f' to
+      ;; positions past marker `orig-start'.  It has to be done *after*
+      ;; python-send-command's call to compilation-forget-errors.
+      (compilation-fake-loc orig-start f))))
 
 (defun python-send-string (string)
   "Evaluate STRING in inferior Python process."
@@ -1182,6 +1223,8 @@ print '_emacs_ok'"))
   (interactive)
   (python-send-region (point-min) (point-max)))
 
+;; Fixme: Try to define the function or class within the relevant
+;; module, not just at top level.
 (defun python-send-defun ()
   "Send the current defun (class or method) to the inferior Python process."
   (interactive)
@@ -1192,15 +1235,11 @@ print '_emacs_ok'"))
   "Switch to the Python process buffer.
 With prefix arg, position cursor at end of buffer."
   (interactive "P")
-  (if (get-buffer python-buffer)
-      (pop-to-buffer python-buffer)
-    (error "No current process buffer.  See variable `python-buffer'"))
+  (pop-to-buffer (process-buffer (python-proc))) ;Runs python if needed.
   (when eob-p
     (push-mark)
     (goto-char (point-max))))
 
-(add-to-list 'debug-ignored-errors "^No current process buffer.")
-
 (defun python-send-region-and-go (start end)
   "Send the region to the inferior Python process.
 Then switch to the process buffer."
@@ -1228,43 +1267,32 @@ function location information for debugging, and supports users of
 module-qualified names."
   (interactive (comint-get-source "Load Python file: " python-prev-dir/file
                                  python-source-modes
-                                 t)) ; because execfile needs exact name
-  (comint-check-source file-name) ; Check to see if buffer needs saved.
+                                 t))   ; because execfile needs exact name
+  (comint-check-source file-name)     ; Check to see if buffer needs saving.
   (setq python-prev-dir/file (cons (file-name-directory file-name)
                                   (file-name-nondirectory file-name)))
-  (when python-buffer
-    (with-current-buffer python-buffer
-      (let ((end (marker-position (process-mark (python-proc)))))
-       (set (make-local-variable 'compilation-error-list) nil)
-       ;; (set (make-local-variable 'compilation-old-error-list) nil)
-       (let ((comint-input-filter-functions
-              (delete 'python-input-filter comint-input-filter-functions)))
-         (python-send-string
-          (if (string-match "\\.py\\'" file-name)
-              ;; Fixme: make sure the directory is in the path list
-              (let ((module (file-name-sans-extension
-                             (file-name-nondirectory file-name))))
-                (set (make-local-variable 'python-orig-file) nil)
-                (set (make-local-variable 'python-orig-start-line) nil)
-                (format "\
-try:
-    if globals().has_key(%S): reload(%s)
-    else: import %s
-except: None
-" module module module))
-            (set (make-local-variable 'python-orig-file) file-name)
-            (set (make-local-variable 'python-orig-start-line) 1)
-            (format "execfile('%s')" file-name))))
-       (set-marker compilation-parsing-end end)
-       (setq compilation-last-buffer (current-buffer))))))
-
-;; Fixme: Should this start a process if there isn't one?  (Unlike cmuscheme.)
+  (with-current-buffer (process-buffer (python-proc)) ;Runs python if needed.
+    ;; Fixme: I'm not convinced by this logic from python-mode.el.
+    (python-send-command
+     (if (string-match "\\.py\\'" file-name)
+        (let ((module (file-name-sans-extension
+                       (file-name-nondirectory file-name))))
+          (format "emacs.eimport(%S,%S)"
+                  module (file-name-directory file-name)))
+       (format "execfile(%S)" file-name)))
+    (message "%s loaded" file-name)))
+
+;; Fixme: If we need to start the process, wait until we've got the OK
+;; from the startup.
 (defun python-proc ()
-  "Return the current Python process.  See variable `python-buffer'."
-  (let ((proc (get-buffer-process (if (eq major-mode 'inferior-python-mode)
-                                     (current-buffer)
-                                   python-buffer))))
-    (or proc (error "No current process.  See variable `python-buffer'"))))
+  "Return the current Python process.
+See variable `python-buffer'.  Starts a new process if necessary."
+  (or (if python-buffer
+         (get-buffer-process (if (eq major-mode 'inferior-python-mode)
+                                 (current-buffer)
+                               python-buffer)))
+      (progn (run-python nil t)
+            (python-proc))))
 \f
 ;;;; Context-sensitive help.
 
@@ -1276,33 +1304,47 @@ except: None
   "Syntax table giving `.' symbol syntax.
 Otherwise inherits from `python-mode-syntax-table'.")
 
+(defvar view-return-to-alist)
+(eval-when-compile (autoload 'help-buffer "help-fns"))
+
 ;; Fixme: Should this actually be used instead of info-look, i.e. be
-;; bound to C-h S?
+;; bound to C-h S?  Can we use other pydoc stuff before python 2.2?
 (defun python-describe-symbol (symbol)
-  "Get help on SYMBOL using `pydoc'.
-Interactively, prompt for symbol."
-  ;; Note that we do this in the inferior process, not a separate one to
+  "Get help on SYMBOL using `help'.
+Interactively, prompt for symbol.
+
+Symbol may be anything recognized by the interpreter's `help' command --
+e.g. `CALLS' -- not just variables in scope.
+This only works for Python version 2.2 or newer since earlier interpreters
+don't support `help'."
+  ;; Note that we do this in the inferior process, not a separate one, to
   ;; ensure the environment is appropriate.
   (interactive
    (let ((symbol (with-syntax-table python-dotty-syntax-table
                   (current-word)))
-        (enable-recursive-minibuffers t)
-        val)
-     (setq val (read-string (if symbol
-                               (format "Describe variable (default %s): "
-                                     symbol)
-                             "Describe symbol: ")
-                           nil nil symbol))
-     (list (or val symbol))))
+        (enable-recursive-minibuffers t))
+     (list (read-string (if symbol
+                           (format "Describe symbol (default %s): " symbol)
+                         "Describe symbol: ")
+                       nil nil symbol))))
   (if (equal symbol "") (error "No symbol"))
   (let* ((func `(lambda ()
-                 (comint-redirect-send-command (format "help(%S)\n" ,symbol)
+                 (comint-redirect-send-command (format "emacs.ehelp(%S)\n"
+                                                       ,symbol)
                                                "*Help*" nil))))
     ;; Ensure we have a suitable help buffer.
-    (let (temp-buffer-show-hook)       ; avoid xref stuff
-      (with-output-to-temp-buffer "*Help*"
+    ;; Fixme: Maybe process `Related help topics' a la help xrefs and
+    ;; allow C-c C-f in help buffer.
+    (let ((temp-buffer-show-hook       ; avoid xref stuff
+          (lambda ()
+            (toggle-read-only 1)
+            (setq view-return-to-alist
+                  (list (cons (selected-window) help-return-method))))))
+      (help-setup-xref (list 'python-describe-symbol symbol) (interactive-p))
+      (with-output-to-temp-buffer (help-buffer)
        (with-current-buffer standard-output
-         (set (make-local-variable 'comint-redirect-subvert-readonly) t))))
+         (set (make-local-variable 'comint-redirect-subvert-readonly) t)
+         (print-help-return-message))))
     (if (and python-buffer (get-buffer python-buffer))
        (with-current-buffer python-buffer
          (funcall func))
@@ -1311,6 +1353,15 @@ Interactively, prompt for symbol."
 
 (add-to-list 'debug-ignored-errors "^No symbol")
 
+(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 (or nil)."
+  (let ((proc (python-proc)))
+    (python-send-string string)
+    (setq python-preoutput-result nil)
+    (accept-process-output proc 5)
+    python-preoutput-result))
+
 ;; Fixme: try to make it work with point in the arglist.  Also, is
 ;; there anything reasonable we can do with random methods?
 ;; (Currently only works with functions.)
@@ -1319,14 +1370,9 @@ Interactively, prompt for symbol."
 Only works when point is in a function name, not its arglist, for instance.
 Assumes an inferior Python is running."
   (let ((symbol (with-syntax-table python-dotty-syntax-table
-                 (current-word)))
-       (proc (and python-buffer (python-proc))))
-    (when (and proc symbol)
-      (python-send-string
-       (format "_emacs_args(%S)" symbol))
-      (setq python-preoutput-result nil)
-      (accept-process-output proc 1)
-      python-preoutput-result)))
+                 (current-word))))
+    (when symbol
+      (python-send-receive (format "emacs.eargs(%S)" symbol)))))
 \f
 ;;;; Info-look functionality.
 
@@ -1338,12 +1384,15 @@ Used with `eval-after-load'."
                    (string-match "^Python \\([0-9]+\\.[0-9]+\\>\\)" s)
                    (match-string 1 s)))
         ;; Whether info files have a Python version suffix, e.g. in Debian.
-        (versioned 
+        (versioned
          (with-temp-buffer
-           (Info-mode)
+           (with-no-warnings (Info-mode))
            (condition-case ()
-               (Info-goto-node (format "(python%s-lib)Miscellaneous Index"
-                                       version))
+               ;; 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)
              (error nil)))))
     (info-lookup-maybe-add-help
      :mode 'python-mode
@@ -1409,7 +1458,7 @@ The criterion is either a match for `jython-mode' via
                (while (re-search-forward
                        (rx (and line-start (or "import" "from") (1+ space)
                                 (group (1+ (not (any " \t\n."))))))
-                       10000        ; Probably not worth customizing.
+                       (+ (point-min) 10000) ; Probably not worth customizing.
                        t)
                  (if (member (match-string 1) python-jython-packages)
                      (throw 'done t))))
@@ -1527,6 +1576,92 @@ Uses `python-beginning-of-block', `python-end-of-block'."
   (python-end-of-block)
   (exchange-point-and-mark))
 \f
+;;;; Completion.
+
+(defun python-symbol-completions (symbol)
+  "Return a list of completions of the string SYMBOL from Python process.
+The list is sorted."
+  (when symbol
+    (let ((completions
+          (condition-case ()
+              (car (read-from-string (python-send-receive
+                                      (format "emacs.complete(%S)" symbol))))
+            (error nil))))
+      (sort
+       ;; We can get duplicates from the above -- don't know why.
+       (delete-dups completions)
+       #'string<))))
+
+(defun python-partial-symbol ()
+  "Return the partial symbol before point (for completion)."
+  (let ((end (point))
+       (start (save-excursion
+                (and (re-search-backward
+                      (rx (and (or buffer-start (regexp "[^[:alnum:]._]"))
+                               (group (1+ (regexp "[[:alnum:]._]")))
+                               point))
+                      nil t)
+                     (match-beginning 1)))))
+    (if start (buffer-substring-no-properties start end))))
+
+;; Fixme: We should have an abstraction of this sort of thing in the
+;; core.
+(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 (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))
+                (message "Making completion list...%s" "done"))))))))
+
+(eval-when-compile (require 'hippie-exp))
+
+(defun python-try-complete (old)
+  "Completion function for Python for use with `hippie-expand'."
+  (when (eq major-mode '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
 ;;;; Modes.
 
 (defvar outline-heading-end-regexp)
@@ -1573,12 +1708,11 @@ lines count as headers.
                                   ))
   (set (make-local-variable 'parse-sexp-lookup-properties) t)
   (set (make-local-variable 'comment-start) "# ")
-  ;; Fixme: define a comment-indent-function?
+  (set (make-local-variable 'comment-indent-function) #'python-comment-indent)
   (set (make-local-variable 'indent-line-function) #'python-indent-line)
   (set (make-local-variable 'paragraph-start) "\\s-*$")
-  (set (make-local-variable 'fill-paragraph-function)
-       'python-fill-paragraph)
-  (set (make-local-variable 'require-final-newline) t)
+  (set (make-local-variable 'fill-paragraph-function) 'python-fill-paragraph)
+  (set (make-local-variable 'require-final-newline) mode-require-final-newline)
   (set (make-local-variable 'add-log-current-defun-function)
        #'python-current-defun)
   ;; Fixme: Generalize to do all blocks?
@@ -1595,6 +1729,9 @@ lines count as headers.
        #'python-eldoc-function)
   (add-hook 'eldoc-mode-hook
            '(lambda () (run-python 0 t)) nil t) ; need it running
+  (if (featurep 'hippie-exp)
+      (set (make-local-variable 'hippie-expand-try-functions-list)
+          (cons 'python-try-complete hippie-expand-try-functions-list)))
   (unless font-lock-mode (font-lock-mode 1))
   (when python-guess-indent (python-guess-indent))
   (set (make-local-variable 'python-command) python-python-command)