395 lines
17 KiB
EmacsLisp
395 lines
17 KiB
EmacsLisp
;;; ecb-eshell.el --- eshell integration for the ECB.
|
|
|
|
;; Copyright (C) 2000 - 2005 Jesper Nordenberg,
|
|
;; Klaus Berndl,
|
|
;; Kevin A. Burton,
|
|
;; Free Software Foundation, Inc.
|
|
|
|
;; Author: Klaus Berndl <klaus.berndl@sdm.de>
|
|
;; Kevin A. Burton <burton@openprivacy.org>
|
|
;; Maintainer: Klaus Berndl <klaus.berndl@sdm.de>
|
|
;; Keywords: browser, code, programming, tools
|
|
;; Created: 2001
|
|
|
|
;; 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, or (at your option) 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
|
|
;; details.
|
|
|
|
;; You should have received a copy of the GNU General Public License along with
|
|
;; GNU Emacs; see the file COPYING. If not, write to the Free Software
|
|
;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
;; $Id$
|
|
|
|
;;; Commentary:
|
|
|
|
;; This package provides eshell integration for the ECB. This basically allows
|
|
;; you to jump to the eshell in the compilation window, sync up the current
|
|
;; eshell with the current ECB buffer and run commands without getting in the
|
|
;; way.
|
|
;;
|
|
;; 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.
|
|
;;
|
|
;; - Synchronizes the current directory of the eshell with the current buffer
|
|
;; of the either the edit-window or the ecb-windows.
|
|
;;
|
|
;; - 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
|
|
;; hidden.
|
|
;;
|
|
;; The goal is to make it easy to jump to a command prompt to run OS level
|
|
;; commands.
|
|
;;
|
|
;; If you enjoy this software, please consider a donation to the EFF
|
|
;; (http://www.eff.org)
|
|
|
|
;;; History:
|
|
|
|
;; For the ChangeLog of this file see the CVS-repository. For a complete
|
|
;; history of the ECB-package see the file NEWS.
|
|
|
|
;;; Design:
|
|
;;
|
|
;; Syncing the current buffer with the eshell is done two ways. If the buffer
|
|
;; is visible in a window, we always resync. If it is not visible then
|
|
;; ecb-eshell-goto-eshell will sync up when the user goes to the eshell
|
|
;; buffer.
|
|
;;
|
|
;; Integrating of eshell is mostly done by advicing the command `eshell' which
|
|
;; uses the mechanism of the display-buffer (adviced version).
|
|
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile
|
|
(require 'silentcomp))
|
|
|
|
(require 'ecb-util)
|
|
(require 'ecb-compilation)
|
|
(require 'ecb-common-browser)
|
|
|
|
(silentcomp-defvar eshell-buffer-name)
|
|
(silentcomp-defun eshell)
|
|
(silentcomp-defun eshell/cd)
|
|
(silentcomp-defun eshell-send-input)
|
|
(silentcomp-defun eshell-bol)
|
|
|
|
(defgroup ecb-eshell nil
|
|
"Settings for eshell integration within the ECB."
|
|
:group 'ecb
|
|
:prefix "ecb-eshell-")
|
|
|
|
(defcustom ecb-eshell-enlarge-when-eshell t
|
|
"*Enlarge the compile-window if it is selected by `eshell'.
|
|
This takes only effect if the command `eshell' is called!"
|
|
:group 'ecb-eshell
|
|
:type 'boolean)
|
|
|
|
(defcustom ecb-eshell-fit-window-to-command-output t
|
|
"*Fit the compile-window after an eshell-command to the output.
|
|
This is done by the function `ecb-eshell-fit-window-to-output' which is added
|
|
to `eshell-post-command-hook' ie. which is running autom. after each
|
|
eshell-command."
|
|
:group 'ecb-eshell
|
|
:type 'boolean)
|
|
|
|
(defcustom ecb-eshell-auto-activate nil
|
|
"*Startup the eshell 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."
|
|
:group 'ecb-eshell
|
|
:type 'boolean)
|
|
|
|
;; TODO: Klaus Berndl <klaus.berndl@sdm.de>: was ecb-eshell-synchronize -->
|
|
;; rename in texi and also to ecb-upgrade (also with value-upgrade!)
|
|
(defcustom ecb-eshell-buffer-sync 'basic
|
|
"*Synchronize eshell with the default-directory of current source-buffer.
|
|
|
|
This option takes only effect if a permanant compile-window is used in the
|
|
current layout.
|
|
|
|
If 'always then the synchronization takes place always a buffer
|
|
changes in the edit window and if after this the
|
|
default-directory of the new edit-buffer is different from the
|
|
default-directory of the current eshell-buffer. If value is nil
|
|
then never a synchronization will take place. If a list of
|
|
major-modes then only if the `major-mode' of the new buffer
|
|
belongs NOT to this list.
|
|
|
|
If the special value 'basic is set then ECB uses the setting of the option
|
|
`ecb-basic-buffer-sync'."
|
|
:group 'ecb-eshell
|
|
:type '(radio :tag "Synchronize the eshell if in compile-window."
|
|
(const :tag "Use basic value" :value basic)
|
|
(const :tag "Always" :value always)
|
|
(const :tag "Never" nil)
|
|
(repeat :tag "Not with these modes"
|
|
(symbol :tag "mode"))))
|
|
|
|
|
|
(defcustom ecb-eshell-buffer-sync-delay 'basic
|
|
"*Time Emacs must be idle before the eshell-buffer of ECB is synchronized.
|
|
Synchronizing is done with the current source displayed in the edit window. If
|
|
nil then there is no delay, means synchronization takes place immediately. A
|
|
small value of about 0.25 seconds saves CPU resources and you get even though
|
|
almost the same effect as if you set no delay.
|
|
|
|
If the special value 'basic is set then ECB uses the setting of the option
|
|
`ecb-basic-buffer-sync-delay'."
|
|
:group 'ecb-eshell
|
|
:type '(radio (const :tag "Use basic value" :value basic)
|
|
(const :tag "No synchronizing delay" :value nil)
|
|
(number :tag "Idle time before synchronizing" :value 2))
|
|
:set (function (lambda (symbol value)
|
|
(set symbol value)
|
|
(if (and (boundp 'ecb-minor-mode)
|
|
ecb-minor-mode)
|
|
(ecb-activate-ecb-autocontrol-function
|
|
value 'ecb-analyse-buffer-sync))))
|
|
:initialize 'custom-initialize-default)
|
|
|
|
|
|
|
|
(defvar ecb-eshell-pre-command-point nil
|
|
"Point in the buffer we are at before we executed a command.")
|
|
|
|
(defvar ecb-eshell-buffer-list nil
|
|
"List of eshell-buffers created until now.
|
|
Background: `eshell' creates new eshell-buffers with `generate-new-buffer' if
|
|
called with an prefix arg!")
|
|
|
|
|
|
(defecb-advice-set ecb-eshell-adviced-functions
|
|
"These functions of eshell are adviced if ehsell is active during ECB is
|
|
active.")
|
|
|
|
(defecb-advice eshell around ecb-eshell-adviced-functions
|
|
"Ensure that ehsell is running in the ECB-compile-window if any."
|
|
;; we tell ECB to handle the eshell-buffers as compilation-buffers so they
|
|
;; will be displayed in the compile-window (if any). We must add this as
|
|
;; regexp because ehsell can open new eshell-buffers with a name created by
|
|
;; generate-new-buffer-name! This approach is not completely save because if
|
|
;; a users changes `eshell-buffer-name' during acivated ECB we get not
|
|
;; informed about this and maybe we can handle the new buffer-name of eshell
|
|
;; not as compilation-buffer. But we have no other chance: Adding the return
|
|
;; value of `eshell' in the advice to `ecb-compilation-buffer-names-internal'
|
|
;; does not help because `eshell' uses the new buffer-name already for
|
|
;; `pop-to-buffer'. So an after advice would add the new buffer-name to late
|
|
;; and a before-advice does not know the new-buffer name. The only way would
|
|
;; be to reimplement the whole `eshell'-code in an around advice but this is
|
|
;; not related to the benefit. IMO is it very improbably that a user changes
|
|
;; `eshell-buffer-name' at all...
|
|
(let ((new-elem (cons (concat ".*"
|
|
(regexp-quote eshell-buffer-name)
|
|
".*")
|
|
t)))
|
|
(if ecb-compile-window-height
|
|
(progn
|
|
(add-to-list 'ecb-compilation-buffer-names-internal new-elem)
|
|
(add-to-list 'ecb-compilation-major-modes-internal 'eshell-mode))
|
|
;; if we have no persistent compile-window we do not handle eshell autom.
|
|
;; as compilation-buffer. If the user wants this then he has to modify
|
|
;; `ecb-compilation-buffer-names' and/or `ecb-compilation-major-modes'.
|
|
;; Therefore we remove the new-elem here from the internal lists.
|
|
(setq ecb-compilation-buffer-names-internal
|
|
(delete new-elem ecb-compilation-buffer-names-internal))
|
|
(setq ecb-compilation-major-modes-internal
|
|
(delete 'eshell-mode ecb-compilation-major-modes-internal))))
|
|
|
|
;; maybe we have to auto toggle our compile-window if temporally hidden
|
|
(when (equal 'hidden (ecb-compile-window-state))
|
|
(ecb-layout-debug-error "eshell around-advice: comp-win will be toggled.")
|
|
(ecb-toggle-compile-window 1))
|
|
|
|
(ecb-activate-ecb-autocontrol-function ecb-eshell-buffer-sync-delay
|
|
'ecb-eshell-buffer-sync)
|
|
;; some hooks
|
|
(add-hook 'eshell-post-command-hook 'ecb-eshell-recenter)
|
|
(add-hook 'eshell-post-command-hook 'ecb-eshell-fit-window-to-output)
|
|
(add-hook 'eshell-pre-command-hook 'ecb-eshell-precommand-hook)
|
|
(add-hook 'window-size-change-functions 'ecb-eshell-window-size-change)
|
|
|
|
;; run `eshell' --------------------------------------------
|
|
(ecb-eshell-save-buffer-history
|
|
ad-do-it)
|
|
;; ---------------------------------------------------------
|
|
|
|
;; some post processing
|
|
|
|
;; add the buffer of the buffer used/created by `eshell' to
|
|
;; `ecb-eshell-buffer-list'
|
|
(add-to-list 'ecb-eshell-buffer-list ad-return-value)
|
|
|
|
(when ecb-eshell-enlarge-when-eshell
|
|
(ecb-toggle-compile-window-height 1))
|
|
|
|
;;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
|
|
;;right away.
|
|
(ecb-eshell-recenter)
|
|
|
|
;;sync to the current buffer
|
|
(ecb-eshell-buffer-sync))
|
|
|
|
|
|
|
|
(defun ecb-eshell-activate-integration ()
|
|
"Does all necessary to activate the eshell-integration. But this doesn not
|
|
load or activate eshell - it just prepares ECB to work perfectly with eshell."
|
|
(ecb-enable-advices 'ecb-eshell-adviced-functions))
|
|
|
|
(defun ecb-eshell-deactivate-integration ()
|
|
(ecb-disable-advices 'ecb-eshell-adviced-functions)
|
|
(ecb-stop-autocontrol/sync-function 'ecb-eshell-buffer-sync)
|
|
(remove-hook 'eshell-post-command-hook 'ecb-eshell-recenter)
|
|
(remove-hook 'eshell-post-command-hook 'ecb-eshell-fit-window-to-output)
|
|
(remove-hook 'eshell-pre-command-hook 'ecb-eshell-precommand-hook)
|
|
(remove-hook 'window-size-change-functions 'ecb-eshell-window-size-change))
|
|
|
|
(defecb-autocontrol/sync-function ecb-eshell-buffer-sync nil ecb-eshell-buffer-sync t
|
|
"Synchronize the eshell with the directory of current source-buffer.
|
|
This is only done if the eshell is currently visible in the compile-window of
|
|
ECB and if either this function is called interactively or
|
|
`ecb-eshell-buffer-sync' is not nil."
|
|
(when (and (equal (selected-frame) ecb-frame)
|
|
(ecb-compile-window-live-p)
|
|
(ecb-point-in-edit-window-number))
|
|
(let* ((my-eshell-buffer
|
|
;; nil or a living eshell-buffer in the ecb-compile-window
|
|
(car (member (window-buffer ecb-compile-window)
|
|
ecb-eshell-buffer-list)))
|
|
(my-reference-directory default-directory)
|
|
(my-eshell-directory (and (bufferp my-eshell-buffer)
|
|
(with-current-buffer my-eshell-buffer
|
|
default-directory))))
|
|
(when (and (bufferp my-eshell-buffer)
|
|
(stringp my-reference-directory)
|
|
(stringp my-eshell-directory)
|
|
(not (ecb-string= (ecb-fix-filename my-reference-directory)
|
|
(ecb-fix-filename my-eshell-directory))))
|
|
(ecb-eshell-save-buffer-history
|
|
(with-current-buffer my-eshell-buffer
|
|
;; make sure we have a clean eshell-command-line
|
|
(goto-char (point-max))
|
|
(eshell-bol)
|
|
(delete-region (point) (point-at-eol))
|
|
;;change the directory without showing the cd command
|
|
(eshell/cd my-reference-directory))
|
|
|
|
;;execute the command
|
|
(save-selected-window
|
|
(select-window ecb-compile-window)
|
|
(eshell-send-input)))
|
|
|
|
(ecb-eshell-recenter)
|
|
|
|
;; we need to make sure that that the eshell buffer isn't at the
|
|
;; top of the buffer history list just because we implicitly
|
|
;; 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.
|
|
(bury-buffer eshell-buffer-name)))))
|
|
|
|
(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)
|
|
'buffer-list)))
|
|
(unwind-protect
|
|
(progn
|
|
,@body)
|
|
(modify-frame-parameters nil (list (cons 'buffer-list
|
|
,eshell-buffer-list)))))))
|
|
|
|
(defun ecb-eshell-recenter(&optional display-errors)
|
|
"Recenter the eshell window so that the prompt is at the buffer-end."
|
|
(interactive (list t))
|
|
|
|
(if (and (equal (selected-frame) ecb-frame)
|
|
(ecb-compile-window-live-p)
|
|
;; the buffer in the ecb-compile-window is a living eshell-buffer
|
|
(member (window-buffer ecb-compile-window)
|
|
ecb-eshell-buffer-list))
|
|
(save-selected-window
|
|
(select-window ecb-compile-window)
|
|
(goto-char (point-max))
|
|
(recenter -2))
|
|
(when display-errors
|
|
(ecb-error "Eshell not running or compile-window not visible!"))))
|
|
|
|
(defun ecb-eshell-precommand-hook ()
|
|
;;use the eshell-pre-command-hook to set the point.
|
|
(setq ecb-eshell-pre-command-point (point)))
|
|
|
|
|
|
(defun ecb-eshell-fit-window-to-output()
|
|
"Fit window of eshell to the output of last command. This function is added
|
|
to `eshell-post-command-hook' and only called there. This function tries to
|
|
fit the height of the compile-window best to the last command-output. The
|
|
algorithm fit the window to the height of the last command-output but do not
|
|
enlarge the compile-window over half of the frame-height and also not below
|
|
`ecb-compile-window-height' (in lines)."
|
|
(when (and (equal (selected-frame) ecb-frame)
|
|
(ecb-compile-window-live-p)
|
|
;; the buffer in the ecb-compile-window is a living eshell-buffer
|
|
(member (window-buffer ecb-compile-window)
|
|
ecb-eshell-buffer-list))
|
|
|
|
;; fit the window to the height of the last command-output but do not
|
|
;; enlarge the compile-window over half of the frame-height and also not
|
|
;; below `ecb-compile-window-height' (in lines).
|
|
(when (and ecb-eshell-fit-window-to-command-output
|
|
(integer-or-marker-p ecb-eshell-pre-command-point))
|
|
(let* ((compile-window-height-lines
|
|
(ecb-normalize-number ecb-compile-window-height
|
|
(1- (frame-height))))
|
|
(ecb-enlarged-compilation-window-max-height
|
|
(max (min (with-current-buffer (window-buffer ecb-compile-window)
|
|
;; we want to see the old command line too and 2
|
|
;; must be added because we have a modeline and one
|
|
;; empty line cause of the (recenter -2) in
|
|
;; `ecb-eshell-recenter'. For XEmacs it would be
|
|
;; better to check if a horiz. scrollbar is used.
|
|
;; This causes the one line more we need for XEmacs
|
|
(+ (if ecb-running-xemacs 4 3)
|
|
(count-lines ecb-eshell-pre-command-point
|
|
(point))))
|
|
(/ (1- (frame-height)) 2))
|
|
compile-window-height-lines)))
|
|
(ecb-toggle-compile-window-height 1)
|
|
(ecb-eshell-recenter))
|
|
|
|
;;reset
|
|
(setq ecb-eshell-pre-command-point nil))))
|
|
|
|
|
|
(defun ecb-eshell-auto-activate-hook()
|
|
"Activate the eshell when ECB is activated. See `ecb-eshell-auto-activate'."
|
|
(when ecb-eshell-auto-activate
|
|
(ignore-errors (eshell))))
|
|
|
|
(defun ecb-eshell-window-size-change(frame)
|
|
"Called when we change window sizes so that the eshell can resize."
|
|
(when (and ecb-minor-mode
|
|
(equal frame ecb-frame))
|
|
(ignore-errors (ecb-eshell-recenter))))
|
|
|
|
(add-hook 'ecb-activate-hook 'ecb-eshell-auto-activate-hook)
|
|
|
|
(silentcomp-provide 'ecb-eshell)
|
|
|
|
;;; ecb-eshell.el ends here
|