114
|
1 ;; -*-Emacs-Lisp-*- run a shell in an Emacs window
|
|
2 ;; Copyright (C) 1985, 1986, 1987, 1990 Free Software Foundation, Inc.
|
|
3
|
|
4 ;; This file is part of GNU Emacs.
|
|
5
|
|
6 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
7 ;; it under the terms of the GNU General Public License as published by
|
|
8 ;; the Free Software Foundation; either version 1, or (at your option)
|
|
9 ;; any later version.
|
|
10
|
|
11 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14 ;; GNU General Public License for more details.
|
|
15
|
|
16 ;; You should have received a copy of the GNU General Public License
|
|
17 ;; along with GNU Emacs; see the file COPYING. If not, write to
|
|
18 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
19
|
|
20 ;;; Hacked from tea.el and shell.el by Olin Shivers (shivers@cs.cmu.edu). 8/88
|
|
21
|
|
22 ;;; Since this mode is built on top of the general command-interpreter-in-
|
|
23 ;;; a-buffer mode (comint mode), it shares a common base functionality,
|
|
24 ;;; and a common set of bindings, with all modes derived from comint mode.
|
|
25
|
|
26 ;;; For documentation on the functionality provided by comint mode, and
|
|
27 ;;; the hooks available for customising it, see the file comint.el.
|
|
28
|
|
29 ;;; Needs fixin:
|
|
30 ;;; When sending text from a source file to a subprocess, the process-mark can
|
|
31 ;;; move off the window, so you can lose sight of the process interactions.
|
|
32 ;;; Maybe I should ensure the process mark is in the window when I send
|
|
33 ;;; text to the process? Switch selectable?
|
|
34
|
|
35 (require 'comint)
|
|
36 (provide 'shell)
|
|
37
|
|
38 (defvar shell-popd-regexp "popd"
|
|
39 "*Regexp to match subshell commands equivalent to popd.")
|
|
40
|
|
41 (defvar shell-pushd-regexp "pushd"
|
|
42 "*Regexp to match subshell commands equivalent to pushd.")
|
|
43
|
|
44 (defvar shell-cd-regexp "cd"
|
|
45 "*Regexp to match subshell commands equivalent to cd.")
|
|
46
|
|
47 (defvar explicit-shell-file-name nil
|
|
48 "*If non-nil, is file name to use for explicitly requested inferior shell.")
|
|
49
|
|
50 (defvar explicit-csh-args
|
|
51 (if (eq system-type 'hpux)
|
|
52 ;; -T persuades HP's csh not to think it is smarter
|
|
53 ;; than us about what terminal modes to use.
|
|
54 '("-i" "-T")
|
|
55 '("-i"))
|
|
56 "*Args passed to inferior shell by M-x shell, if the shell is csh.
|
|
57 Value is a list of strings, which may be nil.")
|
|
58
|
|
59 (defvar shell-dirstack nil
|
|
60 "List of directories saved by pushd in this buffer's shell.")
|
|
61
|
|
62 (defvar shell-dirstack-query "dirs"
|
|
63 "Command used by shell-resync-dirlist to query shell.")
|
|
64
|
|
65 (defvar shell-mode-map ())
|
|
66 (cond ((not shell-mode-map)
|
|
67 (setq shell-mode-map (copy-keymap comint-mode-map))
|
|
68 (define-key shell-mode-map "\t" 'comint-dynamic-complete)
|
|
69 (define-key shell-mode-map "\M-?" 'comint-dynamic-list-completions)))
|
|
70
|
|
71 (defvar shell-mode-hook '()
|
|
72 "*Hook for customising shell mode")
|
|
73
|
|
74
|
|
75 ;;; Basic Procedures
|
|
76 ;;; ===========================================================================
|
|
77 ;;;
|
|
78
|
|
79 (defun shell-mode ()
|
|
80 "Major mode for interacting with an inferior shell.
|
|
81 Return after the end of the process' output sends the text from the
|
|
82 end of process to the end of the current line.
|
|
83 Return before end of process output copies rest of line to end (skipping
|
|
84 the prompt) and sends it.
|
|
85 M-x send-invisible reads a line of text without echoing it, and sends it to
|
|
86 the shell.
|
|
87
|
|
88 If you accidentally suspend your process, use \\[comint-continue-subjob]
|
|
89 to continue it.
|
|
90
|
|
91 cd, pushd and popd commands given to the shell are watched by Emacs to keep
|
|
92 this buffer's default directory the same as the shell's working directory.
|
|
93 M-x dirs queries the shell and resyncs Emacs' idea of what the current
|
|
94 directory stack is.
|
|
95 M-x dirtrack-toggle turns directory tracking on and off.
|
|
96
|
|
97 \\{shell-mode-map}
|
|
98 Customisation: Entry to this mode runs the hooks on comint-mode-hook and
|
|
99 shell-mode-hook (in that order).
|
|
100
|
|
101 Variables shell-cd-regexp, shell-pushd-regexp and shell-popd-regexp are used
|
|
102 to match their respective commands."
|
|
103 (interactive)
|
|
104 (comint-mode)
|
|
105 (setq major-mode 'shell-mode
|
|
106 mode-name "Shell"
|
|
107 comint-prompt-regexp shell-prompt-pattern
|
|
108 comint-input-sentinel 'shell-directory-tracker)
|
|
109 (use-local-map shell-mode-map)
|
|
110 (make-local-variable 'shell-dirstack)
|
|
111 (set (make-local-variable 'shell-dirtrackp) t)
|
|
112 (run-hooks 'shell-mode-hook))
|
|
113
|
|
114
|
|
115 (defun shell ()
|
|
116 "Run an inferior shell, with I/O through buffer *shell*.
|
|
117 If buffer exists but shell process is not running, make new shell.
|
|
118 If buffer exists and shell process is running, just switch to buffer *shell*.
|
|
119
|
|
120 The shell to use comes from the first non-nil variable found from these:
|
|
121 explicit-shell-file-name in Emacs, ESHELL in the environment or SHELL in the
|
|
122 environment. If none is found, /bin/sh is used.
|
|
123
|
|
124 If a file ~/.emacs_SHELLNAME exists, it is given as initial input, simulating
|
|
125 a start-up file for the shell like .profile or .cshrc. Note that this may
|
|
126 lose due to a timing error if the shell discards input when it starts up.
|
|
127
|
|
128 The buffer is put in shell-mode, giving commands for sending input
|
|
129 and controlling the subjobs of the shell.
|
|
130
|
|
131 The shell file name, sans directories, is used to make a symbol name
|
|
132 such as `explicit-csh-arguments'. If that symbol is a variable,
|
|
133 its value is used as a list of arguments when invoking the shell.
|
|
134 Otherwise, one argument `-i' is passed to the shell.
|
|
135
|
|
136 \(Type \\[describe-mode] in the shell buffer for a list of commands.)"
|
|
137 (interactive)
|
|
138 (cond ((not (comint-check-proc "*shell*"))
|
|
139 (let* ((prog (or explicit-shell-file-name
|
|
140 (getenv "ESHELL")
|
|
141 (getenv "SHELL")
|
|
142 "/bin/sh"))
|
|
143 (name (file-name-nondirectory prog))
|
|
144 (startfile (concat "~/.emacs_" name))
|
|
145 (xargs-name (intern-soft (concat "explicit-" name "-args"))))
|
|
146 (set-buffer (apply 'make-comint "shell" prog
|
|
147 (if (file-exists-p startfile) startfile)
|
|
148 (if (and xargs-name (boundp xargs-name))
|
|
149 (symbol-value xargs-name)
|
|
150 '("-i"))))
|
|
151 (shell-mode))))
|
|
152 (switch-to-buffer "*shell*"))
|
|
153
|
|
154
|
|
155 ;;; Directory tracking
|
|
156 ;;; ===========================================================================
|
|
157 ;;; This code provides the shell mode input sentinel
|
|
158 ;;; SHELL-DIRECTORY-TRACKER
|
|
159 ;;; that tracks cd, pushd, and popd commands issued to the shell, and
|
|
160 ;;; changes the current directory of the shell buffer accordingly.
|
|
161 ;;;
|
|
162 ;;; This is basically a fragile hack, although it's more accurate than
|
|
163 ;;; the original version in shell.el. It has the following failings:
|
|
164 ;;; 1. It doesn't know about the cdpath shell variable.
|
|
165 ;;; 2. It only spots the first command in a command sequence. E.g., it will
|
|
166 ;;; miss the cd in "ls; cd foo"
|
|
167 ;;; 3. More generally, any complex command (like ";" sequencing) is going to
|
|
168 ;;; throw it. Otherwise, you'd have to build an entire shell interpreter in
|
|
169 ;;; emacs lisp. Failing that, there's no way to catch shell commands where
|
|
170 ;;; cd's are buried inside conditional expressions, aliases, and so forth.
|
|
171 ;;;
|
|
172 ;;; The whole approach is a crock. Shell aliases mess it up. File sourcing
|
|
173 ;;; messes it up. You run other processes under the shell; these each have
|
|
174 ;;; separate working directories, and some have commands for manipulating
|
|
175 ;;; their w.d.'s (e.g., the lcd command in ftp). Some of these programs have
|
|
176 ;;; commands that do *not* effect the current w.d. at all, but look like they
|
|
177 ;;; do (e.g., the cd command in ftp). In shells that allow you job
|
|
178 ;;; control, you can switch between jobs, all having different w.d.'s. So
|
|
179 ;;; simply saying %3 can shift your w.d..
|
|
180 ;;;
|
|
181 ;;; The solution is to relax, not stress out about it, and settle for
|
|
182 ;;; a hack that works pretty well in typical circumstances. Remember
|
|
183 ;;; that a half-assed solution is more in keeping with the spirit of Unix,
|
|
184 ;;; anyway. Blech.
|
|
185 ;;;
|
|
186 ;;; One good hack not implemented here for users of programmable shells
|
|
187 ;;; is to program up the shell w.d. manipulation commands to output
|
|
188 ;;; a coded command sequence to the tty. Something like
|
|
189 ;;; ESC | <cwd> |
|
|
190 ;;; where <cwd> is the new current working directory. Then trash the
|
|
191 ;;; directory tracking machinery currently used in this package, and
|
|
192 ;;; replace it with a process filter that watches for and strips out
|
|
193 ;;; these messages.
|
|
194
|
|
195 ;;; REGEXP is a regular expression. STR is a string. START is a fixnum.
|
|
196 ;;; Returns T if REGEXP matches STR where the match is anchored to start
|
|
197 ;;; at position START in STR. Sort of like LOOKING-AT for strings.
|
|
198 (defun shell-front-match (regexp str start)
|
|
199 (eq start (string-match regexp str start)))
|
|
200
|
|
201 (defun shell-directory-tracker (str)
|
|
202 "Tracks cd, pushd and popd commands issued to the shell.
|
|
203 This function is called on each input passed to the shell.
|
|
204 It watches for cd, pushd and popd commands and sets the buffer's
|
|
205 default directory to track these commands.
|
|
206
|
|
207 You may toggle this tracking on and off with M-x dirtrack-toggle.
|
|
208 If emacs gets confused, you can resync with the shell with M-x dirs.
|
|
209
|
|
210 See variables shell-cd-regexp, shell-pushd-regexp, and shell-popd-regexp.
|
|
211 Environment variables are expanded, see function substitute-in-file-name."
|
|
212 (condition-case err
|
|
213 (cond (shell-dirtrackp
|
|
214 (string-match "^\\s *" str) ; skip whitespace
|
|
215 (let ((bos (match-end 0))
|
|
216 (x nil))
|
|
217 (cond ((setq x (shell-match-cmd-w/optional-arg shell-popd-regexp
|
|
218 str bos))
|
|
219 (shell-process-popd (substitute-in-file-name x)))
|
|
220 ((setq x (shell-match-cmd-w/optional-arg shell-pushd-regexp
|
|
221 str bos))
|
|
222 (shell-process-pushd (substitute-in-file-name x)))
|
|
223 ((setq x (shell-match-cmd-w/optional-arg shell-cd-regexp
|
|
224 str bos))
|
|
225 (shell-process-cd (substitute-in-file-name x)))))))
|
|
226 (error (message (car (cdr err))))))
|
|
227
|
|
228
|
|
229 ;;; Try to match regexp CMD to string, anchored at position START.
|
|
230 ;;; CMD may be followed by a single argument. If a match, then return
|
|
231 ;;; the argument, if there is one, or the empty string if not. If
|
|
232 ;;; no match, return nil.
|
|
233
|
|
234 (defun shell-match-cmd-w/optional-arg (cmd str start)
|
|
235 (and (shell-front-match cmd str start)
|
|
236 (let ((eoc (match-end 0))) ; end of command
|
|
237 (cond ((shell-front-match "\\s *\\(\;\\|$\\)" str eoc)
|
|
238 "") ; no arg
|
|
239 ((shell-front-match "\\s +\\([^ \t\;]+\\)\\s *\\(\;\\|$\\)"
|
|
240 str eoc)
|
|
241 (substring str (match-beginning 1) (match-end 1))) ; arg
|
|
242 (t nil))))) ; something else.
|
|
243 ;;; The first regexp is [optional whitespace, (";" or the end of string)].
|
|
244 ;;; The second regexp is [whitespace, (an arg), optional whitespace,
|
|
245 ;;; (";" or end of string)].
|
|
246
|
|
247
|
|
248 ;;; popd [+n]
|
|
249 (defun shell-process-popd (arg)
|
|
250 (let ((num (if (zerop (length arg)) 0 ; no arg means +0
|
|
251 (shell-extract-num arg))))
|
|
252 (if (and num (< num (length shell-dirstack)))
|
|
253 (if (= num 0) ; condition-case because the CD could lose.
|
|
254 (condition-case nil (progn (cd (car shell-dirstack))
|
|
255 (setq shell-dirstack
|
|
256 (cdr shell-dirstack))
|
|
257 (shell-dirstack-message))
|
|
258 (error (message "Couldn't cd.")))
|
|
259 (let* ((ds (cons nil shell-dirstack))
|
|
260 (cell (nthcdr (- num 1) ds)))
|
|
261 (rplacd cell (cdr (cdr cell)))
|
|
262 (setq shell-dirstack (cdr ds))
|
|
263 (shell-dirstack-message)))
|
|
264 (message "Bad popd."))))
|
|
265
|
|
266
|
|
267 ;;; cd [dir]
|
|
268 (defun shell-process-cd (arg)
|
|
269 (condition-case nil (progn (cd (if (zerop (length arg)) (getenv "HOME")
|
|
270 arg))
|
|
271 (shell-dirstack-message))
|
|
272 (error (message "Couldn't cd."))))
|
|
273
|
|
274
|
|
275 ;;; pushd [+n | dir]
|
|
276 (defun shell-process-pushd (arg)
|
|
277 (if (zerop (length arg))
|
|
278 ;; no arg -- swap pwd and car of shell stack
|
|
279 (condition-case nil (if shell-dirstack
|
|
280 (let ((old default-directory))
|
|
281 (cd (car shell-dirstack))
|
|
282 (setq shell-dirstack
|
|
283 (cons old (cdr shell-dirstack)))
|
|
284 (shell-dirstack-message))
|
|
285 (message "Directory stack empty."))
|
|
286 (message "Couldn't cd."))
|
|
287
|
|
288 (let ((num (shell-extract-num arg)))
|
|
289 (if num ; pushd +n
|
|
290 (if (> num (length shell-dirstack))
|
|
291 (message "Directory stack not that deep.")
|
|
292 (let* ((ds (cons default-directory shell-dirstack))
|
|
293 (dslen (length ds))
|
|
294 (front (nthcdr num ds))
|
|
295 (back (reverse (nthcdr (- dslen num) (reverse ds))))
|
|
296 (new-ds (append front back)))
|
|
297 (condition-case nil
|
|
298 (progn (cd (car new-ds))
|
|
299 (setq shell-dirstack (cdr new-ds))
|
|
300 (shell-dirstack-message))
|
|
301 (error (message "Couldn't cd.")))))
|
|
302
|
|
303 ;; pushd <dir>
|
|
304 (let ((old-wd default-directory))
|
|
305 (condition-case nil
|
|
306 (progn (cd arg)
|
|
307 (setq shell-dirstack
|
|
308 (cons old-wd shell-dirstack))
|
|
309 (shell-dirstack-message))
|
|
310 (error (message "Couldn't cd."))))))))
|
|
311
|
|
312 ;; If STR is of the form +n, for n>0, return n. Otherwise, nil.
|
|
313 (defun shell-extract-num (str)
|
|
314 (and (string-match "^\\+[1-9][0-9]*$" str)
|
|
315 (string-to-int str)))
|
|
316
|
|
317
|
|
318 (defun shell-dirtrack-toggle ()
|
|
319 "Turn directory tracking on and off in a shell buffer."
|
|
320 (interactive)
|
|
321 (setq shell-dirtrackp (not shell-dirtrackp))
|
|
322 (message "directory tracking %s."
|
|
323 (if shell-dirtrackp "ON" "OFF")))
|
|
324
|
|
325 ;;; For your typing convenience:
|
|
326 (fset 'dirtrack-toggle 'shell-dirtrack-toggle)
|
|
327
|
|
328
|
|
329 (defun shell-resync-dirs ()
|
|
330 "Resync the buffer's idea of the current directory stack.
|
|
331 This command queries the shell with the command bound to
|
|
332 shell-dirstack-query (default \"dirs\"), reads the next
|
|
333 line output and parses it to form the new directory stack.
|
|
334 DON'T issue this command unless the buffer is at a shell prompt.
|
|
335 Also, note that if some other subprocess decides to do output
|
|
336 immediately after the query, its output will be taken as the
|
|
337 new directory stack -- you lose. If this happens, just do the
|
|
338 command again."
|
|
339 (interactive)
|
|
340 (let* ((proc (get-buffer-process (current-buffer)))
|
|
341 (pmark (process-mark proc)))
|
|
342 (goto-char pmark)
|
|
343 (insert shell-dirstack-query) (insert "\n")
|
|
344 (sit-for 0) ; force redisplay
|
|
345 (comint-send-string proc shell-dirstack-query)
|
|
346 (comint-send-string proc "\n")
|
|
347 (set-marker pmark (point))
|
|
348 (let ((pt (point))) ; wait for 1 line
|
|
349 ;; This extra newline prevents the user's pending input from spoofing us.
|
|
350 (insert "\n") (backward-char 1)
|
|
351 (while (not (looking-at ".+\n"))
|
|
352 (accept-process-output proc)
|
|
353 (goto-char pt)))
|
|
354 (goto-char pmark) (delete-char 1) ; remove the extra newline
|
|
355 ;; That's the dirlist. grab it & parse it.
|
|
356 (let* ((dl (buffer-substring (match-beginning 0) (- (match-end 0) 1)))
|
|
357 (dl-len (length dl))
|
|
358 (ds '()) ; new dir stack
|
|
359 (i 0))
|
|
360 (while (< i dl-len)
|
|
361 ;; regexp = optional whitespace, (non-whitespace), optional whitespace
|
|
362 (string-match "\\s *\\(\\S +\\)\\s *" dl i) ; pick off next dir
|
|
363 (setq ds (cons (substring dl (match-beginning 1) (match-end 1))
|
|
364 ds))
|
|
365 (setq i (match-end 0)))
|
|
366 (let ((ds (reverse ds)))
|
|
367 (condition-case nil
|
|
368 (progn (cd (car ds))
|
|
369 (setq shell-dirstack (cdr ds))
|
|
370 (shell-dirstack-message))
|
|
371 (error (message "Couldn't cd.")))))))
|
|
372
|
|
373 ;;; For your typing convenience:
|
|
374 (fset 'dirs 'shell-resync-dirs)
|
|
375
|
|
376
|
|
377 ;;; Show the current dirstack on the message line.
|
|
378 ;;; Pretty up dirs a bit by changing "/usr/jqr/foo" to "~/foo".
|
|
379 ;;; (This isn't necessary if the dirlisting is generated with a simple "dirs".)
|
|
380 ;;; All the commands that mung the buffer's dirstack finish by calling
|
|
381 ;;; this guy.
|
|
382 (defun shell-dirstack-message ()
|
|
383 (let ((msg "")
|
|
384 (ds (cons default-directory shell-dirstack)))
|
|
385 (while ds
|
|
386 (let ((dir (car ds)))
|
|
387 (if (string-match (format "^%s\\(/\\|$\\)" (getenv "HOME")) dir)
|
|
388 (setq dir (concat "~/" (substring dir (match-end 0)))))
|
|
389 (if (string-equal dir "~/") (setq dir "~"))
|
|
390 (setq msg (concat msg dir " "))
|
|
391 (setq ds (cdr ds))))
|
|
392 (message msg)))
|