;;; ecb-eshell.el --- eshell integration for the ECB.
;; Copyright (C) 2000-2003 Free Software Foundation, Inc.
;; Copyright (C) 2000-2003 Kevin A. Burton (firstname.lastname@example.org)
;; Author: Kevin A. Burton (email@example.com)
;; Maintainer: Kevin A. Burton (firstname.lastname@example.org)
;; Location: http://relativity.yi.org
;; Version: 1.1.0
;; This file is [not yet] part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify it under
;; the terms of the GNU General Public License as published by the Free Software
;; Foundation; either version 2 of the License, or any later version.
;; This program is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
;; You should have received a copy of the GNU General Public License along with
;; this program; if not, write to the Free Software Foundation, Inc., 59 Temple
;; Place - Suite 330, Boston, MA 02111-1307, USA.
;; This package provides eshell integration for the ECB. This basically allows
;; you to jump to the eshell in the compilation window, synch up the current
;; eshell with the current ECB buffer and run commands without getting in the
;; It provides the following features:
;; - ability to jump to the eshell buffer within the compilation window ( C-.e )
;; If the eshell isn't running it will be started
;; - expands the compilation window when you run commands. So for example it
;; allows you to view the eshell in minimized mode and then when you run 'ls'
;; the window automatically expands.
;; - Sychronizes the current directory of the eshell with the current buffer
;; with each every ECB buffer.
;; - Provides smart window layout of the eshell buffer. This makes sure that
;; the eshell is taking up the exact amount of space and that nothing is
;; The goal is to make it easy to jump to a command prompt to run OS level
;; If you enjoy this software, please consider a donation to the EFF
;; Tue Jan 14 2003 14:53 PM (email@example.com):
;; Swiched back to the use of visible-window during body of
;; Mon Jan 13 2003 09:33 AM (burton@peace): Fixed a bug with
;; ecb-eshell-shrink-if-necessary if it wasn't being run from within the
;; correct buffer. Reworked some code to restore the old behavior prior to
;; visible-window being used as a side effect from within a macro.
;; Sun Jan 05 2003 04:05 PM (burton@universe): Fixed a bug with the way we
;; handle the switch-to-buffer after a eshell sync.
;; we need to make sure that that the eshell buffer isn't at the top of the
;; buffer history list just because we implicity changed its directory and
;; switched to it. It might not be a good idea in the long term to put it
;; all the way at the end of the history list but it is better than leaving
;; it at the top.
;; - Mon Dec 30 2002 6:57 PM (firstname.lastname@example.org):
;; Added ecb-eshell-start. See docstring.
;; - Son Dec 29 2002 9:43 AM (email@example.com):
;; Fixes some bugs and cleans up the code:
;; + ecb-eshell-save-buffer-history: In macros with a let-clause we must
;; generate the local variables with make-symbol!!
;; + ecb-eshell-current-buffer-sync:
;; - Now only executed if called in ecb-compile-window; see comment in the
;; function itself.
;; - Normalizing directories before comparing it
;; + ecb-eshell-cleanse: removed (end-of-buffer) because this always sets
;; the mark. This should not be used in elisp-programs!
;; + Added to all functions ecb-eshell-running-p
;; + Removed a lot of superfluous save-excursion, save-window-excursion
;; + Removed ecb-eshell-enlarge-when-starting; eshell is always implicit
;; started and selected by `ecb-eshell-goto-eshell' so the option
;; `ecb-eshell-enlarge-when-selecting' should be enough.
;; + Removed the ecb-eshell-buffer-name.
;; + Added new option ecb-eshell-synchronize
;; + Moved all add-hook into ecb-eshell-activate
;; + Added new function ecb-eshell-deactivate
;; + Made ecb-eshell-activate idempotent.
;; - Mon Feb 04 2002 10:44 PM (firstname.lastname@example.org): BUG: I think at certain
;; conditions, we might be able to get the eshell window to expand after we
;; automatically change directories. I think this has to be:
;; - Mon Feb 04 2002 10:43 PM (email@example.com): BUGFIX: make sure the
;; eshell is not buffer-read-only. This is manifested by desktop.el for some
;; - Tue Jan 29 2002 03:33 AM (firstname.lastname@example.org): Include the ability to
;; startup the eshell when the ECB is started.
;; - Tue Jan 29 2002 03:31 AM (email@example.com): RFE: make the
;; auto-enlargement on command execution optional, true by default.
;; - Tue Jan 22 2002 11:38 PM (firstname.lastname@example.org): enable customization
;; - Tue Dec 25 2001 07:54 PM (email@example.com): We now enlarge the ecb
;; window when you go to it.
;; - Sat Dec 15 2001 01:50 AM (firstname.lastname@example.org): We are not being much
;; smarter about saving selected windows.
;; - Fri Dec 14 2001 05:57 PM (email@example.com): fixed a bug which caused
;; - Fri Dec 14 2001 04:48 PM (firstname.lastname@example.org): only run eshell/cd if
;; the current directory is different than the eshell/pwd (this is a performance
;; issue and needs to be added in ecb-eshell-current-buffer-sync
;; - we can't do this. eshell/pwd does't return a string. Instead we should
;; change to the eshell-buffer and see what the directory default-directory
;; is there.
;; - Fri Dec 14 2001 03:24 PM (email@example.com): use
;; eshell-pre-command-hook to increase the size of the window if we are in an
;; ECB layout (and the ecb is activated)...
;; - Sun Nov 18 2001 07:20 PM (firstname.lastname@example.org): putting the cursor one
;; line from the bottom of the window.
;; Synching the current buffer with the eshell is done two ways. If the buffer
;; is visible in a window, we always resynch. If it is not visible then
;; ecb-eshell-goto-eshell will synch up when the user goes to the eshell buffer.
(defgroup ecb-eshell nil
"Settings for eshell integration within the ECB."
(defcustom ecb-eshell-enlarge-when-selecting t
"*Enlarge the compile-window if it displays the eshell and
if it is selected by `ecb-eshell-goto-eshell'."
(defcustom ecb-eshell-auto-activate nil
"*Startup the eshell when the ECB is activated and display it in the
compile-window. If current layout does not display a compile-window \(see
`ecb-compile-window-height') then nothing is done."
(defcustom ecb-eshell-synchronize t
"*Synchronize the eshell with the default-directory of current
source-buffer. The synchronization is done by `ecb-eshell-current-buffer-sync'
which can be called interactively but normally it is called autom. by the
(defvar ecb-eshell-pre-command-point nil
"Point in the buffer we are at before we executed a command.")
(defvar ecb-eshell-pre-window-enlarged nil
"True if we enlarged the window before we executed a command.")
(defun ecb-eshell-start ()
"Create an interactive Eshell buffer.
This function is used instead of the original `eshell' cause of a mysterious
behavior of XEmacs which always starts eshell in the edit-window even if
called from the compile-window. This functions ensures that eshell is started
in that window where this function is called from!"
(display-buffer (get-buffer-create eshell-buffer-name)))
"Synchronize the eshell with the current buffer. This is only done if the
eshell is currently visible and if either this function is called
interactively or `ecb-eshell-synchronize' is not nil."
(when (and (or ecb-eshell-synchronize (interactive-p))
;;only do this if the user is looking at the eshell buffer; for this we
;;have the macro `ecb-do-if-buffer-visible-in-ecb-frame'.
;; here we can be sure that the eshell is visible in a window of
;; This macro locally binds the variables visible-buffer and
;; visible-window. See documentation.
(let ((source-buffer-directory nil)
;; we synchronize only if the eshell is displayed in the
;; compile-window otherwise the following ecb-eshell-cleanse would
;; prevent from inserting any command if the eshell is displayed in
;; the edit-window (e.g. by calling `eshell' in the edit-window)
(when (equal visible-window ecb-compile-window)
;;make sure we are clean.
;;get copies of the current source directory.
(setq source-buffer-directory default-directory)
(setq buffer-read-only nil)
(setq ecb-buffer-directory default-directory))
;; at this point source-buffer-directory is a snapshot of the
;; source buffer window and default directory is the directory
;; in the eshell window
(when (not (string-equal (ecb-fix-filename source-buffer-directory)
;;change the directory without showing the cd command
;;execute the command
;; we need to make sure that that the eshell buffer isn't at the
;; top of the buffer history list just because we implicity
;; changed its directory and switched to it. It might not be a
;; good idea in the long term to put it all the way at the end of
;; the history list but it is better than leaving it at the top.
(defmacro ecb-eshell-save-buffer-history (&rest body)
"Protect the buffer-list so that the eshell buffer name is not placed early
in the buffer list or at all if it currently doesn't exist."
(let ((eshell-buffer-list (make-symbol "my-buffer-list")))
`(let ((,eshell-buffer-list (ecb-frame-parameter (selected-frame)
(modify-frame-parameters nil (list (cons 'buffer-list
(defun ecb-eshell-recenter(&optional display-errors)
"Recenter the eshell window so that the prompt is at the end of the buffer."
(interactive (list t))
(let ((window (and (ecb-eshell-running-p)
(if (and window
(equal window ecb-compile-window))
(ecb-error "Eshell not running or compile-window not live!")))))
"Return true if eshell is currently running."
(and (boundp 'eshell-buffer-name)
"Go to the eshell buffer in the compile-window. If eshell is not alive then
;; This is idempotent!
;;always recenter because if the point is at the top of the eshell buffer
;;and we switch to it the user is not going to be able to type a command
;;sync to the current buffer
"Startup the eshell in the compile window. If no compile-window is visible
then an error is reported!"
(when (ecb-compile-window-live-p 'display-msg)
(if (not (ecb-eshell-running-p))
(ecb-error "Can not start eshell. Please check eshell installation!")))))
(when (and (ecb-compile-window-live-p 'display-msg)
(add-hook 'ecb-current-buffer-sync-hook 'ecb-eshell-current-buffer-sync)
(add-hook 'eshell-post-command-hook 'ecb-eshell-recenter)
(add-hook 'eshell-post-command-hook 'ecb-eshell-shrink-if-necessary)
(add-hook 'eshell-pre-command-hook 'ecb-eshell-enlarge)
(add-hook 'window-size-change-functions 'ecb-eshell-window-size-change)
(cons eshell-buffer-name nil))))
(defun ecb-eshell-deactivate ()
;;TODO: Klaus Berndl <email@example.com>: Should we also try to exit the
;;eshell itself? IMHO we should not...
(remove-hook 'ecb-current-buffer-sync-hook 'ecb-eshell-current-buffer-sync)
(remove-hook 'eshell-post-command-hook 'ecb-eshell-recenter)
(remove-hook 'eshell-post-command-hook 'ecb-eshell-shrink-if-necessary)
(remove-hook 'eshell-pre-command-hook 'ecb-eshell-enlarge)
(remove-hook 'window-size-change-functions 'ecb-eshell-window-size-change))
"Enlarge the eshell so more information is visible. This is usually done so
that the eshell has more screen space after we execute a command. "
;;use the eshell-pre-command-hook to see the point and then only enlarge if
;;we enlarge past the maximum amount of lines we can use.
(setq ecb-eshell-pre-command-point (point))
(let((window (get-buffer-window eshell-buffer-name)))
(when (and window
(equal (selected-window) ecb-compile-window)
;;is there a better way to do this? It seems that there should be a
;;way to have emacs split or expand a window by 50% like it is done in
;;a lot of other places (display-buffer, etc)
;;determine if we should actually go ahead and do this.
(when (< (window-height ecb-compile-window)
(/ (frame-height) 2))
(setq ecb-eshell-pre-window-enlarged t)
"If we have expanded the compile buffer after a command, but there was no need
to because the command didn't output much text, go ahead and shrink it again.
Note that this function needs to be standalone as a hook so we have to make sure
we execute it with the eshell buffer."
(when (and (ecb-eshell-running-p)
(equal ecb-compile-window visible-window))
;; only shrink up if we expanded... we don't want to shrink if we just
;; happend to be runnning in large mode
(when (and ecb-eshell-pre-command-point ecb-eshell-pre-window-enlarged
(< (count-lines ecb-eshell-pre-command-point
(setq ecb-eshell-pre-command-point nil)
(setq ecb-eshell-pre-window-enlarged nil))))
"If the user has entered text in the eshell, we need to clean it. If we
don't do this we could end up executing a strange command resulting in a
'command not found'."
;; go to the buffer-end without clobbering the mark!!
(delete-region (point) (point-at-eol)))))
"Activate the eshell when ECB is activated. See `ecb-eshell-auto-activate'."
"Called when we change window sizes so that the eshell can resize."
(when (and ecb-minor-mode
(equal frame ecb-frame))
(add-hook 'ecb-activate-hook 'ecb-eshell-auto-activate-hook)
;;; ecb-eshell.el ends here