source: release/1.5/source/cocoa-ide/hemlock/unused/archive/shell.lisp

Last change on this file was 6567, checked in by Gary Byers, 18 years ago

Move lots of (currently unused, often unlikely to ever be used) stuff to an
archive directory.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.8 KB
Line 
1;;; -*- Log: hemlock.log; Package: Hemlock -*-
2;;;
3;;; **********************************************************************
4;;; This code was written as part of the CMU Common Lisp project at
5;;; Carnegie Mellon University, and has been placed in the public domain.
6;;;
7#+CMU (ext:file-comment
8 "$Header$")
9;;;
10;;; **********************************************************************
11;;;
12;;; Hemlock command level support for processes.
13;;;
14;;; Written by Blaine Burks.
15;;;
16
17(in-package :hemlock)
18
19
20(defun setup-process-buffer (buffer)
21 (let ((mark (copy-mark (buffer-point buffer) :right-inserting)))
22 (defhvar "Buffer Input Mark"
23 "The buffer input mark for this buffer."
24 :buffer buffer
25 :value mark)
26 (defhvar "Process Output Stream"
27 "The process structure for this buffer."
28 :buffer buffer
29 :value (make-hemlock-output-stream mark :full))
30 (defhvar "Interactive History"
31 "A ring of the regions input to an interactive mode (Eval or Typescript)."
32 :buffer buffer
33 :value (make-ring (value interactive-history-length)))
34 (defhvar "Interactive Pointer"
35 "Pointer into \"Interactive History\"."
36 :buffer buffer
37 :value 0)
38 (defhvar "Searching Interactive Pointer"
39 "Pointer into \"Interactive History\"."
40 :buffer buffer
41 :value 0)
42 (unless (buffer-modeline-field-p buffer :process-status)
43 (setf (buffer-modeline-fields buffer)
44 (nconc (buffer-modeline-fields buffer)
45 (list (modeline-field :process-status)))))))
46
47(defmode "Process" :major-p nil :setup-function #'setup-process-buffer)
48
49
50
51
52;;;; Shell-filter streams.
53
54;;; We use shell-filter-streams to capture text going from the shell process to
55;;; a Hemlock output stream. They pass character and misc operations through
56;;; to the attached hemlock-output-stream. The string output function scans
57;;; the string for ^A_____^B, denoting a change of directory.
58;;;
59;;; The following aliases in a .cshrc file are required for using filename
60;;; completion:
61;;; alias cd 'cd \!* ; echo ""`pwd`"/"'
62;;; alias popd 'popd \!* ; echo ""`pwd`"/"'
63;;; alias pushd 'pushd \!* ; echo ""`pwd`"/"'
64;;;
65
66(defstruct (shell-filter-stream
67 (:include sys:lisp-stream
68 (:out #'shell-filter-out)
69 (:sout #'shell-filter-string-out)
70 (:misc #'shell-filter-output-misc))
71 (:print-function print-shell-filter-stream)
72 (:constructor
73 make-shell-filter-stream (buffer hemlock-stream)))
74 ;; The buffer where output will be going
75 buffer
76 ;; The Hemlock stream to which output will be directed
77 hemlock-stream)
78
79
80;;; PRINT-SHELL-FILTER-STREAM -- Internal
81;;;
82;;; Function for printing a shell-filter-stream.
83;;;
84(defun print-shell-filter-stream (s stream d)
85 (declare (ignore d s))
86 (write-string "#<Shell filter stream>" stream))
87
88
89;;; SHELL-FILTER-OUT -- Internal
90;;;
91;;; This is the character-out handler for the shell-filter-stream.
92;;; It writes the character it is given to the underlying
93;;; hemlock-output-stream.
94;;;
95(defun shell-filter-out (stream character)
96 (write-char character (shell-filter-stream-hemlock-stream stream)))
97
98
99;;; SHELL-FILTER-OUTPUT-MISC -- Internal
100;;;
101;;; This will also simply pass the output request on the the
102;;; attached hemlock-output-stream.
103;;;
104(defun shell-filter-output-misc (stream operation &optional arg1 arg2)
105 (let ((hemlock-stream (shell-filter-stream-hemlock-stream stream)))
106 (funcall (hi::hemlock-output-stream-misc hemlock-stream)
107 hemlock-stream operation arg1 arg2)))
108
109
110;;; CATCH-CD-STRING -- Internal
111;;;
112;;; Scans String for the sequence ^A...^B. Returns as multiple values
113;;; the breaks in the string. If the second start/end pair is nil, there
114;;; was no cd sequence.
115;;;
116(defun catch-cd-string (string start end)
117 (declare (simple-string string))
118 (let ((cd-start (position (code-char 1) string :start start :end end)))
119 (if cd-start
120 (let ((cd-end (position (code-char 2) string :start cd-start :end end)))
121 (if cd-end
122 (values start cd-start cd-end end)
123 (values start end nil nil)))
124 (values start end nil nil))))
125
126;;; SHELL-FILTER-STRING-OUT -- Internal
127;;;
128;;; The string output function for shell-filter-stream's.
129;;; Any string containing a ^A...^B is caught and assumed to be
130;;; the path-name of the new current working directory. This is
131;;; removed from the orginal string and the result is passed along
132;;; to the Hemlock stream.
133;;;
134(defun shell-filter-string-out (stream string start end)
135 (declare (simple-string string))
136 (let ((hemlock-stream (shell-filter-stream-hemlock-stream stream))
137 (buffer (shell-filter-stream-buffer stream)))
138
139 (multiple-value-bind (start1 end1 start2 end2)
140 (catch-cd-string string start end)
141 (write-string string hemlock-stream :start start1 :end end1)
142 (when start2
143 (write-string string hemlock-stream :start (+ 2 start2) :end end2)
144 (let ((cd-string (subseq string (1+ end1) start2)))
145 (setf (variable-value 'current-working-directory :buffer buffer)
146 (pathname cd-string)))))))
147
148
149;;; FILTER-TILDES -- Internal
150;;;
151;;; Since COMPLETE-FILE does not seem to deal with ~'s in the filename
152;;; this function expands them to a full path name.
153;;;
154(defun filter-tildes (name)
155 (declare (simple-string name))
156 (if (char= (schar name 0) #\~)
157 (concatenate 'simple-string
158 (if (or (= (length name) 1)
159 (char= (schar name 1) #\/))
160 (cdr (assoc :home *environment-list*))
161 "/usr/")
162 (subseq name 1))
163 name))
164
165
166
167
168;;;; Support for handling input before the prompt in process buffers.
169
170(defun unwedge-process-buffer ()
171 (buffer-end (current-point))
172 (deliver-signal-to-process :SIGINT (value process))
173 (editor-error "Aborted."))
174
175(defhvar "Unwedge Interactive Input Fun"
176 "Function to call when input is confirmed, but the point is not past the
177 input mark."
178 :value #'unwedge-process-buffer
179 :mode "Process")
180
181(defhvar "Unwedge Interactive Input String"
182 "String to add to \"Point not past input mark. \" explaining what will
183 happen if the the user chooses to be unwedged."
184 :value "Interrupt and throw to end of buffer?"
185 :mode "Process")
186
187
188
189
190;;;; Some Global Variables.
191
192(defhvar "Current Shell"
193 "The shell to which \"Select Shell\" goes."
194 :value nil)
195
196(defhvar "Ask about Old Shells"
197 "When set (the default), Hemlock prompts for an existing shell buffer in
198 preference to making a new one when there is no \"Current Shell\"."
199 :value t)
200
201(defhvar "Kill Process Confirm"
202 "When set, Hemlock prompts for confirmation before killing a buffer's process."
203 :value t)
204
205(defhvar "Shell Utility"
206 "The \"Shell\" command uses this as the default command line."
207 :value "/bin/csh")
208
209(defhvar "Shell Utility Switches"
210 "This is a string containing the default command line arguments to the
211 utility in \"Shell Utility\". This is a string since the utility is
212 typically \"/bin/csh\", and this string can contain I/O redirection and
213 other shell directives."
214 :value "")
215
216
217
218
219;;;; The Shell, New Shell, and Set Current Shell Commands.
220
221(defvar *shell-names* (make-string-table)
222 "A string-table of the string-name of all process buffers and corresponding
223 buffer structures.")
224
225(defcommand "Set Current Shell" (p)
226 "Sets the value of \"Current Shell\", which the \"Shell\" command uses."
227 "Sets the value of \"Current Shell\", which the \"Shell\" command uses."
228 (declare (ignore p))
229 (set-current-shell))
230
231;;; SET-CURRENT-SHELL -- Internal.
232;;;
233;;; This prompts for a known shell buffer to which it sets "Current Shell".
234;;; It signals an error if there are none.
235;;;
236(defun set-current-shell ()
237 (let ((old-buffer (value current-shell))
238 (first-old-shell (do-strings (var val *shell-names* nil)
239 (declare (ignore val))
240 (return var))))
241 (when (and (not old-buffer) (not first-old-shell))
242 (editor-error "Nothing to set current shell to."))
243 (let ((default-shell (if old-buffer
244 (buffer-name old-buffer)
245 first-old-shell)))
246 (multiple-value-bind
247 (new-buffer-name new-buffer)
248 (prompt-for-keyword (list *shell-names*)
249 :must-exist t
250 :default default-shell
251 :default-string default-shell
252 :prompt "Existing Shell: "
253 :help "Enter the name of an existing shell.")
254 (declare (ignore new-buffer-name))
255 (setf (value current-shell) new-buffer)))))
256
257(defcommand "Shell" (p)
258 "This spawns a shell in a buffer. If there already is a \"Current Shell\",
259 this goes to that buffer. If there is no \"Current Shell\", there are
260 shell buffers, and \"Ask about Old Shells\" is set, this prompts for one
261 of them, setting \"Current Shell\" to that shell. Supplying an argument
262 forces the creation of a new shell buffer."
263 "This spawns a shell in a buffer. If there already is a \"Current Shell\",
264 this goes to that buffer. If there is no \"Current Shell\", there are
265 shell buffers, and \"Ask about Old Shells\" is set, this prompts for one
266 of them, setting \"Current Shell\" to that shell. Supplying an argument
267 forces the creation of a new shell buffer."
268 (let ((shell (value current-shell))
269 (no-shells-p (do-strings (var val *shell-names* t)
270 (declare (ignore var val))
271 (return nil))))
272 (cond (p (make-new-shell nil no-shells-p))
273 (shell (change-to-buffer shell))
274 ((and (value ask-about-old-shells) (not no-shells-p))
275 (set-current-shell)
276 (change-to-buffer (value current-shell)))
277 (t (make-new-shell nil)))))
278
279(defcommand "Shell Command Line in Buffer" (p)
280 "Prompts the user for a process and a buffer in which to run the process."
281 "Prompts the user for a process and a buffer in which to run the process."
282 (declare (ignore p))
283 (make-new-shell t))
284
285;;; MAKE-NEW-SHELL -- Internal.
286;;;
287;;; This makes new shells for us dealing with prompting for various things and
288;;; setting "Current Shell" according to user documentation.
289;;;
290(defun make-new-shell (prompt-for-command-p &optional (set-current-shell-p t)
291 (command-line (get-command-line) clp))
292 (let* ((command (or (and clp command-line)
293 (if prompt-for-command-p
294 (prompt-for-string
295 :default command-line :trim t
296 :prompt "Command to execute: "
297 :help "Shell command line to execute.")
298 command-line)))
299 (buffer-name (if prompt-for-command-p
300 (prompt-for-string
301 :default
302 (concatenate 'simple-string command " process")
303 :trim t
304 :prompt `("Buffer in which to execute ~A? "
305 ,command)
306 :help "Where output from this process will appear.")
307 (new-shell-name)))
308 (temp (make-buffer
309 buffer-name
310 :modes '("Fundamental" "Process")
311 :delete-hook
312 (list #'(lambda (buffer)
313 (when (eq (value current-shell) buffer)
314 (setf (value current-shell) nil))
315 (delete-string (buffer-name buffer) *shell-names*)
316 (kill-process (variable-value 'process
317 :buffer buffer))))))
318 (buffer (or temp (getstring buffer-name *buffer-names*)))
319 (stream (variable-value 'process-output-stream :buffer buffer))
320 (output-stream
321 ;; If we re-used an old shell buffer, this isn't necessary.
322 (if (hemlock-output-stream-p stream)
323 (setf (variable-value 'process-output-stream :buffer buffer)
324 (make-shell-filter-stream buffer stream))
325 stream)))
326 (buffer-end (buffer-point buffer))
327 (defhvar "Process"
328 "The process for Shell and Process buffers."
329 :buffer buffer
330 :value (ext::run-program "/bin/sh" (list "-c" command)
331 :wait nil
332 :pty output-stream
333 :env (frob-environment-list
334 (car (buffer-windows buffer)))
335 :status-hook #'(lambda (process)
336 (declare (ignore process))
337 (update-process-buffer buffer))
338 :input t :output t))
339 (defhvar "Current Working Directory"
340 "The pathname of the current working directory for this buffer."
341 :buffer buffer
342 :value (default-directory))
343 (setf (getstring buffer-name *shell-names*) buffer)
344 (update-process-buffer buffer)
345 (when (and (not (value current-shell)) set-current-shell-p)
346 (setf (value current-shell) buffer))
347 (change-to-buffer buffer)))
348
349;;; GET-COMMAND-LINE -- Internal.
350;;;
351;;; This just conses up a string to feed to the shell.
352;;;
353(defun get-command-line ()
354 (concatenate 'simple-string (value shell-utility) " "
355 (value shell-utility-switches)))
356
357;;; FROB-ENVIRONMENT-LIST -- Internal.
358;;;
359;;; This sets some environment variables so the shell will be in the proper
360;;; state when it comes up.
361;;;
362(defun frob-environment-list (window)
363 (list* (cons :termcap (concatenate 'simple-string
364 "emacs:co#"
365 (if window
366 (lisp::quick-integer-to-string
367 (window-width window))
368 "")
369 ":tc=unkown:"))
370 (cons :emacs "t") (cons :term "emacs")
371 (remove-if #'(lambda (keyword)
372 (member keyword '(:termcap :emacs :term)
373 :test #'(lambda (cons keyword)
374 (eql (car cons) keyword))))
375 ext:*environment-list*)))
376
377;;; NEW-SHELL-NAME -- Internal.
378;;;
379;;; This returns a unique buffer name for a shell by incrementing the value of
380;;; *process-number* until "Process <*process-number*> is not already the name
381;;; of a buffer. Perhaps this is being overly cautious, but I've seen some
382;;; really stupid users.
383;;;
384(defvar *process-number* 0)
385;;;
386(defun new-shell-name ()
387 (loop
388 (let ((buffer-name (format nil "Shell ~D" (incf *process-number*))))
389 (unless (getstring buffer-name *buffer-names*) (return buffer-name)))))
390
391
392
393;;;; Modeline support.
394
395(defun modeline-process-status (buffer window)
396 (declare (ignore window))
397 (when (hemlock-bound-p 'process :buffer buffer)
398 (let ((process (variable-value 'process :buffer buffer)))
399 (ecase (ext:process-status process)
400 (:running "running")
401 (:stopped "stopped")
402 (:signaled "killed by signal ~D" (unix:unix-signal-name
403 (ext:process-exit-code process)))
404 (:exited (format nil "exited with status ~D"
405 (ext:process-exit-code process)))))))
406
407
408(make-modeline-field :name :process-status
409 :function #'modeline-process-status)
410
411(defun update-process-buffer (buffer)
412 (when (buffer-modeline-field-p buffer :process-status)
413 (dolist (window (buffer-windows buffer))
414 (update-modeline-field buffer window :process-status)))
415 (let ((process (variable-value 'process :buffer buffer)))
416 (unless (ext:process-alive-p process)
417 (ext:process-close process)
418 (when (eq (value current-shell) buffer)
419 (setf (value current-shell) nil)))))
420
421
422
423;;;; Supporting Commands.
424
425(defcommand "Confirm Process Input" (p)
426 "Evaluate Process Mode input between the point and last prompt."
427 "Evaluate Process Mode input between the point and last prompt."
428 (declare (ignore p))
429 (unless (hemlock-bound-p 'process :buffer (current-buffer))
430 (editor-error "Not in a process buffer."))
431 (let* ((process (value process))
432 (stream (ext:process-pty process)))
433 (case (ext:process-status process)
434 (:running)
435 (:stopped (editor-error "The process has been stopped."))
436 (t (editor-error "The process is dead.")))
437 (let ((input-region (get-interactive-input)))
438 (write-line (region-to-string input-region) stream)
439 (force-output (ext:process-pty process))
440 (insert-character (current-point) #\newline)
441 ;; Move "Buffer Input Mark" to end of buffer.
442 (move-mark (region-start input-region) (region-end input-region)))))
443
444(defcommand "Shell Complete Filename" (p)
445 "Attempts to complete the filename immediately preceding the point.
446 It will beep if the result of completion is not unique."
447 "Attempts to complete the filename immediately preceding the point.
448 It will beep if the result of completion is not unique."
449 (declare (ignore p))
450 (unless (hemlock-bound-p 'current-working-directory)
451 (editor-error "Shell filename completion only works in shells."))
452 (let ((point (current-point)))
453 (with-mark ((start point))
454 (pre-command-parse-check start)
455 (unless (form-offset start -1) (editor-error "Can't grab filename."))
456 (when (member (next-character start) '(#\" #\' #\< #\>))
457 (mark-after start))
458 (let* ((name-region (region start point))
459 (fragment (filter-tildes (region-to-string name-region)))
460 (dir (default-directory))
461 (shell-dir (value current-working-directory)))
462 (multiple-value-bind (filename unique)
463 (unwind-protect
464 (progn
465 (setf (default-directory) shell-dir)
466 (complete-file fragment :defaults shell-dir))
467 (setf (default-directory) dir))
468 (cond (filename
469 (delete-region name-region)
470 (insert-string point (namestring filename))
471 (when (not unique)
472 (editor-error)))
473 (t (editor-error "No such file exists."))))))))
474
475(defcommand "Kill Main Process" (p)
476 "Kills the process in the current buffer."
477 "Kills the process in the current buffer."
478 (declare (ignore p))
479 (unless (hemlock-bound-p 'process :buffer (current-buffer))
480 (editor-error "Not in a process buffer."))
481 (when (or (not (value kill-process-confirm))
482 (prompt-for-y-or-n :default nil
483 :prompt "Really blow away shell? "
484 :default nil
485 :default-string "no"))
486 (kill-process (value process))))
487
488(defcommand "Stop Main Process" (p)
489 "Stops the process in the current buffer. With an argument use :SIGSTOP
490 instead of :SIGTSTP."
491 "Stops the process in the current buffer. With an argument use :SIGSTOP
492 instead of :SIGTSTP."
493 (unless (hemlock-bound-p 'process :buffer (current-buffer))
494 (editor-error "Not in a process buffer."))
495 (deliver-signal-to-process (if p :SIGSTOP :SIGTSTP) (value process)))
496
497(defcommand "Continue Main Process" (p)
498 "Continues the process in the current buffer."
499 "Continues the process in the current buffer."
500 (declare (ignore p))
501 (unless (hemlock-bound-p 'process :buffer (current-buffer))
502 (editor-error "Not in a process buffer."))
503 (deliver-signal-to-process :SIGCONT (value process)))
504
505(defun kill-process (process)
506 "Self-explanatory."
507 (deliver-signal-to-process :SIGKILL process))
508
509(defun deliver-signal-to-process (signal process)
510 "Delivers a signal to a process."
511 (ext:process-kill process signal :process-group))
512
513(defcommand "Send EOF to Process" (p)
514 "Sends a Ctrl-D to the process in the current buffer."
515 "Sends a Ctrl-D to the process in the current buffer."
516 (declare (ignore p))
517 (unless (hemlock-bound-p 'process :buffer (current-buffer))
518 (editor-error "Not in a process buffer."))
519 (let ((stream (ext:process-pty (value process))))
520 (write-char (code-char 4) stream)
521 (force-output stream)))
522
523(defcommand "Interrupt Buffer Subprocess" (p)
524 "Stop the subprocess currently executing in this shell."
525 "Stop the subprocess currently executing in this shell."
526 (declare (ignore p))
527 (unless (hemlock-bound-p 'process :buffer (current-buffer))
528 (editor-error "Not in a process buffer."))
529 (buffer-end (current-point))
530 (buffer-end (value buffer-input-mark))
531 (deliver-signal-to-subprocess :SIGINT (value process)))
532
533(defcommand "Kill Buffer Subprocess" (p)
534 "Kill the subprocess currently executing in this shell."
535 "Kill the subprocess currently executing in this shell."
536 (declare (ignore p))
537 (unless (hemlock-bound-p 'process :buffer (current-buffer))
538 (editor-error "Not in a process buffer."))
539 (deliver-signal-to-subprocess :SIGKILL (value process)))
540
541(defcommand "Quit Buffer Subprocess" (p)
542 "Quit the subprocess currently executing int his shell."
543 "Quit the subprocess currently executing int his shell."
544 (declare (ignore p))
545 (unless (hemlock-bound-p 'process :buffer (current-buffer))
546 (editor-error "Not in a process buffer."))
547 (deliver-signal-to-subprocess :SIGQUIT (value process)))
548
549(defcommand "Stop Buffer Subprocess" (p)
550 "Stop the subprocess currently executing in this shell."
551 "Stop the subprocess currently executing in this shell."
552 (unless (hemlock-bound-p 'process :buffer (current-buffer))
553 (editor-error "Not in a process buffer."))
554 (deliver-signal-to-subprocess (if p :SIGSTOP :SIGTSTP) (value process)))
555
556(defun deliver-signal-to-subprocess (signal process)
557 "Delivers a signal to a subprocess of a shell."
558 (ext:process-kill process signal :pty-process-group))
Note: See TracBrowser for help on using the repository browser.