; -*- Mode:LISP; Package:STEVE; Readtable:CL; Base:10 -*-

;Copyright (c) May 1983 by Christopher Eliot
; and Massachusetts Institute of Technology.
;Permission to copy all or part of this material is granted, provided
; that the copies are not made or distributed for resale, the MIT
; copyright notice and reference to the source file and the software
; distribution version appear, and that notice is given that copying
; is by permission of Massachusetts Institute of Technology.

;Editor.
;
;*********
;

(defconstant line-not-modified 0)
(defconstant this-line-modified-bit 1)
(defconstant next-line-modified-bit 2)

;
;These "old and in the way" inconsistent definitions might have caused
;quite a chaotic situation. I hope nobody used them.
;YOW what a loss!
;(defconstant buffer-any-access 0)
;(defconstant buffer-read-only 1)
;(defconstant file-read-only 2)
;
(defconstant buffer-access-any 0)
(defconstant buffer-access-file-read-only 1)
(defconstant buffer-access-buffer-read-only 2)


(defvar *editor-redisplay-count*)

(defflavor buffer (name content modified? file-name 
		   access environment mark-ring mark-ring-index truename
		   narrow-info)
  ()
  :ordered-instance-variables
  :initable-instance-variables
  ;;:gettable-instance-variables
  :outside-accessible-instance-variables)

