;;; -*- Mode:LISP; Package:ZWEI; Readtable:T; Base:10 -*-
;;; ** (c) Copyright 1980 Massachusetts Institute of Technology **
;;;  This file contains basic text manipulation functions for ZWEI. 

;>> this file is full of kludgey wired-in knowledge about art-strings and art-fat-strings.

;;; This file provides the following functions:
;;; INSERT INSERT-INTERVAL DELETE-INTERVAL COPY-INTERVAL

;;; Internal function for inserting and deleting.
(DEFUN SET-LINE-LENGTH (LINE LENGTH)
  (LET ((CURRENT-SIZE (ARRAY-LENGTH LINE)))
    (WHEN (> LENGTH CURRENT-SIZE)
      (ADJUST-ARRAY-SIZE LINE (MAX LENGTH (FIX (* (MAX CURRENT-SIZE 30.) 1.3s0)))))
    (SETF (LINE-LENGTH LINE) LENGTH)))

;;; Change the type of an array, used to turn ordinary lines into 16-bit ones
(DEFUN SET-LINE-ARRAY-TYPE (LINE ARRAY-TYPE)
  "Change the array type of LINE to ARRAY-TYPE, preserving the contents and leader.
ARRAY-TYPE should be ART-STRING or ART-FAT-STRING."
  (LET ((NEW-LINE (CREATE-LINE ARRAY-TYPE (ARRAY-LENGTH LINE) (LINE-NODE LINE))))
    (COPY-ARRAY-CONTENTS-AND-LEADER LINE NEW-LINE)
    (STRUCTURE-FORWARD LINE NEW-LINE)))

