diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2023-01-08 10:02:32 -0700 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2023-01-08 11:48:19 -0700 |
commit | e8c5d47e66bf2be77004ba3a56af8b6fabc9dd8f (patch) | |
tree | 414654c360a889c861368771f01bfdc88fd15a0d | |
parent | 623bbd5062ef362dd2a9bfb860d4705430a1e312 (diff) | |
download | dotfiles-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.el | 251 |
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 ") |