corfu-history.el (3589B)
1 ;;; corfu-history.el --- Sorting by history for Corfu -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2022-2024 Free Software Foundation, Inc. 4 5 ;; Author: Daniel Mendler <mail@daniel-mendler.de> 6 ;; Maintainer: Daniel Mendler <mail@daniel-mendler.de> 7 ;; Created: 2022 8 ;; Version: 1.3 9 ;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4") (corfu "1.3")) 10 ;; Homepage: https://github.com/minad/corfu 11 12 ;; This file is part of GNU Emacs. 13 14 ;; This program is free software: you can redistribute it and/or modify 15 ;; it under the terms of the GNU General Public License as published by 16 ;; the Free Software Foundation, either version 3 of the License, or 17 ;; (at your option) any later version. 18 19 ;; This program is distributed in the hope that it will be useful, 20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 ;; GNU General Public License for more details. 23 24 ;; You should have received a copy of the GNU General Public License 25 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 26 27 ;;; Commentary: 28 29 ;; Enable `corfu-history-mode' to sort candidates by their history 30 ;; position. Maintain a list of recently selected candidates. In order 31 ;; to save the history across Emacs sessions, enable `savehist-mode' and 32 ;; add `corfu-history' to `savehist-additional-variables'. 33 ;; 34 ;; (corfu-history-mode 1) 35 ;; (savehist-mode 1) 36 ;; (add-to-list 'savehist-additional-variables 'corfu-history) 37 38 ;;; Code: 39 40 (require 'corfu) 41 (eval-when-compile 42 (require 'cl-lib)) 43 44 (defvar corfu-history nil 45 "History of Corfu candidates. 46 The maximum length is determined by the variable `history-length' 47 or the property `history-length' of `corfu-history'.") 48 49 (defvar corfu-history--hash nil 50 "Hash table of Corfu candidates.") 51 52 (defun corfu-history--sort-predicate (x y) 53 "Sorting predicate which compares X and Y." 54 (or (< (cdr x) (cdr y)) 55 (and (= (cdr x) (cdr y)) 56 (corfu--length-string< (car x) (car y))))) 57 58 (defun corfu-history--sort (cands) 59 "Sort CANDS by history." 60 (unless corfu-history--hash 61 (setq corfu-history--hash (make-hash-table :test #'equal :size (length corfu-history))) 62 (cl-loop for elem in corfu-history for index from 0 do 63 (unless (gethash elem corfu-history--hash) 64 (puthash elem index corfu-history--hash)))) 65 ;; Decorate each candidate with (index<<13) + length. This way we sort first by index and then by 66 ;; length. We assume that the candidates are shorter than 2**13 characters and that the history is 67 ;; shorter than 2**16 entries. 68 (cl-loop for cand on cands do 69 (setcar cand (cons (car cand) 70 (+ (ash (gethash (car cand) corfu-history--hash #xFFFF) 13) 71 (length (car cand)))))) 72 (setq cands (sort cands #'corfu-history--sort-predicate)) 73 (cl-loop for cand on cands do (setcar cand (caar cand))) 74 cands) 75 76 ;;;###autoload 77 (define-minor-mode corfu-history-mode 78 "Update Corfu history and sort completions by history." 79 :global t :group 'corfu 80 (if corfu-history-mode 81 (add-function :override corfu-sort-function #'corfu-history--sort) 82 (remove-function corfu-sort-function #'corfu-history--sort))) 83 84 (cl-defmethod corfu--insert :before (_status &context (corfu-history-mode (eql t))) 85 (when (>= corfu--index 0) 86 (add-to-history 'corfu-history 87 (substring-no-properties 88 (nth corfu--index corfu--candidates))) 89 (setq corfu-history--hash nil))) 90 91 (provide 'corfu-history) 92 ;;; corfu-history.el ends here