;;; -*- Mode:Emacs-Lisp -*-
;;; Copyright © 1992 by Lucid, Inc.  All Rights Reserved.

;;; Displaying buffers.  Why is this so hard?


;;; This crud is damage control, because sometimes things get confused, and
;;; the server asks us to display a buffer that has been killed.  

(defun energize-request-kill-buffer-if-dead (buffer)
  (cond ((not (bufferp buffer)) t)
        ((null (buffer-name buffer))
         (if (energize-buffer-p buffer)
             (energize-request-kill-buffer buffer))
         t)
        (t nil)))

(defun energize-prune-killed-buffers-from-list (buffer-extent-list)
  (let ((rest buffer-extent-list)
        (buffer-count 0)
        (deleted-count 0))
    (while rest
      (let* ((buffer (car rest))
             (extent (car (cdr rest))))
        (setq rest (cdr (cdr rest)))
        (setq buffer-count (1+ buffer-count))
        (if (energize-request-kill-buffer-if-dead buffer)
            (progn
              (setq deleted-count (1+ deleted-count))
              (setq buffer-extent-list (delq buffer buffer-extent-list))
              (setq buffer-extent-list (delq extent buffer-extent-list))))))
    (if (> deleted-count 0)
        (progn
          (message 
            (format "Oops, confused about %s selected %s -- please try again."
                    (if (> deleted-count 1) 
                        (format "%d of the" deleted-count)
                        (if (> buffer-count 1)
                            "one of the"
                            "the"))
                    (if (> buffer-count 1)
                        "buffers"
                        "buffer")))
          (ding t)))
    buffer-extent-list))


(defvar energize-auto-scroll-p t	;#### this should be nil, t is LOSING
  "*If t, energize will scroll your debugger and error log buffers
to the bottom whenever output appears with reckless abandon.  If nil,
it will behave just like normal shell and gdb-mode buffers.")

(defvar energize-error-log-context-lines 0
  "*Number of lines to skip above the current error in the Energize error log")

;;; called by energize-show-all-buffers
;;; If the extent is specified:
;;;   - scrolls the window so that point is at at the beginning of the extent.
;;;   - If the buffer is "Error Log", the extent is moved to top-of-window.
;;;   - if `only-one' and the buffer is a source buffer, then... what?
;;; If the buffer is "*Debugger*" or "Error Log", point is moved to eof,
;;;   IF and ONLY if it was at EOF already.
;;;
(defun energize-scroll-window-at-extent (window extent only-one)
  (let* ((buffer (window-buffer window))
	 (type (energize-buffer-type buffer)))
    (if (and extent (null (extent-start-position extent)))
	;; it has been detached somehow.
	(setq extent nil))
    (if extent
	(let ((pos (extent-start-position extent)))
	  (if (not (eq pos 0))
	      (progn
		(set-window-point window pos)
		(cond ((eq type 'energize-error-log-buffer)
		       ;; scroll the Error Log buffer so that the first error
		       ;; is at the top of the window.
		       (set-window-start window
					 (save-excursion
					   (set-buffer buffer)
					   (goto-char pos)
					   (forward-line
					    (-
					     energize-error-log-context-lines))
					   (beginning-of-line)
					   (point))))
		      ((and only-one (eq type 'energize-source-buffer))
		       ;; if only one buffer is requested to be visible and it
		       ;; is a source buffer then scroll point close to the top
		       (set-window-start window
					 (save-excursion
					   (set-buffer buffer)
					   (goto-char pos)
					   (beginning-of-line)
					   (if (> (window-height window)
						  next-screen-context-lines)
					       (vertical-motion
						(- next-screen-context-lines)
						window)
					     (vertical-motion -1 window))
					   (point)))))))))

    (cond ((and (memq type '(energize-error-log-buffer
			     energize-debugger-buffer))
		; don't move point if it's before the last line
		(or energize-auto-scroll-p
		    (>= (window-point window)
			(save-excursion
			  (set-buffer (window-buffer window))
			  ;;(comint-mark)
			  (energize-user-input-buffer-mark)
			  )))
		)
	   ;; Debugger and Error Log buffers generally get scrolled to
	   ;; the bottom when displayed.
	   (set-window-point window
			     (save-excursion (set-buffer buffer)
					     (+ 1 (buffer-size))))
	   ;; Careful to deactivate the selection when automatically moving
	   ;; the user to the end of the buffer.  This is suboptimal, but
	   ;; otherwise we have bad interactions with the debugger-panel
	   ;; Print button.  (Double-click on a value (point is now at the
	   ;; end of that word); hit Print; point is now at point-max, but
	   ;; the original word is still highlighted, which is incorrect -
	   ;; we're now in a state where the selection highlighting and the
	   ;; region between point and mark is out of sync.  I'm not entirely
	   ;; sure how to fix this short of using a point-motion hook of some
	   ;; kind, so we'll punt, and just deactivate the region instead.)
	   (zmacs-deactivate-region)
	   ))))


;;; called by energize-make-buffers-visible
;;; For each of the contents of an plist of buffers and extents:
;;;   - If the buffer is visible in a window
;;;     - dedicate the window
;;;     - energize-scroll-window-at-extent
;;; If we dedicated any windows, select the last one dedicated.
;;; For each of the buffers and extents:
;;;   - pop-to-buffer
;;;   - remember the first window selected in this way
;;;   - dedicate the window
;;;   - energize-scroll-window-at-extent; only-one arg is true if there
;;;     is only one buffer/extent pair in the list
;;;   - if energize-edit-buffer-externally-p make it read-only
;;; Un-dedicate the windows
;;; Select the remembered window (the first one we popped-to-buffer on)
;;; Maybe raise its frame
;;;
(defun energize-show-all-buffers (buffer-extent-list)
  (let ((pop-up-windows t)
	(dedicated-windows ())
	(buffer-extent-current)
	(window-to-select ())
	(only-one (null (cdr (cdr buffer-extent-list)))))
    (setq buffer-extent-current buffer-extent-list)
    (while buffer-extent-current
      (let* ((buffer (car buffer-extent-current))
	     (extent (car (cdr buffer-extent-current)))
	     (window (get-buffer-window buffer (selected-frame))))
	(if window
	    (progn
	      (set-window-buffer-dedicated window buffer)
	      (setq dedicated-windows (cons window dedicated-windows))
	      (energize-scroll-window-at-extent window extent only-one)))
	(setq buffer-extent-current (cdr (cdr buffer-extent-current)))))
    (if dedicated-windows
	(select-window (car dedicated-windows)))
    (setq buffer-extent-current buffer-extent-list)
    (while buffer-extent-current
      (let* ((buffer (car buffer-extent-current))
	     (extent (car (cdr buffer-extent-current))))
;; ## what was this intended to do? a frame is being passed as the
;; ## argument which means "always select a different window even if
;; ## it's visible in the selected window.
;;	(pop-to-buffer buffer nil (selected-frame))
	(pop-to-buffer buffer)
	(if (energize-edit-buffer-externally-p buffer)
	    (setq buffer-read-only t))
	(let ((window (selected-window)))
	  (if (null window-to-select)
	      (setq window-to-select window))
	  (set-window-buffer-dedicated window buffer)
	  (setq dedicated-windows (cons window dedicated-windows))
	  (energize-scroll-window-at-extent window extent only-one))
	(setq buffer-extent-current (cdr (cdr buffer-extent-current)))))
    (while dedicated-windows
      (set-window-buffer-dedicated (car dedicated-windows) ())
      (setq dedicated-windows (cdr dedicated-windows)))

    (select-window window-to-select)
    ;; now we may have to pop the frame up
    (let ((frame (selected-frame)))
      (if (and energize-auto-raise-screen
	       (or (not (frame-visible-p frame))
		   (not (frame-totally-visible-p frame))))
	  (progn
	    (sit-for 0)
	    (make-frame-visible frame))))))

;;; called by energize-make-buffers-visible
(defun energize-main-buffer-of-list (list)
  ;; Given an alternating list of buffers and extents, pick out the
  ;; "interesting" buffer.  If one of the buffers is in debugger-mode,
  ;; or in breakpoint-mode, then that's the interesting one; otherwise,
  ;; the last buffer in the list is the interesting one.
  (let (buffer mode result)
    (while list
      (setq buffer (car list))
      (or (memq mode '(energize-debugger-mode energize-breakpoint-mode))
	  (setq result buffer
		mode (save-excursion (set-buffer buffer) major-mode)))
      (setq list (cdr (cdr list))))
    result))

;;; called by energize-make-many-buffers-visible-function
;;; If there is only one buffer/extent pair, and it's a source buffer, then
;;;  edit it in vi if that's the kind of kinkiness we're into.
;;; Get the "main" buffer, and select a frame for it.
;;; Then call energize-show-all-buffers.
;;;
(defun energize-make-buffers-visible (buffer-extent-list)
  (let ((main-buffer (energize-main-buffer-of-list buffer-extent-list))
	window)
    (if (and (null (cdr (cdr buffer-extent-list)))
	     (energize-edit-buffer-externally-p main-buffer))
	(energize-edit-buffer-externally-1 main-buffer
					   (car (cdr buffer-extent-list)))
      ;; This may create and/or select a frame as a side-effect.
      ;; I'm not sure it's necessary to call this, as display-buffer
      ;; calls it too.  But it can't hurt to select the appropriate
      ;; frame early...
      (let ((hacked-frame nil))
	(cond ((null energize-split-screens-p)
	       nil)
	      ((get-frame-name-for-buffer main-buffer)
	       (setq hacked-frame t)
	       (if pre-display-buffer-function
		   (funcall pre-display-buffer-function main-buffer nil nil))
	       )
	      ((setq window (get-buffer-window main-buffer t))
	       (cond (window
		      (setq hacked-frame t)
		      (select-frame (window-frame window))))))
	(let ((pre-display-buffer-function
	       (if hacked-frame nil pre-display-buffer-function)))
	  (energize-show-all-buffers buffer-extent-list))
;;	;; kludge!!  Select the debugger frame, not the sources frame.
;;	(if (and (null energize-split-screens-p)
;;		 pre-display-buffer-function)
;;	    (funcall pre-display-buffer-function main-buffer nil nil))
	))))

;;; this is the guts of energize-make-many-buffers-visible
;;; `arg' is really two args: `buffer-extent-list' and `go-there'.
;;; go-there is specified by 
;;; Given a list of buffer/extent pairs, make them all visible at once
;;;  (presumably in the same frame?)
;;; If `go-there'
;;;  - call energize-make-buffers-visible
;;; else
;;;  - dedicate the selected window
;;;  - call energize-make-buffers-visible
;;;  - re-select and undedicate the original selected window
;;;
(defun energize-make-many-buffers-visible-function (arg)
  (let ((buffer-extent-list (car arg))
	(go-there (cdr arg)))
    ;; enqueue an history record if we're going to move
    (if go-there
	(energize-history-enqueue))
    (setq buffer-extent-list 
	  (energize-prune-killed-buffers-from-list buffer-extent-list))
    (if buffer-extent-list
	(if go-there
	    (energize-make-buffers-visible buffer-extent-list)
	  (let ((window (selected-window)))
	    (set-window-buffer-dedicated window (window-buffer window))
	    (unwind-protect 
		(energize-make-buffers-visible buffer-extent-list)
	      (set-window-buffer-dedicated window ())
	      (select-window window)))))))

(defvar energize-make-many-buffers-visible-should-enqueue-event t
  "Special variable bound by energize-execute-command to allow the
buffers to be selected while the command is executed")

;;; called by by editorside.c:MakeBufferAndExtentVisible().
;;; should-enqueue is bound by `energize-execute-command'
;;;
(defun energize-make-many-buffers-visible (buffer-extent-list go-there)
  "First arg is a list of buffers and extents. All those should be
made visible at the same time.  If the second argument is T then point
should be moved to the first character of the extent of the first
buffer, or to the buffer if no extent is specified for this buffer.  
If second argument is NIL point should not change."
  (if energize-make-many-buffers-visible-should-enqueue-event
      ;; don't do it from process filters, but wait until we come back to
      ;; top-level.  Using go-there should still be done sparingly, as we can
      ;; surprise the user and grab their keystrokes into another buffer.
      (enqueue-eval-event 'energize-make-many-buffers-visible-function
			  (cons buffer-extent-list go-there))
    ;; go-there is always true when called from energize-execute-command,
    ;; I guess under the assumption that it's always ok to select a buffer
    ;; when we're doing something in direct response to a menu selection.
    (energize-make-many-buffers-visible-function
     (cons buffer-extent-list t))))


;;; This deales with the energize history
(defvar energize-navigation-history '(nil)
  "List of places where Energize took you to.
It is a list of (file-name/buffer-name . position)")

(defvar energize-history-maximum-length 20
  "Maximum number of locations kept in the energize history")

(defvar energize-navigation-current ()
  "Current pointer into the energize-navigation-history")

(defvar energize-navigation-current-length 0)

(defun energize-history-enqueue ()
  "Memorize the current place in the history.
Trim the history if need be."
  (let ((new-item
	 (cons (or buffer-file-truename (current-buffer))
	       (1+ (count-lines 1 (point))))))
    (if (not (equal new-item (car energize-navigation-history)))
	(progn
	  (setq energize-navigation-history
		(cons new-item energize-navigation-history))
	  (setq energize-navigation-current-length
		(1+ energize-navigation-current-length))
	  (if (> energize-navigation-current-length
		 (* 2 energize-history-maximum-length))
	      (let ((tail (nthcdr energize-history-maximum-length
				  energize-navigation-history)))
		(rplacd tail nil)
		(setq energize-navigation-current-length
		      energize-history-maximum-length)))))))

(defun energize-history-dequeue ()
  "Forget the current place in the history"
  (setq energize-navigation-history (cdr energize-navigation-history)))

(defun energize-history-go-back (item)
  "Go back to the place memorized by item"
  (let ((buffer-or-file (car item))
	(position (cdr item))
	(buffer ()))
    (cond ((bufferp buffer-or-file)
	   (setq buffer buffer-or-file))
	  ((stringp buffer-or-file)
	   (setq buffer (or (get-file-buffer buffer-or-file)
			    (find-file-noselect buffer-or-file)))))
    (if (null (buffer-name buffer))
	()
      (pop-to-buffer buffer)
      (goto-line position)
      t)))

(defun energize-history-previous ()
  "Go back in the history.
If the last command was the same go back more"
  (interactive)
  (if (not (eq last-command 'energize-history-previous))
      (setq energize-navigation-current energize-navigation-history))
  (energize-history-enqueue)
  (while (and (car energize-navigation-current)
	      (not
	       (energize-history-go-back (car energize-navigation-current))))
    (rplaca energize-navigation-current
	    (car (cdr energize-navigation-current)))
    (rplacd energize-navigation-current
	    (cdr (cdr energize-navigation-current))))
  (if (null (car energize-navigation-current))
      (progn
	(energize-history-dequeue)
	(setq last-command 'beep)
	(error "You reached the beginning of the Energize history"))
    (setq energize-navigation-current
	  (cdr energize-navigation-current))))

(define-key global-map '(shift f14) 'energize-history-previous)

(defun energize-history ()
  "Show the energize history in the energize history buffer"
  (interactive)
  (pop-to-buffer "*Energize History*")
  (erase-buffer)
  (mapcar (function (lambda (item)
		      (if item
			  (progn
			    (insert (format "%s" (car item)))
			    (indent-to-column 32 1)
			    (insert (format "%s\n" (cdr item)))))))
	  energize-navigation-history)
  (goto-char (point-min))
  (energize-history-mode))

(defun energize-history-mode ()
  "Turn on energize history mode"
  )
