🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: gnu.emacs.bug
Article #99335

Emacs: shell-mode additions: electric filename completion & electric shell command history.

#99335
From: Simon Marshall
Date: Fri, 28 Sep 1990 12:31
848 lines
37503 bytes
In GNU Emacs 18.51.15 of Mon Sep 19 1988 on hullseq (berkeley-unix)
			 (We also have 18.54 to build)

    This is some mail about extensions and `improvements' written for GNU Emacs
I wish to share.  I originally sent this mail to jla@prep (address was given to
me by Len Tower (tower@edu.mit.ai)) in May, but I heard no more and I think
it's possible that it never arrived: a 3rd item was returned "Returned mail:
Cannot send message for 3 days"...

    This is the second of 2 mail shots.  This concerns further work on file
name completion (see previous mail) plus shell command history functions, both
of which appear like electric modes.

    This mail is a much more extensive than the previous, and to keep it down
to a minimum, reference is made to the first mail shot.  There are also
fundamental concepts involved in the suite of (Emacs Lisp) functions.  One set
of functions is an extension of the previous mail shot for file name completion
embodying the principle of Selection-Not-Entry.  The other set is a new set of
functions to include a shell command history also embodying this principle.
The result was the plaguerising of the electric-type modes and the creation of
a new `mode', "electric-shell-mode".

    "electric-shell-mode" is similar to other electric modes, and is the mode
used in the displaying of the file name completion buffer, for selection of a
file name, and the shell's command history buffer, for selection of a previous
command (cf electric-command-history).  They share a mode for compactness,
simplicity and consistency; they share actions: selection, selection and
execution (selection and immediate "shell-send-input"), and quit.

    A summary of the functions are given, some comments made, and the functions
themselves are given at the end of the mail.

    Electric shell mode:

    "electric-shell-mode".  This new function is equivalent to the Emacs
function "Command-history-setup", and sets the buffer to use the correct keymap
"electric-shell-map", syntax table, clearing local variables etc.  If non-nil,
it runs the value of the new variable "electric-shell-hook".  The prompt is the
value of the new function "electric-shell-prompt".

    "electric-shell-map".  This new variable is the keymap for the new mode
"electric-shell-mode".  By default:
	"q" & "C-c C-c" are bound to "Electric-shell-quit",
	"c" & " " are bound to "Electric-shell-copy" and
	"x" & "RET" are bound to "Electric-shell-execute".
    These are new functions for "electric-shell-mode".  They return nil (quit)
or a cons cell of the form (action . text), where action is `copy' or `execute'
and text is the text on that line: a command or file name.

    Electric file name completion: (extensions to the first mail shot)

    "shell-complete-file-name".  This is almost identical to the last mail
shot, except that to display a buffer of possible completions it calls the new
function "electric-list-file-name-completions".  As before, it should be bound
to a key in "shell-mode-map".

    "electric-list-file-name-completions".  This new function is similar to
"electric-command-history", it is the main electric loop (for file name
completion) and uses Emacs functions to electrically pop up a buffer and calls
the new function "list-file-name-completions" to insert in this buffer.  The
buffer is left in the new mode "electric-shell-mode".  The Emacs function
"Electric-command-loop" is used to control the buffer.  If a selection is made
from this buffer it is performed in the buffer where this function was invoked.

    "list-file-name-completions".  This new function is equivalent to
"display-completions" from the previous mail shot.  It inserts the possible
completions and also filters them according to the new function
"list-shell-completions-filter".

    "list-shell-completions-filter".  This new function (cf. Emacs'
"list-command-history-filter") defaults to the new function
"default-list-shell-completions-filter" which matches its argument with the new
regexp variable "default-list-shell-completions-filter-garbage" which initially
matches "./" and "../".  A user may also want it to match "*~" etc.

    Electric shell command history:

    "electric-list-shell-command-history".  This new function is equivalent to
Emacs' "electric-command-history" and should be bound to a key in
"shell-mode-map" (eg. C-c C-h).  It is the main electric loop (for shell
command history) and uses Emacs functions to electrically pop up a buffer and
calls the new function "list-shell-command-history" to insert in this buffer.
The buffer is left in the new mode "electric-shell-mode".  The Emacs function
"Electric-command-loop" is used to control the buffer.  If a selection is made
from this buffer it is performed in the buffer where this function was invoked.

    "list-shell-command-history".  This new function is equivalent to Emacs'
