Emacs built-ins

Dired

Dired is Emacs’ built-in file manager. It’s really great, and replaces any graphical file manager for me most of the time because:

  • I am not limited to x tabs or panes
  • All actions can be done with keybindings
  • I get a consistent behaviour between Dired and Emacs, since it’s the same thing.

I used to have an extensive configuration for Dired with a couple of additional packages to make it more usable. Dirvish rendered that obsolete!

(use-package dirvish
  :straight (:build t)
  :defer t
  :init (dirvish-override-dired-mode)
  :custom
  (dirvish-quick-access-entries
   '(("h" "~/" "Home")
     ("d" "~/Downloads/" "Downloads")
     ("c" "~/org/config" "Config")
     ("C" "~/Documents/conlanging/content" "Conlanging")))
  (dirvish-mode-line-format
   '(:left (sort file-time "" file-size symlink) :right (omit yank index)))
  (dirvish-attributes '(all-the-icons file-size collapse subtree-state vc-state git-msg))
  :config
  (dirvish-peek-mode)
  <<dired-drag-and-drop>>
  <<dired-listing-flags>>
  <<dired-files-and-dirs>>
  <<dirvish-exa-offload>>
  (setq dired-dwim-target         t
        dired-recursive-copies    'always
        dired-recursive-deletes   'top
        delete-by-moving-to-trash t
        dirvish-preview-dispatchers (cl-substitute 'pdf-preface 'pdf dirvish-preview-dispatchers))
  :general
  (phundrak/evil
    :keymaps 'dirvish-mode-map
    :packages '(dired dirvish)
    "q" #'dirvish-quit
    "TAB" #'dirvish-subtree-toggle)
  (phundrak/major-leader-key
    :keymaps 'dirvish-mode-map
    :packages '(dired dirvish)
    "A"   #'gnus-dired-attach
    "a"   #'dirvish-quick-access
    "d"   #'dirvish-dispatch
    "e"   #'dirvish-emerge-menu
    "f"   #'dirvish-fd-jump
    "F"   #'dirvish-file-info-menu
    "h"   '(:ignore t :which-key "history")
    "hp"  #'dirvish-history-go-backward
    "hn"  #'dirvish-history-go-forward
    "hj"  #'dirvish-history-jump
    "hl"  #'dirvish-history-last
    "l"   '(:ignore t :which-key "layout")
    "ls"  #'dirvish-layout-switch
    "lt"  #'dirvish-layout-toggle
    "m"   #'dirvish-mark-menu
    "s"   #'dirvish-quicksort
    "S"   #'dirvish-setup-menu
    "y"   #'dirvish-yank-menu
    "n"   #'dirvish-narrow))

It requires some programs which can be installed like so:

pacman -S --needed --noprogressbar --noconfirm --color=never \
       fd poppler ffmpegthumbnailer mediainfo imagemagick tar unzip

Since Emacs 29, it is possible to enable drag-and-drop between Emacs and other applications.

(csetq dired-mouse-drag-files                   t
       mouse-drag-and-drop-region-cross-program t)

In Dirvish, it’s best to use the long name of flags whenever possible, otherwise some commands won’t work.

(csetq dired-listing-switches (string-join '("--all"
                                             "--human-readable"
                                             "--time-style=long-iso"
                                             "--group-directories-first"
                                             "-lv1")
                                           " "))

However, it is possible to instead use eza when it is available (it’s a replacement to the unmaintained exa). Instead of making Emacs’ main thread to the file listing in a directory, we offload it to an external thread.

(dirvish-define-preview eza (file)
  "Use `eza' to generate directory preview."
  :require ("eza")
  (when (file-directory-p file)
    `(shell . ("eza" "--color=always" "-al" ,file))))

(add-to-list 'dirvish-preview-dispatchers 'eza)

Finally, some directories need to be set for Dired to store various files and images.

(let ((my/file (lambda (path &optional dir)
                 (expand-file-name path (or dir user-emacs-directory))))
      (my/dir (lambda (path &optional dir)
                (expand-file-name (file-name-as-directory path)
                                  (or dir user-emacs-directory)))))
  (csetq image-dired-thumb-size             150
         image-dired-dir                    (funcall my/dir "dired-img")
         image-dired-db-file                (funcall my/file "dired-db.el")
         image-dired-gallery-dir            (funcall my/dir "gallery")
         image-dired-temp-image-file        (funcall my/file "temp-image" image-dired-dir)
         image-dired-temp-rotate-image-file (funcall my/file "temp-rotate-image" image-dired-dir)))

Copying files with Dired is a blocking process. It’s usually fine when there’s not a lot to copy, but it becomes annoying when moving larger files. The package dired-rsync allows copying files with rsync in the background; we can then carry on with our tasks while the copy is happening.

(use-package dired-rsync
  :if (executable-find "rsync")
  :defer t
  :straight (:build t)
  :general
  (phundrak/evil
    :keymaps 'dired-mode-map
    :packages 'dired-rsync
    "C-r" #'dired-rsync))

Compilation mode

After reading about a blog article, I found out it is possible to run quite a few things through compilation-mode, so why not? First, let’s redefine some keybinds for this mode. I’ll also define a general keybind in order to re-run my programs from other buffers than the compilation-mode buffer. I also want to follow the output of the compilation buffer, as well as enable some syntax highlighting.

(use-package compile
  :defer t
  :straight (compile :type built-in)
  :hook (compilation-filter . colorize-compilation-buffer)
  :init
  (require 'ansi-color)
  (defun colorize-compilation-buffer ()
    (let ((inhibit-read-only t))
      (ansi-color-apply-on-region (point-min) (point-max))))
  :general
  (phundrak/evil
    :keymaps 'compilation-mode-map
    "g" nil
    "r" nil
    "R" #'recompile
    "h" nil)
  :config
  (setq compilation-scroll-output t))

Eshell

img

Eshell is a built-in shell available from Emacs which I use almost as often as fish. Some adjustments are necessary to make it fit my taste though.

(use-package eshell
  :defer t
  :straight (:type built-in :build t)
  :config
  (setq eshell-prompt-function
        (lambda ()
          (concat (abbreviate-file-name (eshell/pwd))
                  (if (= (user-uid) 0) " # " " λ ")))
        eshell-prompt-regexp "^[^#λ\n]* [#λ] ")
  <<eshell-alias-file>>
  <<eshell-concat-shell-command>>
  <<eshell-alias-open>>
  <<eshell-alias-clear>>
  <<eshell-alias-buffers>>
  <<eshell-alias-emacs>>
  <<eshell-alias-mkcd>>
  :general
  (phundrak/evil
    :keymaps 'eshell-mode-map
    [remap evil-collection-eshell-evil-change] #'evil-backward-char
    "c" #'evil-backward-char
    "t" #'evil-next-visual-line
    "s" #'evil-previous-visual-line
    "r" #'evil-forward-char
    "h" #'evil-collection-eshell-evil-change)
  (general-define-key
   :keymaps 'eshell-mode-map
   :states 'insert
   "C-a" #'eshell-bol
   "C-e" #'end-of-line))

Aliases

First, let’s declare our list of “dumb” aliases we’ll use in Eshell. You can find them here.

(setq eshell-aliases-file (expand-file-name "eshell-alias" user-emacs-directory))

A couple of other aliases will be defined through custom Elisp functions, but first I’ll need a function for concatenating a shell command into a single string:

(defun phundrak/concatenate-shell-command (&rest command)
  "Concatenate an eshell COMMAND into a single string.
All elements of COMMAND will be joined in a single
space-separated string."
  (mapconcat #'identity command " "))

I’ll also declare some aliases here, such as open and openo that respectively allow me to open a file in Emacs, and same but in another window.

(defalias 'open #'find-file)
(defalias 'openo #'find-file-other-window)

The default behaviour of eshell/clear is not great at all, although it clears the screen it also scrolls all the way down. Therefore, let’s alias it to eshell/clear-scrollback which has the correct behaviour.

(defalias 'eshell/clear #'eshell/clear-scrollback)

As you see, these were not declared in my dedicated aliases file but rather were declared programmatically. This is because I like to keep my aliases file for stuff that could work too with other shells were the syntax a bit different, and aliases related to Elisp are kept programmatically. I’ll also declare list-buffers an alias of ibuffer because naming it that way kind of makes more sense to me.

(defalias 'list-buffers 'ibuffer)

I still have some stupid muscle memory telling me to open emacs, vim or nano in Eshell, which is stupid: I’m already inside Emacs and I have all its power available instantly. So, let’s open each file passed to these commands.

(defun eshell/emacs (&rest file)
  "Open each FILE and kill eshell.
Old habits die hard."
  (when file
    (dolist (f (reverse file))
      (find-file f t))))

Finally, I’ll declare mkcd which allows the simultaneous creation of a directory and moving into this newly created directory. And of course, it will also work if the directory also exists or if parent directories don’t, similarly to the -p option passed to mkdir.

(defun eshell/mkcd (dir)
  "Create the directory DIR and move there.
If the directory DIR doesn’t exist, create it and its parents
if needed, then move there."
  (mkdir dir t)
  (cd dir))

Commands

When I’m in Eshell, sometimes I wish to open multiple files at once in Emacs. For this, when I have several arguments for find-file, I want to be able to open them all at once. Let’s modify find-file like so:

(defadvice find-file (around find-files activate)
  "Also find all files within a list of files. This even works recursively."
  (if (listp filename)
      (cl-loop for f in filename do (find-file f wildcards))
    ad-do-it))

I also want to be able to have multiple instances of Eshell opened at once. For that, I declared the function eshell-new that does exactly that.

(defun eshell-new ()
  "Open a new instance of eshell."
  (interactive)
  (eshell 'N))

A very useful command I often use in fish is z, a port from bash’s and zsh’s command that allows to jump around directories based on how often we go in various directories.

(use-package eshell-z
  :defer t
  :after eshell
  :straight (:build t)
  :hook (eshell-mode . (lambda () (require 'eshell-z))))

Environment Variables

Some environment variables need to be correctly set so Eshell can correctly work. I would like to set two environment variables related to Dart development: the DART_SDK and ANDROID_HOME variables.

(setenv "DART_SDK" "/opt/dart-sdk/bin")
(setenv "ANDROID_HOME" (concat (getenv "HOME") "/Android/Sdk/"))

The EDITOR variable also needs to be set for git commands, especially the yadm commands.

(setenv "EDITOR" "emacsclient -c -a emacs")

Finally, for some specific situations I need SHELL to be set to something more standard than fish:

(setenv "SHELL" "/bin/sh")

Visual configuration

I like to have at quick glance some information about my machine when I fire up a terminal. I haven’t found anything that does that the way I like it, so I’ve written a packageopen in new window! It’s actually available on Melpa, but since I’m the main dev of this package, I’ll keep track of the git repository.

(use-package eshell-info-banner
  :after (eshell)
  :defer t
  :straight (eshell-info-banner :build t
                                :type git
                                :host github
                                :protocol ssh
                                :repo "phundrak/eshell-info-banner.el")
  :hook (eshell-banner-load . eshell-info-banner-update-banner)
  :custom-face
  (eshell-info-banner-normal-face ((t :foreground "#A3BE8C")))
  (eshell-info-banner-background-face ((t :foreground "#E5E9F0")))
  (eshell-info-banner-warning-face ((t :foreround "#D08770")))
  (eshell-info-banner-critical-face ((t :foreground "#BF616A")))
  :custom
  (eshell-info-banner-partition-prefixes (list "/dev" "zroot" "tank")))

Another feature I like is fish-like syntax highlight, which brings some more colours to Eshell.

(use-package eshell-syntax-highlighting
  :after (esh-mode eshell)
  :defer t
  :straight (:build t)
  :config
  (eshell-syntax-highlighting-global-mode +1))

Powerline prompts are nice, git-aware prompts are even better! eshell-git-prompt is nice, but I prefer to write my own package for that.

(use-package powerline-eshell
  :if (string= (string-trim (shell-command-to-string "uname -n")) "leon")
  :load-path "~/fromGIT/emacs-packages/powerline-eshell.el/"
  :after eshell)

Eww

Since Emacs 29, it is possible to automatically rename eww buffers to a more human-readable name, see Prot’s blogopen in new window post on the matter.

(use-package eww
  :defer t
  :straight (:type built-in)
  :config
  (setq eww-auto-rename-buffer 'title))

Image-mode

I won’t modify much for image-mode (the mode used to display images) aside from Emacs’ ability to use external converters to display some images it wouldn’t be able to handle otherwise.

(setq image-use-external-converter t)

Info

Let’s define some more intuitive keybinds for info-mode.

(use-package info
  :defer t
  :straight (info :type built-in :build t)
  :general
  (phundrak/evil
    :keymaps 'Info-mode-map
    "c" #'Info-prev
    "t" #'evil-scroll-down
    "s" #'evil-scroll-up
    "r" #'Info-next)
  (phundrak/major-leader-key
    :keymaps 'Info-mode-map
    "?" #'Info-toc
    "b" #'Info-history-back
    "f" #'Info-history-forward
    "m" #'Info-menu
    "t" #'Info-top-node
    "u" #'Info-up))

Tab Bar

(use-package tab-bar
  :defer t
  :straight (:type built-in)
  :custom
  (tab-bar-close-button-show nil)
  (tab-bar-new-button-show nil)
  (tab-bar-new-tab-choice "*dashboard*")
  :custom-face
  (tab-bar ((t (:background "#272C36"
                :foreground "#272C36"
                :box (:line-width (8 . 5) :style flat-button)))))
  :init
  (advice-add #'tab-new
              :after
              (lambda (&rest _) (when (y-or-n-p "Rename tab? ")
                                  (call-interactively #'tab-rename)))))

Tramp

Tramp is an Emacs built-in package that allows the user to connect to various hosts using various protocols, such as ssh and rsync. However, I have some use-case for Tramp which are not supported natively. I will describe them here.

(use-package tramp
  :straight (tramp :type built-in :build t)
  :config
  <<tramp-add-yadm>>
  (csetq tramp-ssh-controlmaster-options nil
         tramp-verbose 0
         tramp-auto-save-directory (locate-user-emacs-file "tramp/")
         tramp-chunksize 2000)
  (add-to-list 'backup-directory-alist ; deactivate auto-save with TRAMP
               (cons tramp-file-name-regexp nil)))

Yadm

yadmopen in new window is a git wrapper made to easily manage your dotfiles. It has loads of features I don’t use (the main one I like but don’t use is its Jinja-like host and OS-aware syntaxopen in new window), but unfortunately Magit doesn’t play nice with it. Tramp to the rescue, and this page explains how! Let’s just insert in my config this code snippet:

(add-to-list 'tramp-methods
                   '("yadm"
                     (tramp-login-program "yadm")
                     (tramp-login-args (("enter")))
                     (tramp-login-env (("SHELL") ("/bin/sh")))
                     (tramp-remote-shell "/bin/sh")
                     (tramp-remote-shell-args ("-c"))))

I’ll also create a fuction for connecting to this new Tramp protocol:

(defun my/yadm ()
  "Manage my dotfiles through TRAMP."
  (interactive)
  (magit-status "/yadm::"))