karthink / consult-dir

Insert paths into the minibuffer prompt in Emacs
GNU General Public License v3.0
160 stars 9 forks source link

+title: consult-dir: insert paths into minibuffer prompts in Emacs

:BADGE: [[https://melpa.org/#/consult-dir][file:https://melpa.org/packages/consult-dir-badge.svg]] :END:

+attr_html: :width 800px :align center

[[file: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.

You can do this at any time, including when using the minibuffer.

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:

https://user-images.githubusercontent.com/8607532/128619960-a89b578c-b627-417b-b045-06bcbb178b71.mp4

https://user-images.githubusercontent.com/8607532/128617436-63aeafcb-02c5-4ae8-894f-9a1f6c240267.mp4

Think of it like the shell tools [[https://github.com/wting/autojump][autojump]], [[https://github.com/clvv/fasd][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 [[https://github.com/oantolin/embark][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.

** With =use-package=

+BEGIN_SRC emacs-lisp

(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)))

+END_SRC

Replace =minibuffer-local-completion-map= above with

** General method After adding MELPA to your package archives,

+BEGIN_SRC emacs-lisp

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

+END_SRC

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

+begin_src emacs-lisp

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

+end_src

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

+BEGIN_SRC emacs-lisp

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

+END_SRC

Replace =minibuffer-local-completion-map= above with

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.

https://user-images.githubusercontent.com/8607532/127817864-57ca9ec5-af67-4ee8-9410-4758c8450c4b.mp4

https://user-images.githubusercontent.com/8607532/127790046-309df054-3e89-4e3c-adcf-16ec5028ad80.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:

https://user-images.githubusercontent.com/8607532/127790057-1fa9f81b-2c3f-412d-be36-925773451c71.mp4

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

https://user-images.githubusercontent.com/8607532/127790065-39ddc117-5e1a-4580-a009-bc0cebd71ad8.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:

https://user-images.githubusercontent.com/8607532/127790071-4f98d212-c127-48e1-84cd-01701cd63d64.mp4

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

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

+BEGIN_SRC emacs-lisp

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

+END_SRC

*** Project directories (Projectile) To enable, customize =consult-dir-project-list-function= or

+BEGIN_SRC emacs-lisp

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

+END_SRC

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

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:

+begin_src emacs-lisp

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

+end_src

*** 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.

+begin_src emacs-lisp

(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 forconsult-dir'.")

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

+end_src

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 [[https://github.com/clvv/fasd][Fasd]] to consult-dir:

+BEGIN_SRC emacs-lisp

;; 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 forconsult-dir'.")

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

+END_SRC

For additional directory sources, check out the [[https://github.com/karthink/consult-dir/wiki#additional-directory-sources][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=.

=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.

Local Variables:

eval: (when (featurep 'toc-org) (toc-org-mode))

End: