em-smart.el 12 KB

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