[EMACS] python-mode patch, 1.08a -> 1.08ay

Tim Peters (tim@ksr.com)
Sat, 23 Apr 94 02:10:42 -0400

Attached is a patch to be applied against the Python/Misc/python-mode.el
(1.08a) distributed with Python 1.0.0:

cd $YOUR_ROOT/Python/Misc
patch < this_message

Only change from 1.08ax is fancier indentation for continuation lines.
Following are some pathological examples people sent, along with the
indentation suggested by 1.08ay. Lines that need to be fiddled by hand
to get the indentation shown have a '*' in column 1:

if a == (6, 3,
2, 4, [12,
9,
4],
'egg',
'gum' ):
pass

a = "#" #\
b = "# #\\" \
+ 'c'

_MONTH_NAMES = [ 'January',
'February',
'March',
'April',
'May' ]

while\
a == \
b:
yuck

a = func( lsdkjflskdjfl, lskdjflsjkflsj, sldkjfslkfj, lskdjflsdj,
lsdkjflsjkf, lsdkjflskj, a[i,j,k,l,m,
n,o,p,q,r],
46) + \
sdf

weekday_translations = {
* 'Monday': 'Maandag',
'Tuesday': 'Dinsdag',
...
* }

if a_very_long_test(_pah_) and \
another_very_complicated_test(_snort_):
pass

matrix[1, 1] = a_boring_long_vector(_giggle_) + \
another_very_long_expression(_wink_)

if matrix[1, 1] == a_boring_long_vector(_giggle_) + \
another_very_long_expression(_wink_):
pass

matrix [ i == "=" ][x==y] = i + \
j

matrix[i == "="][x==y] \
= i+j

matrix[ i == "=" ][x==y] = \
i+j

a, b, c, d, e = \
the_result_of_a_function_returning_a_quintuple()

a, b, c, d = 3, 4, \
the_result_of_a_function_returning_an_object(5,6), \
7

self.tbox = Box({
* 'alignment' : vp.VCENTER,
'stretchability': (vp.FIXED, vp.ELASTIC),
'child_list' :[

* ToggleButton({
* 'name' : 'edit',
'radio_group' : rg,
'callback' : self.SetDrawToolCB,
'use_indicator' : vp.FALSE,
'stretchability': (vp.ELASTIC, vp.FIXED)
}),

ToggleButton({ # auto-indents like first ToggleButton
* 'name' : 'rect',
'radio_group' : rg,
'callback' : self.SetDrawToolCB,
'use_indicator' : vp.FALSE,
'stretchability': (vp.ELASTIC, vp.FIXED)
})
]
})

list = [ (1,2), (3,4), (5,6),
* # just to be irritating

* # ditto
(7,8), (9,10) ]

In short, continued-via-backslash assignment statements are special-
cased, and for continuations via nesting it strives to mimic the
indentation you give to the first item in the nest (but the first
item in a nest still follows the 1.08ax related-to-c-mode guess).

Got more sick examples? I can't wait, but we may be reaching the limits
of lexical telepathy here <wink>.

c-mode's-rolling-over-in-its-grave-ly y'rs - tim

Tim Peters tim@ksr.com
not speaking for Kendall Square Research Corp

Change log

version 1.08ay
fancier indentation for continuation lines
backslash: special-case assignments
nests: strive to mimic indentation of first nest item
update docs

version 1.08ax
teach py-stringlit-re and py-continued-re about double-quoted strings
e.g. a = "#" + \
wasn't recognized as a continued line
make indent-region-function a local vrbl & set to py-indent-region
fiddle indentation for continuation lines
adapt unclosed brace/bracket/paren scheme from donald beaudry
implement guido's suggestion for backslash lines
purge py-continuation-offset
update docs

