(Emacs) Python mode, version 1.01

Tim Peters (tim@ksr.com)
Tue, 18 Feb 92 23:25:41 EST

Sorry for posting this again so soon, but Guido pushed me into doing
some (what I think is) very valuable stuff, and you'll like it <grin>.

Question: If anyone who is interested in python-mode.el does *not* have,
or know how to use, the 'patch' program, please let me know. Context
diffs are generally much smaller than reposting the whole thing, and I'll
do that in the future unless somebody can't handle it. In this
particular case the diff would be larger than the file, though ...

New stuff:

- DEL is much smarter. When you use it to reduce indentation, it now
tells you which code block you're closing, and reduces the
indentation to match that of the opening line of the block (so a
non-uniform pattern of indentation increments won't confuse it).
And if you use it to reduce indentation on a continuation or non-
indenting comment line, it will delete just one column at a time (so
you can use DEL and SPACE to adjust the indentation to exactly what
you want in a straightforward way).

- The mode blurb (C-h m) comes up much (about 20 times) faster now.
Alas, this was bought at the cost of taking almost everything out
of it <grin/sigh>.

- A new&improved (but slow) "long" mode blurb can be gotten via the
new `C-c C-h m'. This is even longer than before, but is (I think)
improved in several ways. E.g., the current values of the mode
variables are shown, you can search for the character '@' to move
among major sections, and the guts of the "leaf" sections exactly
match what you'd get if you did `C-h v' and `C-h f (or k)' one name
at a time (before, in a futile effort to save space & time, `C-h m'
often failed to tell the whole story).

Note that this mode is getting fancy enough that byte-compile'ing it has
a real speed payoff. You do that by going into the directory containing
the .el file and entering

emacs -batch -f batch-byte-compile python-mode.el

That will create python-mode.elc, and Emacs will load the .elc version
first when one exists (i.e., the line above is all you have to do; the
rest is magic).

darned-glad-python-creates-.pyc's-all-by-itself-ly y'rs - tim

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

Change log:

Tue Feb 18 22:35:04 1992 tim
version 1.01
simplified py-goto-statement-below by calling py-goto-beyond-final-line
changed py-compute-indentation to treat indenting comment lines
exactly like code lines
forced early exit in py-goto-block-up if starting indent is 0
rewrote py-delete-char to be a lot smarter
caters to non-std indentation (takes from first line in block)
displays the 1st line of the block being closed
updated the docs accordingly
rewrote the `C-h m' support completely:
`C-h m' made very bare but very fast
new func py-describe-mode does the slow wordy stuff,
bound to C-c C-h m
new helper py-dump-help-string
changed so vrbl & cmd docs are ripped directly out of the
vrbls & cmds in question (so no more will C-h v or C-h f
say one thing but C-h m say another)
changed so all current key bindings are shown for cmds and so
current values are shown for vrbls
stuck '@' in front of major sections to aid in skipping around
the help file
rearranged several parts the way Guido did in his copy

;;; Major mode for editing Python programs, version 1.01
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
;;
;; Copyright (c) 1992 Tim Peters
;;
;; This software is provided as-is, without express or implied warranty.
;; Permission to use, copy, modify, distribute or sell this software,
;; without fee, for any purpose and by any individual or organization, is
;; hereby granted, provided that the above copyright notice and this
;; paragraph appear in all copies.
;;
;;
;; The following statements, placed in your .emacs file or site-init.el,
;; will cause this file to be autoloaded, and python-mode invoked, when
;; visiting .py files (assuming the file is in your load-path):
;;
;; (autoload 'python-mode "python-mode" "" t)
;; (setq auto-mode-alist
;; (cons '("\\.py$" . python-mode) auto-mode-alist))

(provide 'python-mode)

;;; Constants and variables

(defvar py-python-command "python"
"*Shell command used to start Python interpreter.")

(defvar py-indent-offset 8 ; argue with Guido <grin>
"*Indentation increment.")

(defvar py-continuation-offset 2
"*Indentation (in addition to py-indent-offset) for continued lines.
The additional indentation given to the first continuation line in a
multi-line statement. Each subsequent continuation line in the
statement inherits its indentation from the line that precedes it, so if
you don't like the default indentation given to the first continuation
line, change it to something you do like and Python-mode will
automatically use that for the remaining continuation lines (or, until
you change the indentation again).")

(defvar py-block-comment-prefix "##"
"*String used by py-comment-region to comment out a block of code.
This should follow the convention for non-indenting comment lines so
that the indentation commands won't get confused (i.e., the string
should be of the form `#x...' where `x' is not a blank or a tab, and
`...' is arbitrary).")

(defvar py-beep-if-tab-change t
"*Ring the bell if tab-width is changed.
If a comment of the form
\t# vi:set tabsize=<number>:
is found before the first code line when the file is entered, and
the current value of (the general Emacs variable) tab-width does not
equal <number>, tab-width is set to <number>, a message saying so is
displayed in the echo area, and if py-beep-if-tab-change is non-nil the
Emacs bell is also rung as a warning.")

(defvar py-mode-map nil "Keymap used in Python mode buffers.")
(if py-mode-map
()
(setq py-mode-map (make-sparse-keymap))

;; shadow global bindings for newline-and-indent w/ the py- version
(mapcar (function (lambda (key)
(define-key
py-mode-map key 'py-newline-and-indent)))
(where-is-internal 'newline-and-indent))

(define-key py-mode-map "\C-c\C-c" 'py-execute-buffer)
(define-key py-mode-map "\C-c|" 'py-execute-region)
(define-key py-mode-map "\C-c!" 'py-shell)
(define-key py-mode-map "\177" 'py-delete-char)
(define-key py-mode-map "\n" 'py-newline-and-indent)
(define-key py-mode-map "\C-c\t" 'py-indent-region)
(define-key py-mode-map "\C-c<" 'py-shift-region-left)
(define-key py-mode-map "\C-c>" 'py-shift-region-right)
(define-key py-mode-map "\C-c\C-n" 'py-next-statement)
(define-key py-mode-map "\C-c\C-p" 'py-previous-statement)
(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
()
(setq py-mode-syntax-table (make-syntax-table))
(set-syntax-table py-mode-syntax-table)
(modify-syntax-entry ?\( "()")
(modify-syntax-entry ?\) ")(")
(modify-syntax-entry ?\[ "(]")
(modify-syntax-entry ?\] ")[")
(modify-syntax-entry ?\{ "(}")
(modify-syntax-entry ?\} "){")
(modify-syntax-entry ?\_ "w")
(modify-syntax-entry ?\' "\"") ; single quote is string quote
(modify-syntax-entry ?\` "$") ; backquote is open and close paren
(modify-syntax-entry ?\# "<") ; hash starts comment
(modify-syntax-entry ?\n ">")) ; newline ends comment

;; a statement in Python opens a new block iff it ends with a colon;
;; while conceptually trivial, quoted strings, continuation lines, and
;; comments make this hard. E.g., consider the statement
;; if \
;; 1 \
;; :\
;; \
;; \
;; # comment
;; here we define some regexps to help

(defconst py-stringlit-re "'\\([^'\n\\]\\|\\\\.\\)*'"
"regexp matching a Python string literal")

;; warning!: when [^#'\n\\] was written as [^#'\n\\]+ (i.e., with a
;; '+' suffix), this appeared to run 100x slower in some bad cases.
(defconst py-colon-line-re
(concat
"\\(" "[^#'\n\\]" "\\|" py-stringlit-re "\\|" "\\\\\n" "\\)*"
":"
"\\(" "[ \t]\\|\\\\\n" "\\)*"
"\\(#.*\\)?" "$")
"regexp matching Python statements opening a new block")

;; this is tricky because a trailing backslash does not mean
;; continuation if it's in a comment
(defconst py-continued-re
(concat
"\\(" "[^#'\n\\]" "\\|" py-stringlit-re "\\)*"
"\\\\$")
"regexp matching Python lines that are continued")

(defconst py-blank-or-comment-re "[ \t]*\\($\\|#\\)"
"regexp matching blank or comment lines")

;;; General Functions

(defun python-mode ()
"Major mode for editing Python files.
Do `\\[py-describe-mode]' for detailed documentation.
Knows about Python tokens, comments and continuation lines.
Paragraphs are separated by blank lines only.

COMMANDS
\\{py-mode-map}
VARIABLES

py-python-command\tshell command to invoke Python interpreter
py-indent-offset\tindentation increment
py-continuation-offset\textra indentation given to continuation lines
py-block-comment-prefix\tcomment string used by py-comment-region
py-beep-if-tab-change\tring the bell if tab-width is changed"
(interactive)
(kill-all-local-variables)
(setq major-mode 'python-mode mode-name "Python")
(use-local-map py-mode-map)
(set-syntax-table py-mode-syntax-table)

(mapcar (function (lambda (x)
(make-local-variable (car x))
(set (car x) (cdr x))))
'( (paragraph-separate . "^[ \t]*$")
(paragraph-start . "^[ \t]*$")
(require-final-newline . t)
(comment-start . "# ")
(comment-start-skip . "# *")
(comment-column . 40)
(indent-line-function . py-indent-line)))

;; hack to allow overriding the tabsize in the file (see tokenizer.c)

;; not sure where the magic comment has to be; to save time searching
;; for a rarity, we give up if it's not found prior to the first
;; executable statement
(let ( (case-fold-search nil)
new-tab-width)
(if (re-search-forward
"^[ \t]*#[ \t]*vi:set[ \t]+tabsize=\\([0-9]+\\):"
(prog2 (py-next-statement 1) (point) (goto-char 1))
t)
(progn
(setq new-tab-width
(string-to-int
(buffer-substring (match-beginning 1) (match-end 1))))
(if (= tab-width new-tab-width)
nil
(setq tab-width new-tab-width)
(message "Caution: tab-width changed to %d" new-tab-width)
(if py-beep-if-tab-change (beep))))))

(run-hooks 'py-mode-hook))

;;; Functions that execute Python commands in a subprocess

(defun py-shell ()
"Start an interactive Python interpreter in another window.
The variable py-python-command names the interpreter."
(interactive)
(require 'shell)
(switch-to-buffer-other-window
(make-shell "Python" py-python-command))
(make-local-variable 'shell-prompt-pattern)
(setq shell-prompt-pattern "^>>> \\|^\\.\\.\\. "))

(defun py-execute-region (start end)
"Send the region between START and END to a Python interpreter.
If there is a *Python* process it is used."
(interactive "r")
(condition-case nil
(process-send-string "Python" (buffer-substring start end))
(error (shell-command-on-region start end py-python-command nil))))

(defun py-execute-buffer nil
"Send the contents of the buffer to a Python interpreter.
If there is a *Python* process buffer it is used."
(interactive)
(py-execute-region (point-min) (point-max)))

;;; Functions for Python style indentation

(defun py-delete-char ()
"Reduce indentation or delete character.
If point is at the leftmost column, deletes the preceding newline.

Else if point is at the leftmost non-blank character of a line that is
neither a continuation line nor a non-indenting comment line, or if
point is at the end of a blank line, reduces the indentation to match
that of the line that opened the current block of code. The line that
opened the block is displayed in the echo area to help you keep track of
where you are.

Else the preceding character is deleted, converting a tab to spaces if
needed so that only a single column position is deleted."
(interactive "*")
(if (or (/= (current-indentation) (current-column))
(bolp)
(py-continuation-line-p)
(looking-at "#[^ \t\n]")) ; non-indenting #
(backward-delete-char-untabify 1)
;; else indent the same as the colon line that opened the block

;; force non-blank so py-goto-block-up doesn't ignore it
(insert-char ?* 1)
(backward-char)
(let ( (base-indent 0) ; indentation of base line
(base-text "") ; and text of base line
(base-found-p nil))
(condition-case nil ; in case no enclosing block
(save-excursion
(py-goto-block-up 'no-mark)
(setq base-indent (current-indentation)
base-text (py-suck-up-leading-text)
base-found-p t))
(error nil))
(delete-char 1) ; toss the dummy character
(delete-horizontal-space)
(indent-to base-indent)
(if base-found-p
(message "Closes block: %s" base-text)))))

(defun py-indent-line ()
"Fix the indentation of the current line according to Python rules."
(interactive)
(let* ( (ci (current-indentation))
(move-to-indentation-p (<= (current-column) ci))
(need (py-compute-indentation)) )
(if (/= ci need)
(save-excursion
(beginning-of-line)
(delete-horizontal-space)
(indent-to need)))
(if move-to-indentation-p (back-to-indentation))))

(defun py-newline-and-indent ()
"Strives to act like the Emacs newline-and-indent.
This is just `strives to' because correct indentation can't be computed
from scratch for Python code. In general, deletes the whitespace before
point, inserts a newline, and takes an educated guess as to how you want
the new line indented."
(interactive)
(let ( (ci (current-indentation)) )
(if (or (< ci (current-column)) ; if point is beyond indentation
(looking-at "[ \t]*$")) ; or line is empty
(newline-and-indent)
;; else try to act like newline-and-indent "normally" acts
(beginning-of-line)
(insert-char ?\n 1)
(move-to-column ci))))

(defun py-compute-indentation ()
(save-excursion
(beginning-of-line)
(cond
;; are we on a continuation line?
( (py-continuation-line-p)
(forward-line -1)
(if (py-continuation-line-p) ; on at least 3rd line in block
(current-indentation) ; so just continue the pattern
;; else on 2nd line in block, so indent more
(+ (current-indentation) py-indent-offset
py-continuation-offset)))
;; not on a continuation line

;; if at start of restriction, or on a non-indenting comment line,
;; assume they intended whatever's there
( (or (bobp) (looking-at "[ \t]*#[^ \t\n]"))
(current-indentation) )

;; else indentation based on that of the statement that precedes
;; us; use the first line of that statement to establish the base,
;; in case the user forced a non-std indentation for the
;; continuation lines (if any)
( t
;; skip back over blank & non-indenting comment lines
;; note: will skip a blank or non-indenting comment line that
;; happens to be a continuation line too
(re-search-backward "^[ \t]*\\([^ \t\n#]\\|#[ \t\n]\\)"
nil 'move)
(py-goto-initial-line)
(if (looking-at py-colon-line-re)
(+ (current-indentation) py-indent-offset)
(current-indentation))))))

(defun py-shift-region (start end count)
(save-excursion
(goto-char end) (beginning-of-line) (setq end (point))
(goto-char start) (beginning-of-line) (setq start (point))
(indent-rigidly start end count)))

(defun py-shift-region-left (start end &optional count)
"Shift region of Python code to the left.
The lines from the line containing the start of the current region up
to (but not including) the line containing the end of the region are
shifted to the left, by py-indent-offset columns.

If a prefix argument is given, the region is instead shifted by that
many columns."
(interactive "*r\nP") ; region; raw prefix arg
(py-shift-region start end
(- (prefix-numeric-value
(or count py-indent-offset)))))

(defun py-shift-region-right (start end &optional count)
"Shift region of Python code to the right.
The lines from the line containing the start of the current region up
to (but not including) the line containing the end of the region are
shifted to the right, by py-indent-offset columns.

If a prefix argument is given, the region is instead shifted by that
many columns."
(interactive "*r\nP") ; region; raw prefix arg
(py-shift-region start end (prefix-numeric-value
(or count py-indent-offset))))

(defun py-indent-region (start end &optional indent-offset)
"Reindent a region of Python code.
The lines from the line containing the start of the current region up
to (but not including) the line containing the end of the region are
reindented. If the first line of the region has a non-whitespace
character in the first column, the first line is left alone and the rest
of the region is reindented with respect to it. Else the entire region
is reindented with respect to the (closest code or indenting-comment)
statement immediately preceding the region.

This is useful when code blocks are moved or yanked, when enclosing
control structures are introduced or removed, or to reformat code using
a new value for the indentation offset.

If a numeric prefix argument is given, it will be used as the value of
the indentation offset. Else the value of py-indent-offset will be
used.

Warning: The region must be consistently indented before this function
is called! This function does not compute proper indentation from
scratch (that's impossible in Python), it merely adjusts the existing
indentation to be correct in context.

Warning: This function really has no idea what to do with non-indenting
comment lines, and shifts them as if they were indenting comment lines.
Fixing this appears to require telepathy.

Special cases: whitespace is deleted from blank lines; continuation
lines are shifted by the same amount their initial line was shifted, in
order to preserve their relative indentation with respect to their
initial line; and comment lines beginning in column 1 are ignored."

(interactive "*r\nP") ; region; raw prefix arg
(save-excursion
(goto-char end) (beginning-of-line) (setq end (point-marker))
(goto-char start) (beginning-of-line)
(let ( (py-indent-offset (prefix-numeric-value
(or indent-offset py-indent-offset)))
(indents '(-1)) ; stack of active indent levels
(target-column 0) ; column to which to indent
(base-shifted-by 0) ; amount last base line was shifted
(indent-base (if (looking-at "[ \t\n]")
(py-compute-indentation)
0))
ci)
(while (< (point) end)
(setq ci (current-indentation))
;; figure out appropriate target column
(cond
( (or (eq (following-char) ?#) ; comment in column 1
(looking-at "[ \t]*$")) ; entirely blank
(setq target-column 0))
( (py-continuation-line-p) ; shift relative to base line
(setq target-column (+ ci base-shifted-by)))
(t ; new base line
(if (> ci (car indents)) ; going deeper; push it
(setq indents (cons ci indents))
;; else we should have seen this indent before
(setq indents (memq ci indents)) ; pop deeper indents
(if (null indents)
(error "Bad indentation in region, at line %d"
(save-restriction
(widen)
(1+ (count-lines 1 (point)))))))
(setq target-column (+ indent-base
(* py-indent-offset
(- (length indents) 2))))
(setq base-shifted-by (- target-column ci))))
;; shift as needed
(if (/= ci target-column)
(progn
(delete-horizontal-space)
(indent-to target-column)))
(forward-line 1))))
(set-marker end nil))

;;; Functions for moving point

(defun py-previous-statement (count)
"Go to the start of previous Python statement.
If the statement at point is the i'th Python statement, goes to the
start of statement i-COUNT. If there is no such statement, goes to the
first statement. Returns count of statements left to move.
`Statements' do not include blank, comment, or continuation lines."
(interactive "p") ; numeric prefix arg
(if (< count 0) (py-next-statement (- count))
(py-goto-initial-line)
(let ( start )
(while (and
(setq start (point)) ; always true -- side effect
(> count 0)
(zerop (forward-line -1))
(py-goto-statement-at-or-above))
(setq count (1- count)))
(if (> count 0) (goto-char start)))
count))

(defun py-next-statement (count)
"Go to the start of next Python statement.
If the statement at point is the i'th Python statement, goes to the
start of statement i+COUNT. If there is no such statement, goes to the
last statement. Returns count of statements left to move. `Statements'
do not include blank, comment, or continuation lines."
(interactive "p") ; numeric prefix arg
(if (< count 0) (py-previous-statement (- count))
(beginning-of-line)
(let ( start )
(while (and
(setq start (point)) ; always true -- side effect
(> count 0)
(py-goto-statement-below))
(setq count (1- count)))
(if (> count 0) (goto-char start)))
count))

(defun py-goto-block-up (&optional nomark)
"Move up to start of current block.
Go to the statement that starts the smallest enclosing block; roughly
speaking, this will be the closest preceding statement that ends with a
colon and is indented less than the statement you started on. If
successful, also sets the mark to the starting point.

`\\[py-mark-block]' can be used afterward to mark the whole code block, if desired.

If called from a program, the mark will not be set if optional argument
NOMARK is not nil."
(interactive)
(let ( (start (point))
(found nil)
initial-indent)
(py-goto-initial-line)
;; if on blank or non-indenting comment line, use the preceding stmt
(if (looking-at "[ \t]*\\($\\|#[^ \t\n]\\)")
(progn
(py-goto-statement-at-or-above)
(setq found (looking-at py-colon-line-re))))
;; search back for colon line indented less
(setq initial-indent (current-indentation))
(if (zerop initial-indent)
;; force fast exit
(goto-char (point-min)))
(while (not (or found (bobp)))
(setq found
(and
(re-search-backward ":[ \t]*\\($\\|[#\\]\\)" nil 'move)
(progn
(py-goto-initial-line)
(and
(< (current-indentation) initial-indent)
(looking-at py-colon-line-re))))))
(if found
(progn
(or nomark (push-mark start))
(back-to-indentation))
(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
the next non-blank line. That will be the start of the region. The end
of the region depends on the kind of line at the start:

- If a comment, the region will include all succeeding comment lines up
to (but not including) the next non-comment line (if any).

- If a code line that opens a new block, the region will include all
succeeding lines up to (but not including) the next code statement
(if any) that's indented no more than the starting line, except that
trailing blank and comment lines are excluded. E.g., if the starting
line is a `def' statement, the region will be set to the full
function definition, but without any trailing `noise' lines.

- Else the region will include all succeeding lines up to (but not
including) the next blank line, or code or indenting-comment line
indented strictly less than the starting line. Trailing indenting
comment lines are included in this case, but not trailing blank
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
(while (and
(looking-at "[ \t]*$") ; while blank line
(not (eobp))) ; & somewhere to go
(forward-line 1))
(if (eobp)
(error "Hit end of buffer without finding a non-blank stmt"))
(let ( (initial-pos (point))
(initial-indent (current-indentation))
last-pos) ; position of last stmt in region
(cond
;; if comment line, suck up the following comment lines
((looking-at "[ \t]*#")
(re-search-forward "^[ \t]*[^ \t#]" nil 'move) ; look for non-comment
(re-search-backward "^[ \t]*#") ; and back to last comment in block
(setq last-pos (point)))
;; else if line opens a block, search for next stmt indented <=
((looking-at py-colon-line-re)
(while (and
(setq last-pos (point)) ; always true -- side effect
(py-goto-statement-below)
(> (current-indentation) initial-indent))
nil))
;; else plain code line; stop at next blank line, or stmt or
;; indenting comment line indented <
(t
(while (and
(setq last-pos (point)) ; always true -- side effect
(or (py-goto-beyond-final-line) t)
(not (looking-at "[ \t]*$")) ; stop at blank line
(or
(>= (current-indentation) initial-indent)
(looking-at "[ \t]*#[^ \t\n]"))) ; ignore non-indenting #
nil)))

;; 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
to (but not including) the line containing the end of the region are
commented out, by inserting the string py-block-comment-prefix at the
start of each line. With a prefix arg, removes py-block-comment-prefix
from the start of each line instead."
(interactive "*r\nP") ; region; raw prefix arg
(goto-char end) (beginning-of-line) (setq end (point))
(goto-char start) (beginning-of-line) (setq start (point))
(let ( (prefix-len (length py-block-comment-prefix)) )
(save-excursion
(save-restriction
(narrow-to-region start end)
(while (not (eobp))
(if uncomment-p
(and (string= py-block-comment-prefix
(buffer-substring
(point) (+ (point) prefix-len)))
(delete-char prefix-len))
(insert py-block-comment-prefix))
(forward-line 1))))))

;;; Documentation functions

;; dump the long form of the mode blurb; does the usual doc escapes,
;; plus lines of the form ^[vc]:name$ to suck variable & command
;; docs out of the right places, along with the keys they're on &
;; current values
(defun py-dump-help-string (str)
(with-output-to-temp-buffer "*Help*"
(let ( kind
funcname func funcdoc
(start 0) mstart end
keys )
(while (string-match "^%\\([vc]\\):\\(.+\\)\n" str start)
(setq mstart (match-beginning 0) end (match-end 0)
kind (substring str (match-beginning 1) (match-end 1))
funcname (substring str (match-beginning 2) (match-end 2))
func (car (read-from-string funcname)))
(princ (substitute-command-keys (substring str start mstart)))
(if (equal kind "c")
(setq funcdoc (documentation func)
keys (concat
"Key(s): "
(mapconcat 'key-description
(where-is-internal func py-mode-map)
", ")))
(setq funcdoc (substitute-command-keys
(get func 'variable-documentation))
keys (concat
"Value: "
(prin1-to-string (symbol-value func)))))
(princ (format "\n-> %s:\t%s\t%s\n\n"
(if (equal kind "c") "Command" "Variable")
funcname keys))
(princ funcdoc)
(princ "\n")
(setq start end))
(princ (substitute-command-keys (substring str start))))
(print-help-return-message)))

(defun py-describe-mode ()
"Dump long form of Python-mode docs."
(interactive)
(py-dump-help-string "Major mode for editing Python files.
Knows about Python tokens, comments and continuation lines.
Paragraphs are separated by blank lines only.

Major sections below begin with the string `@'; specific function and
variable docs begin with `->'.

@EXECUTING PYTHON CODE

\\[py-execute-buffer]\tsends the entire buffer to the Python interpreter
\\[py-execute-region]\tsends the current region
\\[py-shell]\tstarts a Python interpreter window; this will be used by
\tsubsequent \\[py-execute-buffer] or \\[py-execute-region] commands
%c:py-execute-buffer
%c:py-execute-region
%c:py-shell

@VARIABLES

py-python-command\tshell command to invoke Python interpreter
py-indent-offset\tindentation increment
py-continuation-offset\textra indentation given to continuation lines
py-block-comment-prefix\tcomment string used by `\\[py-comment-region]'
py-beep-if-tab-change\tring the bell if tab-width is changed
%v:py-python-command
%v:py-indent-offset
%v:py-continuation-offset
%v:py-block-comment-prefix
%v:py-beep-if-tab-change

@KINDS OF LINES

Each physical line in the file is either a `continuation line' (the
preceding line ends with a backslash that's not part of a comment) or an
`initial line' (everything else).

An initial line is in turn a `blank line' (contains nothing except
possibly blanks or tabs), a `comment line' (leftmost non-blank character
is `#'), or a `code line' (everything else).

Comment Lines

Although all comment lines are treated alike by Python, Python mode
recognizes two kinds that act differently with respect to indentation.

An `indenting comment line' is a comment line with a blank, tab or
nothing after the initial `#'. The indentation commands (see below)
treat these exactly as if they were code lines: a line following an
indenting comment line will be indented like the comment line. All
other comment lines (those with a non-whitespace character immediately
following the initial `#') are `non-indenting comment lines', and their
indentation is ignored by the indentation commands.

Indenting comment lines are by far the usual case, and should be used
whenever possible. Non-indenting comment lines are useful in cases like
these:

\ta = b # a very wordy single-line comment that ends up being
\t #... continued onto another line

\tif a == b:
##\t\tprint 'panic!' # old code we've `commented out'
\t\treturn a

Since the `#...' and `##' comment lines have a non-whitespace character
following the initial `#', Python mode ignores them when computing the
proper indentation for the next line.

Continuation Lines and Statements

The Python-mode commands generally work on statements instead of on
individual lines, where a `statement' is a comment or blank line, or a
code line and all of its following continuation lines (if any)
considered as a single logical unit. The commands in this mode
generally (when it makes sense) automatically move to the start of the
statement containing point, even if point happens to be in the middle of
some continuation line.

A Bad Idea

Always put something on the initial line of a multi-line statement
besides the backslash! E.g., don't do this:

\t\\
\ta = b # what's the indentation of this stmt?

While that's legal Python, it's silly & would be very expensive for
Python mode to handle correctly.

@INDENTATION

Primarily for entering new code:
\t\\[indent-for-tab-command]\t indent line appropriately
\t\\[py-newline-and-indent]\t insert newline, then indent
\t\\[py-delete-char]\t reduce indentation, or delete single character

Primarily for reindenting existing code:
\t\\[py-indent-region]\t reindent region to match its context
\t\\[py-shift-region-left]\t shift region left by py-indent-offset
\t\\[py-shift-region-right]\t shift region right by py-indent-offset

Unlike most programming languages, Python uses indentation, and only
indentation, to specify block structure. Hence the indentation supplied
automatically by Python-mode is just an educated guess: only you know
the block structure you intend, so only you can supply correct
indentation.

The \\[indent-for-tab-command] and \\[py-newline-and-indent] keys try to suggest plausible indentation, based on
the indentation of preceding statements. E.g., assuming
py-indent-offset is 4, after you enter
\tif a > 0: \\[py-newline-and-indent]
the cursor will be moved to the position of the `x':
\tif a > 0:
\t x
If you then enter `c = d' \\[py-newline-and-indent], the cursor will move
to
\tif a > 0:
\t c = d
\t x
Python-mode cannot know whether that's what you intended, or whether
\tif a > 0:
\t c = d
\tx
was your intent. In general, Python-mode either reproduces the
indentation of the (closest code or indenting-comment) preceding
statement, or adds an extra py-indent-offset blanks if the preceding
statement has `:' as its last significant (non-whitespace and non-
comment) character. If the suggested indentation is too much, use
\\[py-delete-char] to reduce it.

Warning: indent-region should not normally be used! It calls \\[indent-for-tab-command]
repeatedly, and as explained above, \\[indent-for-tab-command] can't guess the block
structure you intend.
%c:indent-for-tab-command
%c:py-newline-and-indent
%c:py-delete-char

The remaining `indent' functions apply to a region of Python code. They
assume the block structure (equals indentation, in Python) of the region
is correct, and alter the indentation in various ways while preserving
the block structure:
%c:py-indent-region
%c:py-shift-region-left
%c:py-shift-region-right

@MARKING & MANIPULATING REGIONS OF CODE

\\[py-mark-block]\t mark block of lines
\\[py-comment-region]\t comment out region of code
\\[universal-argument] \\[py-comment-region]\t uncomment region of code
%c:py-mark-block
%c:py-comment-region

@MOVING POINT

\\[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

The first two move to one statement beyond the statement that contains
point. A numeric prefix argument tells them to move that many
statements instead. Blank lines, comment lines, and continuation lines
do not count as `statements' for these commands. So, e.g., you can go
to the first code statement in a file by entering
\t\\[beginning-of-buffer]\t to move to the top of the file
\t\\[py-next-statement]\t to skip over initial comments and blank lines
Or do `\\[py-previous-statement]' with a huge prefix argument.
%c:py-previous-statement
%c:py-next-statement
%c:py-goto-block-up

@LITTLE-KNOWN EMACS COMMANDS PARTICULARLY USEFUL IN PYTHON MODE

`\\[indent-new-comment-line]' is handy for entering a multi-line comment.

`\\[set-selective-display]' with a `small' prefix arg is ideally suited for viewing
the overall class and def structure of a module.

`\\[indent-relative]' is handy for creating odd indentation.

@OTHER EMACS HINTS

If you don't like the default value of a variable, change its value to
whatever you do like by putting a `setq' line in your .emacs file.
E.g., to set the indentation increment to 4, put this line in your
.emacs:
\t(setq py-indent-offset 4)
To see the value of a variable, do `\\[describe-variable]' and enter the variable
name at the prompt.

Entering Python mode calls with no arguments the value of the variable
`py-mode-hook', if that value exists and is not nil; see the `Hooks'
section of the Elisp manual for details.

Obscure: When python-mode is first loaded, it looks for all bindings
to newline-and-indent in the global keymap, and shadows them with
local bindings to py-newline-and-indent."))

;;; Helper functions

;; go to initial line of 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
(defun py-goto-initial-line ()
(while (py-continuation-line-p)
(forward-line -1))
(beginning-of-line))

;; go to point right beyond final line of current statement; usually
;; this is the start of the next line, but if this is a multi-line
;; statement we need to skip over the continuation lines
(defun py-goto-beyond-final-line ()
(forward-line 1)
(while (and (py-continuation-line-p)
(not (eobp)))
(forward-line 1)))

;; t iff on continuation line == preceding line ends with backslash
;; that's not in a comment
(defun py-continuation-line-p ()
(save-excursion
(beginning-of-line)
(and
;; use a cheap test first to avoid the regexp if possible
;; use 'eq' because char-after may return nil
(eq (char-after (- (point) 2)) ?\\ )
(progn
(forward-line -1) ; since eq test passed, there is a line above
(looking-at py-continued-re)))))

;; go to start of first statement (not blank or comment or continuation
;; line) at or preceding point
;; returns t if there is one, else nil
(defun py-goto-statement-at-or-above ()
(py-goto-initial-line)
(if (looking-at py-blank-or-comment-re)
;; skip back over blank & comment lines
;; note: will skip a blank or comment line that happens to be
;; a continuation line too
(if (re-search-backward "^[ \t]*[^ \t#\n]" nil t)
(progn (py-goto-initial-line) t)
nil)
t))

;; go to start of first statement (not blank or comment or continuation
;; line) following the statement containing point
;; returns t if there is one, else nil
(defun py-goto-statement-below ()
(beginning-of-line)
(let ( (start (point)) )
(py-goto-beyond-final-line)
(while (and
(looking-at py-blank-or-comment-re)
(not (eobp)))
(forward-line 1))
(if (eobp)
(progn (goto-char start) nil)
t)))

;; return string in buffer from start of indentation to end of line;
;; prefix "..." if leading whitespace was skipped
(defun py-suck-up-leading-text ()
(save-excursion
(back-to-indentation)
(concat
(if (bolp) "" "...")
(buffer-substring (point) (progn (end-of-line) (point))))))

;; To do:
;; - add a newline when executing buffer ending in partial line
;; - suppress prompts when executing regions
;; - switch back to previous buffer when starting shell
;; - support for ptags

>>> END OF MSG