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