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