unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
blob 7763396a7ddc246dd5c0bf8891a6f1b758c107ab 11408 bytes (raw)
name: lisp/eshell/em-smart.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
 
;;; em-smart.el --- smart display of output  -*- lexical-binding:t -*-

;; Copyright (C) 1999-2022 Free Software Foundation, Inc.

;; Author: John Wiegley <johnw@gnu.org>

;; This file is part of GNU Emacs.

;; GNU Emacs 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 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs 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.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; The best way to get a sense of what this code is trying to do is by
;; using it.  Basically, the philosophy represents a blend between the
;; ease of use of modern day shells, and the review-before-you-proceed
;; mentality of Plan 9's 9term.
;;
;; @ When you invoke a command, it is assumed that you want to read
;;   the output of that command.
;;
;; @ If the output is not what you wanted, it is assumed that you will
;;   want to edit, and then resubmit a refined version of that
;;   command.
;;
;; @ If the output is valid, pressing any self-inserting character key
;;   will jump to end of the buffer and insert that character, in
;;   order to begin entry of a new command.
;;
;; @ If you show an intention to edit the previous command -- by
;;   moving around within it -- then the next self-inserting
;;   characters will insert *there*, instead of at the bottom of the
;;   buffer.
;;
;; @ If you show an intention to review old commands, such as M-p or
;;   M-r, point will jump to the bottom of the buffer before invoking
;;   that command.
;;
;; @ If none of the above has happened yet (i.e., your point is just
;;   sitting on the previous command), you can use SPACE and BACKSPACE
;;   (or DELETE) to page forward and backward *through the output of
;;   the last command only*.  It will constrain the movement of the
;;   point and window so that the maximum amount of output is always
;;   displayed at all times.
;;
;; @ While output is being generated from a command, the window will
;;   be constantly reconfigured (until it would otherwise make no
;;   difference) in order to always show you the most output from the
;;   command possible.  This happens if you change window sizes,
;;   scroll, etc.
;;
;; @ Like I said, it's not really comprehensible until you try it! ;)
;;
;; One disadvantage of this module is that it increases Eshell's
;; memory consumption by a factor of two or more.  With small commands
;; (such as pwd), where the screen is mostly full, consumption can
;; increase by orders of magnitude.

;;; Code:

