config.phundrak.com/docs/emacs/packages/emacs-builtin.org
Lucien Cartier-Tilet 788ecefbe5
docs(emacs): move most keybindings to same file
All general keybindings (not linked to a specific mode) are now
defined in keybindings.org in neatly displayed tables.
2024-04-03 06:07:58 +02:00

17 KiB
Raw Blame History

Emacs — Packages — Emacs Built-ins

Emacs built-ins

Dired

Dired is Emacs built-in file manager. Its 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 its 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, its best to use the long name of flags whenever possible, otherwise some commands wont 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 (its 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. Its usually fine when theres 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, lets redefine some keybinds for this mode. Ill 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

/phundrak/config.phundrak.com/media/commit/39f8a4510b9ffa37b84d707da1a343c459f1e0dd/docs/emacs/img/emacs-eshell.svg

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, lets declare our list of “dumb” aliases well 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 Ill 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 " "))

Ill 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, lets 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. Ill 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: Im already inside Emacs and I have all its power available instantly. So, lets 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, Ill 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 dont, similarly to the -p option passed to mkdir.

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

Commands

When Im 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. Lets 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 bashs and zshs 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 havent found anything that does that the way I like it, so Ive written a package! Its actually available on Melpa, but since Im the main dev of this package, Ill 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 Prots blog post on the matter.

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

Image-mode

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

(setq image-use-external-converter t)

Info

Lets 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

yadm is a git wrapper made to easily manage your dotfiles. It has loads of features I dont use (the main one I like but dont use is its Jinja-like host and OS-aware syntax), but unfortunately Magit doesnt play nice with it. Tramp to the rescue, and this page explains how! Lets 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"))))

Ill also create a fuction for connecting to this new Tramp protocol:

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