(Emacs python-mode.el) Patch for version 1.04 -> 1.05

Tim Peters (tim@ksr.com)
Thu, 27 Feb 92 23:18:28 EST

Small changes:

- `C-c C-b' now accepts a prefix arg as meaning "mark the following
structure". E.g., if you're on an `if' statement, do `C-u C-c C-b'
and the region will be set to the `if' block plus all the following
`elif' and `else' blocks (if any) that belong to that `if'
structure. Handy when it's needed, but perhaps that's so rarely
that you'll have a hard time remembering how to do it <0.7 grin>.

If anyone has written Elisp that calls py-mark-block, note that you
must change your calls because the interface has changed (the former
only argument is now the second argument; pass `nil' as the first
argument and it will do exactly what it did before).

- If you ever did `M-x python-mode' by hand, you probably noticed that
it (probably) moved point to the start of the buffer as a surprising
side effect. Fixed.

- Added some words to the `C-c |' docs: a handy hint from Guido, and
a warning against sending regions to a Python buffer "too fast".

Plans: None other than to rewrite the "execute region/buffer" commands
completely, contingent upon Guido releasing some experimental changes
he's made to Python itself. If those make it into the world, sending a
region or a buffer to an active Python process will change like so:

- Error msgs will push the envelope of the cutting edge of the state
of the art, a veritable quantum leap beyond the stale & clumsy msgs
of today's old-fashioned technology (oops! forgive me; I'm working
at a startup <grin> ...). The unvarnished truth is that the line
numbers will be correct, the naughty lines will actually be
displayed, and there won't be tabs in front of them that you didn't
put there.

- You'll get more display options: (1) if you want, the process buffer
will scroll to show output as it arrives, even if it's not in the
selected window; and, (2) if you want, you can remove the process
buffer from all the windows and request that it "pop up" again the
next time output arrives (handy if you have a long-running
computation and don't won't to tie up screen area waiting for it say
something).

- The two above are certain (already have code that does 'em). This
one is iffy: it *might* be possible to let you send over regions
while other regions are still executing. This is much harder to do
than I thought it would be, even given Guido's changes (without
which it, along with the two above, looks next to impossible).

so-in-the-absence-of-more-bug-reports-version-1.05-looks-like-"it"-
for-a-while-ly y'rs - tim

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

Change log:
Thu Feb 27 22:04:27 1992 tim
version 1.05
added new arg to py-mark-block to mark whole structures (eg, if/elif/else)
added helper py-goto-beyond-block
changed end-of-python-def-or-class to use it
new helper py-suck-up-first-keyword
changed the docs
changed other calls to py-mark-block to match new arglist
added guido's narrow-to-region hint to the py-execute-region docs
+ warning about waiting for python to finish one region
before sending another one
changed python-mode to restore point when searching for vi tab directive
prevents buffer movement when `M-x python-mode' done by hand

Patch:

*** python-mode.el Thu Feb 27 22:36:57 1992
--- ../python-mode.el Thu Feb 27 22:17:59 1992
***************
*** 1,4 ****
! ;;; Major mode for editing Python programs, version 1.04
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
--- 1,4 ----
! ;;; Major mode for editing Python programs, version 1.05
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
***************
*** 181,186 ****
--- 181,187 ----
;; for a rarity, we give up if it's not found prior to the first
;; executable statement
(let ( (case-fold-search nil)
+ (start (point))
new-tab-width)
(if (re-search-forward
"^[ \t]*#[ \t]*vi:set[ \t]+tabsize=\\([0-9]+\\):"
***************
*** 194,200 ****
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))

--- 195,202 ----
nil
(setq tab-width new-tab-width)
(message "Caution: tab-width changed to %d" new-tab-width)
! (if py-beep-if-tab-change (beep)))))
! (goto-char start))

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

***************
*** 225,232 ****
--- 227,244 ----

is inserted at the end to let you know something is happening.

+ Hint: If you want to execute part of a Python file several times (e.g.,
+ perhaps you're developing a function and want to flesh it out a bit at a
+ time), use `\\[narrow-to-region]' to restrict the buffer to the region of interest,
+ and send the code to a *Python* process via `\\[py-execute-buffer]' instead.
+
Following are subtleties to note when using a *Python* process:

+ Related to the following (but in an obscure way), after you send a
+ region to a *Python* process, wait for Python to finish it before
+ sending another region. Fixing this under the current scheme is
+ difficult, but feel free to try <grin>.
+
The syntax of interactive Python differs a bit from the syntax of
`batch' Python: in interactive mode, a top-level code block is closed
always and only by an empty line. So, e.g., in interactive mode this
***************
*** 737,751 ****
Returns the position of the start of the def or class."
(interactive "P") ; raw prefix arg
(beginning-of-python-def-or-class class)
! (prog1 (point)
! (if (looking-at py-colon-line-re)
! (py-mark-block 'just-move)
! (py-goto-beyond-final-line))))

;;; Functions for marking regions