(require 'esh-mode)
(eval-when-compile (require 'eshell))

;;;###autoload
(progn
(defgroup eshell-smart nil
  "This module combines the facility of normal, modern shells with
some of the edit/review concepts inherent in the design of Plan 9's
9term.  See the docs for more details.

Most likely you will have to turn this option on and play around with
it to get a real sense of how it works."
  :tag "Smart display of output"
  ;; :link '(info-link "(eshell)Smart display of output")
  :group 'eshell-module))

;;; User Variables:

(defcustom eshell-smart-load-hook nil
  "A list of functions to call when loading `eshell-smart'."
  :version "24.1"			; removed eshell-smart-initialize
  :type 'hook
  :group 'eshell-smart)

(defcustom eshell-smart-unload-hook
  (list
   (lambda ()
     (remove-hook 'window-configuration-change-hook
                  'eshell-smart-scroll-windows)))
  "A hook that gets run when `eshell-smart' is unloaded."
  :type 'hook
  :group 'eshell-smart)

(defcustom eshell-review-quick-commands nil
  "If t, always review commands.
Reviewing means keeping point on the text of the command that was just
invoked, to allow corrections to be made easily.

If set to nil, quick commands won't be reviewed.  A quick command is a
command that produces no output, and exits successfully.

If set to `not-even-short-output', then the definition of \"quick
command\" is extended to include commands that produce output, if and
only if that output can be presented in its entirely in the Eshell window."
  :type '(choice (const :tag "No" nil)
		 (const :tag "Yes" t)
		 (const :tag "Not even short output"
			not-even-short-output))
  :group 'eshell-smart)

(defcustom eshell-smart-display-navigate-list
  '(insert-parentheses
    mouse-yank-at-click
    mouse-yank-primary
    mouse-yank-secondary
    yank-pop
    yank-rectangle
    yank)
  "A list of commands which cause Eshell to jump to the end of buffer."
  :type '(repeat function)
  :group 'eshell-smart)

(defcustom eshell-smart-space-goes-to-end t
  "If non-nil, space will go to end of buffer when `point-max' is visible.
That is, if a command is running and the user presses SPACE at a time
when the end of the buffer is visible, point will go to the end of the
buffer and smart-display will be turned off (that is, subsequently
pressing backspace will not cause the buffer to scroll down).

This feature is provided to make it very easy to watch the output of a
long-running command, such as make, where it's more desirable to see
the output go by than to review it afterward.

Setting this variable to nil means that space and backspace will
always have a consistent behavior, which is to move back and forth
through displayed output.  But it also means that enabling output
tracking requires the user to manually move point to the end of the
buffer using \\[end-of-buffer]."
  :type 'boolean
  :group 'eshell-smart)

(defcustom eshell-where-to-jump 'begin
  "This variable indicates where point should jump to after a command.
The options are `begin', `after' or `end'."
  :type '(radio (const :tag "Beginning of command" begin)
		(const :tag "After command word" after)
		(const :tag "End of command" end))
  :group 'eshell-smart)

;;; Internal Variables:

(defvar eshell-smart-command-done nil)

;;; Functions:

(defun eshell-smart-initialize ()   ;Called from `eshell-mode' via intern-soft!
  "Setup Eshell smart display."
  (unless eshell-non-interactive-p
    ;; override a few variables, since they would interfere with the
    ;; smart display functionality.
    (setq-local eshell-scroll-to-bottom-on-output nil)
    (setq-local eshell-scroll-to-bottom-on-input nil)
    (setq-local eshell-scroll-show-maximum-output t)

    (add-hook 'eshell-output-filter-functions 'eshell-smart-scroll-windows nil t)

    (add-hook 'window-configuration-change-hook 'eshell-smart-scroll-windows nil t)

    (add-hook 'after-change-functions 'eshell-disable-after-change nil t)

    (add-hook 'eshell-input-filter-functions 'eshell-smart-display-setup nil t)

    (make-local-variable 'eshell-smart-command-done)
    (add-hook 'eshell-post-command-hook
              (lambda ()
                (setq eshell-smart-command-done t))
              t t)

    (unless (eq eshell-review-quick-commands t)
      (add-hook 'eshell-post-command-hook
		'eshell-smart-maybe-jump-to-end nil t))))

(defun eshell-smart-scroll-windows ()
  "Scroll all eshell windows to display as much output as possible, smartly."
  (walk-windows
   (lambda (wind)
     (with-current-buffer (window-buffer wind)
       (if eshell-mode
           (eshell-smart-scroll wind))))
   0 t))

(defun eshell-smart-display-setup ()
  "Set the point to somewhere in the beginning of the last command."
  (cond
   ((eq eshell-where-to-jump 'begin)
    (goto-char eshell-last-input-start))
   ((eq eshell-where-to-jump 'after)
    (goto-char (next-single-property-change
		eshell-last-input-start 'arg-end))
    (if (= (point) (- eshell-last-input-end 2))
	(forward-char)))
   ((eq eshell-where-to-jump 'end)
    (goto-char (1- eshell-last-input-end)))
   (t
    (error "Invalid value for `eshell-where-to-jump'")))
  (setq eshell-smart-command-done nil)
  (add-hook 'pre-command-hook 'eshell-smart-display-move nil t))

;; Called from after-change-functions with 3 arguments.
(defun eshell-disable-after-change (_b _e _l)
  "Disable smart display mode if the buffer changes in any way."
  (when eshell-smart-command-done
    (remove-hook 'pre-command-hook 'eshell-smart-display-move t)
    (setq eshell-smart-command-done nil)))

(defun eshell-smart-maybe-jump-to-end ()
  "Jump to the end of the input buffer.
This is done whenever a command exits successfully and both the command
and the end of the buffer are still visible."
  (when (and (= eshell-last-command-status 0)
	     (if (eq eshell-review-quick-commands 'not-even-short-output)
		 (and (pos-visible-in-window-p (point-max))
		      (pos-visible-in-window-p eshell-last-input-start))
	       (= (count-lines eshell-last-input-end
			       eshell-last-output-end) 0)))
    (goto-char (point-max))
    (remove-hook 'pre-command-hook 'eshell-smart-display-move t)))

(defun eshell-smart-scroll (window)
  "Scroll WINDOW to display as much output as possible, smartly."
  (let ((top-point (point)))
    (and (memq 'eshell-smart-display-move pre-command-hook)
         (>= (point) eshell-last-input-start)
         (< (point) eshell-last-input-end)
         (set-window-start window
                           (line-beginning-position) t))
    (if (pos-visible-in-window-p (point-max) window)
        (save-excursion
          (goto-char (point-max))
          (recenter -1)
          (unless (pos-visible-in-window-p top-point window)
            (goto-char top-point)
            (set-window-start window
                              (line-beginning-position) t))))))

(defun eshell-smart-goto-end ()
  "Like `end-of-buffer', but do not push a mark."
  (interactive)
  (goto-char (point-max)))

(defun eshell-smart-display-move ()
  "Handle self-inserting or movement commands intelligently."
  (let (clear)
    (if (or current-prefix-arg
	    (and (> (point) eshell-last-input-start)
		 (< (point) eshell-last-input-end))
	    (>= (point) eshell-last-output-end))
	(setq clear t)
      (cond
       ((eq this-command 'self-insert-command)
	(if (eq last-command-event ? )
	    (if (and eshell-smart-space-goes-to-end
		     eshell-current-command)
		(if (not (pos-visible-in-window-p (point-max)))
		    (setq this-command 'scroll-up)
		  (setq this-command 'eshell-smart-goto-end))
	      (setq this-command 'scroll-up))
	  (setq clear t)
	  (goto-char (point-max))))
       ((eq this-command 'delete-backward-char)
	(setq this-command 'ignore)
	(if (< (point) eshell-last-input-start)
	    (eshell-show-output)
	  (if (pos-visible-in-window-p eshell-last-input-start)
	      (progn
		(ignore-errors
		  (scroll-down))
		(eshell-show-output))
	    (scroll-down)
	    (if (pos-visible-in-window-p eshell-last-input-end)
		(eshell-show-output)))))
       ((or (memq this-command eshell-smart-display-navigate-list)
	    (and (eq this-command 'eshell-send-input)
		 (not (and (>= (point) eshell-last-input-start)
			   (< (point) eshell-last-input-end)))))
	(setq clear t)
	(goto-char (point-max)))))
    (if clear
	(remove-hook 'pre-command-hook 'eshell-smart-display-move t))))

(provide 'em-smart)

;; Local Variables:
;; generated-autoload-file: "esh-groups.el"
;; End:

;;; em-smart.el ends here

debug log:

solving 7763396a7d ...
found 7763396a7d in https://yhetil.org/emacs-bugs/DM5PR03MB3163B313390CF7E1B6286E26C5419@DM5PR03MB3163.namprd03.prod.outlook.com/
found 6768cee4c3 in https://git.savannah.gnu.org/cgit/emacs.git
preparing index
index prepared:
100644 6768cee4c342c958d67adefa293fcacd2c9a1dfb	lisp/eshell/em-smart.el

applying [1/1] https://yhetil.org/emacs-bugs/DM5PR03MB3163B313390CF7E1B6286E26C5419@DM5PR03MB3163.namprd03.prod.outlook.com/
diff --git a/lisp/eshell/em-smart.el b/lisp/eshell/em-smart.el
index 6768cee4c3..7763396a7d 100644

Checking patch lisp/eshell/em-smart.el...
Applied patch lisp/eshell/em-smart.el cleanly.

index at:
100644 7763396a7ddc246dd5c0bf8891a6f1b758c107ab	lisp/eshell/em-smart.el

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).