; -*- 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.

;Newer version of lisp-indentor

(defparameter *lisp-indent-alist*
  '((lambda 1 1) (let 1 1) (let* 1 1)
    (flet 1 1) (macrolet 1 1) (compiler-let 1 1)
    (do 2 1) (do* 2 1) (do-named 3 1) (do*-named 3 1)
    (dotimes 1 1) (dolist 1 1)
    (progv 2 1) (progw 1 1)
    (labels 1 1) (block 1 1) (return-from 1 1)
    (when 1 1) (unless 1 1) (eval-when 1 1)
    (defun 2 1) (defmacro 2 1) 
    (defparameter 1 1) (defvar 1 1)
    (defstruct 1 1) (defflavor 1 12 3 1)
    (unwind-protect 0 3 1 1) (catch 1 1) (eval-when 1 1)
    (if 2 1) (cond-every 0 1) (typecase 1 1)
    (select 1 1) (selectq 1 1) (selectq-every 1 1)
    (case 1 1) (typecase 1 1)
    (with-output-to-string 1 1) (with-input-from-string 1 1)
    (with-open-file 1 1) 
    (multiple-value 1 1) (multiple-value-bind 0 3 1 5 1 1)
    (:top-level . rigid-indentor)
    (:non-atomic . rigid-indentor)
    ))

(defun indentation-type (bp)
  (send bp :set-pos 0)
  (loop with count = -1			;don't count opening-atom
	do (send bp :backward-over-white-space)
	until (send bp :beginning-of-line?)
	do (setq count (1+& count))
	do (setq bp (bp-backward-over-sexp bp))
	until (or (send bp :beginning-of-line?)
		  (char= left-paren (send bp :peek-char-backward)))
	finally (return (values (if (0p (bp-position bp))
				    :top-level
				  (let ((type (get-atom-for-indentor-only bp)))
				    (if (equal type "")
					:non-atomic
				      type)))
				count
				(position-really
				  (bp-line bp) (bp-position bp) 
				  (window-x-size (edit-cursor-window
					           *editor-cursor*)))))))

