(Emacs python-mode.el) Patch 1.03 -> 1.04

Tim Peters (tim@ksr.com)
Sun, 23 Feb 92 22:02:34 EST

If you're curious about why `C-c |' didn't work for me on multi-line
regions, turns out it worked fine after I recompiled Python without the
GNU Readline library (& thanks to Guido for suggesting that). I don't
know any detail about *why* Readline got in the way, because it's
irrelevant to the real problem: there are any number of obscure OS-
related things that can go wrong in trying to send a long string to a
process.

So this patch redoes the `C-c |' and `C-c C-c' commands in a way that
tries to sidestep all the known problems, both the OS-related headaches
and the Python's-syntax-is-a-little-different-in-interactive-mode
headaches. Seems to work well for me! Let me know if it doesn't for
you.

The major remaining glitches seem too hard to fix without special help
from Python, and have to do with sending a region to an active Python
process:

1) Because the functions send an altered form of the region to Python,
in case of a syntax or runtime error the line numbers reported by
Python may not be right, and you may see an extra tab in front of
lines that Python echoes.

2) In case of a runtime error, in interactive mode Python (apparently)
doesn't save the input lines to echo back to you. This makes sense
when you're typing in lines one at a time, but `C-c |' is essentially
trying to use an interactive Python in a batch fashion: if you hit a
runtime error while executing a region's code, you won't be shown the
line that triggered the error.

Maybe it would be better overall (it would certainly be less hassle for
me <grin>) to write the region into a /tmp file and then just send a
line to Python to tell it to exec the contents of that file. The reason
the attached code doesn't settle for that is that the error msgs are
then even poorer.

Etc. Guido & I are arguing about this stuff in the background. In the
meantime, I hope you'll find this pleasant enough.

better-than-what-you-had-anyway-ly y'rs - tim

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

Change log:

Sun Feb 23 18:06:30 1992 tim
version 1.04
lots of hacks for execute-code cmds
replaced py-execute-region
new helper py-process-filter
new helper py-process-send-string-wait
new helper py-append-to-process-buffer
removed to-do's as "done":
;; - add a newline when executing buffer ending in partial line
;; - suppress prompts when executing regions
removed to-do because i like the current behavior better:
;; - switch back to previous buffer when starting shell
fleshed out docs for py-execute-buffer

Patch:

*** python-mode.el Sun Feb 23 21:18:03 1992
--- ../python-mode.el Sun Feb 23 21:01:57 1992
***************
*** 1,4 ****
! ;;; Major mode for editing Python programs, version 1.03
;; 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.04
;; by: Michael A. Guravage
;; Guido van Rossum <guido@cwi.nl>
;; Tim Peters <tim@ksr.com>
***************
*** 212,226 ****

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

--- 212,341 ----

(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. A trailing newline will be
! supplied if needed.
!
! If a trailing newline must be supplied, the region is first copied to
! buffer `-Python Input-'.
!
! If a *Python* process is used, the process buffer is popped into another
! window so you can see it, and the comment
!
! \t## working on region...
!
! is inserted at the end to let you know something is happening.
!
! Following are subtleties to note when using a *Python* process:
!
! 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
! triggers a syntax error:
!
! for i in 1, 2, 3:
! print i,
!
! print i*i
!
! The `for' loop was closed by the empty line, so the indented `print i*i'
! makes no sense.
!
! This function worms around such surprises by, in effect, deleting the
! blank lines from the region before sending it to Python. But note that
! deleting blank lines can create a (unlikely!) problem of its own:
!
! for i in 1, 2, 3:
! print i, \\
!
! print i*i
!
! If someone actually uses entirely blank continuation lines, gripe and
! maybe I'll fix it.
!
! Other tricks used to reduce surprises:
!
! - In effect, an `if 1:' is inserted at the front of the region, and a
! tab is put in front of every non-blank line in the region. This
! forces Python to treat the region as a unit with respect to
! exceptions, so that the first unhandled runtime exception (divide by
! 0, etc) will abort execution of the region.
!
! Note that in case of error, the line numbers Python reports may be
! wrong, because of the `if 1:' inserted at the start and because
! Python never sees the blank lines. Note also that lines displayed
! as part of a traceback will have an extra tab at the start.
!
! - The lines are sent to Python one at a time, and the function waits
! for Python to respond before sending another. This is done for two
! reasons: (1) In case of a syntax error, the code detects that Python
! is unhappy as soon as the error occurs, and stops sending more
! lines. (2) Sending more than one line at a time simply doesn't work
! on many systems, for a variety of obscure reasons."
(interactive "r")
! (or (< start end) (error "Region is empty"))
! (let ( (pyproc (get-process "Python")) )
! (save-excursion
! ;; if newline not at end, take source from a temp buffer
! (cond
! ( (= (char-after (1- end)) ?\n)
! nil)
! ( (and (< end (point-max))
! (= (char-after end) ?\n))
! (setq end (1+ end))) ; lucked out
! ;; else we have to create a newline
! ( t
! (let ( (temp-buffer (get-buffer-create "-Python Input-")) )
! (copy-to-buffer temp-buffer start end)
! (set-buffer temp-buffer)
! (goto-char (point-max))
! (insert-char ?\n 1)
! (setq start (point-min)
! end (point-max)))))

! ;; send the text
! (if (null pyproc)
! (shell-command-on-region start end py-python-command)
! ;; else feed it one line at a time, hack upon hack
! (unwind-protect
! (progn
! (set-process-filter pyproc 'py-process-filter)
! (py-append-to-process-buffer
! pyproc "## working on region...\n")
! (py-process-send-string-wait pyproc "if 1:\n")
! (goto-char start)
! (while (< (point) end)
! (if (looking-at "[ \t]*$"); skip blank line
! (forward-line 1)
! ;; else
! (py-process-send-string-wait
! pyproc
! (concat "\t" (buffer-substring
! (point)
! (progn (forward-line 1) (point)))))))
! (set-process-filter pyproc nil)
! (process-send-string pyproc "\n"))
! ;; unwind-protect'ed code
! (set-process-filter pyproc nil))))))
!
! ;; throw away "... " prompts, and gripe about anything else; this is
! ;; intended to be used only while py-execute-region is feeding gimmicked
! ;; strings to Python
! (defun py-process-filter (pyproc string)
! ;; (save-excursion
! ;; (set-buffer (get-buffer-create "Trace"))
! ;; (insert "{" string "}"))
! (if (equal string "... ")
! nil
! (py-append-to-process-buffer pyproc string)
! (error "Unexpected response from Python -- syntax error?")))
!
! (defun py-execute-buffer ()
"Send the contents of the buffer to a Python interpreter.
! If there is a *Python* process buffer it is used. If a clipping
! restriction is in effect, only the accessible portion of the buffer is
! sent. A trailing newline will be supplied if needed.
!
! See the `\\[py-execute-region]' docs for an account of some subtleties."
(interactive)
(py-execute-region (point-min) (point-max)))

***************
*** 1127,1134 ****
(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
--- 1242,1265 ----
(if (bolp) "" "...")
(buffer-substring (point) (progn (end-of-line) (point))))))

+ ;; send PROCESS the STRING as input, and wait for the process to send
+ ;; something back
+ (defun py-process-send-string-wait (process string)
+ (process-send-string process string)
+ (accept-process-output process))
+
+ ;; make PROCESS's buffer visible, append STRING to it, and force display
+ (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))
+ (insert string)
+ (move-marker (process-mark process) (point-max))
+ (set-window-point proc-win (point-max))
+ (sit-for 0))))
+
;; To do:
;; - support for ptags

>>> END OF MSG