doomemacs/modules/term/eshell/autoload/eshell.el
Itai Y. Efrat 7933e54542 refactor!(:term): toggle commands now always hide
BREAKING CHANGE: previously, <leader> o t commands would only hide the
terminal popup if it was focused. If not, they would move the focus to
the terminal window. This is unintuitive to the "toggle" description,
and arguably less useful, since refocusing to the terminal can be easily
done with regular window refocus commands. Therefore, <leader> o t now
just hides the terminal popup.

Fix #3374
2021-09-23 11:39:36 +02:00

320 lines
11 KiB
EmacsLisp

;;; term/eshell/autoload/eshell.el -*- lexical-binding: t; -*-
(defvar eshell-buffer-name "*doom:eshell*")
(defvar +eshell-buffers (make-ring 25)
"List of open eshell buffers.")
(defvar +eshell--last-buffer nil)
;;
;;; Helpers
(defun +eshell--add-buffer (buf)
(ring-remove+insert+extend +eshell-buffers buf 'grow))
(defun +eshell--remove-buffer (buf)
(when-let (idx (ring-member +eshell-buffers buf))
(ring-remove +eshell-buffers idx)
t))
(defun +eshell--bury-buffer (&optional dedicated-p)
(let ((directory default-directory))
(unless (switch-to-prev-buffer nil 'bury)
(switch-to-buffer (doom-fallback-buffer)))
(when (eq major-mode 'eshell-mode)
(switch-to-buffer (doom-fallback-buffer)))
(when +eshell-enable-new-shell-on-split
(let ((default-directory directory))
(when-let (win (get-buffer-window (+eshell/here)))
(set-window-dedicated-p win dedicated-p))))))
(defun +eshell--setup-window (window &optional flag)
(when (window-live-p window)
(set-window-parameter window 'no-other-window flag)
(set-window-parameter window 'visible flag)))
(defun +eshell--unused-buffer (&optional new-p)
(or (unless new-p
(cl-loop for buf in (+eshell-buffers)
if (and (buffer-live-p buf)
(not (get-buffer-window buf t)))
return buf))
(generate-new-buffer eshell-buffer-name)))
;;;###autoload
(defun +eshell-last-buffer (&optional noerror)
"Return the last opened eshell buffer."
(let ((buffer (cl-find-if #'buffer-live-p (+eshell-buffers))))
(cond (buffer)
(noerror nil)
((user-error "No live eshell buffers remaining")))))
;;;###autoload
(defun +eshell-buffers ()
"TODO"
(ring-elements +eshell-buffers))
;;;###autoload
(defun +eshell-run-command (command &optional buffer)
"TODO"
(let ((buffer
(or buffer
(if (eq major-mode 'eshell-mode)
(current-buffer)
(cl-find-if #'buffer-live-p (+eshell-buffers))))))
(unless buffer
(user-error "No living eshell buffers available"))
(unless (buffer-live-p buffer)
(user-error "Cannot operate on a dead buffer"))
(with-current-buffer buffer
(goto-char eshell-last-output-end)
(goto-char (line-end-position))
(insert command)
(eshell-send-input nil t))))
;;
;;; Commands
;;;###autoload
(defun +eshell/toggle (arg &optional command)
"Toggle eshell popup window."
(interactive "P")
(let ((eshell-buffer
(get-buffer-create
(format "*doom:eshell-popup:%s*"
(if (bound-and-true-p persp-mode)
(safe-persp-name (get-current-persp))
"main"))))
confirm-kill-processes
current-prefix-arg)
(when arg
(when-let (win (get-buffer-window eshell-buffer))
(delete-window win))
(when (buffer-live-p eshell-buffer)
(with-current-buffer eshell-buffer
(fundamental-mode)
(erase-buffer))))
(if-let (win (get-buffer-window eshell-buffer))
(let (confirm-kill-processes)
(delete-window win)
(ignore-errors (kill-buffer eshell-buffer)))
(with-current-buffer (pop-to-buffer eshell-buffer)
(doom-mark-buffer-as-real-h)
(if (eq major-mode 'eshell-mode)
(run-hooks 'eshell-mode-hook)
(eshell-mode))
(when command
(+eshell-run-command command eshell-buffer))))))
;;;###autoload
(defun +eshell/here (&optional command)
"Open eshell in the current buffer."
(interactive "P")
(let ((buf (+eshell--unused-buffer)))
(with-current-buffer (switch-to-buffer buf)
(if (eq major-mode 'eshell-mode)
(run-hooks 'eshell-mode-hook)
(eshell-mode))
(when command
(+eshell-run-command command buf)))
buf))
;;;###autoload
(defun +eshell/frame (&optional command)
"Open a frame dedicated to eshell.
Once the eshell process is killed, the previous frame layout is restored."
(interactive "P")
(let ((buf (+eshell--unused-buffer 'new)))
(unless (frame-parameter nil 'saved-wconf)
(set-frame-parameter nil 'saved-wconf (current-window-configuration)))
(delete-other-windows)
(with-current-buffer (switch-to-buffer buf)
(eshell-mode)
(when command
(+eshell-run-command command buf)))
buf))
;;
;;; Keybinds
;;;###autoload
(defun +eshell/search-history ()
"Search the eshell command history with helm, ivy or `eshell-list-history'."
(interactive)
(cond ((featurep! :completion ivy)
(require 'em-hist)
(let* ((ivy-completion-beg (eshell-bol))
(ivy-completion-end (point-at-eol))
(input (buffer-substring-no-properties
ivy-completion-beg
ivy-completion-end)))
;; Better than `counsel-esh-history' because that doesn't
;; pre-populate the initial input or selection.
(ivy-read "Command: "
(delete-dups
(when (> (ring-size eshell-history-ring) 0)
(ring-elements eshell-history-ring)))
:initial-input input
:action #'ivy-completion-in-region-action)))
((featurep! :completion helm)
(helm-eshell-history))
((eshell-list-history))))
;;;###autoload
(defun +eshell/pcomplete ()
"Use pcomplete with completion-in-region backend instead of popup window at
bottom. This ties pcomplete into ivy or helm, if they are enabled."
(interactive)
(require 'pcomplete)
(if (and (bound-and-true-p company-mode)
(or company-candidates
(and (company-pcomplete-available)
(company-pcomplete--prefix)
(company-pcomplete--candidates))))
(call-interactively #'company-pcomplete)
(ignore-errors (pcomplete-std-complete))))
;;;###autoload
(defun +eshell/quit-or-delete-char (arg)
"Delete a character (ahead of the cursor) or quit eshell if there's nothing to
delete."
(interactive "p")
(if (and (eolp) (looking-back eshell-prompt-regexp nil))
(eshell-life-is-too-much)
(delete-char arg)))
;;;###autoload
(defun +eshell/split-below ()
"Create a new eshell window below the current one."
(interactive)
(let ((ignore-window-parameters t)
(dedicated-p (window-dedicated-p))
(+eshell-enable-new-shell-on-split
(or +eshell-enable-new-shell-on-split (frame-parameter nil 'saved-wconf))))
(select-window (split-window-vertically))
(+eshell--bury-buffer dedicated-p)))
;;;###autoload
(defun +eshell/split-right ()
"Create a new eshell window to the right of the current one."
(interactive)
(let* ((ignore-window-parameters t)
(dedicated-p (window-dedicated-p))
(+eshell-enable-new-shell-on-split
(or +eshell-enable-new-shell-on-split (frame-parameter nil 'saved-wconf))))
(select-window (split-window-horizontally))
(+eshell--bury-buffer dedicated-p)))
;;;###autoload
(defun +eshell/switch-to-next ()
"Switch to the next eshell buffer."
(interactive)
(when (ring-empty-p +eshell-buffers)
(user-error "No eshell buffers are available"))
(switch-to-buffer (ring-next +eshell-buffers (current-buffer))))
;;;###autoload
(defun +eshell/switch-to-previous ()
"Switch to the previous eshell buffer."
(interactive)
(when (ring-empty-p +eshell-buffers)
(user-error "No eshell buffers are available"))
(switch-to-buffer (ring-previous +eshell-buffers (current-buffer))))
;;;###autoload
(defun +eshell/switch-to-last ()
"Switch to the last eshell buffer that was open (and is still alive)."
(interactive)
(unless (buffer-live-p +eshell--last-buffer)
(setq +eshell--last-buffer nil)
(user-error "No last eshell buffer to jump to"))
(switch-to-buffer +eshell--last-buffer))
;;;###autoload
(defun +eshell/switch-to (buffer)
"Interactively switch to another eshell buffer."
(interactive
(let ((buffers (doom-buffers-in-mode
'eshell-mode (delq (current-buffer) (+eshell-buffers)))))
(if (not buffers)
(user-error "No eshell buffers are available")
(list
(completing-read "Eshell buffers"
(mapcar #'buffer-name buffers)
#'get-buffer
'require-match
nil nil
(when (eq major-mode 'eshell-mode)
(buffer-name (current-buffer))))))))
(if-let* ((window (get-buffer-window buffer)))
(select-window window)
(switch-to-buffer buffer)))
;;;###autoload
(defun +eshell/kill-and-close ()
"Kill the current eshell buffer and close its window."
(interactive)
(unless (eq major-mode 'eshell-mode)
(user-error "Not in an eshell buffer"))
(let ((+eshell-kill-window-on-exit t))
(kill-current-buffer)))
;;
;;; Hooks
;;;###autoload
(defun +eshell-init-h ()
"Initialize and track this eshell buffer in `+eshell-buffers'."
(let ((current-buffer (current-buffer)))
(dolist (buf (+eshell-buffers))
(unless (buffer-live-p buf)
(+eshell--remove-buffer buf)))
(+eshell--setup-window (get-buffer-window current-buffer))
(+eshell--add-buffer current-buffer)
(setq +eshell--last-buffer current-buffer)))
;;;###autoload
(defun +eshell-cleanup-h ()
"Close window (or workspace) on quit."
(let ((buf (current-buffer)))
(when (+eshell--remove-buffer buf)
(when-let (win (get-buffer-window buf))
(+eshell--setup-window win nil)
(cond ((and (one-window-p t)
(window-configuration-p (frame-parameter nil 'saved-wconf)))
(set-window-configuration (frame-parameter nil 'saved-wconf))
(set-frame-parameter nil 'saved-wconf nil))
((one-window-p)
(let ((prev (save-window-excursion (previous-buffer))))
(unless (and prev (doom-real-buffer-p prev))
(switch-to-buffer (doom-fallback-buffer)))))
((or (window-dedicated-p win)
+eshell-kill-window-on-exit)
(let ((ignore-window-parameters t)
(popup-p (window-dedicated-p win)))
(delete-window win)
(when popup-p
(cl-loop for win in (window-list)
for buf = (window-buffer win)
for mode = (buffer-local-value 'major-mode buf)
if (eq mode 'eshell-mode)
return (select-window win))))))))))
;;;###autoload
(defun +eshell-switch-workspace-fn (type)
(when (eq type 'frame)
(setq +eshell-buffers
(or (persp-parameter 'eshell-buffers)
(make-ring 25)))))
;;;###autoload
(defun +eshell-save-workspace-fn (_workspace target)
(when (framep target)
(set-persp-parameter 'eshell-buffers +eshell-buffers)))