"list-command-history".  It inserts the commands from the new variable
"shell-command-history".  (They are not filtered here.)  The maximum number is
controlled by the new variable "list-shell-command-history-max", (cf. Emacs'
"list-command-history-max").

    "shell-command-history".  This is a new local variable which is a list of
commands.  It may be initialised by the new function "read-history-file" and is
added to by the modified "shell-send-input".

    "read-history-file".  This new function reads the user's ~/.history file
(if it can) and initialises the new variable "shell-command-history".  It
filters according to the new function "shell-command-history-filter" and also
removes repeated commands using the function "member-string" (which is pirated
from <EMACS>/lisp/blackbox.el - the function called "bb-member").

    "shell-command-history-filter".  This new function if non-nil (cf. Emacs'
"list-command-history-filter") is the name of a function of one argument, and
is bound to the new function "default-shell-command-history-filter".  This new
function (cf. Emacs' "default-command-history-filter") matches its argument to
the value of the new variable "default-shell-command-history-filter-garbage"
(also cf. Emacs' "default-command-history-filter-garbage") which by default
matches any command less than 3 chars long.

    "buffer-line".  This new function is used in various places to get the text
between the 2 args if given, else the whole line at point, minus leading and
trailing whitespaces in the buffer.

    Modified existing Emacs functions:

    "shell-set-directory", "filename-at-point".  These are as stated in the
previous mail shot.

    "shell".  This has been extended to read the user's ~/.history file using
the new function "read-history-file" if there are none for this buffer (the new
variable "shell-command-history" is nil), and also prints Emacs' opinion of the
buffer's working directory.

    "shell-send-input".  This function has an extension to try and get the text
processed by the shell (note: not just the command) by calling the new function
"last-shell-input", and add it to the list of commands if it does not fail the
filter "shell-command-history-filter".  Repeated commands are also removed.
Filtering is done here for efficiency: it spreads the load so it's not done on
display, and the new variable "shell-command-history" doesn't grow full of
never to be displayed commands.

    "last-shell-input".  This new function trys to get the text processed by
the shell.  The use of the editing `^' or history `!' mechanisms must be
recognised and the actual command (in this case) found.  If errors are detected
that match the function's regexp, the command is ignored.

    "save-buffers-kill-emacs".  This has also been extended, but it is not
necessary to any of the above.  It was included as I wished Emacs' shells to
control the ~/.history file, and not the login shell, since I don't use it for
work.  It makes all sub-processes see eofs, so that if you've something like:
	"set history=49; set savehist=49"
  in your ~/.cshrc, but put:
	"unset history; unset savehist"
  in your ~/.logout file, then the Emacs shells will write to the history file,
but the login shell won't.  Emacs will not ask for permission to kill the
shells.  This could of course be made optional if it were included...

    Notes:

    The advantage of the electrified version of file name completion over the
version given in the previous mail shot may be debatable or may depend on the
individual.  It still does not make any attempt to selectively expand depending
on its place in the arg list, or any of the other points mentioned in the first
mail shot.

    It is hard to get the actual text parsed by the shell (the new function
"last-shell-input") and this may be a little too hack-like for you.  You may
come up with a better way.  How about forcing the shell to repeat what it's
about to process and then removing that from the buffer?

    The modified function "shell" has to do more work than seems necessary.
This is because "make-shell" contains "(shell-mode)" which in turn contains
"(kill-all-local-variables)", thus re-setting (to its default value) the new
variable "shell-command-history".  Since we want to keep it (the user may type
`M-x shell', or a key "shell" is bound to, whenever (s)he wants a shell rather
than switching to the shell buffer), the effort must be made.

    Some of these functions are UN*X dependent, especially "read-history-file"
and "last-shell-input".

    Is it necessary to have "(put 'electric-shell-mode 'mode-class 'special)"
which has been commented out in the list of functions below?

    Alsos as in previous mail shot.

    The reason, if your suspicions haven't already been aroused, that I have
sent both sets of mail, the first on file name completion, the second on
electrified file name completion and command history, is that the electrified
functions are quite large, and you may feel too large compared with their
usefulness.  I have felt myself that the file name completion (from the first
mail shot) is small and useful enough to be of the sort included in
<EMACS>/lisp/shell.el, but the electrified functions may not be, but put in
something like <EMACS>/lisp/eshell.el, as with Emacs' interactive command
history and electric command history.  You may also think that certain aspects
of the functions should be changed, and I'd be happy to implement any that you
suggest.

    A comment that I'd like to make is, could all electric modes could be
somehow replaced by one, in the interests of compactness, simplicity and
consistency?  All electric modes are similar, they are used for selection, plus
some other application-dependent functions such as "Buffer-menu-mark" in
"Electric-buffer-menu-mode".  It would have been preferable to use a basic
electric mode rather than adding a new mode.  Examples of inconsistencies can
be seen in "electric-buffer-list" and "electric-command-history".  In the
first, typing `space' immediately causes an exit, but not in the second.  In
the first, `p' or `n' moves up or down, but not in the second: only C-p or C-n
works...

    Thanks again for taking the time to go through this stuff.  I hope that
it's of use, and I'd be greatful if you'd let me know the outcome, or of any
suggestions, or if I could be of any further help.

	Simon Marshall,			(ARPA:	sm@cs.hull.ac.uk
	Dept. Computer Science,		 JANET:	sm@uk.ac.hull.cs
	Hull University,		 UUCP:	..!ukc!hu-cs!sm)
	Hull	HU6 7RX
	ENGLAND

    The functions follow.  To use them, all you should need to do is invoke an
Emacs shell as normal (eg. M-x shell) and then load a file containing the
following functions. ie.  These new functions should be merged with the old.
Typing whatever key "shell-complete-file-name" is bound to will attempt to
complete the preceding string as a file name, ditto with displaying the shell's
history with "electric-list-shell-command-history".

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;; Shell extensions for subshells running under Emacs.
;; Electric file name completion and command history.

;; Simon Marshall, 1989, 1990.
;; Suggestions, comments, improvements, critiques, abuse to sm@cs.hull.ac.uk

;; This file is in addition to GNU Emacs.


(require 'electric)

(define-key shell-mode-map "\M-\C-i" 'shell-complete-file-name)
(define-key shell-mode-map "\C-c\C-f" 'shell-complete-file-name)
(define-key shell-mode-map "\C-c\C-h" 'electric-list-shell-command-history)

;(put 'electric-shell-mode 'mode-class 'special)   what's it for???


(defun electric-shell-mode (modename)
  "Major mode for selecting and redoing shell commands or file names.

Used by the shell's Electric History and Electric Completions buffers.
Commands or filenames can be selected; copied or executed in the buffer from
where  electric-list-shell-command-history  or  shell-complete-file-name  was
invoked.
Letters do not insert themselves; instead, they are commands.
\\{electric-shell-map}
Calls the value of  electric-shell-hook  if that is non-nil."
  (kill-all-local-variables)
  (use-local-map electric-shell-map)
  (set-syntax-table text-mode-syntax-table)
  (setq buffer-read-only t
	major-mode 'electric-shell-mode
	mode-name modename)
  (run-hooks 'electric-shell-hook)
  )



; Shell completion functions.


(defun shell-complete-file-name ()
  "Perform file name completion on the string preceding point.
If the file name is not absolute or relative, the list of paths in the variable
 exec-path  are used to search for a completion - this is the value of the
shell environment variable  $PATH  plus  exec-directory .

The start of the file name is matched using the value of the variable
 file-name-delimiters .  The shell prompt must end with a character which
is also in this string."
  (interactive)
  (let* ((stub-end	(point))
	 (stub-start 	(save-excursion
			  (skip-chars-backward file-name-delimiters)
			  (point)))
	 (stub		(buffer-substring stub-start stub-end))
	 (stub-file	(file-name-nondirectory stub))
	 (stub-dir	(file-name-directory stub))
	 (paths		(if stub-dir
			    (list (substitute-in-file-name stub-dir))
			  exec-path))
	 path
	 completed)
	;;; Got the stub file name string and a list of path(s) to try.
	;;; Go through them...
    (while (and (not completed)
		paths)
      (setq path	(expand-file-name (car paths))
	    completed	(file-name-completion stub-file path))
      (cond ((eq completed t)				; Complete as could be.
	     (message "Already complete in %s" path))
	    ((not completed)				; Nope, try another.
	     (setq paths (cdr paths)))
	    ((not (string= completed stub-file))	; [Part] Completed it.
	     (if (not (symbolp (file-name-completion completed path)))
		 (electric-list-file-name-completions completed path)
	       (backward-delete-char (length stub-file))
	       (insert completed)			; Complete as could be.
	       (message "Completed in %s" path)))
	    (t						; Stub nonunique, list.
	     (electric-list-file-name-completions completed path))))
    (if (not completed)
	(message "Can't find completion for \"%s\" in %s" stub-file
		 (if stub-dir
		     (expand-file-name (concat default-directory stub-dir))
		   (mapcar 'expand-file-name exec-path)) (beep)))
    ))



(defun electric-list-file-name-completions (stub path)
  "Display list of completions with stub STUB in PATH for selection.
The file names are filtered by  list-shell-completions-filter  if non-nil.

This pops up a window with the File Name Completions listing.  You can browse
through the list, selecting the required file name to be copied or re-executed.

See  electric-shell-mode ."
  (interactive)
  (let ((window-min-height 3)
	(Helper-return-blurb "return to the Completions list")
	selection)
    (save-window-excursion
      (unwind-protect
	  (setq selection
		(catch 'electric-shell-select
		  (save-window-excursion (list-file-name-completions stub path))
		  (Electric-pop-up-window "*Completions*")
		  (electric-shell-mode "Electric Completions")
		  (Electric-command-loop 'electric-shell-select
					 'electric-shell-prompt t)))
	(bury-buffer)
	(message "")))
    (if selection
	(progn (if (get-buffer-process (current-buffer))	; It's a shell?
		   (goto-char (point-max)))
	       (backward-delete-char (length stub-file))
	       (insert (cdr selection))
	       (if (eq (car selection) 'execute)
		   (shell-send-input))))))



(defun list-file-name-completions (stub path)
  "Display in a buffer completions with stub file name STUB in PATH.
The file names are filtered by  list-shell-completions-filter  if non-nil."
  (message "Making completion list in %s..." path)
  (with-output-to-temp-buffer "*Completions*"
    (let ((completions (sort (file-name-all-completions stub path) 'string<))
	  completion)
      (while completions
	(setq completion (car completions)
	      completions (cdr completions))
	(if (and list-shell-completions-filter
		 (funcall list-shell-completions-filter completion))
	    nil
	  (princ completion)
	  (terpri)))))
  (message "Making completion list in %s...done" path))



(defconst file-name-delimiters "^ ;<>|'`()\"\t\n"
  "*String used by file name completion command to match file names.
The string (regexp) -must- start with \"^\".  A file name will -not- be
recognised if it contains a character in this string.")



(defvar list-shell-completions-filter 'default-list-shell-completions-filter
  "If non-nil, should be the name of a function of one argument.
It is passed each file name when \\[shell-complete-file-name] displays a list.
If the filter returns non-nil for some element, that element is excluded from
the file name listing.  The default filter removes \"./\" and \"../\".")


(defun default-list-shell-completions-filter (frob)
  "Filter commands matching  default-list-shell-completions-filter-garbage
regexp from the file name listing."
  (string-match default-list-shell-completions-filter-garbage frob))


(defvar default-list-shell-completions-filter-garbage "^\\.\\.?/$"
  "*A regexp.  If  default-list-shell-completions-filter  is given a string
which matches this regexp, then it will return non-nil (indicating that the
file name should be discarded from the list).  Initially, the files \"./\" and
\"../\" are discarded.
To add other regexps, concat the extra regexp and \"\\\\|\" onto this regexp.")



; electric shell command history

(defun electric-list-shell-command-history ()
  "Display list of previous shell commands for selecting and redoing.
The number of commands from  shell-command-history  listed is controlled by
list-command-history-max.  The command history is filtered on reading from
~/.history and saving by  shell-send-input  by  shell-command-history-filter
if non-nil.

This pops up a window with the Shell Command History listing.  You can browse
through the list, selecting a command to be copied or re-executed.

See  electric-shell-mode ."
  (interactive)
  (let ((window-min-height 2)
	(Helper-return-blurb "return to the History list.")
	selection)
    (save-window-excursion
      (unwind-protect
	  (setq selection
		(catch 'electric-shell-select
		  (save-window-excursion (list-shell-command-history))
		  (Electric-pop-up-window "*Shell History*")
		  (electric-shell-mode "Electric History")
		  (if (not (eobp))
		      (Electric-command-loop 'electric-shell-select
					     'electric-shell-prompt t)
		    (message "No shell command history")
		    (throw 'electric-shell-select nil))))
	(bury-buffer)
	(message "")))
    (if selection
	(progn (if (get-buffer-process (current-buffer))	; It's a shell?
		   (goto-char (point-max)))
	       (insert (cdr selection))
	       (if (eq (car selection) 'execute)
		   (shell-send-input))))))



(defun list-shell-command-history ()
  "List history of commands typed to a shell buffer.
The number of commands listed is controlled by  list-shell-command-history-max.
The commands are selectively saved, see  shell-send-input ."
  (interactive)
  (message "Making shell command history list...")
  (with-output-to-temp-buffer "*Shell History*"
    (let ((history shell-command-history)
	  (count (or list-shell-command-history-max -1)))
      (while (and (/= count 0)
		  history)
	(princ (car history))
	(terpri)
	(setq history (cdr history)
	      count (1- count)))))
  (message "Making shell command history list...done"))



(defvar electric-shell-map nil
  "Local keymap for  electric-shell-mode  buffers.")

(if electric-shell-map
    nil
  (setq electric-shell-map (make-keymap))
  (fillarray electric-shell-map 'Electric-shell-undefined)
  (define-key electric-shell-map "\e" (make-keymap))
  (fillarray (lookup-key electric-shell-map "\e") 'Electric-shell-undefined)
  (define-key electric-shell-map "\C-u" 'universal-argument)
  (define-key electric-shell-map " " 'Electric-shell-copy)
  (define-key electric-shell-map "c" 'Electric-shell-copy)
  (define-key electric-shell-map "q" 'Electric-shell-quit)
  (define-key electric-shell-map "x" 'Electric-shell-execute)
  (define-key electric-shell-map "\C-m" 'Electric-shell-execute)
  (define-key electric-shell-map "\C-c" nil)
  (define-key electric-shell-map "\C-c\C-c" 'Electric-shell-quit)
  (define-key electric-shell-map "\C-]" 'Electric-shell-quit)
  (define-key electric-shell-map "\C-z" 'suspend-emacs)
  (define-key electric-shell-map "\C-h" 'Helper-help)
  (define-key electric-shell-map "?" 'Helper-describe-bindings)
  (define-key electric-shell-map "\e>" 'end-of-buffer)
  (define-key electric-shell-map "\e<" 'beginning-of-buffer)
  (define-key electric-shell-map "n" 'next-line)
  (define-key electric-shell-map "p" 'previous-line)
  (define-key electric-shell-map "\177" 'previous-line)  
  (define-key electric-shell-map "\C-n" 'next-line)
  (define-key electric-shell-map "\C-p" 'previous-line)
  (define-key electric-shell-map "\ev" 'scroll-down)
  (define-key electric-shell-map "\C-v" 'scroll-up)
  (define-key electric-shell-map "\C-l" 'recenter)
  (define-key electric-shell-map "\e\C-v" 'scroll-other-window)
  )



(defvar electric-shell-hook nil
  "If non-nil, its value is called on entry to  electric-shell-mode .")



(defvar shell-command-history nil
  "List of recent commands typed to a shell buffer.
They can be selected by \\[electric-list-shell-command-history].
They are initialised from user's ~/.history file if it exists and selectively
saved by  shell-send-input .")

(make-variable-buffer-local 'shell-command-history)



(defvar list-shell-command-history-max 32
  "*If non-nil, should be a positive number which
specifies the maximum length of the Shell Command History listing produced by
 list-shell-command-history .")


(defvar shell-command-history-filter 'default-shell-command-history-filter
  "If non-nil, should be the name of a function of one argument.
It is used to test whether the argument should be included in the buffer's
 shell-command-history .  If the filter returns non-nil for some element,
that element is excluded from the history.  The default filter removes
commands less than 3 characters long.")


(defun default-shell-command-history-filter (frob)
  "Filter commands matching  default-shell-command-history-filter-garbage
regexp from the shell command history."
  (string-match default-shell-command-history-filter-garbage frob))


(defvar default-shell-command-history-filter-garbage "^.?.?$"
  "*A regexp.  If  default-shell-command-history-filter  is
given a string which matches this regexp, then it will return non-nil
(indicating that the command should be discarded from the shell history).
Initially, all commands less than 3 characters long are discarded.
To add other regexps, concat the extra regexp and \"\\\\|\" onto this regexp.")



(defun electric-shell-prompt ()
  "Returns a string used as a prompt in  electric-shell-mode ."
  (if (bobp)
      (Electric-shell-undefined t)
    (if (eq (key-binding "\C-h") 'Helper-help)
	(concat "Type C-h for help.  <" (buffer-line) ">  ")
      (concat (substitute-command-keys "Type \\[Helper-help] for help.  <")
	      (buffer-line) ">  "))))



(defun Electric-shell-quit ()
  "Leave Electric Shell listing, restoring previous window configuration."
  (interactive)
  (throw 'electric-shell-select nil))


(defun Electric-shell-copy ()
  "Leave Electric Shell listing, selecting the command to copy."
  (interactive)
  (throw 'electric-shell-select (cons 'copy (buffer-line))))


(defun Electric-shell-execute ()
  "Leave Electric Shell listing, selecting the command to execute."
  (interactive)
  (throw 'electric-shell-select (cons 'execute (buffer-line))))


(defun Electric-shell-undefined (&optional silent)
  (interactive)
  (if (not silent)
      (beep))
  (message (if (and (eq (key-binding "q") 'Electric-shell-quit)
		    (eq (key-binding " ") 'Electric-shell-copy)
		    (eq (key-binding "\C-m") 'Electric-shell-execute)
		    (eq (key-binding "\C-h") 'Helper-help)
		    (eq (key-binding "?") 'Helper-describe-bindings))
	       "Type C-h for help, ? for commands, q to quit, SPC (RET) to copy (& execute)."
	     (substitute-command-keys "Type \\[Helper-help] for help, \\[Helper-describe-bindings] for commands, \\[Electric-shell-quit] to quit, \\[Electric-shell-copy] (\\[Electric-shell-execute]) to copy (& execute)."))))



(defun read-history-file ()
  "Read the user's ~/.history file if it exists.
Each command is added to  shell-command-history  if the filter function
shell-command-history-filter  returns non-nil and the command is not repeated
later in the file."
  (if (file-attributes (expand-file-name "~/.history"))
      (let (commands command)
	(message "Reading ~/.history...")
	(find-file (expand-file-name "~/.history"))
	(set-buffer ".history")
	(goto-char (point-min))
	(while (not (eobp))
	  (setq commands (cons (buffer-line) commands))
	  (next-line 1))
	(kill-buffer nil)
      ;;; Got them all.  Now discard multiple occurrences and those failing
      ;;;	the filter.
	(while commands
	  (setq command (car commands)
		commands (cdr commands))
	  (if (not (or (member-string command shell-command-history)
		       (and shell-command-history-filter
			    (funcall shell-command-history-filter command))))
	      (setq shell-command-history (cons command shell-command-history))))
	(setq shell-command-history (nreverse shell-command-history))
	(message "Reading ~/.history...done"))))



(defun buffer-line (&optional start end)
  "Return the buffer's line, from START to END if given.  Skips over
leading and trailing whitespaces."
  (save-excursion (buffer-substring (progn (if start
					       (goto-char start)
					     (beginning-of-line))
					   (skip-chars-forward " \t")
					   (point))
				    (progn (if end
					       (goto-char end)
					     (end-of-line))
					   (skip-chars-backward " \t")
					   (point)))))



(defun member-string (elt list)
  "Returns non-nil if ELT is an element of LIST.  Comparison done with equal.
Taken from  blackbox.el  (bb-member)."
  (eval (cons 'or (mapcar (function (lambda (x) (equal x elt))) list))))



;; Modified shell, shell-send-input, shell-set-directory and
;;	save-buffers-kill-emacs.
;; shell reads ~/.history if it doesn't have any history commands and pwd.
;; shell-set-directory parses the whole line, rather than just looking at the
;;	word starting at the first char.
;; shell-send-input saves and saves the	command in shell-command-history if it
;;	passes a filter test.  removes previous occurence.
;; save-buffers-kill-emacs sends eof to processes (ie. including shells)
;;	so command history may be done by emacs' shell and not login shell.

;; shell-set-directory currently:
;; - Parses the i/t line for shell-cd/popd/pushd-regexp & attempts to set
;;	buffer's dir[-stack].
;; - Quits further parsing if fails to set.  eg. if "pushd nosuchfile",
;;	"pop file" etc.



(defun shell ()
  "Run an inferior shell, with I/O through buffer *shell*.
If buffer exists but shell process is not running, make new shell.
Program used comes from variable explicit-shell-file-name,
 or (if that is nil) from the ESHELL environment variable,
 or else from SHELL if there is no ESHELL.
If a file ~/.emacs_SHELLNAME exists, it is given as initial input
 (Note that this may lose due to a timing error if the shell
  discards input when it starts up.)
The buffer is put in shell-mode, giving commands for sending input
and controlling the subjobs of the shell.  See shell-mode.
See also variable shell-prompt-pattern.

The shell file name (sans directories) is used to make a symbol name
such as `explicit-csh-arguments'.  If that symbol is a variable,
its value is used as a list of arguments when invoking the shell.
Otherwise, one argument `-i' is passed to the shell.

Note that many people's .cshrc files unconditionally clear the prompt.
If yours does, you will probably want to change it."
  (interactive)
  (let* ((prog (or explicit-shell-file-name
		   (getenv "ESHELL")
		   (getenv "SHELL")
		   "/bin/sh"))		     
	 (name (file-name-nondirectory prog))
	;;; Rest of "let" varlist is an sm addition to keep record of
	;;;	shell-command-history, shell-mode clears it to default nil.
	 (buffer-has-shell-command-history shell-command-history))
    (switch-to-buffer
     (apply 'make-shell "shell" prog
	    (if (file-exists-p (concat "~/.emacs_" name))
		(concat "~/.emacs_" name))
	    (let ((symbol (intern-soft (concat "explicit-" name "-args"))))
	      (if (and symbol (boundp symbol))
		  (symbol-value symbol)
		'("-i")))))
	;;; Rest of "let" body is an sm addition to get command history and
  	;;; 	print the directory Emacs thinks we're in.
    (if buffer-has-shell-command-history
	(setq shell-command-history buffer-has-shell-command-history)
      (read-history-file))
    (pwd)))



(defun shell-set-directory ()
  "An extended version of the one supplied with Emacs.
It attempts to parse the whole line, rather than the command starting at the
first character, looking for cd/pushd/popd commands to keep Emacs' opinion of
the buffer's current directory (\"default-directory\") accurate.  Stops parsing
when an \"error\" in a command is detected: cd/pushd no-such-directory,
popd directory etc.  It can still be fooled when interpretation by the shell
fails although no problem was detected during parsing.

The variables \"shell-cd/pushd/popd-regexp\" -must- be set if any of these
commands are aliased in the shell."
  (let (change-dir
	change-error)
    (while (not (or change-error
		    (eolp)))
      (skip-chars-forward " \t;\"\'")
      (cond ((looking-at shell-cd-regexp)	; Is it 'cd'?
	     (goto-char (match-end 0))
	     (skip-chars-forward " \t\'\"\`")
	     (cond ((looking-at "[;\n]")	; Is there a [valid] directory?
		    (cd (getenv "HOME")))
		   ((file-directory-p (setq change-dir (filename-at-point)))
		    (cd change-dir))
		   (t
		    (setq change-error t))))
	    ((looking-at shell-pushd-regexp)	; Is it 'pushd'?
	     (goto-char (match-end 0))
	     (skip-chars-forward " \t\'\"\`")
	     (cond ((looking-at "[;\n]")	; Is there a [valid] directory?
		    (setq change-error t))
		   ((file-directory-p (setq change-dir (filename-at-point)))
		    (setq shell-directory-stack (cons default-directory
						      shell-directory-stack))
		    (cd change-dir))
		   (t
		    (setq change-error t))))
	    ((looking-at shell-popd-regexp)	; Is it 'popd'?
	     (goto-char (match-end 0))
	     (skip-chars-forward " \t")
	     (if (looking-at "[;\n]")		; Has it an argument?
		 (if (not shell-directory-stack)
		     (setq change-error t)
		   (cd (car shell-directory-stack))
		   (setq shell-directory-stack (cdr shell-directory-stack)))
	       (setq change-error t)))
	    (t					; Find next command...
	     (skip-chars-forward "^;")))
      )))



(defun filename-at-point ()
  "Return the file name after point.
Also expands and substitutes in the file name and leaves point after it.
The end of the file name is matched using the value of the variable
 file-name-delimiters ."
  (expand-file-name
   (substitute-in-file-name
    (buffer-substring (point)
		      (save-excursion (skip-chars-forward file-name-delimiters)
				      (point))))))
  


(defun shell-send-input ()
  "Send input to subshell.
At end of buffer, sends all text after last output
 as input to the subshell, including a newline inserted at the end.
When not at end, copies current line to the end of the buffer and sends it,
after first attempting to discard any prompt at the beginning of the line
by matching the regexp that is the value of shell-prompt-pattern if possible.
This regexp should start with \"^\".

An extended version of the one supplied with Emacs.  Also saves the command
in  shell-command-history  if it survives  shell-command-history-filter .
Previous occurences are removed."
  (interactive)
  (or (get-buffer-process (current-buffer))
      (error "Current buffer has no process"))
  (end-of-line)
  (if (eobp)
      (progn
	(move-marker last-input-start
		     (process-mark (get-buffer-process (current-buffer))))
	(insert ?\n)
	(move-marker last-input-end (point)))
    (beginning-of-line)
    (re-search-forward shell-prompt-pattern nil t)
    (let ((copy (buffer-substring (point)
				  (progn (forward-line 1) (point)))))
      (goto-char (point-max))
      (move-marker last-input-start (point))
      (insert copy)
      (move-marker last-input-end (point))))
  ;; Even if we get an error trying to hack the working directory,
  ;; still send the input to the subshell.
  (condition-case ()
      (save-excursion
	(goto-char last-input-start)
	(shell-set-directory))
    (error (funcall shell-set-directory-error-hook)))
  (let ((process (get-buffer-process (current-buffer))))
    (process-send-region process last-input-start last-input-end)
    (set-marker (process-mark process) (point)))
	;;; "let" body is an sm addition to save the command for selection
	;;;	by electric-list-shell-command-history.
  (let ((command (last-shell-input)))
    (if (and shell-command-history-filter
	     (funcall shell-command-history-filter command))
	nil
	;;; Before adding the command, remove any repetitions from the history.
      (if (member-string command shell-command-history)
	  (let (tmp)
	    (while shell-command-history
	      (if (not (string= command (car shell-command-history)))
		  (setq tmp (cons (car shell-command-history) tmp)))
	      (setq shell-command-history (cdr shell-command-history)))
	    (setq shell-command-history (nreverse tmp))))
      (setq shell-command-history (cons command shell-command-history)))))



(defun last-shell-input ()
  "Return the last input to the shell.
Also attempts to find the expansion when csh substitution stuff is used."
  (sit-for 1)				; wait for shell reply.
  (let (input)
    (save-excursion
      (goto-char last-input-start)
      (while (string-match "^![^ \t\n]\\|[^\\]![^ \t\n]\\|^\\^\\|[^\\]^"
			   (setq input (buffer-line (point))))
	(beginning-of-line 2))		; skip substitution stuff.
      (if (string-match "Bad.*selector\\|Bad.*substitute\\|Modifier.*failed\\|Event.*not.*found\\|Variable.*Syntax"
			input)
	  ""
	input))))



				
(defun save-buffers-kill-emacs (&optional arg)
  "Offer to save each buffer, then kill this Emacs fork.
With prefix arg, silently save all file-visiting buffers, then kill.

An extended version of the one supplied with Emacs.  Sends an eof to all
sub-processes ie. including sub-shells, enabling them to save the command
history if this is done by a shell.  If an Emacs shell is the only shell used
for work, then \"unset history; unset savehist\" can be put in your ~/.logout
file to prevent your ~/.history file from being overwritten on logout from the
login shell.  Emacs will not ask for permission to kill the sub-processes."
  (interactive "P")
  (save-some-buffers arg t)
	;;; The "let" body is an sm addition to send eof to *shell*.
  	;;; Rest of code is as before.
  (let ((procs (process-list)))
    (while procs
      (unwind-protect (process-send-eof (car procs))
	(setq procs (cdr procs))))
    (sleep-for 1))			; Is this enough?
  (kill-emacs))
Technical Details
Message-ID: <7342.9009281031@uk.ac.hull.cc.sequent>
Path: arc1-ca.pugleaf.net!gmdzi!unido!mcsun!uunet!snorkelwacker!tut.cis.ohio-state.edu!sequent.cc.hull.ac.uk!sm2

Show Full Headers
Complete Article Headers
X-Google-Language: ENGLISH,ASCII-7-bit
X-Google-Thread: fb2f1,8764803c1f00ee93,start
X-Google-Attributes: gidfb2f1,public
X-Google-ArrivalTime: 1990-09-29 00:48:28 PST
Path: arc1-ca.pugleaf.net!gmdzi!unido!mcsun!uunet!snorkelwacker!tut.cis.ohio-state.edu!sequent.cc.hull.ac.uk!sm2
From: sm2@sequent.cc.hull.ac.uk (Simon Marshall)
Newsgroups: gnu.emacs.bug
Subject: Emacs: shell-mode additions: electric filename completion & electric 
         shell command history.
Message-ID: <7342.9009281031@uk.ac.hull.cc.sequent>
Date: 28 Sep 90 12:31:21 GMT
Sender: daemon@tut.cis.ohio-state.edu
Distribution: gnu
Organization: GNUs Not Usenet
Lines: 848
Posted: Fri Sep 28 13:31:21 1990