dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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