Will these Python Emacs modes never stop?!

Tim Peters (tim@ksr.com)
Thu, 06 Feb 92 00:42:30 EST

Couldn't help myself <0.9 grin>.

Three new keys:

C-c TAB
C-c <
C-c >

These all have to do with shifting a region of Python code. C-c TAB is
the most important one, and indeed can do anything the other two can do
(although the other two may be more convenient at times).

Example: Say you're typing away in file a.py:

this = that

and need some code to get a temp file name. You remember you have some
in file b.py, so you visit that, cut it out, go back to a.py, and yank
it in (C-y) after the line above. This leaves you with:

this = that
if tmpfilename == '':
for i in range(100):
tmpfilename = '/tmp/tmp' + `posix.getpid()` + '.' + `i`
if not path.exists(tmpfilename): break
else: raise TmpError, 'all /tmp names already taken'

and an indentation headache. No more! Just hit C-c TAB to get:

this = that
if tmpfilename == '':
for i in range(100):
tmpfilename = '/tmp/tmp' + `posix.getpid()` + '.' + `i`
if not path.exists(tmpfilename): break
else: raise TmpError, 'all /tmp names already taken'

In other words, C-c TAB shifts regions of Python code, by an amount
based on context, much as TAB does for individual lines.

You can also give an absolute shift count to C-c TAB. C-c < and C-c >
just shift a region of code left or right (respectively) by py-indent-
offset columns (or by any other amount you like, if you give them a
numeric prefix argument).

getting-close-to-having-a-real-language-mode<grin>-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
;
; Wed Feb 5 03:23:31 1992 tim
; added support for auto-indenting of continuation lines:
; new vrbl py-continuation-offset
; new function py-continuation-line-p
; new function py-goto-initial-line
; new function py-compute-indentation
; rewrote py-indent-line
; changed py-indent-line to refrain from modifying the buffer if the
; indentation is already correct
; hid the hairy colon-line regexp in a const
; changed indent-region example to use legal Python
; documented all that
;
; Wed Feb 5 21:52:15 1992 tim
; changed new doc strings so first line makes sense on its own
; new function py-shift-region; bound to C-c TAB
; new function py-shift-region-left; bound to C-c <
; new function py-shift-region-right; bound to C-c >
; reorganized mode blurb
; sped up py-continuation-line-p

(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-continuation-offset 2
"*Indentation (in addition to py-indent-offset) for continued lines")

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

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

(defconst py-colon-line-re
"\\([^#'\n]\\|'\\([^'\n\\]\\|\\\\.\\)*'\\)*:[ \t]*\\(#.*\\)?$"
"regexp matching lines opening a new block")

;; 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)
(define-key py-mode-map "\C-c\C-i" 'py-shift-region)
(define-key py-mode-map "\C-c<" 'py-shift-region-left)
(define-key py-mode-map "\C-c>" 'py-shift-region-right)))

;; General Functions

(defun python-mode nil
"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.

INTERFACE TO PYTHON INTERPRETER

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

VARIABLES

py-indent-offset indentation increment
py-continuation-offset extra indentation given to continuation lines

INDENTATION

\\[newline-and-indent] indent line appropriately
\\[py-delete-char] reduce indentation, or delete single character
\\[py-shift-region-left] shift region left by py-indent-offset
\\[py-shift-region-right] shift region right by py-indent-offset
\\[py-shift-region] shift region to match its context

\\[newline-and-indent] indents by an extra py-indent-offset columns where
necessary.

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

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

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

\\[py-shift-region] shifts the region to indent it properly with respect
to the line preceding the region. This is useful when code blocks are
moved or yanked, or when enclosing control structures are introduced or
removed.

The three \"shift\" functions above also honor numeric prefix arguments;
see the individual function documentation for details.

Warning: indent-region should not normally be used! It 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!'

MODE MAP
\\{py-mode-map}"
(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-delete-char ()
"Reduce indentation or delete character.
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)))

(defun py-indent-line ()
"Fix the indentation of the current line according to Python rules."
(interactive)
(let ( (need (py-compute-indentation)) )
(if (= (current-indentation) need)
nil
(beginning-of-line)
(delete-horizontal-space)
(indent-to need))))

; go to start 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 ()
(if (re-search-backward "[^\\]\n" (point-min) 1)
(forward-char 2))) ; else leave point at start of restriction

; t iff on continuation line == preceding line ends with backslash
(defun py-continuation-line-p ()
(save-excursion
(beginning-of-line)
(eq (char-after (- (point) 2)) ; nil if argument out of range
?\\ )))

(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, assume they intended whatever's there
( (bobp) (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
(backward-to-indentation 1)
(let ( (need-more-p (looking-at py-colon-line-re)) )
(py-goto-initial-line)
(if need-more-p
(+ (current-indentation) py-indent-offset)
(current-indentation)))))))

(defun py-shift-region (start end &optional count)
"Shift region of Python code horizontally.
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 the same amount, so that they're indented properly with respect
to the line immediately preceding the region. This is useful when code
blocks are moved or yanked, or when enclosing control structures are
introduced or removed.
If a prefix argument is given, the region is instead shifted by that
many columns (to the right if the prefix argument is positive, else to
the left)."
(interactive "*r\nP") ; region; raw prefix arg
(save-excursion
(goto-char end) (beginning-of-line) (setq end (point))
(goto-char start) (beginning-of-line) (setq start (point))
(indent-rigidly start end
(if (null count)
(- (py-compute-indentation)
(current-indentation))
(prefix-numeric-value 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
(- (if (null count)
py-indent-offset
(prefix-numeric-value count)))))

(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 (or count py-indent-offset)))

;; 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