
;; -*- Mode: Lisp; Package: System-Internals -*-

;; Lisp Machine Editor Stream

;; The rubout handler implemented here is a crock since it duplicates
;; the functions of ZWEI.  However, this editor is implemented in terms of stream
;; operations, and thus can be used from consoles other than the
;; local Lisp Machine console.  This file makes use of the definitions
;; in LMMAX;UTILS. 

;; There are a couple ideas here:
;; The editor stream itself is separated from the underlying console
;; stream.  This is nice since the console stream need know nothing about
;; the rubout handler, and thus rubout handlers can be changed easily
;; without having to duplicate the code for the entire console stream.
;; In addition, it makes it easy to have one editor stream work for
;; different console streams.

;; Another thought would be to split the bare console stream into
;; an input stream from a keyboard and an output stream to a window
;; on a given monitor.  The editor stream would then bind these two
;; streams together into a single interactive i/o stream.  I can't see
;; much use for this, unless somebody wanted a keyboard stream without
;; having a window, or there were multiple keyboard types in use.

;; Terminology:

;; CONSOLE-STREAM -- a bi-directional stream to some console.  This stream
;;		     knows nothing about rubout handling.
;; EDITOR-STREAM -- a bi-directional stream connected to some console stream.
;;		    It handles both the :RUBOUT-HANDLER and :UNTYI stream operations.
;; TV-STREAM -- a bi-directional stream to the Lisp Machine console.  Special
;;		case of a console stream.
;; SUPDUP-STREAM -- a bi-directional stream to a console connected via the
;;		    Chaos net.  Special case of a console stream.

;; MAKE-TV-STREAM takes a piece of paper, and returns a
;; stream which talks to the TV terminal of the Lisp Machine.  For input,
;; it uses the keyboard.  For output, it types on the piece of paper. 

(DECLARE (SPECIAL TV-STREAM-PC-PPR))

(DEFUN MAKE-TV-STREAM (TV-STREAM-PC-PPR)
  (CLOSURE '(TV-STREAM-PC-PPR) #'TV-STREAM))

