;; This buffer is for text that is not saved, and for Lisp evaluation. ;; To create a file, visit it with C-x C-f and enter text in its buffer. ;;; Excerpt of my configurations (add-to-list 'display-buffer-alist '("\\*\\(Embark\\)?.*Completions.*" (display-buffer-in-side-window) (side . bottom) (slot . 0) (window-parameters . ((no-other-window . t) (mode-line-format . none))))) (setq completion-show-help nil) (setq completion-auto-help t) (setq completions-format 'one-column) (setq completions-detailed nil) (file-name-shadow-mode 1) (setq prot-minibuffer-remove-shadowed-file-names t) (setq prot-minibuffer-minimum-input 3) (setq prot-minibuffer-live-update-delay 0.3) ;; ;; NOTE: `prot-minibuffer-completion-blocklist' can be used for ;; ;; commands with lots of candidates, depending also on how low ;; ;; `prot-minibuffer-minimum-input' is. With my current settings, ;; ;; this is not required, otherwise I would use this list: ;; ;; '( describe-symbol describe-function ;; describe-variable execute-extended-command ;; insert-char) (setq prot-minibuffer-completion-blocklist nil) (setq prot-minibuffer-completion-passlist nil) ;; This is for commands that should always pop up the completions' ;; buffer. It circumvents the default method of waiting for some user ;; input before displaying and updating the completions' buffer. (setq prot-minibuffer-completion-passlist nil) (define-key global-map (kbd "s-v") #'prot-minibuffer-focus-mini-or-completions) (let ((map completion-list-mode-map)) (define-key map (kbd "M-v") #'prot-minibuffer-focus-minibuffer) (define-key map (kbd "C-g") #'prot-minibuffer-keyboard-quit-dwim) (define-key map (kbd "C-n") #'prot-minibuffer-next-completion-or-mini) (define-key map (kbd "") #'prot-minibuffer-next-completion-or-mini) (define-key map (kbd "C-p") #'prot-minibuffer-previous-completion-or-mini) (define-key map (kbd "") #'prot-minibuffer-previous-completion-or-mini) (define-key map (kbd "") #'prot-minibuffer-choose-completion-exit) (define-key map (kbd "") #'prot-minibuffer-choose-completion-dwim)) (let ((map minibuffer-local-completion-map)) (define-key map (kbd "M-g") #'prot-minibuffer-choose-completion-number) (define-key map (kbd "C-n") #'prot-minibuffer-switch-to-completions-top) (define-key map (kbd "") #'prot-minibuffer-switch-to-completions-top) (define-key map (kbd "C-p") #'prot-minibuffer-switch-to-completions-bottom) (define-key map (kbd "") #'prot-minibuffer-switch-to-completions-bottom) (define-key map (kbd "C-l") #'prot-minibuffer-toggle-completions)) (add-hook 'completion-list-mode-hook #'prot-minibuffer-hl-line) (add-hook 'completion-list-mode-hook #'prot-minibuffer-display-line-numbers) ;;; Excerpt of prot-minibuffer.el (defcustom prot-minibuffer-completion-windows-regexp "\\*\\(Completions\\|Embark Collect \\(Live\\|Completions\\)\\)" "Regexp to match window names with completion candidates. Used by `prot-minibuffer--get-completion-window'." :group 'prot-minibuffer :type 'string) (defcustom prot-minibuffer-remove-shadowed-file-names nil "Delete shadowed parts of file names. For example, if the user types ~/ after a long path name, everything preceding the ~/ is removed so the interactive selection process starts again from the user's $HOME. Only works when variable `file-name-shadow-mode' is non-nil." :type 'boolean :group 'prot-minibuffer) (defcustom prot-minibuffer-minimum-input 3 "Live update completions when input is >= N. Setting this to a value greater than 1 can help reduce the total number of candidates that are being computed." :type 'integer :group 'prot-minibuffer) (defcustom prot-minibuffer-live-update-delay 0.3 "Delay in seconds before updating the Completions' buffer. Set this to 0 to disable the delay." :type 'number :group 'prot-minibuffer) (defcustom prot-minibuffer-completion-blocklist nil "Commands that do not do live updating of completions. A less drastic measure is to set `prot-minibuffer-minimum-input' to an appropriate value. The Completions' buffer can still be accessed with commands that put it in a window (e.g. `prot-minibuffer-toggle-completions', `prot-minibuffer-switch-to-completions-top')." :type '(repeat symbol) :group 'prot-minibuffer) (defcustom prot-minibuffer-completion-passlist nil "Commands that do live updating of completions from the start. This means that they ignore `prot-minibuffer-minimum-input' and the inherent constraint of updating the Completions' buffer only upon user input. Furthermore, they also bypass any possible delay introduced by `prot-minibuffer-live-update-delay'." :type '(repeat symbol) :group 'prot-minibuffer) ;; Thanks to Omar Antolín Camarena for providing the messageless and ;; stealthily. Source: . (defun prot-minibuffer--messageless (fn &rest args) "Set `minibuffer-message-timeout' to 0. Meant as advice around minibuffer completion FN with ARGS." (let ((minibuffer-message-timeout 0)) (apply fn args))) (dolist (fn '(exit-minibuffer choose-completion minibuffer-force-complete minibuffer-complete-and-exit minibuffer-force-complete-and-exit)) (advice-add fn :around #'prot-minibuffer--messageless)) ;; Copied from Daniel Mendler's `vertico' library: ;; . (defun prot-minibuffer--crm-indicator (args) "Add prompt indicator to `completing-read-multiple' filter ARGS." (cons (concat "[CRM] " (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'prot-minibuffer--crm-indicator) ;; Adapted from Omar Antolín Camarena's live-completions library: ;; . (defun prot-minibuffer--honor-inhibit-message (fn &rest args) "Skip applying FN to ARGS if `inhibit-message' is t. Meant as `:around' advice for `minibuffer-message', which does not honor minibuffer message." (unless inhibit-message (apply fn args))) (advice-add #'minibuffer-message :around #'prot-minibuffer--honor-inhibit-message) ;; Note that this solves bug#45686 and is only considered a temporary ;; measure: (defun prot-minibuffer--stealthily (fn &rest args) "Prevent minibuffer default from counting as a modification. Meant as advice for FN `minibuf-eldef-setup-minibuffer' with rest ARGS." (let ((inhibit-modification-hooks t)) (apply fn args))) (advice-add 'minibuf-eldef-setup-minibuffer :around #'prot-minibuffer--stealthily) ;; Copied from icomplete.el (defun prot-minibuffer--field-beg () "Determine beginning of completion." (if (window-minibuffer-p) (minibuffer-prompt-end) (nth 0 completion-in-region--data))) ;; Copied from icomplete.el (defun prot-minibuffer--field-end () "Determine end of completion." (if (window-minibuffer-p) (point-max) (nth 1 completion-in-region--data))) ;; Copied from icomplete.el (defun prot-minibuffer--completion-category () "Return completion category." (let* ((beg (prot-minibuffer--field-beg)) (md (completion--field-metadata beg))) (alist-get 'category (cdr md)))) ;; Adapted from icomplete.el (defun prot-minibuffer--shadow-filenames (&rest _) "Hide shadowed file names." (let ((saved-point (point))) (when (and prot-minibuffer-remove-shadowed-file-names (eq (prot-minibuffer--completion-category) 'file) rfn-eshadow-overlay (overlay-buffer rfn-eshadow-overlay) (eq this-command 'self-insert-command) (= saved-point (prot-minibuffer--field-end)) (or (>= (- (point) (overlay-end rfn-eshadow-overlay)) 2) (eq ?/ (char-before (- (point) 2))))) (delete-region (overlay-start rfn-eshadow-overlay) (overlay-end rfn-eshadow-overlay))))) (defun prot-minibuffer--setup-shadow-files () "Set up shadowed file name deletion. To be assigned to `minibuffer-setup-hook'." (add-hook 'after-change-functions #'prot-minibuffer--shadow-filenames nil t)) (add-hook 'minibuffer-setup-hook #'prot-minibuffer--setup-shadow-files) ;;;###autoload (defun prot-minibuffer-focus-minibuffer () "Focus the active minibuffer." (interactive) (let ((mini (active-minibuffer-window))) (when mini (select-window mini)))) (defun prot-minibuffer--get-completion-window () "Find a live window showing completion candidates." (get-window-with-predicate (lambda (window) (string-match-p prot-minibuffer-completion-windows-regexp (format "%s" window))))) (defun prot-minibuffer-focus-mini-or-completions () "Focus the active minibuffer or the completions' window. If both the minibuffer and the Completions are present, this command will first move per invocation to the former, then the latter, and then continue to switch between the two. The continuous switch is essentially the same as running `prot-minibuffer-focus-minibuffer' and `switch-to-completions' in succession. What constitutes a completions' window is ultimately determined by `prot-minibuffer-completion-windows-regexp'." (interactive) (let* ((mini (active-minibuffer-window)) (completions (prot-minibuffer--get-completion-window))) (cond ((and mini (not (minibufferp))) (select-window mini nil)) ((and completions (not (eq (selected-window) completions))) (select-window completions nil))))) (defface prot-minibuffer-hl-line '((default :extend t) (((class color) (min-colors 88) (background light)) :background "#b0d8ff" :foreground "#000000") (((class color) (min-colors 88) (background dark)) :background "#103265" :foreground "#ffffff") (t :inherit (font-lock-string-face elfeed-search-title-face))) "Face for current line in the completions' buffer." :group 'prot-minibuffer) (defface prot-minibuffer-line-number '((default :inherit default) (((class color) (min-colors 88) (background light)) :background "#f2eff3" :foreground "#252525") (((class color) (min-colors 88) (background dark)) :background "#151823" :foreground "#dddddd") (t :inverse-video t)) "Face for line numbers in the completions' buffer." :group 'prot-minibuffer) (defface prot-minibuffer-line-number-current-line '((default :inherit default) (((class color) (min-colors 88) (background light)) :background "#8ac7ff" :foreground "#000000") (((class color) (min-colors 88) (background dark)) :background "#142a79" :foreground "#ffffff") (t :inverse-video t)) "Face for current line number in the completions' buffer." :group 'prot-minibuffer) (autoload 'display-line-numbers-mode "display-line-numbers") (autoload 'face-remap-remove-relative "face-remap") ;;;###autoload (defun prot-minibuffer-display-line-numbers () "Set up line numbers for the completions' buffer. Add this to `completion-list-mode-hook'." (when (derived-mode-p 'completion-list-mode) (face-remap-add-relative 'line-number 'prot-minibuffer-line-number) (face-remap-add-relative 'line-number-current-line 'prot-minibuffer-line-number-current-line) (display-line-numbers-mode 1))) ;;;###autoload (defun prot-minibuffer-hl-line () "Set up line highlighting for the completions' buffer. Add this to `completion-list-mode-hook'." (when (derived-mode-p 'completion-list-mode) (face-remap-add-relative 'hl-line 'prot-minibuffer-hl-line) (hl-line-mode 1))) ;; Thanks to Omar Antolín Camarena for recommending the use of ;; `cursor-sensor-functions' and the concomitant hook with ;; `cursor-censor-mode' instead of the dirty hacks I had before to ;; prevent the cursor from moving to that position where no completion ;; candidates could be found at point (e.g. it would break `embark-act' ;; as it could not read the topmost candidate when point was at the ;; beginning of the line, unless the point was moved forward). (defun prot-minibuffer--clean-completions () "Keep only completion candidates in the Completions." (with-current-buffer standard-output (let ((inhibit-read-only t)) (goto-char (point-min)) (delete-region (point-at-bol) (1+ (point-at-eol))) (insert (propertize " " 'cursor-sensor-functions (list (lambda (_win prev dir) (when (eq dir 'entered) (goto-char prev)))))) (put-text-property (point-min) (point) 'invisible t)))) (add-hook 'completion-list-mode-hook #'cursor-sensor-mode) (add-hook 'completion-setup-hook #'prot-minibuffer--clean-completions) (defun prot-minibuffer--fit-completions-window () "Fit Completions' buffer to its window." (fit-window-to-buffer (get-buffer-window "*Completions*") (floor (frame-height) 2) 1)) (defun prot-minibuffer--input-string () "Return the contents of the minibuffer as a string." (buffer-substring-no-properties (minibuffer-prompt-end) (point-max))) (defun prot-minibuffer--minimum-input () "Test for minimum requisite input for live completions." (>= (length (prot-minibuffer--input-string)) prot-minibuffer-minimum-input)) ;; Adapted from Omar Antolín Camarena's live-completions library: ;; . (defun prot-minibuffer--live-completions (&rest _) "Update the *Completions* buffer. Meant to be added to `after-change-functions'." (when (minibufferp) ; skip if we've exited already (let ((while-no-input-ignore-events '(selection-request))) (while-no-input (if (prot-minibuffer--minimum-input) (condition-case nil (save-match-data (save-excursion (goto-char (point-max)) (let ((inhibit-message t) ;; don't ring the bell in `minibuffer-completion-help' ;; when <= 1 completion exists. (ring-bell-function #'ignore)) (minibuffer-completion-help) (prot-minibuffer--fit-completions-window)))) (quit (abort-recursive-edit))) (minibuffer-hide-completions)))))) (defun prot-minibuffer--live-completions-timer (&rest _) "Update Completions with `prot-minibuffer-live-update-delay'." (let ((delay prot-minibuffer-live-update-delay)) (when (>= delay 0) (run-with-idle-timer delay nil #'prot-minibuffer--live-completions)))) (defun prot-minibuffer--setup-completions () "Set up the completions buffer." (cond ((member this-command prot-minibuffer-completion-passlist) (minibuffer-completion-help) (add-hook 'after-change-functions #'prot-minibuffer--live-completions nil t)) ((unless (member this-command prot-minibuffer-completion-blocklist) (add-hook 'after-change-functions #'prot-minibuffer--live-completions-timer nil t))))) (add-hook 'minibuffer-setup-hook #'prot-minibuffer--setup-completions) ;;;###autoload (defun prot-minibuffer-toggle-completions () "Toggle the presentation of the completions' buffer." (interactive) (if (get-buffer-window "*Completions*" 0) (minibuffer-hide-completions) (minibuffer-completion-help))) ;;;###autoload (defun prot-minibuffer-keyboard-quit-dwim () "Control the exit behaviour for completions' buffers. If in a completions' buffer and unless the region is active, run `abort-recursive-edit'. Otherwise run `keyboard-quit'. If the region is active, deactivate it. A second invocation of this command is then required to abort the session." (interactive) (when (derived-mode-p 'completion-list-mode) (if (use-region-p) (keyboard-quit) (abort-recursive-edit)))) (defun prot-minibuffer--switch-to-completions () "Subroutine for switching to the completions' buffer." (unless (get-buffer-window "*Completions*" 0) (minibuffer-completion-help)) (switch-to-completions) (prot-minibuffer--fit-completions-window)) ;;;###autoload (defun prot-minibuffer-switch-to-completions-top () "Switch to the top of the completions' buffer. Meant to be bound in `minibuffer-local-completion-map'." (interactive) (prot-minibuffer--switch-to-completions) (goto-char (point-min)) (next-completion 1)) ;;;###autoload (defun prot-minibuffer-switch-to-completions-bottom () "Switch to the bottom of the completions' buffer. Meant to be bound in `minibuffer-local-completion-map'." (interactive) (prot-minibuffer--switch-to-completions) (goto-char (point-max)) (next-completion -1) (goto-char (point-at-bol)) (recenter (- -1 (min (max 0 scroll-margin) (truncate (/ (window-body-height) 4.0)))) t)) ;;;###autoload (defun prot-minibuffer-next-completion-or-mini (&optional arg) "Move to the next completion or switch to the minibuffer. This performs a regular motion for optional ARG lines, but when point can no longer move in that direction it switches to the minibuffer." (interactive "p") (if (or (eobp) (eq (point-max) (save-excursion (forward-line 1) (point)))) (prot-minibuffer-focus-minibuffer) (next-completion (or arg 1))) (setq this-command 'next-line)) ;;;###autoload (defun prot-minibuffer-previous-completion-or-mini (&optional arg) "Move to the next completion or switch to the minibuffer. This performs a regular motion for optional ARG lines, but when point can no longer move in that direction it switches to the minibuffer." (interactive "p") (let ((num (* -1 arg))) (if (or (bobp) (eq (point) (1+ (point-min)))) ; see hack in `prot-minibuffer--clean-completions' (prot-minibuffer-focus-minibuffer) (next-completion (or num 1))))) ;;;###autoload (defun prot-minibuffer-choose-completion-exit () "Run `choose-completion' in the Completions buffer and exit." (interactive) (when (and (derived-mode-p 'completion-list-mode) (active-minibuffer-window)) (choose-completion) (minibuffer-force-complete-and-exit))) (defun prot-minibuffer--goto-line (n &optional args) "Go to line N in the Completions' with optional ARGS." (let ((bounds (count-lines (point-min) (point-max)))) (if (<= n bounds) (progn `(,@args) (goto-char (point-min)) (forward-line (1- n)) (choose-completion)) (user-error "%d is not within Completions' buffer bounds (%d)" n bounds)))) ;;;###autoload (defun prot-minibuffer-choose-completion-number (n) "Select completion candidate on line number N with prefix arg. The idea is to pass a prefix numeric argument that refers to a line number in the Completions' buffer." (interactive "p") (if current-prefix-arg (cond ((and (derived-mode-p 'completion-list-mode) (active-minibuffer-window)) (prot-minibuffer--goto-line n)) ((and (minibufferp) (prot-minibuffer--get-completion-window)) (prot-minibuffer--goto-line n (select-window (prot-minibuffer--get-completion-window)))) (t (user-error "Only use this inside the minibuffer of the Completions"))) (user-error "Pass a numeric argument first"))) (defvar crm-completion-table) ;;;###autoload (defun prot-minibuffer-choose-completion-dwim () "Append to minibuffer when at `completing-read-multiple' prompt. Otherwise behave like `prot-minibuffer-choose-completion-exit'." (interactive) (when (and (derived-mode-p 'completion-list-mode) (active-minibuffer-window)) (choose-completion) (with-current-buffer (window-buffer (active-minibuffer-window)) (unless (eq (prot-minibuffer--completion-category) 'file) (minibuffer-force-complete)) (when crm-completion-table ;; FIXME 2021-04-02: assumes the `crm-separator' as constant. ;; UPDATE 2021-04-22: actually `crm-default-separator' is a ;; defconst, so I am leaving this here just in case I ever need ;; it. We will have a problem if some command let-binds its own ;; value, but it is not our fault here... (insert ",") (let ((inhibit-message t)) (switch-to-completions))))))