An improved Emacs Python mode

Tim Peters (tim@ksr.com)
Mon, 03 Feb 92 22:32:24 EST

You might want to give the following a try in place of
Python/misc/python.el.

Major enhancements:

- Honors your choice of indentation increment.

- Less naive about when to indent. E.g., will not indent after
a = b # ok: # found
or
a = ': #'
In the absence of continuation lines, I don't believe this version
will ever get it wrong (so please send an example if it does).

Details:

- Suggest you install this as python-mode.el (the Python distribution
names it 'python.el', but the '-mode' suffix is a universal
convention for Emacs language modes). This will require that you
change your .emacs to say
(autoload 'python-mode "python-mode" "" t)
instead of the current
(autoload 'python-mode "python" "" t)

- As written, the indentation increment defaults to 4 columns, just
because that's what I seem to like best for Python code. Change the
variable py-indent-offset to whatever you want it to be; e.g., to be
compatible with the current python.el, which always indents by full
tabs, stick this in your .emacs file:
(setq py-indent-offset 8)

- "Unindenting" a line is still done via the DEL key, but the DEL key
is now smarter: it will "dedent" the line by py-indent-offset
columns if point happens to be at the first non-blank character of a
line (or at the end of an entirely blank line), else it will delete
just the preceding character (& convert tabs to spaces if needed, so
that just 1 column position will be deleted).

I worried that this interface might be too much of a hack, but in
practice I've found it to be quite natural. If you have a better
idea, or find it too surprising or confusing, please holler.

- Still no effective support for continuation lines (this is all
driven by what bugs me when I'm writing code, & I simply haven't had
a need for a continuation line yet hence didn't worry about 'em).

- Changed the mode documentation (C-h m) to warn against using
indent-region, and to suggest using indent-rigidly for shifting a
block of Python code. One thing I would like to do is add some
shortcut support for marking & shifting regions of Python code;
indent-region will never be able to figure out what you wanted
(think about it <grin>), so after cutting a piece of Python code out
of one file & pasting it into a new context, it can be quite a pain
to figure out how to get the indentation right for the new context.
It should be possible for Python-mode to give the user some help
with this (e.g., something as simple as "'C-c <' shifts the region
left by py-indent-offset columns, and 'C-c >' shifts it right
similarly" would, I think, be helpful).

If there's something that you think would help you often & a lot in
Python mode, drop me a note & maybe I'll find the time to try & hack it
in.

just-another-aggressive-member-of-the-python-youth-corps-ly y'rs - tim

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

;; Major mode for editing Python programs.
;; by: Michael A. Guravage and Guido van Rossum <guido@cwi.nl>
;;
;; 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))

;; Change log:
;
; Sun Feb 2 02:08:47 1992 tim
; added support for user-defined indentation increment:
; added python-indent variable
; changed py-indent-line to indent python-indent columns
; fixed small bug in py-indent-line (changed start of r.e.
; from [^#] to [^#\n])
; added py-delete-char function; bound to \177
; changed mode blurb accordingly
;
; Sun Feb 2 21:48:59 1992 tim
; renamed 'python-indent' to 'py-indent-offset' for internal consistency
; replaced regexp in py-indent-line so it no longer indents after
; lines like:
; a = b # ok: # found
; a = ': #'
;
; Mon Feb 3 20:37:27 1992 tim
; renamed file to 'python-mode.el' for consistency with other Emacs
; language modes; changed autoload instructions accordingly
; improved accuracy of new/changed docs
; changed py-python-command from defconst to defvar so .emacs can
; override it if desired
; add warning about indent-region; suggest indent-rigidly

(provide 'python)

;; Constants and variables

(defvar py-python-command "python"
"*UNIX shell command used to start Python interpreter")

(defvar py-indent-offset 4
"*Indentation increment in Python mode")

(defvar py-mode-map nil
"Keymap used in Python mode buffers")

(defvar py-mode-syntax-table nil
"Python mode syntax table")

;; Initialize the keymap if it doesn't already exist

(if (null py-mode-map)
(progn
(setq py-mode-map (make-sparse-keymap))
(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)))

;; General Functions

(defun python-mode nil
"Major mode for editing Python files.

\\[py-execute-buffer] sends the entire buffer to the Python interpreter.
\\[py-execute-region] sends the current region.
\\[py-shell] starts a Python interpreter window; this will be used by
subsequent \\[py-execute-buffer] or \\[py-execute-region] commands.

Variable py-indent-offset is the indentation increment.

\\[newline-and-indent] indents by an extra py-indent-offset columns where
necessary. Use \\[py-delete-char] to reduce indentation.

\\[py-delete-char] 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 last character of an entirely blank line; else it deletes the
preceding character, converting tabs to spaces as needed so that only
one character position is deleted.

Warning: indent-region should not be used! Use indent-rigidly to shift
regions of Python code instead. indent-region can't guess the
indentation you had in mind, and will, e.g., change

xxx
if a = b:
c = d
if e = f: print 'ouch!'

to
xxx
if a = b:
c = d
if e = f: print 'ouch!'

Paragraphs are separated by blank lines only.
\\{py-mode-map}
\\[python-mode] calls the value of the variable py-mode-hook with no args,
if that value is non-nil."
(interactive)
(kill-all-local-variables)
(use-local-map py-mode-map)
(setq major-mode 'python-mode)
(setq mode-name "Python")
(if py-mode-syntax-table
(set-syntax-table py-mode-syntax-table)
(progn
(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
))
(make-local-variable 'paragraph-separate)
(setq paragraph-separate "^[ \t\f]*$")
(make-local-variable 'paragraph-start)
(setq paragraph-start "^[ \t\f]*$")
(make-local-variable 'require-final-newline)
(setq require-final-newline t)
(make-local-variable 'comment-start)
(setq comment-start "# ")
(make-local-variable 'comment-start-skip)
(setq comment-start-skip "# *")
(make-local-variable 'comment-column)
(setq comment-column 40)
(make-local-variable 'indent-line-function)
(setq indent-line-function 'py-indent-line)
(run-hooks 'py-mode-hook))

;; Functions that execute Python commands in a subprocess

(defun py-shell nil
"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-indent-line nil
"Fix the indentation of the current line according to Python rules."
(interactive)
(backward-to-indentation 1)
(let ( (ci (current-indentation)) )
(if (looking-at
"\\([^#'\n]\\|'\\([^'\n\\\\]\\|\\\\.\\)*'\\)*:[ \t]*\\(#.*\\)?$")
(setq ci (+ ci py-indent-offset)))
(forward-line 1)
(delete-horizontal-space)
(indent-to ci)))

(defun py-delete-char nil
"If at first non-blank character, or at last character of blank line,
reduce indentation by the value of variable py-indent-offset. 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)))

;; To do:
;; - proper indentation for continuation lines
;; - 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