(defun get-atom-for-indentor-only (bp)
  ;;assume in one line.
  (let ((mark (copy-bp bp)))
    (send mark :forward-over-atom)
    (prog1
      (cond ((not (eq (bp-line bp) (bp-line mark))) nil)
	    (t (let ((start (bp-position bp)) (end (bp-position mark)))
		 (let ((cnt (-& end start)))
		   (let ((new (make-string cnt :initial-element #\space)))
		     (%string-translate new (line-chars (bp-line bp))
					*:character-upcase-table 0 start cnt)
		     (or (intern-soft new) new))))))
      (send mark :expire))))

(defun lisp-indentor (&optional (bp *editor-cursor*) &aux indent-info)
  (let ((mark (send bp :get-mark)))
    (indent-line-to-column
      (bp-line bp)
      (unwind-protect
	  (multiple-value-bind
	      (type count start-pos) (indentation-type bp)
	    (send bp :goto-mark mark)	  
	    (cond ((and (symbolp type)
			(setq indent-info
			      (cdr (assoc type *lisp-indent-alist*))))
		   (if (symbolp indent-info)
		       (funcall indent-info bp type count start-pos)
		     (generic-lisp-indentor bp type count
					    start-pos indent-info)))
		  ((let ((type-pname (typecase type
				       (symbol (get-pname type))
				       (string type)
				       (otherwise ""))))
		     (and (>=& (string-length type-pname) 3)
			  (string-equal type-pname "DEF" 0 0 3 3)))
		   (def-hack-indentor bp type count start-pos))
		  (:else
		   (default-indentor bp type count start-pos))))
	(send bp :goto-mark mark)))))


(defun generic-lisp-indentor (bp type count start-pos offsets-list)
  (loop for (skip offset . rest) first offsets-list then rest
	summing skip into scount
	if (= scount count)
	   return (+& start-pos offset)
	if (or (>& scount count) (null rest))
	   return (default-indentor bp type count start-pos)))

(defun rigid-indentor (ignore ignore ignore start-pos)
  start-pos)

(defun prog-indentor (bp type count start-pos)
  ;;~~manana~~
  )

(defun def-hack-indentor (bp type count start-pos)
  (send bp :to-end-of-previous-line)
  (bp-backward-over-sexp bp)
  (loop with line = (bp-line bp)
	with x-size = (window-x-size (edit-cursor-window *editor-cursor*))
	for pos = (bp-position bp)
	do (send bp :backward-over-horizontal-white-space)
	if (char= (send bp :peek-char-backward) left-paren)
	return (1+& (position-really (bp-line bp) (bp-position bp) x-size))
	do (bp-backward-over-sexp bp)
	if (not (eq (bp-line bp) line))
	return (position-really line pos x-size)))

(defun default-indentor (bp type count start-pos)
  (send bp :to-end-of-previous-line)
  (bp-backward-over-sexp bp)
  (loop with line = (bp-line bp)
	with x-size = (window-x-size (edit-cursor-window *editor-cursor*))
	for i from 1 below count
	for pos = (bp-position bp)
	do (bp-backward-over-sexp bp)
	if (not (eq (bp-line bp) line))
	do (return (position-really line pos x-size))
	finally (return (+& (position-really (bp-line bp)
					     (bp-position bp) x-size)
			    (if (char= #\( (send bp :peek-char-backward))
				1
			      0)))))



(editor-bind-key #\tab indent-for-lisp lisp)

(defun indent-for-lisp ()
  (if (=& *argument* 1)
      (lisp-indentor)
      (loop for i from 1 to *argument*
	    do (lisp-indentor)
	    do (send *editor-cursor* :to-beginning-of-next-line)))
  nil)

(defun indent-line-to-column (line column)
  (with-bp (mark *editor-cursor*)
    (send *editor-cursor* :move line 0)
    (back-to-indentation)
    (let ((current-indentation (position-really (bp-line *editor-cursor*)
						(bp-position *editor-cursor*)
						259))) ; Very wide screen...
      (unless (= column current-indentation)
	(send *editor-cursor* :set-pos 0)
	(delete-horizontal-space *editor-cursor*)
	(do ((i 1 (1+& i))
	      (whole (/& column 8)))
	    ((>& i whole))
	  (send *editor-cursor* :insert-char #\tab))
	(do ((i 1 (1+& i))
	      (part (\\& column 8)))
	    ((>& i part))
	  (send *editor-cursor* :insert-char #\space))))
    (when (> (bp-position mark) (bp-position *editor-cursor*))
      (move-bp *editor-cursor* mark))))

(editor-bind-key #\control-meta-\\ indent-region)

(defun indent-region ()
  (cond ((-p *argument*) (ed-lose "Indent To Negative Column?"))
	((or (>& *argument* 1) (not (null argument-supplied?)))
	 ;;Indent to column.
	 (process-region-as-lines #'indent-line-to-column *argument*))
	;;Apply TAB to each line.
	(t (process-region-as-lines-with-point
	    #'(lambda (line)
		(send *editor-cursor* :set-pos 0)
		(funcall-key #\tab)))))
  nil)

(editor-bind-key '(#\control-x #\tab) indent-rigidly)

(defun indent-rigidly ()
  (process-region-as-lines-with-point
   #'(lambda (line)
       (send *editor-cursor* :set-pos 0)
       (send *editor-cursor* :forward-over-horizontal-white-space)
       (indent-line-to-column
	line (max& 0 (+& (edit-cursor-position-really *editor-cursor*)
			 *argument*))))))



(editor-bind-key #\meta-\\ delete-horizontal-space)

(defun delete-horizontal-space (&optional (bp *editor-cursor*))
  (unless (or argument-supplied? (not (=& *argument* 1)))
    (do ((chr))
	((send bp :beginning-of-line?))
      (setq chr (send bp :get-char-backward))
      (unless (horizontal-white-space? chr)
	(send bp :advance-pos 1)
	(return t))))
  (do ((chr (send bp :get-char)
	    (send bp :get-char)))
      ((not (horizontal-white-space? chr)))
    (send bp :delete-characters 1))
  nil)

(defun empty-line? (line)
  (loop with chars = (line-chars line)
	for i from 0 below (line-char-count line)
	for chr = (schar chars i)
	always (horizontal-white-space? chr)))

(editor-bind-key '(#\control-x #\control-o) delete-blank-lines)

(defun delete-blank-lines ()
  (let ((line (bp-line *editor-cursor*)) (q))
    (cond ((and (=& (line-char-count line) 0)	;(empty-line? line)
		(or (not (setq q (line-previous line)))
		    (not (empty-line? q)))
		(setq q (line-next line))
		(not (empty-line? q)))
	     ;Single blank line
	     (send line :delete-chars-from-position 0)
	     (send q :delete-line-separator))
	  (t (loop while (and (line-next line) (empty-line? (line-next line)))
		   do (send (line-next line) :delete-chars-from-position 0)
		      (send (line-next line) :delete-line-separator))
	     (loop for line = (bp-line *editor-cursor*)
		   while (and (empty-line? line)
			      (line-previous line)
			      (empty-line? (line-previous line)))
		   do (send (line-previous line) :delete-chars-from-position 0)
		   do (send line :delete-line-separator))
	     (delete-horizontal-space))))
  nil)

(defun delete-blank-space (&optional (bp *editor-cursor*))
  (send bp :backward-over-white-space)
  (loop until (or (send bp :end-of-buffer?)
		  (not (white-space? (send bp :get-char))))
	do (send bp :delete-characters 1))
  (unless (or (send bp :beginning-of-buffer?)
	      (send bp :end-of-buffer?)
	      (char= right-paren (send bp :get-char))
	      (char= left-paren (send bp :peek-char-backward)))
    (send bp :insert-char #\space))
  nil)

;;; In GSB's emacs we bind meta-slash with this command:
;(editor-bind-key #\meta-/ delete-blank-space)
;
;Also, it might be better to define control-x control-o 
;to the more consistent function:
;(editor-defun-key '(#\control-x #\control-o) delete-blank-lines-proposal
; (delete-blank-space)
; (linefeed)
; nil)
;

(editor-bind-key #\meta-\) move-over-right-paren)

(defun move-over-right-paren ()
  (loop while
	(not (char= right-paren (send *editor-cursor* :get-char-forward))))
  (send *editor-cursor* :advance-pos -1)
  (delete-blank-space *editor-cursor*)
  (send *editor-cursor* :advance-pos 1)
  (linefeed)
  nil)


