diff options
Diffstat (limited to 'Misc/python-mode.el')
-rw-r--r-- | Misc/python-mode.el | 206 |
1 files changed, 183 insertions, 23 deletions
diff --git a/Misc/python-mode.el b/Misc/python-mode.el index 20fc4cf..e185bca 100644 --- a/Misc/python-mode.el +++ b/Misc/python-mode.el @@ -27,7 +27,8 @@ ;; Note: this version of python-mode.el is no longer compatible with ;; Emacs 18. For a gabazillion reasons, I highly recommend upgrading ;; to X/Emacs 19 or X/Emacs 20. For older versions of the 19 series, -;; you may need to acquire the Custom library. +;; you may need to acquire the Custom library. Please see +;; <http://www.python.org/ftp/emacs/> for details. ;; python-mode.el is currently distributed with XEmacs 19 and XEmacs ;; 20. Since this file is not GPL'd it is not distributed with Emacs, @@ -344,13 +345,17 @@ Currently-active file is at the head of the list.") ;; change this, you probably have to change `py-current-defun' as ;; well. This is only used by `py-current-defun' to find the name for ;; add-log.el. -(defvar py-defun-start-re +(defconst py-defun-start-re "^\\([ \t]*\\)def[ \t]+\\([a-zA-Z_0-9]+\\)\\|\\(^[a-zA-Z_0-9]+\\)[ \t]*=") ;; Regexp for finding a class name. If you change this, you probably ;; have to change `py-current-defun' as well. This is only used by ;; `py-current-defun' to find the name for add-log.el. -(defvar py-class-start-re "^class[ \t]*\\([a-zA-Z_0-9]+\\)") +(defconst py-class-start-re "^class[ \t]*\\([a-zA-Z_0-9]+\\)") + +;; Regexp that describes tracebacks +(defconst py-traceback-line-re + "[ \t]+File \"\\([^\"]+\\)\", line \\([0-9]+\\), in ") @@ -370,6 +375,43 @@ Currently-active file is at the head of the list.") (and (boundp 'zmacs-region-stays) (setq zmacs-region-stays t))) +(defsubst py-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 defun + ;; boi -- back to indentation + ;; + ;; 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) (beginning-of-python-def-or-class)) + ((eq position 'bob) (beginning-of-buffer)) + ((eq position 'eob) (end-of-buffer)) + ((eq position 'boi) (back-to-indentation)) + (t (error "unknown buffer position requested: %s" position)) + ) + (prog1 + (point) + (goto-char here)))) + +(defsubst py-highlight-line (from to file line) + (cond + ((fboundp 'make-extent) + ;; XEmacs + (let ((e (make-extent from to))) + (set-extent-property e 'mouse-face 'highlight) + (set-extent-property e 'py-exc-info (cons file line)) + (set-extent-property e 'keymap py-mode-output-map))) + (t + ;; Emacs -- Please port this! + ) + )) + ;; Major mode boilerplate @@ -433,6 +475,8 @@ Currently-active file is at the head of the list.") (define-key py-mode-map "\C-c\C-hm" 'py-describe-mode) (define-key py-mode-map "\e\C-a" 'beginning-of-python-def-or-class) (define-key py-mode-map "\e\C-e" 'end-of-python-def-or-class) + (define-key py-mode-map "\C-c-" 'py-up-exception) + (define-key py-mode-map "\C-c=" 'py-down-exception) ;; information (define-key py-mode-map "\C-c\C-b" 'py-submit-bug-report) (define-key py-mode-map "\C-c\C-v" 'py-version) @@ -441,10 +485,24 @@ Currently-active file is at the head of the list.") ;; shadow global bindings for newline-and-indent w/ the py- version. ;; BAW - this is extremely bad form, but I'm not going to change it ;; for now. - (mapcar (function (lambda (key) - (define-key - py-mode-map key 'py-newline-and-indent))) - (where-is-internal 'newline-and-indent)) + (mapcar #'(lambda (key) + (define-key py-mode-map key 'py-newline-and-indent)) + (where-is-internal 'newline-and-indent)) + ) + +(defvar py-mode-output-map nil + "Keymap used in *Python Output* buffers*") +(if py-mode-output-map + nil + (setq py-mode-output-map (make-sparse-keymap)) + (define-key py-mode-output-map [button2] 'py-mouseto-exception) + (define-key py-mode-output-map "\C-c\C-c" 'py-goto-exception) + ;; TBD: Disable all self-inserting keys. This is bogus, we should + ;; really implement this as *Python Output* buffer being read-only + (mapcar #' (lambda (key) + (define-key py-mode-output-map key + #'(lambda () (interactive) (beep)))) + (where-is-internal 'self-insert-command)) ) (defvar py-mode-syntax-table nil @@ -964,9 +1022,24 @@ Electric behavior is inhibited inside a string or comment." )) (set-buffer curbuf)))) +(defun py-postprocess-output-buffer (buf) + (save-excursion + (set-buffer buf) + (beginning-of-buffer) + (while (re-search-forward py-traceback-line-re nil t) + (let ((file (match-string 1)) + (line (string-to-int (match-string 2)))) + (py-highlight-line (py-point 'bol) (py-point 'eol) file line)) + ))) + ;;; Subprocess commands +;; only used when (memq 'broken-temp-names py-emacs-features) +(defvar py-serial-number 0) +(defvar py-exception-buffer nil) +(defconst py-output-buffer "*Python Output*") + ;;;###autoload (defun py-shell () "Start an interactive Python interpreter in another window. @@ -1004,9 +1077,12 @@ filter." (setq comint-prompt-regexp "^>>> \\|^[.][.][.] ") (set-process-filter (get-buffer-process (current-buffer)) 'py-process-filter) (set-syntax-table py-mode-syntax-table) - (local-set-key [tab] 'self-insert-command)) + ;; set up keybindings for this subshell + (local-set-key [tab] 'self-insert-command) + (local-set-key "\C-c-" 'py-up-exception) + (local-set-key "\C-c=" 'py-down-exception) + ) - (defun py-clear-queue () "Clear the queue of temporary files waiting to execute." (interactive) @@ -1015,9 +1091,6 @@ filter." (setq py-file-queue nil) (message "%d pending files de-queued." n))) -;; only used when (memq 'broken-temp-names py-emacs-features) -(defvar py-serial-number 0) - (defun py-execute-region (start end &optional async) "Execute the the region in a Python interpreter. The region is first copied into a temporary file (in the directory @@ -1046,15 +1119,15 @@ is inserted at the end. See also the command `py-clear-queue'." (format "python-%d" py-serial-number) (setq py-serial-number (1+ py-serial-number))) (make-temp-name "python"))) - (file (concat (file-name-as-directory py-temp-directory) temp)) - (outbuf "*Python Output*")) + (file (concat (file-name-as-directory py-temp-directory) temp))) (write-region start end file nil 'nomsg) (cond ;; always run the code in it's own asynchronous subprocess (async - (let* ((buf (generate-new-buffer-name "*Python Output*"))) + (let* ((buf (generate-new-buffer-name py-output-buffer))) (start-process "Python" buf py-python-command "-u" file) (pop-to-buffer buf) + (py-postprocess-output-buffer buf) )) ;; if the Python interpreter shell is running, queue it up for ;; execution there. @@ -1063,10 +1136,13 @@ is inserted at the end. See also the command `py-clear-queue'." (if (not py-file-queue) (py-execute-file proc file) (message "File %s queued for execution" file)) - (push file py-file-queue)) + (push file py-file-queue) + (setq py-exception-buffer (cons file (current-buffer)))) (t ;; otherwise either run it synchronously in a subprocess - (shell-command-on-region start end py-python-command outbuf) + (shell-command-on-region start end py-python-command py-output-buffer) + (setq py-exception-buffer (current-buffer)) + (py-postprocess-output-buffer py-output-buffer) )))) ;; Code execution command @@ -1080,6 +1156,90 @@ See the `\\[py-execute-region]' docs for an account of some subtleties." (interactive "P") (py-execute-region (point-min) (point-max) async)) + + +(defun py-jump-to-exception (file line) + (let ((buffer (cond ((string-equal file "<stdin>") + py-exception-buffer) + ((and (consp py-exception-buffer) + (string-equal file (car py-exception-buffer))) + (cdr py-exception-buffer)) + ((py-safe (find-file-noselect file))) + ;; could not figure out what file the exception + ;; is pointing to, so prompt for it + (t (find-file (read-file-name "Exception file: " + nil + file t)))))) + (pop-to-buffer buffer) + (goto-line line) + (message "Jumping to exception in file %s on line %d" file line))) + +(defun py-mouseto-exception (event) + (interactive "e") + (cond + ((fboundp 'event-point) + ;; XEmacs + (let* ((point (event-point event)) + (buffer (event-buffer event)) + (e (and point buffer (extent-at point buffer 'py-exc-info))) + (info (and e (extent-property e 'py-exc-info)))) + (message "Event point: %d, info: %s" point info) + (and info + (py-jump-to-exception (car info) (cdr info))) + )) + ;; Emacs -- Please port this! + )) + +(defun py-goto-exception () + "Go to the line indicated by the traceback." + (interactive) + (let (file line) + (save-excursion + (beginning-of-line) + (if (looking-at py-traceback-line-re) + (setq file (match-string 1) + line (string-to-int (match-string 2))))) + (if (not file) + (error "Not on a traceback line.")) + (py-jump-to-exception file line))) + +(defun py-find-next-exception (start buffer searchdir errwhere) + ;; Go to start position in buffer, search in the specified + ;; direction, and jump to the exception found. If at the end of the + ;; exception, print error message + (let (file line) + (save-excursion + (set-buffer buffer) + (goto-char (py-point start)) + (if (funcall searchdir py-traceback-line-re nil t) + (setq file (match-string 1) + line (string-to-int (match-string 2))))) + (if (and file line) + (py-jump-to-exception file line) + (error "%s of traceback" errwhere)))) + +(defun py-down-exception (&optional bottom) + "Go to the next line down in the traceback. +With optional \\[universal-argument], jump to the bottom (innermost) +exception in the exception stack." + (interactive "P") + (let* ((proc (get-process "Python")) + (buffer (if proc "*Python*" py-output-buffer))) + (if bottom + (py-find-next-exception 'eob buffer 're-search-backward "Bottom") + (py-find-next-exception 'eol buffer 're-search-forward "Bottom")))) + +(defun py-up-exception (&optional top) + "Go to the previous line up in the traceback. +With optional \\[universal-argument], jump to the top (outermost) +exception in the exception stack." + (interactive "P") + (let* ((proc (get-process "Python")) + (buffer (if proc "*Python*" py-output-buffer))) + (if top + (py-find-next-exception 'bob buffer 're-search-forward "Top") + (py-find-next-exception 'boi buffer 're-search-backward "Top")))) + ;; Electric deletion (defun py-electric-backspace (arg) @@ -1198,14 +1358,14 @@ the new line indented." ;; honor-block-close-p is non-nil, statements such as return, raise, ;; break, continue, and pass force one level of outdenting. (save-excursion - (let ((pps (parse-partial-sexp (save-excursion - (beginning-of-python-def-or-class) - (point)) - (point)))) + (let* ((bod (py-point 'bod)) + (pps (parse-partial-sexp bod (point)))) (beginning-of-line) (cond - ;; are we inside a string or comment? - ((or (nth 3 pps) (nth 4 pps)) + ;; are we inside a multi-line string or comment? + ((or (and (nth 3 pps) + (nth 3 (parse-partial-sexp bod (py-point 'boi)))) + (nth 4 pps)) (save-excursion (if (not py-align-multiline-strings-p) 0 ;; skip back over blank & non-indenting comment lines |