(defmethod (buffer :print-self) (&optional (stream standard-output) ignore ignore)
  (format stream "#<~s ~a>" 'buffer name))

(defmethod (buffer :not-last-version?) ()
  (if (null file-name)
      nil
      (let ((true (probe-file file-name)))
	(if (null true)
	    (not (null truename))
	    (not (send true :equal truename))))))

(defmethod (buffer :set-file-name) (pathname)
 (when (not (of-type pathname 'pathname))
   (ed-lose "Set buffer file-name to non-pathname"))
 (setq file-name pathname)
 (setq truename (probe-file pathname))
 t)

(defflavor line (buffer buffer-pointers chars char-count previous next
		 bashed? graphic-length parens string-bits) ()
  :ordered-instance-variables
  :initable-instance-variables
  ;;:gettable-instance-variables
  :outside-accessible-instance-variables)

(defmacro bash-line (&optional (line nil))
  (cond ((null line) ;In a LINE defmethod only!
	 `(progn (setq bashed? *editor-redisplay-count*)
		 (setf (buffer-modified? buffer) t)))
	(t `(setf (line-bashed? ,line) *editor-redisplay-count*
		  (buffer-modified? (line-buffer ,line)) t))))

(defmacro line-not-bashed? (line)
  `(<& (line-bashed? ,line) *editor-redisplay-count*))

(defsubst graphic-length (x)
  (if (line-not-bashed? x)
      (ed-lose "Internal Error In Graphic Length")
      (line-graphic-length x)))

(defflavor bp (buffer line position) ()
  :ordered-instance-variables
  :initable-instance-variables
  ;;:gettable-instance-variables
  :outside-accessible-instance-variables)

(defmacro preserving-point-on-error (&body corpus)
  (let ((home (gentemp))
	(lost-big-p (gentemp)))
    `(let ((,lost-big-p t))
       (with-bp (,home *editor-cursor*)
	  (unwind-protect
	     (prog1 (progn ,@corpus)
		    (setq ,lost-big-p nil))
	   (when ,lost-big-p
	     (move-bp *editor-cursor* ,home)))))))

(defun buffer-default-pathname ()
  (merge-pathname-defaults (user-workingdir-pathname) "foo.lsp"))

(defun make-line (buffer prev next &optional
		  (string (make-string 10 :initial-element #\space) str?))
  (let ((line (make-instance 'line
			     :buffer buffer
			     :buffer-pointers nil
			     :chars string
			     :char-count (if str? (string-length string) 0)
			     :previous prev
			     :next next
			     ;;:bashed? this-line-modified-bit
			     :graphic-length *:min-fixnum
			     :parens nil
			     :string-bits :reserved-for-future-use)))
    (bash-line line)
    (if prev (setf (line-next prev) line))
    (if next (setf (line-previous next) line))
    line))

(defun make-bp (buffer line pos)
  (let ((new-bp (make-instance 'bp :buffer buffer :line line :position pos)))
    (send line :add-bp new-bp)
    new-bp))

(defun replace-bp (bp1 bp2)
 (send (bp-line bp2) :remove-bp bp2)
 (setf (bp-buffer bp2) (bp-buffer bp1)
       (bp-line bp2) (bp-line bp1)
       (bp-position bp2) (bp-position bp1))
 (send (bp-line bp2) :add-bp bp2))

(defun copy-bp (bp &optional (drift? nil))
 (make-bp (bp-buffer bp) (bp-line bp) (bp-position bp)))

(defun bp-equal (bp1 bp2)
  (and (eq (bp-line bp1) (bp-line bp2))
       (=& (bp-position bp1) (bp-position bp2))))

;(defun insert-char (buffer line pos char)
;  (send line :insert-char pos char))

(defun insert-string (buffer line pos string)
 (send line :insert-string pos string))

(defun delete-char (buffer line pos)
  (send line :delete-char pos))

;(defun break-line (buffer line pos)
;  (send line :break pos))

;(defun get-char (buffer line pos)
;  (send line :get-char pos))

;(defun move-bp (buffer bp line pos)
;  (send bp :move line pos))



;
;Methods needed.
;

;This returns something which stroes the current BP position.
;It is invalid if anything is done to the buffer.
(defmethod (bp :get-mark) ()
  (list buffer line position))

(defmethod (bp :goto-mark) (mark)
  (if (equal buffer (car mark))
      (send self :move (cadr mark) (caddr mark))
      (ed-lose "Bp Mark For Wrong Buffer")))

;Special cases: beggining of line, end of line, empty line, full line.
(defmethod (line :insert-char) (pos char)
  (when (not (zerop& (logand& (buffer-access buffer)
			  buffer-access-buffer-read-only)))
    (ed-warn :read-only))
  (when (and (not (string-charp char))
	     (null (setq char (coerse-to-string-charp char))))
    (ed-lose "Char Not String Charp"))
  (cond ((char= char #\return) (send self :break pos))
	(t (when (>& pos char-count) (ed-lose "Insert Past End Of Line"))
	   (when (>=& char-count (string-length chars))
	     (send self :grow-line 10))
	   (without-interrupts
	    (bash-line)
	    (%string-replace chars chars (1+& pos) pos (-& char-count pos))
	    (setf (char chars pos) (character char)) ;What else might it be???
	    (setq char-count (1+& char-count))
	    (loop for bp in buffer-pointers
		  if (>=& (bp-position bp) pos) 
		  do (send bp :advance-pos 1))))))

(defmethod (line :insert-string) (pos string
				      &optional (skip 0)
				      (take (string-length string)))
  (unless (zerop& take)
    (when (not (zerop& (logand& (buffer-access buffer)
			    buffer-access-buffer-read-only)))
      (ed-warn :read-only))
    (let ((break-pos (%string-posq #\return string skip take)))
      (cond ((not (null break-pos))
	     (send self :insert-primative-string
		   pos string skip (-& break-pos skip))
	     (send self :break (+& pos (-& break-pos skip)))
	     (cond ((>=& (1+& break-pos) (+& skip take)) nil)
		   ((char= (char string (+& break-pos 1)) #\line)
		    (send next :insert-string 0 string (+& break-pos 2)
			  (-& take (-& break-pos skip) 2)))
		   (t (send next :insert-string 0 string (+& break-pos 1)
			    (-& take (-& break-pos skip) 1)))))
	    (t (send self :insert-primative-string pos string skip take))))))

(defmethod (line :insert-primative-string) (pos string 
						&optional (skip 0)
				                (take (string-length string)))
 (when (not (zerop& (logand& (buffer-access buffer)
			 buffer-access-buffer-read-only)))
  (ed-warn :read-only))
 (when (>& pos char-count) (ed-lose "Insert Past End Of Line"))
 (without-interrupts
  (let ((new-string (make-string (+& char-count take) :initial-element #\space)))
   (%string-replace new-string chars 0 0 pos) ;Copy first part.
   (%string-replace new-string chars (+& pos take) pos (-& char-count pos))
   ;Copy end before clobbering it with the new-middle.
   (%string-replace new-string string pos skip take) ;middle part.
   (setq chars new-string)
   (setq char-count (+& char-count take))
   (bash-line)
   (loop for bp in buffer-pointers
	 if (>=& (bp-position bp) pos)
	 do (send bp :advance-pos take)))))

(defmethod (bp :insert-char) (char &optional (repeat 1))
  (loop repeat repeat
	do (send line :insert-char position char)))

(defmethod (bp :insert-string) (string)
 (send line :insert-string position string))

(defmethod (line :replace-char) (pos char)
 (when (not (zerop& (logand& (buffer-access buffer)
			 buffer-access-buffer-read-only)))
  (ed-warn :read-only))
 (cond ((minusp& pos) (ed-lose "Replace Char Before Line"))
       ((<& pos char-count) 
	(bash-line)
	(setf (char chars pos) char)
	(loop for bp in buffer-pointers
		  if (=& (bp-position bp) pos) 
		  do (send bp :advance-pos 1)))
       (t (send self :delete-char pos)
	  (send self :insert-char pos char))))

;Remember about beginning/end of line.
(defmethod (bp :advance-pos) (count)
  (let ((new-position (+& position count)))
    (cond ((and (not (minusp& new-position))
		(<=& new-position (line-char-count line)))
	   (setq position new-position))
	  ((minusp& new-position)
	   (send self :to-end-of-previous-line)
	   (send self :advance-pos (1+& new-position)))
	  (t (setq new-position (-& new-position 1 (line-char-count line)))
	     (when (null (line-next line)) (ed-warn :move-past-end))
	     (send self :move (line-next line) 0)
	     (send self :advance-pos new-position)))))

(defmethod (bp :advance-pos-no-error) (count)
  ;;Return count unused or NIL to signify success.
  (let ((new-position (+& position count)))
    (cond ((and (not (minusp& new-position))
		(<=& new-position (line-char-count line)))
	   (setq position new-position)
	   ;;Return NIL to signify no errors.
	   nil)
	  ((minusp& new-position)
	   (cond ((null (line-previous line))
		  (setq position 0)
		  ;;Error value.
		  new-position)
		 (t (send self :to-end-of-previous-line)
		    (send self :advance-pos-no-error (1+& new-position)))))
	  (t (setq new-position (-& new-position 1 (line-char-count line)))
	     (cond ((null (line-next line))
		    (setq position (line-char-count line))
		    ;;Error value takes into account no <CR> at buffer end.
		    (1+& new-position))
		   (t (send self :move (line-next line) 0)
		      (send self :advance-pos-no-error new-position)))))))

;What if previous line is empty?
(defmethod (bp :to-end-of-previous-line) ()
  (if (null (line-previous line))
      (ed-warn :move-past-start)
      (send self :move (line-previous line)
	    (line-char-count (line-previous line)))))

;What if previous line is empty?
(defmethod (bp :backward-over-crlf) ()
  (if (null (line-previous line))
      nil
      (send self :move (line-previous line)
	    (line-char-count (line-previous line)))))

(defmethod (bp :to-beginning-of-next-line) ()
  (if (null (line-next line))
      (ed-warn :move-past-end)
      (send self :move (line-next line) 0)))
 
(defmethod (bp :forward-over-crlf) ()
  (if (null (line-next line))
      nil
      (send self :move (line-next line) 0)))

(defmethod (line :grow-line) (count)
  (let ((oldlen (simple-string-length chars)))
    (let ((new (make-string (+& count oldlen) :initial-element #\space)))
      (setq chars (%string-replace new chars 0 0 oldlen)))))

(defmethod (bp :delete-char) ()
  (send line :delete-char position))

;What about end of lines?
(defmethod (line :delete-char) (pos)
 (when (not (zerop& (logand& (buffer-access buffer)
			 buffer-access-buffer-read-only)))
  (ed-warn :read-only))
  (if (>& pos char-count) (ed-lose "Delete Past End Of Line"))
  (if (=& pos char-count)
      (if (null next)
	  (ed-lose "Delete End Of Buffer")
	  (send next :delete-line-separator))
      (without-interrupts
       (bash-line)
       (%string-replace chars chars pos (1+& pos) (-& char-count pos 1))
       (setq char-count (1-& char-count))
       (loop for bp in buffer-pointers
	     if (>& (bp-position bp) pos)
	     do (send bp :advance-pos -1)))))

(defmethod (bp :set-pos) (pos)
  (if (and (not (minusp& pos)) (<=& pos (line-char-count line)))
      (setq position pos)
      (send self :advance-pos (-& pos position))))
      
(defmethod (line :delete-line-separator) ()
 (when (not (zerop& (logand& (buffer-access buffer)
			 buffer-access-buffer-read-only)))
  (ed-warn :read-only))
  (if (null previous) (ed-warn :move-past-start))
  (without-interrupts
   (let* ((prev-linelen (line-char-count previous))
	  (new-line-length (+& prev-linelen char-count))
	  (prev-line (line-chars previous)))
     (when (<& (string-length prev-line) new-line-length)
      (send previous :grow-line (-& new-line-length (string-length prev-line)))
      (setq prev-line (line-chars previous))) ;Do it again, Sam.
     (%string-replace prev-line chars prev-linelen 0 char-count)
     (bash-line)
     (bash-line previous) ;Note that this is ineffiecient.
     (setf (line-char-count previous) (+& prev-linelen char-count)
	   (line-next previous) next)
     (if next (setf (line-previous next) previous))
     (loop for bp in buffer-pointers
	   do (send bp :move previous (+& (bp-position bp) prev-linelen))))))

(defmethod (line :set-next) (line)
 (when (not (zerop& (logand& (buffer-access buffer)
			 buffer-access-buffer-read-only)))
  (ed-warn :read-only))
  (when (eq line self)
	(ed-lose "Internal Error Attempt To Create Circularity In Buffer"))
  (do ((dead next (line-next dead))
       (n-chars char-count))
      ((or (null dead) (eq dead line)))
    (mapcar #'(lambda (old-bp) (send old-bp :move self n-chars))
	    (line-buffer-pointers dead)))
  (setq next line)
  (unless (null line) (setf (line-previous line) self)))

(defmethod (line :get-char) (pos)
  (if (or (minusp& pos) (>=& pos char-count))
      (if (minusp& pos) (ed-lose "Get Char With Negative Position")
	  (ed-lose "Get Char After Line")))
  (char chars pos))

(defmethod (bp :get-char) ()
  (if (>=& position (line-char-count line))
      (if (>& position (line-char-count line))
	  (ed-lose "Get Char After Line")
	  #\return)
      (char (line-chars line) position)))

(defmethod (bp :peek-char-backward) ()
 (send self :advance-pos -1)
 (send self :get-char-forward))

(defmethod (bp :get-char-forward) ()
  (cond ((>=& position (line-char-count line))
	 (send self :to-beginning-of-next-line)
	 #\return)
	(t (prog1 (schar (line-chars line) position)
		  (setq position (1+& position))))))

(defmethod (bp :get-char-backward) ()
  (cond ((plusp& position)
	 (setq position (1-& position))
	 (schar (line-chars line) position))
	(t (send self :to-end-of-previous-line)
	   #\return)))

(defmacro bp-char-slashified? (bp)
  `(let ((start-index (1-& (bp-position ,bp))))
     (unless (zerop& start-index)
       (let ((line-chars (line-chars (bp-line ,bp))))
	 (oddp (loop for index from start-index downto 0
		     while (of-syntax (schar line-chars index) character-quote)
		       count :each-character-quote))))))

(defmethod (bp :slashified?) ()
  (bp-char-slashified? self))

(defmethod (bp :move) (new-line new-pos)
  (if (and (eq new-line line) 
           (not (minusp& new-pos))
	   (<=& new-pos (line-char-count new-line)))
      (setq position new-pos)
      (progn (send line :remove-bp self)
	     (setq line new-line
		   position new-pos)
	     (send new-line :add-bp self))))

(defmethod (bp :expire) ()
  (send line :remove-bp self)
  (setq buffer "Expired BP")
  (setq line nil)
  (setq position nil))

(defmethod (bp :move-adjusting-pos) (new-line new-pos)
  (if (null new-line) (ed-lose "Move To Non-Line"))
  (send line :remove-bp self)
  (setq line new-line)
  (if (>& new-pos (line-char-count new-line))
      (setq new-pos (line-char-count new-line)))
  (setq position new-pos)
  (send new-line :add-bp self))

(defmethod (line :remove-bp) (bp)
  (setq buffer-pointers (delq bp buffer-pointers)))

(defmethod (line :add-bp) (bp)
  (if (minusp& (bp-position bp))
      (ed-lose "Bp Added With Negative Position"))
  (if (>& (bp-position bp) char-count)
      (ed-lose "Bp Added To Line Is Out Of Range"))
  (push bp buffer-pointers))

(defmethod (line :break) (pos)
  (when (not (zerop& (logand& (buffer-access buffer)
			  buffer-access-buffer-read-only)))
    (ed-warn :read-only))
  (let ((new-line (make-line buffer self next)))
    (bash-line)
    (loop for i upfrom 0
	  for j from pos below char-count
	  do (send new-line :insert-char i (schar chars j)))
    (loop for bp in buffer-pointers
	  if (>=& (bp-position bp) pos) ;This has been changed!!!
	  do (send bp :move new-line (-& (bp-position bp) pos ; 1
					 )))
    (setq char-count pos)))

(defmethod (bp :beginning-of-line?) ()
  (zerop& position))

(defmethod (bp :end-of-line?) ()
  (=& position (line-char-count line)))

(defmethod (bp :end-of-buffer?) ()
  (null (line-next line)))

(defmethod (bp :beginning-of-buffer?) ()
  (null (line-previous line)))

;(defmethod (bp :delete-chars) () ;This is being phased out. don;t use it.
;  (send line :delete-chars-from-position position))

(defmethod (bp :delete-chars-from-position) ()
  (send line :delete-chars-from-position position))

(defmethod (bp :delete-characters) (n)
  ;;COding this so it is faster would speed up KILLING.
  (if (plusp& n)
      (do ((i 1 (1+& i)))
	  ((>& i n) t)
	(send self :delete-char))
      (progn (send self :advance-pos n)
	     (setq n (-& n))
	     (do ((i 1 (1+& i)))
		 ((>& i n) t)
	       (send self :delete-char)))))

;(defmethod (bp :delete-characters) (n)
;  ;;This won't work for negative N because line <= :delete-characters won't.
;  (send line :delete-characters position n))

(defmethod (line :delete-characters) (pos n)
  ;;Code this for negative N. (Not tested.)
  (unless (zerop& n)
    (when (not (zerop& (logand& (buffer-access buffer) 
			    buffer-access-buffer-read-only)))
      (ed-warn :read-only))
    (unless (<=& pos char-count)
      (ed-lose "Reference Past End Of Line"))
    (cond ((>& n (-& char-count pos))
	   (when (null next) (ed-lose "Delete End Of Buffer"))
	   (send next :delete-line-separator)
	   (send self :delete-characters pos (1-& n)))
	  ;;Check for (and (minusp&  n) (<& pos (-& n))) here.
	  ;;This clause is for negative N and new (i.e. partially tested.)
	  ;;Monday the twenty-fifth of July, 1983; 6:15:45 pm
	  ((minusp& (+& pos n))
	   (when (null previous) (ed-lose "Delete beginning of buffer"))
	   (send previous :delete-characters (line-char-count previous)
		 (+& n pos))
	   (send self :delete-characters 0 pos))
	  (t (%string-replace chars chars pos (+& pos n) (-& char-count pos n))
	     (setq char-count (-& char-count n))
	     (bash-line) 
	     (loop for bp in buffer-pointers
		   for bppos = (bp-position bp)
		   if (>& bppos pos)
		   do (send bp :set-pos (max& pos (-& bppos n))))))))

(defmethod (line :delete-chars-from-position) (pos)
  (when (not (zerop& (logand& (buffer-access buffer)
			  buffer-access-buffer-read-only)))
    (ed-warn :read-only))
  (unless (<=& char-count pos)
    (bash-line)
    (loop for bp in buffer-pointers
	  if (>& (bp-position bp) pos)
	  do (send bp :set-pos pos)) ;Put BPs on the end of the line.
				     (setq char-count pos)))

(defmethod (line :match-string) (string pos)
  (and (<=& (string-length string)
	    (-& char-count pos))
       (loop for i from 0 below (string-length string)
	     for p from pos below char-count
	     always (char-equal (char string i) (schar chars p)))))

;Create a new line following this one.
(defmethod (line :insert-line) (string)
  (when (not (zerop& (logand& (buffer-access buffer)
			  buffer-access-buffer-read-only)))
    (ed-warn :read-only))
  (bash-line)
  (setq next (make-line buffer self next string)))

(defmethod (line :translate) (function &optional (start 0) (count char-count))
  (bash-line)
  (loop for i from start below (min& count char-count)
	do (setf (schar chars i) (funcall function (schar chars i)))))


;
;More internal cruft.
;


;Buffer streams.
;These are used to read from a buffer directly into NIL, or vice versa.
;

(defflavor forward-buffer-stream (buffer line position) 
  (bp input-stream output-stream)
  :ordered-instance-variables
  :initable-instance-variables
  :outside-accessible-instance-variables)

(defun make-buffer-stream (bp-start &aux (strm))
  (setq strm (make-instance 'forward-buffer-stream
			    :buffer (bp-buffer bp-start)
			    :line (bp-line bp-start)
			    :position (bp-position bp-start)))
  (send (bp-line strm) :add-bp strm)
  strm)

;Now for buffer streams.
(defmethod (forward-buffer-stream :tyi) ()
  (prog1 (char-code (send self :get-char))
	 (send self :advance-pos 1)))

(defmethod (forward-buffer-stream :read-char) ()
  (prog1 (send self :get-char)
	 (send self :advance-pos 1)))

(defmethod (forward-buffer-stream :peek-char) ()
  (send self :get-char))

(defmethod (forward-buffer-stream :untyi) (&optional char)
  (send self :advance-pos -1))

(defmethod (forward-buffer-stream :unread-char) (&optional char)
  (send self :advance-pos -1))

(defmethod (forward-buffer-stream :unread-n-chars) (n)
  (send self :advance-pos (-& n)))

;What is the eof-value for?
(defmethod (forward-buffer-stream :readline) (&optional eof-value)
  (let ((v1 (string-subseq (line-chars line) 0 (line-char-count line)))
	(v2 (null (line-next line))))
    (when (not v2)
      (send self :to-beginning-of-next-line))
    (values v1 v2)))
 
;These are used to implement lisp-listeners.

(defmethod (forward-buffer-stream :write-char) (char)
  (case char
    (#\return (send line :break position))
    (#\line nil)
    (t (send line :insert-char position char))))

(defmethod (forward-buffer-stream :terpri) ()
  (send line :break position))


;
;region functions.
;
;A region is defined by two BPs.
;We will want to be able to kill a region, move it, load it etc.
;
;This returns its arguments as two values, and guarantees that the
;first value is not after the second value.
(defun order-bps (bp-1 bp-2 &aux (bp-line-1 (bp-line bp-1))
		                 (bp-line-2 (bp-line bp-2)))
  (when (or (null bp-1) (null bp-2))
    (ed-lose "No mark. (Null buffer pointer.)"))
  (if (eq bp-line-1 bp-line-2)
      (if (<& (bp-position bp-1) (bp-position bp-2))
	  (values bp-1 bp-2)
	  (values bp-2 bp-1))
      (loop with bp-line-1 = (bp-line bp-1)
	    with bp-line-2 = (bp-line bp-2)
	    for line-1 first bp-line-1 then (line-next line-1)
	    for line-2 first bp-line-2 then (line-next line-2)
	    if (null line-1) return (values bp-2 bp-1)
	    if (null line-2) return (values bp-1 bp-2)
	    if (eq bp-line-1 line-2) return (values bp-2 bp-1)
	    if (eq bp-line-2 line-1) return (values bp-1 bp-2))))

(defun order-lines (line-1 line-2)
  (if (eq line-1 line-2)
      (values line-1 line-2)
      (loop for line1 first line-1 then (line-next line1)
	    for line2 first line-2 then (line-next line2)
	    if (null line1) return (values line-2 line-1)
	    if (null line2) return (values line-1 line-2)
	    if (eq line1 line-2) return (values line-1 line-2)
	    if (eq line2 line-1) return (values line-2 line-1))))

(defmethod (bp :forward-over-word) ()
  (let ((chrs (line-chars line))
	(max (line-char-count line)))
    (when (and (plusp& position)
	       (=& 1 (get-char-syntax-bit syntax-table
					  (schar chrs (1-& position))
					  character-quote)))
      (setq position (1+& position)))
    (loop for i from position below max
	  for chr = (schar chrs i)
	  while (word-char? chr)
	  if (=& 1 (get-char-syntax-bit syntax-table chr character-quote))
	  do (setq i (1+& i))
	  finally (setq position i))
    self))

(defmethod (bp :forward-over-not-word) ()
  (loop for chrs = (line-chars line)
	for max = (line-char-count line)
	until (loop for i from position below max
		    for chr = (schar chrs i)
		    if (word-char? chr)
		    do (progn (setq position i) (return t))
		    if (=& 1 (get-char-syntax-bit syntax-table chr 
						  character-quote))
		    do (progn (setq position (1+& i)) (return t)))
	if (null (line-next line)) 
	do (progn (setq position max)
		  (return nil))
	do (send self :to-beginning-of-next-line))
  self)

(defmethod (bp :backward-over-word) ()
  (let ((chrs (line-chars line)))
    (do ((i (1-& position) (1-& i)))
	((or (minusp& i)
	     (and (not (word-char? (schar chrs i)))
		  (or (zerop& i)
		      (zerop& (get-char-syntax-bit syntax-table 
					       (schar chrs (1-& i))
					       character-quote)))))
	 (setq position (1+& i)))))
  self)

(defmethod (bp :backward-over-not-word) ()
  (loop for chrs = (line-chars line)
	until (do ((i (1-& position) (1-& i)))
		  ((minusp& i) nil)
		  (when (or (word-char? (schar chrs i))
			    (and (plusp& i)
				 (=& 1 (get-char-syntax-bit
					 syntax-table (schar chrs (1-& i))
					 character-quote))))
		    (setq position i)
		    (send self :advance-pos 1)
		    (return t)))
	if (null (line-previous line))
	do (progn (setq position 0) (return nil))
	do (send self :to-end-of-previous-line))
  self)

(defmethod (bp :forward-over-string) ()
  (let ((delimiter (send self :get-char)))
    (when (and (of-syntax delimiter string-quote)
	       (not (bp-char-slashified? self)))
      (send self :advance-pos 1)
      (loop do (send self :forward-to-char delimiter)
	    until (not (bp-char-slashified? self))
	    do (send self :advance-pos 1))
      (send self :advance-pos 1)))
  self)

(defmethod (bp :backward-over-string) ()
  (let ((delimiter (send self :get-char-backward)))
    (cond ((and (of-syntax delimiter string-quote)
		(not (bp-char-slashified? self)))
	     (loop do (send self :backward-to-char delimiter)
		      (send self :advance-pos -1)
		   until (not (bp-char-slashified? self))))
	  (:not-a-string
	     (send self :advance-pos 1))))
  self)


(defmethod (bp :forward-over-atom) ()
  (let ((chrs (line-chars line))
	(max (line-char-count line)))
    (cond ((and (<& position max)
		(=& 1 (get-char-syntax-bit syntax-table
					   (schar chrs position)
					   string-quote)))
	   (send self :forward-over-string))
	  (t (do ((i position (1+& i)))
		 ((or (>=& i max)
		      (and (not (atom-char? (schar chrs i)))
			   (or (zerop& i)
			       (zerop& (get-char-syntax-bit
				     syntax-table (schar chrs (1-& i))
				     character-quote)))))
		  (setq position i))))))
  self)

(defmethod (bp :backward-over-atom) ()
  (let ((chrs (line-chars line)))
    (cond ((zerop& position))
	  ((=& 1 (get-char-syntax-bit syntax-table 
				      (schar chrs (1-& position))
				      string-quote))
	   (send self :backward-over-string))
	  (t (do ((i (1-& position) (1-& i)))
		 ((or (minusp& i)
		      (and (not (atom-char? (char chrs i)))
			   (or (zerop& i)
			       (zerop& (get-char-syntax-bit
				     syntax-table (schar chrs (1-& i))
				     character-quote)))))
		  (setq position (1+& i)))))))
  self)

(defmethod (bp :forward-over-horizontal-white-space) ()
  (let ((chrs (line-chars line))
	(max (line-char-count line)))
    (do ((i position (1+& i)))
	((or (>=& i max) (not (white-space? (schar chrs i))))
	 (setq position i))))
  self)

(defmethod (bp :backward-over-horizontal-white-space) ()
  (let ((chrs (line-chars line)))
    (do ((i (1-& position) (1-& i)))
	((or (minusp& i) (not (white-space? (schar chrs i))))
	 (setq position (1+& i)))))
  self)

(defmethod (bp :forward-over-white-space) ()
  (do ()
      ((do ((chrs (line-chars line))
	    (max (line-char-count line))
	    (i position (1+& i)))
	   ((>=& i max) (setq position max) nil)
	   (when (not (white-space? (schar chrs i))) 
	    (setq position i)
	    (return t)))
       T)
      (if (line-next line)
	  (send self :move (line-next line) 0) ;To-beginning-of-next-line.
	  (return nil)))
 self)

(defmethod (bp :backward-over-white-space) ()
  (do ((prev))
      ((do ((chrs (line-chars line))
	    (i (1-& position) (1-& i)))
	   ((minusp& i) (setq position 0) nil)
	   (when (not (white-space? (schar chrs i))) 
	    (setq position (1+& i))
	    (return t)))
       T)
      (if (setq prev (line-previous line))
	  (send self :move prev (line-char-count prev))
	  (return nil)))
 self)

;The above two methods could be written like this:
;(defmethod (bp :forward-over-white-space) ()
;  (send self :forward-over-syntax white-space-mask))
;
;(defmethod (bp :backward-over-white-space) ()
;  (send self :backward-over-syntax white-space-mask))


(defmethod (bp :forward-over-syntax) (syntax-mask)
  (loop until
	(loop with chrs = (line-chars line)
	      with max = (line-char-count line)
	      until (>=& position max)
	      thereis (zerop& (logand& (get-char-syntax syntax-table 
						    (schar chrs position))
				   syntax-mask))
	      do (setq position (1+& position)))
	until (or (null (line-next line))
		  (zerop& (logand& (get-char-syntax syntax-table #\return)
			       syntax-mask)))
	do (send self :move (line-next line) 0))
  self)

(defmethod (bp :backward-over-syntax) (syntax-mask)
  (loop until 
	(loop with chrs = (line-chars line)
	      until (zerop& position)
	      thereis (zerop& (logand& (get-char-syntax
				     syntax-table (schar chrs (1-& position)))
				   syntax-mask))
	      do (setq position (1-& position)))
	until (or (null (line-previous line)) 
		  (zerop& (logand& (get-char-syntax syntax-table #\return) 
			       syntax-mask)))
	do (send self :to-end-of-previous-line))
  self)

;;; These methods will move a buffer buffer to the beginning or end of
;;; the buffer rather than signalling an error if the character is not found.
(defmethod (bp :forward-to-char) (target-char)
  (loop for line* first line then (line-next line*)
	for pos first position then 0
	for i = (%string-posq target-char
			      (line-chars line*) pos (-& (line-char-count line*) pos))
	if (not (null i))
	return (send self :move line* i)
	if (null (line-next line*))
	return (send self :move line* (line-char-count line*)))
  self)

(defmethod (bp :backward-to-char) (target-char)
  (loop for line* first line then (line-previous line*)
	for pos first position then (line-char-count line*)
	for i = (and (plusp& pos)
		     (string-reverse-search-char target-char
						 (line-chars line*) pos 0))
	if (not (null i))
	return (send self :move line* i)
	if (null (line-previous line*))
	return (send self :move line* 0))
  (send self :advance-pos 1) ;leave BP after char.
  self)