*** python-mode.el Fri Apr 22 23:51:18 1994
--- ../python-mode.el Sat Apr 23 01:42:53 1994
***************
*** 1,4 ****
! ;;; Major mode for editing Python programs, version 1.08a
;; by: Tim Peters <tim@ksr.com>
;; after an original idea by: Michael A. Guravage
;;
--- 1,4 ----
! ;;; Major mode for editing Python programs, version 1.08ay
;; by: Tim Peters <tim@ksr.com>
;; after an original idea by: Michael A. Guravage
;;
***************
*** 40,55 ****
Note that `\\[py-guess-indent-offset]' can usually guess a good value when you're
editing someone else's Python code.")

- (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
--- 40,45 ----
***************
*** 204,210 ****
( ?\# . "<") ; hash starts comment
( ?\n . ">")))) ; newline ends comment

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

;; this is tricky because a trailing backslash does not mean
--- 194,204 ----
( ?\# . "<") ; hash starts comment
( ?\n . ">")))) ; newline ends comment

! (defconst py-stringlit-re
! (concat
! "'\\([^'\n\\]\\|\\\\.\\)*'" ; single-quoted
! "\\|" ; or
! "\"\\([^\"\n\\]\\|\\\\.\\)*\"") ; double-quoted
"regexp matching a Python string literal")

;; this is tricky because a trailing backslash does not mean
***************
*** 211,219 ****
;; 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")
--- 205,213 ----
;; continuation if it's in a comment
(defconst py-continued-re
(concat
! "\\(" "[^#'\"\n\\]" "\\|" py-stringlit-re "\\)*"
"\\\\$")
! "regexp matching Python lines that are continued via backslash")

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

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-python-command\tshell command to invoke Python interpreter
py-scroll-process-buffer\talways scroll Python process buffer
--- 225,230 ----
***************
*** 254,259 ****
--- 247,253 ----
(comment-start . "# ")
(comment-start-skip . "# *")
(comment-column . 40)
+ (indent-region-function . py-indent-region)
(indent-line-function . py-indent-line)))

;; hack to allow overriding the tabsize in the file (see tokenizer.c)
***************
*** 515,526 ****
(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,
--- 509,584 ----
(cond
;; are we on a continuation line?
( (py-continuation-line-p)
! (let ( (startpos (point))
! (open-bracket-pos (py-nesting-level))
! endpos searching found)
! (if open-bracket-pos
! (progn
! ;; if preceding line in same structure, presumably the
! ;; user already has an indentation they like for this
! ;; structure, so just copy it
! (forward-line -1)
! (while (looking-at "[ \t]*[#\n]")
! (forward-line -1)) ; ignore noise lines
! (if (eq open-bracket-pos (py-nesting-level))
! (current-indentation)
! ;; else copy the indentation of the first item (if
! ;; any) in this structure
! (goto-char startpos)
! (condition-case nil
! (progn (backward-list) (setq found t))
! (error nil)) ; no preceding item
! (goto-char (1+ open-bracket-pos)) ; just beyond bracket
! (if found
! (progn
! (while (looking-at "[ \t]*[#\n\\\\]")
! (forward-line 1))
! (skip-chars-forward " \t")
! (current-column))
! ;; else to first real character (not whitespace or
! ;; comment hash) after open bracket; if none, to
! ;; 1 beyond the open bracket
! (and (looking-at "[ \t]*[^ \t\n#]")
! (goto-char (1- (match-end 0))))
! (current-column))))
!
! ;; else on backslash continuation line
! (forward-line -1)
! (if (py-continuation-line-p) ; on at least 3rd line in block
! (current-indentation) ; so just continue the pattern
! ;; else started on 2nd line in block, so indent more.
! ;; if base line is an assignment with a start on a RHS,
! ;; indent to 2 beyond the leftmost "="; else skip first
! ;; chunk of non-whitespace characters on base line, + 1 more
! ;; column
! (end-of-line)
! (setq endpos (point) searching t)
! (back-to-indentation)
! (setq startpos (point))
! ;; look at all "=" from left to right, stopping at first
! ;; one not nested in a list or string
! (while searching
! (skip-chars-forward "^=" endpos)
! (if (= (point) endpos)
! (setq searching nil)
! (forward-char 1)
! (setq state (parse-partial-sexp startpos (point)))
! (if (and (zerop (car state)) ; not in a bracket
! (null (nth 3 state))) ; & not in a string
! (progn
! (setq searching nil) ; done searching in any case
! (setq found
! (not (or
! (eq (char-after (point)) ?=)
! (memq (char-after (- (point) 2))
! '(?< ?> ?!)))))))))
! (if (or (not found) ; not an assignment
! (looking-at "[ \t]*\\\\")) ; <=><spaces><backslash>
! (progn
! (goto-char startpos)
! (skip-chars-forward "^ \t\n")))
! (1+ (current-column))))))
!
;; not on a continuation line

;; if at start of restriction, or on a non-indenting comment line,
***************
*** 1144,1150 ****
@VARIABLES

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-python-command\tshell command to invoke Python interpreter
--- 1202,1207 ----
***************
*** 1153,1159 ****

py-beep-if-tab-change\tring the bell if tab-width is changed
%v:py-indent-offset
- %v:py-continuation-offset
%v:py-block-comment-prefix
%v:py-python-command
%v:py-scroll-process-buffer
--- 1210,1215 ----
***************
*** 1209,1225 ****
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:
--- 1265,1271 ----
***************
*** 1265,1270 ****
--- 1311,1340 ----
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.
+
+ Continuation lines are given extra indentation. If you don't like the
+ suggested indentation, change it to something you do like, and Python-
+ mode will strive to indent later lines of the statement in the same way.
+
+ If a line is a continuation line by virtue of being in an unclosed
+ paren/bracket/brace structure (`list', for short), the suggested
+ indentation depends on whether the current line will contain the first
+ item in the list. If it is the first item, it's indented to line up with
+ the first non-whitespace and non-comment character following the list's
+ opening bracket; if no such character exists, it's indented to one column
+ beyond the opening bracket. If you don't like that, change it by hand.
+ The remaining items in the list will mimic whatever indentation you gave
+ to the first item.
+
+ If a line is a continuation line because the line preceding it ends with
+ a backslash, the third and following lines of the statement inherit their
+ indentation from the line preceding them. The indentation of the second
+ line in the statement depends on the form of the first (base) line: if
+ the base line is an assignment statement with anything more interesting
+ than the backslash following the leftmost assigning `=', the second line
+ is indented two columns beyond that `='. Else it's indented to two
+ columns beyond the leftmost solid chunk of non-whitespace characters on
+ the base line.

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

>>> END OF PATCH