(Emacs python-mode.el) Patch 1.02.01 -> 1.03

Tim Peters (tim@ksr.com)
Sat, 22 Feb 92 00:04:43 EST

Almost all language modes set up bindings for these three keys:

ESC C-a move to start of current function
ESC C-e move to end of current function
ESC C-h wrap region around current function

The patch below (finally ...) does this for Python. Two odd wrinkles
unique to Python:

1) "Current function" is ambiguous because it might reasonably be taken
to mean "def" or "class". By default, these keys look at the current
def. Give them a prefix arg (e.g., C-u first), and they look at the
current class instead.

2) "Current def (or class)" is also ambiguous because def's and class's
can nest in arbitrary ways to arbitrary depths in Python. The
ambiguity is resolved by always looking at the smallest def (or
class) enclosing point (the first one "up the parse tree").

Future plans:

1) Just about out of both-possible-&-worth-more-than-they-cost ideas for
additional editing functions in Python mode. So speak up if there's
something you'd really like that isn't here yet.

2) Intend to take a crack at making the "execute Python code" commands
pleasant (or at least functional <grin>).

unbearably y'rs - tim

Tim Peters Kendall Square Research Corp
tim@ksr.com, ksr!tim@uunet.uu.net

Change log:

Thu Feb 20 22:55:39 1992 tim
version 1.03
finally added support for the conventional ESC C-{a,e,h}
new helper py-go-up-tree-to-keyword
new cmd beginning-of-python-def-or-class, bound to ESC C-a
mew cmd end-of-python-def-or-class, bound to ESC C-e
new cmd mark-python-def-or-class, bound to ESC C-h
added optional arg JUST-MOVE to py-mark-block to help out
updated docs

Patch:

