[comp.emacs] CMU process modes addenda

shivers@centro.soar.cs.cmu.edu (Olin Shivers) (02/11/89)

The version of comint.el posted to the net contains code that logs the user in
a local user-database I keep at CMU to track the beta-test users. Leaving this
code in place will do no harm, besides chewing up a few extra cycles when you
load the package in, and it checks to see if the user log file exists.
However, you should probably remove it. It's easy to do: just delete the
function comint-log-user and the following call to it.

Sorry about that.  I actually remembered to remove the code in the original
shar file I constructed for posting to the net. However, Usenet is unhappy
with bboard posts of 130k, so I split it into three posts... and forgot to
remove the logging code in the new shar files.
    -Olin
-- 

shivers@centro.soar.cs.cmu.edu (Olin Shivers) (02/17/89)

------

This addenda has three topics:
- A discussion of some frequently-asked questions.
  Why does comint-send-input [return] work the way it does?
  Why is file-complete bound to M-tab?
- A bug in cmuscheme.el
  A fix is given. The entire file is included at the end of this msg as well.
- A discussion of cmuscheme.el vs. xscheme.el for Scheme users. 
  Particularly MIT Cscheme users.
===============================================================================

"Why does comint-send-input (return) only send the input text before point"

The way comint-send-input is defined, you can build up several lines of input
(e.g., a complete, multiline lisp defun, or csh script) by using C-n or
linefeed when entering the input text. Now you have text sitting at the end of
the buffer, waiting to be sent to the process. You might want to submit some
or all of it: 1 line, several lines, the whole thing, a partial line. How to
specify how much?  You do that by positioning the cursor at the end of the
text to be sent, and hitting return. So you get control over how much to send.
This feature usually costs nothing, because you enter text sequentially, so
the cursor's at the end of the input text when you're done, anyway.  You have
to pay for it with an extra C-e when you're editting the input text. But in
this case you were issuing editing commands anyway, so it's not so difficult
to just issue the extra C-e.

This is also consistent with the way you submit lisp text when you
hit return in old text (before the process mark): the s-exp ending at
point is resubmitted. So you use the cursor position to indicate the
end of submitted text there, too.

So what we have is a consistent mechanism for sending text: position point at
the end of the text you wish to send, and hit return. The mechanism gives you
a lot of control, and is *invariant across all the process modes* -- Lisp,
Scheme, shell or other.  This is important because it makes it easier to learn
the modes, and keeps you from getting confused if you use more than one.

"Why is filename completion bound to M-tab in cmushell mode?"

M-tab is already completion for gnu-emacs lisp mode (it's bound to
lisp-complete-symbol), so it's a consistent place to put it.  I want to leave
TAB available for indentation commands, which is its usual function.  This
means that if you want filename completion available in the Lisp or Scheme
process modes, you can consistently bind it to the M-tab key, and still have
TAB retain its lisp-indent-line binding.  This way both TAB and M-tab are
consistently bound in all the relevant modes.  Help stamp out cognitive
stress.

===============================================================================

A bug in cmuscheme.el

The M-x scheme command invokes the scheme program with the -emacs flag.  This
is a holdover from code I snarfed from an older T package (which, in turn must
have been snarfed from xscheme.el), and is the *wrong* thing to do.  The
-emacs flag really only has meaning to cscheme, and causes it to do strange
things that cmuscheme can't deal with (see notes below for further
discussion).  The -emacs flag causes oaklisp to abort with an error message. T
ignores it.

The fix is just to change the lines
   (apply 'make-comint (append (list "scheme" scheme-program-name nil)
			       (scheme-args-to-list arg)
			       '("-emacs"))))
to
   (apply 'make-comint (append (list "scheme" scheme-program-name nil)
			       (scheme-args-to-list arg))))
in function scheme in cmuscheme.el. I have appended the fixed file
to this msg to save you even that much work.

===============================================================================

MIT Cscheme, xscheme.el and cmuscheme.el

