| @ -1,85 +0,0 @@ | |||||
| ;;; grizzl-autoloads.el --- automatically extracted autoloads | |||||
| ;; | |||||
| ;;; Code: | |||||
| ;;;### (autoloads (grizzl-result-strings grizzl-result-count grizzl-search | |||||
| ;;;;;; grizzl-make-index) "grizzl-core" "grizzl-core.el" (21002 | |||||
| ;;;;;; 10325 0 0)) | |||||
| ;;; Generated autoloads from grizzl-core.el | |||||
| (autoload 'grizzl-make-index "grizzl-core" "\ | |||||
| Makes an index from the list STRINGS for use with `grizzl-search'. | |||||
| If :PROGRESS-FN is given as a keyword argument, it is called repeatedly | |||||
| with integers N and TOTAL. | |||||
| If :CASE-SENSITIVE is specified as a non-nil keyword argument, the index | |||||
| will be created case-sensitive, otherwise it will be case-insensitive. | |||||
| \(fn STRINGS &rest OPTIONS)" nil nil) | |||||
| (autoload 'grizzl-search "grizzl-core" "\ | |||||
| Fuzzy searches for TERM in INDEX prepared with `grizzl-make-index'. | |||||
| OLD-RESULT may be specified as an existing search result to increment from. | |||||
| The result can be read with `grizzl-result-strings'. | |||||
| \(fn TERM INDEX &optional OLD-RESULT)" nil nil) | |||||
| (autoload 'grizzl-result-count "grizzl-core" "\ | |||||
| Returns the number of matches present in RESULT. | |||||
| \(fn RESULT)" nil nil) | |||||
| (autoload 'grizzl-result-strings "grizzl-core" "\ | |||||
| Returns the ordered list of matched strings in RESULT, using INDEX. | |||||
| If the :START option is specified, results are read from the given offset. | |||||
| If the :END option is specified, up to :END results are returned. | |||||
| \(fn RESULT INDEX &rest OPTIONS)" nil nil) | |||||
| ;;;*** | |||||
| ;;;### (autoloads (grizzl-set-selection-1 grizzl-set-selection+1 | |||||
| ;;;;;; grizzl-selected-result grizzl-completing-read) "grizzl-read" | |||||
| ;;;;;; "grizzl-read.el" (21002 10325 0 0)) | |||||
| ;;; Generated autoloads from grizzl-read.el | |||||
| (autoload 'grizzl-completing-read "grizzl-read" "\ | |||||
| Performs a completing-read in the minibuffer using INDEX to fuzzy search. | |||||
| Each key pressed in the minibuffer filters down the list of matches. | |||||
| \(fn PROMPT INDEX)" nil nil) | |||||
| (autoload 'grizzl-selected-result "grizzl-read" "\ | |||||
| Get the selected string from INDEX in a `grizzl-completing-read'. | |||||
| \(fn INDEX)" nil nil) | |||||
| (autoload 'grizzl-set-selection+1 "grizzl-read" "\ | |||||
| Move the selection up one row in `grizzl-completing-read'. | |||||
| \(fn)" t nil) | |||||
| (autoload 'grizzl-set-selection-1 "grizzl-read" "\ | |||||
| Move the selection down one row in `grizzl-completing-read'. | |||||
| \(fn)" t nil) | |||||
| ;;;*** | |||||
| ;;;### (autoloads nil nil ("grizzl-pkg.el" "grizzl.el") (21002 10325 | |||||
| ;;;;;; 472906 0)) | |||||
| ;;;*** | |||||
| (provide 'grizzl-autoloads) | |||||
| ;; Local Variables: | |||||
| ;; version-control: never | |||||
| ;; no-byte-compile: t | |||||
| ;; no-update-autoloads: t | |||||
| ;; coding: utf-8 | |||||
| ;; End: | |||||
| ;;; grizzl-autoloads.el ends here | |||||
| @ -1,226 +0,0 @@ | |||||
| ;;; grizzl-core.el --- Fast fuzzy search index for Emacs. | |||||
| ;; Copyright © 2013 Chris Corbyn | |||||
| ;; | |||||
| ;; Author: Chris Corbyn <chris@w3style.co.uk> | |||||
| ;; URL: https://github.com/d11wtq/grizzl | |||||
| ;; Version: 0.1.1 | |||||
| ;; Keywords: convenience, usability | |||||
| ;; This file is NOT part of GNU Emacs. | |||||
| ;;; --- License | |||||
| ;; Licensed under the same terms as Emacs. | |||||
| ;;; --- Commentary | |||||
| ;; Grizzl provides a fuzzy completion framework for general purpose | |||||
| ;; use in Emacs Lisp projects. | |||||
| ;; | |||||
| ;; grizzl-core.el provides the underlying data structures and sesrch | |||||
| ;; algorithm without any UI attachment. At the core, a fuzzy search | |||||
| ;; index is created from a list of strings, using `grizzl-make-index'. | |||||
| ;; A fuzzy search term is then used to get a result from this index | |||||
| ;; with `grizzl-search'. Because grizzl considers the usage of a | |||||
| ;; fuzzy search index to operate in real-time as a user enters a | |||||
| ;; search term in the minibuffer, the framework optimizes for this use | |||||
| ;; case. Any result can be passed back into `grizzl-search' as a hint | |||||
| ;; to continue searching. The search algorithm is able to understand | |||||
| ;; insertions and deletions and therefore minimizes the work it needs | |||||
| ;; to do in this case. The intended use here is to collect a result | |||||
| ;; on each key press and feed that result into the search for the next | |||||
| ;; key press. Once a search is complete, the matched strings are then | |||||
| ;; read, using `grizzl-result-strings'. The results are ordered on the | |||||
| ;; a combination of the Levenshtein Distance and a character-proximity | |||||
| ;; scoring calculation. This means shorter strings are favoured, but | |||||
| ;; adjacent letters are more heavily favoured. | |||||
| ;; | |||||
| ;; It is assumed that the index will be re-used across multiple | |||||
| ;; searches on larger sets of data. | |||||
| ;; | |||||
| ;; | |||||
| (eval-when-compile | |||||
| (require 'cl-lib)) | |||||
| ;;; --- Public Functions | |||||
| ;;;###autoload | |||||
| (defun grizzl-make-index (strings &rest options) | |||||
| "Makes an index from the list STRINGS for use with `grizzl-search'. | |||||
| If :PROGRESS-FN is given as a keyword argument, it is called repeatedly | |||||
| with integers N and TOTAL. | |||||
| If :CASE-SENSITIVE is specified as a non-nil keyword argument, the index | |||||
| will be created case-sensitive, otherwise it will be case-insensitive." | |||||
| (let ((lookup-table (make-hash-table)) | |||||
| (total-strs (length strings)) | |||||
| (case-sensitive (plist-get options :case-sensitive)) | |||||
| (progress-fn (plist-get options :progress-fn)) | |||||
| (string-data (vconcat (mapcar (lambda (s) | |||||
| (cons s (length s))) | |||||
| strings)))) | |||||
| (reduce (lambda (list-offset str) | |||||
| (grizzl-index-insert str list-offset lookup-table | |||||
| :case-sensitive case-sensitive) | |||||
| (when progress-fn | |||||
| (funcall progress-fn (1+ list-offset) total-strs)) | |||||
| (1+ list-offset)) | |||||
| strings | |||||
| :initial-value 0) | |||||
| (maphash (lambda (char str-map) | |||||
| (maphash (lambda (list-offset locations) | |||||
| (puthash list-offset (reverse locations) str-map)) | |||||
| str-map)) lookup-table) | |||||
| `((case-sensitive . ,case-sensitive) | |||||
| (lookup-table . ,lookup-table) | |||||
| (string-data . ,string-data)))) | |||||
| ;;;###autoload | |||||
| (defun grizzl-search (term index &optional old-result) | |||||
| "Fuzzy searches for TERM in INDEX prepared with `grizzl-make-index'. | |||||
| OLD-RESULT may be specified as an existing search result to increment from. | |||||
| The result can be read with `grizzl-result-strings'." | |||||
| (let* ((cased-term (if (grizzl-index-case-sensitive-p index) | |||||
| term | |||||
| (downcase term))) | |||||
| (result (grizzl-rewind-result cased-term index old-result)) | |||||
| (matches (copy-hash-table (grizzl-result-matches result))) | |||||
| (from-pos (length (grizzl-result-term result))) | |||||
| (remainder (substring cased-term from-pos)) | |||||
| (lookup-table (grizzl-lookup-table index))) | |||||
| (reduce (lambda (acc-res ch) | |||||
| (let ((sub-table (gethash ch lookup-table))) | |||||
| (if (not sub-table) | |||||
| (clrhash matches) | |||||
| (grizzl-search-increment sub-table matches)) | |||||
| (grizzl-cons-result cased-term matches acc-res))) | |||||
| remainder | |||||
| :initial-value result))) | |||||
| ;;;###autoload | |||||
| (defun grizzl-result-count (result) | |||||
| "Returns the number of matches present in RESULT." | |||||
| (hash-table-count (grizzl-result-matches result))) | |||||
| ;;;###autoload | |||||
| (defun grizzl-result-strings (result index &rest options) | |||||
| "Returns the ordered list of matched strings in RESULT, using INDEX. | |||||
| If the :START option is specified, results are read from the given offset. | |||||
| If the :END option is specified, up to :END results are returned." | |||||
| (let* ((matches (grizzl-result-matches result)) | |||||
| (strings (grizzl-index-strings index)) | |||||
| (loaded '()) | |||||
| (start (plist-get options :start)) | |||||
| (end (plist-get options :end))) | |||||
| (maphash (lambda (string-offset char-offset) | |||||
| (push string-offset loaded)) | |||||
| matches) | |||||
| (let* ((ordered (sort loaded | |||||
| (lambda (a b) | |||||
| (< (cadr (gethash a matches)) | |||||
| (cadr (gethash b matches)))))) | |||||
| (best (if (or start end) | |||||
| (delete-if-not 'identity | |||||
| (subseq ordered (or start 0) end)) | |||||
| ordered))) | |||||
| (mapcar (lambda (n) | |||||
| (car (elt strings n))) | |||||
| best)))) | |||||
| ;;; --- Private Functions | |||||
| (defun grizzl-cons-result (term matches results) | |||||
| "Build a new result for TERM and hash-table MATCHES consed with RESULTS." | |||||
| (cons (cons term matches) results)) | |||||
| (defun grizzl-rewind-result (term index result) | |||||
| "Adjusts RESULT according to TERM, ready for a new search." | |||||
| (if result | |||||
| (let* ((old-term (grizzl-result-term result)) | |||||
| (new-len (length term)) | |||||
| (old-len (length old-term))) | |||||
| (if (and (>= new-len old-len) | |||||
| (string-equal old-term (substring term 0 old-len))) | |||||
| result | |||||
| (grizzl-rewind-result term index (cdr result)))) | |||||
| (grizzl-cons-result "" (grizzl-base-matches index) nil))) | |||||
| (defun grizzl-base-matches (index) | |||||
| "Returns the full set of matches in INDEX, with an out-of-bound offset." | |||||
| (let ((matches (make-hash-table))) | |||||
| (reduce (lambda (n s-len) | |||||
| (puthash n (list -1 0 (cdr s-len)) matches) | |||||
| (1+ n)) | |||||
| (grizzl-index-strings index) | |||||
| :initial-value 0) | |||||
| matches)) | |||||
| (defun grizzl-result-term (result) | |||||
| "Returns the search term used to find the matches in RESULT." | |||||
| (car (car result))) | |||||
| (defun grizzl-result-matches (result) | |||||
| "Returns the internal hash used to track the matches in RESULT." | |||||
| (cdar result)) | |||||
| (defun grizzl-index-insert (string list-offset index &rest options) | |||||
| "Inserts STRING at LIST-OFFSET into INDEX." | |||||
| (let ((case-sensitive (plist-get options :case-sensitive))) | |||||
| (reduce (lambda (char-offset cs-char) | |||||
| (let* ((char (if case-sensitive | |||||
| cs-char | |||||
| (downcase cs-char))) | |||||
| (str-map (or (gethash char index) | |||||
| (puthash char (make-hash-table) index))) | |||||
| (offsets (gethash list-offset str-map))) | |||||
| (puthash list-offset | |||||
| (cons char-offset offsets) | |||||
| str-map) | |||||
| (1+ char-offset))) | |||||
| string | |||||
| :initial-value 0))) | |||||
| (defun grizzl-lookup-table (index) | |||||
| "Returns the lookup table portion of INDEX." | |||||
| (cdr (assoc 'lookup-table index))) | |||||
| (defun grizzl-index-strings (index) | |||||
| "Returns the vector of strings stored in INDEX." | |||||
| (cdr (assoc 'string-data index))) | |||||
| (defun grizzl-index-case-sensitive-p (index) | |||||
| "Predicate to test of INDEX is case-sensitive." | |||||
| (cdr (assoc 'case-sensitive index))) | |||||
| (defun grizzl-search-increment (sub-table result) | |||||
| "Use the search lookup table to filter already-accumulated results." | |||||
| (cl-flet ((next-offset (key current sub-table) | |||||
| (find-if (lambda (v) | |||||
| (> v current)) | |||||
| (gethash key sub-table)))) | |||||
| (maphash (lambda (k v) | |||||
| (let* ((oldpos (car v)) | |||||
| (oldrank (cadr v)) | |||||
| (len (caddr v)) | |||||
| (newpos (next-offset k oldpos sub-table))) | |||||
| (if newpos | |||||
| (puthash k (list newpos | |||||
| (grizzl-inc-rank oldrank oldpos newpos len) | |||||
| len) | |||||
| result) | |||||
| (remhash k result)))) | |||||
| result))) | |||||
| (defun grizzl-inc-rank (oldrank oldpos newpos len) | |||||
| "Increment the current match distance as a new char is matched." | |||||
| (let ((distance (if (< oldpos 0) 1 (- newpos oldpos)))) | |||||
| (+ oldrank (* len (* distance distance))))) | |||||
| (provide 'grizzl-core) | |||||
| ;;; grizzl-core.el ends here | |||||
| @ -1,3 +0,0 @@ | |||||
| (define-package "grizzl" "0.1.1" | |||||
| "Fuzzy Search Library & Completing Read" | |||||
| '((cl-lib "0.1"))) | |||||
| @ -1,186 +0,0 @@ | |||||
| ;;; grizzl-read.el --- A fuzzy completing-read backed by grizzl. | |||||
| ;; Copyright © 2013 Chris Corbyn | |||||
| ;; | |||||
| ;; Author: Chris Corbyn <chris@w3style.co.uk> | |||||
| ;; URL: https://github.com/d11wtq/grizzl | |||||
| ;; Version: 0.1.1 | |||||
| ;; Keywords: convenience, usability | |||||
| ;; This file is NOT part of GNU Emacs. | |||||
| ;;; --- License | |||||
| ;; Licensed under the same terms as Emacs. | |||||
| ;;; --- Commentary | |||||
| ;; grizzl-read.el provides an implementation of the built-in Emacs | |||||
| ;; completing-read function, except it is backed by the grizzl fuzzy | |||||
| ;; search index. The goals are similar to ido-mode and helm, but grizzl | |||||
| ;; is heavily optimized for large data-sets, and as-such uses a | |||||
| ;; persistent fuzzy search index in its algorithm. | |||||
| ;; | |||||
| ;; The indexing and searching algorithm itself is defined in grizzl-core.el | |||||
| ;; with grizzl-read.el simply wrapping the search in a minibuffer with a | |||||
| ;; minor-mode defined. | |||||
| ;; | |||||
| ;; ---- Usage | |||||
| ;; | |||||
| ;; Call `grizzl-completing-read' with an index returned by | |||||
| ;; `grizzl-make-index': | |||||
| ;; | |||||
| ;; (defvar *index* (grizzl-make-index '("one" "two" "three"))) | |||||
| ;; (grizzl-completing-read "Number: " index) | |||||
| ;; | |||||
| ;; When the user hits ENTER, either one of the strings is returned on | |||||
| ;; success, or nil of nothing matched. | |||||
| ;; | |||||
| ;; The arrow keys can be used to navigate within the results. | |||||
| ;; | |||||
| (eval-when-compile | |||||
| (require 'cl-lib)) | |||||
| ;;; --- Configuration Variables | |||||
| (defvar *grizzl-read-max-results* 10 | |||||
| "The maximum number of results to show in `grizzl-completing-read'.") | |||||
| ;;; --- Runtime Processing Variables | |||||
| (defvar *grizzl-current-result* nil | |||||
| "The search result in `grizzl-completing-read'.") | |||||
| (defvar *grizzl-current-selection* 0 | |||||
| "The selected offset in `grizzl-completing-read'.") | |||||
| ;;; --- Minor Mode Definition | |||||
| (defvar *grizzl-keymap* (make-sparse-keymap) | |||||
| "Internal keymap used by the minor-mode in `grizzl-completing-read'.") | |||||
| (define-key *grizzl-keymap* (kbd "<up>") 'grizzl-set-selection+1) | |||||
| (define-key *grizzl-keymap* (kbd "C-p") 'grizzl-set-selection+1) | |||||
| (define-key *grizzl-keymap* (kbd "<down>") 'grizzl-set-selection-1) | |||||
| (define-key *grizzl-keymap* (kbd "C-n") 'grizzl-set-selection-1) | |||||
| (define-minor-mode grizzl-mode | |||||
| "Toggle the internal mode used by `grizzl-completing-read'." | |||||
| nil | |||||
| " Grizzl" | |||||
| *grizzl-keymap*) | |||||
| ;;; --- Public Functions | |||||
| ;;;###autoload | |||||
| (defun grizzl-completing-read (prompt index) | |||||
| "Performs a completing-read in the minibuffer using INDEX to fuzzy search. | |||||
| Each key pressed in the minibuffer filters down the list of matches." | |||||
| (minibuffer-with-setup-hook | |||||
| (lambda () | |||||
| (setq *grizzl-current-result* nil) | |||||
| (setq *grizzl-current-selection* 0) | |||||
| (grizzl-mode 1) | |||||
| (lexical-let* | |||||
| ((hookfun (lambda () | |||||
| (setq *grizzl-current-result* | |||||
| (grizzl-search (minibuffer-contents) | |||||
| index | |||||
| *grizzl-current-result*)) | |||||
| (grizzl-display-result index prompt))) | |||||
| (exitfun (lambda () | |||||
| (grizzl-mode -1) | |||||
| (remove-hook 'post-command-hook hookfun t)))) | |||||
| (add-hook 'minibuffer-exit-hook exitfun nil t) | |||||
| (add-hook 'post-command-hook hookfun nil t))) | |||||
| (read-from-minibuffer ">>> ") | |||||
| (grizzl-selected-result index))) | |||||
| ;;;###autoload | |||||
| (defun grizzl-selected-result (index) | |||||
| "Get the selected string from INDEX in a `grizzl-completing-read'." | |||||
| (elt (grizzl-result-strings *grizzl-current-result* index | |||||
| :start 0 | |||||
| :end *grizzl-read-max-results*) | |||||
| (grizzl-current-selection))) | |||||
| ;;;###autoload | |||||
| (defun grizzl-set-selection+1 () | |||||
| "Move the selection up one row in `grizzl-completing-read'." | |||||
| (interactive) | |||||
| (grizzl-move-selection 1)) | |||||
| ;;;###autoload | |||||
| (defun grizzl-set-selection-1 () | |||||
| "Move the selection down one row in `grizzl-completing-read'." | |||||
| (interactive) | |||||
| (grizzl-move-selection -1)) | |||||
| ;;; --- Private Functions | |||||
| (defun grizzl-move-selection (delta) | |||||
| "Move the selection by DELTA rows in `grizzl-completing-read'." | |||||
| (setq *grizzl-current-selection* (+ (grizzl-current-selection) delta)) | |||||
| (when (not (= (grizzl-current-selection) *grizzl-current-selection*)) | |||||
| (beep))) | |||||
| (defun grizzl-display-result (index prompt) | |||||
| "Renders a series of overlays to list the matches in the result." | |||||
| (let* ((matches (grizzl-result-strings *grizzl-current-result* index | |||||
| :start 0 | |||||
| :end *grizzl-read-max-results*))) | |||||
| (delete-all-overlays) | |||||
| (overlay-put (make-overlay (point-min) (point-min)) | |||||
| 'before-string | |||||
| (format "%s\n%s\n" | |||||
| (mapconcat 'identity | |||||
| (grizzl-map-format-matches matches) | |||||
| "\n") | |||||
| (grizzl-format-prompt-line prompt))) | |||||
| (set-window-text-height nil (max 3 (+ 2 (length matches)))))) | |||||
| (defun grizzl-map-format-matches (matches) | |||||
| "Convert the set of string MATCHES into propertized text objects." | |||||
| (if (= 0 (length matches)) | |||||
| (list (propertize "-- NO MATCH --" 'face 'outline-3)) | |||||
| (cdr (reduce (lambda (acc str) | |||||
| (let* ((idx (car acc)) | |||||
| (lst (cdr acc)) | |||||
| (sel (= idx (grizzl-current-selection)))) | |||||
| (cons (1+ idx) | |||||
| (cons (grizzl-format-match str sel) lst)))) | |||||
| matches | |||||
| :initial-value '(0))))) | |||||
| (defun grizzl-format-match (match-str selected) | |||||
| "Default match string formatter in `grizzl-completing-read'. | |||||
| MATCH-STR is the string in the selection list and SELECTED is non-nil | |||||
| if this is the current selection." | |||||
| (let ((margin (if selected "> " " ")) | |||||
| (face (if selected 'diredp-symlink 'default))) | |||||
| (propertize (format "%s%s" margin match-str) 'face face))) | |||||
| (defun grizzl-format-prompt-line (prompt) | |||||
| "Returns a string to render a full-width prompt in `grizzl-completing-read'." | |||||
| (let* ((count (grizzl-result-count *grizzl-current-result*)) | |||||
| (match-info (format " (%d candidate%s) ---- *-" | |||||
| count (if (= count 1) "" "s")))) | |||||
| (concat (propertize (format "-*%s *-" prompt) 'face 'modeline-inactive) | |||||
| (propertize " " | |||||
| 'face 'modeline-inactive | |||||
| 'display `(space :align-to (- right | |||||
| ,(1+ (length match-info))))) | |||||
| (propertize match-info 'face 'modeline-inactive)))) | |||||
| (defun grizzl-current-selection () | |||||
| "Get the currently selected index in `grizzl-completing-read'." | |||||
| (let ((max-selection | |||||
| (min (1- *grizzl-read-max-results*) | |||||
| (1- (grizzl-result-count *grizzl-current-result*))))) | |||||
| (max 0 (min max-selection *grizzl-current-selection*)))) | |||||
| (provide 'grizzl-read) | |||||
| ;;; grizzl-read.el ends here | |||||
| @ -1,26 +0,0 @@ | |||||
| ;;; grizzl.el --- Fast fuzzy search index for Emacs. | |||||
| ;; Copyright © 2013 Chris Corbyn | |||||
| ;; | |||||
| ;; Author: Chris Corbyn <chris@w3style.co.uk> | |||||
| ;; URL: https://github.com/d11wtq/grizzl | |||||
| ;; Version: 0.1.1 | |||||
| ;; Keywords: convenience, usability | |||||
| ;; This file is NOT part of GNU Emacs. | |||||
| ;;; --- License | |||||
| ;; Licensed under the same terms as Emacs. | |||||
| ;;; --- Commentary | |||||
| ;; This package is broken into separate files. | |||||
| ;; | |||||
| (require 'grizzl-core) | |||||
| (require 'grizzl-read) | |||||
| (provide 'grizzl) | |||||
| ;;; grizzl.el ends here | |||||