(DEFSELECT (TV-STREAM TV-STREAM-DEFAULT-HANDLER)
  
  ;; Input operations
  (:TYI 	() (KBD-TYI))
  (:TYI-NO-HANG () (KBD-TYI-NO-HANG))
  (:LISTEN 	() (KBD-CHAR-AVAILABLE))
  (:CLEAR-INPUT ()
    (DO () ((NOT (KBD-CHAR-AVAILABLE)))	      ;Clear hardware/firmware buffers
      (KBD-TYI)))
  
  ;; Output operations
  ;; TV-TYO doesn't print "control" characters.
  (:TYO (CHAR)
    (TV-TYO TV-STREAM-PC-PPR CHAR))
  (:STRING-OUT (STRING &OPTIONAL (BEGIN 0) END)
    (TV-STRING-OUT TV-STREAM-PC-PPR STRING BEGIN END))
  (:LINE-OUT (STRING &OPTIONAL (BEGIN 0) END)
    (TV-STRING-OUT TV-STREAM-PC-PPR STRING BEGIN END)
    (TV-CRLF TV-STREAM-PC-PPR))
  ;; If at the beginning of a line, clear it.  Otherwise, go
  ;; to the beginning of the next line and clear it.
  (:FRESH-LINE ()
    (COND ((= (PC-PPR-CURRENT-X TV-STREAM-PC-PPR)
	      (PC-PPR-LEFT-MARGIN TV-STREAM-PC-PPR))
	   (TV-CLEAR-EOL TV-STREAM-PC-PPR))
	  (T (TV-CRLF TV-STREAM-PC-PPR))))
  (:TRIGGER-MORE ()
    (OR (ZEROP (PC-PPR-EXCEPTIONS TV-STREAM-PC-PPR))
	(TV-EXCEPTION TV-STREAM-PC-PPR)))
  (:BEEP (&OPTIONAL (WAVELENGTH TV-BEEP-WAVELENGTH) (DURATION TV-BEEP-DURATION))
	 (%BEEP WAVELENGTH DURATION))
  
  ;; If no unit is specified, then the "preferred" unit is used.  This is the
  ;; unit that :SET-CURSORPOS defaults to and that :COMPUTE-MOTION uses.
  ;; The :CHARACTER unit of these operations is useless with variable width fonts.
  (:READ-CURSORPOS (&OPTIONAL (UNIT ':PIXEL))
    (MULTIPLE-VALUE-BIND (X Y)
	(TV-READ-CURSORPOS TV-STREAM-PC-PPR)
      (SELECTQ UNIT
	(:PIXEL)
	(:CHARACTER (SETQ X (// X (PC-PPR-CHAR-WIDTH TV-STREAM-PC-PPR)))
		    (SETQ Y (// Y (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))))
	(:OTHERWISE (FERROR NIL "~S is not a known unit." UNIT)))
      (MVRETURN X Y)))
  (:SET-CURSORPOS (X Y &OPTIONAL (UNIT ':PIXEL))
    ;; For compatibility
    (IF (FIXP UNIT) (PSETQ UNIT X X Y Y UNIT))
    (SELECTQ UNIT
      (:PIXEL)
      (:CHARACTER (SETQ X (* X (PC-PPR-CHAR-WIDTH TV-STREAM-PC-PPR)))
		  (SETQ Y (* Y (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))))
      (:OTHERWISE (FERROR NIL "~S is not a known unit." UNIT)))
    (TV-SET-CURSORPOS TV-STREAM-PC-PPR X Y))
  (:SET-CURSORPOS-RELATIVE (X Y &OPTIONAL (UNIT ':PIXEL))
    (SELECTQ UNIT
      (:PIXEL)
      (:CHARACTER (SETQ X (* X (PC-PPR-CHAR-WIDTH TV-STREAM-PC-PPR)))
		  (SETQ Y (* Y (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))))
      (:OTHERWISE (FERROR NIL "~S is not a known unit." UNIT)))
    (TV-SET-CURSORPOS-RELATIVE TV-STREAM-PC-PPR X Y))
  (:SIZE-IN-CHARACTERS ()
    (MVRETURN (// (- (PC-PPR-RIGHT-MARGIN TV-STREAM-PC-PPR)
		     (PC-PPR-LEFT-MARGIN TV-STREAM-PC-PPR))
		  (PC-PPR-CHAR-WIDTH TV-STREAM-PC-PPR))
	      (// (- (PC-PPR-BOTTOM-MARGIN TV-STREAM-PC-PPR)
		     (PC-PPR-TOP-MARGIN TV-STREAM-PC-PPR))
		  (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))))

  ;; Compute the new cursor position if STRING were output at the specified
  ;; point.  NIL for X-POS and Y-POS mean use the current cursor position.
  ;; X-POS and Y-POS are in pixels.
  (:COMPUTE-MOTION (X-POS Y-POS STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING)))
    (TV-COMPUTE-MOTION TV-STREAM-PC-PPR X-POS Y-POS STRING BEGIN END))

  ;; Compute the motion for printing a string and then move the cursor there.
  ;; This eliminates the problem of knowing whether to use the :PIXEL or :CHARACTER
  ;; unit when calling :SET-CURSORPOS.  Since the string is passed in here as an
  ;; argument, this stream need know nothing about the typein buffer maintained by the
  ;; editor stream.  This might be better named :MOVE-OVER-STRING.
  (:CURSOR-MOTION (X-POS Y-POS STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING)))
    (MULTIPLE-VALUE-BIND (X Y HOW-FAR)
	(TV-COMPUTE-MOTION TV-STREAM-PC-PPR X-POS Y-POS STRING BEGIN END)
      ;; Check for page wrap-around.
      (IF HOW-FAR
	  (MULTIPLE-VALUE (X Y) (TV-COMPUTE-MOTION TV-STREAM-PC-PPR 0 0 STRING HOW-FAR END)))
      (TV-SET-CURSORPOS TV-STREAM-PC-PPR X Y)))

  ;; This assumes that the cursor is positioned before the string to be underlined.
  ;; The operation should be defined to xor, so that underlining twice erases.
  (:UNDERLINE (STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING))
		      &AUX (X1 (PC-PPR-CURRENT-X TV-STREAM-PC-PPR))
			   (Y1 (+ (PC-PPR-CURRENT-Y TV-STREAM-PC-PPR)
				  (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))))
    (MULTIPLE-VALUE-BIND (X2 Y2)
	(TV-COMPUTE-MOTION TV-STREAM-PC-PPR X1 Y1 STRING BEGIN END)
      (TV-DRAW-LINE X1 Y1 X2 Y2 TV-ALU-XOR (PC-PPR-SCREEN TV-STREAM-PC-PPR))))

  ;; Font hackery.  FONT-LIST is a list of strings describing fonts, since
  ;; internal font representations are per-stream.  Try to avoid calling
  ;; TV-REDEFINE-PC-PPR whenever a font change is made since this reallocates
  ;; a new font map rather than reusing the old one.  If the typein buffer is
  ;; to contain font changes, then it should contain 16-bit characters.
  (:SET-FONTS (&REST FONT-LIST)
    (TV-REDEFINE-PC-PPR TV-STREAM-PC-PPR ':FONTS
			(MAPCAR #'(LAMBDA (FONT)
				    (SETQ FONT (INTERN FONT "FONTS"))
				    (IF (BOUNDP FONT) (SYMEVAL FONT) FONTS:CPTFONT))
				FONT-LIST)))
  (:SET-CURRENT-FONT (N)
    (TV-TYO TV-STREAM-PC-PPR (+ 240 N)))
  (:CHAR-WIDTH (&OPTIONAL CHAR FONT)
    (COND ((NOT CHAR) (PC-PPR-CHAR-WIDTH TV-STREAM-PC-PPR))
	  (T (SETQ FONT (IF (FIXP FONT)
			    (AREF (PC-PPR-FONT-MAP TV-STREAM-PC-PPR))
			    (PC-PPR-CURRENT-FONT TV-STREAM-PC-PPR)))
	     (TV-CHAR-WIDTH TV-STREAM-PC-PPR CHAR FONT))))
  (:LINE-HEIGHT () (PC-PPR-LINE-HEIGHT TV-STREAM-PC-PPR))
  (:STRING-WIDTH (STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING)))
    (TV-STRING-LENGTH TV-STREAM-PC-PPR STRING BEGIN END))

  (:DRAW-LINE (X0 Y0 X1 Y1 &OPTIONAL (TV-ALU TV-ALU-IOR))
    (LET ((TOP (PC-PPR-TOP TV-STREAM-PC-PPR))
	  (LEFT (PC-PPR-LEFT TV-STREAM-PC-PPR)))
      (TV-DRAW-LINE (+ LEFT X0) (+ TOP Y0) (+ LEFT X1) (+ TOP Y1) TV-ALU
		    TV-DEFAULT-SCREEN)))

  ;; The character or string to be inserted is passed along so that variable
  ;; width fonts can work correctly.  Fixed width font console
  ;; streams can ignore this argument.  By default, the characters are printed
  ;; in the newly created whitespace since this is what happens most of the time.
  ;; :INSERT-STRING and :DELETE-STRING both assume that the strings contain no newlines.
  (:INSERT-CHAR (&OPTIONAL (CHAR #\SPACE) (COUNT 1))
    (TV-INSERT-WIDTH TV-STREAM-PC-PPR
		     (* COUNT (TV-CHAR-WIDTH TV-STREAM-PC-PPR CHAR
					     (PC-PPR-CURRENT-FONT TV-STREAM-PC-PPR))))
    (DOTIMES (I COUNT) (TV-TYO TV-STREAM-PC-PPR CHAR)))
  (:INSERT-STRING (STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING)))
    (TV-INSERT-WIDTH TV-STREAM-PC-PPR (TV-STRING-LENGTH TV-STREAM-PC-PPR STRING BEGIN END))
    (TV-STRING-OUT TV-STREAM-PC-PPR STRING BEGIN END))
  (:DELETE-CHAR (&OPTIONAL (CHAR #\SPACE) (COUNT 1))
    (TV-DELETE-WIDTH TV-STREAM-PC-PPR
		     (* COUNT (TV-CHAR-WIDTH TV-STREAM-PC-PPR CHAR
					     (PC-PPR-CURRENT-FONT TV-STREAM-PC-PPR)))))
  (:DELETE-STRING (STRING &OPTIONAL (BEGIN 0) (END (STRING-LENGTH STRING)))
    (TV-DELETE-WIDTH TV-STREAM-PC-PPR (TV-STRING-LENGTH TV-STREAM-PC-PPR STRING BEGIN END)))
  
  (:CLEAR-SCREEN () (TV-CLEAR-PC-PPR TV-STREAM-PC-PPR))
  (:CLEAR-EOL	 () (TV-CLEAR-EOL TV-STREAM-PC-PPR))
  
  ;; Operations particular to this type of stream
  (:PC-PPR () TV-STREAM-PC-PPR)
  (:SET-PC-PPR (PC-PPR) (SETQ TV-STREAM-PC-PPR PC-PPR))
  )

(DEFUN TV-STREAM-DEFAULT-HANDLER (OP &OPTIONAL ARG1 &REST REST)
  (STREAM-DEFAULT-HANDLER #'TV-STREAM OP ARG1 REST))

;; The stream in AI:LMIO;SUPSER also follows this protocol.


;; Below is the definition of the editor stream.  The particular
;; "editor" or rubout handler used is a parameter of the stream.
;; This stream handles the :UNTYI and :RUBOUT-HANDLER operations
;; so that console streams don't have to worry about this.

;; Set up the :WHICH-OPERATIONS list ahead of time so as to avoid
;; excessive consing.  This operation gets invoked every time READ
;; or READLINE is called, for instance.

(DECLARE (SPECIAL ES-CONSOLE-STREAM ES-EDITOR ES-UNTYI-CHAR
		  ES-KILL-RING ES-BUFFER ES-WHICH-OPERATIONS
		  ES-X-ORIGIN ES-Y-ORIGIN))

(DEFUN MAKE-EDITOR-STREAM (ES-CONSOLE-STREAM ES-EDITOR &OPTIONAL (KILL-RING-SIZE 20.))
  (LET ((ES-UNTYI-CHAR NIL) (ES-KILL-RING NIL)
	(ES-BUFFER) (ES-X-ORIGIN) (ES-Y-ORIGIN)
	;; Be sure to include :CLEAR-INPUT for BREAK.  Don't include :TYI and
	;; :TYI-NO-HANG since we can't really do them unless the underlying
	;; stream can.
	(ES-WHICH-OPERATIONS (UNION '(:UNTYI :RUBOUT-HANDLER :CLEAR-INPUT :LISTEN)
				    (FUNCALL ES-CONSOLE-STREAM ':WHICH-OPERATIONS))))
    (DOTIMES (I KILL-RING-SIZE)
      (PUSH (MAKE-ARRAY NIL 'ART-8B 400 NIL '(0 0 0)) ES-KILL-RING))
    ;; Make the kill ring into a real ring.
    (RPLACD (LAST ES-KILL-RING) ES-KILL-RING)
    (SETQ ES-BUFFER (CAR ES-KILL-RING))
    (CLOSURE '(ES-CONSOLE-STREAM ES-EDITOR ES-UNTYI-CHAR
				 ES-KILL-RING ES-BUFFER ES-WHICH-OPERATIONS)
	     #'EDITOR-STREAM)))

;; FILL-POINTER points to what has been typed so far.  SCAN-POINTER points
;; to what has been read so far.  TYPEIN-POINTER points to where in the
;; middle of the line we are typing.

(DEFMACRO FILL-POINTER   () '(ARRAY-LEADER ES-BUFFER 0))
(DEFMACRO SCAN-POINTER   () '(ARRAY-LEADER ES-BUFFER 1))
(DEFMACRO TYPEIN-POINTER () '(ARRAY-LEADER ES-BUFFER 2))

;; The third argument in the DEFSELECT function specification means
;; that we're handling :WHICH-OPERATIONS ourselves.
;; May want to flush UNTYI-CHAR infavor of using the buffer.

(DEFSELECT (EDITOR-STREAM EDITOR-STREAM-DEFAULT-HANDLER T)
  (:WHICH-OPERATIONS () ES-WHICH-OPERATIONS)
  
  ;; Input Operations
  ;; Don't need EOF-OPTION for :TYI since this will always be an interactive stream.
  ;; Accept the option anyway since some functions may work for both interactive and
  ;; batch streams.
  (:TYI (&OPTIONAL IGNORE &AUX SCAN-POINTER)
    (COND
      ;; Give buffered-back character, if any.  Don't bother echoing or putting
      ;; it in the editor buffer since this has already happened.  We are guaranteed
      ;; that the untyi'ed char is the one just tyi'ed.
      (ES-UNTYI-CHAR
	(PROG1 ES-UNTYI-CHAR (SETQ ES-UNTYI-CHAR NIL)))
      ;; If not using a rubout processor, go directly to the underlying stream.
      ((NOT RUBOUT-HANDLER)
       (FUNCALL ES-CONSOLE-STREAM ':TYI))
      ;; If using a rubout processor and unread input exists, then return it.
      ((> (FILL-POINTER) (SETQ SCAN-POINTER (SCAN-POINTER)))
       (SETF (SCAN-POINTER) (1+ SCAN-POINTER))
       (AREF ES-BUFFER SCAN-POINTER))
      ;; Else get more input via the rubout processor.
      ;; This is kind of a kludge.  We really want a handle of SELF.
      ;; The stream state will be passed along since the specials are still bound.
      (T (FUNCALL ES-EDITOR #'EDITOR-STREAM))))
  (:TYI-NO-HANG (&AUX SCAN-POINTER)
    (COND
      ;; Give buffered-back character, if any.
      (ES-UNTYI-CHAR
	(PROG1 ES-UNTYI-CHAR (SETQ ES-UNTYI-CHAR NIL)))
      ;; If not using a rubout processor, go directly to the underlying stream.
      ((NOT RUBOUT-HANDLER)
       (FUNCALL ES-CONSOLE-STREAM ':TYI-NO-HANG))
      ;; Give buffered input from the rubout processor, if unread input exists.
      ((> (FILL-POINTER) (SETQ SCAN-POINTER (SCAN-POINTER)))
       (SETF (SCAN-POINTER) (1+ SCAN-POINTER))
       (AREF ES-BUFFER SCAN-POINTER))
      ;; In the case where the rubout handler is on, but no unread input exists,
      ;; just go to the underlying stream and try to get a character.  Don't
      ;; bother with echoing it or putting it in the character buffer, although
      ;; this is probably what we should do.
      (T (FUNCALL ES-CONSOLE-STREAM ':TYI-NO-HANG))))
  (:UNTYI (CHAR)
    (SETQ ES-UNTYI-CHAR CHAR))
  ;; Should we check here to see of the console stream really supports :LISTEN?
  (:LISTEN ()
    (COND (ES-UNTYI-CHAR)
	  ((> (FILL-POINTER) (SCAN-POINTER))
	   (AREF ES-BUFFER (SCAN-POINTER)))
	  (T (FUNCALL ES-CONSOLE-STREAM ':LISTEN))))
  ;; If the rubout handler is on, empty the buffer.  Perhaps we should clear the
  ;; input from the screen in this case as well since the user is still typing.
  ;; I don't see how this could ever happen.  If the handler is off, don't clear
  ;; the buffer since we want to save the input to be yanked back.
  (:CLEAR-INPUT ()
    (COND (RUBOUT-HANDLER
	    (SETF (FILL-POINTER) 0)
	    (SETF (SCAN-POINTER) 0)
	    (SETF (TYPEIN-POINTER) 0)))
    (SETQ ES-UNTYI-CHAR NIL)
    (FUNCALL ES-CONSOLE-STREAM ':CLEAR-INPUT))

  ;; The first argument for this operation is an alist of options
  ;; or NIL.  The options currently supported are :FULL-RUBOUT, :PROMPT,
  ;; :REPROMPT and :PASS-THROUGH.

  (:RUBOUT-HANDLER (OPTIONS READ-FUNCTION &REST ARGS TEMP)
    ;; Save whatever the previous input was by cycling through the kill ring.
    ;; If the previous input was the empty string, as in exiting via the :FULL-RUBOUT
    ;; option, don't bother.
    (COND ((NOT (ZEROP (FILL-POINTER)))
	   (SETQ ES-KILL-RING (CIRCULAR-LIST-LAST ES-KILL-RING))
	   (SETQ ES-BUFFER (CAR ES-KILL-RING))))
    ;; Empty the rubout handler buffer of the new buffer.
    (SETF (FILL-POINTER) 0)
    (SETF (TYPEIN-POINTER) 0)

    ;; Prompt if desired.
    (SETQ TEMP (ASSQ ':PROMPT OPTIONS))
    (IF TEMP (FUNCALL (CADR TEMP) #'EDITOR-STREAM NIL))
    ;; Record the position on the screen at which the handler was entered.
    (IF (MEMQ ':READ-CURSORPOS ES-WHICH-OPERATIONS)
	(MULTIPLE-VALUE (ES-X-ORIGIN ES-Y-ORIGIN)
	  (FUNCALL ES-CONSOLE-STREAM ':READ-CURSORPOS)))

    ;; Absorb and echo the untyi'ed char.  We don't need to do this while inside
    ;; the rubout handler since characters get echoed there.  This code runs when
    ;; READ is called from inside the error handler, for instance.
    ;; This is kind of a crock since the rubout handler should decide what to do
    ;; with the character.
    (COND (ES-UNTYI-CHAR (ARRAY-PUSH ES-BUFFER ES-UNTYI-CHAR)
			 (FUNCALL ES-CONSOLE-STREAM ':TYO (LDB %%KBD-CHAR ES-UNTYI-CHAR))
			 (SETQ ES-UNTYI-CHAR NIL)))

    ;; These two specials used for communication up and down the stack.
    ;; First says we're inside the rubout handler, and the second passes
    ;; options down to the editor function.  These specials could be flushed if this
    ;; state information were kept in the stream, but it would have to be explicitly
    ;; turned on and off when a specific stack frame was entered and exited.
    (DO ((RUBOUT-HANDLER T)
	 (TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS OPTIONS))
	(NIL)
      ;; Loop until normal (non-throw) exit, which will pass by the CATCH and DO.
      ;; Each time we enter this loop (after the first time), we are preparing for
      ;; a re-scan of the input string, so reset the scan pointer.
      (SETF (SCAN-POINTER) 0)
      (*CATCH 'RUBOUT-HANDLER
	      (PROGN
		(ERRSET
		  ;; APPLY is more efficient than LEXPR-FUNCALL if the read
		  ;; function takes a &rest argument.
		  (MULTIPLE-VALUE-RETURN (APPLY READ-FUNCTION ARGS)))
		;; On read error, reprint contents of buffer so user can rub out ok.
		;; The ERRSET lets the error message get printed.
		(IF (MEMQ ':READ-CURSORPOS ES-WHICH-OPERATIONS)
		    (MULTIPLE-VALUE (ES-X-ORIGIN ES-Y-ORIGIN)
		      (FUNCALL ES-CONSOLE-STREAM ':READ-CURSORPOS)))
		(FUNCALL ES-CONSOLE-STREAM ':STRING-OUT ES-BUFFER)
		;; If the user makes an error during read-time, swallow
		;; all input until a rubout (or some editing character)
		;; is typed.  When the edit is complete, we will
		;; throw back out of here.  This should be changed to stop
		;; echoing until the edit is done.
		(DO () (NIL)
		  (EDITOR-STREAM ':TYI))))
      ;; When a rubout or other editing operation is done, throws back to the
      ;; catch to reread the input.  But if the :FULL-RUBOUT option was specified
      ;; and everything was rubbed out, we return NIL and the specified value.
      (IF (AND (ZEROP (FILL-POINTER))
	       (SETQ TEMP (ASSQ ':FULL-RUBOUT OPTIONS)))
	  (RETURN NIL (CADR TEMP)))
      )))

;; Returns the "last" element in a circular list, i.e. the cons pointing to the cons we
;; are holding on to.

(DEFUN CIRCULAR-LIST-LAST (LIST)
  (DO ((L (CDR LIST) (CDR L))
       (PREV LIST L))
      ((EQ LIST L) PREV)))

;; (DEFUN CIRCULAR-LIST-LENGTH (LIST)
;;   (IF (NULL LIST) 0
;;       (DO ((L (CDR LIST) (CDR L))
;; 	   (I 1 (1+ I)))
;; 	  ((EQ LIST L) I))))

;; Forward all other operations to the underlying stream rather than
;; STREAM-DEFAULT-HANDLER.  Again, we really need a handle on SELF.

(DEFUN EDITOR-STREAM-DEFAULT-HANDLER (&REST REST)
  (LEXPR-FUNCALL ES-CONSOLE-STREAM REST))


;; Below are two editor functions which form a part of the editor stream.
;; The first is a simple display editor which is a partial emacs, and the
;; second is a printing terminal rubout handler for use via supdup.

;; These functions get called whenever a :TYI message is sent to the stream
;; and we are inside the rubout handler.  If the user types a normal
;; character it is echoed, put in the buffer, and returned.  If the user
;; types a rubout, or any other editing character, any number of editing
;; commands are processed by modifying the buffer, then, when the first
;; non-editing character is typed, a throw is done back to the top level of
;; the read function and the buffered input will be re-scanned.  The
;; character must be typed at the end of the line in order for the throw to
;; take place.  This may be a bug. 

(DEFVAR DISPLAY-EDITOR-COMMAND-ALIST NIL)

(DEFUN DISPLAY-EDITOR
       (STREAM &AUX CH CH-CHAR CH-CONTROL-META COMMAND
	       (OPTIONS TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS)
	       (RUBBED-OUT-SOME NIL)
	       (PASS-THROUGH (CDR (ASSQ ':PASS-THROUGH OPTIONS)))
	       (NUMERIC-ARG NIL))
  ;; Read characters.  If an ordinary character typed and nothing rubbed out,
  ;; return immediately.  Otherwise, let all editing operations complete
  ;; before returning. 
  ;; This {{#/}} is correct, but is it needed?
  (SETF (TYPEIN-POINTER) (FILL-POINTER))
  (*CATCH
    'RETURN-CHARACTER
    (DO () (NIL)
      ;; Read a character from the underlying stream.  This is a kludge for
      ;; speed to avoid making two function calls -- one back to the editor
      ;; stream and then one to the console stream.
      ;; Really should be:
      ;; (LET ((RUBOUT-HANDLER NIL)) (FUNCALL STREAM ':TYI))
      ;; or better (FUNCALL STREAM ':RAW-TYI) which would bypass the rubout handler.
      (SETQ CH (FUNCALL ES-CONSOLE-STREAM ':TYI))
      (SETQ CH-CHAR (LDB %%KBD-CHAR CH))
      (SETQ CH-CONTROL-META (LDB %%KBD-CONTROL-META CH))
      (SETQ COMMAND (ASSQ (CHAR-UPCASE CH) DISPLAY-EDITOR-COMMAND-ALIST))
      
      (COND
	;; Don't touch this character
	((MEMQ CH PASS-THROUGH)
	 (DE-INSERT-CHAR STREAM CH 1 RUBBED-OUT-SOME)
	 (SETQ RUBBED-OUT-SOME T))
	;; A stream editor character of some sort.  The RUBBED-OUT-SOME bit can
	;; only be cleared by entering this function again.  The stream editor
	;; editor function is passed the stream and a numeric argument.
	(COMMAND
	  (SETQ RUBBED-OUT-SOME
		(OR (FUNCALL (CDR COMMAND) STREAM (OR NUMERIC-ARG 1)) RUBBED-OUT-SOME))
	  (SETQ NUMERIC-ARG NIL))
	
	;;Handle control-number, control-u, and control-minus specially.
	((AND (NOT (ZEROP CH-CONTROL-META))
	      ({{#/}} CH-CHAR #/0)
	      ({{#/}} CH-CHAR #/9))
	 (SETQ CH-CHAR (- CH-CHAR #/0))
	 (SETQ NUMERIC-ARG (+ (* (OR NUMERIC-ARG 0) 10.) CH-CHAR)))
	((= (CHAR-UPCASE CH) #{{#/}}/U)
	 (SETQ NUMERIC-ARG (* (OR NUMERIC-ARG 1) 4)))
	
	;; Some other random control character -- beep and ignore
	((NOT (ZEROP CH-CONTROL-META))
	 (FUNCALL STREAM ':BEEP)
	 (SETQ NUMERIC-ARG NIL))
	
	;; Self-inserting character.  Set RUBBED-OUT-SOME since if we return,
	;; we were typing in the middle of the line.  Typing at the end of the
	;; line throws to RETURN-CHARACTER.
	(T (DE-INSERT-CHAR STREAM CH (OR NUMERIC-ARG 1) RUBBED-OUT-SOME)
	   (SETQ NUMERIC-ARG NIL)
	   (SETQ RUBBED-OUT-SOME T))))))

;; A self-inserting character.  A character gets here by being non-control-meta
;; and not having an editing command associated with it.  Or it can get here
;; via the :PASS-THROUGH option.  If a control-meta character gets here, it is
;; echoed as one, but loses its control-meta bits in the buffer.  Also, inserting
;; a character with bucky bits on doesn't currently work since TV-CHAR-WIDTH returns
;; 0 as the width, and TV-TYO can't print it.

(DEFUN DE-INSERT-CHAR (STREAM CH N RUBBED-OUT-SOME &AUX STRING)
  (SETQ STRING (MAKE-ARRAY NIL 'ART-16B N))
  (DOTIMES (I N) (ASET CH STRING I))
  (UNWIND-PROTECT (DE-INSERT-STRING STREAM STRING 0 N RUBBED-OUT-SOME)
		  (RETURN-ARRAY STRING)))

;; Insert a string into the buffer and print it on the screen.  The string
;; is inserted at the current cursor position, and the cursor is left after the string.

(DEFUN DE-INSERT-STRING (STREAM STRING BEGIN END RUBBED-OUT-SOME
				&AUX (WIDTH (- END BEGIN))
				     (TYPEIN-POINTER (TYPEIN-POINTER))
				     (FILL-POINTER   (FILL-POINTER)))
  ;; Increase the size of of the typein buffer, if necessary.
  (IF (= FILL-POINTER (ARRAY-LENGTH ES-BUFFER))
      (ADJUST-ARRAY-SIZE ES-BUFFER (+ (* 2 FILL-POINTER) WIDTH)))
  ;; Make room for the characters to be inserted.  Be sure to increment the fill

  ;; pointer first so that we don't reference outside the active part of the string.
  (INCREMENT FILL-POINTER   WIDTH)
  (INCREMENT TYPEIN-POINTER WIDTH)
  (SETF (FILL-POINTER)   FILL-POINTER)
  (SETF (TYPEIN-POINTER) TYPEIN-POINTER)
  (COPY-VECTOR-SEGMENT (- FILL-POINTER TYPEIN-POINTER)
		       ES-BUFFER (- TYPEIN-POINTER WIDTH)
		       ES-BUFFER TYPEIN-POINTER)
  ;; Now copy the string in.
  (COPY-VECTOR-SEGMENT WIDTH STRING BEGIN ES-BUFFER (- TYPEIN-POINTER WIDTH))
  ;; Update the screen.
  (COND
    ;; If the string is being inserted at the end of the line, then we will return
    ;; from the rubout handler.  If the input buffer has been edited, then we will
    ;; rescan.  If not, we return the first character of the string and leave the
    ;; scan pointer pointing at the second character, thus avoiding a rescan.
    ;; Also, updating the display is easier in this case since we don't have to hack
    ;; insert chars.  This is the case for normal typein -- the scan pointer will
    ;; keep up with the typein pointer.
    ;; I think we need an option so that yanks at the end of the line don't throw
    ;; but remain in the editor.
    ((= TYPEIN-POINTER FILL-POINTER)
     (FUNCALL STREAM ':STRING-OUT STRING BEGIN END)
     (COND (RUBBED-OUT-SOME (*THROW 'RUBOUT-HANDLER T))
	   (T (INCREMENT (SCAN-POINTER))
	      (*THROW 'RETURN-CHARACTER (AREF STRING BEGIN)))))
    ;; If typing in the middle of the line, we just insert the text and don't bother
    ;; rescanning.  That's only done when text appears at the end of the line to keep
    ;; the transcript from being confusing.  This may be the wrong thing.
    ;; No need to update the scan pointer in this case since we're going to throw back
    ;; to the rubout handler.
    ((MEMQ ':INSERT-STRING ES-WHICH-OPERATIONS)
     (FUNCALL STREAM ':INSERT-STRING STRING BEGIN END))
    ;; If the console can't insert chars, simulate it vt52 style.
    (T (FUNCALL STREAM ':CLEAR-EOL)
       (FUNCALL STREAM ':STRING-OUT STRING BEGIN END)
       (FUNCALL STREAM ':STRING-OUT ES-BUFFER TYPEIN-POINTER)
       (DE-CURSOR-MOTION STREAM TYPEIN-POINTER)))
  ;; If we didn't throw out, then the input has been modified.  Back to the editor.
  T)


;; Display Editor Commands

(DEFMACRO DEF-DE-COMMAND ((NAME . CHARS) ARGS . BODY)
  `(PROGN 'COMPILE
	  ,@(MAPCAR #'(LAMBDA (CHAR)
			      `(PUSH '(,CHAR . ,NAME) DISPLAY-EDITOR-COMMAND-ALIST))
		    CHARS)
	  (DEFUN ,NAME ,ARGS . ,BODY)))

;; Moves the cursor to a given position on the screen only.  Does no
;; bounds checking.  If the index into the buffer corresponding with the
;; screen cursor position is known, and it is before the position we want to
;; go to, then this will run faster.

(DEFUN DE-CURSOR-MOTION (STREAM POSITION &OPTIONAL CURRENT-POSITION)
  (IF (AND CURRENT-POSITION ({{#/}} POSITION CURRENT-POSITION))
      (FUNCALL STREAM ':CURSOR-MOTION NIL NIL ES-BUFFER CURRENT-POSITION POSITION)
      (FUNCALL STREAM ':CURSOR-MOTION ES-X-ORIGIN ES-Y-ORIGIN ES-BUFFER 0 POSITION)))

;; Moves the cursor on the screen and in the buffer.  Checks appropriately.

(DEFUN DE-SET-POSITION (STREAM POSITION)
  (SETQ POSITION (MIN (MAX POSITION 0) (FILL-POINTER)))
  (DE-CURSOR-MOTION STREAM POSITION (TYPEIN-POINTER))
  (SETF (TYPEIN-POINTER) POSITION)
  NIL)

;; Reprinting Input

(DEFUN DE-REPRINT-INPUT (STREAM CHAR &AUX PROMPT)
  (SETQ PROMPT (OR (ASSQ ':REPROMPT TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS)
		   (ASSQ ':PROMPT TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS)))
  (IF PROMPT (FUNCALL (CADR PROMPT) STREAM CHAR))
  (MULTIPLE-VALUE (ES-X-ORIGIN ES-Y-ORIGIN) (FUNCALL STREAM ':READ-CURSORPOS))
  (FUNCALL STREAM ':STRING-OUT ES-BUFFER)
  (DE-CURSOR-MOTION STREAM (TYPEIN-POINTER))
  NIL)


;; FORM clears and redisplays input.  Cursor is left where it was before.
(DEF-DE-COMMAND (DE-FORM #\FORM #{{#/}}/L) (STREAM &OPTIONAL IGNORE)
  (FUNCALL STREAM ':CLEAR-SCREEN)
  (DE-REPRINT-INPUT STREAM #\FORM))

;; VT reprints input on next line.  Cursor is left at the end of the typein line
.
(DEF-DE-COMMAND (DE-VT #\VT) (STREAM &OPTIONAL IGNORE)
  (DE-END-OF-BUFFER STREAM)
  (FUNCALL STREAM ':LINE-OUT "{{#/}}")
  (DE-REPRINT-INPUT STREAM #\VT))


;; Moving Around

;; Returns the position of the first newline appearing after POS.

(DEFUN DE-SEARCH-FORWARD-NEWLINE (POS)
  (DO ((FILL-POINTER (FILL-POINTER))
       (I POS (1+ I)))
      ((OR (= I FILL-POINTER) (= (AREF ES-BUFFER I) #\RETURN)) FILL-POINTER)))


;; Returns the position of the first newline appearing before POS-1.
;; Returns -1 if reached the beginning of the buffer.

(DEFUN DE-SEARCH-BACKWARD-NEWLINE (POS)
  (DO ((I (1- POS) (1- I)))
      ((OR (= I -1) (= (AREF ES-BUFFER I) #\RETURN)) I)))

(DEF-DE-COMMAND (DE-BEGINNING-OF-LINE #{{#/}}/A) (STREAM &OPTIONAL IGNORE)
  (DE-SET-POSITION STREAM (1+ (DE-SEARCH-BACKWARD-NEWLINE (TYPEIN-POINTER)))))

(DEF-DE-COMMAND (DE-END-OF-LINE #{{#/}}/E) (STREAM &OPTIONAL IGNORE)
  (DE-SET-POSITION STREAM (DE-SEARCH-FORWARD-NEWLINE (TYPEIN-POINTER))))

(DEF-DE-COMMAND (DE-BEGINNING-OF-BUFFER #{{#/}}/<) (STREAM &OPTIONAL IGNORE)
  (DE-SET-POSITION STREAM 0))

(DEF-DE-COMMAND (DE-END-OF-BUFFER #{{#/}}/>) (STREAM &OPTIONAL IGNORE)
  (DE-SET-POSITION STREAM (FILL-POINTER)))

(DEF-DE-COMMAND (DE-FORWARD-CHAR #{{#/}}/F) (STREAM &OPTIONAL (N 1))
  (DE-SET-POSITION STREAM (+ (TYPEIN-POINTER) N)))

(DEF-DE-COMMAND (DE-BACKWARD-CHAR #{{#/}}/B) (STREAM &OPTIONAL (N 1))
  (DE-SET-POSITION STREAM (- (TYPEIN-POINTER) N)))

(DEF-DE-COMMAND (DE-PREVIOUS-LINE #{{#/}}/P) (STREAM &OPTIONAL (N 1) &AUX LINE-BEGIN INDENT)
  (SETQ LINE-BEGIN (DE-SEARCH-BACKWARD-NEWLINE (TYPEIN-POINTER)))
  (SETQ INDENT (- (TYPEIN-POINTER) LINE-BEGIN))
  (DOTIMES (I N)
    (IF (= LINE-BEGIN -1) (RETURN))
    (SETQ LINE-BEGIN (DE-SEARCH-BACKWARD-NEWLINE LINE-BEGIN)))
  ;; When moving up from a long line to a short line, be sure not to go off the end.
  (DE-SET-POSITION STREAM
		   (+ LINE-BEGIN (MIN INDENT (DE-SEARCH-FORWARD-NEWLINE (1+ LINE-BEGIN))))))

(DEF-DE-COMMAND (DE-NEXT-LINE #{{#/}}/N) (STREAM &OPTIONAL (N 1) &AUX LINE-BEGIN INDENT)
  (SETQ LINE-BEGIN (DE-SEARCH-BACKWARD-NEWLINE (TYPEIN-POINTER)))
  (SETQ INDENT (- (TYPEIN-POINTER) LINE-BEGIN))
  (DOTIMES (I N)
    (COND ((= LINE-BEGIN (FILL-POINTER))
	   (SETQ LINE-BEGIN (DE-SEARCH-BACKWARD-NEWLINE LINE-BEGIN))
	   (RETURN)))
    (SETQ LINE-BEGIN (DE-SEARCH-FORWARD-NEWLINE (1+ LINE-BEGIN))))
  (DE-SET-POSITION STREAM
		   (+ LINE-BEGIN (MIN INDENT (DE-SEARCH-FORWARD-NEWLINE (1+ LINE-BEGIN))))))

;; Deleting Things

;; Deletes a buffer interval as marked by two pointers passed in.  The typein pointer
;; is left at the beginning of the interval.

(DEFUN DE-DELETE-STRING (STREAM BEGIN END &AUX WIDTH TYPEIN-POINTER
				(FILL-POINTER (FILL-POINTER)))
  (SETQ BEGIN (MAX BEGIN 0))
  (SETQ END   (MIN END FILL-POINTER))
  (SETQ WIDTH (- END BEGIN))
  (DE-SET-POSITION STREAM BEGIN)
  ;; Set this after moving the cursor to the beginning of the string.
  (SETQ TYPEIN-POINTER (TYPEIN-POINTER))
  (COND
    ;; Efficiency hack for clearing to the end of line, as in C-K and CLEAR.
    ;; Don't need to add up character widths as needed in :DELETE-STRING.
    ((= END FILL-POINTER)
     (FUNCALL STREAM ':CLEAR-EOL))
    ;; Console can delete characters.  Pass in the string to make variable width fonts win.
    ((MEMQ ':DELETE-STRING ES-WHICH-OPERATIONS)
     (FUNCALL STREAM ':DELETE-STRING ES-BUFFER BEGIN END))
    ;; Console can't delete characters.  Be sure to do a clear-eol to flush those
    ;; characters at the very end.
    (T (FUNCALL STREAM ':CLEAR-EOL)
       (FUNCALL STREAM ':STRING-OUT ES-BUFFER END)
       (DE-CURSOR-MOTION STREAM END)))
  ;; Now actually delete the characters from the buffer.  Do this before decrementing
  ;; the fill pointer so that we don't attempt to reference outside the string.
  (COPY-VECTOR-SEGMENT (- FILL-POINTER END)
		       ES-BUFFER (+ TYPEIN-POINTER WIDTH)
		       ES-BUFFER TYPEIN-POINTER)
  (SETF (FILL-POINTER) (- FILL-POINTER WIDTH))
  ;; If all typein has been deleted and the :FULL-RUBOUT option is
  ;; active, then throw now.  This will throw if the user types rubout
  ;; immediately after entering the read function.
  (IF (AND (ZEROP (FILL-POINTER))
	   (ASSQ ':FULL-RUBOUT TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS))
      (*THROW 'RUBOUT-HANDLER T))
  ;; If it turns out that nothing was deleted, then don't bother rescanning input.
  ({{#/}} WIDTH 0))

(DEF-DE-COMMAND (DE-DELETE-CHAR #{{#/}}/D) (STREAM &OPTIONAL (N 1))
  (DE-DELETE-STRING STREAM (TYPEIN-POINTER) (+ (TYPEIN-POINTER) N)))

(DEF-DE-COMMAND (DE-RUBOUT-CHAR #\RUBOUT) (STREAM &OPTIONAL (N 1))
  (DE-DELETE-STRING STREAM (- (TYPEIN-POINTER) N) (TYPEIN-POINTER)))

;; CLEAR flushes all buffered input.  If the full rubout option is in
;; use, then we will throw out of here.  No need to prompt since the prompt still there.
(DEF-DE-COMMAND (DE-CLEAR #\CLEAR) (STREAM &OPTIONAL IGNORE)
  (DE-DELETE-STRING STREAM 0 (FILL-POINTER)))

;; If at the end of the line, change this to kill only the newline.


(DEF-DE-COMMAND (DE-CLEAR-EOL #{{#/}}/K) (STREAM &OPTIONAL IGNORE)
  (DE-DELETE-STRING STREAM (TYPEIN-POINTER) (DE-SEARCH-FORWARD-NEWLINE (TYPEIN-POINTER))))


;; Word Commands

(DEFUN DE-ALPHABETIC? (CHAR)
  (SETQ CHAR (CHAR-UPCASE CHAR))
  (AND ({{#/}} CHAR #/A) ({{#/}} CHAR #/Z)))

;; Returns the position of the first (non) alphabetic character
;; in the buffer.  If no alphabetic characters between current
;; typein position and end of line, return nil.

(DEFUN DE-SEARCH-FORWARD-ALPHABETIC (POS)
  (DO ((FILL-POINTER (FILL-POINTER))
       (I POS (1+ I)))
      ((= I FILL-POINTER) NIL)
    (IF (DE-ALPHABETIC? (AREF ES-BUFFER I)) (RETURN I))))

(DEFUN DE-SEARCH-FORWARD-NON-ALPHABETIC (POS)
  (DO ((FILL-POINTER (FILL-POINTER))
       (I POS (1+ I)))
      ((= I FILL-POINTER) NIL)
    (IF (NOT (DE-ALPHABETIC? (AREF ES-BUFFER I))) (RETURN I))))

(DEFUN DE-SEARCH-BACKWARD-ALPHABETIC (POS)
  (DO ((I POS (1- I)))
      ((= I -1) NIL)
    (IF (DE-ALPHABETIC? (AREF ES-BUFFER I)) (RETURN I))))

(DEFUN DE-SEARCH-BACKWARD-NON-ALPHABETIC (POS)
  (DO ((I POS (1- I)))
      ((= I -1) NIL)
    (IF (NOT (DE-ALPHABETIC? (AREF ES-BUFFER I))) (RETURN I))))

;; Search for a point N words away and return that point.
;; If on an alphabetic character, skip to the first non alphabetic one.
;; If on a non-alphabetic, skip over non-alphabetics and then over alphabetics,
;; If no alphabetics follow the non-alphabetics, then don't move at all.

(DEFUN DE-SEARCH-FORWARD-WORD (N &AUX (POS (TYPEIN-POINTER)) SEARCH-POS)
  (DO ((I 0 (1+ I)))
      ((= I N) POS)
    (COND ((DE-ALPHABETIC? (AREF ES-BUFFER POS))
	   (SETQ POS (DE-SEARCH-FORWARD-NON-ALPHABETIC POS)))
	  (T (SETQ SEARCH-POS (DE-SEARCH-FORWARD-ALPHABETIC POS))
	     (IF (NOT SEARCH-POS) (RETURN POS))
	     (SETQ POS (DE-SEARCH-FORWARD-NON-ALPHABETIC SEARCH-POS))))
    ;;If within a word and can't find whitespace, leave at right end.
    (IF (NOT POS) (RETURN (FILL-POINTER)))))

;; Search for a point N words back and return that point.
;; If on an alphabetic character, skip to the character just following the
;; first non-alphabetic one.  If on a non-alphabetic, skip over non-alphabetics
;; and then over alphabetics.  If no alphabetics after non-alphabetics, then
;; don't move at all.  Treat cursor on first character of a word as a special case.

(DEFUN DE-SEARCH-BACKWARD-WORD (N &AUX (POS (TYPEIN-POINTER)) SEARCH-POS)
  (DO ((I 0 (1+ I)))
      ((= I N) POS)
    (COND
      ;;At beginning of line -- punt
      ((= POS 0) (RETURN 0))
      ;;Inside a word but not at the beginning of a word.
      ((AND (DE-ALPHABETIC? (AREF ES-BUFFER POS))
	    (DE-ALPHABETIC? (AREF ES-BUFFER (1- POS))))
       (SETQ POS (DE-SEARCH-BACKWARD-NON-ALPHABETIC POS)))
      ;;Within whitespace or at beginning of a word.
      (T (SETQ SEARCH-POS (IF (DE-ALPHABETIC? (AREF ES-BUFFER POS))
		       (1- POS) POS))
	 (SETQ SEARCH-POS (DE-SEARCH-BACKWARD-ALPHABETIC SEARCH-POS))
	 (IF (NOT SEARCH-POS) (RETURN POS))
	 (SETQ POS (DE-SEARCH-BACKWARD-NON-ALPHABETIC SEARCH-POS))))
    ;;If within a word and can't find whitespace, leave at left end.
    (IF (NOT POS) (RETURN 0))
    ;;Leave cursor on first character of the word
    (INCREMENT POS)
    ))

(DEF-DE-COMMAND (DE-FORWARD-WORD #{{#/}}/F) (STREAM &OPTIONAL (N 1))
  (DE-SET-POSITION STREAM (DE-SEARCH-FORWARD-WORD N)))

(DEF-DE-COMMAND (DE-BACKWARD-WORD #{{#/}}/B) (STREAM &OPTIONAL (N 1))
  (DE-SET-POSITION STREAM (DE-SEARCH-BACKWARD-WORD N)))

(DEF-DE-COMMAND (DE-DELETE-WORD #{{#/}}/D) (STREAM &OPTIONAL (N 1))
  (DE-DELETE-STRING STREAM (TYPEIN-POINTER) (DE-SEARCH-FORWARD-WORD N)))

(DEF-DE-COMMAND (DE-RUBOUT-WORD #{{#/}}\RUBOUT) (STREAM &OPTIONAL (N 1))
  (DE-DELETE-STRING STREAM (DE-SEARCH-BACKWARD-WORD N) (TYPEIN-POINTER)))

(DEF-DE-COMMAND (DE-TWIDDLE-CHARS #{{#/}}/T) (STREAM &OPTIONAL IGNORE &AUX DELETE-POINTER STRING)
  (SETQ DELETE-POINTER (TYPEIN-POINTER))
  ;; At end of line, go back two chars; in middle of line, one; at beginning, none.
  (DECREMENT DELETE-POINTER (COND ((= DELETE-POINTER 0) 0)
				  ((= DELETE-POINTER (FILL-POINTER)) 2)
				  (T 1)))
  (SETQ STRING (SUBSTRING ES-BUFFER DELETE-POINTER (+ DELETE-POINTER 2)))
  (DE-DELETE-STRING STREAM DELETE-POINTER (+ DELETE-POINTER 2))

  (SETQ STRING (STRING-NREVERSE STRING))
  (DE-INSERT-STRING STREAM STRING 0 2 T))


;; Kill Ring Commands

;; This doesn't really yank things killed, only previous complete lines typed
;; or the current line.  C-0 C-Y yanks the current line, C-1 C-Y yanks the previous
;; thing typed.  Everything but the last character is brought back, since that
;; is generally the character which triggers the return of the read function.
;; E.g. ) in reading a list, space in reading a symbol, cr in readline, control-c
;; in qsend.  This way, we will be asked for more input rather than having
;; the read function return for us.  Alternately, we could force the editor
;; to retain control and yank back the complete line.
;; We pass in T for RUBBED-OUT-SOME since it isn't currently given
;; to us.  So, we always rescan when yanking at the end of the line.

(DEF-DE-COMMAND (DE-YANK #{{#/}}/Y) (STREAM &OPTIONAL (N 1)
				       &AUX (YANKED (NTH N ES-KILL-RING)))
	(DE-INSERT-STRING STREAM YANKED 0 (MAX 0 (1- (STRING-LENGTH YANKED))) T))

;; Move the thing at the top of the kill ring to the bottom of the kill ring
(DEFUN DE-POP-KILL-RING ()
  (SETF (FIRST ES-KILL-RING) (SECOND ES-KILL-RING))
  (SETF (SECOND ES-KILL-RING) ES-BUFFER)
  (POP ES-KILL-RING))

;; If previous command was a Yank or a Yank-Pop, then remove what was placed there
;; by the command, yank in the top thing on the ring, and cycle the ring forward.
;; Otherwise, treat the command like an ordinary yank, except pop the ring afterward.

(DEF-DE-COMMAND (DE-YANK-POP #{{#/}}/Y) (STREAM &OPTIONAL IGNORE
					   &AUX YANKED YANKED-LENGTH
					   (TYPEIN-POINTER (TYPEIN-POINTER)))
  (PROG ()
   ;; First see if it was a Yank-Pop.
   (SETQ YANKED (CAR (CIRCULAR-LIST-LAST ES-KILL-RING)))
   (SETQ YANKED-LENGTH (1- (STRING-LENGTH YANKED)))
   (IF (AND ({{#/}} TYPEIN-POINTER YANKED-LENGTH)
	    (STRING-EQUAL ES-BUFFER YANKED
			  (- TYPEIN-POINTER YANKED-LENGTH) 0
			  TYPEIN-POINTER YANKED-LENGTH))
       (RETURN))
   ;; Then check for an ordinary yank.  If it matches, then pop it off
   ;; the ring so that we'll get the next thing.
   (SETQ YANKED (SECOND ES-KILL-RING))
   (SETQ YANKED-LENGTH (1- (STRING-LENGTH YANKED)))
   (COND ((AND ({{#/}} TYPEIN-POINTER YANKED-LENGTH)
	       (STRING-EQUAL ES-BUFFER YANKED
			     (- TYPEIN-POINTER YANKED-LENGTH) 0
			     TYPEIN-POINTER YANKED-LENGTH))
	  (DE-POP-KILL-RING)
	  (RETURN)))
   ;; No match.
   (SETQ YANKED NIL))
  ;; The previous command was a yank of some type.  Delete the yanked screen from
  ;; the screen and the buffer.
  (IF YANKED
      (DE-DELETE-STRING STREAM (- TYPEIN-POINTER YANKED-LENGTH) TYPEIN-POINTER))
  ;; Yank the thing at the top of the kill ring, and pop the kill ring
  (DE-YANK STREAM 1)
  (DE-POP-KILL-RING))

#+DEBUG
(DEF-DE-COMMAND (DE-DEBUG #\HELP) (STREAM &OPTIONAL IGNORE)
  (FORMAT STREAM "~%[Fill pointer = ~D  Typein pointer = ~D]~%"
	  (FILL-POINTER) (TYPEIN-POINTER))
  (DE-REPRINT-INPUT STREAM #\HELP))


;; Now a printing (heh, heh) terminal editor.  For poor souls coming via supdup
;; and for purposes of comparison.

(DEFUN PRINTING-EDITOR
       (STREAM &AUX CH PROMPT
	       (OPTIONS TV-MAKE-STREAM-RUBOUT-HANDLER-OPTIONS)
	       (RUBBED-OUT-SOME NIL)
	       (DOING-RUBOUT NIL)
	       (PASS-THROUGH (CDR (ASSQ ':PASS-THROUGH OPTIONS))))
  (*CATCH
    'RETURN-CHARACTER
    (DO () (NIL)
      (SETQ CH (FUNCALL ES-CONSOLE-STREAM ':TYI))
      (COND ((MEMQ CH PASS-THROUGH)
	     (PRINTING-EDITOR-INSERT-CHAR STREAM CH RUBBED-OUT-SOME DOING-RUBOUT))
	    ;; Control-D and Control-U kill the current line, ITS and Twenex style.
	    ;; If nothing in the buffer, don't do anything.
	    ((MEMQ CH '(#{{#/}}/d #{{#/}}/D #{{#/}}/u #{{#/}}/U))
	     (COND ((NOT (ZEROP (FILL-POINTER)))
		    (SETF (FILL-POINTER) 0)
		    (SETQ RUBBED-OUT-SOME T)
		    (COND (DOING-RUBOUT (FUNCALL STREAM ':TYO #/])
					(SETQ DOING-RUBOUT NIL)))
		    (FUNCALL STREAM ':LINE-OUT " XXX")
		    (SETQ PROMPT (OR (ASSQ ':REPROMPT OPTIONS) (ASSQ ':PROMPT OPTIONS)))
		    (IF PROMPT (FUNCALL PROMPT STREAM CH))
		    (IF (ASSQ ':FULL-RUBOUT OPTIONS) (*THROW 'RUBOUT-HANDLER T)))))
	    ;; Control-K or Control-L echo themselves and then reprint the current
	    ;; line, prompting if necessary.
	    ((MEMQ CH '(#\VT #\FORM #{{#/}}/k #{{#/}}/K #{{#/}}/l #{{#/}}/L))
	     (FUNCALL STREAM ':TYO CH)
	     (FUNCALL STREAM ':FRESH-LINE)
	     (SETQ PROMPT (OR (ASSQ ':REPROMPT OPTIONS) (ASSQ ':PROMPT OPTIONS)))
	     (IF PROMPT (FUNCALL PROMPT STREAM CH))
	     (FUNCALL STREAM ':STRING-OUT ES-BUFFER))
	    ;; Echo characters backwards Unix style.
	    ((= CH #\RUBOUT)
	     (MULTIPLE-VALUE (RUBBED-OUT-SOME DOING-RUBOUT)
	       (PRINTING-EDITOR-RUBOUT-CHAR STREAM OPTIONS RUBBED-OUT-SOME DOING-RUBOUT)))
	    ;; Control-W flushes word.
	    ((MEMQ CH '(#{{#/}}/w #{{#/}}/W))
	     (COND ((NOT (ZEROP (FILL-POINTER)))
		    ;; First flush whitespace.
		    (DO ()
			((OR (ZEROP (FILL-POINTER))
			     (DE-ALPHABETIC? (AREF ES-BUFFER (1- (FILL-POINTER))))))
		      (MULTIPLE-VALUE (RUBBED-OUT-SOME DOING-RUBOUT)
			(PRINTING-EDITOR-RUBOUT-CHAR STREAM OPTIONS
						     RUBBED-OUT-SOME DOING-RUBOUT)))
		    ;; Then flush alphabetics.
		    (DO ()
			((OR (ZEROP (FILL-POINTER))
			     (NOT (DE-ALPHABETIC? (AREF ES-BUFFER (1- (FILL-POINTER)))))))
		      (MULTIPLE-VALUE (RUBBED-OUT-SOME DOING-RUBOUT)
			(PRINTING-EDITOR-RUBOUT-CHAR STREAM OPTIONS
						     RUBBED-OUT-SOME DOING-RUBOUT))))
		   (DOING-RUBOUT
		     (FUNCALL STREAM ':LINE-OUT "]")
		     (SETQ DOING-RUBOUT NIL))))
	    ;; Flush random control characters.
	    ((NOT (ZEROP (LDB %%KBD-CONTROL-META CH))) (FUNCALL STREAM ':BEEP))
	    (T (PRINTING-EDITOR-INSERT-CHAR STREAM CH RUBBED-OUT-SOME DOING-RUBOUT))))))

(DEFUN PRINTING-EDITOR-RUBOUT-CHAR (STREAM OPTIONS RUBBED-OUT-SOME DOING-RUBOUT &AUX CH)
  (COND ((NOT (ZEROP (FILL-POINTER)))
	 (SETQ RUBBED-OUT-SOME T)
	 (SETQ CH (ARRAY-POP ES-BUFFER))
	 ;; If we're already rubbing out, then echo character.
	 ;; If we're not rubbing out and the character is a space, then backspace.
	 ;; If this is our first rubout, print "[" and echo.
	 (COND (DOING-RUBOUT (FUNCALL STREAM ':TYO CH))
	       ((= CH #\SPACE) (FUNCALL STREAM ':TYO #\BS))
	       (T (SETQ DOING-RUBOUT T)
		  (FUNCALL STREAM ':TYO #/[)
		  (FUNCALL STREAM ':TYO CH)))
	 ;; Rubbed out everything, and :FULL-RUBOUT option active, so throw.
	 (IF (AND (ZEROP (FILL-POINTER)) (ASSQ ':FULL-RUBOUT OPTIONS))
	     (*THROW 'RUBOUT-HANDLER T))
	 (MVRETURN RUBBED-OUT-SOME DOING-RUBOUT))
	;; Nothing left in the input buffer.   If we were rubbing out, close the
	;; rubout and go to the next line.
	(DOING-RUBOUT
	  (FUNCALL STREAM ':LINE-OUT "]")
	  (MVRETURN RUBBED-OUT-SOME NIL))))

;; This is only called when typing at the end of the line.  There is no
;; such thing as typing in the middle of the line here.

(DEFUN PRINTING-EDITOR-INSERT-CHAR (STREAM CH RUBBED-OUT-SOME DOING-RUBOUT)
  (IF DOING-RUBOUT (FUNCALL STREAM ':TYO #/]))
  (FUNCALL STREAM ':TYO CH)
  (ARRAY-PUSH-EXTEND ES-BUFFER CH)
  (INCREMENT (SCAN-POINTER))
  (IF RUBBED-OUT-SOME
      (*THROW 'RUBOUT-HANDLER T)
      (*THROW 'RETURN-CHARACTER CH)))


#+DEBUG
(PROGN 'COMPILE

;; These functions for debugging the editor stream.
;; Random "read" functions which test various aspects of the stream.

(DEFUN READ-10-CHARS (&OPTIONAL (STREAM STANDARD-INPUT) EOF-OPTION &AUX RESULT)
  (IF (AND (NOT RUBOUT-HANDLER)
	   (MEMQ ':RUBOUT-HANDLER (FUNCALL STREAM ':WHICH-OPERATIONS)))
      ;; If the :FULL-RUBOUT option is activated, this will return NIL.
      ;; Keep looping until a string is returned.
      (DO () (NIL)
	(SETQ RESULT (FUNCALL STREAM ':RUBOUT-HANDLER
			      '((:FULL-RUBOUT T)
				(:PROMPT ES-DEBUG-PROMPT)
				(:REPROMPT ES-DEBUG-REPROMPT))
			      #'READ-10-CHARS STREAM EOF-OPTION))
	(IF RESULT (RETURN RESULT))
	(ES-DEBUG-FULL-RUBOUT STREAM))
      (LET ((CHARS (MAKE-ARRAY NIL ART-STRING 10.)))
	(DOTIMES (I 10.)
	  (ASET (FUNCALL STREAM ':TYI EOF-OPTION) CHARS I))
	CHARS)))

;; A study in compensating for missing capabilities -- :INSERT-CHAR
;; [1] Let the receiving end (console stream) worry about it, press file style.
;;     It would have to maintain a complete screen image.
;; [2] The editor stream can handle some cases.  This would only work during
;;     interactive typein.
;; [3] Let the toplevel program handle it.  It most likely has the screen image
;;     buffered in another form.  But now, everybody has to worry about the missing
;;     capability, although some programs will have better ideas about how to
;;     compensate for them.  I think having both [1] and [3] is the right thing.

(DEFUN READ-10-CHARS-FLASH (&OPTIONAL (STREAM TERMINAL-IO)
				      &AUX (W-O (FUNCALL STREAM ':WHICH-OPERATIONS))
					   CHARS X Y)
  (SETQ CHARS (READ-10-CHARS STREAM))
  (COND ((MEMQ ':READ-CURSORPOS W-O)
	 ;; Kludge.  Hopefully unnecessary in nws.
	 (SETQ X (SYMEVAL-IN-CLOSURE STREAM 'ES-X-ORIGIN))
	 (SETQ Y (SYMEVAL-IN-CLOSURE STREAM 'ES-Y-ORIGIN))
	 (FUNCALL STREAM ':SET-CURSORPOS X Y)
	 (COND ((MEMQ ':INSERT-CHAR W-O)
		(FUNCALL STREAM ':INSERT-CHAR #/[)
		(FUNCALL STREAM ':CURSOR-MOTION NIL NIL CHARS))
	       (T (FUNCALL STREAM ':TYO #/[)
		  (FUNCALL STREAM ':STRING-OUT CHARS)))
	 (FUNCALL STREAM ':TYO #/])))
  (IF (MEMQ ':BEEP W-O) (FUNCALL STREAM ':BEEP))
  (RETURN-ARRAY CHARS))

;; Uses the rubout handler to read characters until a Control-C is typed, but
;; does no consing at all.  Useful for testing if a rubout handler conses
;; and also tests the :PASS-THROUGH option.

(DEFUN CHARACTER-SINK (&OPTIONAL (STOP-CHARS '(#{{#/}}/c #{{#/}}/C)) (STREAM STANDARD-INPUT))
  (COND ((AND (NOT RUBOUT-HANDLER)
	      (MEMQ ':RUBOUT-HANDLER (FUNCALL STREAM ':WHICH-OPERATIONS)))
	 (FUNCALL STREAM ':RUBOUT-HANDLER `((:PASS-THROUGH . ,STOP-CHARS)
					    (:PROMPT ES-DEBUG-PROMPT)
					    (:REPROMPT ES-DEBUG-REPROMPT))
		  #'CHARACTER-SINK STOP-CHARS STREAM))
	;; CHAR-EQUAL throws away bucky bits.
	(T (DO () ((MEMQ (FUNCALL STREAM ':TYI) STOP-CHARS)))
	   (DOTIMES (I 3) (FUNCALL STREAM ':BEEP)))))

(DEFUN ES-DEBUG-PROMPT (STREAM IGNORE)
  (FORMAT STREAM "~&~11A" "Prompt:"))

(DEFUN ES-DEBUG-REPROMPT (STREAM IGNORE)
  (FORMAT STREAM "~11A" "Reprompt:"))

(DEFUN ES-DEBUG-FULL-RUBOUT (STREAM)
  (FORMAT STREAM "[Full Rubout]~%"))


;; Various testing functions which affect a local lisp listener only.
;; Test display editor, printing editor, multiple font stuff.

(DEFUN ES-DEBUG-DISPLAY (&AUX WINDOW STREAM)
  (SETQ WINDOW (<- SELECTED-WINDOW ':PANE))
  (SETQ TERMINAL-IO
	(MAKE-EDITOR-STREAM (MAKE-TV-STREAM (<- WINDOW ':PC-PPR)) #'DISPLAY-EDITOR))
  (<- WINDOW ':STREAM<- TERMINAL-IO))

(DEFUN ES-DEBUG-PRINTING (&AUX WINDOW)
  (SETQ WINDOW (<- SELECTED-WINDOW ':PANE))
  (SETQ TERMINAL-IO
	(MAKE-EDITOR-STREAM (MAKE-TV-STREAM (<- WINDOW ':PC-PPR)) #'PRINTING-EDITOR))
  ;; Make the stream look like a printing console.
  (SET-IN-CLOSURE TERMINAL-IO 'ES-WHICH-OPERATIONS
		  '(:TYI :TYI-NO-HANG :LISTEN :TYO :STRING-OUT :LINE-OUT :FRESH-LINE :BEEP
			 :UNTYI :RUBOUT-HANDLER :CLEAR-INPUT :LISTEN))
  (<- WINDOW ':STREAM<- TERMINAL-IO))

(DEFUN ES-DEBUG-FONT (&OPTIONAL (FONT "TR10B"))
  (SETQ FONT (STRING-UPCASE FONT))
  ;; Can't use LOAD-IF-NEEDED since CPTFONT comes from CPTFON >.
  (IF (NOT (SECOND (MULTIPLE-VALUE-LIST (INTERN FONT "Fonts"))))
      (LOAD (FORMAT NIL "AI:LMFONT;~A" FONT)))
  (SETQ FONT (SYMEVAL (INTERN FONT "Fonts")))
  (TV-REDEFINE-PC-PPR (FUNCALL TERMINAL-IO ':PC-PPR) ':FONTS (LIST FONT)))

(DEFUN ES-DEBUG-OFF (&AUX WINDOW)
  (SETQ WINDOW (<- SELECTED-WINDOW ':PANE))
  (SETQ TERMINAL-IO (TV-MAKE-STREAM (<- WINDOW ':PC-PPR)))
  (<- WINDOW ':STREAM<- TERMINAL-IO))

) ;; End of Debug conditionalization

(DEFUN DE-TV-MAKE-STREAM (PC-PPR)
  (MAKE-EDITOR-STREAM (MAKE-TV-STREAM PC-PPR) #'DISPLAY-EDITOR))

;; This function is for turning the thing on globally.  It clobbers
;; TV-MAKE-STREAM and TOP-WINDOW and should only be called from inside
;; the initial lisp listener.

(DEFUN DE-GLOBAL-ENABLE ()
  (FSET 'TV-MAKE-STREAM  #'DE-TV-MAKE-STREAM)
  (SETQ TERMINAL-IO (DE-TV-MAKE-STREAM CONSOLE-IO-PC-PPR))
  (<- TOP-WINDOW ':STREAM<- TERMINAL-IO))

#-DEBUG (DE-GLOBAL-ENABLE)

;; To do:  Modify kill ring representation to not include current buffer.
;;	   Change font set and default font.
;;	   Make kill ring stuff work for printing editor.
;;	   Make printing editor cross out characters with slashes.  Be careful
;;	    about newlines.
;;	   Write a Macsyma printing editor (regular editor plus ?? command)
{{#/}}{{#/}}