MIT Cscheme, when invoked with the -emacs flag, has a special user interface
that communicates process state back to the superior emacs by outputting
special control sequences. The gnumacs package, xscheme.el, has lots and lots
of special purpose code to read these control sequences, and so is very
tightly integrated with the cscheme process. The cscheme interrupt handler and
debugger read single character commands in cbreak mode; when this happens,
xscheme.el switches to special keymaps that bind the single letter command
keys to emacs functions that directly send the character to the scheme
process.  Cmuscheme mode does *not* provide this functionality. If you are a
cscheme user, you may prefer to use the xscheme.el/cscheme -emacs interaction.

Here's a summary of the pros and cons, as I see them.
xscheme: Tightly integrated with inferior cscheme process!  A few commands not
         in cmuscheme. But. Integration is a bit of a hack.  Input history
         only keeps the immediately prior input. Bizarre keybindings.

cmuscheme: Not tightly integrated with inferior cscheme process.
           But. Carefully integrated functionality with the entire suite of
	   comint-derived CMU process modes. Keybindings reminiscent of Zwei
	   and Hemlock. Good input history. A few commands not in xscheme.

It's a tradeoff. Pay your money; take your choice. If you use a Scheme
that isn't Cscheme, of course, there isn't a choice. Xscheme.el is *very*
Cscheme-specific; you must use cmuscheme.el.

