changeset 91964:70017176d475

Also require comint when loading. (python-mode-map): Bind python-pdbtrack-toggle-stack-tracking, Replace python-shell with run-python on menu bar, (python-shell-map): New map. (python-default-interpreter, python-python-command-args) (python-jython-command-args, python-pdbtrack-do-tracking-p): New options. (python-which-shell, python-which-args, python-which-bufname): New buffer local variables. (python-file-queue, python-pdbtrack-is-tracking-p): * progmodes/python.el (python-pdbtrack-stack-entry-regexp) (python-pdbtrack-input-prompt, python-pdbtrack-track-range): New constants. Pdbtrack features: (python-point, python-end-of-def-or-class) (python-beginning-of-def-or-class, python-goto-initial-line) (python-comint-output-filter-function) (python-pdbtrack-overlay-arrow) (python-pdbtrack-track-stack-file, python-toggle-shells) (python-shell, python-pdbtrack-toggle-stack-tracking) (turn-on-pdbtrack, turn-off-pdbtrack, python-sentinel): New functions.
author Nick Roberts <nickrob@snap.net.nz>
date Wed, 20 Feb 2008 00:18:23 +0000
parents 5b14940a3645
children d0edd28f5f95
files lisp/progmodes/python.el
diffstat 1 files changed, 445 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/progmodes/python.el	Wed Feb 20 00:13:09 2008 +0000
+++ b/lisp/progmodes/python.el	Wed Feb 20 00:18:23 2008 +0000
@@ -64,9 +64,10 @@
 
 ;;; Code:
 
