diff --git a/init.example.el b/init.example.el index b4e9df284..ea853ade2 100644 --- a/init.example.el +++ b/init.example.el @@ -64,6 +64,7 @@ :term ;;eshell ; a consistent, cross-platform shell (WIP) + ;;shell ; a terminal REPL for Emacs ;;term ; terminals in Emacs ;;vterm ; another terminals in Emacs diff --git a/modules/term/shell/autoload.el b/modules/term/shell/autoload.el new file mode 100644 index 000000000..180331a60 --- /dev/null +++ b/modules/term/shell/autoload.el @@ -0,0 +1,99 @@ +;;; 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 + "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)))) + + +;;;###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-name + (get-buffer-create + (format "*doom:shell-popup:%s*" + (if (bound-and-true-p persp-mode) + (safe-persp-name (get-current-persp)) + "main"))))) + (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) + (erase-buffer) + (cd dir)) + (let ((process (get-buffer-process (current-buffer)))) + (set-process-sentinel process #'+shell--sentinel) + (when command + (comint-send-string process 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 (not (eq major-mode 'shell-mode)) + (shell buffer) + (erase-buffer) + (cd dir)) + (let ((process (get-buffer-process (current-buffer)))) + (set-process-sentinel process #'+shell--sentinel) + (when command + (comint-send-string process command)))) + buffer)) + + +;; TODO +shell/frame -- dedicate current frame to shell buffers +;; TODO +shell/frame-quite -- revert frame to before +term/frame diff --git a/modules/term/shell/config.el b/modules/term/shell/config.el new file mode 100644 index 000000000..c716701c4 --- /dev/null +++ b/modules/term/shell/config.el @@ -0,0 +1,5 @@ +;;; term/shell/config.el -*- lexical-binding: t; -*- + +;;;###package shell +(add-hook 'shell-mode-hook #'doom|mark-buffer-as-real) +(add-hook 'shell-mode-hook #'hide-mode-line-mode) diff --git a/modules/term/shell/packages.el b/modules/term/shell/packages.el new file mode 100644 index 000000000..94be96f5c --- /dev/null +++ b/modules/term/shell/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; term/shell/packages.el + +(package! shell :built-in t) diff --git a/modules/term/term/autoload.el b/modules/term/term/autoload.el index 4d4ddb491..459f29210 100644 --- a/modules/term/term/autoload.el +++ b/modules/term/term/autoload.el @@ -53,3 +53,7 @@ If prefix ARG, recreate the term buffer." ;; before `switch-to-buffer', the hooks don't trigger, so we use this ;; roundabout way to trigger them properly. (switch-to-buffer (save-window-excursion (multi-term)))) + + +;; TODO +term/frame -- dedicate current frame to term buffers +;; TODO +term/frame-quite -- revert frame to before +term/frame