branch: externals/aggressive-completion commit 4a6065a29b190b43894112a0bac0f08af2bb2f32 Author: Tassilo Horn <t...@gnu.org> Commit: Tassilo Horn <t...@gnu.org>
Add new package aggressive-completion.el --- aggressive-completion.el | 181 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/aggressive-completion.el b/aggressive-completion.el new file mode 100644 index 0000000..d0f593a --- /dev/null +++ b/aggressive-completion.el @@ -0,0 +1,181 @@ +;;; aggressive-completion.el --- Automatic minibuffer completion -*- lexical-binding: t -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc. + +;; Author: Tassilo Horn <t...@gnu.org> +;; Maintainer: Tassilo Horn <t...@gnu.org> +;; Keywords: minibuffer completion +;; Package-Requires: ((emacs "27.1")) +;; Version: 1.0 + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; Aggressive completion mode (`aggressive-completion-mode') is a minor mode +;; which automatically completes for you after a short delay +;; (`aggressive-completion-delay') and always shows all possible completions +;; using the standard completion help (unless the number of possible +;; completions exceeds `aggressive-completion-max-shown-completions'). +;; +;; Automatic completion is temporarily disabled after all commands in +;; `aggressive-completion-no-complete-commands'. Basically all deletion/kill +;; commands are listed here in order not to complete back to the thing you just +;; deleted. +;; +;; Aggressive completion can be toggled using +;; `aggressive-completion-toggle-auto-complete' (bound to `M-t' by default) +;; which is especially useful when trying to find a not yet existing file or +;; switch to a new buffer. +;; +;; You can switch from minibuffer to *Completions* buffer and back again using +;; `aggressive-completion-switch-to-completions' (bound to `M-c' by default). +;; All keys bound to this command in `aggressive-completion-minibuffer-map' +;; will be bound to `other-window' in `completion-list-mode-map' so that those +;; keys act as switch-back-and-forth commands. + +;;; Code: + +(eval-when-compile + ;; For `when-let'. + (require 'subr-x)) + +(defgroup aggressive-completion nil + "Aggressive completion completes for you." + :group 'completion) + +(defcustom aggressive-completion-delay 0.3 + "Delay in seconds before aggressive completion kicks in." + :type 'number) + +(defcustom aggressive-completion-auto-complete t + "Complete automatically if non-nil. +If nil, only show the completion help." + :type 'boolean) + +(defcustom aggressive-completion-max-shown-completions 1000 + "Maximum number of possible completions for showing completion help." + :type 'integer) + +(defcustom aggressive-completion-no-complete-commands + '( left-char icomplete-fido-backward-updir minibuffer-complete + right-char delete-backward-char backward-kill-word + backward-kill-paragraph backward-kill-sentence backward-kill-sexp + delete-char kill-word kill-line completion-at-point) + "Commands after which automatic completion is not performed." + :type '(repeat command)) + +(defvar aggressive-completion--timer nil) + +(defun aggressive-completion--do () + "Perform aggressive completion." + (when (window-minibuffer-p) + (let* ((completions (completion-all-sorted-completions)) + ;; Don't ding if there are no completions, etc. + (visible-bell nil) + (ring-bell-function #'ignore) + ;; Automatic completion should not cycle. + (completion-cycle-threshold nil) + (completion-cycling nil)) + (let ((i 0)) + (while (and (<= i aggressive-completion-max-shown-completions) + (consp completions)) + (setq completions (cdr completions)) + (cl-incf i)) + (if (and (> i 0) + (< i aggressive-completion-max-shown-completions)) + (if (or (null aggressive-completion-auto-complete) + (memq last-command + aggressive-completion-no-complete-commands)) + ;; This ensures we still can repeatedly hit TAB to scroll + ;; through the list of completions. + (unless (and (= last-command-event ?\t) + (window-live-p + (get-buffer-window "*Completions*")) + (with-current-buffer "*Completions*" + (> (point) (point-min)))) + (minibuffer-completion-help)) + (minibuffer-complete) + (unless (window-live-p (get-buffer-window "*Completions*")) + (minibuffer-completion-help))) + ;; Close the *Completions* buffer if there are too many + ;; or zero completions. + (when-let ((win (get-buffer-window "*Completions*"))) + (when (and (window-live-p win) + (not (memq last-command + '(minibuffer-completion-help + minibuffer-complete + completion-at-point)))) + (quit-window nil win)))))))) + +(defun aggressive-completion--timer-restart () + "Restart `aggressive-completion--timer'." + (when aggressive-completion--timer + (cancel-timer aggressive-completion--timer)) + + (setq aggressive-completion--timer + (run-with-idle-timer aggressive-completion-delay nil + #'aggressive-completion--do))) + +(defun aggressive-completion-toggle-auto-complete () + "Toggle automatic completion." + (interactive) + (setq aggressive-completion-auto-complete + (not aggressive-completion-auto-complete))) + +;; Add an alias so that we can find out the bound key using +;; `where-is-internal'. +(defalias 'aggressive-completion-switch-to-completions + #'switch-to-completions) + +(defvar aggressive-completion-minibuffer-map + (let ((map (make-sparse-keymap))) + (require 'icomplete) + (define-key map (kbd "DEL") #'icomplete-fido-backward-updir) + (define-key map (kbd "M-t") #'aggressive-completion-toggle-auto-complete) + (define-key map (kbd "M-c") #'aggressive-completion-switch-to-completions) + map) + "The local minibuffer keymap when `aggressive-completion-mode' is enabled.") + +(defun aggressive-completion--setup () + "Setup aggressive completion." + (when (and (not executing-kbd-macro) + (window-minibuffer-p) + minibuffer-completion-table) + (set-keymap-parent aggressive-completion-minibuffer-map (current-local-map)) + (use-local-map aggressive-completion-minibuffer-map) + + ;; If `aggressive-completion-switch-to-completions' is bound to keys, bind + ;; the same keys in `completion-list-mode-map' to `other-window' so that + ;; one can conveniently switch back and forth using the same key. + (dolist (key (where-is-internal + #'aggressive-completion-switch-to-completions)) + (define-key completion-list-mode-map key #'other-window)) + + (add-hook 'post-command-hook + #'aggressive-completion--timer-restart nil t))) + +(define-minor-mode aggressive-completion-mode + "Perform aggressive minibuffer completion." + :lighter " ACmp" + :global t + (if aggressive-completion-mode + (add-hook 'minibuffer-setup-hook #'aggressive-completion--setup) + (remove-hook 'minibuffer-setup-hook #'aggressive-completion--setup))) + +(provide 'aggressive-completion) + +;;; aggressive-completion.el ends here