Version 1.0 of (Emacs) Python mode

Tim Peters (tim@ksr.com)
Mon, 17 Feb 92 16:06:56 EST

Attached. Many changes from the last version you saw, including an
obnoxious copyright notice that's as weak as possible without inviting
nuisance litigation.

The only major incompatibility with previous versions is that
py-indent-offset now defaults to 8.

New commands include:
C-c C-n go to next statement
C-c C-p go to previous statement
LFD in most ways unchanged
C-c C-u go to start of smallest enclosing code block
C-c C-b mark the next "interesting" block of lines
C-c # comment out a region of code
C-u C-c # uncomment a commented-out region of code

And almost everything else has changed in some small way or another in
response to various gripes. Note especially that a convention has been
introduced for distinguishing between comments that should and shouldn't
affect the indentation of following lines.

The mode blurb (C-h m) has gotten huge; sorry about that. An Emacs Info
node would be more appropriate, but I don't know how to write one &
don't have time to learn.

gripes-&-suggestions-welcome-ly y'rs - tim

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

The change log has been removed from the code. Here are the entries
(most recent first) since the last version you saw:

Mon Feb 17 15:28:12 1992 tim
replaced '(point-min)' by 'nil' in a couple regexp search cmds (faster)
rewrote py-mark-block to be more useful
new helper py-goto-beyond-final-line
new const py-blank-or-comment-re
changed the docs accordingly
stuck in some `Emacs hints' since half the questions i get are
really general Emacs questions
added copyright
added version number
removed change log

Sun Feb 16 01:03:32 1992 tim
changed 'provide' to provide 'python-mode, so that a plain
(require 'python-mode) will work
changed binding of py-mark-block to C-c C-b (`C-c RET' was too confusing)
new function py-goto-block-up, bound to C-c C-u
changed py-newline-and-indent to try to act more like
newline-and-indent usually acts in other language modes (obscure)

Sat Feb 15 00:05:33 1992 tim
minor code cleanups (no change in function)
removed old instances of \f, since Python doesn't accept formfeeds
changed py-compute-indentation to distinguish between two kinds
of comment lines (indenting & non-indenting)
added to mode blurb, explaining the various terms used in the docs
for talking about python lines & statements
changed all the docs to use those terms consistently
changed py-indent-offset default to 8, to humor Guido <grin>
added code to recognize embedded vi directive to change tabsize
added new vrbl py-beep-if-tab-change
new function py-comment-region, bound to C-c #
new supporting vrbl py-block-comment-prefix

Wed Feb 12 23:44:45 1992 tim
added function py-mark-block, bound to C-c C-m
added helper function py-suck-up-leading-text

Tue Feb 11 21:54:44 1992 tim
changed py-indent-line to leave point where the `Basic Indent' node
of the Emacs docs sez TAB will leave it
added new function py-newline-and-indent, bound to LFD
updated docs
added initialization code to search the global keymap for
instances of newline-and-indent, and to shadow them
by binding the same keys to py-newline-and-indent in
the local py-mode-map (CAUTION: not entirely sure I
know all the implications)

Mon Feb 10 22:33:39 1992 tim
mentioned py-python-command in 'variable' section of mode blurb
fixed py-goto-initial-line so it always moves to start of line
rearranged some of the functions
added my name to the "who's to blame?" blurb
added new functions for moving point:
py-goto-statement-at-or-above
py-goto-statement-below
py-next-statement; bound to C-c C-n
py-previous-statement; bound to C-c C-p

And here's the code:

;;; Major mode for editing Python programs, version 1.00
;; 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"
"*UNIX shell command used to start Python interpreter")

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

(defvar py-continuation-offset 2
"*Indentation (in addition to py-indent-offset) for continued lines")

(defvar py-block-comment-prefix "##"
"*String used by py-comment-region to comment out a block of code")

(defvar py-beep-if-tab-change t
"*Ring the bell if tab-width is changed")

(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))

(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.

Paragraphs are separated by blank lines only.

\\[python-mode] calls the value of the variable py-mode-hook with no
args, if that value is non-nil.

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.

INTERFACE TO PYTHON INTERPRETER

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

VARIABLES

If you don't like the default value for one of these, 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 7, put this line in
your .emacs:
\t(setq py-indent-offset 7)
To see the value of a variable, do `\\[describe-variable]' and enter the variable
name at the prompt.

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

py-continuation-offset is 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).

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.

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.

\\[py-newline-and-indent] strives to `act like' the Emacs newline-and-indent function; this
requires some obscure Python-specific trickery because, as noted above,
it's not always possible to compute your intended indentation.

\\[py-delete-char] is handy after \\[py-newline-and-indent] to reduce excess indentation. It reduces the
indentation of a line by py-indent-offset columns if point is at the
first non-blank character (if any) of a line, or at the end of a blank
line; else it deletes the preceding character, converting tabs to spaces
as needed so that only one character position is deleted.

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:

`\\[py-indent-region]' reindents a region to match its context and/or with a
different value for the indentation offset. 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. See the function documentation for details.

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.

`\\[py-shift-region-left]' shifts the region left by py-indent-offset columns.

`\\[py-shift-region-right]' shifts the region right by py-indent-offset columns.

The two `shift' functions above also honor numeric prefix arguments; see
the individual function documentation for details.

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

`\\[py-mark-block]' is 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.

`\\[py-comment-region]' comments out a region of code by inserting the string
py-block-comment-prefix at the start of each line. If given a prefix
argument (like `\\[universal-argument]', which is kinda mnemonic for `uncomment'), it instead
deletes that string from the start of each line in the region.

MOVING POINT AROUND

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

`\\[py-goto-block-up]' goes 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.
Also sets the mark to the starting point.

SOME 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 seeing
the overall class and def structure of a module.

PYTHON-MODE's LOCAL KEYMAP
\\{py-mode-map}"

(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 at first non-blank character, or at end of blank line, reduce
indentation by py-indent-offset columns. Else delete preceding
character, converting tabs to spaces."
(interactive)
(backward-delete-char-untabify
(if (and
(= (current-indentation) (current-column))
(>= (current-indentation) py-indent-offset))
py-indent-offset
1)))

(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 comment line, assume they
;; intended whatever's there
( (or (bobp) (looking-at "[ \t]*#"))
(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))
(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. py-block-comment-prefix should be
of the form `#x...', where `x' is some character other than a blank or
a tab, and `...' is arbitrary. This is so that the indentation commands
aren't confused by seeing these comments start in the leftmost column."
(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))))))

;;; 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)) )
(forward-line 1)
(while (and
(py-continuation-line-p)
(not (eobp)))
(forward-line 1))
(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