corfu-history.el (3863B)
1 ;;; corfu-history.el --- Sorting by history for Corfu -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2022 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: 0.1 9 ;; Package-Requires: ((emacs "27.1") (corfu "0.27")) 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 <http://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 (defcustom corfu-history-length nil 45 "Corfu history length." 46 :type '(choice (const nil) integer) 47 :group 'corfu) 48 49 (defvar corfu-history--hash nil 50 "Hash table of Corfu candidates.") 51 52 (defvar corfu-history nil 53 "History of Corfu candidates.") 54 55 (defun corfu-history--sort-predicate (x y) 56 "Sorting predicate which compares X and Y." 57 (pcase-let ((`(,sx . ,hx) x) 58 (`(,sy . ,hy) y)) 59 (or (< hx hy) 60 (and (= hx hy) 61 (or (< (length sx) (length sy)) 62 (and (= (length sx) (length sy)) 63 (string< sx sy))))))) 64 65 (defun corfu-history--sort (candidates) 66 "Sort CANDIDATES by history." 67 (unless corfu-history--hash 68 (setq corfu-history--hash (make-hash-table :test #'equal :size (length corfu-history))) 69 (cl-loop for elem in corfu-history for index from 0 do 70 (unless (gethash elem corfu-history--hash) 71 (puthash elem index corfu-history--hash)))) 72 ;; Decorate each candidate with (index<<13) + length. This way we sort first by index and then by 73 ;; length. We assume that the candidates are shorter than 2**13 characters and that the history is 74 ;; shorter than 2**16 entries. 75 (cl-loop for cand on candidates do 76 (setcar cand (cons (car cand) 77 (+ (ash (gethash (car cand) corfu-history--hash #xFFFF) 13) 78 (length (car cand)))))) 79 (setq candidates (sort candidates #'corfu-history--sort-predicate)) 80 (cl-loop for cand on candidates do (setcar cand (caar cand))) 81 candidates) 82 83 (defun corfu-history--insert (&rest _) 84 "Advice for `corfu--insert'." 85 (when (>= corfu--index 0) 86 (add-to-history 'corfu-history 87 (substring-no-properties 88 (nth corfu--index corfu--candidates)) 89 corfu-history-length) 90 (setq corfu-history--hash nil))) 91 92 ;;;###autoload 93 (define-minor-mode corfu-history-mode 94 "Update Corfu history and sort completions by history." 95 :global t 96 :group 'corfu 97 (cond 98 (corfu-history-mode 99 (setq corfu-sort-function #'corfu-history--sort) 100 (advice-add #'corfu--insert :before #'corfu-history--insert)) 101 (t 102 (setq corfu-sort-function #'corfu-sort-length-alpha) 103 (advice-remove #'corfu--insert #'corfu-history--insert)))) 104 105 (provide 'corfu-history) 106 ;;; corfu-history.el ends here