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

(defvar energize-auto-raise-screen t
  "If T frames are automatically raised when Energize wants to show them.")

(defvar energize-connect-hook nil
  "*Function or functions to run when the Energize connection is established.")

(defvar energize-disconnect-hook nil
 "*Function or functions to run when the Emacs/Energize connection is closed.")


(defvar energize-screen-mode nil)
(defvar energize-split-screens-p t)

(defun energize-multi-screen-mode ()
  "Call this function to put Energize into multi-frame mode.

A frame named \"debugger\" will be used for the *Debugger* buffer,
  and its associated source files.
A frame named \"energize\" will be used for the Top-Level buffer.
A frame named \"browser\" will be created for each L.E. Browser buffer.
 At most 5 of these will be created; then they will be reused.
A frame named \"project\" will be created for each Project buffer.
A frame named \"error-log\" will be created for the Error Log buffer
 and its associated source files (as when the Next Error command 
 displays a source file.)
A frame named \"manual\" will be created for each UNIX Manual page.
 At most 5 of these will be created; then they will be reused.

If an external editor is being used, then source files will be displayed
read-only in the \"debugger\" frame.

If an external editor is not being used, then frames named \"sources\" 
will be created to edit source files.  At most five of these will be 
created; then they will be reused.  Find-file will use the current frame,
whatever that happens to be, but find-file-other-window, and selecting 
source files from the Buffers menu will use an existing frame displaying
the file in question, or create a new one if there isn't one.

Call `energize-single-screen-mode' to turn this off.

See the documentation for the function get-frame-for-buffer for 
information on how to customize this."
  (interactive)
  (put 'project      'instance-limit 0)
  (put 'sources      'instance-limit 5)
  (put 'manual       'instance-limit 5)
  (put 'browser      'instance-limit 5)
  (put 'energize-debugger-mode        'frame-name 'debugger)
  (put 'gdb-mode		      'frame-name 'debugger)
  (put 'energize-top-level-mode       'frame-name 'energize)
  (put 'energize-browser-mode         'frame-name 'browser)
  (put 'energize-breakpoint-mode      'frame-name 'browser)
  (put 'energize-project-mode         'frame-name 'project)
  (put 'energize-no-file-project-mode 'frame-name 'project)
  (put 'energize-log-mode             'frame-name 'error-log)
  (put 'energize-manual-entry-mode    'frame-name 'manual)
  (if energize-external-editor
      (setq get-frame-for-buffer-default-frame-name 'debugger)
    ;; hmmmm...
    (setq get-frame-for-buffer-default-frame-name 'sources))
  (setq buffers-menu-switch-to-buffer-function 'pop-to-buffer)
  (setq energize-screen-mode 'multi)
  t)

(defun energize-several-screens-mode ()
  "Call this function to put Energize into multi-frame mode, 
but with only a few frames.  See also `energize-multi-screen-mode'.

A frame named \"debugger\" will be used for the *Debugger* buffer,
  and its associated source files.
A frame named \"energize\" will be used for the Top-Level buffer.
A single frame named \"browser\" will be created for L.E. Browser buffers.
A single frame named \"project\" will be created for Project buffers.
A frame named \"error-log\" will be created for the Error Log buffer
 and its associated source files (as when the Next Error command 
 displays a source file.)
A single frame named \"manual\" will be created for UNIX Manual page buffers.

If an external editor is being used, then source files will be displayed
read-only in the \"debugger\" frame.

If an external editor is not being used, then a single frame named 
\"sources\" will be created to edit source files.  Find-file will use the
current frame, whatever that happens to be, but find-file-other-window, 
and selecting source files from the Buffers menu will use an existing frame
displaying the file in question, or create a new one if there isn't one.

Call `energize-single-screen-mode' to turn this off.

See the documentation for the function get-frame-for-buffer for 
information on how to customize this."
  (interactive)
  (energize-multi-screen-mode)
  (remprop 'browser 'instance-limit)
  (remprop 'project 'instance-limit)
  (remprop 'manual  'instance-limit)
  (remprop 'sources 'instance-limit)
  (setq energize-screen-mode 'several)
  t)

(defun energize-single-screen-mode ()
  "Call this function to put Energize into single-frame mode.
All buffers will be displayed in the currently selected frame."
  (interactive)
  (remprop 'browser 'instance-limit)
  (remprop 'project 'instance-limit)
  (remprop 'manual  'instance-limit)
  (remprop 'sources 'instance-limit)
  (remprop 'energize-debugger-mode        'frame-name)
  (remprop 'gdb-mode		          'frame-name)
  (remprop 'energize-top-level-mode       'frame-name)
  (remprop 'energize-browser-mode         'frame-name)
  (remprop 'energize-breakpoint-mode      'frame-name)
  (remprop 'energize-project-mode         'frame-name)
  (remprop 'energize-no-file-project-mode 'frame-name)
  (remprop 'energize-log-mode             'frame-name)
  (remprop 'energize-manual-entry-mode    'frame-name)
  (setq get-frame-for-buffer-default-frame-name nil)
  (setq buffers-menu-switch-to-buffer-function 'switch-to-buffer)
  (setq energize-screen-mode 'single)
  nil)

(energize-single-screen-mode)


;;; Connecting and disconnecting

(or energize-attributes-mapping
    (setq energize-attributes-mapping
	  (purecopy
	   '((0 . default)
	     (1 . bold)
	     (2 . italic)
	     (3 . bold-italic)
	     (4 . attributeSmall)
	     (50 . attributeGlyph)
	     (51 . attributeSectionHeader)
	     (52 . attributeToplevelFormGlyph)
	     (53 . attributeModifiedToplevelFormGlyph)
	     (54 . attributeBrowserHeader)
	     (68 . attributeWriteProtected)
	     (69 . attributeModifiedText)
	     ))))

;; Make the faces before emacs is dumped - this should be ok, they will be
;; initialized from the resource database when the first frame is created.
(let ((rest energize-attributes-mapping))
  (while rest
    (make-face (cdr (car rest)))
    (setq rest (cdr rest))))


(defun any-energize-buffers-p ()
  (let ((rest (buffer-list))
	(result nil))
    (while rest
      (if (energize-buffer-p (car rest))
	  (setq result (car rest) rest nil)
	(setq rest (cdr rest))))
    result))

(defun connect-to-energize (server &optional enarg)
  "Connect this emacs to a Energize server.
The SERVER argument should be the name of the host that the kernel is
running on (empty-string for localhost).  It may also be of the form
``hostname:user'' or ``:user'', meaning to use the server running with
userid USER."
  (interactive (if (connected-to-energize-p)
		   (error "Already connected to the server.") ; you bogon.
		 (list (read-string "connect to energize server: "))))
  (if (connected-to-energize-p)
      (error "Already connected to the server.")) ; you bogon.
  (if (or (null server) (equal server ""))
      (setq server (or (getenv "ENERGIZE_PORT") (system-name))))
  (setq default-frame-name "energize")
  (energize-rename-things)
  (energize-hack-external-editor-mode)

  (let ((energize-disconnect-hook
	 ;; If we're being run interactively, don't exit emacs if connecting
	 ;; to Energize fails!  That's damn annoying.
	 (if (and (interactive-p)
		  (consp energize-disconnect-hook)
		  (memq 'save-buffers-kill-emacs energize-disconnect-hook))
	     (delq 'save-buffers-kill-emacs
		   (copy-sequence energize-disconnect-hook))
	   energize-disconnect-hook)))

    (connect-to-energize-internal server enarg)
    ;; Wait for the Top-Level buffer to be created.
    ;; This really should happen down in C, but...
    (let ((p (or (get-process "energize")
		 (error "Could not connect to Energize.")))
	  b)
      (while (progn
	       (or (connected-to-energize-p)
		   (error "Energize connection refused."))
	       (not (setq b (any-energize-buffers-p))))
	(accept-process-output p))
      ;; Make the displayed Energize buffer initially displayed.
      (pop-to-buffer b)
      (delete-other-windows)
      (run-hooks 'energize-connect-hook))))

(defun disconnect-from-energize ()
  (interactive)
  "Close the connection to energize"
  (close-connection-to-energize))

;;; Energizing all buffers
;; After being connected to energize this function energizes all the
;; buffers that contain files that Energize knows about.

(defun energize-all-buffers ()
  "Energize any buffer showing a file that the Energize server knows about.
Has to be called after Emacs has been connected to Energize"
  (if (not (connected-to-energize-p))
      (error "You have to connect to Energize first"))
  (save-window-excursion
   (save-excursion
    (let ((buffers (buffer-list))
	  (buffers-to-avoid '())
	  (lock-directory nil)
	  buffer
	  filename)
      (while buffers
	(setq buffer (car buffers))
	(setq buffers (cdr buffers))
	(setq filename (buffer-file-name buffer))
	(set-buffer buffer)
	(cond
	 ((and filename
	       (not (energize-buffer-p buffer))
	       (energize-query-buffer filename t))
	  (cond ((buffer-modified-p)
		 (if (y-or-n-p
		      (format
		       "Buffer %s must be saved to be Energized; save it? "
		       (buffer-name buffer)))
		     (progn
		       (set-buffer buffer) ; oh, man...
		       (save-buffer))
		   ;; said "no"
		   (setq buffers-to-avoid (cons buffer buffers-to-avoid))))
		
		((and (null (verify-visited-file-modtime buffer))
		      (file-exists-p filename))
		 (set-buffer buffer)
		 (if (y-or-n-p
		      (format "Buffer %s has changed on disk, revert? "
			      (buffer-name buffer)))
		     (progn
		       (set-buffer buffer)
		       (revert-buffer nil t))
		   ;; said "no"
		   (setq buffers-to-avoid (cons buffer buffers-to-avoid))))

		;; It's wrong to check to also see if someone else is locking
		;; the file.  The file is already in the buffer, and the user
		;; isn't really modifying it -- we're just rewriting it because
		;; energize likes to do that.  That's why locking should be
		;; disabled here.
		)
	  (if (not (memq buffer buffers-to-avoid))
	      (progn
		(message "Energizing buffer %s..." (buffer-name buffer))
		(find-file-noselect filename))
	    (message (format "Buffer %s not Energized." (buffer-name buffer)))
	    (sit-for 1)))))
      (message nil)))))

(add-hook 'energize-connect-hook 'energize-all-buffers)


;; This is called when the connection to Energize is lose (for whatever
;; reason).   We could just run the energize-disconnect-hook from C and
;; put this function on it, but then the user could hurt themselves.
;;
(defun de-energize-all-buffers ()
  (save-excursion
    (let ((buffers (buffer-list))
	  buffer)
      (while buffers
	(condition-case condition
	    (progn
	      (setq buffer (car buffers))
	      (set-buffer buffer)
	      (cond ((not (energize-buffer-p buffer))
		     nil)
		    ((eq (energize-buffer-type buffer) 'energize-source-buffer)
		     (map-extents
		      (function (lambda (extent ignore)
				  (if (extent-property extent 'energize)
				      (delete-extent extent))
				  nil))
		      buffer)
		     (remove-hook 'write-file-data-hooks
				  'energize-write-data-hook)
		     (setq revert-buffer-insert-file-contents-function nil)
		     (ad-Orig-normal-mode-after-energize) ; #### necessary?
		     )
		    (t ; non-source-file Energize buffers
		     (set-buffer-modified-p nil)
		     (if (eq (other-buffer buffer) buffer)
			 (set-buffer (get-buffer-create "*scratch*"))
		       (set-buffer (other-buffer buffer)))
		     (kill-buffer buffer))))
	  (error ;; condition-case clause
	   (beep)
	   (message "Error while de-Energizing: %s" condition)))
	(setq buffers (cdr buffers)))))
  ;; now clean the menubar
  (deactivate-all-energize-menu-items)
  (energize-rename-things 'back)
  (run-hooks 'energize-disconnect-hook)
  nil)


(defun energize-rename-things (&optional back)
  ;; People who don't like emacs don't like seeing the word "Emacs" either
  (let ((case-fold-search t))
    (if (and (consp mode-line-buffer-identification)
	     (stringp (car mode-line-buffer-identification))
	     (string-match (if back "\\bEnergize\\b"
			     "\\bL?Emacs\\([- \t]*[-._0-9]+\\)?\\b")
			   (car mode-line-buffer-identification)))
	(setq-default mode-line-buffer-identification
		      (cons
		       (concat (substring (car mode-line-buffer-identification)
					  0 (match-beginning 0))
			       (if back "Emacs" "Energize")
			       (substring (car mode-line-buffer-identification)
					  (match-end 0)))
		       (cdr mode-line-buffer-identification))))
;    (if (stringp frame-title-format)
;	(if back
;	    (if (string-match "^Energize\\b ?" frame-title-format)
;		(setq-default frame-title-format "%S: %b"))
;	  (or (string-match "Energize" frame-title-format)
;	      (setq-default frame-title-format "Energize: %b"))))
    )
  nil)



;;; The kernel is very random about the buffer-types it returns.
;;; This is a temporary permanent fix...

(defun energize-buffer-type (buffer)
  "Returns a symbol denoting the type of an Energize buffer, or nil."
  (let ((type (energize-buffer-type-internal buffer)))
    (cond ((eq type 'energize-top-level-buffer)
	   (cond ((equal "Error Log" (buffer-name buffer))
		  'energize-error-log-buffer)
		 ((equal "*includers*" (buffer-name buffer))
		  'energize-includers-buffer)
		 ((string-match "^Browser" (buffer-name buffer))
		  'energize-browser-buffer)
		 (t type)))
	  ((eq type 'energize-unspecified-buffer)
	   (signal 'error (list "buffer type unspecified" buffer)))
	  ((and (null type) (energize-buffer-p buffer))
	   (signal 'error
		   (list "null buffer type for energize buffer" buffer)))
	  (t type))))

(defun energize-extent-at (pos &optional buffer)
  (extent-at pos buffer 'energize))

(defun non-energize-errors-exist-p ()
  ;; Whether `next-error' executed right now should do the emacs thing.
  ;; If we're in a *grep* or *compile* buffer, always do the emacs thing.
  ;; If we're in the Error Log, always do the Energize thing.
  ;; Otherwise, do the emacs thing if it would succeed; otherwise do the
  ;; Energize thing.
  (or (compilation-buffer-p (current-buffer))			; in *grep*
      (and (not (eq (energize-buffer-type (current-buffer))	; in ErrLog
		    'energize-error-log-buffer))
	   ;; defined in compile.el (a XEmacs addition).
	   (compilation-errors-exist-p))))


;;; Misc Energize hook functions

(defvar inside-energize-buffer-creation-hook-function nil)

(defun energize-buffer-creation-hook-function (buffer)
  ;; This loser is called every time Energize wants to create a buffer,
  ;; whether it is being spontaniously displayed (as by the debugger) or
  ;; as a result of calling find-file -> energize-find-file-noselect ->
  ;; energize-query-buffer.
  (let ((inside-energize-buffer-creation-hook-function t))
    ;; the above is so we can call this from normal-mode, except when
    ;; we're calling normal-mode.
    (save-excursion
      (set-buffer buffer)

      ;; Energize always hands us truenames, or something close to them
      ;; (it chomps the /tmp_mnt/ automounter cruft off.)  Let the user
      ;; set up a pretty translation just like they can for normal files.
      (if buffer-file-name
	  (setq buffer-file-name (abbreviate-file-name
				  (expand-file-name buffer-file-name))
		default-directory (file-name-directory buffer-file-name))
	(setq default-directory
	      (abbreviate-file-name (expand-file-name default-directory))))

      (if buffer-file-name (set-buffer-modtime buffer))

      (let ((type (energize-buffer-type buffer)))
	(cond ((eq type 'energize-top-level-buffer)
	       (energize-top-level-mode))
	      ((eq type 'energize-browser-buffer)
	       (energize-browser-mode))
	      ((eq type 'energize-includers-buffer)
	       (energize-browser-mode))
	      ((or (eq type 'energize-error-log-buffer)
		   (eq type 'energize-log-file-buffer))
	       (energize-log-mode)
	       (setq buffer-read-only t))
	      ((eq type 'energize-project-buffer)
	       (if (buffer-file-name)
		   (energize-project-mode)
		 (energize-no-file-project-mode)))
	      ((eq type 'energize-debugger-buffer)
	       (energize-debugger-mode))
	      ((eq type 'energize-breakpoint-buffer)
	       (energize-breakpoint-mode))
	      ((eq type 'energize-unix-manual-buffer)
	       (energize-manual-mode))
	      ((or (eq type 'energize-source-buffer)
		   ;;(eq type 'energize-unspecified-buffer)
		   ;;(null type)
		   )
	       (compute-buffer-file-truename)
	       (if (buffer-file-name buffer)
		   (after-find-file nil t)
		 (funcall default-major-mode))
	       )
	      (t
	       (signal 'error (list "unknown energize buffer type" type)))))

      (if (eq (energize-buffer-type (current-buffer)) 'energize-source-buffer)
	  (energize-source-minor-mode))

      (energize-external-editor-set-mode buffer)
      )))

(setq energize-create-buffer-hook 'energize-buffer-creation-hook-function)

;;; Buffer modified hook

(defun energize-send-buffer-modified-1 (start end)
  (if (not (energize-buffer-p (current-buffer)))
      nil
    (map-extents #'(lambda (ext ignore)
		     (and (extent-property ext 'energize)
			  (set-extent-face ext 'attributeModifiedText))
		     nil)
		 (current-buffer) start end)
    (energize-send-buffer-modified t start end)))

(add-hook 'before-change-functions 'energize-send-buffer-modified-1)

;;; Reverting buffers
;;; This is called when Energize has informed us that a buffer has changed
;;; on disk, and we need to revert.

(defun energize-auto-revert-buffer (buf)
  (cond ((not (file-exists-p (buffer-file-name buf)))
	 ;; Signal an error here?  Naah, let someone else deal with it.
	 nil)
	;; If it's not modified, just revert.  If it is modified, ask.
	((or (not (buffer-modified-p buf))
	     (yes-or-no-p
	      (format "File %s changed on disk.  Discard your edits? "
		      (file-name-nondirectory (buffer-file-name buf)))))
	 (save-excursion
	   (set-buffer buf)
	   (revert-buffer t t)))))

;;; Energize kernel busy hook

(defun energize-message-if-not-in-minibuffer (reason)
  (if (not (eq (selected-window) (minibuffer-window)))
      (message reason)))

(setq energize-kernel-busy-hook 'energize-message-if-not-in-minibuffer)

;;; set-buffer-modified-p hook

(defun energize-send-buffer-modified-2 (state start end)
  (if (not (energize-buffer-p (current-buffer)))
      nil
    (if (not state)
	;; If we're unmodifying the buffer, un-highlight all Energize extents.
	(let ((e (next-extent (current-buffer))))
	  (while e
	    (if (and (extent-property e 'energize)
		     (eq (extent-face e) 'attributeModifiedText))
		(set-extent-face e nil))
	    (setq e (next-extent e)))))
    (energize-send-buffer-modified state start end)))

(setq energize-buffer-modified-hook 'energize-send-buffer-modified-2)

;;; hook in editorside.c

(setq energize-kernel-modification-hook nil)


;; command line

(defconst energize-args '(("-energize"	 . command-line-process-energize)
			  ("-context"	 . command-line-process-energize-1)
			  ("-beam-me-up" . command-line-process-energize-1)))

(setq command-switch-alist (append command-switch-alist energize-args))

(fset 'command-line-process-energize 'command-line-process-energize-1)
(put 'command-line-process-energize-1 'undocumented t)
(defun command-line-process-energize-1 (arg)
  "Connect to the Energize server at $ENERGIZE_PORT."
  (let ((e-arg (car command-line-args-left))
	(e-host (getenv "ENERGIZE_PORT"))) ; maybe nil
    (if (and e-arg (string-match "\\`[0-9a-fA-F]+[,][0-9a-fA-F]+\\'" e-arg))
	(setq command-line-args-left (cdr command-line-args-left))
      (setq e-arg nil))
    (message "Connecting to Energize...") 
    (sit-for 0)
    (condition-case ()
	(connect-to-energize e-host e-arg)
      (error
       (beep)
       (if e-host
	   (message "Failed to connect to Energize at %s." e-host)
	 (message "Failed to connect to Energize."))
       (sit-for 1)))))


;;; Originally defined in frame.el
;;; If we're being invoked with -energize, then set the default
;;; frame name to "energize"
;;; This is a total kludge; there ought to be a hook that gets
;;; run before the first frame is created (either before or
;;; after term/x-win.el is loaded.)

(or (fboundp 'energize-orig-frame-initialize)
    (fset 'energize-orig-frame-initialize
	  (symbol-function 'frame-initialize)))

(defun frame-initialize ()
  (if (let ((rest energize-args))
	(catch 'done
	  (while rest
	    (if (member (car (car rest)) command-line-args)
		(throw 'done t))
	    (setq rest (cdr rest)))
	  nil))
      (setq default-frame-name "energize"))
  (energize-orig-frame-initialize))

(defun energize-x-initializations ()
  (cond ((not noninteractive)
	 (energize-define-function-keys)
	 (energize-configure-font-lock-mode t (not (x-color-display-p)) t)
	 ;; faces will be initialized from the resource database when the first
	 ;; frame is created.    
	 (let ((rest energize-attributes-mapping))
	   (while rest
	     (make-face (cdr (car rest)))
	     (setq rest (cdr rest))))
	 )))

;; Do these bindings after connecting to the X server, but before 
;;; loading any init files, so that init files can override them.
(add-hook 'before-init-hook 'energize-x-initializations)