It would definitely be possible to stick the winning bits of cmuscheme.el into
xscheme.el, or vice-versa. The friendly folks at Cscheme Central may be moved
to do the former (I haven't discussed it with them).  Interested parties are
invited to do the latter.  I am not a Cscheme user, so I won't be doing it
myself.
	-Olin

------ cmuscheme.el ------
;;; cmuscheme.el -- Scheme process in a buffer. Adapted from tea.el.
;;; Copyright Olin Shivers (1988)
;;; Please imagine a long, tedious, legalistic 5-page gnu-style copyright
;;; notice appearing here to the effect that you may use this code any
;;; way you like, as long as you don't charge money for it, remove this
;;; notice, or hold me liable for its results.
;;;
;;;    This is a customisation of comint-mode (see comint.el)
;;;
;;; Written by Olin Shivers (olin.shivers@cs.cmu.edu). With bits and pieces
;;; lifted from scheme.el, shell.el, clisp.el, newclisp.el, cobol.el, et al..
;;; 8/88
;;;
;;; Please send me bug reports, bug fixes, and extensions, so that I can
;;; merge them into the master source.
;;;
;;; Fix log:
;;; Removed -emacs flag from process invocation. It's only useful for
;;;     cscheme, and makes cscheme assume it's running under xscheme.el,
;;;     which messes things up royally. A bug. 2/15/89 Olin
;;;
;;; NOTE: MIT Cscheme, when invoked with the -emacs flag, has a special user
;;; interface that communicates process state back to the superior emacs by
;;; outputting special control sequences. The gnumacs package, xscheme.el, has
;;; lots and lots of special purpose code to read these control sequences, and
;;; so is very tightly integrated with the cscheme process. The cscheme
;;; interrupt handler and debugger read single character commands in cbreak
;;; mode; when this happens, xscheme.el switches to special keymaps that bind
;;; the single letter command keys to emacs functions that directly send the
;;; character to the scheme process.  Cmuscheme mode does *not* provide this
;;; functionality. If you are a cscheme user, you may prefer to use the
;;; xscheme.el/cscheme -emacs interaction.
;;; 
;;; Here's a summary of the pros and cons, as I see them.
;;; xscheme: Tightly integrated with inferior cscheme process!  A few commands
;;;	     not in cmuscheme. But. Integration is a bit of a hack.  Input
;;;	     history only keeps the immediately prior input. Bizarre
;;;	     keybindings.
;;; 
;;; cmuscheme: Not tightly integrated with inferior cscheme process.  But.
;;;            Carefully integrated functionality with the entire suite of
;;;            comint-derived CMU process modes. Keybindings reminiscent of
;;;            Zwei and Hemlock. Good input history. A few commands not in
;;;            xscheme.
;;;  
;;; It's a tradeoff. Pay your money; take your choice. If you use a Scheme
;;; that isn't Cscheme, of course, there isn't a choice. Xscheme.el is *very*
;;; Cscheme-specific; you must use cmuscheme.el.  Interested parties are
;;; invited to port xscheme functionality on top of comint mode...

;; YOUR .EMACS FILE
;;=============================================================================
;; Some suggestions for your .emacs file.
;;
;; ; If cmuscheme lives in some non-standard directory, you must tell emacs
;; ; where to get it. This may or may not be necessary.
;; (setq load-path (cons (expand-file-name "~jones/lib/emacs") load-path))
;;
;; ; Autoload run-scheme from file cmuscheme.el
;; (autoload 'run-scheme "cmuscheme"
;;           "Run an inferior Scheme process."
;;           t)
;;
;; ; Files ending in ".scm" are Scheme source, 
;; ; so put their buffers in scheme-mode.
;; (setq auto-mode-alist 
;;       (cons '("\\.scm$" . scheme-mode)  
;;             auto-mode-alist))
;;
;; ; Define C-c C-t to run my favorite command in inferior scheme mode:
;; (setq cmuscheme-load-hook
;;       '((lambda () (define-key inferior-scheme-mode-map "\C-c\C-t"
;;                                'favorite-cmd))))
;;;
;;; Unfortunately, scheme.el defines run-scheme to autoload from xscheme.el.
;;; This will womp your declaration to autoload run-scheme from cmuscheme.el
;;; if you haven't loaded cmuscheme in before scheme. Two fixes:
;;; - load cmuscheme.el in your .emacs: (load-library 'cmuscheme)
;;; - change autoload declaration in scheme.el to point to cmuscheme.el:
;;;   (autoload 'run-scheme "cmuscheme" "Run an inferior Scheme" t)
;;;   *or* just delete the autoload declaration from scheme.el altogether,
;;;   which will allow the autoload in your .emacs to have its say.

(provide 'cmuscheme)
(require 'scheme)
(require 'comint)

;;; INFERIOR SCHEME MODE STUFF
;;;============================================================================

(defvar inferior-scheme-mode-hook nil
  "*Hook for customising inferior-scheme mode.")
(defvar inferior-scheme-mode-map nil)

(cond ((not inferior-scheme-mode-map)
       (setq inferior-scheme-mode-map
	     (full-copy-sparse-keymap comint-mode-map))
       (define-key scheme-mode-map "\M-\C-x" ;gnu convention
	           'scheme-send-definition)
       (define-key inferior-scheme-mode-map "\C-cl"    'scheme-load-file)
       (define-key inferior-scheme-mode-map "\C-ck"    'scheme-compile-file)
       (scheme-mode-commands inferior-scheme-mode-map))) 

;; Install the process communication commands in the scheme-mode keymap.
(define-key scheme-mode-map "\M-\C-x" 'scheme-send-definition);gnu convention
(define-key scheme-mode-map "\C-ce"    'scheme-send-definition)
(define-key scheme-mode-map "\C-c\C-e" 'scheme-send-definition-and-go)
(define-key scheme-mode-map "\C-cr"    'scheme-send-region)
(define-key scheme-mode-map "\C-c\C-r" 'scheme-send-region-and-go)
(define-key scheme-mode-map "\C-cc"    'scheme-compile-definition)
(define-key scheme-mode-map "\C-c\C-c" 'scheme-compile-definition-and-go)
(define-key scheme-mode-map "\C-cz"    'switch-to-scheme)
(define-key scheme-mode-map "\C-cl"    'scheme-load-file)
(define-key scheme-mode-map "\C-ck"    'scheme-compile-file) ;k for "kompile"

(defun inferior-scheme-mode ()
  "Major mode for interacting with an inferior Scheme process.

The following commands are available:
\\{inferior-scheme-mode-map}

A Scheme process can be fired up with M-x run-scheme.

Customisation: Entry to this mode runs the hooks on comint-mode-hook and
inferior-scheme-mode-hook (in that order).

You can send text to the inferior Scheme process from other buffers containing
Scheme source.  
    switch-to-scheme switches the current buffer to the Scheme process buffer.
    scheme-send-definition sends the current definition to the Scheme process.
    scheme-compile-definition compiles the current definition.
    scheme-send-region sends the current region to the Scheme process.
    scheme-compile-region compiles the current region.

    scheme-send-definition-and-go, scheme-compile-definition-and-go,
        scheme-send-region-and-go, and scheme-compile-region-and-go
        switch to the Scheme process buffer after sending their text.

Commands:
Return after the end of the process' output sends the text from the 
    end of process to point.
Return before the end of the process' output copies the sexp ending at point
    to the end of the process' output, and sends it.
Delete converts tabs to spaces as it moves back.
Tab indents for Scheme; with argument, shifts rest
    of expression rigidly with the current line.
C-M-q does Tab on each line starting within following expression.
Paragraphs are separated only by blank lines.  Semicolons start comments.
If you accidentally suspend your process, use \\[comint-continue-subjob]
to continue it."
  (interactive)
  (comint-mode)
  ;; Customise in inferior-scheme-mode-hook
  (setq comint-prompt-regexp "^[^>]*>+ *") ; OK for cscheme, oaklisp, T,...
  (scheme-mode-variables)
  (setq major-mode 'inferior-scheme-mode)
  (setq mode-name "Inferior Scheme")
  (setq mode-line-process '(": %s"))
  (use-local-map inferior-scheme-mode-map)
  (setq comint-input-filter (function scheme-input-filter))
  (setq comint-input-sentinel (function ignore))
  (setq comint-get-old-input (function scheme-get-old-input))
  (run-hooks 'inferior-scheme-mode-hook))

(defun scheme-input-filter (str)
  "Don't save anything matching inferior-scheme-filter-regexp"
  (not (string-match inferior-scheme-filter-regexp str)))

(defvar inferior-scheme-filter-regexp "\\`\\s *\\S ?\\S ?\\s *\\'"
  "*Input matching this regexp are not saved on the history list.
Defaults to a regexp ignoring all inputs of 0, 1, or 2 letters.")

(defun scheme-get-old-input ()
  "Snarf the sexp ending at point"
  (save-excursion
    (let ((end (point)))
      (backward-sexp)
      (buffer-substring (point) end))))

(defun scheme-args-to-list (string)
  (let ((where (string-match "[ \t]" string)))
    (cond ((null where) (list string))
	  ((not (= where 0))
	   (cons (substring string 0 where)
		 (scheme-args-to-list (substring string (+ 1 where)
						 (length string)))))
	  (t (let ((pos (string-match "[^ \t]" string)))
	       (if (null pos)
		   nil
		 (scheme-args-to-list (substring string pos
						 (length string)))))))))

(defvar scheme-program-name "scheme"
  "*Program invoked by the scheme and run-scheme commands")

(defun scheme (arg)
  "Like run-scheme, except prompts for a command line."
  (interactive "sExtra arguments to Scheme: ")
  (switch-to-buffer
   (apply 'make-comint (append (list "scheme" scheme-program-name nil)
			       (scheme-args-to-list arg))))
  (inferior-scheme-mode))

(defun run-scheme (arg)
  "Run an inferior Scheme process, input and output via buffer *scheme*.
With argument, it asks for a command line.  Take the program name from the
variable scheme-program-name.  Runs the hooks from inferior-scheme-mode-hook
\(after the comint-mode-hook is run).
\(Type \\[describe-mode] in the process buffer for a list of commands.)"
  (interactive "P")
  (if arg (call-interactively 'scheme)
      (scheme "")))

(defun scheme-send-region (start end)
  "Send the current region to the inferior Scheme process."
  (interactive "r")
  (send-region "scheme" start end)
  (send-string "scheme" "\n"))

(defun scheme-send-definition ()
  "Send the current definition to the inferior Scheme process."
  (interactive)
  (save-excursion
   (end-of-defun)
   (let ((end (point)))
     (beginning-of-defun)
     (scheme-send-region (point) end))))

(defvar scheme-compile-exp-command "(compile '%s)"
  "*Template for issuing commands to compile arbitrary Scheme expressions.")

(defun scheme-compile-region (start end)
  "Compile the current region in the inferior Scheme process
\(A BEGIN is wrapped around the region: (BEGIN <region>))"
  (interactive "r")
  (send-string "scheme" (format scheme-compile-exp-command
                                (format "(begin %s)"
				        (buffer-substring start end))))
  (send-string "scheme" "\n"))

(defun scheme-compile-definition ()
  "Compile the current definition in the inferior Scheme process."
  (interactive)
  (save-excursion
   (end-of-defun)
   (let ((end (point)))
     (beginning-of-defun)
     (scheme-compile-region (point) end))))

(defun switch-to-scheme (eob-p)
  "Switch to the *scheme* buffer.
With argument, positions cursor at end of buffer."
  (interactive "P")
  (pop-to-buffer "*scheme*")
  (cond (eob-p
	 (push-mark)
	 (goto-char (point-max)))))

(defun scheme-send-region-and-go (start end)
  "Send the current region to the inferior Scheme process,
and switch to the process buffer."
  (interactive "r")
  (scheme-send-region start end)
  (switch-to-scheme t))

(defun scheme-send-definition-and-go ()
  "Send the current definition to the inferior Scheme, 
and switch to the process buffer."
  (interactive)
  (scheme-send-definition)
  (switch-to-scheme t))

(defun scheme-compile-definition-and-go ()
  "Compile the current definition in the inferior Scheme, 
and switch to the process buffer."
  (interactive)
  (scheme-compile-definition)
  (switch-to-scheme t))

(defun scheme-compile-region-and-go (start end)
  "Compile the current region in the inferior Scheme, 
and switch to the process buffer."
  (interactive "r")
  (scheme-compile-region start end)
  (switch-to-scheme t))

(defvar scheme-source-modes '(scheme-mode)
  "*Used to determine if a buffer contains Scheme source code.
If it's loaded into a buffer that is in one of these major modes, it's
considered a scheme source file by scheme-load-file and scheme-compile-file.
Used by these commands to determine defaults.")

(defvar scheme-prev-l/c-dir/file nil
  "Caches the (directory . file) pair used in the last scheme-load-file or
scheme-compile-file command. Used for determining the default in the 
next one.")

(defun scheme-load-file (file-name)
  "Load a Scheme file into the inferior Scheme process."
  (interactive (comint-get-source "Load Scheme file: " scheme-prev-l/c-dir/file
				  scheme-source-modes t)) ; T because LOAD 
                                                          ; needs an exact name
  (comint-check-source file-name) ; Check to see if buffer needs saved.
  (setq scheme-prev-l/c-dir/file (cons (file-name-directory    file-name)
				       (file-name-nondirectory file-name)))
  (send-string "scheme" (concat "(load \""
				file-name
				"\"\)\n"))
  (switch-to-scheme t))

(defun scheme-compile-file (file-name)
  "Compile a Scheme file in the inferior Scheme process."
  (interactive (comint-get-source "Compile Scheme file: "
				  scheme-prev-l/c-dir/file
				  scheme-source-modes
				  nil)) ; NIL because COMPILE doesn't
                                        ; need an exact name.
  (comint-check-source file-name) ; Check to see if buffer needs saved.
  (setq scheme-prev-l/c-dir/file (cons (file-name-directory    file-name)
				       (file-name-nondirectory file-name)))
  (send-string "scheme" (concat "(compile-file \""
				file-name
				"\"\)\n"))
  (switch-to-scheme t))


;;; Do the user's customisation...

(defvar cmuscheme-load-hook nil
  "This hook is run when cmuscheme is loaded in.
This is a good place to put keybindings.")
	
(run-hooks 'cmuscheme-load-hook)
--