branch: externals/mct commit f6610dd69b9efc91aaebaf9e6105c88faf7f92cf Author: Protesilaos Stavrou <i...@protesilaos.com> Commit: Protesilaos Stavrou <i...@protesilaos.com>
Implement dynamic completion setup --- README.org | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ mct.el | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/README.org b/README.org index 5809747b0b..21bb0cfc25 100644 --- a/README.org +++ b/README.org @@ -359,6 +359,63 @@ If nil, do not try to fit the Completions' buffer to its window. [[#h:1a85ed4c-f54d-482b-9915-563f60c64f15][Live completion]]. +** Dynamic completion tables in mct-minibuffer-mode +:PROPERTIES: +:CUSTOM_ID: h:28dc0355-c461-4e3a-bc3a-479d67827cac +:END: +#+cindex: Persistent dynamic completion in the minibuffer +#+vindex: mct-persist-dynamic-completion + +[ Part of {{{development-version}}}. ] + +Brief: Whether to keep dynamic completion live. + +Symbol: ~mct-persist-dynamic-completion~ (=boolean= type) + +Possible values: + +1. ~nil~ +2. ~t~ (default) + +Without any intervention from MCT, the default Emacs behavior for +commands such as ~find-file~ or for a ~file~ completion category is to hide +the =*Completions*= buffer after updating the list of candidates in a +non-exiting fashion (e.g. select a directory and expect to continue +typing the path). This, however, runs contrary to the interaction model +of MCT when it performs live completions, because the user expects the +Completions' buffer to remain visible while typing out the path to the +file ([[#h:1a85ed4c-f54d-482b-9915-563f60c64f15][Live completion]]). + +When this user option is non-nil (the default) it makes all non-exiting +commands keep the =*Completions*= visible when updating the list of +candidates. + +This applies to prompts in the ~file~ completion category whenever the +user selects a candidate with ~mct-choose-completion-no-exit~, +~mct-edit-completion~, ~minibuffer-complete~, ~minibuffer-force-complete~ +(i.e. any command that does not exit the minibuffer). + +[[#h:bb445062-2e39-4082-a868-2123bfb793cc][Selecting candidates with mct-minibuffer-mode]]. + +The two exceptions are (i) when the current completion +session runs a command or category that is blocked by the +~mct-completion-blocklist~ or (ii) the user option ~mct-live-completion~ is +nil. + +[[#h:36f56245-281a-4389-a998-66778de100db][Blocklist for commands or completion categories]]. + +The underlying rationale: + +Most completion commands present a flat list of candidates to choose +from. Picking a candidate concludes the session. Some prompts, +however, can recalculate the list of completions based on the selected +candidate. A case in point is ~find-file~ (or any command with the ~file~ +completion category) which dynamically adjusts the completions to show +only the elements which extend the given file system path. We call such +cases "dynamic completion". Due to their particular nature, these need +to be handled explicitly. The present user option is provided primarily +to raise awareness about this state of affairs. + ** Hide the Completions mode line :PROPERTIES: :CUSTOM_ID: h:36adcbbb-f534-4595-9629-babe38a35efc @@ -790,6 +847,7 @@ And with more options: (setq mct-minimum-input 3) (setq mct-live-completion t) (setq mct-live-update-delay 0.6) +(setq mct-persist-dynamic-completion t) (setq mct-completions-format 'one-column) ;; This is for commands or completion categories that should always pop diff --git a/mct.el b/mct.el index 2c937f7585..e072d18783 100644 --- a/mct.el +++ b/mct.el @@ -215,6 +215,49 @@ See `completions-format' for possible values." (make-obsolete 'mct-region-completions-format 'mct-completions-format "0.5.0") +(defcustom mct-persist-dynamic-completion t + "When non-nil, keep dynamic completion live. + +Without any intervention from MCT, the default Emacs behavior for +commands such as `find-file' or for a `file' completion category +is to hide the `*Completions*' buffer after updating the list of +candidates in a non-exiting fashion (e.g. select a directory and +expect to continue typing the path). This, however, runs +contrary to the interaction model of MCT when it performs live +completions, because the user expects the Completions' buffer to +remain visible while typing out the path to the file. + +When this user option is non-nil (the default) it makes all +non-exiting commands keep the `*Completions*' visible when +updating the list of candidates. + +This applies to prompts in the `file' completion category +whenever the user selects a candidate with +`mct-choose-completion-no-exit', `mct-edit-completion', +`minibuffer-complete', `minibuffer-force-complete' (i.e. any +command that does not exit the minibuffer). + +The two exceptions are (i) when the current completion session +runs a command or category that is blocked by the +`mct-completion-blocklist' or (ii) the user option +`mct-live-completion' is nil. + +The underlying rationale: + +Most completion commands present a flat list of candidates to +choose from. Picking a candidate concludes the session. Some +prompts, however, can recalculate the list of completions based +on the selected candidate. A case in point is `find-file' (or +any command with the `file' completion category) which +dynamically adjusts the completions to show only the elements +which extend the given file system path. We call such cases +\"dynamic completion\". Due to their particular nature, these +need to be handled explicitly. The present user option is +provided primarily to raise awareness about this state of +affairs." + :type 'boolean + :group 'mct) + ;;;; Completion metadata (defun mct--this-command () @@ -248,6 +291,14 @@ See `completions-format' for possible values." (or (memq (mct--this-command) mct-completion-blocklist) (memq (mct--completion-category) mct-completion-blocklist))) +;; Normally we would also include `imenu', but it has its own defcustom +;; for popping up the Completions eagerly... Let's not interfere with +;; that. +;; +;; See bug#52389: <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=52389>. +(defvar mct--dynamic-completion-categories '(file) + "Completion categories that perform dynamic completion.") + ;;;; Basics of intersection between minibuffer and Completions' buffer (define-obsolete-variable-alias @@ -1160,6 +1211,28 @@ region.") (mct--setup-line-numbers) (cursor-sensor-mode))) +;;;;; Dynamic completion + +;; TODO 2022-01-29: Research how things work for relevant cases in +;; completion-in-region and adapt accordingly. +(defun mct--persist-dynamic-completion (&rest _) + "Persist completion, per `mct-persist-dynamic-completion'." + (when (and (not (mct--symbol-in-list mct-completion-blocklist)) + mct-persist-dynamic-completion + (memq (mct--completion-category) mct--dynamic-completion-categories) + mct-live-completion) + (mct-focus-minibuffer) + (mct--show-completions))) + +(defun mct--setup-dynamic-completion-persist () + "Set up `mct-persist-dynamic-completion'." + (let ((commands '(choose-completion minibuffer-complete minibuffer-force-complete))) + (if mct-minibuffer-mode + (dolist (fn commands) + (advice-add fn :after #'mct--persist-dynamic-completion)) + (dolist (fn commands) + (advice-remove fn #'mct--persist-dynamic-completion))))) + ;;;;; mct-minibuffer-mode declaration (declare-function minibuf-eldef-setup-minibuffer "minibuf-eldef") @@ -1183,6 +1256,7 @@ region.") (advice-remove #'minibuffer-completion-help #'mct--minibuffer-completion-help-advice) (advice-remove #'completing-read-multiple #'mct--crm-indicator) (advice-remove #'minibuf-eldef-setup-minibuffer #'mct--stealthily)) + (mct--setup-dynamic-completion-persist) (mct--setup-shared)) (define-obsolete-function-alias 'mct-mode 'mct-minibuffer-mode "0.4.0")