Article View: gnu.emacs.bug
Article #99337Emacs: shell-mode additions: file name completion.
From: Simon Marshall
Date: Fri, 28 Sep 1990 12:30
Date: Fri, 28 Sep 1990 12:30
359 lines
16797 bytes
16797 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 first of 2 mail shots. This concerns file name completion
only. The second concerns further work on file name completion plus shell
command history, both of which appear like electric modes.
This contains some new Emacs Lisp functions for extending the shell-mode to
include file name completion in the same manner as `tsh' (csh + file name
completion) does on UN*X systems. This includes some re-written/extended
existing Emacs functions; they were written as I wished to use Emacs' shells
rather than the login shell to work in, since it has much more to offer in
(all?) other areas, as an editor...
A summary of the functions are given, some comments made, and the functions
themselves are given at the end of the mail.
File name completion:
"shell-complete-file-name". This is the main function. It attempts to
expand the file name preceding the point, and displays possible completions for
ambiguous names. It should be bound to a key in "shell-mode-map" (eg. M-TAB, a
la "lisp-complete-symbol", or C-c C-f). It picks out the preceding string as a
file name using the new variable "file-name-delimiters". Completion is
attempted, if there is no absolute or relative path given, in the paths
specified in "exec-path" (which originates from the environment variable
"$PATH"). Note that this means commands can be expanded. If there is more
than one possible completion in any directory, then the file name is completed
as far as possible, and a list of completions is shown in a temp buffer.
"display-completions". This is the new function used to display a list of
completions in temp buffer. It saves the current window configuration in the
new variable "saved-window-configuration", if this has not already been done
previously (since the last "shell-send-input"), so that the temp buffer can be
removed.
"file-name-delimiters". This new variable is a string of chars deemed to
be delimiters for file names: they are assumed never to occur in file names
themselves (it is a regexp starting with "^"). It used to pick out the file
name preceding the point. Point is skipped backwards stopping after a char in
this variable, and this is assumed to be the start of the file name.
"saved-window-configuration". This new variable (if non-nil) is a window
configuration with which the screen is restored to on execution of the extended
"shell-send-input" function. It is the window configuration before the
displaying of a temp buffer. This is so that once the input line has been
sent, any temp completions buffer present will disappear.
These are the only new functions (2) and new variables (2) (both of which
use existing Emacs functions to the full).
Modified existing Emacs functions:
"shell-set-directory". This function is re-written to pick out changes of
directory more accurately, as the original was not particularly thorough: it's
fooled by a leading space, for example. This was necessary so that the
variable "default-directory" is kept accurate and completion (when not
absolute) is attempted in the correct directory. The function attempts to
parse each command on the input line for cd/popd/pushds, and attempt to
discover whether they would succeed. It is still possible to trick it, and I'm
sure a better function could still be devised. (Is there some way of
interrogating the shell without the user noticing, for instance? Tagging
";pwd" on the end and removing the reply from the buffer?).
"filename-at-point". This is a new function, but is only used by the
improved "shell-set-directory" function. It returns the file name after point
using the new variable "file-name-delimiters" in a similar way as in the new
"shell-complete-file-name" function.
"shell-send-input". This function has an extension to restore the display
to the value of the new variable "saved-window-configuration" (if non-nil), to
remove the temp completions buffer.
Notes:
The method of clearing the display of the completions buffer is a little
hack-like. It could not be done at all, like the "describe-*" functions, or
alternatively "save-window-excursion" could be used surrounding
"display-completions" which ended with (sit-for <HUGE>) so that restoring the
display occurs on the next key-press. Neither seems to be as good to use;
getting rid of the completions buffer manually is a pain and having it
disappear on the next key press is unnerving and too soon. At first glance, an
alternative is to use (delete-windows-on <temp buffer>), but this does not
restore the window state exactly; a buffer originally in another window may
disappear. This problem set the desire to use electric-modes, see following
mail message.
There is no attempt to complete depending on whether the file name must be
of a particular mode type. For instance, the first file name on the command
line could be assumed to be executable only. This not necessarily always the
case: input to requests from running commands for example. Arguments to
commands could be assumed to be only from the current directory if not given
absolute or relative. This again is not 100% definite; it is a complex
problem. It may be that you feel these problems are enough to exclude the use
of "exec-path" in the case of non absolute or relative file name completions,
replacing it with "default-directory" and removing the "while" loop from
"shell-complete-file-name".
Unfortunately, I only have access to Emacs on UN*X machines, and so I've
been unable the do the necessary modifications for other types of OS, though I
can't see there being much. The new variable "file-name-delimiters" would be
different for, say VMS files, and the modified "shell-set-directory" may need
to be changed.
Aliases are not expanded. Is this the coup de grais?!
Shell wild cards are not understood, eg. in a directory containing some
files ending with `.output', an attempt at completion of `*.out' would not
work.
Also:
A `bug', at least in release 18.51.15:
(expand-file-name "../../../" "/users/cs/sm2") etc
returns "//" not "/". This results in a visual error only.
If multiple instances of "~[user name]/" are used, the functions will
attempt to complete correctly but the result may not be understood by the
shell; it may only expand the first instance.
Thanks 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;; Shell extensions for subshells running under Emacs.
;; File name completion.
;; Simon Marshall, 1989.
;; Suggestions, comments, improvements, critiques, abuse to sm@cs.hull.ac.uk
;; This file is in addition to GNU Emacs.
(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)
(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.
(delete-region (- stub-end (length stub-file)) stub-end)
(insert completed)
(sit-for 0) ; See it immediately.
(if (symbolp (file-name-completion completed path))
(message "Completed in %s" path) ; Complete as could be.
(display-completions completed path))) ; Partly only, list.
(t ; Stub nonunique, list.
(display-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 display-completions (stub path)
"Display in a buffer completions with stub file name STUB in PATH.
Also saves the current window configuration in \"saved-window-configuration\"
if the value of that variable is non-nil."
(interactive)
(if (not saved-window-configuration)
(setq saved-window-configuration (current-window-configuration)))
(message "Making completion list in %s..." path)
(with-output-to-temp-buffer "*Completions*"
(display-completion-list (sort (file-name-all-completions stub path)
'string<)))
(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 saved-window-configuration nil
"Contains the window configurations when completions are listed.
The window configurations are restored when input is sent to the shell.
Do not change the value of this variable.")
(make-variable-buffer-local 'saved-window-configuration)
;; Modified shell-send-input, shell-set-directory.
;; shell-set-directory parses the whole line, rather than just looking at the
;; word starting at the first char.
;; shell-send-input restores the window configuration if it's been saved.
;; shell-set-directory currently:
;; - Parses the input line for shell-cd/popd/pushd-regexp & attempts to set
;; buffer's directory[-stack].
;; - Quits further parsing if fails to set. Eg. if "pushd nosuchfile",
;; "pop file" etc.
(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)
;;; Lets go through the commands...
(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 restores window
configurations if necessary."
(interactive)
;;; "if" body is an sm addition to restore window config. if nec.
;;; Rest of code is as before.
(if saved-window-configuration
(unwind-protect (set-window-configuration saved-window-configuration)
(setq saved-window-configuration nil)))
(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))))
Technical Details
Message-ID:
<7318.9009281030@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!sm2Show Full Headers
Complete Article Headers
X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: fb2f1,dbdbf5360f1dd3f5,start X-Google-Attributes: gidfb2f1,public X-Google-ArrivalTime: 1990-09-29 01:43:04 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: file name completion. Message-ID: <7318.9009281030@uk.ac.hull.cc.sequent> Date: 28 Sep 90 12:30:52 GMT Sender: news@tut.cis.ohio-state.edu Distribution: gnu Organization: GNUs Not Usenet Lines: 359 Posted: Fri Sep 28 13:30:52 1990