Enhancing Dired Sorting With Transient
16 Jan 2024 Charles Choi
Out of the box, sorting a Dired buffer is rather underwhelming. In this post I describe how to improve that. But before we get into details, let’s see what the end-result looks like. For the keyboard-driven workflow, the following Transient menu is shown below.
Note that setting the time-format offers different choices which you can select via completion.
The mouse-only driven workflow is shown below.
Both workflows work without surprise.
The Problem
Dired’s default behavior is to use the keybinding s
(bound to dired-sort-toggle-or-edit
) to toggle between alphabetical or date/time order. If you want different sorting you can use the prefix C-u
before s
which will offer a minibuffer prompt where you’ll then need to enter the command-line arguments for ls or whatever file listing program you’ve configured Dired to use. Even worse from a user experience point of view is that there are different variants of ls with different command-line arguments that accomplish the same thing or are specific only to that variant. Many of these arguments are arcane, so you’ll likely need to run man to recall them. Topping it off is a single type-in field for arguments, precluding any possible form validation to keep you out of trouble.
While there are other packages like Dired Plus or Dirvish that can offer an improved sorting UI over what is described above, given my recent enchantment with Transient, I wanted to see if I could use it to implement a richer Dired sorting experience without a lot of overhead/dependencies.
Caveats
Given the diversity of ls implementations, rather than supporting them all I’ve decided to focus on the one that I use: GNU ls from coreutils. This means if your system doesn’t have it, you’ll need to install it. On macOS, I use MacPorts to install coreutils. Homebrew users can go here. GNU/Linux users are all set. As I’m not so familiar with Windows nowadays, I suppose Windows Subsystem for Linux or Cygwin are the way to go now.
Another caveat is that for Dired to work with Tramp, you’ll need to have GNU ls installed on that remote system.
Requirements
Let’s capture some lightweight requirements to clarify goals:
-
Menus must support the following sort criteria for both ascending and descending order:
- Name of the file or directory (ASCII sort order)
- Kind (extension) of the file
- Date the file was last opened
- Date the file was added (created)
- Date the file was last modified
- Date the file metadata was last changed
- Version file name sort (numbered file names are sorted in cardinal fashion, e.g. a1, a2, …, a9, a10, …)
- File size
-
A keyboard-driven menu to sort a Dired buffer must be supported.
-
A mouse-only-driven menu to sort a Dired buffer must be supported.
- This workflow can be initiated from a context menu using
context-menu-mode
.
- This workflow can be initiated from a context menu using
-
This implementation must work using Emacs 29.1 or higher.
-
The implementation will only work with GNU ls.
Implementation
The implementation design of the workflows described above is straight-forward:
- Build a private function that passes the command-line arguments to ls.
- Build two separate user interface functions (one for keyboard, the other for mouse) to collect the desired arguments and pass them to the private function described above.
With GNU ls, here’s a survey of the arguments we’ll use to support the requirements, and then some. Note that not all GNU ls arguments and their choice values are supported here. Motivated readers are welcome to modify this implementation to their taste.
Argument | Description |
---|---|
-a, --all | do not ignore entries starting with . |
--group-directories-first | group directories before files |
-h, --human-readable | with -l, print sizes like 1K 234M 2G etc. |
-l | use a long listing format |
-r, --reverse | reverse order while sorting |
--sort=WORD | sort by WORD instead of name (choices: size, time, version, extension) |
--time=WORD | select which timestamp used to display or sort (choices: access, status, modification, creation) |
--time-style=TIME_STYLE | time/date format (choices: full-iso, long-iso, iso, locale) |
-S | sort by size |
Note is that there is a Dired variable dired-listing-switches
which initializes the sort order when viewing a directory for the first time. To avoid dependency on dired-listing-switches
, I've created a new customizable variable cc-dired-listing-switches
as a list of strings which the functions below will refer to. For consistent behavior, it is recommended to initialize dired-listing-switches
to correspond to the value of cc-dired-listing-switches
.
Private Function Implementation
The private function cc/--dired-sort-by
manages the above ls arguments by abstracting subsets of them into Elisp keywords that correspond to the different sort criteria. Note that in Elisp, “private” is a naming convention. Doing the heavy lifting is the call to dired-sort-other
which passes the arguments to ls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
Keyboard-Driven Workflow
The design of the Transient menu presents two separate sections:
- Arguments which are not directly related to the sorting criteria
- Sort by criteria
The implementation of the Transient menu cc/dired-sort-by
is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
|
Note that the :value
keyword is hard-coded with arguments that initialize the menu UI. As I am new to the Transient API, I don’t know if this can be dynamically changed so that the next time the menu is invoked, it is initialized with a different set of values. Ideally I’d like for it to take the current value of a customizable variable I've defined ( cc-dired-listing-switches
). For those readers who can provide guidance on this, I would greatly appreciate it.
Mouse-Only Driven Workflow
To support a mouse-only driven workflow, we can define a keymap using easy-menu-define
. The keymap implementation cc/dired-sort-menu
shown below does just that. This keymap can then be incorporated into a context menu. Note that arguments unrelated to sorting are initialized from the customizable variable cc-dired-listing-switches
which is referred to in the implementation of cc/--dired-sort-by
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
The full source for the above can be found here and is verified for Emacs 29.1. Energized readers are encouraged to try it out!
Closing Thoughts
Given the constrained requirement to only support GNU ls, this exercise turned out to be a day’s worth of work resulting in two different but I think compelling workflows for sorting a Dired buffer. From this vantage point, I’ve got what I wanted and can easily declare victory and possibly never think about this problem again. That said, I imagine I’m not singular in thinking that the default sorting UX for Dired should be improved.
Dired is old. I would not be surprised if the design the Dired maintainers took for the default sorting UX was in response to the wide diversity of Unix ls implementations there were back in the 90’s, without any one being overwhelmingly predominant. It was too hard then to figure out a cross-platform solution, so a least common denominator tack was taken. But it’s 2024 now and the number of platforms to support now are vastly less. From Emacs Survey 2022, GNU/Linux, macOS, Windows, and BSD are the predominant platforms used by Emacs users. For future Emacs users, I think there is a compelling argument to revisit Dired sorting in core.
On thinking about next steps, perhaps I can try to publish this source on MELPA. The only reservation I have in doing that is that it only supports GNU ls and that I'm not inclined to QA other platforms due to time and resources. Perhaps GNU is enough and I don’t need to support others?
Anyways, if you’ve gotten to this point and want to offer feedback, please feel free to do so by replying to a thread linked to this post on my Mastodon account. And thanks for being here!