branch: externals/consult
commit e5f4f5cde390d7f5aaa6e4558d7cf7755aec0e54
Author: Daniel Mendler <[email protected]>
Commit: Daniel Mendler <[email protected]>
Improve file preview
- Fix https://github.com/oantolin/embark/issues/490
- Sources can have a :new function now to create new elements (See #539)
- consult--multi-lookup: Handle non-existing candidates by redirecting to
the
:new function
- Renamed consult--temporary-files to consult--previewed-files
- consult--previewed-files must be called with the nil argument, whenever
the
previewed source is changed, i.e, when the CAND argument is nil.
- Rename previewed file buffers (Prefix the name with "Preview:") and
dissassociate the buffers from the files, such that opening the previewed
buffer via `M-x embark-act RET` becomes possible if
embark-quit-after-action=nil.
---
README.org | 3 +-
consult-xref.el | 4 +-
consult.el | 150 +++++++++++++++++++++++++++++++++-----------------------
3 files changed, 93 insertions(+), 64 deletions(-)
diff --git a/README.org b/README.org
index 0d08443cbf..1491631aff 100644
--- a/README.org
+++ b/README.org
@@ -600,7 +600,8 @@ Optional source fields:
- =:annotate= Annotation function called for each candidate, returns string.
- =:history= Name of history variable to add selected candidate.
- =:default= Must be t if the first item of the source is the default value.
-- =:action= Action function called with the selected candidate.
+- =:action= Function called with the selected candidate.
+- =:new= Function called with new candidate name, only if =:require-match= is
nil.
- =:state= State constructor for the source, must return the state function.
- Other source fields can be added specifically to the use case.
diff --git a/consult-xref.el b/consult-xref.el
index 283781d201..5a80fd17df 100644
--- a/consult-xref.el
+++ b/consult-xref.el
@@ -51,10 +51,10 @@
(defun consult-xref--preview (display)
"Xref preview with DISPLAY function."
- (let ((open (consult--temporary-files))
+ (let ((open (consult--previewed-files))
(preview (consult--jump-preview)))
(lambda (action cand)
- (when (eq action 'exit)
+ (unless cand
(funcall open))
(let ((consult--buffer-display display))
(funcall preview action
diff --git a/consult.el b/consult.el
index b8551e8cdc..44055ee280 100644
--- a/consult.el
+++ b/consult.el
@@ -200,12 +200,12 @@ character, the *Completions* buffer and a few log
buffers."
:type '(repeat regexp))
(defcustom consult-buffer-sources
- '(consult--source-hidden-buffer
- consult--source-buffer
+ '(consult--source-buffer
consult--source-recent-file
consult--source-bookmark
consult--source-project-buffer
- consult--source-project-recent-file)
+ consult--source-project-recent-file
+ consult--source-hidden-buffer)
"Sources used by `consult-buffer'.
See also `consult-project-buffer-sources'.
See `consult--multi' for a description of the source data structure."
@@ -1127,9 +1127,8 @@ ORIG is the original function, HOOKS the arguments."
(apply orig hooks))
(apply orig hooks)))
-(defun consult--find-file-temporarily (name)
+(defun consult--find-file-for-preview (name)
"Open file NAME temporarily for preview."
- (setq name (abbreviate-file-name (expand-file-name name)))
;; file-attributes may throw permission denied error
(when-let* ((attrs (ignore-errors (file-attributes name)))
(size (file-attribute-size attrs)))
@@ -1162,10 +1161,10 @@ ORIG is the original function, HOOKS the arguments."
name (file-size-human-readable size))
nil)))
-(defun consult--temporary-files ()
+(defun consult--previewed-files ()
"Return a function to open files temporarily for preview."
(let ((dir default-directory)
- (hook (make-symbol "consult--temporary-files"))
+ (hook (make-symbol "consult--previewed-files"))
(orig-buffers (buffer-list))
temporary-buffers)
(fset hook
@@ -1173,44 +1172,60 @@ ORIG is the original function, HOOKS the arguments."
;; Fully initialize previewed files and keep them alive.
(unless (consult--completion-window-p)
(let (live-files)
- (dolist (buf temporary-buffers)
- (when-let ((file (buffer-file-name buf))
- (wins (and (buffer-live-p buf)
- (get-buffer-window-list buf))))
+ (pcase-dolist (`(,file . ,buf) temporary-buffers)
+ (when-let (wins (and (buffer-live-p buf)
+ (get-buffer-window-list buf)))
(push (cons file (mapcar
(lambda (win)
- (list (window-state-get win t) win))
+ (cons win (window-state-get win t)))
wins))
- live-files))
+ live-files)))
+ (pcase-dolist (`(,_ . ,buf) temporary-buffers)
(kill-buffer buf))
(setq temporary-buffers nil)
(pcase-dolist (`(,file . ,wins) live-files)
(when-let (buf (find-file-noselect file))
(push buf orig-buffers)
- (dolist (state wins)
- (apply #'window-state-put state))))))))
+ (pcase-dolist (`(,win . ,state) wins)
+ (setf (car (alist-get 'buffer state)) buf)
+ (window-state-put state win))))))))
(lambda (&optional name)
(if name
(let ((default-directory dir))
+ (setq name (abbreviate-file-name (expand-file-name name)))
+ (or
;; get-file-buffer is only a small optimization here. It
;; may not find the actual buffer, for directories it
;; returns nil instead of returning the Dired buffer.
- (or (get-file-buffer name)
- (when-let (buf (consult--find-file-temporarily name))
- ;; Only add new buffer if not already in the list
- (unless (or (memq buf temporary-buffers) (memq buf
orig-buffers))
- (add-hook 'window-selection-change-functions hook)
- (push buf temporary-buffers)
- (with-current-buffer buf
- (setq mode-name `("Preview:" ,mode-name)
- buffer-read-only t))
- ;; Only keep a few buffers alive
- (while (> (length temporary-buffers)
consult-preview-max-count)
- (kill-buffer (car (last temporary-buffers)))
- (setq temporary-buffers (nbutlast temporary-buffers))))
- buf)))
+ (get-file-buffer name)
+ (cdr (assoc name temporary-buffers))
+ (when-let (buf (consult--find-file-for-preview name))
+ ;; Only add new buffer if not already in the list
+ (unless (or (rassq buf temporary-buffers) (memq buf
orig-buffers))
+ (add-hook 'window-selection-change-functions hook)
+ (push (cons name buf) temporary-buffers)
+ (with-current-buffer buf
+ ;; Disassociate buffer from file by setting
+ ;; `buffer-file-name' to nil and rename the buffer.
+ ;; This lets us open an already previewed buffer with
+ ;; the Embark default action C-. RET. We cannot use
+ ;; (set-visited-file-name nil) since then the mode
+ ;; hooks will not run.
+ (rename-buffer
+ (format "Preview:%s"
+ (file-name-nondirectory (directory-file-name
name)))
+ 'unique)
+ (setq buffer-file-name nil
+ buffer-read-only t))
+ ;; Only keep a few buffers alive
+ (while (> (length temporary-buffers)
consult-preview-max-count)
+ (kill-buffer (cdar (last temporary-buffers)))
+ (setq temporary-buffers (nbutlast temporary-buffers))))
+ buf)))
(remove-hook 'window-selection-change-functions hook)
- (mapc #'kill-buffer temporary-buffers)))))
+ (pcase-dolist (`(,_ . ,buf) temporary-buffers)
+ (kill-buffer buf))
+ (setq temporary-buffers nil)))))
(defun consult--invisible-open-permanently ()
"Open overlays which hide the current line.
@@ -2283,16 +2298,23 @@ INHERIT-INPUT-METHOD, if non-nil the minibuffer
inherits the input method."
(defun consult--multi-lookup (sources _ candidates cand)
"Lookup CAND in CANDIDATES given SOURCES."
(unless (string-blank-p cand)
- (if (consult--tofu-p (aref cand (1- (length cand))))
- (cons (if-let (found (member cand candidates))
- (cdr (get-text-property 0 'multi-category (car found)))
- ;; Non-existing tofu'ed candidates can occur due an
- ;; interaction of Consult preview and Embark.
- ;; See https://github.com/oantolin/embark/issues/490.
- (substring cand 0 -1))
+ (if-let (found (member cand candidates))
+ (cons (cdr (get-text-property 0 'multi-category (car found)))
(consult--multi-source sources cand))
- ;; TODO return source with :new field, depending on current narrowing
- (list cand))))
+ (let* ((tofu (consult--tofu-p (aref cand (1- (length cand)))))
+ (source (if tofu
+ (consult--multi-source sources cand)
+ (aref sources 0))))
+ (cons (if tofu (substring cand 0 -1) cand)
+ `(:action
+ ,(or (plist-get source :new)
+ (apply-partially #'consult--multi-new-fallback
+ (plist-get source :category)))
+ ,@source))))))
+
+(defun consult--multi-new-fallback (cat cand)
+ "Fallback for new CAND of CAT for sources which do not specify a :new
function."
+ (message "Cannot create new %s `%s'" cat cand))
(defun consult--multi-candidates (sources)
"Return `consult--multi' candidates from SOURCES."
@@ -2390,7 +2412,8 @@ Optional source fields:
* :annotate - Annotation function called for each candidate, returns string.
* :history - Name of history variable to add selected candidate.
* :default - Must be t if the first item of the source is the default value.
-* :action - Action function called with the selected candidate.
+* :action - Function called with the selected candidate.
+* :new - Function called with new candidate name, only if :require-match is
nil.
* :state - State constructor for the source, must return the state function.
* Other source fields can be added specifically to the use case."
(let* ((sources (consult--multi-enabled-sources sources))
@@ -3389,10 +3412,10 @@ narrowing and the settings `consult-goto-line-numbers'
and
(defun consult--file-preview ()
"Create preview function for files."
- (let ((open (consult--temporary-files))
+ (let ((open (consult--previewed-files))
(preview (consult--buffer-preview)))
(lambda (action cand)
- (when (eq action 'exit)
+ (unless cand
(funcall open))
(funcall preview action
(and cand
@@ -3638,9 +3661,9 @@ There exists no equivalent of this command in Emacs 28."
(defun consult--bookmark-preview ()
"Create preview function for bookmarks."
(let ((preview (consult--jump-preview))
- (open (consult--temporary-files)))
+ (open (consult--previewed-files)))
(lambda (action cand)
- (when (eq action 'exit)
+ (unless cand
(funcall open))
(funcall
preview action
@@ -4199,8 +4222,14 @@ If NORECORD is non-nil, do not record the buffer switch
in the buffer list."
:face consult-file
:history file-name-history
:state ,#'consult--file-state
- :enabled ,(lambda () (and consult-project-function
- recentf-mode))
+ :new
+ ,(lambda (file)
+ (consult--file-action
+ (expand-file-name file (consult--project-root))))
+ :enabled
+ ,(lambda ()
+ (and consult-project-function
+ recentf-mode))
:items
,(lambda ()
(when-let (root (consult--project-root))
@@ -4239,6 +4268,7 @@ If NORECORD is non-nil, do not record the buffer switch
in the buffer list."
:face consult-buffer
:history buffer-name-history
:state ,#'consult--buffer-state
+ :new ,#'consult--buffer-action
:default t
:items
,(lambda () (consult--buffer-query :sort 'visibility
@@ -4252,6 +4282,7 @@ If NORECORD is non-nil, do not record the buffer switch
in the buffer list."
:face consult-file
:history file-name-history
:state ,#'consult--file-state
+ :new ,#'consult--file-action
:enabled ,(lambda () recentf-mode)
:items
,(lambda ()
@@ -4272,16 +4303,12 @@ keys. In order to determine the project-specific files
and buffers, the
default to `consult-buffer-sources'. See `consult--multi' for the
configuration of the virtual buffer sources."
(interactive)
- (when-let (buffer (consult--multi (or sources consult-buffer-sources)
- :require-match
- (confirm-nonexistent-file-or-buffer)
- :prompt "Switch to: "
- :history 'consult--buffer-history
- :sort nil))
- ;; When the buffer does not belong to a source,
- ;; create a new buffer with the name.
- (unless (cdr buffer)
- (consult--buffer-action (car buffer)))))
+ (consult--multi (or sources consult-buffer-sources)
+ :require-match
+ (confirm-nonexistent-file-or-buffer)
+ :prompt "Switch to: "
+ :history 'consult--buffer-history
+ :sort nil))
;; Populate `consult-project-buffer-sources'.
(setq consult-project-buffer-sources
@@ -4455,13 +4482,14 @@ FIND-FILE is the file open function, defaulting to
`find-file'."
(defun consult--grep-state ()
"Grep state function."
- (let ((open (consult--temporary-files))
+ (let ((open (consult--previewed-files))
(jump (consult--jump-state)))
(lambda (action cand)
- (when (eq action 'exit)
- (funcall open)
- (setq open nil))
- (funcall jump action (consult--grep-position cand open)))))
+ (unless cand
+ (funcall open))
+ (funcall jump action (consult--grep-position
+ cand
+ (and (not (eq action 'exit)) open))))))
(defun consult--grep-group (cand transform)
"Return title for CAND or TRANSFORM the candidate."