;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: INTERACTORS; Base: 10 -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;         The Garnet User Interface Development Environment.      ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This code was written as part of the Garnet project at          ;;;
;;; Carnegie Mellon University, and has been placed in the public   ;;;
;;; domain.  If you are using this code or any part of Garnet,      ;;;
;;; please contact garnet@cs.cmu.edu to be put on the mailing list. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  Multifont LISP text-editor
;;;  Matthew Goldberg
;;;  Feb 18, 1993
;;;
#|
============================================================
Change log:
     9/20/93 Fernando D. Mato Mira - Variable name: position ---> text-position
     8/05/93 Andrew Mickish - Added indent of kr:define-method
     7/13/93 Matt Goldberg - Rewrote many functions, using opal:marks.
     6/01/93 Matt Goldberg - Set interactor's :match-obj with the
                set-parenthesis message and added function LISPIFY
     5/26/93 Matt Goldberg - started
============================================================
|#


(in-package "INTERACTORS" :use '("KR" "LISP"))

(export '(indent TURN-OFF-MATCH LISPIFY ADD-LISP-CHAR DELETE-LISP-REGION))

;; TABS

; function WORD-TAB
;  input: TEXT-OBJ a text-object
;         FUNC-LINE, FUNC-CHAR the position of the beginning of the function
;          that the current word is an argument of.
;         ARG-NUM which argument is it (first, second, third)?
;  returns: the number of characters to the word to tab to

(defun word-tab (text-obj func-line func-char arg-num)
  (let* ((start (multiple-value-list
		    (opal:get-cursor-line-char-position text-obj)))
	 (start-line (first start)))
    (cond
      ((= 0 start-line) nil)
      ((= 0 arg-num) (1+ func-char))
      (t (opal:set-cursor-to-line-char-position text-obj
						func-line (1+ func-char))
         (do ((arg 0 (1+ arg))
              (location '(-1 -1)
                        (let ((new-loc (multiple-value-list
					   (opal:get-cursor-line-char-position
					    text-obj))))
                          (if (equal (first new-loc) (first location))
                              location
                              new-loc))))
	     ((= arg arg-num) (second location))
	   (skip text-obj)
           (opal:go-to-next-word text-obj)
	   (let ((char (opal:fetch-next-char text-obj)))
	     (if (equal char #\#)
		 (progn
		   (opal:go-to-next-char text-obj)
		   (let ((next-char (opal:fetch-next-char text-obj)))
		     (opal:go-to-prev-char text-obj)
		     (if (equal next-char #\|)
			 (progn
			   (skip text-obj)
			   (setf char (opal:fetch-next-char text-obj))
			   (if (and char (find char '(#\space #\newline)))
			       (opal:go-to-next-word text-obj))
			   (setf char (opal:fetch-next-char text-obj)))))))
	     (if (equal char #\;)
		 (progn
		   (skip text-obj)
		   (opal:go-to-next-word text-obj))))
	   )))))

; function GET-WORD
;  input: A line, and a number (the character-position on the line)
;         TRIM: if specified T, will not include the first character of the
;               word... useful for "(defun" -> "defun"
;  returns: the word that the cursor is currently on or before on same line
;    ->         (in :strings format) 

(defun get-word (line char-pos &key (trim nil))
  (multiple-value-bind (frag frag-pos)
      (opal::calculate-cursor-pos line char-pos)
    (let ((word ""))
      (when (not trim)
	(do* ((first-frag frag (opal::frag-prev first-frag))
	      (string (and frag (if (opal::frag-object-p frag)
				    "" (opal::frag-string frag)))
		      (and first-frag (if (opal::frag-object-p first-frag)
					  "" (opal::frag-string first-frag))))
	      (first-frag-pos
	       (position #\space (subseq string 0 frag-pos) :from-end t)
	       (position #\space string :from-end t)))
	     ((or first-frag-pos (null first-frag))
	      (when first-frag
		(setq word (concatenate
			    'string word
			    (reverse (if (eq first-frag frag)
					 (subseq string first-frag-pos frag-pos)
					 (subseq string first-frag-pos)))))))
	  (setq word (concatenate 'string word
				  (reverse (if (eq first-frag frag)
					       (subseq string 0 frag-pos)
					       string)))))
	(setq word (reverse word)))
      (do* ((last-frag frag (opal::frag-next last-frag))
	    (string (and frag (if (opal::frag-object-p frag)
				  "" (opal::frag-string frag)))
		    (and last-frag (if (opal::frag-object-p last-frag)
				       "" (opal::frag-string last-frag))))
	    (last-frag-pos
	     (position #\space string :start frag-pos)
	     (position #\space string)))
	   ((or last-frag-pos (null last-frag))
	    (when last-frag
	      (setq word (concatenate 
			  'string word (if (eq last-frag frag)
					   (subseq string frag-pos last-frag-pos)
					   (subseq string 0 last-frag-pos))))))
	(setq word (concatenate 'string word (if (eq last-frag frag)
						 (subseq string frag-pos)
						 string))))
      (remove #\space word))))

;; function CHAR-CODE-P
;;   returns 't' if the current word is a character-code, 'nil' if not.

(defun char-code-p (frag frag-pos)
  (if (and (null (opal::frag-prev frag)) (< frag-pos 2))
      nil
      (progn
	(when (<= frag-pos 0)
	  (setq frag (do* ((my-frag (opal::frag-prev frag)
				    (opal::frag-prev my-frag))
			   (length (opal::frag-length my-frag)
				   (opal::frag-length my-frag)))
			  ((or (null my-frag) (> length 0))
			   (if my-frag
			       (setq frag-pos (+ length frag-pos))
			       (return-from char-code-p nil))
			   my-frag))))
	(let ((prev-char
	       (do* ((my-frag frag (opal::frag-prev frag))
		     (pos frag-pos (opal::frag-length frag)))
		    ((> pos 0) (schar (opal::frag-string frag) (1- pos)))))
	      char)
	  (if (member prev-char '(#\\ #\-))
	      (progn
		(cond
		  ((= frag-pos 1)
		   (do* ((my-frag (opal::frag-prev frag)
				  (opal::frag-prev my-frag))
			 (length (opal::frag-length my-frag)
				 (and my-frag (opal::frag-length my-frag))))
			((> length 0)
			 (setq char (schar (opal::frag-string my-frag)
					   (1- length))))))
		  ((= frag-pos 0)
		   (let ((two-frags-flag nil))
		     (do* ((my-frag (opal::frag-prev frag)
				    (opal::frag-prev my-frag))
			   (length (opal::frag-length my-frag)
				   (and my-frag (opal::frag-length my-frag))))
			  ((> length 1)
			   (setq char (schar (opal::frag-string my-frag)
					    (- length (if two-frags-flag 1 2)))))
		       (when (= length 1) (setq two-frags-flag t)))))
		  (t (setq char
			   (schar (opal::frag-string frag) (- frag-pos 2)))))
		(not (find char '(#\\ #\-))))
	      nil)))))

(defvar *legit-fonts* (list (opal:get-standard-font :fixed :roman :medium)
			    (opal:get-standard-font :fixed :bold :medium)))

(defvar *lisp-delim-chars* (list #\space #\newline #\tab #\( #\)))

(defvar *lisp-names* (list :open :close :arg
			   :open-comment :close-comment :quote))

(defmacro legit-font-p (font)
  `(member ,font *legit-fonts*))

(defmacro lisp-delim-char-p (char)
  `(or (null ,char) (member ,char *lisp-delim-chars*)))

(defmacro lisp-mark-p (name)
  `(member ,name *lisp-names*))

;; function LAST-PAREN
;;   input: text-obj, the text object
;;          IN-QUOTE-P: If specified T, the count will begin as if starting
;;                      within a quotation, otherwise not.
;;   This function will return the frag of the last unmatched open
;;   parentheses before the initial cursor position, or nil if
;;   there are none.
;;   Returns: 2 values, the frag, and
;;            the number of arguments to the function, or -1 if there
;;            is none.

(defun last-paren (line frag &key (in-quote-p nil))
  (let ((arguments 0)
	(closes 0))
    (do* ((mark (if in-quote-p
		  (progn
		    (setq in-quote-p nil)
		    (opal::mark-prev
		     (opal::search-backwards-for-mark-from line
							   frag :name :quote)))
		  (opal::search-backwards-for-mark-from line frag))
		(and mark (opal::mark-prev mark)))
	  (name (and mark (opal::mark-name mark))
		(and mark (opal::mark-name mark)))
	  (frag (and mark (opal::frag-prev (opal::mark-frag mark)))
		(and mark (opal::frag-prev (opal::mark-frag mark))))
	  (pos (and frag (1- (opal::frag-length frag)))
	       (and frag (1- (opal::frag-length frag)))))
	 ((or (null mark) (and (eq name :open) (zerop closes)
			       (not (char-code-p frag pos))))
	  (if mark
	    (values mark (if (zerop arguments) 0 (1- arguments)))
	    (values nil -1)))
      (cond
	((char-code-p frag pos) nil)
	((eq name :arg) (when (zerop closes) (incf arguments)))
	((eq name :close)
	 (when (zerop closes) (incf arguments))
	 (incf closes))
	((eq name :open)
	 (decf closes))
	((eq name :quote)
	 (incf arguments)
	 (let ((prev-quote (opal::search-backwards-for-mark-from
			    (opal::mark-line mark)
			    (opal::frag-prev (opal::mark-frag mark))
			    :name :quote)))
	   (if prev-quote
	       (setq mark prev-quote)
	       (setq closes 0
		     arguments 0))))
	((eq name :open-comment)
	 (setq closes 0
	       arguments 0))
	((eq name :close-comment)
	 (let ((open-comment (opal::search-backwards-for-mark-from
			      (opal::mark-line mark)
			      (opal::frag-prev (opal::mark-frag mark))
			      :name :open-comment)))
	   (if open-comment
	       (setq mark open-comment)
	       (return-from last-paren (values nil -1)))))))))

;; Function Count-Chars-Before-Frag
;;  input: A Fragment
;;  returns: the number of chars from the beginning of the line to the
;;           end of that fragment

(defun count-chars-before-frag (frag)
  (let ((chars 0))
    (do ((my-frag frag (opal::frag-prev my-frag)))
	((null my-frag) chars)
      (incf chars (opal::frag-length my-frag)))))

;; Function Get-Line-Number
;;  input: A Line
;;  returns: the number of that line, that is, 0 for first, 1 for second, etc.

(defun get-line-number (line)
  (let ((num 0))
    (do ((my-line (g-value line :prev-line) (g-value my-line :prev-line)))
	((null my-line) num)
      (incf num))))

;; Function FUNCTION-FIND
;;  input: TEXT-OBJ, a text-object
;;         START-LINE, the line to begin searching backwards from
;;  returns:  the closest unclosed function name, i.e. (dotimes ...
;;    in a list, first element: function-name (:strings format)
;;              second element: number of arguments of that function
;;               third element: line-position (number)
;;              fourth element: character-position (number)

(defun function-find (text-obj start-line)
  (cond
    ((= start-line 0) (list nil 0 0 0))
    (t
     (opal:set-selection-to-line-char-position text-obj start-line 0)
     (opal:toggle-selection text-obj t)
     (let* ((text (opal:text-to-string (opal:copy-selected-text text-obj)))
	    (is-in-a-quote-p (oddp (- (length text)
				      (length (remove #\" text))))))
       (opal:toggle-selection text-obj nil)
       (let ((prev-line (g-value text-obj :cursor-line :prev-line))
	     func-list mark arguments)
	 (multiple-value-setq (mark arguments)
	   (last-paren prev-line (g-value prev-line :last-frag)
		       :in-quote-p is-in-a-quote-p))
	 (if (>= arguments 0)
	     (progn
	       (let* ((line (opal::mark-line mark))
		      (line-pos (get-line-number line))
		      (char-pos (count-chars-before-frag
				 (opal::mark-frag mark))))
		 (setf func-list (list (get-word line char-pos :trim t)
				       arguments line-pos (1- char-pos)))))
	     (setf func-list (list nil 0 0 0)))
	 func-list)))
    )
  )

;;; ---------------------------------
;;;
;;;   The indentation table: how far to indent for each lisp function
;;;

(defvar *tab-table* (make-hash-table :test #'equal :size 3))

;; function GET-TAB-AMOUNT
;;   input: name (the name of the function) a string
;;          arg   then number of the argument to tab
;;   output: the number of spaces to indent, or -1 if it should do a
;;            "WORD-TAB"

(defun get-tab-amount (name arg)
  (let ((amount-list (gethash name *tab-table*)))
    (cond
      ((string-equal name "") 1)
      ((equal (elt name 0) #\() 1)
      ((not amount-list) -1)
      ((= (1- arg) (first amount-list)) 2)
      ((> (1- arg) (first amount-list)) -1)
      (t (second amount-list)))))

;; function INDENT
;;  input: NAME, the name of the function
;;         SPECIAL-ARGS one or two numbers.
;;  output: will put the function into the *tab-table* hash table, giving
;;           the function with name NAME special tab-properties.
;;           The first number of SPECIAL-ARGS signifies how many arguments
;;            have the special property.
;;           The second number signifies how many spaces from the
;;            function name these special args should be placed.
;;           The number -1 means that this arg should act as a WORD-TAB.
;;           The argument after the last special argument will be placed
;;            one space in from the function-name.
;;           All other args will act as WORD-TABS.

(defun indent (name &rest special-args)
  (setf (gethash name *tab-table*) (if special-args special-args '(0)))
  )

(indent "defun" 2 4)
(indent "create-instance" 2 4)
(indent "let" 1 4)
(indent "let*" 1 4)
(indent "flet" 1 4)
(indent "labels" 1 4)
(indent "when" 1 4)
(indent "unless" 1 4)
(indent "cond" 0)
(indent "case" 1 4)
(indent "ecase" 1 4)
(indent "ccase" 1 4)
(indent "typecase" 1 4)
(indent "etypecase" 1 4)
(indent "ctypecase" 1 4)
(indent "dotimes" 1 4)
(indent "dolist" 1 4)
(indent "do" 2 -1)
(indent "do*" 2 -1)
(indent "defvar" 1 4)
(indent "defparameter" 1 4)
(indent "lambda" 1 4)
(indent "defstruct" 1 4)
(indent "with-open-file" 1 4)
(indent "with-open-stream" 1 4)
(indent "progn" 0)
(indent "multiple-value-bind" 1 6)
(indent "multiple-value-setq" 1 4)
(indent "defclass" 2 4)
(indent "defmethod" 2 4)
(indent "with-accessors" 1 4)
(indent "define-method" 3 4)

;;;
;;; -----------------------------


;; function TAB
;;  input: TEXT-OBJ, the text-object
;;  output: will put the correct (LISP-wise) number of spaces between
;;           the first word on the line and the beginning of the line.

(defun tab (interact text-obj event)
  (declare (ignore interact event))
  (let* ((start (multiple-value-list
		    (opal:get-cursor-line-char-position text-obj)))
         (start-char (second start))
         (start-line (first start))
         (info (function-find text-obj start-line))
         (special-tab (if (first info)
			  (get-tab-amount  (opal:text-to-string (first info))
					   (1+ (second info)))
			  0))
         (tab-amount (if (= -1 special-tab)
                         (word-tab text-obj (third info)
				   (fourth info) (second info))
                         (+ (fourth info) special-tab))))
    
    (opal:set-cursor-to-line-char-position text-obj start-line 0)
    (when (opal::delim-char-p (opal:fetch-next-char text-obj))
      (opal:go-to-next-word text-obj))
    (when (not (= start-line
		  (first (multiple-value-list
			     (opal:get-cursor-line-char-position text-obj)))))
      (opal:set-cursor-to-line-char-position text-obj start-line 0)
      (opal:go-to-end-of-line text-obj))
    (opal:set-selection-to-line-char-position
     text-obj start-line (second
			  (multiple-value-list
			      (opal:get-cursor-line-char-position text-obj))))
    (opal:go-to-beginning-of-line text-obj)
    (let ((num-of-deleted-chars (length (opal:text-to-string
					 (opal:delete-selection text-obj)))))
      (s-value text-obj :current-font
	       (opal:get-standard-font :fixed :roman :medium))
      (opal:insert-string text-obj
			  (make-string tab-amount :initial-element #\space))
      (opal:set-cursor-to-line-char-position
       text-obj start-line (+ (if (and (> start-char num-of-deleted-chars)
				       (>= num-of-deleted-chars 0))
				  (- start-char num-of-deleted-chars)
				  0)
			      tab-amount)))))



;; MATCHING PARENTHESIS

;; function LINE-STRING
;;  input: a line
;;  output: a string containing all the text on that line

(defun line-string (line)
  (opal::copy-text line (g-value line :first-frag) 0 line
		   (g-value line :last-frag)
		   (second (multiple-value-list
			       (opal::calculate-cursor-pos
				line (g-value line :length))))))

;; function MATCH-PARENS
;;  input: text-obj, the text-object, with the cursor next to the close-paren
;;          that should be matched.
;;  output: Will bold-face the corresponding open paren, and set the
;;          message-window to read the function name, if such a window exists.

(defun match-parens (interact text-obj)
  (let* ((start (multiple-value-list
		    (opal:get-cursor-line-char-position text-obj)))
         (start-char (second start))
         (start-line (first start))
	 (selection (multiple-value-list
			(opal:get-selection-line-char-position text-obj)))
	 (selection-p (g-value text-obj :selection-p)))
    (opal:go-to-prev-char text-obj)
    (if (char-code-p (g-value text-obj :cursor-frag)
		     (g-value text-obj :cursor-frag-pos))
	(opal:go-to-next-char text-obj)
	(multiple-value-bind (mark arguments)
	    (last-paren (g-value text-obj :cursor-line)
			(g-value text-obj :cursor-frag))
	  (if (>= arguments 0)
	    (progn
	      (opal:set-cursor-to-line-char-position
	       text-obj
	       (get-line-number (opal::mark-line mark))
	       (1- (count-chars-before-frag (opal::mark-frag mark))))
	      (let* ((begin (multiple-value-list
				(opal:get-cursor-line-char-position text-obj)))
		     (begin-char (second begin))
		     (begin-line (first begin)))
		(s-value text-obj :curr-paren begin)
		(opal:set-selection-to-line-char-position text-obj begin-line
							  (1+ begin-char))
		(opal:toggle-selection text-obj t)
		(opal:change-font-of-selection text-obj nil :bold t)
		(opal:toggle-selection text-obj nil)
		(if (g-value interact :match-obj)
		  (opal:set-text (g-value interact :match-obj)
				(line-string (g-value text-obj :cursor-line))))))
	    (progn
	      (inter:beep)
	      (s-value text-obj :curr-paren 0)
	      (if (g-value interact :match-obj)
		  (opal:set-text (g-value interact :match-obj) "No match."))))
	  (opal:set-cursor-to-line-char-position text-obj start-line start-char)
	  (when selection-p
	    (opal:set-selection-to-line-char-position
	     text-obj (first selection) (second selection))
	    (opal:toggle-selection text-obj t))))))

(defun TURN-OFF-MATCH (interact text-obj)
  (let ((curr-paren (g-value text-obj :curr-paren)))
    (if curr-paren
      (if (eq curr-paren 0)
	(progn
	  (if (g-value interact :match-obj)
	      (opal:set-text (g-value interact :match-obj) ""))
	  (s-value text-obj :curr-paren nil))
	(let* ((start (multiple-value-list
			  (opal:get-cursor-line-char-position text-obj)))
	       (start-char (second start))
	       (start-line (first start))
	       (selection (multiple-value-list
			      (opal:get-selection-line-char-position text-obj)))
	       (selection-p (g-value text-obj :selection-p)))
	  (opal:set-cursor-to-line-char-position
	   text-obj (first curr-paren) (second curr-paren))
	  (opal:set-selection-to-line-char-position
	   text-obj (first curr-paren) (1+ (second curr-paren)))
	  (opal:toggle-selection text-obj t)
	  (opal:change-font-of-selection text-obj nil :bold nil)
	  (if selection-p
	      (opal:set-selection-to-line-char-position
	       text-obj (first selection) (second selection))
	      (opal:toggle-selection text-obj nil))
	  (opal:set-cursor-to-line-char-position text-obj start-line start-char)
	  (s-value text-obj :curr-paren nil)
	  (if (g-value interact :match-obj)
	      (opal:set-text (g-value interact :match-obj) "")))))))

;; function CHECK-PARENS
;;  If there is currently a parenthesis highlighted, will turn it off.
;;  Also, if the cursor is next to a close paren, will call MATCH-PARENS
(defun check-parens (interact text-obj)
  (TURN-OFF-MATCH interact text-obj)
  (let ((curr-font (g-value text-obj :current-font))
	(last-char (opal:fetch-prev-char text-obj)))
    (if last-char
	(if (and last-char (equal last-char #\))
		 (equal curr-font
			(opal:get-standard-font :fixed :roman :medium)))
	    (match-parens interact text-obj)
	    (if (and last-char (equal last-char #\newline)
		     (equal curr-font
			    (opal:get-standard-font :fixed :italic :medium)))
		(s-value text-obj :current-font
			 (opal:get-standard-font :fixed :roman :medium))))
	(s-value text-obj :current-font
		 (opal:get-standard-font :fixed :roman :medium)))))

;; COMMENTS

;; function ITALICIZE
;;  Input: text-obj, the text-object
;;         FROM-LOCATION, a list of the form (line-no. char-no.)
;;  Will make italics all text from "from-location" to end of line.

(defun italicize (text-obj from-location)
  (let* ((start (multiple-value-list
		    (opal:get-cursor-line-char-position text-obj)))
         (start-char (second start))
         (start-line (first start))
	 (from-line (first from-location))
	 (from-char (second from-location)))
    (opal:set-cursor-to-line-char-position text-obj from-line from-char)
    (opal:go-to-end-of-line text-obj)
    (do ((curr-char (opal:fetch-next-char text-obj)
		    (opal:fetch-next-char text-obj)))
        ((or (null curr-char) (equal curr-char #\newline)))
      (opal:go-to-next-line text-obj)
      (opal:go-to-end-of-line text-obj))
    (opal:set-selection-to-line-char-position text-obj from-line from-char)
    (opal:toggle-selection text-obj t)
    (opal:change-font-of-selection text-obj nil :italic t)
    (opal:toggle-selection text-obj nil)
    (opal:set-cursor-to-line-char-position text-obj from-line from-char)
    (let ((removed-mark nil))
      (do* ((frag (g-value text-obj :cursor-frag) (opal::frag-next frag))
	    (obj (and frag (opal::frag-object frag))
		 (and frag (opal::frag-object frag))))
	   ((null frag)
	    (when removed-mark
	      (opal::calculate-size-of-line text-obj
					    (g-value text-obj :cursor-line))))
	(when (and (opal::mark-p obj) (lisp-mark-p (opal::mark-name obj)))
	  (opal::remove-mark text-obj obj)
	  (setq removed-mark t))))
    (opal:set-cursor-to-line-char-position text-obj start-line start-char)))

;; function DEFAULT-UP-TO-NEXT-SEMI
;;  Will make default-font all text from current location until the first
;;   semi-colon or end of line.

(defun default-up-to-next-semi (text-obj)
  (let* ((start (multiple-value-list
		    (opal:get-cursor-line-char-position text-obj)))
         (start-char (second start))
         (start-line (first start)))
    (do ((prev-char (opal:fetch-prev-char text-obj) curr-char)
	 (curr-char (opal:fetch-next-char text-obj)
		    (opal:fetch-next-char text-obj)))
        ((or (null curr-char) (equal curr-char #\;)
	     (equal curr-char #\newline))
	 (if (eq prev-char #\()
	     (opal:insert-mark text-obj t :name :open)
	     (when (eq prev-char #\))
	       (opal:insert-mark text-obj t :name :close)))
         (opal:set-selection-to-line-char-position
	  text-obj start-line start-char)
         (opal:toggle-selection text-obj t)
         (opal:change-font-of-selection
	  text-obj (opal:get-standard-font :fixed :roman :medium))
         (opal:set-cursor-to-line-char-position text-obj
						start-line start-char))
      (cond
	((and (eq prev-char #\#) (eq curr-char #\|))
	 (opal:insert-mark text-obj nil :name :open-comment))
	((and (eq prev-char #\|) (eq curr-char #\#))
	 (opal:insert-mark text-obj t :name :close-comment))
	((lisp-delim-char-p prev-char)
	 (when (not (lisp-delim-char-p curr-char))
	   (opal:insert-mark text-obj nil :name :arg))
	 (if (eq prev-char #\()
	     (opal:insert-mark text-obj t :name :open)
	     (when (eq prev-char #\))
	       (opal:insert-mark text-obj t :name :close)))))
      (opal:go-to-next-char text-obj))))

;; SKIPS through LISP EXPRESSIONS (Control-Meta-F etc.)

;; function FIND-CLOSE-PAREN
;;  Will search forward for the first unmatched close paren.

(defun find-close-paren (text-obj line frag)
  (let ((opens 0)
	(in-quote-p nil))
    (do* ((mark (if in-quote-p
		  (progn
		    (setq in-quote-p nil)
		    (opal::mark-prev
		     (opal::search-for-mark-from line frag :name :quote)))
		  (opal::search-for-mark-from line frag))
		(and mark (opal::mark-next mark)))
	  (name (and mark (opal::mark-name mark))
		(and mark (opal::mark-name mark)))
	  (frag (and mark (opal::frag-prev (opal::mark-frag mark)))
		(and mark (opal::frag-prev (opal::mark-frag mark))))
	  (pos (and frag (1- (opal::frag-length frag)))
	       (and frag (1- (opal::frag-length frag)))))
	 ((or (null mark) (and (eq name :close) (zerop opens)
			       (not (char-code-p frag pos))))
	  (if mark
	      (opal:set-cursor-to-line-char-position
	       text-obj
	       (get-line-number (opal::mark-line mark))
	       (count-chars-before-frag frag))
	      nil))
      (cond
	((char-code-p frag pos) nil)
	((eq name :open)
	 (incf opens))
	((eq name :close)
	 (decf opens))
	((eq name :quote)
	 (let ((next-quote (opal::search-for-mark-from
			    (opal::mark-line mark)
			    (opal::frag-next (opal::mark-frag mark))
			    :name :quote)))
	   (if next-quote
	       (setq mark next-quote)
	       (setq opens 0))))
	((eq name :close-comment)
	 (setq opens 0))
	((eq name :open-comment)
	 (let ((close-comment (opal::search-for-mark-from
			       (opal::mark-line mark)
			       (opal::frag-next (opal::mark-frag mark))
			       :name :close-comment)))
	   (if close-comment
	       (setq mark close-comment)
	       (return-from find-close-paren nil))))))))

;; function SKIP
;;  Will move cursor, depending on current location.
;;   If currently before an open paren, will move to corresponding close-paren.
;;   If currently before a comment/quote, will move to end.
;;   Else move to end of word.

(defun skip (text-obj)
  (opal:toggle-selection text-obj nil)
  (when (opal::delim-char-p (opal:fetch-next-char text-obj))
    (opal:go-to-next-word text-obj))
  (let ((char (opal:fetch-next-char text-obj)))
    (cond
      ((not char) nil)
      ((eq char #\()
       (find-close-paren text-obj (g-value text-obj :cursor-line)
			 (opal::frag-next
			  (opal::frag-next (g-value text-obj :cursor-frag)))))
      ((eq char #\#)
       (opal:go-to-next-char text-obj)
       (if (equal (opal:fetch-next-char text-obj) #\|)
	   (let ((mark (opal::search-for-mark-from
			(g-value text-obj :cursor-line)
			(g-value text-obj :cursor-frag)
			:name :close-comment)))
	     (when mark
	       (opal:set-cursor-to-line-char-position
		text-obj
		(get-line-number (opal::mark-line mark))
		(count-chars-before-frag (opal::mark-frag mark)))
	       (do ((char (opal:go-to-next-char text-obj)
			  (opal:go-to-next-char text-obj)))
		   ((or (null char) (lisp-delim-char-p char) (eq char #\"))
		    (opal:go-to-prev-char text-obj)))))))
      ((eq char #\")
       (opal:go-to-next-char text-obj)
       (let ((mark (opal::search-for-mark-from
		    (g-value text-obj :cursor-line)
		    (g-value text-obj :cursor-frag)
		    :name :quote)))
	 (when mark
	   (opal:set-cursor-to-line-char-position
	    text-obj
	    (get-line-number (opal::mark-line mark))
	    (count-chars-before-frag (opal::mark-frag mark))))))
      (T
       (do ((char (opal:go-to-next-char text-obj)
		  (opal:go-to-next-char text-obj)))
	   ((or (null char) (lisp-delim-char-p char) (eq char #\"))
	    (opal:go-to-prev-char text-obj)))
       (do ()
           ((not (equal (g-value text-obj :current-font)
			(opal:get-standard-font :fixed :italic :medium))))
         (opal:go-to-next-word text-obj))))))

;; function SKIP-BACK
;;  Will move cursor, depending on current location.
;;   If currently after a close paren, will move to corresponding open-paren.
;;   If currently after a comment/quote, will move to beginning
;;   Else move to beginning of word.

(defun skip-back (text-obj)
  (opal:toggle-selection text-obj nil)
  (when (opal::delim-char-p (opal:fetch-prev-char text-obj))
    (opal:go-to-prev-word text-obj))
  (let ((char (opal:fetch-prev-char text-obj)))
    (cond
      ((not char) nil)
      ((eq char #\))
       (let ((mark (last-paren (g-value text-obj :cursor-line)
			       (opal::frag-prev
				(opal::frag-prev
				 (g-value text-obj :cursor-frag))))))
	 (when mark
	   (opal:set-cursor-to-line-char-position
	    text-obj
	    (get-line-number (opal::mark-line mark))
	    (1- (count-chars-before-frag (opal::mark-frag mark)))))))
      ((eq char #\#)
       (opal:go-to-prev-char text-obj)
       (if (equal (opal:fetch-prev-char text-obj) #\|)
	   (let ((mark (opal::search-backwards-for-mark-from
			(g-value text-obj :cursor-line)
			(g-value text-obj :cursor-frag)
			:name :open-comment)))
	     (when mark
	       (opal:set-cursor-to-line-char-position
		text-obj
		(get-line-number (opal::mark-line mark))
		(count-chars-before-frag (opal::mark-frag mark)))
	       (do ((char (opal:go-to-prev-char text-obj)
			  (opal:go-to-prev-char text-obj)))
		   ((or (null char) (lisp-delim-char-p char) (eq char #\"))
		    (opal:go-to-next-char text-obj)))))))
      ((eq char #\")
       (opal:go-to-prev-char text-obj)
       (let ((mark (opal::search-backwards-for-mark-from
		    (g-value text-obj :cursor-line)
		    (g-value text-obj :cursor-frag)
		    :name :quote)))
	 (when mark
	   (opal:set-cursor-to-line-char-position
	    text-obj
	    (get-line-number (opal::mark-line mark))
	    (count-chars-before-frag (opal::mark-frag mark))))))
      (T
       (do ((char (opal:go-to-prev-char text-obj)
		  (opal:go-to-prev-char text-obj)))
	   ((or (null char) (lisp-delim-char-p char) (eq char #\"))
	    (opal:go-to-next-char text-obj)))
       (do ()
           ((not (equal (g-value text-obj :current-font)
			(opal:get-standard-font :fixed :italic :medium))))
         (opal:go-to-prev-word text-obj))))))
    
;;  LISPIFY
;;  Changes a string into LISP format
;;  Input: a string
;;  Output: text, in "strings" format

(defun LISPIFY (text)
  (cond
    ((stringp text) (setq text (opal::add-space-to-line text)))
    ((atom text)    (setq text (list " ")))
    (T              (setq text (opal::add-space-to-line
				(opal:text-to-string text)))))
  (let ((line-pos -1)
	(paren-stack nil)
	(lispified-text nil)
	in-comment-p in-quote-p)
    (dolist (my-line text (reverse lispified-text))
      (incf line-pos)
      (let* ((paren-info (if paren-stack (first paren-stack) nil))
	     (special-tab (if paren-stack
			      (get-tab-amount (second paren-info)
					      (third paren-info))
			      0))
	     (tab-amount (if paren-stack
			     (if (= -1 special-tab)
				 (if (<= (third paren-info) 1)
				     (1+ (second (first paren-info)))
				     (second (fourth paren-info)))
				 (+ (second (first paren-info))
				    special-tab))
			     0))
	     (frag-string "")
	     (string (concatenate 'string
				  (make-string tab-amount
					       :initial-element #\space)
				  (let ((last-space
					 (position #\space (first my-line)
						   :test
						   #'(lambda (a b)
						       (declare (ignore a))
						       (not
							(opal::delim-char-p
							 b))))))
				    (if last-space
					(subseq (first my-line) last-space)
					""))))
	     (string-length (length string))
	     new-line char prev-char prev-prev-char in-argument-p)
	(dotimes (char-pos string-length)
	  (setq prev-prev-char prev-char
		prev-char char
		char (schar string char-pos))
	  (when (and (not in-argument-p) (not (lisp-delim-char-p char))
		     (not (eq char #\;)))
	    (push frag-string new-line)
	    (push (list :mark nil :arg) new-line)
	    (when (and paren-stack (not in-comment-p) (not in-quote-p)
		       (not (and (eq char #\#)
				 (eq (schar string (1+ char-pos)) #\|))))
	      (let ((paren-info (first paren-stack)))
		(when (and (> (incf (third paren-info)) 1)
			   (not (= line-pos (first (fourth paren-info)))))
		  (setf (fourth paren-info)
			(list line-pos char-pos)))))
	    (setq frag-string ""
		  in-argument-p t))
	  (cond
	    ((eq char #\space)
	     (setq frag-string (concatenate 'string frag-string
					    (string #\space))
		   in-argument-p nil))
	    ((and (or (eq prev-char #\-) (eq prev-char #\\ ))
		  (not (or (eq prev-prev-char #\-) (eq prev-prev-char #\\ ))))
	     (setq frag-string (concatenate 'string frag-string
					    (string char))))
	    ((eq char #\()
	     (when (and (not in-comment-p) (not in-quote-p))
	       (when paren-stack
		 (let ((paren-info (first paren-stack)))
		   (when (and (> (incf (third paren-info)) 1)
			      (not (= line-pos (first (fourth paren-info)))))
		     (setf (fourth paren-info)
			   (list line-pos char-pos)))))
	       (push (list (list line-pos char-pos)
			   (subseq string (1+ char-pos)
				   (position #\space string :start char-pos))
			   0 '(-1 0))
		     paren-stack))
	     (push (concatenate 'string frag-string (string char)) new-line)
	     (setq frag-string ""
		   in-argument-p nil)
	     (push (list :mark t :open) new-line))
	    ((eq char #\))
	     (when (and paren-stack (not in-comment-p) (not in-quote-p))
	       (pop paren-stack))
	     (push (concatenate 'string frag-string (string char)) new-line)
	     (setq frag-string ""
		   in-argument-p nil)
	     (push (list :mark t :close) new-line))
	    ((eq char #\")
	     (push (concatenate 'string frag-string (string char)) new-line)
	     (setq frag-string ""
		   in-quote-p (not in-quote-p))
	     (push (list :mark t :quote) new-line))
	    ((eq char #\;)
	     (push frag-string new-line)
	     (push (list (subseq string char-pos string-length)
			 (opal:get-standard-font :fixed :italic :medium))
		   new-line)
	     (setq char-pos string-length
		   frag-string ""))
	    ((and (eq char #\|) (eq prev-char #\#))
	     (push (concatenate 'string frag-string (string char)) new-line)
	     (setq frag-string ""
		   in-comment-p t)
	     (push (list :mark t :open-comment) new-line))
	    ((and (eq char #\#) (eq prev-char #\|))
	     (push frag-string new-line)
	     (push (list :mark t :close-comment) new-line)
	     (setq frag-string "#"
		   in-comment-p nil))
	    (T
	     (setq frag-string
		   (concatenate 'string frag-string (string char))))))
	(push (concatenate
	       'list (reverse (push frag-string new-line)) (rest my-line))
	      lispified-text)))))


;;; EDITING FUNCTIONS

(defun ADD-LISP-CHAR (text-obj char &optional new-font new-fcolor new-bcolor)
  (let* ((prev-char (opal:fetch-prev-char text-obj))
	 (next-char (opal:fetch-next-char text-obj))
	 (added-lisp-arg
	  (and (not (lisp-delim-char-p char))
	       (legit-font-p (g-value text-obj :current-font))
	       (or (null new-font) (legit-font-p new-font))
	       (lisp-delim-char-p prev-char))))
    (cond
      ((and (eq prev-char #\|) (eq next-char #\#))
       (let ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag)))
	 (opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				      line frag :name :close-comment))
	 (opal::calculate-size-of-line text-obj line)))
      ((and (eq prev-char #\#) (eq next-char #\|))
       (let ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag)))
	 (opal::remove-mark text-obj (opal::search-for-mark-from
				      line frag :name :open-comment))
	 (opal::calculate-size-of-line text-obj line))))
    
    (opal:add-char text-obj char new-font new-fcolor new-bcolor)
    
    (when added-lisp-arg
      (when (and (not (lisp-delim-char-p next-char))
		 (not (eq next-char #\;)))
	(let* ((line (g-value text-obj :cursor-line))
	       (frag (g-value text-obj :cursor-frag))
	       (mark (opal::search-for-mark-from line frag :name :arg)))
	  (opal::remove-mark text-obj mark)
	  (opal::calculate-size-of-line text-obj line)))
      (opal:insert-mark text-obj t :name :arg))))
    

(defun DELETE-LISP-REGION (text-obj)
  (let* ((line1 (g-value text-obj :cursor-line))
	 (region (opal:delete-selection text-obj))
	 (string (opal:text-to-string region))
	 (string-length (length string)))
    (if (zerop string-length)
	nil
  (let* ((line2 (g-value text-obj :cursor-line))
	 (first-char (schar string 0))
	 (last-char (schar string (1- string-length)))
	 (next-char (opal:fetch-next-char text-obj))
	 (prev-char (opal:fetch-prev-char text-obj))
	 (legit-font (legit-font-p (g-value text-obj :current-font)))
	 (need-mark (and (lisp-delim-char-p prev-char)
			 (not (lisp-delim-char-p last-char))
			 (not (lisp-delim-char-p next-char))
			 (not (eq next-char #\;))
			 legit-font))
	 (lost-arg (and (not (lisp-delim-char-p next-char))
			(not (eq next-char #\;))
			(lisp-delim-char-p last-char)
			(not (lisp-delim-char-p prev-char))
			legit-font
			(not (eq last-char #\;)))))
    (setq region (lispify string))
    (when (and legit-font string (find #\; string) (eq line1 line2))
      (default-up-to-next-semi text-obj))
    (when (and legit-font (eq first-char #\#) (eq prev-char #\|))
      (let ((line (g-value text-obj :cursor-line)))
	(opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				     line (g-value text-obj :cursor-frag)
				     :name :close-comment))
	(opal::calculate-size-of-line text-obj line)))
    (when (and legit-font (eq last-char #\#) (eq next-char #\|))
      (let ((line (g-value text-obj :cursor-line)))
	(opal::remove-mark text-obj (opal::search-for-mark-from
				     line (g-value text-obj :cursor-frag)
				     :name :open-comment))
	(opal::calculate-size-of-line text-obj line)))
    (when (or
	   (and string (find #\newline string) (not legit-font))
	   (and last-char legit-font
		(not (find (if (> string-length 1)
			       (elt string (- string-length 2)) prev-char)
			   '(#\\ #\-)))
		(find last-char '(#\\ #\-)) (eq next-char #\;)))
      (italicize text-obj (multiple-value-list
			      (opal:get-cursor-line-char-position text-obj))))
    (when (and legit-font string)
      (if (eq prev-char #\|)
	  (when (eq next-char #\#)
	    (opal:insert-mark text-obj t :name :close-comment))
	  (when (and (eq prev-char #\#)
		     (eq next-char #\|))
	    (opal:insert-mark text-obj t :name :open-comment))))
    (when need-mark
      (opal:insert-mark text-obj nil :name :arg))
    (when lost-arg
      (let* ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag))
	     (mark (opal::search-for-mark-from line frag :name :arg)))
	(opal::remove-mark text-obj mark)
	(opal::calculate-size-of-line text-obj line)))))
    region))

;;  KEY-BINDING-FUNCTIONS

(defun semi-func (interact text-obj event)
  (declare (ignore interact event))
  (cond
   ((char-code-p (g-value text-obj :cursor-frag)
		 (g-value text-obj :cursor-frag-pos))
    (add-lisp-char text-obj #\;))
   ((not (equal (g-value text-obj :current-font)
		(opal:get-standard-font :fixed :roman :medium)))
    (add-lisp-char text-obj #\;))
   (t
    (add-lisp-char text-obj #\;
		   (opal:get-standard-font :fixed :italic :medium))
    (italicize text-obj (multiple-value-list
			    (opal:get-cursor-line-char-position text-obj))))))

(defun return-func (interact text-obj event)
  (let* ((next-char (opal:fetch-next-char text-obj))
	 (prev-char (opal:fetch-prev-char text-obj))
	 (need-mark (and (not (lisp-delim-char-p next-char))
			 (not (eq next-char #\;))
			 (not (lisp-delim-char-p prev-char))
			 (legit-font-p (g-value text-obj :current-font)))))
    (add-lisp-char text-obj #\newline)
    (if (not (equal (g-value text-obj :current-font)
		    (opal:get-standard-font :fixed :roman :medium)))
	(default-up-to-next-semi text-obj))
    (tab interact text-obj event)
    (s-value text-obj :current-font
	     (opal:get-standard-font :fixed :roman :medium))
    (when need-mark
      (opal:insert-mark text-obj nil :name :arg))))
  
(defun bslash-func (interact text-obj event)
  (declare (ignore interact))
  (add-lisp-char text-obj (event-char event))
  (when (legit-font-p (g-value text-obj :current-font))
    (let ((char (opal:fetch-next-char text-obj)))
      (when (eq char #\;)
	(opal:delete-char text-obj)
	(default-up-to-next-semi text-obj)
	(add-lisp-char text-obj #\;
		       (opal:get-standard-font :fixed :roman :medium))
	(opal:go-to-prev-char text-obj)
	(opal:toggle-selection text-obj nil))))
  )

(defun rubout-func  (interact text-obj event)
  (declare (ignore interact event))
  (when (g-value text-obj :selection-p)
    (when (not (zerop (length (DELETE-LISP-REGION text-obj))))
      (return-from rubout-func nil)))
  (let* ((next-char (opal:fetch-next-char text-obj))
    	 (char (opal:delete-prev-char text-obj))
	 (prev-char (opal:fetch-prev-char text-obj))
	 (legit-font (legit-font-p (g-value text-obj :current-font)))
	 (need-mark (and (lisp-delim-char-p prev-char)
			 (not (lisp-delim-char-p char))
			 (not (lisp-delim-char-p next-char))
			 (not (eq next-char #\;))
			 legit-font))
	 (lost-arg (and (not (lisp-delim-char-p next-char))
			(not (eq next-char #\;))
			(lisp-delim-char-p char)
			(not (lisp-delim-char-p prev-char))
			legit-font)))
    (cond
     ((and legit-font (eq char #\;))
      (default-up-to-next-semi text-obj))
     ((and legit-font (eq char #\#))
      (when (eq prev-char #\|)
	(let ((line (g-value text-obj :cursor-line)))
	  (opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				       line (g-value text-obj :cursor-frag)
				       :name :close-comment))
	  (opal::calculate-size-of-line text-obj line)))
      (when (eq next-char #\|)
	(let ((line (g-value text-obj :cursor-line)))
	  (opal::remove-mark text-obj (opal::search-for-mark-from
				       line (g-value text-obj :cursor-frag)
				       :name :open-comment))
	  (opal::calculate-size-of-line text-obj line))))
     ((or
      (and (eq char #\newline) (not legit-font))
      (and char legit-font
	   (not (find prev-char '(#\\ #\-)))
	   (find char '(#\\ #\-)) (eq next-char #\;)))

      (italicize text-obj (multiple-value-list
			      (opal:get-cursor-line-char-position text-obj)))))
    (when (and legit-font char)
      (if (eq prev-char #\|)
	  (when (eq next-char #\#)
	    (opal:insert-mark text-obj t :name :close-comment))
	  (when (and (eq prev-char #\#)
		     (eq next-char #\|))
	    (opal:insert-mark text-obj nil :name :open-comment))))
    (when need-mark
      (opal:insert-mark text-obj nil :name :arg))
    (when lost-arg
      (let* ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag))
	     (mark (opal::search-for-mark-from line frag :name :arg)))
	(opal::remove-mark text-obj mark)
	(opal::calculate-size-of-line text-obj line)))))

(defun cd-func (interact text-obj event)
  (declare (ignore interact event))
  (when (g-value text-obj :selection-p)
    (when (not (zerop (length (DELETE-LISP-REGION text-obj))))
      (return-from cd-func nil)))
  (let* ((prev-char (opal:fetch-prev-char text-obj))
	 (char (opal:delete-char text-obj))
	 (next-char (opal:fetch-next-char text-obj))
	 (legit-font (legit-font-p (g-value text-obj :current-font)))
	 (need-mark (and (lisp-delim-char-p prev-char)
			 (not (lisp-delim-char-p char))
			 (not (lisp-delim-char-p next-char))
			 (not (eq next-char #\;))
			 legit-font))
	 (lost-arg (and (not (lisp-delim-char-p next-char))
			(not (eq next-char #\;))
			(lisp-delim-char-p char)
			(not (lisp-delim-char-p prev-char))
			legit-font
			(not (eq char #\;)))))
    (cond
     ((and legit-font (eq char #\;))
      (default-up-to-next-semi text-obj))
     ((and legit-font (eq char #\#))
      (when (eq prev-char #\|)
	(let ((line (g-value text-obj :cursor-line)))
	  (opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				       line (g-value text-obj :cursor-frag)
				       :name :close-comment))
	  (opal::calculate-size-of-line text-obj line)))
      (when (eq next-char #\|)
	(let ((line (g-value text-obj :cursor-line)))
	  (opal::remove-mark text-obj (opal::search-for-mark-from
				       line (g-value text-obj :cursor-frag)
				       :name :open-comment))
	  (opal::calculate-size-of-line text-obj line))))
     ((or
       (and (eq char #\newline) (not legit-font))
       (and char legit-font
	    (not (find prev-char '(#\\ #\-)))
	    (find char '(#\\ #\-)) (eq next-char #\;)))
      (italicize text-obj (multiple-value-list
			      (opal:get-cursor-line-char-position text-obj)))))
    (when (and legit-font char)
      (if (eq prev-char #\|)
	  (when (eq next-char #\#)
	    (opal:insert-mark text-obj t :name :close-comment))
	  (when (and (eq prev-char #\#)
		     (eq next-char #\|))
	    (opal:insert-mark text-obj nil :name :open-comment))))
    (when need-mark
      (opal:insert-mark text-obj nil :name :arg))
    (when lost-arg
      (let* ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag))
	     (mark (opal::search-for-mark-from line frag :name :arg)))
	(opal::remove-mark text-obj mark)
	(opal::calculate-size-of-line text-obj line)))))

(defun md-func (interact text-obj event)
  (declare (ignore interact event))
  (when (g-value text-obj :selection-p)
    (when (not (zerop (length (DELETE-LISP-REGION text-obj))))
      (return-from md-func nil)))
  (let* ((prev-char (opal:fetch-prev-char text-obj))
	 (word (opal:delete-word text-obj))
	 (word-length (length word)))
    (if (zerop word-length)
	nil
  (let* ((first-char (schar word 0))
	 (last-char (schar word (1- word-length)))
	 (next-char (opal:fetch-next-char text-obj))
	 (legit-font (legit-font-p (g-value text-obj :current-font)))
	 (need-mark (and (lisp-delim-char-p prev-char)
			 (not (lisp-delim-char-p last-char))
			 (not (lisp-delim-char-p next-char))
			 (not (eq next-char #\;))
			 legit-font))
	 (lost-arg (and (not (lisp-delim-char-p next-char))
			(not (eq next-char #\;))
			(lisp-delim-char-p last-char)
			(not (lisp-delim-char-p prev-char))
			legit-font
			(not (eq last-char #\;)))))
    (when (and legit-font word (find #\; word))
      (default-up-to-next-semi text-obj))
    (when (and legit-font (eq first-char #\#) (eq prev-char #\|))
      (let ((line (g-value text-obj :cursor-line)))
	(opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				     line (g-value text-obj :cursor-frag)
				     :name :close-comment))
	(opal::calculate-size-of-line text-obj line)))
    (when (and legit-font (eq last-char #\#) (eq next-char #\|))
      (let ((line (g-value text-obj :cursor-line)))
	(opal::remove-mark text-obj (opal::search-for-mark-from
				     line (g-value text-obj :cursor-frag)
				     :name :open-comment))
	(opal::calculate-size-of-line text-obj line)))
    (when (or
	   (and word (find #\newline word) (not legit-font))
	   (and last-char legit-font
		(not (find (if (> word-length 1)
			       (elt word (- word-length 2)) prev-char)
			   '(#\\ #\-)))
		(find last-char '(#\\ #\-)) (eq next-char #\;)))
      (italicize text-obj (multiple-value-list
			      (opal:get-cursor-line-char-position text-obj))))
    (when (and legit-font word)
      (if (eq prev-char #\|)
	  (when (eq next-char #\#)
	    (opal:insert-mark text-obj t :name :close-comment))
	  (when (and (eq prev-char #\#)
		     (eq next-char #\|))
	    (opal:insert-mark text-obj t :name :open-comment))))
    (when need-mark
      (opal:insert-mark text-obj nil :name :arg))
    (when lost-arg
      (let* ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag))
	     (mark (opal::search-for-mark-from line frag :name :arg)))
	(opal::remove-mark text-obj mark)
	(opal::calculate-size-of-line text-obj line)))))))


(defun ck-func (interact text-obj event)
  (let ((char (opal:fetch-next-char text-obj))
	(cut-buffer (g-value interact :cut-buffer))
	(kill-mode (g-value interact :kill-mode))
	(a-window (event-window event))
	deleted-stuff)
    (s-value interact :kill-mode t)
    (cond
     ((and char (equal char #\newline))
      (cd-func interact text-obj event)
      (if kill-mode
	  (setq deleted-stuff (opal:concatenate-text cut-buffer
						     (string #\newline)))
	  (setq deleted-stuff (string #\newline))))
     (t
      (let ((prev-char (opal:fetch-prev-char text-obj))
	    (legit-font (legit-font-p (g-value text-obj :current-font))))
	(when (and legit-font (eq char #\#) (eq prev-char #\|))
	  (let ((line (g-value text-obj :cursor-line)))
	    (opal::remove-mark text-obj (opal::search-backwards-for-mark-from
					 line (g-value text-obj :cursor-frag)
					 :name :close-comment))
	    (opal::calculate-size-of-line text-obj line)))
	(when (and legit-font (eq char #\|) (eq prev-char #\#))
	  (let ((line (g-value text-obj :cursor-line)))
	    (opal::remove-mark text-obj (opal::search-for-mark-from
					 line (g-value text-obj :cursor-frag)
					 :name :open-comment))
	    (opal::calculate-size-of-line text-obj line)))
	(when (and (not (lisp-delim-char-p prev-char))
		   (not (lisp-delim-char-p char))
		   (not (eq char #\;)))
	  (opal:insert-mark text-obj nil :name :arg))
      (if (and char (not (legit-font-p (g-value text-obj :current-font)))
	       (not (equal char #\;))) (default-up-to-next-semi text-obj))
      (if kill-mode
	  (setq deleted-stuff (opal:concatenate-text
			       cut-buffer (opal:kill-rest-of-line text-obj)))
	  (setq deleted-stuff (opal:kill-rest-of-line text-obj))))))
    (s-value interact :cut-buffer deleted-stuff)
    (opal:set-x-cut-buffer a-window (opal:text-to-string deleted-stuff))))

(defun cy-func (interact text-obj event)
  (declare (ignore event))
  (let* ((prev-char (opal:fetch-prev-char text-obj))
	 (next-char (opal:fetch-next-char text-obj))
	 (legit-font (legit-font-p (g-value text-obj :current-font)))
	 (text-position (multiple-value-list
		       (opal:get-cursor-line-char-position text-obj))))
  (cond
   ((not legit-font)
    (inter::paste-selection interact)
    (italicize text-obj text-position))
   (t
    (cond
      ((and (eq prev-char #\|) (eq next-char #\#))
       (let ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag)))
	 (opal::remove-mark text-obj (opal::search-backwards-for-mark-from
				line frag :name :close-comment))
	 (opal::calculate-size-of-line text-obj line)))
      ((and (eq prev-char #\#) (eq next-char #\|))
       (let ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag)))
	 (opal::remove-mark text-obj (opal::search-for-mark-from line frag
						:name :open-comment))
	 (opal::calculate-size-of-line text-obj line))))
    (inter::paste-selection interact)
    (let ((new-position (multiple-value-list
			    (opal:get-cursor-line-char-position text-obj)))
	  (last-char (opal:fetch-prev-char text-obj))
	  (new-legit-font (legit-font-p (g-value text-obj :current-font)))
	  first-char)
      (opal:set-cursor-to-line-char-position text-obj (first text-position)
					     (second text-position))
      (setq first-char (opal:fetch-next-char text-obj))
      (when (and legit-font
		 (not (lisp-delim-char-p first-char))
		 (not (eq first-char #\;))
		 (not (lisp-delim-char-p prev-char))
		 (not (eq prev-char #\;)))
	(let ((line (g-value text-obj :cursor-line))
	      (frag (g-value text-obj :cursor-frag)))
	  (opal::remove-mark text-obj (opal::search-for-mark-from line frag
						      :name :arg))
	  (opal::calculate-size-of-line text-obj line)))
      (when (and legit-font (eq prev-char #\#) (eq first-char #\|))
	(opal:insert-mark text-obj nil :name :open-comment))
      (when (and legit-font (eq prev-char #\|) (eq first-char #\#))
	(opal:insert-mark text-obj t :name :close-comment))
      (opal:set-cursor-to-line-char-position text-obj (first new-position)
					     (second new-position))
     (when (and new-legit-font
		(lisp-delim-char-p prev-char)
		(not (lisp-delim-char-p last-char))
		(not (eq last-char #\;))
		(not (lisp-delim-char-p next-char))
		(not (eq next-char #\;)))
       (let ((line (g-value text-obj :cursor-line))
	     (frag (g-value text-obj :cursor-frag)))
	 (opal::remove-mark text-obj (opal::search-for-mark-from line frag
						     :name :arg))
	 (opal::calculate-size-of-line text-obj line)))
     (when (and new-legit-font (eq last-char #\#) (eq next-char #\|))
       (opal:insert-mark text-obj nil :name :open-comment))
     (when (and new-legit-font (eq last-char #\|) (eq next-char #\#))
       (opal:insert-mark text-obj t :name :close-comment))
     (when (not new-legit-font)
       (italicize text-obj (multiple-value-list
			       (opal:get-cursor-line-char-position text-obj))))
     )))))

(defun cmf-func (interact text-obj event)
  (declare (ignore interact event))
  (skip text-obj))

(defun cmb-func (interact text-obj event)
  (declare (ignore interact event))
  (skip-back text-obj))

(defun cmd-func (interact text-obj event)
  (declare (ignore interact event))
  (multiple-value-bind (line char)
      (opal:get-cursor-line-char-position text-obj)
    (skip text-obj)
    (opal:set-selection-to-line-char-position text-obj line char)
    (opal:toggle-selection text-obj t)
    (DELETE-LISP-REGION text-obj)))

(defun cmh-func (interact text-obj event)
  (declare (ignore interact event))
  (multiple-value-bind (line char)
      (opal:get-cursor-line-char-position text-obj)
    (skip-back text-obj)
    (opal:set-selection-to-line-char-position text-obj line char)
    (opal:toggle-selection text-obj t)
    (DELETE-LISP-REGION text-obj)))
     
(defun hash-func (interact text-obj event)
  (declare (ignore interact event))
  (let ((prev-char (opal:fetch-prev-char text-obj))
	(next-char (opal:fetch-next-char text-obj)))
    (when (eq prev-char #\|)
      (s-value text-obj :current-font
	       (opal:get-standard-font :fixed :roman :medium))
      (opal:insert-mark text-obj t :name :close-comment))
    (when (eq next-char #\|)
      (opal:insert-mark text-obj nil :name :open-comment)))
  (add-lisp-char text-obj #\#))

(defun bar-func (interact text-obj event)
  (declare (ignore interact event))
  (let ((prev-char (opal:fetch-prev-char text-obj))
	(next-char (opal:fetch-next-char text-obj)))
    (add-lisp-char text-obj #\|)
    (when (eq prev-char #\#)
      (opal:insert-mark text-obj t :name :open-comment))
    (when (eq next-char #\#)
      (opal:insert-mark text-obj t :name :close-comment))))

(defun quote-func (interact text-obj event)
  (declare (ignore interact event))
  (let ((need-mark (legit-font-p (g-value text-obj :current-font))))
    (add-lisp-char text-obj #\")
    (when need-mark
      (opal:insert-mark text-obj t :name :quote))))

(defun space-func (interact text-obj event)
  (declare (ignore interact event))  
  (let ((next-char (opal:fetch-next-char text-obj))
	(prev-char (opal:fetch-prev-char text-obj)))
    (add-lisp-char text-obj #\space)
    (when (and (not (lisp-delim-char-p next-char))
	       (not (eq next-char #\;))
	       (not (lisp-delim-char-p prev-char))
	       (legit-font-p (g-value text-obj :current-font)))
      (opal:insert-mark text-obj nil :name :arg))))

(defun open-paren-func (interact text-obj event)
  (declare (ignore interact event))
  (let* ((need-mark (legit-font-p (g-value text-obj :current-font)))
	 (next-char (and need-mark (opal:fetch-next-char text-obj)))
	 (prev-char (and need-mark (opal:fetch-prev-char text-obj))))
    (add-lisp-char text-obj #\()
    (when need-mark
      (opal:insert-mark text-obj t :name :open)
      (when (and (not (lisp-delim-char-p next-char))
		 (not (eq next-char #\;))
		 (not (lisp-delim-char-p prev-char)))
	(opal:insert-mark text-obj nil :name :arg)))))

(defun close-paren-func (interact text-obj event)
  (declare (ignore interact event))
  (let* ((need-mark (legit-font-p (g-value text-obj :current-font)))
	 (next-char (and need-mark (opal:fetch-next-char text-obj)))
	 (prev-char (and need-mark (opal:fetch-prev-char text-obj))))
    (add-lisp-char text-obj #\))
    (when need-mark
      (opal:insert-mark text-obj t :name :close)
      (when (and (not (lisp-delim-char-p next-char))
		 (not (eq next-char #\;))
		 (not (lisp-delim-char-p prev-char)))
	(opal:insert-mark text-obj nil :name :arg)))))



