branch: externals/vertico commit ad5069726a6e4d588757f8c952ab9df9eb293f1e Author: Daniel Mendler <m...@daniel-mendler.de> Commit: Daniel Mendler <m...@daniel-mendler.de>
Extract vertico-sort --- CHANGELOG.org | 1 + README.org | 1 + extensions/vertico-sort.el | 104 +++++++++++++++++++++++++++++++++++++++++++++ vertico.el | 72 +++---------------------------- 4 files changed, 113 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index e76c465db4..393e388c78 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -5,6 +5,7 @@ * Development - =vertico-flat-format=: Customizable =:spacer= string. +- =vertico-sort=: Extracted sort functions to separate extension. * Version 2.0 (2025-03-11) diff --git a/README.org b/README.org index 4127b526f8..8a95bf8bbc 100644 --- a/README.org +++ b/README.org @@ -248,6 +248,7 @@ following extensions come with the Vertico ELPA package: - [[https://github.com/minad/vertico/blob/main/extensions/vertico-quick.el][vertico-quick]]: Commands to select using Avy-style quick keys. - [[https://github.com/minad/vertico/blob/main/extensions/vertico-repeat.el][vertico-repeat]]: The command =vertico-repeat= repeats the last completion session. - [[https://github.com/minad/vertico/blob/main/extensions/vertico-reverse.el][vertico-reverse]]: =vertico-reverse-mode= to reverse the display. +- [[https://github.com/minad/vertico/blob/main/extensions/vertico-sort.el][vertico-sort]]: Provides optimized sort functions, by history, by length and lexical. - [[https://github.com/minad/vertico/blob/main/extensions/vertico-suspend.el][vertico-suspend]]: The command =vertico-suspend= suspends and restores the current session. - [[https://github.com/minad/vertico/blob/main/extensions/vertico-unobtrusive.el][vertico-unobtrusive]]: =vertico-unobtrusive-mode= displays only the topmost candidate. diff --git a/extensions/vertico-sort.el b/extensions/vertico-sort.el new file mode 100644 index 0000000000..d35e253c63 --- /dev/null +++ b/extensions/vertico-sort.el @@ -0,0 +1,104 @@ +;;; vertico-sort.el --- Sort functions for Vertico -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2025 Free Software Foundation, Inc. + +;; Author: Daniel Mendler <m...@daniel-mendler.de> +;; Maintainer: Daniel Mendler <m...@daniel-mendler.de> +;; Created: 2021 +;; Version: 2.0 +;; Package-Requires: ((emacs "28.1") (compat "30") (vertico "2.0")) +;; URL: https://github.com/minad/vertico + +;; This file is part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides a set of sort functions for Vertico, which +;; can be used as `vertico-sort-function'. By default, Vertico uses +;; the `vertico-sort-history-length-alpha' function. + +;;; Code: + +(require 'vertico) +(eval-when-compile (require 'cl-lib)) + +(defvar-local vertico-sort--history nil + "History hash table and corresponding base string.") + +(defun vertico-sort--history () + "Recompute history hash table and return it." + (or (and (equal (car vertico-sort--history) vertico--base) (cdr vertico-sort--history)) + (let* ((base vertico--base) + (base-len (length base)) + (hist (and (not (eq minibuffer-history-variable t)) ;; Disabled for `t'. + (symbol-value minibuffer-history-variable))) + (hash (make-hash-table :test #'equal :size (length hist))) + (file-p (and (> base-len 0) ;; Step-wise completion, unlike `project-find-file' + (eq minibuffer-history-variable 'file-name-history))) + (curr-file (when-let ((win (and file-p (minibuffer-selected-window))) + (file (buffer-file-name (window-buffer win)))) + (abbreviate-file-name file)))) + (cl-loop for elem in hist for index from 0 do + (when (and (not (equal curr-file elem)) ;; Deprioritize current file + (or (= base-len 0) + (and (>= (length elem) base-len) + (eq t (compare-strings base 0 base-len elem 0 base-len))))) + (let ((file-sep (and file-p (string-search "/" elem base-len)))) + ;; Drop base string from history elements & special file handling. + (when (or (> base-len 0) file-sep) + (setq elem (substring elem base-len (and file-sep (1+ file-sep))))) + (unless (gethash elem hash) (puthash elem index hash))))) + (cdr (setq vertico-sort--history (cons base hash)))))) + +(defun vertico-sort--length-string< (x y) + "Sorting predicate which compares X and Y first by length then by `string<'." + (or (< (length x) (length y)) (and (= (length x) (length y)) (string< x y)))) + +(defun vertico-sort--decorated (list) + "Sort decorated LIST and remove decorations." + (setq list (sort list #'car-less-than-car)) + (cl-loop for item on list do (setcar item (cdar item))) + list) + +(defmacro vertico-sort--define (by bsize bindex bpred pred) + "Generate optimized sorting function. +The function is configured by BY, BSIZE, BINDEX, BPRED and PRED." + `(defun ,(intern (mapconcat #'symbol-name `(vertico sort ,@by) "-")) (candidates) + ,(concat "Sort candidates by " (mapconcat #'symbol-name by ", ") ".") + (let* ((buckets (make-vector ,bsize nil)) + ,@(and (eq (car by) 'history) '((hhash (vertico-sort--history)) (hcands)))) + (dolist (% candidates) + ;; Find recent candidate in history or fill bucket + (,@(if (not (eq (car by) 'history)) `(progn) + `(if-let ((idx (gethash % hhash))) (push (cons idx %) hcands))) + (push % (aref buckets (min ,(1- bsize) ,bindex))))) + (nconc ,@(and (eq (car by) 'history) '((vertico-sort--decorated hcands))) + (mapcan (lambda (bucket) (sort bucket #',bpred)) + (nbutlast (append buckets nil))) + ;; Last bucket needs special treatment + (sort (aref buckets ,(1- bsize)) #',pred))))) + +;;;###autoload (autoload 'vertico-sort-history-length-alpha "vertico-sort") +;;;###autoload (autoload 'vertico-sort-history-alpha "vertico-sort") +;;;###autoload (autoload 'vertico-sort-length-alpha "vertico-sort") +;;;###autoload (autoload 'vertico-sort-alpha "vertico-sort") +(vertico-sort--define (history length alpha) 48 (length %) string< vertico-sort--length-string<) +(vertico-sort--define (history alpha) 32 (if (equal % "") 0 (/ (aref % 0) 4)) string< string<) +(vertico-sort--define (length alpha) 48 (length %) string< vertico-sort--length-string<) +(vertico-sort--define (alpha) 32 (if (equal % "") 0 (/ (aref % 0) 4)) string< string<) + +(provide 'vertico-sort) +;;; vertico-sort.el ends here diff --git a/vertico.el b/vertico.el index 362b6f1fdd..8655774479 100644 --- a/vertico.el +++ b/vertico.el @@ -92,14 +92,15 @@ The value should lie between 0 and vertico-count/2." "Replacements for multiline strings." :type '(cons (string :tag "Newline") (string :tag "Truncation"))) -(defcustom vertico-sort-function #'vertico-sort-history-length-alpha +(defcustom vertico-sort-function + (and (fboundp 'vertico-sort-history-length-alpha) 'vertico-sort-history-length-alpha) "Default sorting function, used if no `display-sort-function' is specified." - :type `(choice + :type '(choice (const :tag "No sorting" nil) - (const :tag "By history, length and alpha" ,#'vertico-sort-history-length-alpha) - (const :tag "By history and alpha" ,#'vertico-sort-history-alpha) - (const :tag "By length and alpha" ,#'vertico-sort-length-alpha) - (const :tag "Alphabetically" ,#'vertico-sort-alpha) + (const :tag "By history, length and alpha" vertico-sort-history-length-alpha) + (const :tag "By history and alpha" vertico-sort-history-alpha) + (const :tag "By length and alpha" vertico-sort-length-alpha) + (const :tag "Alphabetically" vertico-sort-alpha) (function :tag "Custom function"))) (defcustom vertico-sort-override-function nil @@ -146,9 +147,6 @@ The value should lie between 0 and vertico-count/2." (defvar-local vertico--hilit #'identity "Lazy candidate highlighting function.") -(defvar-local vertico--history-hash nil - "History hash table and corresponding base string.") - (defvar-local vertico--candidates-ov nil "Overlay showing the candidates.") @@ -191,62 +189,6 @@ The value should lie between 0 and vertico-count/2." (defvar-local vertico--allow-prompt nil "Prompt selection is allowed.") -(defun vertico--history-hash () - "Recompute history hash table and return it." - (or (and (equal (car vertico--history-hash) vertico--base) (cdr vertico--history-hash)) - (let* ((base vertico--base) - (base-len (length base)) - (hist (and (not (eq minibuffer-history-variable t)) ;; Disabled for `t'. - (symbol-value minibuffer-history-variable))) - (hash (make-hash-table :test #'equal :size (length hist))) - (file-p (and (> base-len 0) ;; Step-wise completion, unlike `project-find-file' - (eq minibuffer-history-variable 'file-name-history))) - (curr-file (when-let ((win (and file-p (minibuffer-selected-window))) - (file (buffer-file-name (window-buffer win)))) - (abbreviate-file-name file)))) - (cl-loop for elem in hist for index from 0 do - (when (and (not (equal curr-file elem)) ;; Deprioritize current file - (or (= base-len 0) - (and (>= (length elem) base-len) - (eq t (compare-strings base 0 base-len elem 0 base-len))))) - (let ((file-sep (and file-p (string-search "/" elem base-len)))) - ;; Drop base string from history elements & special file handling. - (when (or (> base-len 0) file-sep) - (setq elem (substring elem base-len (and file-sep (1+ file-sep))))) - (unless (gethash elem hash) (puthash elem index hash))))) - (cdr (setq vertico--history-hash (cons base hash)))))) - -(defun vertico--length-string< (x y) - "Sorting predicate which compares X and Y first by length then by `string<'." - (or (< (length x) (length y)) (and (= (length x) (length y)) (string< x y)))) - -(defun vertico--sort-decorated (list) - "Sort decorated LIST and remove decorations." - (setq list (sort list #'car-less-than-car)) - (cl-loop for item on list do (setcar item (cdar item))) - list) - -(defmacro vertico--define-sort (by bsize bindex bpred pred) - "Generate optimized sorting function. -The function is configured by BY, BSIZE, BINDEX, BPRED and PRED." - `(defun ,(intern (mapconcat #'symbol-name `(vertico sort ,@by) "-")) (candidates) - ,(concat "Sort candidates by " (mapconcat #'symbol-name by ", ") ".") - (let* ((buckets (make-vector ,bsize nil)) last - ,@(and (eq (car by) 'history) '((hhash (vertico--history-hash)) hcands))) - (dolist (% candidates) - ;; Find recent candidate in history or fill bucket - (,@(if (not (eq (car by) 'history)) `(progn) - `(if-let ((idx (gethash % hhash))) (push (cons idx %) hcands))) - (let ((i ,bindex)) (if (< i ,bsize) (push % (aref buckets i)) (push % last))))) - (nconc ,@(and (eq (car by) 'history) '((vertico--sort-decorated hcands))) - (mapcan (lambda (bucket) (sort bucket #',bpred)) buckets) - (sort last #',pred))))) - -(vertico--define-sort (history length alpha) 48 (length %) string< vertico--length-string<) -(vertico--define-sort (history alpha) 32 (if (equal % "") 0 (/ (aref % 0) 4)) string< string<) -(vertico--define-sort (length alpha) 48 (length %) string< vertico--length-string<) -(vertico--define-sort (alpha) 32 (if (equal % "") 0 (/ (aref % 0) 4)) string< string<) - (defun vertico--affixate (cands) "Annotate CANDS with annotation function." (if-let ((aff (vertico--metadata-get 'affixation-function)))