*** python-mode.el Fri Feb 21 23:35:59 1992
--- ../python-mode.el Fri Feb 21 18:13:10 1992
***************
*** 1,4 ****
! ;;; Major mode for editing Python programs, version 1.02.01
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
--- 1,4 ----
! ;;; Major mode for editing Python programs, version 1.03
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
***************
*** 84,90 ****
(define-key py-mode-map "\C-c\C-u" 'py-goto-block-up)
(define-key py-mode-map "\C-c\C-b" 'py-mark-block)
(define-key py-mode-map "\C-c#" 'py-comment-region)
! (define-key py-mode-map "\C-c\C-hm" 'py-describe-mode))

(defvar py-mode-syntax-table nil "Python mode syntax table")
(if py-mode-syntax-table
--- 84,93 ----
(define-key py-mode-map "\C-c\C-u" 'py-goto-block-up)
(define-key py-mode-map "\C-c\C-b" 'py-mark-block)
(define-key py-mode-map "\C-c#" 'py-comment-region)
! (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 "\e\C-h" 'mark-python-def-or-class))

(defvar py-mode-syntax-table nil "Python mode syntax table")
(if py-mode-syntax-table
***************
*** 571,579 ****
(goto-char start)
(error "Enclosing block not found"))))

;;; Functions for marking regions

! (defun py-mark-block ()
"Mark following block of lines.
Easier to use than explain. It sets the region to an `interesting'
block of succeeding lines. If point is on a blank line, it goes down to
--- 574,635 ----
(goto-char start)
(error "Enclosing block not found"))))

+ (defun beginning-of-python-def-or-class (&optional class)
+ "Move point to start of def (or class, with prefix arg).
+
+ By default, looks for an appropriate `def'. If you supply a prefix arg,
+ looks for a `class' instead. The docs assume the `def' case; just
+ substitute `class' for `def' for the other case:
+
+ If point is on a blank or non-indenting comment line, moves back to
+ start of closest preceding code statement or indenting comment line.
+
+ If this is a `def' statement, leaves point at the start of it and
+ returns t.
+
+ Else searches for the smallest enclosing `def' block, leaves point at
+ the start of it and returns t (note that since class & def statements
+ can nest to arbitrary depths in Python, `smallest enclosing' doesn't
+ necessarily mean `closest preceding that's indented less'; this point is
+ subtle, and this remark is just to let you know that `smallest
+ enclosing' means what it says ...).
+
+ If no `def' statement can be found by those rules, leaves point at its
+ original location and signals an error.
+
+ If you just want to mark the def/class, see `\\[mark-python-def-or-class]'."
+ (interactive "P") ; raw prefix arg
+ (let ( (start (point))
+ (which (if class "class" "def")))
+ (if (py-go-up-tree-to-keyword which)
+ t
+ (goto-char start)
+ (error "Enclosing %s not found" which))))
+
+ (defun end-of-python-def-or-class (&optional class)
+ "Move point beyond end of def (or class, with prefix arg) body.
+ See `\\[beginning-of-python-def-or-class]' docs for how the def (or class) is found.
+
+ Once the beginning statement is found, this function leaves point
+ immediately after the end of the body of this def (or class). If it's a
+ one-liner (like `def onemore(n): return n+1'), point will move to the
+ start of the line immediately following the def or class statement.
+ Else point will move beyond the end of the body as defined in the docs
+ for `\\[py-mark-block]'.
+
+ If you just want to mark the def/class, see `\\[mark-python-def-or-class]'.
+
+ Returns the position of the start of the def or class."
+ (interactive "P") ; raw prefix arg
+ (beginning-of-python-def-or-class class)
+ (prog1 (point)
+ (if (looking-at py-colon-line-re)
+ (py-mark-block 'just-move)
+ (py-goto-beyond-final-line))))
+
;;; Functions for marking regions

! (defun py-mark-block (&optional just-move)
"Mark following block of lines.
Easier to use than explain. It sets the region to an `interesting'
block of succeeding lines. If point is on a blank line, it goes down to
***************
*** 597,603 ****
lines.

A msg identifying the location of the mark is displayed in the echo
! area; or do `\\[exchange-point-and-mark]' to flip down to the end."
(interactive)
(py-goto-initial-line)
;; skip over blank lines
--- 653,663 ----
lines.

A msg identifying the location of the mark is displayed in the echo
! area; or do `\\[exchange-point-and-mark]' to flip down to the end.
!
! If called from a program and optional argument JUST-MOVE is not nil,
! instead just moves to the end of the block, and does not set mark or
! display a msg."
(interactive)
(py-goto-initial-line)
;; skip over blank lines
***************
*** 638,651 ****
;; skip to end of last stmt
(goto-char last-pos)
(py-goto-beyond-final-line)
;; set mark & display
! (push-mark (point) 'no-msg)

! (forward-line -1)
! (message "Mark set after: %s" (py-suck-up-leading-text))

! (goto-char initial-pos)))

(defun py-comment-region (start end &optional uncomment-p)
"Comment out region of code; with prefix arg, uncomment region.
The lines from the line containing the start of the current region up
--- 698,736 ----
;; skip to end of last stmt
(goto-char last-pos)
(py-goto-beyond-final-line)
+
;; set mark & display
! (if just-move
! () ; just return
! (push-mark (point) 'no-msg)
! (forward-line -1)
! (message "Mark set after: %s" (py-suck-up-leading-text))
! (goto-char initial-pos))))

! (defun mark-python-def-or-class (&optional class)
! "Set region to body of def (or class, with prefix arg) enclosing point.
! Pushes the current mark, then point, on the mark ring (all language
! modes do this, but although it's handy it's never documented ...).

! See `\\[beginning-of-python-def-or-class]' docs for how the start of the def (or class, with
! prefix arg) is found. This command leaves point in the same place,
! except that if the preceding line is blank, point is instead left at its
! start (mostly for compatibility with other language modes; it's handy if
! you get into the habit of leaving an empty line before def and class
! stmts). The mark is set immediately after the end of the def (or class,
! with prefix arg), at the same place `\\[end-of-python-def-or-class]' leaves point."

+ (interactive "P") ; raw prefix arg
+ (push-mark (point))
+ (let ( (start (end-of-python-def-or-class class)) )
+ (push-mark (point))
+ (goto-char (1- start)) ; end of preceding line or bobp
+ (if (= (current-indentation) (current-column))
+ (beginning-of-line)
+ ;; note that (forward-char 1) wouldn't work if def was at
+ ;; start of restriction
+ (goto-char start))))
+
(defun py-comment-region (start end &optional uncomment-p)
"Comment out region of code; with prefix arg, uncomment region.
The lines from the line containing the start of the current region up
***************
*** 879,887 ****
--- 964,975 ----
@MARKING & MANIPULATING REGIONS OF CODE

\\[py-mark-block]\t mark block of lines
+ \\[mark-python-def-or-class]\t mark smallest enclosing def
+ \\[universal-argument] \\[mark-python-def-or-class]\t mark smallest enclosing class
\\[py-comment-region]\t comment out region of code
\\[universal-argument] \\[py-comment-region]\t uncomment region of code
%c:py-mark-block
+ %c:mark-python-def-or-class
%c:py-comment-region

@MOVING POINT
***************
*** 889,894 ****
--- 977,986 ----
\\[py-previous-statement]\t move to statement preceding point
\\[py-next-statement]\t move to statement following point
\\[py-goto-block-up]\t move up to start of current block
+ \\[beginning-of-python-def-or-class]\t move to start of def
+ \\[universal-argument] \\[beginning-of-python-def-or-class]\t move to start of class
+ \\[end-of-python-def-or-class]\t move to end of def
+ \\[universal-argument] \\[end-of-python-def-or-class]\t move to end of class

The first two move to one statement beyond the statement that contains
point. A numeric prefix argument tells them to move that many
***************
*** 901,906 ****
--- 993,1000 ----
%c:py-previous-statement
%c:py-next-statement
%c:py-goto-block-up
+ %c:beginning-of-python-def-or-class
+ %c:end-of-python-def-or-class

@LITTLE-KNOWN EMACS COMMANDS PARTICULARLY USEFUL IN PYTHON MODE

***************
*** 997,1002 ****
--- 1091,1122 ----
(if (eobp)
(progn (goto-char start) nil)
t)))
+
+ ;; go to start of statement, at or preceding point, starting with keyword
+ ;; KEY. Skips blank lines and non-indenting comments upward first. If
+ ;; that statement starts with KEY, done, else go back to first enclosing
+ ;; block starting with KEY.
+ ;; If successful, leaves point at the start of the KEY line & returns t.
+ ;; Else leaves point at an undefined place & returns nil.
+ (defun py-go-up-tree-to-keyword (key)
+ ;; skip blanks and non-indenting #
+ (py-goto-initial-line)
+ (while (and
+ (looking-at "[ \t]*\\($\\|#[^ \t\n]\\)")
+ (zerop (forward-line -1))) ; go back
+ nil)
+ (py-goto-initial-line)
+ (let* ( (re (concat "[ \t]*" key "\\b"))
+ (case-fold-search nil) ; let* so looking-at sees this
+ (found (looking-at re))
+ (dead nil))
+ (while (not (or found dead))
+ (condition-case nil ; in case no enclosing block
+ (py-goto-block-up 'no-mark)
+ (error (setq dead t)))
+ (or dead (setq found (looking-at re))))
+ (beginning-of-line)
+ found))

;; return string in buffer from start of indentation to end of line;
;; prefix "..." if leading whitespace was skipped

>>> END OF MSG