! (defun py-mark-block (&optional just-move)
! "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
--- 749,760 ----
Returns the position of the start of the def or class."
(interactive "P") ; raw prefix arg
(beginning-of-python-def-or-class class)
! (prog1 (point) (py-goto-beyond-block)))

;;; Functions for marking regions

! (defun py-mark-block (&optional extend just-move)
! "Mark following block of lines. With prefix arg, mark structure.
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
***************
*** 754,766 ****
- 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
--- 763,789 ----
- If a comment, the region will include all succeeding comment lines up
to (but not including) the next non-comment line (if any).

! - Else if a prefix arg is given, and the line begins one of these
! structures:
! \tif elif else try except finally for while def class
! the region will be set to the body of the structure, including
! following blocks that `belong' to it, but excluding trailing blank
! and comment lines. E.g., if on a `try' statement, the `try' block and
! all (if any) of the following `except' and `finally' blocks that
! belong to the `try' structure will be in the region. Ditto for
! if/elif/else and for/else structures, and (a bit degenerate, since
! they're always one-block structures) while, def and class blocks.

+ - Else if no prefix argument is given, and the line begins a Python
+ block (see list above), and the block is not a `one-liner' (i.e., the
+ statement ends with a colon, not with code), 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 begins a multi-statement `def' structure, 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
***************
*** 770,779 ****
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.

! If called from a program and optional argument JUST-MOVE is not nil,
! instead just moves to the end of the block, and does not set mark or
! display a msg."
! (interactive)
(py-goto-initial-line)
;; skip over blank lines
(while (and
--- 793,803 ----
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.

! If called from a program, optional argument EXTEND plays the role of the
! prefix arg, and if optional argument JUST-MOVE is not nil, just moves to
! the end of the block (& does not set mark or display a msg)."
!
! (interactive "P") ; raw prefix arg
(py-goto-initial-line)
;; skip over blank lines
(while (and
***************
*** 784,790 ****
(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]*#")
--- 808,821 ----
(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
! (followers
! '( (if elif else) (elif elif else) (else)
! (try except finally) (except except finally) (finally)
! (for else)
! (def) (class) (while) ) )
! first-symbol next-symbol)
!
(cond
;; if comment line, suck up the following comment lines
((looking-at "[ \t]*#")
***************
*** 791,797 ****
(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
--- 822,844 ----
(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 is a block line and EXTEND given, suck up
! ;; the whole structure
! ((and extend
! (setq first-symbol (py-suck-up-first-keyword) )
! (assq first-symbol followers))
! (while (and
! (or (py-goto-beyond-block) t) ; side effect
! (forward-line -1) ; side effect
! (setq last-pos (point)) ; side effect
! (py-goto-statement-below)
! (= (current-indentation) initial-indent)
! (setq next-symbol (py-suck-up-first-keyword))
! (memq next-symbol (cdr (assq first-symbol followers))))
! (setq first-symbol next-symbol)))
!
! ;; 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
***************
*** 798,803 ****
--- 845,851 ----
(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
***************
*** 1165,1170 ****
--- 1213,1228 ----
(not (eobp)))
(forward-line 1)))

+ ;; go to point right beyond final line of block begun by the current
+ ;; line. This is the same as where py-goto-beyond-final-line goes
+ ;; unless we're on colon line, in which case we go to the end of the
+ ;; block.
+ ;; assumes point is at bolp
+ (defun py-goto-beyond-block ()
+ (if (looking-at py-colon-line-re)
+ (py-mark-block nil 'just-move)
+ (py-goto-beyond-final-line)))
+
;; t iff on continuation line == preceding line ends with backslash
;; that's not in a comment
(defun py-continuation-line-p ()
***************
*** 1242,1247 ****
--- 1300,1313 ----
(if (bolp) "" "...")
(buffer-substring (point) (progn (end-of-line) (point))))))

+ ;; assuming point at bolp, return first keyword ([a-z]+) on the line,
+ ;; as a Lisp symbol; return nil if none
+ (defun py-suck-up-first-keyword ()
+ (let ( (case-fold-search nil) )
+ (if (looking-at "[ \t]*\\([a-z]+\\)\\b")
+ (intern (buffer-substring (match-beginning 1) (match-end 1)))
+ nil)))
+
;; send PROCESS the STRING as input, and wait for the process to send
;; something back
(defun py-process-send-string-wait (process string)
***************
*** 1252,1258 ****
(defun py-append-to-process-buffer (process string)
(save-excursion
(let* ( (proc-buf (process-buffer process))
! (pop-up-windows t)
(proc-win (display-buffer proc-buf)))
(set-buffer proc-buf)
(goto-char (point-max))
--- 1318,1324 ----
(defun py-append-to-process-buffer (process string)
(save-excursion
(let* ( (proc-buf (process-buffer process))
! (pop-up-windows t) ; "let*" so display-buffer sees this
(proc-win (display-buffer proc-buf)))
(set-buffer proc-buf)
(goto-char (point-max))

>>> END OF MSG