doomemacs/modules/term/shell/autoload.el
2020-01-01 22:47:59 -05:00

110 lines
4.3 KiB
EmacsLisp

;;; term/shell/autoload.el -*- lexical-binding: t; -*-
(defun +shell-idle-p (buf)
"Return t if the shell in BUF is not running something.
When available, use process hierarchy information via pstree for
local shells. Otherwise, we ask comint if the point is after a
prompt."
(with-current-buffer buf
(let ((comint-says-idle (and
(> (point) 1) ;; if point > 1
;; see if previous char has the prompt face
(equal '(comint-highlight-prompt)
(get-text-property
(- (point) 1) 'font-lock-face)))))
(if (file-remote-p default-directory)
;; for remote shells we have to rely on comint
comint-says-idle
;; for local shells, we can potentially do better using pgrep
(condition-case nil
(case (call-process ;; look at the exit code of pgrep -P <pid>
"pgrep" nil nil nil "-P"
(number-to-string (process-id (get-buffer-process buf))))
(0 nil) ;; child procxesses found, not idle
(1 t) ;; not running any child processes, it's idle
(t comint-says-idle)) ;; anything else, fall back on comint.
(error comint-says-idle)))))) ;; comint fallback if execution failed
(defun +shell-unused-buffer ()
"TODO"
(or (cl-find-if #'+shell-idle-p (doom-buffers-in-mode 'shell-mode))
(generate-new-buffer "*doom:shell*")))
(defun +shell-tramp-hosts ()
"Ask tramp for a list of hosts that we can reach through ssh."
(cl-reduce #'append
(mapcar (lambda (x)
(delq nil (mapcar #'cadr (apply (car x) (cdr x)))))
(tramp-get-completion-function "scp"))))
(defun +shell--sentinel (process _event)
(when (memq (process-status process) '(exit stop))
(kill-buffer (process-buffer process))))
(defun +shell--send-input (buffer input &optional no-newline)
(when input
(with-current-buffer buffer
(unless (number-or-marker-p (cdr comint-last-prompt))
(message "Waiting for shell to start up...")
(while (not (number-or-marker-p (cdr comint-last-prompt)))
(sleep-for 0.1)))
(goto-char (cdr comint-last-prompt))
(delete-region (cdr comint-last-prompt) (point-max))
(insert input)
(comint-send-input no-newline))))
;;;###autoload
(defun +shell/toggle (&optional command)
"Toggle a persistent terminal popup window.
If popup is visible but unselected, selected it.
If popup is focused, kill it."
(interactive)
(let ((buffer
(get-buffer-create
(format "*doom:shell-popup:%s*"
(if (bound-and-true-p persp-mode)
(safe-persp-name (get-current-persp))
"main"))))
(dir default-directory))
(if-let (win (get-buffer-window buffer))
(if (eq (selected-window) win)
(let (confirm-kill-processes)
(set-process-query-on-exit-flag (get-buffer-process buffer) nil)
(delete-window win)
(ignore-errors (kill-buffer buffer)))
(select-window win)
(when (bound-and-true-p evil-local-mode)
(evil-change-to-initial-state))
(goto-char (point-max)))
(with-current-buffer (pop-to-buffer buffer)
(if (not (eq major-mode 'shell-mode))
(shell buffer)
(cd dir)
(run-mode-hooks 'shell-mode-hook))))
(when-let (process (get-buffer-process buffer))
(set-process-sentinel process #'+shell--sentinel)
(+shell--send-input buffer command))))
;;;###autoload
(defun +shell/here (&optional command)
"Open a terminal buffer in the current window.
If already in a shell buffer, clear it and cd into the current directory."
(interactive)
(let ((buffer (+shell-unused-buffer))
(dir default-directory))
(with-current-buffer (switch-to-buffer buffer)
(if (eq major-mode 'shell-mode)
(+shell--send-input buffer (format "cd %S" dir))
(shell buffer))
(let ((process (get-buffer-process buffer)))
(set-process-sentinel process #'+shell--sentinel)
(+shell--send-input buffer command)))
buffer))
;; TODO +shell/frame -- dedicate current frame to shell buffers
;; TODO +shell/frame-quite -- revert frame to before +term/frame