| @ -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 | |||