Dired is an Emacs mode providing an interactive interface to navigate and manipulate files. It took me a while to start using it, but it is so efficient that I now spend more time in it than in my usual shell.
However I am no fond of the way files are listed. Dired displays a lot of information which is — at least for me — rarely used. As a result the important content (the name of the file) is flushed to the right of the window, often leading to unpractical line wrapping.
It is hard to find what you are searching for in this kind of interface:
So I decided to spend some time improving my Dired experience.
Configuring a simpler view
Dired comes with the dired-hide-details-mode
minor mode which hides extra
information. By default it is activated with the (
key.
It has two drawbacks:
- First it also hides the display of symbolic link target while still showing
the
->
marker after the link. This behaviour can be changed by setting thedired-hide-details-hide-symlink-targets
variable tonil
. - Second, Dired disables this minor mode each time you move to a different directory, which is really annoying
We create a g-dired-minimal-view
variable to store the current state. When
then write two functions: one to either enable or disable the minor mode
depending on the state, called by the dired-mode-hook
, and one to switch
between simple and extended view bound to the more accessible <tab>
key.
(setq g-dired-minimal-view t)
(defun g-dired-setup-view ()
(dired-hide-details-mode (if g-dired-minimal-view 1 -1)))
(defun g-dired-switch-view ()
(interactive)
(setq g-dired-minimal-view (not g-dired-minimal-view))
(g-dired-setup-view))
(use-package dired
:config
(setq dired-hide-details-hide-symlink-targets nil)
:hook
((dired-mode-hook . g-dired-setup-view))
:bind
(:map dired-mode-map
("<tab>" . g-dired-switch-view)))
A better extended view
While I prefer the simple view by default, the extended view still has its use; but I find it hard to read:
- The traditional representations of permissions takes a lot of horizontal space, the 4 digit octal value would be shorter.
- The number of links is not really useful.
- The size of the file would be more convenient in a human readable format.
- The date and time uses an irregular US-style format which is either “month day year” or “month day hour:minute”.
To list files, Dired call the ls
program with a list of options (defined by
the dired-listing-switches
variable), insert the output in the buffer and
rely on regular expressions to extract information. This means that we have
very little control on formatting. We can change the options passed to ls
,
but we have to be careful not to break Dired features. For example, Dired can
highlight files with specific permissions, but rely on a regular expression
based on the format used by ls
, so we cannot change the permission column.
First we use dired-listing-switches
to add the -h
(for human-readable
sizes) and --time-style=long-iso
option. Since these options are only
available for the GNU version of ls
, I will have to add platform detection
for FreeBSD support in a way that works when Dired is running with
TRAMP. Something for another day.
Then we add a dired-after-readin-hook
hook which is called by Dired after
the output of ls
has been collected and inserted in the buffer. In this
hook, we iterate on lines and process them. We highlight metadata with the
shadow
face to keep them visually distinct from the filename, and we hide
the link count column using the invisible
text property so that the text
stays in the buffer, making sure not to break Dired features.
Finally we disable wrapping with a hook.
(defun g-dired-postprocess-ls-output ()
"Postprocess the list of files printed by the ls program when
executed by Dired."
(save-excursion
(goto-char (point-min))
(while (not (eobp))
;; Go to the beginning of the next line representing a file
(while (null (dired-get-filename nil t))
(dired-next-line 1))
(beginning-of-line)
;; Narrow to the line and process it
(let ((start (line-beginning-position))
(end (line-end-position)))
(save-restriction
(narrow-to-region start end)
(setq inhibit-read-only t)
(unwind-protect
(g-dired-postprocess-ls-line)
(setq inhibit-read-only nil))))
;; Next line
(dired-next-line 1))))
(defun g-dired-disable-line-wrapping ()
(setq truncate-lines t))
(defun g-dired-postprocess-ls-line ()
"Postprocess a single line in the ls output, i.e. the information
about a single file. This function is called with the buffer
narrowed to the line."
;; Highlight everything but the filename
(when (re-search-forward directory-listing-before-filename-regexp nil t 1)
(add-text-properties (point-min) (match-end 0) '(font-lock-face shadow)))
;; Hide the link count
(beginning-of-line)
(when (re-search-forward " +[0-9]+" nil t 1)
(add-text-properties (match-beginning 0) (match-end 0) '(invisible t))))
(use-package dired
:config
(setq dired-listing-switches "-alh --time-style=long-iso")
:hook
((dired-mode-hook . g-dired-disable-line-wrapping)
(dired-after-readin-hook . g-dired-postprocess-ls-output)))
Much better now!