emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNU ELPA] New package proposal: aggressive-completion.el


From: Tassilo Horn
Subject: [GNU ELPA] New package proposal: aggressive-completion.el
Date: Sat, 03 Apr 2021 09:53:50 +0200
User-agent: mu4e 1.5.11; emacs 28.0.50

Hi all,

I'd like to propose the attached aggressive-completion.el as a new GNU
ELPA package.  I've used this since several months and now had the time
to extract it from my ~/.emacs and make a proper minor-mode out of it.

What is it?
===========

I've used several different minibuffer completion frameworks in the past
(including ivy, raven, and selectrum) in the past but always came back
to the standard emacs minibuffer completion with its nice configuration
means in terms of `completion-category-overrides' and friends.

What I've liked with the other frameworks, however, was that the
completion candidates are immediately visible and in many scenarios I
needed less typing (especially less pinky-stressing TAB-ing).

So the central idea of aggressive-completion.el is that it

  1) automatically completes for you after a short delay, and it
  2) always shows the completion help (unless there are too many).

Without further ado, here it is (comments welcome):

;;; aggressive-completion.el --- Automatic minibuffer completion -*- 
lexical-binding: t -*-

;; Copyright (C) 2021 Free Software Foundation, Inc.

;; Author: Tassilo Horn <tsdh@gnu.org>
;; Maintainer: Tassilo Horn <tsdh@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.

(defgroup aggressive-completion nil
  "Aggressive completion completes for you.")

(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.")

(defvar aggressive-completion--timer nil)

(defun aggressive-completion--do ()
  (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-start ()
  (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 ()
  "Toggles automatic completion."
  (interactive)
  (setq aggressive-completion-auto-complete
        (not aggressive-completion-auto-complete)))

(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-start nil t)))

;; 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.")

(define-minor-mode aggressive-completion-mode
  "Perform aggressive minibuffer completion."
  :lighter "ACmp"
  (if aggressive-completion-mode
      (add-hook 'minibuffer-setup-hook #'aggressive-completion--setup)
    (remove-hook 'minibuffer-setup-hook #'aggressive-completion--setup)))
Bye,
Tassilo

reply via email to

[Prev in Thread] Current Thread [Next in Thread]