(timeclock-day-required): If the time required for a particular day is
[bpt/emacs.git] / lisp / eshell / em-smart.el
CommitLineData
affbf647
GM
1;;; em-smart --- smart display of output
2
faadfb0a 3;; Copyright (C) 1999, 2000 Free Software Foundation
affbf647 4
7de5b421
GM
5;; Author: John Wiegley <johnw@gnu.org>
6
affbf647
GM
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
23
24(provide 'em-smart)
25
26(eval-when-compile (require 'esh-maint))
27
28(defgroup eshell-smart nil
29 "This module combines the facility of normal, modern shells with
30some of the edit/review concepts inherent in the design of Plan 9's
319term. See the docs for more details.
32
33Most likely you will have to turn this option on and play around with
34it to get a real sense of how it works."
35 :tag "Smart display of output"
c3bc30c8 36 :link '(info-link "(eshell)Smart display of output")
affbf647
GM
37 :group 'eshell-module)
38
39;;; Commentary:
40
41;; The best way to get a sense of what this code is trying to do is by
42;; using it. Basically, the philosophy represents a blend between the
43;; ease of use of modern day shells, and the review-before-you-proceed
44;; mentality of Plan 9's 9term.
45;;
46;; @ When you invoke a command, it is assumed that you want to read
47;; the output of that command.
48;;
49;; @ If the output is not what you wanted, it is assumed that you will
50;; want to edit, and then resubmit a refined version of that
51;; command.
52;;
53;; @ If the output is valid, pressing any self-inserting character key
54;; will jump to end of the buffer and insert that character, in
55;; order to begin entry of a new command.
56;;
57;; @ If you show an intention to edit the previous command -- by
58;; moving around within it -- then the next self-inserting
59;; characters will insert *there*, instead of at the bottom of the
60;; buffer.
61;;
62;; @ If you show an intention to review old commands, such as M-p or
63;; M-r, point will jump to the bottom of the buffer before invoking
64;; that command.
65;;
66;; @ If none of the above has happened yet (i.e., your point is just
67;; sitting on the previous command), you can use SPACE and BACKSPACE
68;; (or DELETE) to page forward and backward *through the output of
69;; the last command only*. It will constrain the movement of the
70;; point and window so that the maximum amount of output is always
71;; displayed at all times.
72;;
73;; @ While output is being generated from a command, the window will
74;; be constantly reconfigured (until it would otherwise make no
75;; difference) in order to always show you the most output from the
76;; command possible. This happens if you change window sizes,
77;; scroll, etc.
78;;
79;; @ Like I said, it's not really comprehensible until you try it! ;)
dace60cf
JW
80;;
81;; One disadvantage of this module is that it increases Eshell's
82;; memory consumption by a factor of two or more. With small commands
83;; (such as pwd), where the screen is mostly full, consumption can
84;; increase by orders of magnitude.
affbf647
GM
85
86;;; User Variables:
87
88(defcustom eshell-smart-load-hook '(eshell-smart-initialize)
89 "*A list of functions to call when loading `eshell-smart'."
90 :type 'hook
91 :group 'eshell-smart)
92
93(defcustom eshell-smart-unload-hook
94 (list
95 (function
96 (lambda ()
97 (remove-hook 'window-configuration-change-hook
98 'eshell-refresh-windows))))
99 "*A hook that gets run when `eshell-smart' is unloaded."
100 :type 'hook
101 :group 'eshell-smart)
102
103(defcustom eshell-review-quick-commands nil
b4bd214e
JW
104 "*If t, always review commands.
105Reviewing means keeping point on the text of the command that was just
106invoked, to allow corrections to be made easily.
107
108If set to nil, quick commands won't be reviewed. A quick command is a
109command that produces no output, and exits successfully.
110
111If set to `not-even-short-output', then the definition of \"quick
112command\" is extended to include commands that produce output, iff
113that output can be presented in its entirely in the Eshell window."
114 :type '(choice (const :tag "No" nil)
115 (const :tag "Yes" t)
116 (const :tag "Not even short output"
117 not-even-short-output))
affbf647
GM
118 :group 'eshell-smart)
119
120(defcustom eshell-smart-display-navigate-list
121 '(insert-parentheses
122 mouse-yank-at-click
123 mouse-yank-secondary
124 yank-pop
125 yank-rectangle
126 yank)
127 "*A list of commands which cause Eshell to jump to the end of buffer."
128 :type '(repeat function)
129 :group 'eshell-smart)
130
131(defcustom eshell-smart-space-goes-to-end t
132 "*If non-nil, space will go to end of buffer when point-max is visible.
133That is, if a command is running and the user presses SPACE at a time
134when the end of the buffer is visible, point will go to the end of the
135buffer and smart-display will be turned off (that is, subsequently
136pressing backspace will not cause the buffer to scroll down).
137
138This feature is provided to make it very easy to watch the output of a
139long-running command, such as make, where it's more desirable to see
140the output go by than to review it afterward.
141
142Setting this variable to nil means that space and backspace will
143always have a consistent behavior, which is to move back and forth
144through displayed output. But it also means that enabling output
145tracking requires the user to manually move point to the end of the
146buffer using \\[end-of-buffer]."
147 :type 'boolean
148 :group 'eshell-smart)
149
150(defcustom eshell-where-to-jump 'begin
151 "*This variable indicates where point should jump to after a command.
152The options are `begin', `after' or `end'."
153 :type '(radio (const :tag "Beginning of command" begin)
154 (const :tag "After command word" after)
155 (const :tag "End of command" end))
156 :group 'eshell-smart)
157
158;;; Internal Variables:
159
160(defvar eshell-smart-displayed nil)
161(defvar eshell-smart-command-done nil)
dace60cf 162(defvar eshell-currently-handling-window nil)
affbf647
GM
163
164;;; Functions:
165
166(defun eshell-smart-initialize ()
167 "Setup Eshell smart display."
168 (unless eshell-non-interactive-p
169 ;; override a few variables, since they would interfere with the
170 ;; smart display functionality.
171 (set (make-local-variable 'eshell-scroll-to-bottom-on-output) nil)
172 (set (make-local-variable 'eshell-scroll-to-bottom-on-input) nil)
173 (set (make-local-variable 'eshell-scroll-show-maximum-output) t)
174
175 (make-local-hook 'window-scroll-functions)
176 (add-hook 'window-scroll-functions 'eshell-smart-scroll-window nil t)
177 (add-hook 'window-configuration-change-hook 'eshell-refresh-windows)
178
179 (make-local-hook 'eshell-output-filter-functions)
180 (add-hook 'eshell-output-filter-functions 'eshell-refresh-windows t t)
181
182 (make-local-hook 'pre-command-hook)
183 (make-local-hook 'after-change-functions)
dace60cf 184 (add-hook 'after-change-functions 'eshell-disable-after-change nil t)
affbf647
GM
185
186 (make-local-hook 'eshell-input-filter-functions)
dace60cf 187 (add-hook 'eshell-input-filter-functions 'eshell-smart-display-setup nil t)
affbf647
GM
188
189 (make-local-variable 'eshell-smart-command-done)
190 (make-local-hook 'eshell-post-command-hook)
dace60cf
JW
191 (add-hook 'eshell-post-command-hook
192 (function
193 (lambda ()
194 (setq eshell-smart-command-done t))) t t)
affbf647 195
b4bd214e 196 (unless (eq eshell-review-quick-commands t)
affbf647
GM
197 (add-hook 'eshell-post-command-hook
198 'eshell-smart-maybe-jump-to-end nil t))))
199
200(defun eshell-smart-scroll-window (wind start)
201 "Scroll the given Eshell window accordingly."
202 (unless eshell-currently-handling-window
203 (let ((inhibit-point-motion-hooks t)
204 (eshell-currently-handling-window t))
dace60cf
JW
205 (save-selected-window
206 (select-window wind)
207 (eshell-smart-redisplay)))))
affbf647
GM
208
209(defun eshell-refresh-windows (&optional frame)
210 "Refresh all visible Eshell buffers."
211 (let (affected)
212 (walk-windows
213 (function
214 (lambda (wind)
215 (with-current-buffer (window-buffer wind)
dace60cf
JW
216 (if eshell-mode
217 (let (window-scroll-functions)
218 (eshell-smart-scroll-window wind (window-start))
219 (setq affected t))))))
affbf647
GM
220 0 frame)
221 (if affected
222 (let (window-scroll-functions)
223 (eshell-redisplay)))))
224
225(defun eshell-smart-display-setup ()
226 "Set the point to somewhere in the beginning of the last command."
227 (cond
228 ((eq eshell-where-to-jump 'begin)
229 (goto-char eshell-last-input-start))
230 ((eq eshell-where-to-jump 'after)
231 (goto-char (next-single-property-change
232 eshell-last-input-start 'arg-end))
233 (if (= (point) (- eshell-last-input-end 2))
234 (forward-char)))
235 ((eq eshell-where-to-jump 'end)
236 (goto-char (1- eshell-last-input-end)))
237 (t
238 (error "Invalid value for `eshell-where-to-jump'")))
239 (setq eshell-smart-command-done nil)
240 (add-hook 'pre-command-hook 'eshell-smart-display-move nil t)
241 (eshell-refresh-windows))
242
243(defun eshell-disable-after-change (b e l)
244 "Disable smart display mode if the buffer changes in any way."
245 (when eshell-smart-command-done
246 (remove-hook 'pre-command-hook 'eshell-smart-display-move t)
247 (setq eshell-smart-command-done nil)))
248
249(defun eshell-smart-maybe-jump-to-end ()
250 "Jump to the end of the input buffer.
b4bd214e
JW
251This is done whenever a command exits sucessfully and both the command
252and the end of the buffer are still visible."
affbf647 253 (when (and (= eshell-last-command-status 0)
b4bd214e
JW
254 (if (eq eshell-review-quick-commands 'not-even-short-output)
255 (and (pos-visible-in-window-p (point-max))
256 (pos-visible-in-window-p eshell-last-input-start))
257 (= (count-lines eshell-last-input-end
258 eshell-last-output-end) 0)))
affbf647
GM
259 (goto-char (point-max))
260 (remove-hook 'pre-command-hook 'eshell-smart-display-move t)))
261
262(defun eshell-smart-redisplay ()
263 "Display as much output as possible, smartly."
264 (if (eobp)
265 (recenter -1)
ca7aae91
JW
266 (let ((top-point (point)))
267 (and (memq 'eshell-smart-display-move pre-command-hook)
268 (>= (point) eshell-last-input-start)
269 (< (point) eshell-last-input-end)
270 (set-window-start (selected-window)
271 (line-beginning-position) t))
272 (if (pos-visible-in-window-p (point-max))
273 (save-excursion
274 (goto-char (point-max))
275 (recenter -1)
276 (unless (pos-visible-in-window-p top-point)
277 (goto-char top-point)
278 (set-window-start (selected-window)
279 (line-beginning-position) t)))))))
affbf647
GM
280
281(defun eshell-smart-goto-end ()
282 "Like `end-of-buffer', but do not push a mark."
283 (interactive)
284 (goto-char (point-max)))
285
286(defun eshell-smart-display-move ()
287 "Handle self-inserting or movement commands intelligently."
288 (let (clear)
289 (if (or current-prefix-arg
290 (and (> (point) eshell-last-input-start)
291 (< (point) eshell-last-input-end))
292 (>= (point) eshell-last-output-end))
293 (setq clear t)
294 (cond
295 ((eq this-command 'self-insert-command)
296 (if (eq last-command-char ? )
297 (if (and eshell-smart-space-goes-to-end
298 eshell-current-command)
299 (if (not (pos-visible-in-window-p (point-max)))
300 (setq this-command 'scroll-up)
301 (setq this-command 'eshell-smart-goto-end))
302 (setq this-command 'scroll-up))
303 (setq clear t)
304 (goto-char (point-max))))
305 ((eq this-command 'delete-backward-char)
306 (setq this-command 'ignore)
307 (if (< (point) eshell-last-input-start)
308 (eshell-show-output)
309 (if (pos-visible-in-window-p eshell-last-input-start)
310 (progn
311 (ignore-errors
312 (scroll-down))
313 (eshell-show-output))
314 (scroll-down)
315 (if (pos-visible-in-window-p eshell-last-input-end)
316 (eshell-show-output)))))
317 ((or (memq this-command eshell-smart-display-navigate-list)
318 (and (eq this-command 'eshell-send-input)
319 (not (and (>= (point) eshell-last-input-start)
320 (< (point) eshell-last-input-end)))))
321 (setq clear t)
322 (goto-char (point-max)))))
323 (if clear
324 (remove-hook 'pre-command-hook 'eshell-smart-display-move t))))
325
326;;; Code:
327
328;;; em-smart.el ends here