+(require 'comint)
+
 (eval-when-compile
   (require 'compile)
-  (require 'comint)
   (require 'hippie-exp))
 
 (autoload 'comint-mode "comint")
@@ -201,6 +202,7 @@
     (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)
@@ -248,7 +250,7 @@
 			     (vector (car elt) (cdr elt) t))
 			   python-skeletons))) ; defined later
 	"-"
-	["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"]
@@ -278,6 +280,14 @@
 ;; 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
@@ -442,6 +452,73 @@
   :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 buffers and the *Python* buffer.  When using pdb to debug a
+Python program, pdbtrack notices the pdb prompt and displays the
+source file and line that the program is stopped at, much the same way
+as gud-mode does for debugging C programs with gdb."
+  :type 'boolean
+  :group 'python)
+(make-variable-buffer-local 'python-pdbtrack-do-tracking-p)
+
+;; 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[(<]?pdb[>)]? "
+  "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."
@@ -2331,6 +2408,372 @@
   :group 'python
   (set (make-local-variable 'python-command) python-jython-command))
 
+
+
+;; pdbtrack features
+
+(defsubst python-point (position)
+  "Returns the value of point at certain commonly referenced POSITIONs.
+POSITION can be one of the following symbols:
+
+  bol  -- beginning of line
+  eol  -- end of line
+  bod  -- beginning of def or class
+  eod  -- end of def or class
+  bob  -- beginning of buffer
+  eob  -- end of buffer
+  boi  -- back to indentation
+  bos  -- beginning of statement
+
+This function does not modify point or mark."
+  (let ((here (point)))
+    (cond
+     ((eq position 'bol) (beginning-of-line))
+     ((eq position 'eol) (end-of-line))
+     ((eq position 'bod) (python-beginning-of-def-or-class))
+     ((eq position 'eod) (python-end-of-def-or-class))
+     ;; Kind of funny, I know, but useful for python-up-exception.
+     ((eq position 'bob) (goto-char (point-min)))
+     ((eq position 'eob) (goto-char (point-max)))
+     ((eq position 'boi) (back-to-indentation))
+     ((eq position 'bos) (python-goto-initial-line))
+     (t (error "Unknown buffer position requested: %s" position)))
+    (prog1
+	(point)
+      (goto-char here))))
+
+(defun python-end-of-def-or-class (&optional class count)
+  "Move point beyond end of `def' or `class' body.
+
+By default, looks for an appropriate `def'.  If you supply a prefix
+arg, looks for a `class' instead.  The docs below assume the `def'
+case; just substitute `class' for `def' for the other case.
+Programmatically, if CLASS is `either', then moves to either `class'
+or `def'.
+
+When second optional argument is given programmatically, move to the
+COUNTth end of `def'.
+
+If point is in a `def' statement already, this is the `def' we use.
+
+Else, if the `def' found by `\\[python-beginning-of-def-or-class]'
+contains the statement you started on, that's the `def' we use.
+
+Otherwise, we search forward for the closest following `def', and use that.
+
+If a `def' can be found by these rules, point is moved to the start of
+the line immediately following the `def' block, and the position of the
+start of the `def' is returned.
+
+Else point is moved to the end of the buffer, and nil is returned.
+
+Note that doing this command repeatedly will take you closer to the
+end of the buffer each time.
+
+To mark the current `def', see `\\[python-mark-def-or-class]'."
+  (interactive "P")			; raw prefix arg
+  (if (and count (/= count 1))
+      (python-beginning-of-def-or-class (- 1 count)))
+  (let ((start (progn (python-goto-initial-line) (point)))
+	(which (cond ((eq class 'either) "\\(class\\|def\\)")
+		     (class "class")
+		     (t "def")))
+	(state 'not-found))
+    ;; move point to start of appropriate def/class
+    (if (looking-at (concat "[ \t]*" which "\\>")) ; already on one
+	(setq state 'at-beginning)
+      ;; else see if python-beginning-of-def-or-class hits container
+      (if (and (python-beginning-of-def-or-class class)
+	       (progn (python-goto-beyond-block)
+		      (> (point) start)))
+	  (setq state 'at-end)
+	;; else search forward
+	(goto-char start)
+	(if (re-search-forward (concat "^[ \t]*" which "\\>") nil 'move)
+	    (progn (setq state 'at-beginning)
+		   (beginning-of-line)))))
+    (cond
+     ((eq state 'at-beginning) (python-goto-beyond-block) t)
+     ((eq state 'at-end) t)
+     ((eq state 'not-found) nil)
+     (t (error "Internal error in `python-end-of-def-or-class'")))))
+
+(defun python-beginning-of-def-or-class (&optional class count)
+  "Move point to start of `def' or `class'.
+
+Searches back for the closest preceding `def'.  If you supply a prefix
+arg, looks for a `class' instead.  The docs below assume the `def'
+case; just substitute `class' for `def' for the other case.
+Programmatically, if CLASS is `either', then moves to either `class'
+or `def'.
+
+When second optional argument is given programmatically, move to the
+COUNTth start of `def'.
+
+If point is in a `def' statement already, and after the `d', simply
+moves point to the start of the statement.
+
+Otherwise (i.e. when point is not in a `def' statement, or at or
+before the `d' of a `def' statement), searches for the closest
+preceding `def' statement, and leaves point at its start.  If no such
+statement can be found, leaves point at the start of the buffer.
+
+Returns t iff a `def' statement is found by these rules.
+
+Note that doing this command repeatedly will take you closer to the
+start of the buffer each time.
+
+To mark the current `def', see `\\[python-mark-def-or-class]'."
+  (interactive "P")			; raw prefix arg
+  (setq count (or count 1))
+  (let ((at-or-before-p (<= (current-column) (current-indentation)))
+	(start-of-line (goto-char (python-point 'bol)))
+	(start-of-stmt (goto-char (python-point 'bos)))
+	(start-re (cond ((eq class 'either) "^[ \t]*\\(class\\|def\\)\\>")
+			(class "^[ \t]*class\\>")
+			(t "^[ \t]*def\\>"))))
+    ;; searching backward
+    (if (and (< 0 count)
+	     (or (/= start-of-stmt start-of-line)
+		 (not at-or-before-p)))
+	(end-of-line))
+    ;; search forward
+    (if (and (> 0 count)
+	     (zerop (current-column))
+	     (looking-at start-re))
+	(end-of-line))
+    (if (re-search-backward start-re nil 'move count)
+	(goto-char (match-beginning 0)))))
+
+(defun python-goto-initial-line ()
+  "Go to the initial line of the current statement.
+Usually this is the line we're on, but if we're on the 2nd or
+following lines of a continuation block, we need to go up to the first
+line of the block."
+  ;; Tricky: We want to avoid quadratic-time behavior for long
+  ;; continued blocks, whether of the backslash or open-bracket
+  ;; varieties, or a mix of the two.  The following manages to do that
+  ;; in the usual cases.
+  ;;
+  ;; Also, if we're sitting inside a triple quoted string, this will
+  ;; drop us at the line that begins the string.
+  (let (open-bracket-pos)
+    (while (python-continuation-line-p)
+      (beginning-of-line)
+      (if (python-backslash-continuation-line-p)
+	  (while (python-backslash-continuation-line-p)
+	    (forward-line -1))
+	;; else zip out of nested brackets/braces/parens
+	(while (setq open-bracket-pos (python-nesting-level))
+	  (goto-char open-bracket-pos)))))
+  (beginning-of-line))
+
+(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)
+    (python-safe (delete-file (car python-file-queue)))
+    (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 de arrow at beginning-of-line in current buffer."
+  ;; This was derived/simplified from edebug-overlay-arrow
+  (cond (activation
+	 (setq overlay-arrow-position (make-marker))
+	 (setq overlay-arrow-string "=>")
+	 (set-marker overlay-arrow-position
+		     (python-point 'bol) (current-buffer))
+	 (setq python-pdbtrack-is-tracking-p t))
+	(python-pdbtrack-is-tracking-p
+	 (setq overlay-arrow-position nil)
+	 (setq 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 matching `python-pdbtrack-input-prompt'
+at the beginning of the line."
+  ;; 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.
+  ;;
+  ;; KLM: It might be nice to provide an optional override, so this
+  ;; routine could be fed debugger output strings as the text
+  ;; argument, for deliberate application elsewhere.
+  ;;
+  ;; KLM: We're very conservative about clearing the overlay arrow, to
+  ;; minimize residue.  This means, for instance, that executing other
+  ;; pdb commands wipes out the highlight.
+  (let* ((origbuf (current-buffer))
+	 (currproc (get-buffer-process origbuf)))
+    (if (not (and currproc python-pdbtrack-do-tracking-p))
+        (python-pdbtrack-overlay-arrow nil)
+      (let* (;(origdir default-directory)
+             (procmark (process-mark currproc))
+             (block (buffer-substring (max comint-last-input-end
+                                           (- procmark
+                                              python-pdbtrack-track-range))
+                                      procmark))
+             fname lineno)
+        (if (not (string-match (concat python-pdbtrack-input-prompt "$") block))
+            (python-pdbtrack-overlay-arrow nil)
+          (if (not (string-match
+                    (concat ".*" python-pdbtrack-stack-entry-regexp ".*")
+                    block))
+              (python-pdbtrack-overlay-arrow nil)
+            (setq fname (match-string 1 block)
+                  lineno (match-string 2 block))
+            (if (file-exists-p fname)
+                (progn
+                  (find-file-other-window fname)
+                  (goto-line (string-to-number lineno))
+                  (message "pdbtrack: line %s, file %s" lineno fname)
+                  (python-pdbtrack-overlay-arrow t)
+                  (pop-to-buffer origbuf t) )
+              (if (= (elt fname 0) ?\<)
+                  (message "pdbtrack: (Non-file source: '%s')" fname)
+                (message "pdbtrack: File not found: %s" fname)))))))))
+
+(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)))
+
+;;;###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
+    (add-hook 'comint-output-filter-functions
+	      'python-pdbtrack-track-stack-file nil t)
+    (setq python-pdbtrack-do-tracking-p t)
+    (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