branch: externals/consult commit e5f4f5cde390d7f5aaa6e4558d7cf7755aec0e54 Author: Daniel Mendler <m...@daniel-mendler.de> Commit: Daniel Mendler <m...@daniel-mendler.de>
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."