;;; corfu-auto.el --- Auto completion -*- lexical-binding: t -*-

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

;; Author: Daniel Mendler <mail@daniel-mendler.de>
;; Maintainer: Daniel Mendler <mail@daniel-mendler.de>
;; Created: 2022

;; This file is part of GNU Emacs.

;; This program 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.

;; This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Automatically show the popup if `corfu-auto' is non-nil.

;;; Code:

(require 'corfu)

(defcustom corfu-auto-trigger ""
  "Characters which trigger auto completion.
If a trigger character is detected `corfu-auto-prefix' is ignored."
  :type 'string
  :group 'corfu)

(defcustom corfu-auto-prefix 3
  "Minimum length of prefix for auto completion.
The completion backend can override this with :company-prefix-length.
It is not recommended to use a small prefix length (below 2), since this
will create high load for Emacs.  See also `corfu-auto-delay' and
`corfu-auto-trigger'."
  :type 'natnum
  :group 'corfu)

(defcustom corfu-auto-delay 0.2
  "Delay for auto completion.
It is not recommended to use a short delay or even 0, since this will
create high load for Emacs, in particular if executing the completion
backend is costly.  Instead of reducing the delay too much, try
`corfu-auto-trigger' to trigger immediate completion after certain
characters."
  :type 'float
  :group 'corfu)

(defcustom corfu-auto-commands
  '("self-insert-command\\'" "delete-backward-char\\'" "\\`backward-delete-char"
    c-electric-colon c-electric-lt-gt c-electric-slash c-scope-operator)
  "Commands which initiate auto completion.
The list can contain either command symbols or regular expressions."
  :type '(repeat (choice regexp symbol))
  :group 'corfu)

(defvar corfu-auto--timer (timer-create)
  "Auto completion timer.")

(defun corfu-auto--complete-deferred (&optional tick)
  "Initiate auto completion if TICK did not change."
  (corfu--protect
   (lambda ()
     (when (and (not completion-in-region-mode)
                (or (not tick) (equal tick (corfu-auto--tick))))
       (pcase (while-no-input ;; Interruptible Capf query
                (run-hook-wrapped
                 'completion-at-point-functions
                 #'corfu--capf-wrapper
                 (unless (seq-contains-p corfu-auto-trigger last-command-event)
                   corfu-auto-prefix)))
         (`(,fun ,beg ,end ,table . ,plist)
          (pcase-let* ((completion-in-region-mode-predicate
                        (lambda ()
                          (when-let* ((newbeg (car-safe (funcall fun))))
                            (= newbeg beg))))
                       (completion-extra-properties plist)
                       (pred (plist-get plist :predicate))
                       (state (plist-get plist :corfu--state))
                       (`(,str . ,pt) (alist-get 'corfu--input state))
                       (cands (alist-get 'corfu--candidates state)))
            ;; Bail out immediately for exactly matching candidates
            ;; if no further completion is possible.
            (when (or (eq corfu-on-exact-match 'show)
                      (not (equal cands (list str)))
                      (consp (corfu--try-completion str table pred pt)))
              (corfu--setup beg end table pred)
              (corfu--exhibit)))))))))

(defun corfu-auto--post-command ()
  "Post command hook which initiates auto completion."
  (corfu--protect
   (lambda ()
     (cancel-timer corfu-auto--timer)
     (when (and (not completion-in-region-mode)
                (not defining-kbd-macro)
                (not buffer-read-only)
                (corfu--match-symbol-p corfu-auto-commands this-command)
                (corfu--popup-support-p))
       (if (or (<= corfu-auto-delay 0)
               (seq-contains-p corfu-auto-trigger last-command-event))
           (corfu-auto--complete-deferred)
         ;; Do not use `timer-set-idle-time' since this leads to
         ;; unpredictable pauses, in particular with `flyspell-mode'.
         (timer-set-time corfu-auto--timer
                         (timer-relative-time nil corfu-auto-delay))
         (timer-set-function corfu-auto--timer #'corfu-auto--complete-deferred
                             (list (corfu-auto--tick)))
         (timer-activate corfu-auto--timer))))))

(defun corfu-auto--tick ()
  "Return the current tick/status of the buffer.
Auto completion is only performed if the tick did not change."
  (list (selected-window) (current-buffer) (buffer-chars-modified-tick) (point)))

(provide 'corfu-auto)
;;; corfu-auto.el ends here
