| 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))
|
|---|