Skip to content

karthink/consult-dir

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 

Repository files navigation

consult-dir: insert paths into minibuffer prompts in Emacs

https://melpa.org/packages/consult-dir-badge.svg

media/consult-dir.png

Consult-dir allows you to easily insert directory paths into the minibuffer prompt in Emacs.

When using the minibuffer, you can switch - with completion and filtering provided by your completion setup - to any directory you’ve visited recently, or to a project, a bookmarked directory or even a remote host via tramp. The minibuffer prompt will be replaced with the directory you choose.

Why would you want to do this?

To avoid “navigating” long distances when picking a file or directory in any Emacs command that requires one. Here I use it to select a distant directory when copying a file with dired:

demo-dired.mp4

Think of it like the shell tools autojump, fasd or z but for Emacs. See the demos section below for many more examples. consult-dir works with all Emacs commands that require you to specify file paths, and with Embark actions on files.

The directory candidates are collected from user bookmarks, Projectile project roots (if available), project.el project roots (if available) and recentf file locations. The default-directory variable is not changed in the process.

Contents

Installation

consult-dir is on MELPA.

With use-package

(use-package consult-dir
  :ensure t
  :bind (("C-x C-d" . consult-dir)
         :map minibuffer-local-completion-map
         ("C-x C-d" . consult-dir)
         ("C-x C-j" . consult-dir-jump-file)))

Replace minibuffer-local-completion-map above with

  • vertico-map if you use Vertico,
  • selectrum-minibuffer-map if you use Selectrum.

General method

After adding MELPA to your package archives,

(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))

you can install it with M-x package-install consult-dir and bind consult-dir as convenient:

(define-key global-map (kbd "C-x C-d") #'consult-dir)
(define-key minibuffer-local-completion-map (kbd "C-x C-d") #'consult-dir)

If you want to use the file-jump functionality, you can bind consult-dir-jump-file in the minibuffer-local-completion-map.

(define-key minibuffer-local-completion-map (kbd "C-x C-j") #'consult-dir-jump-file)

Replace minibuffer-local-completion-map above with

  • vertico-map if you use Vertico,
  • selectrum-minibuffer-map if you use Selectrum.

Usage

Call consult-dir when in the minibuffer to choose a directory with completion and insert it into the minibuffer prompt, shadowing or replacing the directory path showing currently. The file name part of the text is retained. This lets the user switch to distant directories very quickly when finding files, for instance.

Call consult-dir from a regular buffer to choose a directory with completion and then interactively find a file in that directory. The command run with this directory is configurable via consult-dir-default-command and defaults to find-file.

Call consult-dir-jump-file from the minibuffer to asynchronously find a file anywhere under the directory that is currently in the prompt. This can be used with consult-dir to quickly switch directories and find files at an arbitrary depth under them. consult-dir-jump-file uses consult-find under the hood.

Demos

Here I show the different directory sources using Consult’s narrowing feature, then use consult-dir to easily jump around the filesystem and open files. I also use consult-dir-jump-file to quickly drill down a directory when I don’t find the file I’m looking for at the top level:

demo-1.mp4

In this demo I call consult-grep with a prefix argument. This requires me to specify a directory to grep inside of, so I use consult-dir to specify that directory:

demo-2.mp4

Here I use consult-dir to jump to one of my project directories when attaching a file to an email:

demo-3.mp4

In this example I combine consult-dir with Embark. I use consult-dir to specify a directory, then Embark to spawn an eshell there. I then use consult-dir again when tab-completing inside eshell to specify a distant directory to copy files from. Finally I use consult-dir with Embark to jump to a bookmark in a window-split:

demo-embark-1.mp4

(In these demos I am using Vertico as my completion system.)

Configuration

consult-dir should work out of the box with no configuration needed beyond binding it to a key.

However, only bookmarked directories and Project.el projects are displayed by default. if you use Projectile or want finer control over the directories that are offered as candidates to jump to, read on.

Directory sources

Bookmarks

Enabled by default. To disable, customize consult-dir-sources.

Recent directories

To enable, turn on recentf-mode. (M-x recentf-mode). Note that if you don’t already use recentf-mode, the recentf directory cache will start out empty and build up over time as you use Emacs.

Project directories (Project.el)

Enabled by default. To disable, customize consult-dir-project-list-function or

(setq consult-dir-project-list-function nil)

Project directories (Projectile)

To enable, customize consult-dir-project-list-function or

(setq consult-dir-project-list-function #'consult-dir-projectile-dirs)

Remote hosts

Also included are a number of sources for interacting with remote hosts via tramp, principally:

  • consult-dir--source-tramp-local for a set list of local/custom hosts (see consult-dir-tramp-local-hosts to customize)
  • consult-dir--source-tramp-ssh for a list of parsed hosts from your ~/.ssh/config

By default consult-dir does not display known SSH hosts as a separate directory source. If you wish to enable it, customize consult-dir-sources or use the following:

(add-to-list 'consult-dir-sources 'consult-dir--source-tramp-ssh t)

Docker hosts

It’s also possible to define a source to switch to containers using consult-dir. This approach will work for both podman and docker containers, so adjust as desired.

If you’re using Emacs 29+, you can also wrap tramp-container--completion-function instead.

(defcustom consult-dir--tramp-container-executable "docker"
  "Default executable to use for querying container hosts."
  :group 'consult-dir
  :type 'string)

(defcustom consult-dir--tramp-container-args nil
  "Optional list of arguments to pass when querying container hosts."
  :group 'consult-dir
  :type '(repeat string))

(defun consult-dir--tramp-container-hosts ()
  "Get a list of hosts from a container host."
  (cl-loop for line in (cdr
                        (ignore-errors
                          (apply #'process-lines consult-dir--tramp-container-executable
                                 (append consult-dir--tramp-container-args (list "ps")))))
           for cand = (split-string line "[[:space:]]+" t)
           collect (let ((user (unless (string-empty-p (car cand))
                                 (concat (car cand) "@")))
                         (hostname (car (last cand))))
                     (format "/docker:%s%s:/" user hostname))))

(defvar consult-dir--source-tramp-docker
  `(:name     "Docker"
    :narrow   ?d
    :category file
    :face     consult-file
    :history  file-name-history
    :items    ,#'consult-dir--tramp-docker-hosts)
  "Docker candiadate source for `consult-dir'.")

;; Adding to the list of consult-dir sources
(add-to-list 'consult-dir-sources 'consult-dir--source-tramp-docker t)

Then amend consult-dir-sources as in the above snippet to include the source you defined.

Writing your own directory source

If none of the above include directories you want to jump to, you can write your own source. As a template, here is a source that adds paths provided by the shell tool Fasd to consult-dir:

;; A function that returns a list of directories
(defun consult-dir--fasd-dirs ()
  "Return list of fasd dirs."
  (split-string (shell-command-to-string "fasd -ld") "\n" t))

;; A consult source that calls this function
(defvar consult-dir--source-fasd
 `(:name     "Fasd dirs"
   :narrow   ?f
   :category file
   :face     consult-file
   :history  file-name-history
   :enabled  ,(lambda () (executable-find "fasd"))
   :items    ,#'consult-dir--fasd-dirs)
  "Fasd directory source for `consult-dir'.")

;; Adding to the list of consult-dir sources
(add-to-list 'consult-dir-sources 'consult-dir--source-fasd t)

For additional directory sources, check out the wiki.

Default consult-dir action

When called from a regular buffer (i.e not the minibuffer), consult-dir defaults to calling find-file after you choose a directory. To set it to open the directory in dired instead or to run a custom command, customize consult-dir-default-command.

File name shadowing

By default, choosing a directory using consult-dir when in the minibuffer results in the text already in the prompt being “shadowed” or made inactive, but you can still delete the new text to recover it. You can make the new text replace the old instead by setting consult-dir-shadow-filenames to nil.

Alternatives

consult-dir is akin to shell tools like autojump or fasd but for all file/directory contexts in Emacs.

consult-buffer (part of Consult) already allows you to switch to bookmarks and recentf files, so this might be sufficient for you if you need to visit a proximal set of files quickly. consult-dir is different in that it is composable with all Emacs commands that require you to specify a directory and thus works in more contexts.

Projectile and the built-in project.el have extensive support for listing and quickly switching projects and running actions on them. consult-dir is more of a one-stop shop (“just get me there”) for switching directories as it includes recent directories and bookmarks in the mix, allows jumping to files with consult-dir-jump-file, and supports running arbitrary actions on directories using Embark. Of course, it also allows for fast directory selection when using any Emacs command that requires specifying a directory.

Acknowledgements