17 KiB
Emacs — Packages — Emacs Built-ins
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)
(phundrak/leader-key
"R" #'recompile)
:config
(setq compilation-scroll-output t))
Eshell
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 package! 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 blog 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
yadm
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 syntax), 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::"))