;;; Make something into a string
(DEFUN ASSURE-STRING (STRING)
  "Coerce STRING into a string, or a fat-string if given a number >= CHAR-CODE-LIMIT"
;character lossage
  (IF (CHARACTERP STRING) (SETQ STRING (CHAR-INT STRING)))
  (COND ((ARRAYP STRING) STRING)
	((AND (NUMBERP STRING) ( STRING CHAR-CODE-LIMIT))
	 (LET ((NEW-STRING (MAKE-ARRAY 1 :TYPE 'ART-FAT-STRING)))
	   (SETF (CHAR NEW-STRING 0) STRING)
	   NEW-STRING))
	(T (STRING STRING))))

;>> this should be a little smarter about line breaks.
;>> If we insert a newline at position two of a line, perhaps a
;>> better strategy than leaving the old line with just one character in it
;>> and consing up a whole new array for the n-2 characters left, would be to
;>> put the short part of the line in a new, shorter string and shift the rest
;>> of the line backwards. I should do some metering.
;>> For example, we --REALLY-- lose big doing something like replacing all the
;>> linefeeds in a file with newlines when the file had no linebreaks in it
;>> originally -- can get this sort of lossage cftping of un*x hosts, etc

;;; Insert the STRING at the BP.
(DEFUN INSERT (BP STRING &OPTIONAL (START 0) END
	       &AUX LINE INDEX LINE-LENGTH FIRST-NEWLINE FIRST-LINE LAST-LINE)
  "Insert a copy of STRING, or the part of it from START to END, into text at BP.
STRING can actually be a string or a character.
START and END are allowed only with strings.
BP is left pointing before the inserted text, unless it is of type :MOVES.
The value is a BP pointing after the inserted text."
  (AND (NOT *BATCH-UNDO-SAVE*) *UNDO-SAVE-SMALL-CHANGES*
       (UNDO-SAVE-NEW-SMALL-CHANGE BP BP))
  (MUNG-BP-INTERVAL BP)
  (SETQ LINE (BP-LINE BP)
	INDEX (BP-INDEX BP)
	LINE-LENGTH (LINE-LENGTH LINE))
;character lossage
  (IF (FIXNUMP STRING) (SETQ STRING (INT-CHAR STRING)))
  (COND ((ARRAYP STRING)
	 (OR END (SETQ END (ARRAY-ACTIVE-LENGTH STRING)))
	 (SETQ FIRST-NEWLINE (%STRING-SEARCH-CHAR #/NEWLINE STRING START END))
	 (COND ((NULL FIRST-NEWLINE)
		;; The string doesn't have any newlines in it.
		(INSERT-WITHIN-LINE LINE INDEX STRING START END))
	       (T
		;; First, construct the "last" line, which is made up of the last
		;; line of the STRING followed by the part of LINE after INDEX.
		;; The copy the first line of STRING into LINE.
		(LET* ((LAST-NEWLINE (STRING-REVERSE-SEARCH-CHAR #/NEWLINE STRING
								 END START))
		       (ARRAY-TYPE (IF (EQ (ARRAY-TYPE STRING) 'ART-FAT-STRING)
				       'ART-FAT-STRING (ARRAY-TYPE LINE)))
		       (LCHARS (- END LAST-NEWLINE 1)))
		  (COND ((AND (= LAST-NEWLINE (1- END))
			      (ZEROP INDEX))
			 ;; Inserting stuff ending with CR at front of line
			 ;; implies we can just shove down the old line
			 (SETQ LAST-LINE LINE)
			 ;; But then we can't use it as the first line.
			 (SETQ FIRST-LINE (CREATE-LINE ARRAY-TYPE
						       (- FIRST-NEWLINE START)
						       (BP-NODE BP)))
			 (SETF (LINE-PREVIOUS FIRST-LINE) (LINE-PREVIOUS LINE))
			 (AND (LINE-PREVIOUS LINE)
			      (SETF (LINE-NEXT (LINE-PREVIOUS LINE)) FIRST-LINE))
			 (COPY-ARRAY-PORTION STRING START FIRST-NEWLINE
					     FIRST-LINE 0 (ARRAY-LENGTH FIRST-LINE))
			 ;; Transfer bps from the front of LINE to FIRST-LINE.
			 (DOLIST (BP (LINE-BP-LIST LINE))
			   (AND (ZEROP (BP-INDEX BP))
				(EQ (BP-STATUS BP) ':NORMAL)
				(MOVE-BP BP FIRST-LINE 0))))
			(T
			 ;; Otherwise, keep the beginning of the line we are inserting in,
			 ;; and make a new line for the tail end of the string.
			 (SETQ FIRST-LINE LINE)
			 (SETQ LAST-LINE (CREATE-LINE ARRAY-TYPE
						      (+ LCHARS (- LINE-LENGTH INDEX))
						      (BP-NODE BP)))
			 ;; Copy the last line of STRING into LAST-LINE.
			 (COPY-ARRAY-PORTION STRING (1+ LAST-NEWLINE) END
					     LAST-LINE 0 LCHARS)
			 ;; Copy the part of LINE after INDEX into LAST-LINE
			 (COPY-ARRAY-PORTION LINE INDEX LINE-LENGTH
					     LAST-LINE LCHARS (ARRAY-LENGTH LAST-LINE))
			 ;; Figure out whether LINE is being changed at all.
			 (OR (AND (= FIRST-NEWLINE START)
				  (= INDEX LINE-LENGTH))
			     (MUNG-LINE LINE))
			 ;; Copy the first line of STRING into LINE.
			 (SET-LINE-LENGTH LINE (+ INDEX (- FIRST-NEWLINE START)))
			 (OR (EQ ARRAY-TYPE (ARRAY-TYPE LINE))
			     (SET-LINE-ARRAY-TYPE LINE 'ART-FAT-STRING))
			 (COPY-ARRAY-PORTION STRING START FIRST-NEWLINE
					     LINE INDEX (LINE-LENGTH LINE))
			 ;; Relocate buffer pointers.
			 (DOLIST (BP (LINE-BP-LIST LINE))
			   (LET ((I (BP-INDEX BP)))
			     (COND ((OR (> I INDEX)
					(AND (= I INDEX)
					     (EQ (BP-STATUS BP) ':MOVES)))
				    (MOVE-BP BP LAST-LINE (+ (- I INDEX) LCHARS))))))))
		  (DO ((PREV-LINE FIRST-LINE THIS-LINE)
		       (THIS-LINE)
		       (PREV-NEWLINE FIRST-NEWLINE NEWLINE)
		       (NEWLINE)
		       (THE-LINE-BEYOND (LINE-NEXT LINE)))
		      (NIL)
		    (COND ((= PREV-NEWLINE LAST-NEWLINE)
			   ;; We are at the end.
			   (AND THE-LINE-BEYOND
				(SETF (LINE-PREVIOUS THE-LINE-BEYOND) LAST-LINE))
			   (SETF (LINE-NEXT LAST-LINE) THE-LINE-BEYOND)
			   (SETF (LINE-NEXT PREV-LINE) LAST-LINE)
			   (SETF (LINE-PREVIOUS LAST-LINE) PREV-LINE)
			   (RETURN NIL)))
		    (SETQ NEWLINE (%STRING-SEARCH-CHAR #/NEWLINE STRING
						       (1+ PREV-NEWLINE) END))
		    (LET ((LENGTH (- NEWLINE PREV-NEWLINE 1)))
		      (SETQ THIS-LINE (CREATE-LINE (ARRAY-TYPE STRING) LENGTH (BP-NODE BP)))
		      (COPY-ARRAY-PORTION STRING (1+ PREV-NEWLINE) NEWLINE
					  THIS-LINE 0 LENGTH)
		      (SETF (LINE-NEXT PREV-LINE) THIS-LINE)
		      (SETF (LINE-PREVIOUS THIS-LINE) PREV-LINE)))
		  (CREATE-BP LAST-LINE LCHARS)))))
	;; These are for INSERT of a non-string
	((EQ STRING #/NEWLINE)
	 ;; Breaking a line.
	 (COND ((ZEROP INDEX)
		;; Shove down the old line if inserting a CR at its front
		(SETQ FIRST-LINE (CREATE-LINE 'ART-STRING 0 (BP-NODE BP)))
		(SETF (LINE-PREVIOUS FIRST-LINE) (LINE-PREVIOUS LINE))
		(AND (LINE-PREVIOUS LINE)
		     (SETF (LINE-NEXT (LINE-PREVIOUS LINE)) FIRST-LINE))
		(SETF (LINE-NEXT FIRST-LINE) LINE)
		(SETF (LINE-PREVIOUS LINE) FIRST-LINE)
		;; Transfer bps from the front of LINE to FIRST-LINE.
		(DOLIST (BP (LINE-BP-LIST LINE))
		  (AND (ZEROP (BP-INDEX BP))
		       (EQ (BP-STATUS BP) ':NORMAL)
		       (MOVE-BP BP FIRST-LINE 0)))
		(CREATE-BP LINE 0))
	       (T
		;; Otherwise, keep the beginning of the line we are inserting in,
		;; and make a new line for the tail end of the string.
		(SETQ LAST-LINE (CREATE-LINE (ARRAY-TYPE LINE) (- LINE-LENGTH INDEX)
					     (BP-NODE BP)))
		(SETF (LINE-NEXT LAST-LINE) (LINE-NEXT LINE))
		(AND (LINE-NEXT LINE)
		     (SETF (LINE-PREVIOUS (LINE-NEXT LINE)) LAST-LINE))
		(SETF (LINE-NEXT LINE) LAST-LINE)
		(SETF (LINE-PREVIOUS LAST-LINE) LINE)
		;; Copy the part of LINE after INDEX into LAST-LINE
		(COPY-ARRAY-PORTION LINE INDEX LINE-LENGTH
				    LAST-LINE 0 (ARRAY-LENGTH LAST-LINE))
		;; Figure out whether LINE is being changed at all.
		(OR (= INDEX LINE-LENGTH)
		    (MUNG-LINE LINE))
		;; Truncate LINE
		(SET-LINE-LENGTH LINE INDEX)
		;; Relocate buffer pointers.
		(DOLIST (BP (LINE-BP-LIST LINE))
		  (LET ((I (BP-INDEX BP)))
		    (COND ((OR (> I INDEX)
			       (AND (= I INDEX)
				    (EQ (BP-STATUS BP) ':MOVES)))
			   (MOVE-BP BP LAST-LINE (- I INDEX))))))
		(CREATE-BP LAST-LINE 0))))
	;; Insert ordinary character -- code simplified from INSERT-WITHIN-LINE
	((CHARACTERP STRING)
	 (AND (GETF (LINE-PLIST LINE) ':DIAGRAM) (BARF "Diagram line"))
	 (SET-LINE-LENGTH LINE (1+ LINE-LENGTH))
	 (IF (AND ( STRING CHAR-CODE-LIMIT)
		  (NEQ (ARRAY-TYPE LINE) 'ART-FAT-STRING))
	     (SET-LINE-ARRAY-TYPE LINE 'ART-FAT-STRING))
	 ;; Move the characters ahead of the inserting forward.
	 (DO ((LF (1- LINE-LENGTH) (1- LF))
	      (LT LINE-LENGTH (1- LT)))
	     ((< LF INDEX))
	   (SETF (CHAR LINE LT) (CHAR LINE LF)))
	 ;; Insert the new characters into the line.
	 (SETF (CHAR LINE INDEX) STRING)
	 ;; Relocate buffer pointers.
	 (DOLIST (BP (LINE-BP-LIST LINE))
	   (LET ((I (BP-INDEX BP)))
	     (IF (OR (> I INDEX)
		     (AND (= I INDEX)
			  (EQ (BP-STATUS BP) ':MOVES)))
		 (SETF (BP-INDEX BP) (1+ I)))))
	 (MUNG-LINE LINE)
	 (CREATE-BP LINE (1+ INDEX)))
	;; Inserting something random
	(T (INSERT BP (STRING STRING) START END))))

(DEFUN INSERT-MOVING (BP STRING &OPTIONAL (START 0) END)
  "Insert STRING at BP, and relocate BP to point after the inserted text.
STRING can be a string or a character.
The value is a temporary buffer pointer also pointing after the inserted text
but not EQ to BP."
  (LET ((NBP (INSERT BP STRING START END)))
    (MOVE-BP BP NBP)
    NBP))

;;; First arg is a BP.  Second is an interval, or second&third are an ordered range.
;;; Insert the stuff from the interval at the BP.
(DEFUN INSERT-INTERVAL (AT-BP FROM-BP &OPTIONAL TO-BP IN-ORDER-P)
  "Insert a copy of an interval into text at AT-BP.
Either pass the interval to insert as the second argument,
or pass a pair of BPs that delimit the interval.
AT-BP is left pointing before the inserted text unless it is of type :MOVES.
The value is a BP pointing after the inserted text."
  (AND (NOT *BATCH-UNDO-SAVE*) *UNDO-SAVE-SMALL-CHANGES*
       (UNDO-SAVE-NEW-SMALL-CHANGE AT-BP AT-BP))
  (MUNG-BP-INTERVAL AT-BP)
  (GET-INTERVAL FROM-BP TO-BP IN-ORDER-P)
  (LET ((AT-LINE (BP-LINE AT-BP))
	(AT-INDEX (BP-INDEX AT-BP))
	(FROM-LINE (BP-LINE FROM-BP))
	(FROM-INDEX (BP-INDEX FROM-BP))
	(TO-LINE (BP-LINE TO-BP))
	(TO-INDEX (BP-INDEX TO-BP)))
    (IF (EQ FROM-LINE TO-LINE)
	;; Insert within AT-LINE.
	(INSERT-WITHIN-LINE AT-LINE AT-INDEX FROM-LINE FROM-INDEX TO-INDEX)
      (LET ((AT-LINE-LENGTH (LINE-LENGTH AT-LINE))
	    (FROM-LINE-LENGTH (LINE-LENGTH FROM-LINE))
	    (ARRAY-TYPE (IF (OR (EQ (ARRAY-TYPE TO-LINE) 'ART-FAT-STRING)
				(EQ (ARRAY-TYPE FROM-LINE) 'ART-FAT-STRING))
			    'ART-FAT-STRING
			  (ARRAY-TYPE AT-LINE)))
	    FIRST-LINE LAST-LINE)
	(COND ((AND (ZEROP TO-INDEX)
		    (ZEROP AT-INDEX))
	       ;;Inserting stuff ending with CR at front of line
	       ;;implies we can just shove down the old line
	       (SETQ LAST-LINE AT-LINE)
	       ;; But then we can't use it as the first line.
	       (SETQ FIRST-LINE (CREATE-LINE ARRAY-TYPE (- FROM-LINE-LENGTH FROM-INDEX)
					     (BP-NODE AT-BP)))
	       (SETF (LINE-PREVIOUS FIRST-LINE) (LINE-PREVIOUS AT-LINE))
	       (AND (LINE-PREVIOUS AT-LINE)
		    (SETF (LINE-NEXT (LINE-PREVIOUS AT-LINE)) FIRST-LINE))
	       (COPY-ARRAY-PORTION FROM-LINE FROM-INDEX FROM-LINE-LENGTH
				   FIRST-LINE 0 (- FROM-LINE-LENGTH FROM-INDEX))
	       (SETF (LINE-PLIST FIRST-LINE) (LINE-PLIST FROM-LINE))
	       ;; Transfer bps from the front of AT-LINE to FIRST-LINE.
	       (DOLIST (BP (LINE-BP-LIST AT-LINE))
		 (AND (ZEROP (BP-INDEX BP))
		      (EQ (BP-STATUS BP) ':NORMAL)
		      (MOVE-BP BP FIRST-LINE 0))))
	      (T
	       ;; Otherwise, keep the beginning of the line we are inserting in,
	       ;; and make a new line for the tail end of the string.
	       (SETQ FIRST-LINE AT-LINE)
	       (SETQ LAST-LINE
		     (CREATE-LINE ARRAY-TYPE (+ TO-INDEX (- AT-LINE-LENGTH AT-INDEX))
				  (BP-NODE AT-BP)))
	       ;; Copy the first part of TO-LINE into the LAST-LINE.
	       (COPY-ARRAY-PORTION TO-LINE 0 TO-INDEX LAST-LINE 0 TO-INDEX)
	       ;; Figure out whether AT-LINE is being changed at all.
	       (OR (AND (ZEROP FROM-LINE-LENGTH)
			(= AT-INDEX (LINE-LENGTH AT-LINE)))
		   (MUNG-LINE AT-LINE))
	       ;; Copy the second part of AT-LINE to LAST-LINE.
	       (COPY-ARRAY-PORTION AT-LINE AT-INDEX AT-LINE-LENGTH
				   LAST-LINE TO-INDEX
				   (+ (- AT-LINE-LENGTH AT-INDEX) TO-INDEX))
	       ;; Copy FROM-LINE into AT-LINE.
	       (SET-LINE-LENGTH AT-LINE (+ AT-INDEX (- FROM-LINE-LENGTH FROM-INDEX)))
	       (DO ((FF FROM-INDEX (1+ FF))
		    (AT AT-INDEX (1+ AT))
		    (FATP (EQ (ARRAY-TYPE AT-LINE) 'ART-FAT-STRING))
		    (CH))
		   (( FF FROM-LINE-LENGTH))
		 (UNLESS (OR (< (SETQ CH (CHAR FROM-LINE FF)) CHAR-CODE-LIMIT) FATP)
		   (SET-LINE-ARRAY-TYPE AT-LINE 'ART-FAT-STRING)
		   (SETQ FATP T))
		 (SETF (CHAR AT-LINE AT) CH))
	       ;; Relocate buffer pointers.
	       (DOLIST (BP (LINE-BP-LIST AT-LINE))
		 (LET ((I (BP-INDEX BP)))
		   (IF (OR (> I AT-INDEX)
			   (AND (= I AT-INDEX)
				(EQ (BP-STATUS BP) ':MOVES)))
		       (MOVE-BP BP LAST-LINE (+ (- I AT-INDEX) TO-INDEX)))))))
	(DO ((PREV-LINE FIRST-LINE THIS-LINE)
	     (THIS-LINE)
	     (NODE (BP-NODE AT-BP))
	     (THE-LINE-BEYOND (LINE-NEXT AT-LINE))
	     (ORIGINAL-LINE (LINE-NEXT FROM-LINE) (LINE-NEXT ORIGINAL-LINE)))
	    ((EQ ORIGINAL-LINE TO-LINE)
	     (AND THE-LINE-BEYOND
		  (SETF (LINE-PREVIOUS THE-LINE-BEYOND) LAST-LINE))
	     (SETF (LINE-NEXT LAST-LINE) THE-LINE-BEYOND)
	     (SETF (LINE-NEXT PREV-LINE) LAST-LINE)
	     (SETF (LINE-PREVIOUS LAST-LINE) PREV-LINE))
	  (SETQ THIS-LINE (COPY-LINE ORIGINAL-LINE NODE))
	  (SETF (LINE-NEXT PREV-LINE) THIS-LINE)
	  (SETF (LINE-PREVIOUS THIS-LINE) PREV-LINE))
	(CREATE-BP LAST-LINE TO-INDEX)))))

(DEFUN INSERT-INTERVAL-MOVING (BP FIRST-BP &OPTIONAL LAST-BP IN-ORDER-P)
  "Insert a copy of an interval into text at AT-BP, relocating AT-BP to point after it.
Either pass the interval to insert as the second argument,
or pass a pair of BPs that delimit the interval.
The value is AT-BP, as modified."
  (LET ((NBP (INSERT-INTERVAL BP FIRST-BP LAST-BP IN-ORDER-P)))
    (MOVE-BP BP NBP)))

(DEFUN INSERT-THING (BP THING)
  "Insert a copy of the string, character or interval THING at BP.
BP is left pointing before the inserted text, unless it is of type :MOVES.
The value is a BP pointing after the inserted text."
  (IF (OR (STRINGP THING) (NUMBERP THING) (SYMBOLP THING))
      (INSERT BP THING)
    ;; This is a kludge, to prevent getting bad data into the mini-buffer
    ;; there may be a more general solution, i am not sure.
    (AND (EQ (BP-TOP-LEVEL-NODE BP) *INTERVAL*)
	 (NULL (SEND *WINDOW* :FONT-ALIST))
	 (DO ((LINE (BP-LINE (INTERVAL-FIRST-BP THING)) (LINE-NEXT LINE)))
	     ((NULL LINE))
	   (SET-LINE-ARRAY-TYPE LINE 'ART-STRING)))
    (INSERT-INTERVAL BP THING NIL T)))

;;; Delete the text between FROM-BP and TO-BP.  FROM-BP and TO-BP must be in order.
;;; Return a BP to the place from which text was deleted.
(DEFUN DELETE-INTERVAL (FROM-BP &OPTIONAL TO-BP IN-ORDER-P &AUX KEPT-LINE)
  "Delete the text of an interval from all intervals that contain it.
Either pass the interval to delete, or pass a pair of BPs that delimit the interval.
Whatever intervals contain the lines containing this text
will be modified by this operation."
  (GET-INTERVAL FROM-BP TO-BP IN-ORDER-P)
  (AND (NOT *BATCH-UNDO-SAVE*) *UNDO-SAVE-SMALL-CHANGES*
       (UNDO-SAVE-NEW-SMALL-CHANGE FROM-BP TO-BP))
  (MUNG-BP-INTERVAL FROM-BP)
  (LET ((FROM-LINE (BP-LINE FROM-BP))
	(FROM-INDEX (BP-INDEX FROM-BP))
	(TO-LINE (BP-LINE TO-BP))
	(TO-INDEX (BP-INDEX TO-BP)))
    (COND ((EQ FROM-LINE TO-LINE)
	   (COND ((= TO-INDEX FROM-INDEX))
		 ((< TO-INDEX FROM-INDEX)
		  (FERROR NIL "The BPs ~S and ~S were not in order." FROM-BP TO-BP))
		 (T
		  (LET ((LINE-LENGTH (LINE-LENGTH FROM-LINE))
			(NDEL (- TO-INDEX FROM-INDEX)))
		    (DO ((FL TO-INDEX (1+ FL))
			 (TL FROM-INDEX (1+ TL)))
			(( FL LINE-LENGTH))
		      (SETF (CHAR FROM-LINE TL) (CHAR FROM-LINE FL)))
		    (SET-LINE-LENGTH FROM-LINE (- LINE-LENGTH NDEL))
		    ;; Relocate buffer pointers.
		    (DOLIST (BP (LINE-BP-LIST FROM-LINE))
		      (LET ((I (BP-INDEX BP)))
			(COND (( I TO-INDEX)
			       (SETF (BP-INDEX BP) (- I NDEL)))
			      (( I FROM-INDEX)
			       (SETF (BP-INDEX BP) FROM-INDEX)))))
		    (MUNG-LINE FROM-LINE)))))
	  (T
	   (MUNG-BP-INTERVAL TO-BP)		;May be in another node
	   (COND ((AND (ZEROP TO-INDEX) (ZEROP FROM-INDEX))
		  ;; If deleting all of from-line and none of to-line,
		  ;; we don't need to touch to-line at all.
		  (SETQ KEPT-LINE TO-LINE)
		  (SETF (GETF (LINE-PLIST TO-LINE) 'PRECEDING-LINES-DELETED-TICK) (TICK))
		  (SETF (LINE-TICK FROM-LINE) 'DELETED))
		 (T
		  ;; Copy characters from end of TO-LINE to replace end of FROM-LINE.
		  (SETQ KEPT-LINE FROM-LINE)
		  (SETF (LINE-TICK TO-LINE) 'DELETED)
		  (LET ((TO-LENGTH (LINE-LENGTH TO-LINE)))
		    (OR (AND (= TO-INDEX TO-LENGTH)
			     (= FROM-INDEX (LINE-LENGTH FROM-LINE)))
			(MUNG-LINE FROM-LINE))
		    (SET-LINE-LENGTH FROM-LINE (+ FROM-INDEX (- TO-LENGTH TO-INDEX)))
		    (DO ((FTL TO-INDEX (1+ FTL))
			 (TFL FROM-INDEX (1+ TFL))
			 (FATP (EQ (ARRAY-TYPE FROM-LINE) 'ART-FAT-STRING))
			 (CH))
			(( FTL TO-LENGTH))
		      (UNLESS (OR (< (SETQ CH (CHAR TO-LINE FTL)) CHAR-CODE-LIMIT) FATP)
			(SET-LINE-ARRAY-TYPE FROM-LINE 'ART-FAT-STRING)
			(SETQ FATP T))
		      (SETF (CHAR FROM-LINE TFL) CH)))))
	   ;; Relocate BPs on the FROM-LINE.
	   (DOLIST (BP (LINE-BP-LIST FROM-LINE))
	     (MOVE-BP BP KEPT-LINE (MIN (BP-INDEX BP) FROM-INDEX)))
	   ;; Relocate BPs on the TO-LINE.
	   (DOLIST (BP (LINE-BP-LIST TO-LINE))
	     (MOVE-BP BP KEPT-LINE
		      (+ FROM-INDEX (MAX 0 (- (BP-INDEX BP) TO-INDEX)))))
	   ;; Loop over intermedidiate lines, relocating bps.
	   (DO ((LINE (LINE-NEXT FROM-LINE) (LINE-NEXT LINE)))
	       ((EQ LINE TO-LINE)
		;; We have reached the TO-LINE.
		;; Splice out all lines FROM-LINE to TO-LINE inclusive except KEPT-LINE.
		(LET ((LINE-BEFORE (LINE-PREVIOUS FROM-LINE))
		      (LINE-AFTER (LINE-NEXT TO-LINE)))
		  (SETF (LINE-NEXT KEPT-LINE) LINE-AFTER)
		  (SETF (LINE-PREVIOUS KEPT-LINE) LINE-BEFORE)
		  (AND LINE-BEFORE (SETF (LINE-NEXT LINE-BEFORE) KEPT-LINE))
		  (AND LINE-AFTER (SETF (LINE-PREVIOUS LINE-AFTER) KEPT-LINE))))
	     (OR LINE
		 (FERROR NIL "The BPs ~S and ~S were not in order." FROM-BP TO-BP))
	     (SETF (LINE-TICK LINE) 'DELETED)
	     (DOLIST (BP (LINE-BP-LIST LINE))
	       (MOVE-BP BP KEPT-LINE FROM-INDEX))))))
  (COPY-BP FROM-BP))

;;; This is an internal function of INSERT and INSERT-INTERVAL
(DEFUN INSERT-WITHIN-LINE (LINE INDEX STRING FROM TO)
  (AND (GETF (LINE-PLIST LINE) ':DIAGRAM) (BARF "Diagram line"))
  (COND ((EQ STRING LINE)
	 (SETQ STRING (SUBSTRING STRING FROM TO)
	       TO (- TO FROM)
	       FROM 0)))
  (LET ((LINE-LENGTH (LINE-LENGTH LINE))
	(STRING-LENGTH (- TO FROM)))
    (LET ((NEW-LINE-LENGTH (+ LINE-LENGTH STRING-LENGTH)))
      (SET-LINE-LENGTH LINE NEW-LINE-LENGTH)
      (OR (EQ (ARRAY-TYPE STRING) 'ART-STRING) (EQ (ARRAY-TYPE LINE) 'ART-FAT-STRING)
	  (SET-LINE-ARRAY-TYPE LINE 'ART-FAT-STRING))
      ;; Move the characters ahead of the inserting forward.
      (DO ((LF (1- LINE-LENGTH) (1- LF))
	   (LT (1- NEW-LINE-LENGTH) (1- LT)))
	  ((< LF INDEX))
	(SETF (CHAR LINE LT) (CHAR LINE LF)))
      ;; Insert the new characters into the line.
      (COPY-ARRAY-PORTION STRING FROM TO LINE INDEX (+ INDEX STRING-LENGTH))
      ;; Relocate buffer pointers.
      (DOLIST (BP (LINE-BP-LIST LINE))
	(LET ((I (BP-INDEX BP)))
	  (IF (OR (> I INDEX)
		  (AND (= I INDEX)
		       (EQ (BP-STATUS BP) ':MOVES)))
	      (SETF (BP-INDEX BP) (+ I STRING-LENGTH)))))
      (MUNG-LINE LINE)
      (CREATE-BP LINE (+ INDEX STRING-LENGTH)))))

(DEFUN INSERT-LINE-WITH-LEADER (LINE AT-LINE)
  "Insert LINE into *INTERVAL* before AT-LINE.
LINE should have a leader of the proper size.  It is actually spliced in."
  (LET ((BP (CREATE-BP AT-LINE 0)))
    (AND (NOT *BATCH-UNDO-SAVE*) *UNDO-SAVE-SMALL-CHANGES*
	 (UNDO-SAVE-NEW-SMALL-CHANGE BP BP))
    (MUNG-BP-INTERVAL BP))
  (LET ((PREV (LINE-PREVIOUS AT-LINE)))
    (COND (PREV
           (SETF (LINE-NEXT PREV) LINE)
           (SETF (LINE-PREVIOUS LINE) PREV))))
  (SETF (LINE-NEXT LINE) AT-LINE)
  (SETF (LINE-PREVIOUS AT-LINE) LINE)
  (SETF (LINE-NODE LINE) (LINE-NODE AT-LINE))
  (SETF (LINE-TICK LINE) *TICK*)
  ;; Now hack the BPs
  (DOLIST (BP (LINE-BP-LIST AT-LINE))
    (WHEN (EQ (BP-STATUS BP) ':NORMAL)
      (SETF (LINE-BP-LIST AT-LINE) (DELQ BP (LINE-BP-LIST AT-LINE)))
      (PUSH BP (LINE-BP-LIST LINE))
      (SETF (BP-LINE BP) LINE))))

;;; This is an internal function of INSERT-INTERVAL
(DEFUN COPY-LINE (LINE NODE)
  (LET ((LEN (LINE-LENGTH LINE)))
    (LET ((NEW-LINE (CREATE-LINE (ARRAY-TYPE LINE) LEN NODE)))
      (COPY-ARRAY-CONTENTS LINE NEW-LINE)
      (SETF (LINE-PLIST NEW-LINE) (LINE-PLIST LINE))
      NEW-LINE)))

;;; This takes either an interval or a pair of BPs, and returns
;;; an interval with the same characters.
(DEFUN COPY-INTERVAL (FROM-BP &OPTIONAL TO-BP IN-ORDER-P &OPTIONAL INTO-INTERVAL)
  "Given an interval, construct a new interval whose text is a copy of the old.
The interval to copy can be specified as an interval object or
as a pair of BPs that delimit the interval.
If INTO-INTERVAL is specified, it is an interval to copy the text into."
  (GET-INTERVAL FROM-BP TO-BP IN-ORDER-P)
  (SETQ INTO-INTERVAL (MAKE-NODE *TICK*))
  (LET ((FROM-LINE (BP-LINE FROM-BP))
	(FROM-INDEX (BP-INDEX FROM-BP))
	(TO-LINE (BP-LINE TO-BP))
	(TO-INDEX (BP-INDEX TO-BP)))
    (COND ((EQ FROM-LINE TO-LINE)
	   (LET ((LEN (- TO-INDEX FROM-INDEX)))
	     (LET ((LINE (CREATE-LINE (ARRAY-TYPE FROM-LINE) LEN INTO-INTERVAL)))
	       (DO ((FLF FROM-INDEX (1+ FLF))
		    (LT 0 (1+ LT)))
		   (( LT LEN))
		 (SETF (CHAR LINE LT) (CHAR FROM-LINE FLF)))
	       (AND (ZEROP FROM-INDEX) (= TO-INDEX (LINE-LENGTH FROM-LINE))
		    (SETF (LINE-PLIST LINE) (LINE-PLIST FROM-LINE)))
	       (SETF (INTERVAL-FIRST-BP INTO-INTERVAL) (CREATE-BP LINE 0 :NORMAL))
	       (SETF (INTERVAL-LAST-BP INTO-INTERVAL) (CREATE-BP LINE LEN :MOVES)))))
	  (T
	   (LET ((FROM-LINE-LENGTH (LINE-LENGTH FROM-LINE)))
	     (LET ((FIRST-LINE (CREATE-LINE (ARRAY-TYPE FROM-LINE)
					    (- FROM-LINE-LENGTH FROM-INDEX)
					    INTO-INTERVAL))
		   (LAST-LINE (CREATE-LINE (ARRAY-TYPE TO-LINE) TO-INDEX INTO-INTERVAL)))
	       ;; Copy text from FROM-LINE to FIRST-LINE.
	       (DO ((FRF FROM-INDEX (1+ FRF))
		    (FIT 0 (1+ FIT)))
		   (( FRF FROM-LINE-LENGTH))
		 (SETF (CHAR FIRST-LINE FIT) (CHAR FROM-LINE FRF)))
	       (AND (ZEROP FROM-INDEX) (SETF (LINE-PLIST FIRST-LINE) (LINE-PLIST FROM-LINE)))
	       ;; Copy text from TO-LINE to LAST-LINE.
	       (DO ((I 0 (1+ I)))
		   (( I TO-INDEX))
		 (SETF (CHAR LAST-LINE I) (CHAR TO-LINE I)))
	       (AND (= TO-INDEX (LINE-LENGTH TO-LINE))
		    (SETF (LINE-PLIST LAST-LINE) (LINE-PLIST TO-LINE)))
	       (DO ((PREV-LINE FIRST-LINE THIS-LINE)
		    (THIS-LINE)
		    (ORIGINAL-LINE (LINE-NEXT FROM-LINE) (LINE-NEXT ORIGINAL-LINE)))
		   ((EQ ORIGINAL-LINE TO-LINE)
		    (SETF (LINE-NEXT PREV-LINE) LAST-LINE)
		    (SETF (LINE-PREVIOUS LAST-LINE) PREV-LINE)
		    (SETF (INTERVAL-FIRST-BP INTO-INTERVAL) (CREATE-BP FIRST-LINE 0 ':NORMAL))
		    (SETF (INTERVAL-LAST-BP INTO-INTERVAL) (CREATE-BP LAST-LINE TO-INDEX ':MOVES)))
		 (SETQ THIS-LINE (COPY-LINE ORIGINAL-LINE INTO-INTERVAL))
		 (SETF (LINE-NEXT PREV-LINE) THIS-LINE)
		 (SETF (LINE-PREVIOUS THIS-LINE) PREV-LINE)))))))
  INTO-INTERVAL)

;;; Make a string whose text is that of the interval.
(DEFUN STRING-INTERVAL (FROM-BP &OPTIONAL TO-BP IN-ORDER-P REMOVE-FONTS-P)
  "Return a string whose text is a copy of the specified interval.
If REMOVE-FONTS-P is non-NIL, all font information is discarded
 and the value is always ART-STRING.
The interval to copy can be specified as an interval object or
as a pair of BPs that delimit the interval."
  (GET-INTERVAL FROM-BP TO-BP IN-ORDER-P)
  (LET ((FROM-LINE (BP-LINE FROM-BP))
	(FROM-INDEX (BP-INDEX FROM-BP))
	(TO-LINE (BP-LINE TO-BP))
	(TO-INDEX (BP-INDEX TO-BP))
	STRING)
    (SETQ STRING (MAKE-ARRAY (COUNT-CHARS FROM-BP TO-BP)
			     :TYPE (IF REMOVE-FONTS-P ART-STRING (ARRAY-TYPE FROM-LINE))))
    (COND ((EQ FROM-LINE TO-LINE)
	   ;; Within a line.  Copy the characters.
	   (DO ((LF FROM-INDEX (1+ LF))
		(ST 0 (1+ ST)))
	       (( LF TO-INDEX))
	     (SETF (CHAR STRING ST) (CHAR FROM-LINE LF))))
	  (T
	   (LET ((ST 0))
	     ;; Copy from the first line.
	     (DO ((FLF FROM-INDEX (1+ FLF))
		  (LEN (LINE-LENGTH FROM-LINE)))
		 (( FLF LEN))
	       (SETF (CHAR STRING ST) (CHAR FROM-LINE FLF))
	       (INCF ST))
	     (SETF (CHAR STRING ST) #/NEWLINE)
	     (INCF ST)
	     ;; Copy from intermediate lines.
	     (DO ((LINE (LINE-NEXT FROM-LINE) (LINE-NEXT LINE)))
		 ((EQ LINE TO-LINE))
	       (DO ((LF 0 (1+ LF))
		    (LEN (LINE-LENGTH LINE)))
		   (( LF LEN))
		 (SETF (CHAR STRING ST) (CHAR LINE LF))
		 (INCF ST))
	       (SETF (CHAR STRING ST) #/NEWLINE)
	       (INCF ST))
	     ;; Copy from the last line.
	     (DO ((TLF 0 (1+ TLF)))
		 (( TLF TO-INDEX))
	       (SETF (CHAR STRING ST) (CHAR TO-LINE TLF))
	       (INCF ST)))))
    STRING))

;;;Insert n copies of a character
(DEFUN INSERT-CHARS (BP CHAR N)
  "Insert N copies of CHAR at BP.
BP is left pointing before the inserted text, unless it is of type :MOVES.
The value is a BP pointing after the inserted text."
  (INSERT BP (MAKE-ARRAY N :TYPE (IF (ZEROP (CHAR-BITS CHAR))
				     'ART-STRING
				   'ART-FAT-STRING)
			 :INITIAL-ELEMENT CHAR)))
