Here are a couple of the features I was looking for
- It should be projectile project aware
- It should be attached to a single frame. I usually have one frame for each project I’m working on and as such I need one for each frame.
- It would be nice if it displayed folders before files
- It should be positioned on either the right or left side of the frame
- The window shouldn’t be affected by
delete-other-windows
and similar functions. - The window should never be overtaken by another buffer
I believe I’ve accomplished this without too much hacking around; I’m using a couple of packages and some built-in emacs features.
Lets start with the packages. I’m using use-package to configure all my emacs packages; if you aren’t using it already I can really recommend it.
The first part of the solution is dired-subtree. This is a very helpful package that makes it possible to insert a subdirectory as a separate listing in the active dired buffer thus giving you a tree-like dired buffer.
(use-package dired-subtree
:demand
:bind
(:map dired-mode-map
("<enter>" . mhj/dwim-toggle-or-open)
("<return>" . mhj/dwim-toggle-or-open)
("<tab>" . mhj/dwim-toggle-or-open)
("<down-mouse-1>" . mhj/mouse-dwim-to-toggle-or-open))
:config
(progn
;; Function to customize the line prefixes (I simply indent the lines a bit)
(setq dired-subtree-line-prefix (lambda (depth) (make-string (* 2 depth) ?\s)))
(setq dired-subtree-use-backgrounds nil)))
The functions mhj/dwim-toggle-or-open
and mhj/mouse-dwim-to-toggle-or-open
are optional but I use them to either expand a folder or open a file depending
on the what is under the point when you execute it. Here’s the implementation.
(defun mhj/dwim-toggle-or-open ()
"Toggle subtree or open the file."
(interactive)
(if (file-directory-p (dired-get-file-for-visit))
(progn
(dired-subtree-toggle)
(revert-buffer))
(dired-find-file)))
(defun mhj/mouse-dwim-to-toggle-or-open (event)
"Toggle subtree or the open file on mouse-click in dired."
(interactive "e")
(let* ((window (posn-window (event-end event)))
(buffer (window-buffer window))
(pos (posn-point (event-end event))))
(progn
(with-current-buffer buffer
(goto-char pos)
(mhj/dwim-toggle-or-open)))))
So now we have a dired
buffer that works as a tree-view. To have dired
put
folders before files in its list, and to hide a couple of files I don’t care
about I use the following configuration
(use-package dired
:ensure nil
:config
(progn
(setq insert-directory-program "/usr/local/opt/coreutils/libexec/gnubin/ls")
(setq dired-listing-switches "-lXGh --group-directories-first")
(add-hook 'dired-mode-hook 'dired-omit-mode)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)))
This configuration is for OS X. Notice that I use GNU ls
rather than
the ls
that ships with OS X as it doesn’t support the command line
options we need for this. You can get this by running
brew install coreutils
Now we have a dired buffer with the functionality we want. To solve the last two requirements we use two very handy Emacs features: Dedicated Windows and Action Functions for display-buffer.
(defun mhj/toggle-project-explorer ()
"Toggle the project explorer window."
(interactive)
(let* ((buffer (dired-noselect (projectile-project-root)))
(window (get-buffer-window buffer)))
(if window
(mhj/hide-project-explorer)
(mhj/show-project-explorer))))
(defun mhj/show-project-explorer ()
"Project dired buffer on the side of the frame.
Shows the projectile root folder using dired on the left side of
the frame and makes it a dedicated window for that buffer."
(let ((buffer (dired-noselect (projectile-project-root))))
(progn
(display-buffer-in-side-window buffer '((side . left) (window-width . 0.2)))
(set-window-dedicated-p (get-buffer-window buffer) t))))
(defun mhj/hide-project-explorer ()
"Hide the project-explorer window."
(let ((buffer (dired-noselect (projectile-project-root))))
(progn
(delete-window (get-buffer-window buffer))
(kill-buffer buffer))))
The interesting functions here are display-buffer-in-side-window
and
set-window-dedicated-p
.
That’s all it takes. I’ve been using it for a couple of weeks and so far I’ve
very happy with the solution. It’s pretty convenient that it’s using dired
as
it gives you a lot of features for free and it plays well with other dired
packages like dired-narrow.