;;; corfu-prescient.el --- prescient.el + Corfu -*- lexical-binding: t -*-
;; Copyright (C) 2022 Radian LLC and contributors
;; Author: Radian LLC
;; Homepage: https://github.com/radian-software/prescient.el
;; Keywords: extensions
;; Created: 23 Sep 2022
;; Package-Requires: ((emacs "27.1") (prescient "6.1.0") (corfu "1.1"))
;; SPDX-License-Identifier: MIT
;; Version: 6.3.1
;;; Commentary:
;; corfu-prescient.el provides an interface for using prescient.el
;; to sort and filter candidates in Corfu menus. To enable its
;; functionality, turn on `corfu-prescient-mode' in your init-file
;; or interactively.
;; For more information, see https://github.com/radian-software/prescient.el.
;;; Code:
;;;; Libraries and Declarations
(require 'cl-lib)
(require 'corfu)
(require 'prescient)
(require 'subr-x)
;; Remove references to `corfu--state-vars' once the next stable
;; version of Corfu is released:
(defvar corfu--state-vars)
(defvar corfu--initial-state)
;;;; Customization
(defgroup corfu-prescient nil
"Prescient adapter for Corfu."
:group 'convenience
:prefix "corfu-prescient"
:link '(url-link "https://github.com/radian-software/prescient.el"))
(defcustom corfu-prescient-enable-filtering t
"Whether the `prescient' completion style is used in Corfu."
:type 'boolean
:group 'corfu-prescient)
(defcustom corfu-prescient-enable-sorting t
"Whether prescient.el sorting is used in Corfu."
:type 'boolean
:group 'corfu-prescient)
(defcustom corfu-prescient-override-sorting nil
"Whether to force sorting by `corfu-prescient'.
If non-nil, then `corfu-prescient-mode' sets
`corfu-sort-override-function' to the function
`prescient-completion-sort'.
Changing this variable will not take effect until
`corfu-prescient-mode' has been reloaded."
:group 'corfu-prescient
:type 'boolean)
(defcustom corfu-prescient-completion-styles
prescient--completion-recommended-styles
"The completion styles used by `corfu-prescient-mode'."
:group 'corfu-prescient
:type '(repeat symbol))
(defcustom corfu-prescient-completion-category-overrides
prescient--completion-recommended-overrides
"The completion-category overrides used by `corfu-prescient-mode'."
:group 'corfu-prescient
:type '(repeat (cons symbol (repeat (cons symbol (repeat symbol))))))
;;;; Toggling Commmands
(defun corfu-prescient--toggle-refresh ()
"Refresh the Corfu UI.
This function is added to `prescient--toggle-refresh-functions'
by `corfu-prescient-mode'."
(when corfu--input
(setq corfu--input nil)
(corfu--update)))
;;;; Minor mode
(defvar corfu-prescient--old-sort-function nil
"Previous value of `corfu-sort-function'.")
(defvar corfu-prescient--old-sort-override-function nil
"Previous value of `corfu-sort-override-function'.")
(defvar corfu-prescient--old-toggle-binding nil
"Previous binding of `M-s' in `corfu-map'.")
(defun corfu-prescient--remember (&rest _)
"Advice for remembering candidates in Corfu."
(when (>= corfu--index 0)
(prescient-remember
(substring-no-properties
(nth corfu--index corfu--candidates)))))
(defvar-local corfu-prescient--local-settings nil
"Whether this buffer has local settings due to `corfu-prescient-mode'.")
(defun corfu-prescient--apply-completion-settings ()
"Apply the local completion settings."
(prescient--completion-make-vars-local)
(prescient--completion-save-completion-settings)
(prescient--completion-apply-completion-settings
:styles corfu-prescient-completion-styles
:overrides corfu-prescient-completion-category-overrides)
(setq corfu-prescient--local-settings t))
(defun corfu-prescient--undo-completion-settings ()
"Undo the local completion settings."
(prescient--completion-restore-completion-settings
:styles corfu-prescient-completion-styles
:overrides corfu-prescient-completion-category-overrides)
(prescient--completion-kill-local-vars)
(setq corfu-prescient--local-settings nil))
(defun corfu-prescient--change-completion-settings (&rest _)
"Apply or undo the local completion settings in `corfu-mode-hook'."
(if corfu-mode
(unless corfu-prescient--local-settings
(corfu-prescient--apply-completion-settings))
;; We need this here in case `corfu-mode' itself is disabled. We
;; also need to undo things when `corfu-prescient-mode' is
;; disabled, which happens in the mode's definition.
(when corfu-prescient--local-settings
(corfu-prescient--undo-completion-settings))))
;;;###autoload
(define-minor-mode corfu-prescient-mode
"Minor mode to use prescient.el in Corfu menus.
This mode will:
- if `corfu-prescient-override-sorting' is non-nil,
configure `corfu-sort-override-function' and set
`corfu-prescient-enable-filtering' to t
- if `corfu-prescient-enable-filtering' is non-nil,
configure `corfu-sort-function'
- if `corfu-prescient-enable-filtering' is non-nil:
- bind `prescient-toggle-map' to `M-s' in `corfu-map'
- change `completion-stlyes' to `corfu-prescient-completion-styles'
- apply `corfu-prescient-completion-category-overrides'
to `completion-category-overrides'
- set `completion-category-defaults' to nil
- advise `corfu-insert' to remember candidates"
:global t
:group 'prescient
(if corfu-prescient-mode
;; Turn on the mode.
(progn
;; Prevent messing up variables if we explicitly enable the
;; mode when it's already on.
(corfu-prescient-mode -1)
(setq corfu-prescient-mode t)
(when corfu-prescient-override-sorting
(setq corfu-prescient-enable-sorting t)
(cl-shiftf corfu-prescient--old-sort-override-function
corfu-sort-override-function
#'prescient-completion-sort))
(when corfu-prescient-enable-sorting
(cl-shiftf corfu-prescient--old-sort-function
corfu-sort-function
#'prescient-completion-sort))
(when corfu-prescient-enable-filtering
;; Configure changing settings in the hook.
(add-hook 'corfu-mode-hook
#'corfu-prescient--change-completion-settings)
;; Immediately apply the settings in buffers where
;; `corfu-mode' is already on.
(dolist (b (buffer-list))
(when (buffer-local-value corfu-mode b)
(with-current-buffer b
(corfu-prescient--apply-completion-settings))))
;; Bind toggling commands.
(setq corfu-prescient--old-toggle-binding
(lookup-key corfu-map (kbd "M-s")))
(define-key corfu-map (kbd "M-s") prescient-toggle-map)
;; Make sure Corfu refreshes immediately.
(add-hook 'prescient--toggle-refresh-functions
#'corfu-prescient--toggle-refresh)
;; Clean up the local versions of the toggling variables
;; after the Corfu pop-up closes. For the toggling vars, it
;; is the commands themselves that make the variables buffer
;; local.
(if (boundp 'corfu--state-vars)
(cl-callf cl-union corfu--state-vars prescient--toggle-vars
:test #'eq)
(cl-callf cl-union corfu--initial-state
(mapcar (lambda (k) (cons k (symbol-value k)))
prescient--toggle-vars)
:test #'eq :key #'car)))
;; While sorting might not be enabled in Corfu, it might
;; still be enabled in another UI, such as Selectrum or Vertico.
;; Therefore, we still want to remember candidates.
(advice-add 'corfu--insert :before #'corfu-prescient--remember))
;; Turn off mode.
;; Undo sorting settings.
(when (eq corfu-sort-function #'prescient-completion-sort)
(setq corfu-sort-function corfu-prescient--old-sort-function))
(when (eq corfu-sort-override-function #'prescient-completion-sort)
(setq corfu-sort-override-function
corfu-prescient--old-sort-override-function))
;; Unbind toggling commands and unhook refresh function.
(when (equal (lookup-key corfu-map (kbd "M-s"))
prescient-toggle-map)
(define-key corfu-map (kbd "M-s")
corfu-prescient--old-toggle-binding))
(remove-hook 'prescient--toggle-refresh-functions
#'corfu-prescient--toggle-refresh)
(if (boundp 'corfu--state-vars)
(cl-callf cl-set-difference corfu--state-vars
prescient--toggle-vars
:test #'eq)
(cl-callf cl-set-difference corfu--initial-state
(mapcar (lambda (k) (cons k (symbol-value k)))
prescient--toggle-vars)
:test #'eq :key #'car))
;; Undo filtering settings.
(remove-hook 'corfu-mode-hook
#'corfu-prescient--change-completion-settings)
(dolist (b (buffer-list))
(when (buffer-local-value 'corfu-prescient--local-settings b)
(with-current-buffer b
(corfu-prescient--undo-completion-settings))))
;; Undo remembrance settings.
(advice-remove 'corfu-insert #'corfu-prescient--remember)))
(provide 'corfu-prescient)
;;; corfu-prescient.el ends here