;-*- Mode:LISP; Package:ZWEI; Lowercase:T; Base:8; Readtable:T -*- ;;; An editor that is not a window and displays on another specified window. ;;; And an interface to use such an editor ;;; as the rubout handler for any window with TV:STREAM-MIXIN. ;>> Minibuffers!!!!!!!!!!!!! ;>> needs a way to be notified about change of size (or other characteristics?) of WINDOW (defflavor external-stream-displayer (;; The window we do our display on. ;; Any kind of window will do. window ;; The stream used as *QUERY-IO* and *TYPEIN-WINDOW* ;; when editing in this displayer. typein-stream ;; The stream used as *TERMINAL-IO* and *TYPEOUT-WINDOW*. typeout-stream ;; The positions of the edges of the subrectangle of WINDOW we should use. ;; All four are relative to the inside upper left corner of WINDOW. ;; Note that it does not really work for LEFT and RIGHT to be anything but the ;; full width of WINDOW. Our redisplay can handle it, ;; but the typeout and typein streams cannot. left top right bottom ;; The vertical position (relative to TOP) of the "top" of the display. ;; This records how our display is rotated within the allotted space. (starting-vpos nil) ;; We don't really use these, but (:method window :redisplay) looks for them. pline-marking-left-array ;Hpos of left edge of region-marking on this pline. pline-marking-width-array ;width in pixels of region-marking. pline-text-width-array ;width in pixels of text. ;; The hpos (relative to the inside edge of WINDOW) ;; to start display at for our first line only. ;; (For following lines, LEFT is used.) ;; This is how the rubout handler can manage to display ;; starting at whatever cursorpos it is entered with. first-line-left ;; Height of screen used up by typeout through the TYPEOUT-STREAM. ;; Normally zero when there is no typeout present. typeout-used-height ;; Height of screen used up by typein through the TYPEIN-STREAM. ;; Normally zero when there is no typein present. typein-used-height ;; The width of a character in WINDOW. ;; Used to translate cursor positions in WINDOW into character column numbers. char-width ;; The height of a line in WINDOW. line-height ;; The number of columns between tab stops. (tab-nchars 8)) (displayer) :settable-instance-variables (:documentation "A flavor which does zwei-style displaying for another window.")) (defmethod (external-stream-displayer :print-self) (stream &rest ignore) (si:printing-random-object (self stream :type :no-pointer) (format stream "~O for ~A" (%pointer self) window))) ;;; TYPE-IN appears after the end of the editor display. ;;; If it gets big enough vertically, it can end up ;;; overwriting the beginning of the editor display. ;;; If this happens, the beginning of the display ;;; (represented by STARTING-VPOS) is advanced ;;; so that the area used for TYPE-IN is not part of the display area. ;;; This also involves decrementing N-PLINES. ;;; TYPE-OUT starts at the START of the editor display ;;; (where STARTING-VPOS points). ;;; It overwrites the editor display. ;;; At the end of the command, the editor display ;;; is output again, starting after where the type-out finished. ;>> yes, we really DO need to do the :set-window :before and :after :init !! ;>> (trust me --- I tried removal) ;>> I suppose there is a modularity problem here, but it doesn't really hurt, so wtf. (defmethod (external-stream-displayer :before :init) (init-plist) (send self :set-window window) (let ((*standard-input* window) (*terminal-io* typeout-stream) (*standard-output* si:syn-terminal-io) (*query-io* syn-typein-window-io) (*mode-line-list* nil) (*comtab* (get init-plist :comtab))) (setq editor-closure (make-editor-closure (or (get init-plist :editor-closure-variables) top-level-editor-closure-variables) nil)))) (defmethod (external-stream-displayer :after :init) (ignore) (let ((max-n-plines (send self :max-n-plines))) (setq pline-line-array (make-array max-n-plines)) (setq pline-from-index-array (make-array max-n-plines)) (setq pline-to-index-array (make-array max-n-plines)) (setq pline-tick-array (make-array max-n-plines)) (setq pline-marking-left-array (make-array max-n-plines)) (setq pline-marking-width-array (make-array max-n-plines)) (setq pline-text-width-array (make-array max-n-plines))) (send self :set-window window)) (defmethod (external-stream-displayer :set-window) (new-window &optional (new-left 0) (new-top 0) (new-right (tv:sheet-inside-width new-window)) (new-bottom (tv:sheet-inside-height new-window))) (setq window new-window) (multiple-value-setq (typeout-stream typein-stream) (make-external-stream-displayer-typeout-stream self window)) (setq line-height (send window :line-height)) (setq char-width (send window :char-width)) (send window :send-if-handles :set-tab-nchars tab-nchars) (setq left new-left top new-top right new-right bottom new-bottom) (when (variable-boundp editor-closure) (funcall editor-closure #'initialize-top-level-editor self) (let () (sys:%using-binding-instances (closure-bindings editor-closure)) (setq *debug-io* tv:cold-load-stream ;>> for debugging *error-output* tv:cold-load-stream *trace-output* tv:cold-load-stream) (setq *terminal-io* typeout-stream *query-io* typein-stream *typeout-window* typeout-stream *typein-window* typein-stream)))) ;;; Initialize the contents and starting position of the displayer. ;;; ARRAY becomes the text being edited, with point positioned after INDEX characters. ;;; HPOS and VPOS are the cursorpos in WINDOW for the beginning of the display. (defmethod (external-stream-displayer :initialize-contents) (array index vpos &optional (starting-hpos left) &aux string) ;; Make sure TOP and BOTTOM are set so as to make sure ;; we have an integral number of lines, always at same vertical positions. (setq top (\ vpos line-height)) (setq bottom (* line-height (floor (- (send window :inside-height) top) line-height))) (setq starting-vpos (- vpos top)) (setq first-line-left starting-hpos) (setq typeout-used-height 0) (setq string (make-string (array-active-length array))) (copy-array-portion array 0 (array-active-length array) string 0 (array-active-length array)) (send self :set-interval (create-interval string nil t)) (setq redisplay-degree dis-text) (let ((*interval* interval)) (move-bp point (forward-char point index t))) (setq typein-used-height 0 typeout-used-height 0) (send typeout-stream :initialize-no-typeout) (setq n-plines 1) (unless (variable-boundp pline-tick-array) (let ((max-n-plines (floor (- bottom top) line-height))) (setq pline-tick-array (make-array max-n-plines) pline-line-array (make-array max-n-plines) pline-from-index-array (make-array max-n-plines) pline-to-index-array (make-array max-n-plines))))) ;Returns a list (hpos vpos) of the cursorpos ;at which our display on WINDOW begins. (defmethod (external-stream-displayer :starting-cursorpos) () (list first-line-left (+ top starting-vpos))) ;Returns a list (hpos vpos) of the cursorpos ;of the beginning of the first line of WINDOW that follows ;the editor display. ;This is used to tell where to start printing "type-in". (defmethod (external-stream-displayer :cursorpos-after-display) () (list left (+ top (\ (+ (* n-plines line-height) starting-vpos) (- bottom top))))) ;Returns a list (hpos vpos) of the cursorpos ;of the beginning of the first line of WINDOW that follows ;all type-in. This is used for erasing "type-in". (defmethod (external-stream-displayer :cursorpos-after-typein) () (list (if (zerop typein-used-height) first-line-left left) (+ top (\ (+ typein-used-height starting-vpos) (- bottom top))))) ;;; Trivial stubs for certain operations that ZWEI always wants to use. (defmethod (external-stream-displayer :max-n-plines) () (max 1 (1- (truncate (send window :inside-height) line-height)))) (defmethod (external-stream-displayer :new-plines) (pline) ;; We are about to overwrite this! So forget it was there. (when (send *typein-window* :exposed-p) (send *typein-window* :clear-screen)) (setq n-plines (1+ pline))) ;(defmethod (external-stream-displayer :after :redisplay) () ; ;; If we are using the entire window, ; ;; and there is a lot of blank space at the end of it, ; ;; and we have text not displayed above the top, ; ;; rotate our starting vpos to display that text ; ;; above the first text now displayed. ; (LET ((MAX-N-PLINES (MAX 1 (1- (TRUNCATE (SEND WINDOW :INSIDE-HEIGHT) LINE-HEIGHT))))) ; (WHEN (AND (= N-PLINES MAX-N-PLINES) ; (< (+ 4 LAST-PLINE) N-PLINES) ; (NOT (BP-= START-BP (INTERVAL-FIRST-BP INTERVAL)))) ; (LET ((ADDED-LINES (- MAX-N-PLINES LAST-PLINE 1))) ; (CONDITION-CASE () ; (RECENTER-WINDOW-RELATIVE SELF (- ADDED-LINES)) ; (BARF) ; (:NO-ERROR ; (setq STARTING-VPOS (nth-value 1 (FLOOR (- STARTING-VPOS ; (* ADDED-LINES LINE-HEIGHT)) ; (- BOTTOM TOP)))) ; (DO ((P (1- MAX-N-PLINES) (1- P))) ; ((< P ADDED-LINES)) ; (SETF (AREF PLINE-LINE-ARRAY P) ; (AREF PLINE-LINE-ARRAY (- P ADDED-LINES))) ; (SETF (AREF PLINE-TICK-ARRAY P) ; (AREF PLINE-TICK-ARRAY (- P ADDED-LINES))) ; (SETF (AREF PLINE-FROM-INDEX-ARRAY P) ; (AREF PLINE-FROM-INDEX-ARRAY (- P ADDED-LINES))) ; (SETF (AREF PLINE-TO-INDEX-ARRAY P) ; (AREF PLINE-TO-INDEX-ARRAY (- P ADDED-LINES)))) ; (SETQ FIRST-LINE-LEFT LEFT) ; (SETQ LAST-POINT-PLINE ; (MIN (1- N-PLINES) ; (+ LAST-POINT-PLINE ADDED-LINES))) ;; (WHEN POINT-PLINE (INCF POINT-PLINE)) ; (MUST-REDISPLAY SELF DIS-TEXT) ; (SEND SELF :REDISPLAY :NONE NIL NIL NIL))))))) ;This is how the ZWEI asks what to use for *TYPEOUT-WINDOW* and *TYPEIN-WINDOW* (defmethod (external-stream-displayer :typeout-window) () typeout-stream) (defmethod (external-stream-displayer :typein-window) () typein-stream) ;List of all windows that need to be redisplayed for this displayer. (defmethod (external-stream-displayer :editor-windows) () (list self)) ; Sent by some aspect of redisplay. (defmethod (external-stream-displayer :finish-delayed-select) () nil) (defmethod (external-stream-displayer :top-of-editor-hierarchy) () (ferror nil "Should not get here.")) ;This is sent by RECENTER-WINDOW. (defmethod (external-stream-displayer :new-scroll-position) () nil) ;This is sent by (:method displayer :redisplay) (defmethod (external-stream-displayer :update-region-marking) () nil) (defmethod (external-stream-displayer :redisplay-blt) () nil) (defmethod (external-stream-displayer :set-blinker-size) (ignore ignore x y) (send window :set-cursorpos x (external-stream-displayer-pline-vpos (// y line-height)))) ;These return nil to turn off certain activities inside (:method displayer :redisplay) (defmethod (external-stream-displayer :font-map) () nil) (defmethod (external-stream-displayer :special-blinker-list) () nil) ;; This returns a dummy blinker that (:method displayer :redisplay) can send to harmlessly. (defmethod (external-stream-displayer :point-blinker) () 'ignore) (defmethod (external-stream-displayer :inside-width) () (send window :inside-width)) (defmethod (external-stream-displayer :clear-screen) () (send window :clear-screen)) (defmethod (external-stream-displayer :screen-line-out) (string &optional (start 0) (stop nil) xpos ypos dwidth) (send window :screen-line-out string start stop xpos (external-stream-displayer-pline-vpos (// ypos line-height)) dwidth)) (defmethod (external-stream-displayer :set-cursorpos) (x y) (send window :set-cursorpos x (external-stream-displayer-pline-vpos (// y line-height)))) (defmethod (external-stream-displayer :clear-eof) () (do ((pline (// (mod (- (nth-value 1 (send window :read-cursorpos)) top starting-vpos) (* (truncate (- bottom top) line-height) line-height)) line-height) (1+ pline))) ((>= pline n-plines)) (send window :clear-between-cursorposes left (external-stream-displayer-pline-vpos pline) right (external-stream-displayer-pline-vpos pline)) (setf (pline-line self pline) nil) (setf (pline-tick self pline) *tick*))) (defmethod (external-stream-displayer :refresh-margins) () (send window :set-cursorpos left top) (send self :reprompt)) ;;; Methods for computing how output will look. (defmethod (external-stream-displayer :character-width) () char-width) (defmethod (external-stream-displayer :string-length) (&rest args) ;; At first, cheat and work only on windows (lexpr-send window :string-length args)) (defmethod (external-stream-displayer :compute-motion) (&rest args) ;; At first, cheat and work only on windows (lexpr-send window :compute-motion args)) (defmethod (external-stream-displayer :editor-string-length) (string start end continuation &optional stop-x) (if continuation (multiple-value-bind (end-x nil end-index) (send self :compute-motion string start end 0 0 nil (or stop-x 0) (and stop-x 0) ;STOP-X, STOP-Y nil nil nil line-height (* (or *tab-width* tab-nchars) char-width)) (values end-x end-index)) (send self :string-length string start end stop-x nil 0 (* (or *tab-width* tab-nchars) char-width)))) (defmethod (external-stream-displayer :editor-tab-width) () (* (or *tab-width* tab-nchars) char-width)) (defmethod (external-stream-displayer :put-point-at-pline) (point-line point-index point-pline first-bp last-bp &aux (lh line-height)) (cond (( point-pline 0) ;; Algorithm: first find LINE, which will be the new TOP-LINE, ;; by scanning backwards. Then knock off plines from the front ;; of it until POINT ends up at POINT-PLINE. ;; P is the number of plines between POINT and the beginning ;; of the current LINE. (do ((line point-line) ;; P is the point-pline if we start at the beginning of LINE. (p (floor (nth-value 1 ;; Compute which continuation line of POINT-LINE point is on. (zid-ed-compute-motion point-line point-index first-bp last-bp (- right left))) lh)) (stop-line (bp-line first-bp))) (( p point-pline) ;; We have found the new TOP-LINE. Now find TOP-INDEX. (values line (let ((difference (- p point-pline))) (cond ((zerop difference) 0) (t ;; Compute motion to move Y down DIFFERENCE plines. (multiple-value-bind (nil nil nc) (send self :compute-motion line (if (eq line stop-line) (bp-index first-bp) 0) nil 0 0 nil 0 (* difference lh) most-positive-fixnum (- right left)) (if (eq line stop-line) (max nc (bp-index first-bp)) nc))))) point-pline)) (when (or (eq line stop-line) (null (line-previous line))) ;observed to happen in dired RG. (return (values line 0 p))) (setq line (line-previous line)) (incf p (multiple-value-bind (nil fy) ;; Compute downward motion of this line, with fake RETURN. (send self :compute-motion line (if (eq line stop-line) (bp-index first-bp) 0) nil 0 0 t 0 most-positive-fixnum most-positive-fixnum (- right left)) (floor fy lh))))) (t ;; POINT-PLINE is negative, do the same thing in reverse. (do* ((line point-line (line-next line)) (line-start-index point-index 0) (this-line-height) ;; P is the point-pline if we display from beg of (LINE-NEXT LINE). ;; This line is the one if P is too far. (p 0) (stop-line (bp-line last-bp))) (()) (setq this-line-height (zid-ed-compute-line-motion line line-start-index last-bp (- right left))) (decf p this-line-height) (if (< p point-pline) ;; We have found the new TOP-LINE. Now find TOP-INDEX. (return (values line (let ((difference (- p point-pline))) (cond ((zerop (+ this-line-height difference)) 0) (t (multiple-value-bind (nil nil nc) (send self :compute-motion line line-start-index nil 0 0 t 0 (* (+ this-line-height difference) lh) most-positive-fixnum (- right left)) (if (eq line stop-line) (min nc (bp-index last-bp)) nc))))) point-pline))) (when (eq line stop-line) (return (values line 0 p))))))) ;;; Compute the downwarnd motion of an entire line, including following RETURN if any. ;;; The value is in units of lines, not pixels. (defun zid-ed-compute-line-motion (line start-index last-bp &optional width &aux (stop-line (bp-line last-bp))) (declare (:self-flavor external-stream-displayer)) (floor (nth-value 1 (send self :compute-motion line start-index (and (eq line stop-line) (min (1+ (bp-index last-bp)) (line-length stop-line))) 0 0 (neq line stop-line) 0 most-positive-fixnum most-positive-fixnum width)) line-height)) ;;; Do a SHEET-COMPUTE-MOTION, but take into account that if the cursor ;;; reaches the right margin in the middle of the line, it moves to the ;;; beginning of the next line "before" the next character if that char is not a Newline. ;;; WIDTH is the width of window to use in computation -- NIL means use its actual width. (defun zid-ed-compute-motion (line index first-bp last-bp &optional width) (declare (:self-flavor external-stream-displayer)) (multiple-value-bind (final-x final-y final-index max-x) (send self :compute-motion line (if (eq line (bp-line first-bp)) (bp-index first-bp) 0) index 0 0 nil 0 most-positive-fixnum most-positive-fixnum width) (if (and ( index (line-length line)) (not (and (eq line (bp-line last-bp)) (= index (bp-index last-bp)))) (> (+ final-x ;; There must be room for the next character and the "!". (* 2 char-width)) (- right left))) (setq final-x 0 final-y (+ final-y line-height))) (values final-x final-y final-index max-x))) ;;;; Implement the type-in and type-out streams. ;;; Create type-out and type-in streams for displayer *displayer*. ;(defflavor external-stream-displayer-typeout-stream ; (output-stream ; displayer ; (incomplete-p nil) ; (typeout-active-p nil) ; (ouput-stream-which-operations nil) ; (saved-which-operations nil)) ; (output-character-stream) ; (:default-handler external-stream-displayer-typeout-stream-pass-on) ; :inittable-instance-variables) ;(defun external-stream-displayer-typout-stream-pass-on (op &rest args) ; (declare (:self-flavor external-stream-dislayer-typeout-stream)) ; (lexpr-send output-stream op args)) (defun make-external-stream-displayer-typeout-stream (*displayer* *output-window*) "Return type-out and type-in streams for displayer *DISPLAYER*. These are returned as two values. *OUTPUT-WINDOW* is the *DISPLAYER* outputs to." (declare (special *output-window* *displayer* *xpos* *ypos*) (values type-out type-in)) (let (*typeout-active-p* *typeout-stream*) (declare (special *typeout-active-p* *typeout-stream*)) (values (setq *typeout-stream* (let ((*incomplete-p*) (*which-operations* (si:union-eq '(:make-complete :make-incomplete :incomplete-p :expose) (send *output-window* :which-operations)))) (declare (special *incomplete-p* *which-operations*)) (closure '(*output-window* *incomplete-p* *displayer* *typeout-active-p* *which-operations*) 'external-stream-displayer-typeout-stream))) (let (*xpos* *ypos* *typein-active-p* (*which-operations* (si:union-eq '(:command-loop-redisplay :typeout-stays :remake-incomplete :make-complete :incomplete-p) (send *output-window* :which-operations)))) (declare (special *xpos* *ypos* *typein-active-p* *which-operations*)) (closure '(*output-window* *displayer* *typein-active-p* *typeout-active-p* *typeout-stream* *which-operations* *xpos* *ypos*) 'external-stream-displayer-typein-stream))))) ;;; The type-out stream handles these special operations: ;;; :DO-SOMETHING-ABOUT-TYPEOUT ;;; tells the stream to erase its typeout in a suitable way. ;;; This sends :CLEAR-TYPEOUT back to the displayer ;;; to make it redisplay. ;;; :INITIALIZE-NO-TYPEOUT ;;; tells the stream to set its variables to record that ;;; there is no typeout on the output window at the moment. ;;; Output operations send :TYPEOUT-HAPPENED to the displayer ;;; so it can record how far typeout has got ;;; and update TYPEOUT-USED-SIZE. ;;; Variables of the typeout stream: ;;; *DISPLAYER* - the instance of EXTERNAL-STREAM-DISPLAYER we were made for. ;;; *OUTPUT-WINDOW* - the window to output on. Same one the displayer does. ;;; *INCOMPLETE-P* - same as for typeout windows. ;;; *TYPEOUT-ACTIVE-P* - T if there is typeout on the screen. ;;; *WHICH-OPERATIONS* - our list of operations, ;;; for implementing :WHICH-OPERATIONS, :SEND-IF-HANDLES and :OPERATION-HANDLED-P. (defun external-stream-displayer-typeout-stream (op &rest args) (declare (special *output-window* *incomplete-p* *displayer* *typeout-active-p* *which-operations*)) (case op (:which-operations *which-operations*) (:operation-handled-p (memq (car args) *which-operations*)) (:send-if-handles (if (memq (car args) *which-operations*) (apply #'external-stream-displayer-typeout-stream args))) (:make-complete (setq *incomplete-p* nil)) (:make-incomplete (setq *incomplete-p* t)) (:incomplete-p *incomplete-p*) (:deexpose (setq *typeout-active-p* nil)) (:expose) (:top-of-editor-hierarchy #'ignore) (:initialize-no-typeout (setq *typeout-active-p* nil *incomplete-p* nil)) (:do-something-about-typeout (setq *typeout-active-p* nil) (send *displayer* :clear-typeout)) ((:tyi :untyi :tyi-no-hang :any-tyi :any-tyi-no-hang :read-char :Readh-char-no-hang :any-read-char :any-read-char-no-hang :rubout-handler :beep) (lexpr-send *output-window* op args)) (t ;; If output is done, make sure displayer knows to redisplay itself ;; beneath the output. (unless *typeout-active-p* (lexpr-send *output-window* :set-cursorpos (send *displayer* :starting-cursorpos)) (send *output-window* :clear-rest-of-line)) (setq *incomplete-p* t) (setq *typeout-active-p* t) (unwind-protect (lexpr-send *output-window* op args) (send *displayer* :typeout-happened))))) ;;; Special operations of the type-in stream: ;;; :COMMAND-LOOP-REDISPLAY ;;; Sent by command loop after each command. ;;; Get rid of typein, if there is any and it should go away. ;;; :TYPEOUT-STAYS ;;; Says this batch of type-in should not go away ;;; after the next command is typed. ;;; Variables (aside from those documented above the typeout stream) ;;; *TYPEOUT-STREAM* - the typeout stream for this displayer. ;;; Type-in appears as type-out if type-out is active. ;;; *TYPEIN-ACTIVE-P* - non-NIL if there is type-in. ;;; Values include: ;;; :IN-USE - this command made type-in. ;;; :IN-USE-STAYS - this command made it, and it shouldn't be erased. ;;; :USED - previous command made type in, erase it after this command. ;;; :USED-STAYS - previous command made it, but don't erase it. ;;; NIL - no type-in present now. ;;; The stream is "incomplete" if the value of this variable ;;; is :IN-USE or :IN-USE-STAYS. ;;; It is "exposed" if the variable is non-NIL. ;;; *XPOS* and *YPOS* - last cursorposition on WINDOW reached by type-in. ;;; This is so that type-in can come out consecutively ;;; even if editor redisplay intervenes. ;;; Output operations report their progress to the displayer ;;; using the :TYPEIN-HAPPENED operation. (defun external-stream-displayer-typein-stream (op &rest args) (declare (special *output-window* *displayer* *which-operations* *typein-active-p* *typeout-active-p* *typeout-stream* *xpos* *ypos*)) (case op (:which-operations *which-operations*) (:operation-handled-p (memq (car args) *which-operations*)) (:send-if-handles (if (memq (car args) *which-operations*) (apply #'external-stream-displayer-typein-stream args))) ((:clear-window :clear-screen :deexpose) (let ((after-display (send *displayer* :cursorpos-after-display)) (after-typein (send *displayer* :cursorpos-after-typein))) (send *output-window* :clear-between-cursorposes (first after-display) (second after-display) (first after-typein) (second after-typein))) (send *displayer* :set-typein-used-height 0) (setq *typein-active-p* nil)) (:exposed-p *typein-active-p*) (:command-loop-redisplay (case *typein-active-p* (:in-use (setq *typein-active-p* :used)) (:in-use-stays (setq *typein-active-p* :used-stays)) (:used (external-stream-displayer-typein-stream :clear-screen)))) (:typeout-stays (if (eq *typein-active-p* :in-use) (setq *typein-active-p* :in-use-stays))) (:remake-incomplete (setq *typein-active-p* (case *typein-active-p* (:used :in-use) (:used-stays :in-use-stays) (t *typein-active-p*)))) (:make-complete (if (eq *typein-active-p* :in-use) (setq *typein-active-p* :used))) (:incomplete-p (memq *typein-active-p* '(:in-use :in-use-stays))) (:top-of-editor-hierarchy #'ignore) (:output-hold-exception (send *output-window* :output-hold-exception)) (:beep (beep nil *output-window*)) (t (if *typeout-active-p* (lexpr-send *typeout-stream* op args) (if *typein-active-p* (send *output-window* :set-cursorpos *xpos* *ypos*) (lexpr-send *output-window* :set-cursorpos (send *displayer* :cursorpos-after-display))) (setq *typein-active-p* :in-use) (let ((tv:more-processing-global-enable nil)) (unwind-protect (lexpr-send *output-window* op args) (multiple-value-setq (*xpos* *ypos*) (send *output-window* :read-cursorpos)) (send *displayer* :typein-happened))))))) ;;; Sent whenever type-in stream output has happened. ;;; Look at WINDOW's cursor to see how far it got. (defmethod (external-stream-displayer :typein-happened) () (let* ((vpos (+ line-height (nth-value 1 (send window :read-cursorpos)))) ;;(- (+ line-height (tv:sheet-cursor-y window)) (tv:sheet-inside-top window)) (rel-vpos (if ( vpos starting-vpos) (- vpos starting-vpos) (+ (- vpos starting-vpos) (- bottom top))))) ;; If typein has wrapped around and clobbered top of display, ;; it chops off the beginning of the display area. (when (< rel-vpos (* n-plines line-height)) (send window :set-cursorpos left (\ (+ starting-vpos rel-vpos) (- bottom top))) (send self :reprompt :null) (let ((lost-lines (ceiling rel-vpos line-height))) (condition-case () (recenter-window-relative self lost-lines) (barf nil) (:no-error (copy-array-portion pline-line-array lost-lines n-plines pline-line-array 0 (- n-plines lost-lines)) (copy-array-portion pline-tick-array lost-lines n-plines pline-tick-array 0 (- n-plines lost-lines)) (copy-array-portion pline-from-index-array lost-lines n-plines pline-from-index-array 0 (- n-plines lost-lines)) (copy-array-portion pline-to-index-array lost-lines n-plines pline-to-index-array 0 (- n-plines lost-lines)) (decf last-point-pline lost-lines) (if (minusp last-point-pline) (setq last-point-pline 0)))) (setq n-plines (max 1 (- n-plines lost-lines))) (must-redisplay self dis-text) (setq rel-vpos (- bottom top)))) (setq typein-used-height (max typein-used-height rel-vpos)))) ;;; Sent whenever type-out stream output has happened. ;;; Look at WINDOW's cursor to see how far it has got. (defmethod (external-stream-displayer :typeout-happened) () (let* ((vpos (- (tv:sheet-cursor-y window) (tv:sheet-inside-top window))) (rel-vpos (if ( vpos starting-vpos) (- vpos starting-vpos) (+ (- vpos starting-vpos) (- bottom top))))) (setq typeout-used-height (max typeout-used-height (+ rel-vpos line-height))))) ;;; Redisplay after typeout has happened. ;;; In fact, we do not erase the typeout; ;;; we instead reposition the display to start where the typeout finished. (defmethod (external-stream-displayer :clear-typeout) () (terpri window) (terpri window) (send self :reprompt) (must-redisplay self dis-text) (dotimes (p n-plines) (setf (pline-tick self p) -1)) (setq typeout-used-height 0)) (defmethod (external-stream-displayer :mode-line-window) () (let ((*window* self)) (closure '(*window*) 'external-stream-displayer-mode-line))) ;This is the "mode line window" for the external-stream-displayer. ;It doesn't display anything; it just ignores operations so that no errors occur. ;It also allows callers to get their hands on the typein window. (defun external-stream-displayer-mode-line (op &rest args) (declare (ignore args)) (case op (:typein-window (send *window* :typein-stream)) (:top-of-editor-hierarchy #'ignore) ((:mini-buffer-window :search-mini-buffer-window) #'(lambda (&rest ignore) (ferror nil "Mini buffers of EXTERNAL-STREAM-DISPLAYERs not implemented."))))) ;;;; Command loop ;;; This is the function that does the actual work (defmethod (external-stream-displayer :edit) (&optional ignore (*comtab* *comtab*) (*mode-line-list* *mode-line-list*) (top-level-p (send self :top-level-p))) (let ((prompt-array (make-prompt-array)) (tv:kbd-intercepted-characters editor-intercepted-characters) ch) (catch 'return-from-command-loop (catch (if (eq top-level-p t) 'exit-top-level 'exit-control-r) (do-forever (catch-error-restart ((sys:abort error) (if top-level-p "Return to top level editor command loop." "Return to editor command loop.")) (catch 'zwei-command-loop (catch (if (eq top-level-p t) 'top-level 'dummy-tag) (tagbody (setq *last-command-type* *current-command-type* *current-command-type* nil *numeric-arg* 1 *numeric-arg-p* nil *numeric-arg-n-digits* 0 *mark-stays* nil *mini-buffer-command* nil) (clear-prompts) (unless (zerop typeout-used-height) (send typeout-stream :do-something-about-typeout)) (send *typein-window* :command-loop-redisplay) (setq *centering-fraction* *center-fraction*) unreal-command ;arguments loop back here (redisplay-all-windows) (without-io-buffer-output-function (setq ch (input-with-prompts *standard-input* :any-tyi))) (cond ((null ch) ;If EOF, return (return nil)) ((consp ch) (lexpr-send self :process-special-command ch)) ((numberp ch) ;Keyboard or mouse character (when (eq :argument (send self :process-command-char ch)) (incf *numeric-arg-n-digits*) (go unreal-command)))))))) ;; If we abort from BREAK or an error, and after every command, ;; say it is ok to flush the typeout window. (send *typeout-window* :make-complete) (when *mini-buffer-command* (mini-buffer-ring-push *mini-buffer-command*))))))) (defmethod (external-stream-displayer :process-special-command) (&rest ignore) nil) (defmethod (external-stream-displayer :process-command-char) (ch) (process-command-char ch)) (defun external-stream-displayer-pline-vpos (pline) "Return the actual vertical cursorpos of editor line PLINE." (declare (:self-flavor external-stream-displayer)) (+ top (\ (+ starting-vpos (* pline line-height)) (* (truncate (- bottom top) line-height) line-height)))) ;This operation looks at the current cursorpos of WINDOW ;and makes editor display start from there. ;In rubout handling editors it also reprints the prompt ;that was specified by the caller of the rubout handler (eg, PROMPT-AND-READ). (defmethod (external-stream-displayer :reprompt) (&optional ignore) (multiple-value-setq (first-line-left starting-vpos) (send window :read-cursorpos))) (compile-flavor-methods external-stream-displayer)