summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2023-01-08 10:02:32 -0700
committerSean Whitton <spwhitton@spwhitton.name>2023-01-08 11:48:19 -0700
commite8c5d47e66bf2be77004ba3a56af8b6fabc9dd8f (patch)
tree414654c360a889c861368771f01bfdc88fd15a0d
parent623bbd5062ef362dd2a9bfb860d4705430a1e312 (diff)
downloaddotfiles-e8c5d47e66bf2be77004ba3a56af8b6fabc9dd8f.tar.gz
rework spw/eshell-jump again
The main differences are that we ignore whether Eshells are busy, by default, when invoked interactively, and we pay less attention to the current buffer.
-rw-r--r--.emacs.d/init.el251
1 files changed, 129 insertions, 122 deletions
diff --git a/.emacs.d/init.el b/.emacs.d/init.el
index 224372fe..14cadf7c 100644
--- a/.emacs.d/init.el
+++ b/.emacs.d/init.el
@@ -1669,79 +1669,70 @@ the non-side windows deleted by `delete-other-windows' will also reappear."
;;; getting to Eshell buffers
-(defun spw/eshell-jump (&optional arg chdir)
- "Pop to the most recently used Eshell not already running a
-command, and offer transient cycling among other Eshells, unless
-one or more of the following apply:
-
-- If a command is running in all Eshells, start a new one.
- Similarly if all buffers are narrowed; that was probably done
- with C-u C-c C-r, and so such buffers are probably in use.
-
-- If CHDIR, and there is no Eshell in `default-directory' nor any
- Eshell under the current project root, start a new Eshell in
- `default-directory'
-
-- If CHDIR and there is an Eshell in `default-directory', switch
- to that Eshell instead.
-
-- If CHDIR and there is an Eshell under the current project root,
- switch to that Eshell instead, and change its directory to
- `default-directory'.
-
-- If both ARG and CHDIR, or if CHDIR and the current buffer is an
- Eshell buffer, unconditionally start a new Eshell in
- `default-directory'.
-
- (I.e. C-u may be used to override reusing an existing Eshell,
- and separately, if we are already in the buffer that the
- command would have taken us to, assume we want a fresh one.)
-
-- If not CHDIR and the current buffer is an Eshell that's not
- running a command, activate transient cycling to make it easy
- to get back to another Eshell. (This is the only case in which
- we do not use `pop-to-buffer' or equivalent, so C-x 4 4 must be
- used to cycle in another window.)
-
- (This duplicates the functionality of `spw/cycle-forwards-from-here' and
- `spw/cycle-backwards-from-here', so we might do something else that's
- useful and Eshell-specific.)
-
-- If CHDIR is `project', as above except that use the root of the
- current project instead of `default-directory', and select an
- Eshell already in the root of the project even if it's busy.
-
- (The latter exception is to make it easy to use C-x p e to get
- back to long-running builds in project roots for which I'm not
- using C-x p c, such as Debian package builds.)
-
-The ideas behind these behaviours are as follows.
-
-- Just like Lisp REPLs, we do not normally need a lot of
- different Eshells; it is fine for shell history associated with
- different tasks to become mixed together. But we do need to
- start a new Eshell when other Eshells are already busy running
- commands.
-
-- Rename *eshell* to *eshell*<N>, but don't ever rename
- *eshell*<N> back to *eshell*, because that is a conventional
- workflow -- M-&, C-h i, M-x ielm, M-x compile etc. always take
- you to the unnumbered buffer, possibly renaming the numbered one
- out of the way.
-
- We do nevertheless reuse Eshells, not for the sake of creating
- fewer, but just so that this command can be used to get back to
- the most recent few Eshells you were working in, to see output.
-
-- We'll sometimes use C-x 4 1 in front of this command, and if
- we're already in Eshell, we might use C-x 4 4 to start the
- cycling in another window.
-
-- It's not especially convenient to distinguish between
- `project-eshell' and `eshell' Eshells. We just want a way to
- quickly obtain an Eshell in the project root, and bind that to
- C-x p e."
- (interactive "P")
+(defun spw/eshell-jump (&optional chdir busy-okay)
+ "Pop to a recently-used Eshell that isn't busy, or start a fresh one.
+Return a ring for transient cycling among other Eshells, in the order of most
+recent use. An Eshell is busy if there's a command running, or it's narrowed
+(in the latter case, this was probably done with C-u C-c C-r).
+
+Non-nil CHDIR requests an Eshell that's related to `default-directory'.
+Specifically, if CHDIR is non-nil, pop to an Eshell in `default-directory',
+pop to an Eshell under the current project root and change its directory to
+`default-directory', or start a fresh Eshell in `default-directory'.
+If CHDIR is `project', use the current project root as `default-directory'.
+In `dired-mode', unless CHDIR is `strict', use the result of calling
+`dired-current-directory' as `default-directory'.
+
+Non-nil BUSY-OKAY requests ignoring whether Eshells are busy. This makes
+it easy to return to Eshells with long-running commands.
+If BUSY-OKAY is `interactive', as it is interactively, ignore whether Eshells
+are busy unless there is a prefix argument, and unconditionally start a fresh
+Eshell if the prefix argument is 16 or greater (e.g. with C-u C-u).
+If BUSY-OKAY is `fresh', unconditionally start a fresh Eshell, whether or not
+an Eshell that isn't busy already exists.
+Any other non-nil value means to ignore whether Eshells are busy.
+
+If BUSY-OKAY is `interactive', `this-command' is equal to `last-command',
+and there is no prefix argument, set the prefix argument to the numeric
+value of the last prefix argument multiplied by 4, and also bind
+`display-buffer-overriding-action' to use the selected window.
+Thus, M-& M-& is equivalent to M-& C-u M-&, and M-& M-& M-& is equivalent to
+M-& C-u M-& C-u C-u M-&. This streamlines the case where this command takes
+you to a buffer that's busy but you need one that isn't, but note that with
+the current implementation transient cycling is restarted, so the busy buffer
+will become the most recently selected buffer.
+
+Some ideas behind these behaviours are as follows.
+
+- Just like Lisp REPLs, we do not normally need a lot of different Eshells;
+ it is fine for shell history associated with different tasks to become
+ mixed together. But we do require an easy way to start new Eshells when
+ other Eshells are already busy running commands.
+
+- Rename *eshell* to *eshell*<N>, but don't ever rename *eshell*<N> back to
+ *eshell*, because that is a conventional workflow -- upstream M-&, C-h i,
+ M-x ielm, M-x compile etc. always take you to the unnumbered buffer,
+ possibly renaming the numbered one out of the way.
+
+ We do nevertheless reuse Eshells, not for the sake of creating fewer, but
+ just so that this command can be used to get back to the most recent few
+ Eshells you were working in, to see output.
+
+- We'll sometimes use C-x 4 1 in front of this command, and if we're
+ already in an Eshell, we might use C-x 4 4 C-x <left>/<right> to cycle to
+ another Eshell in another window, or a sequence like M-& C-u M-&, which
+ doesn't bind `display-buffer-overriding-action'.
+
+- It's not especially convenient to distinguish between `project-eshell'
+ and `eshell' Eshells. We just want a way to quickly obtain an Eshell in
+ the project root, and bind that to C-x p e.
+
+- Except when `this-command' is equal to `last-command', don't do anything
+ special when the current buffer is the one we'd pop to, as previous
+ versions of this command did. That sort of context-dependent behavioural
+ variation reduces the speed with which one can use the command because
+ you have to think more about what it will do."
+ (interactive '(nil interactive))
(require 'eshell)
(let* ((default-directory (or (and (not (eq chdir 'strict))
(eq major-mode 'dired-mode)
@@ -1749,11 +1740,34 @@ The ideas behind these behaviours are as follows.
default-directory))
(current-project (and (not (file-remote-p default-directory))
(project-current)))
- (project-root (and current-project (project-root current-project)))
+ (proj-root (and current-project (project-root current-project)))
(target-directory (expand-file-name
- (or (and (eq chdir 'project) project-root)
+ (or (and (eq chdir 'project) proj-root)
default-directory)))
- target-directory-eshell same-project-eshell all-eshells)
+ (again (and (not current-prefix-arg) (eq busy-okay 'interactive)
+ (eq this-command last-command)))
+ (display-buffer-overriding-action
+ (if again '(display-buffer-same-window (inhibit-same-window . nil))
+ display-buffer-overriding-action))
+ target-directory-eshells other-eshells
+ most-recent-eshell same-project-eshell target-directory-eshell)
+ ;; It's important that `transient-cycles-cmd-spw/eshell-jump' never sees
+ ;; this prefix argument because it has its own meanings for C-u & C-u C-u.
+ ;; This means that C-u M-! and M-! M-! are different, which is desirable.
+ ;;
+ ;; We could multiply by 16 if `last-prefix-arg' is nil and the current
+ ;; buffer is an Eshell that's not busy. The idea would be that when M-&
+ ;; takes us to a non-busy buffer, a second M-& would only take us to the
+ ;; same buffer, so skip over that step and do C-u C-u M-&.
+ ;; However, this simpler design has the advantage that if I know I want a
+ ;; non-busy Eshell I can just hit M-& M-& without looking and I know I'll
+ ;; get the most recent non-busy Eshell in the right directory.
+ (when again
+ (setq current-prefix-arg (* 4 (prefix-numeric-value last-prefix-arg))))
+ (when (eq busy-okay 'interactive)
+ (setq busy-okay
+ (cond ((>= (prefix-numeric-value current-prefix-arg) 16) 'fresh)
+ ((not current-prefix-arg) t))))
(cl-flet ((busy-p (buffer)
(or (get-buffer-process buffer)
(with-current-buffer buffer (buffer-narrowed-p))))
@@ -1766,45 +1780,38 @@ The ideas behind these behaviours are as follows.
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (eq major-mode 'eshell-mode)
- (push buffer all-eshells)
- (when chdir
- (cond ((and (not target-directory-eshell)
- (or (not (busy-p buffer)) (eq chdir 'project))
- (string= default-directory target-directory))
+ (let ((in-target-p
+ (and chdir (string= default-directory target-directory))))
+ (push buffer
+ (if in-target-p target-directory-eshells other-eshells))
+ (cond ((and (not chdir) (not most-recent-eshell)
+ (or busy-okay (not (busy-p buffer))))
+ (setq most-recent-eshell buffer))
+ ((and in-target-p (not target-directory-eshell)
+ (or busy-okay (not (busy-p buffer))))
(setq target-directory-eshell buffer))
- ((and project-root (not same-project-eshell)
+ ((and chdir proj-root (not same-project-eshell)
+ ;; We'll change its directory so it mustn't be busy.
(not (busy-p buffer))
- (file-in-directory-p default-directory project-root))
+ (file-in-directory-p default-directory proj-root))
(setq same-project-eshell buffer)))))))
- (let ((current-eshell (and (eq major-mode 'eshell-mode)
- (not (busy-p (current-buffer)))
- (current-buffer))))
- (cond ((and chdir (or arg current-eshell))
- (fresh-eshell))
- ((and chdir target-directory-eshell)
- (pop-to-buffer target-directory-eshell))
- ((and chdir same-project-eshell)
- (pop-to-buffer same-project-eshell)
- (goto-char (point-max))
- (spw/eshell-cd target-directory))
- (chdir
- (fresh-eshell))
- ((not current-eshell)
- (if-let ((buf (cl-find-if-not #'busy-p (reverse all-eshells))))
- (pop-to-buffer buf)
- (fresh-eshell)))
- ;; If `display-buffer-overriding-action' has some entries, pop
- ;; to ourselves, to allow subsequent cycling to a different
- ;; Eshell in another window, and similar. E.g. M-! C-x 4 4 M-!.
- ((or (car display-buffer-overriding-action)
- (cdr display-buffer-overriding-action))
- (pop-to-buffer (current-buffer)))))
- (let* ((all (delete (current-buffer) all-eshells))
- (ring (make-ring (1+ (length all)))))
- (dolist (buffer all)
- (ring-insert ring buffer))
- (ring-insert ring (current-buffer))
- ring))))
+ (cond ((eq busy-okay 'fresh) (fresh-eshell))
+ ((and chdir target-directory-eshell)
+ (pop-to-buffer target-directory-eshell))
+ ((and chdir same-project-eshell)
+ (pop-to-buffer same-project-eshell)
+ (goto-char (point-max))
+ (spw/eshell-cd target-directory))
+ (most-recent-eshell ; CHDIR nil
+ (pop-to-buffer most-recent-eshell))
+ (t (fresh-eshell))))
+ (let* ((all (delq (current-buffer)
+ (nconc other-eshells target-directory-eshells)))
+ (ring (make-ring (1+ (length all)))))
+ (dolist (buffer all)
+ (ring-insert ring buffer))
+ (ring-insert ring (current-buffer))
+ ring)))
(spw/reclaim-keys-from dired-x dired-mode-map "\M-!")
(spw/reclaim-keys-from term term-raw-map "\M-!" "\M-&")
@@ -1813,7 +1820,7 @@ The ideas behind these behaviours are as follows.
((("\M-!" . spw/eshell-jump) (arg)
(interactive "p")
(let ((>>> (and (> arg 1) (format " >>>#<buffer %s>" (buffer-name)))))
- (prog1 (spw/eshell-jump nil (> arg 4))
+ (prog1 (spw/eshell-jump (> arg 4) (and (= arg 1) 'interactive))
(when >>>
(let ((there (save-excursion
(goto-char (point-max))
@@ -1828,16 +1835,16 @@ The ideas behind these behaviours are as follows.
(just-one-space)))))))))
;; This could be on C-z C-j, like `dired-jump', w/ corresponding C-z 4 C-j
;; and C-z 5 C-j. But I'd want C-z 4 C-j much more often than C-z C-j.
- (("\M-&" . spw/eshell-jump-from-here) (arg)
- (interactive "P")
- (spw/eshell-jump arg t))))
+ (("\M-&" . spw/eshell-jump-from-here) ()
+ (interactive)
+ (spw/eshell-jump t 'interactive))))
(with-eval-after-load 'project
(when (boundp 'project-prefix-map) ; for Emacs 27
(spw/transient-cycles-define-buffer-switch
- ((("e" . spw/project-eshell) (arg)
- (interactive "P")
- (spw/eshell-jump arg 'project)))
+ ((("e" . spw/project-eshell) ()
+ (interactive)
+ (spw/eshell-jump 'project 'interactive)))
;; Bind into project-prefix-map, rather than adding a remap, so that we
;; have it under C-x 4 p, C-x 5 p etc. too.
:keymap project-prefix-map)))
@@ -2984,11 +2991,11 @@ mutt's review view, after exiting EDITOR."
(prog1 (dired-get-marked-files) (spw/eshell-jump)))
((consp arg)
(prog1 (dired-get-marked-files t)
- (spw/eshell-jump nil 'strict)))
+ (spw/eshell-jump 'strict)))
(t
(prog1 (dired-get-marked-files
'no-dir (and arg (prefix-numeric-value arg)))
- (spw/eshell-jump nil t)))))
+ (spw/eshell-jump t)))))
(string
(mapconcat (lambda (file)
(if (string-match-p "[ \"']" file)
@@ -3826,7 +3833,7 @@ mutt's review view, after exiting EDITOR."
(let ((buffer (current-buffer))
(range (spw/log-view-git-range))
(default-directory (project-root (project-current))))
- (spw/eshell-jump nil t)
+ (spw/eshell-jump t)
(when (> (point-max) eshell-last-output-end)
(eshell-interrupt-process))
(insert "git ")