;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Base:8; Lowercase:T; Readtable:ZL -*- (defun dribble (&optional filename) "Starts writing all input and output to FILENAME. With no arg, stops doing so. Works by binding *STANDARD-OUTPUT* and *STANDARD-INPUT*. See also DRIBBLE-ALL." (if filename (dribble-start filename) (dribble-end))) ;;; This binds *STANDARD-OUTPUT* and *STANDARD-INPUT* and enters a new read-eval-print ;;; loop. SETQ'ing them would be global for all processes and would leave you ;;; totally shafted if the file connection broke. (defun dribble-start (filename &optional editor-p) "Copy input and output to a file, or an editor buffer if second arg is T" (format t "~&Entering dribble read-eval-print loop. Do ~S to exit." '(dribble)) (let* ((*standard-input* (make-dribble-stream *terminal-io* (if (not editor-p) (open filename :direction :output :characters t) (zwei:make-file-buffer-stream filename)))) (*standard-output* *standard-input*)) (unwind-protect (catch 'dribble-end (lisp-top-level1 *terminal-io*)) (send *standard-output* :dribble-end)))) (defun dribble-all (&optional filename &optional editor-p) "Copy input and output to a file, or an editor buffer if second arg is T. With no argument, exits a previously entered DRIBBLE-ALL and closes the file. This differs from DRIBBLE-START in that it binds *TERMINAL-IO* instead of *STANDARD-OUTPUT*, so queries, break loops, etc. are included." (if (null filename) (dribble-end) (format t "~&Entering dribble read-eval-print loop. Do ~S to exit." '(dribble)) (let ((dribble (make-dribble-stream *terminal-io* (if (not editor-p) (open filename :direction :output :characters t) (zwei:make-file-buffer-stream filename))))) (unwind-protect (catch 'dribble-end (lisp-top-level1 dribble)) ;; Do this with old definition of *terminal-io*, in case it gets an error. (send dribble :dribble-end))))) (defun dribble-end () "Exit from the recursive read-eval-print loop entered by DRIBBLE-START or DRIBBLE-ALL." (condition-case () (throw 'dribble-end nil) (eh:throw-tag-not-seen (format t "~&You do not appear to be in a dribble loop.")))) (defun make-dribble-stream (*tv-stream* *file-stream*) (declare (special *tv-stream* *file-stream*)) (let ((*rubout-handler-buffer* (make-string 100. :fill-pointer 0)) (dribble-stream)) (declare (special *rubout-handler-buffer* dribble-stream)) (setq dribble-stream (closure '(*tv-stream* *file-stream* *rubout-handler-buffer* dribble-stream) 'dribble-stream-io)))) (defun dribble-stream-io (op &rest args &aux (old-tio *terminal-io*) (*terminal-io* ;; Don't leave *terminal-io* as the dribble stream ;; so that errors inside here don't bomb out. (if (eq *terminal-io* dribble-stream) *tv-stream* *terminal-io*))) (declare (special *tv-stream* *file-stream* dribble-stream *rubout-handler-buffer*)) (case op ((:tyo :string-out :line-out :fresh-line) (lexpr-send *tv-stream* op args) (lexpr-send *file-stream* op args)) ((:any-tyi :tyi :tyi-no-hang :any-tyi-no-hang :read-char :read-char-no-hang) (prog () (or rubout-handler (send-if-handles *file-stream* :force-output)) (catch (if rubout-handler 'rubout-handler 'dummy-tag) (let ((ch (send *tv-stream* op))) (and ch rubout-handler (vector-push-extend ;; If it's an activation blip, we want the character. (if (consp ch) (cadr ch) ch) *rubout-handler-buffer*)) (return ch))) ;;get here if someone threw to rubout-handler ;;reset our buffer and continue the throw (setf (fill-pointer *rubout-handler-buffer*) 0) (throw 'rubout-handler nil))) ((:untyi :unread-char) (send *tv-stream* op (car args)) (and rubout-handler (plusp (length *rubout-handler-buffer*)) (decf (fill-pointer *rubout-handler-buffer*)))) (:rubout-handler (setf (fill-pointer *rubout-handler-buffer*) 0) ;reset the buffer (prog (vals) (cond ((and (operation-handled-p *file-stream* :safe-to-use-p) (not (send *file-stream* :safe-to-use-p))) (format *tv-stream* "~&Dribble stream cannot accept output!") (dribble-end))) (setq vals (multiple-value-list ;; Bind *terminal-io* back to the dribble stream if that's what it was ;; in case the code run inside the rubout handler ;; uses *terminal-io*. (let ((*terminal-io* old-tio)) (lexpr-send *tv-stream* op args)))) ;; If the stream is having troubles, don't echo to it. (cond ((and (operation-handled-p *file-stream* :safe-to-use-p) (not (send *file-stream* :safe-to-use-p))) (format *tv-stream* "~&Dribble stream cannot accept output!") (dribble-end))) (send *file-stream* :string-out *rubout-handler-buffer*) (send-if-handles *file-stream* :force-output) (return-list vals))) (:dribble-end (format *tv-stream* "~&Closing dribble file.") (close *file-stream*) (send-if-handles *file-stream* :truename)) (:notice (if (and (operation-handled-p *file-stream* :safe-to-use-p) (not (send *file-stream* :safe-to-use-p))) 'tv:cold-load-stream (lexpr-send-if-handles *tv-stream* :notice args))) (:increment-cursorpos (cond ((eq (caddr args) ':character) (dotimes (y-increment (cadr args)) (send *file-stream* :tyo #/return)) (dotimes (x-increment (car args)) (send *file-stream* :tyo #/sp)))) (lexpr-send *tv-stream* op args)) ((:finish :force-output) (lexpr-send-if-handles *file-stream* op args) (lexpr-send *tv-stream* op args)) (:ITEM (LEXPR-FUNCALL #'FORMAT *FILE-STREAM* (CDDR ARGS)) (LEXPR-SEND *TV-STREAM* OP ARGS)) (otherwise (lexpr-send *tv-stream* op args))))