Just published a short video on Emacs shell interactions, which you can view here.

The following content is the video notes showcased in the video.

  • Emacs offers an enhanced experience for interacting with shell tools & may be the most extensible terminal emulator available.
  • Provides seamless integration between shell tools & editing capabilities.

Using shell commands from within a buffer

shell-command

  • M-! CMD <RET>
  • Run the shell command CMD and display the output.

async-shell-command

  • M-& CMD <RET>
  • Run the shell command CMD asynchronously, and display the output

shell-command-on-region

  • M-| CMD <RET>
  • Run the shell command CMD with region contents as input; optionally replace the region with the output

Emacs as a Terminal Emulator

Shell

  • M-x shell
  • A subshell with input and output through an Emacs buffer. You can then give commands interactively.

My configuration

 (use-package shell
   :defer t
   :config
   (defun thanos/shell (n)
	 "Create or switch to a shell buffer."
	 (interactive "P")
	 (let* ((num (if n (prefix-numeric-value n) nil))
			(buf-name (if num (format "*shell<%d>*" num) "*shell*")))
	   (shell buf-name)))
   :bind (("C-c v" . thanos/shell)
		  :map shell-mode-map
		  ("C-l" . 'comint-clear-buffer))
   :hook ((shell-mode . (lambda () (display-line-numbers-mode -1)))))

Eshell

  • M-x eshell
  • Shell implemented entirely in Emacs Lisp

My configuration

 (use-package esh-mode
   :defer t
   :config
   ;; Prompt
   (defun eshell-git-info ()
	 "Return a string with git info."
	 (when (eq (call-process "git" nil nil nil "rev-parse" "--is-inside-work-tree") 0)
	   (let* ((branch-raw (shell-command-to-string "git rev-parse --abbrev-ref HEAD"))
			  (branch (if (or (string-match-p "^fatal" branch-raw)
							  (string-match-p "^error" branch-raw))
						  "Unknown"
						(string-trim branch-raw)))
			  (dirty (not
					  (string= "" (string-trim (shell-command-to-string "git status --porcelain")))))
			  (dirty-info (if dirty " ✎" " ✔")))
		 (concat (propertize "⎇ " 'face 'modus-themes-fg-green-warmer)
				 (propertize branch 'face 'modus-themes-fg-magenta-warmer)
				 (propertize dirty-info 'face
							 (if dirty 'modus-themes-fg-red 'modus-themes-fg-green))))))

   (defun eshell-prompt-multiline ()
	 "Eshell Multiline Git prompt."
	 (let ((separator (propertize " | " 'face 'font-lock-comment-face))
		   (hr (propertize (concat "\n" (make-string (/ (window-total-width) 2) ?─) "\n") 'face 'font-lock-comment-face))
		   (dir (propertize (format "%s" (abbreviate-file-name (eshell/pwd))) 'face 'modus-themes-fg-yellow-warmer))
		   (git-info (eshell-git-info))
		   (time (propertize (format-time-string "%H:%M:%S") 'face 'font-lock-comment-face))
		   (sign (if (= (user-uid) 0)
					 (propertize "\n#" 'face 'modus-themes-fg-blue-intense)
				   (propertize "\nλ" 'face 'modus-themes-fg-red-warmer))))
	   (concat hr dir separator git-info separator time sign " ")))

   (setf eshell-prompt-function 'eshell-prompt-multiline
		 eshell-highlight-prompt nil)
   ;; Aliases
   (defun eshell/o (file)
	 "Open FILE."
	 (find-file file))

   (defvar thanos/aliases
	 '((ll . "ls -lah")
	   (clear . clear-scrollback)))

   (defun thanos/set-eshell-aliases (aliases)
	 "Set ALIASES as eshell aliases."
	 ;; Remove aliases file
	 (when (and eshell-aliases-file
				(file-exists-p eshell-aliases-file))
	   (delete-file eshell-aliases-file))
	 (mapc (lambda (alias)
			 (let ((name (symbol-name (car alias)))
				   (command (cdr alias)))
			   (eshell/alias name
							 (cond
							  ((stringp command) command)
							  ((symbolp command) (symbol-name command))
							  (t (error "Unsupported alias command type"))))))
		   aliases))

   ;; Rebinds
   (defun thanos/eshell-clear ()
	 "Interactive call for clear-scrollback."
	 (interactive)
	 (eshell/clear-scrollback))

   (defun thanos/eshell-preview-insert ()
	 (interactive)
	 (completion-preview-insert)
	 ;; This funciton just deletes the extra space inserted after
	 ;; completion.
	 (delete-char -1))
   :bind (("C-c e" . eshell)
		  :map eshell-mode-map
		  ("C-l" . 'thanos/eshell-clear)
		  ("<tab>" . 'thanos/eshell-preview-insert)
		  ("C-M-i" . 'completion-at-point))
   :hook ((eshell-mode . (lambda ()
						   (thanos/set-eshell-aliases thanos/aliases)
						   (display-line-numbers-mode -1)
						   (eshell-cmpl-mode -1)))))

Eat

Emulate a terminal (Eat)

  • xterm-like emulator, written in Emacs Lisp.
  • Although it’s a standalone terminal for Emacs, similar to term, I’ve mostly used this package to enhance eshell.

My configuration

    (use-package eat
      :ensure t
      :config
      (setf eshell-visual-commands nil
            eat-term-name "xterm-256color")
      :bind (("C-c V" . eat))
      :hook ((eshell-mode . eat-eshell-mode)
             (eshell-mode . eat-eshell-visual-command-mode)
             (eat-mode . (lambda () (visual-line-mode -1)))))

Term & Ansi-Term

  • M-x term | M-x ansi-term
    • ansi-term is the same as term, except that it always creates a new buffer and C-x is being marked as an escape-char.
  • Runs a subshell with input and output through an Emacs buffer.

How to use

  • While in term-mode C-c CHAR is equivalent to C-x CHAR.
    • Example C-c o is the global binding C-x o other-window
  • C-c C-j switches to term-line-mode which allows navigation similar to shell
  • C-c C-k switches back to term-char-mode