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