Coder Social home page Coder Social logo

grizzl's Introduction

Grizzl - A fuzzy-search utility for Emacs

The project is deprecated in favour of Ivy

Screenshot

Grizzl is a small utility library to be used in other Elisp code needing fuzzy search behaviour. It is optimized for large data sets, using a special type of lookup table and supporting incremental searches (searches where the result can be narrowed-down by only searching what is already matched).

Usage

Grizzl is still in development, though it is functional at this point, it just needs some work to make the UI more visually appealing and more informative, in addition to providing extension points in its internal minor-mode.

Using the completing-read

The main intended use-case for Grizzl is as a completing-read implementation, like ido-mode or helm. This is straightforward, but does require the preparation of a search index before use. It is assumed the search index will be re-used, though it need not be. For small data sets it may be better to just create an index on the fly.

;; define a search index
(defvar *search-index* (grizzl-make-index '("one" "two" "three" "four")))

;; prompt the use to pick from the index
(grizzl-completing-read "Number: " *search-index*)

The user is presented with the minibuffer and a list of matches, starting with all possible matches. As the user types, the list is reduced by repeatedly fuzzy searching in the index. The selection within the matched results can be changed by using the UP and DOWN arrow keys or C-p and C-n. The user hits ENTER to select the matching result.

If a match was successfully selected, grizzl-completing-read returns it as a string. If not, it returns nil.

Grizzl is case-insensitive by-default. To make it case-sensitive, specify :case-sensitive t when creating the index.

(grizzl-make-index '("One" "TWO" "three" "Four") :case-sensitive t)

No further settings are required for case-sensitivity; the index does the work.

Using the algorithm non-interactively

Grizzl aims to be small and focused, so that it can be used in other projects, without a huge codebase following it around. grizzl provides the functionality needed to incorporate fuzzy search logic into any Emacs Lisp code.

First an index must be created, as this is used in all other functions. The index is an optimization for large data sets, providing close to O(n+m) time lookup complexity, where n is the length of the search term and m is the number of possible matches. This proves to be extremely fast compared with regular expression matching and globbing.

Indexing

To define the index, pass a list of strings to grizzl-make-index.

(grizzl-make-index '("one" "two" "three"))

The returned data structure is used in other Grizzl functions.

If the data set is particularly large, the index may take a few seconds to build, during which time Emacs will appear non-responsive. You can observe the progress building the index by passing a callback function as the keyword argument :PROGRESS-FN. The callback receives two arguments, N and TOTAL, where N is the number of items processed so far and TOTAL is the number of items to be processed.

The following example shows the progress to the user as indexing is done.

(grizzl-make-index huge-list-of-strings
                   :progress-fn (lambda (n total)
                                  (message (format "Indexed %d/%d" n total))))

Of course, in a real-world implementation you'll probably only update the message area every 1000 or so items to avoid flooding the *Messages* buffer.

Indexes are case-insensitive by default, for the most-effective fuzzy-matching in most cases. If you need a case-sensitive index, specify a non-nil keyword argument :CASE-SENSITIVE.

(grizzl-make-index '("One" "TWO" "three" "Four") :case-sensitive t)

Searching

Given your index, ou may now search for something given a fuzzy search term, using grizzl-search.

(defvar *search-result* (grizzl-search "cntrl" *search-index*))

This function returns a new data structure representing the result of the search. You can read the strings from it with grizzl-result-strings.

If you need to run an interactive search, where the search term is changing as it is received from some external input, such as the minibuffer, you should pass each previous result back into grizzl-search as the third argument. While this is not strictly needed, it greatly improves performance on large data sets, since it allows the algorithm to focus only on what is already matched by the previous result.

If the new search term is, for whatever reason, entirely unrelated to the previous search term, there is little to no cost in passing the previous result in any case, since Grizzl will simply rewind its internal result tree and begin a fresh search, as needed.

(defvar *result-1* (grizzl-search "c"    *search-index*))
(defvar *result-2* (grizzl-search "cn"   *search-index* *result-1*))
(defvar *result-3* (grizzl-search "cnt"  *search-index* *result-2*))
(defvar *result-4* (grizzl-search "cntr" *search-index* *result-3*))

Passing nil as the previous result has the same effect as leaving the argument unspecified.

Result reading

Search results from grizzl-search are read with grizzl-result-strings.

(grizzl-result-strings *search-result* *search-index*)

The matching strings are returned ordered according to best match (first).

It is possible (and perhaps desirable, for performance reasons) to only read a subseq of the matched result. Just specify the keyword arguments :START and :END when reading the result strings.

;; returns at most the best 10 results
(grizzl-result-strings *search-result* *search-index*
                       :start 0
                       :end   10)

Copyright & Licensing

Grizzl is Copyright (c) 2013-2015 Chris Corbyn, Bozhidar Batsov and licensed under the same terms as GNU Emacs.

grizzl's People

Contributors

asok avatar bbatsov avatar d11wtq avatar gnufied avatar jojojames avatar milkypostman avatar rmuslimov avatar syohex avatar xuchunyang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grizzl's Issues

Problem with *grizzl-read-max-results*

I'm working with Emacs on OSX installed with brew.

Given

(defun test ()
  "Use ido to select a recently opened file from the `recentf-list'"
  (interactive)
  (grizzl-completing-read "Recentf open: " (grizzl-make-index recentf-list :case-sensitive t) ))

If i do

(setq *grizzl-read-max-results* 5)

everthing is OK

but when I set

(setq *grizzl-read-max-results* 6)

I see this:

screen shot 2013-12-14 at 16 38 50

I have to click mouse to the main window in order for results to appear
screen shot 2013-12-14 at 16 40 12

I think that it's a bug. Please help me to fix one.

Also take a look at the window when I set

(setq *grizzl-read-max-results* 5)

screen shot 2013-12-14 at 16 41 40

please notice a empty line appear in the bottom.

Bad bounding indices: 0, 10 when using the version on MELPA Stable

OSX, emacs 25 (railwaymac), grizzl 0.1.1

When using projectile with grizzl, after filtering out results a little, I'm getting the following error:

Invalid face reference: diredp-symlink [26 times]
Error in post-command-hook (#[nil "\303\304 � #�\305�
"\207" [index grizzl-current-result prompt grizzl-search minibuffer-contents grizzl-display-result] 4]): (error "Bad bounding indices: 0, 10")
Invalid face reference: diredp-symlink [33 times]

This error does not happen when using grizzl on MELPA.

Usage of non existing faces

I've got some errors with the faces used by grizzl.

For instance on my install I don't have:

outline-3
diredp-symlink

Maybe it would be good to have this faces as variables so one can customise them?

Highlight matched characters

While users are typing they'd be able to get a better feel for the matching algorithm if the matched portions of the text they've entered are highlighted in the list of candidates.

I guess seeing a portion of this screencast will make more clear what I mean (go to around 3:00).

Performance of grizzl-search on large repo via fiplr is poor

I'm using grizzl via fiplr on Facebook's Objective-C repository (about 80000 files in the index). I'm on a MacBook Pro 2.8 GHz Intel Core i7 on OS X 10.9.2, Emacs 24.3.1.

I found that fiplr-find-file's interactive performance is pretty poor on such a large index(about half a second between characters typed).

I used elp (http://www.emacswiki.org/emacs/EmacsLispProfiler) to profile a search for the string "FB" in this repo, and confirmed grizzl-search is the bottleneck, taking about 1.5 (!!!) seconds each time it's called.

Here's the trace, instrumenting 'grizzl', 'fiplr', and 'cl':

https://gist.github.com/bgertzfield/10773115

Read window too small

I'm finding that the completing read window appears too small -- if I set the number added to the number of matches (https://github.com/d11wtq/grizzl/blob/master/grizzl-read.el#L141) to 4 rather than 2, it works perfectly; otherwise the bottom is cut off and I can't see what I'm typing ...

This is completely awesome, by the way. Apart from the matching algorithm itself, the look and feel of the completing read is the best I've come across.

Use dash.el for list manipulation

You'll simplify your life a lot if you use dash.el instead of the built-in cl. dash.el has much more expressive API, excellent performance characteristics and is pretty well maintained.

weird ranking

I use grizzle with projectile and sometimes ranking surprises me. I've entered endless as a query and the highest ranking member is seller/unused_templates.sh, while seller/static/js/react/src/components/common/endless.js is not even visible. If I add js to the query, it obviously improves, endless.js ends up being third.

Is it possible to tune matcher somehow so that it ranks multiple consecutive characters higher?

Allow scrolling in grizzl-completing-read display

It would be great to make grizzl-read-max-results just affect the number of lines displayed in the pop-up, and let the user scroll outside that range if many items match the search. Also, scrolling up or down past the limit shouldn't (beep), it should wrap around.

Cannot display results if there contains fonts with different hight

Results containing Chinese words will not display in some situations. I guess the font height is the cause.

I played with grizzle-display-result and found the following can solve the problem but not ideal. Just change (+ 2 (length matches)) to (* 2 (length matches)):

(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))))))

Another solution is that I modify the Chinese font the same height with the unicode.

void symbols: subseq, find-if, caddr

(emacs noob here). When installing on Emacs for OS X (v24.4) I had to change subseq, find-if and caddr to their cl-* equivalents to prevent it complaining about void symbols. Should this be necessary?

got error if trying the example code in the wiki

evaluate the following:
;; define a search index
(defvar search-index (grizzl-make-index '("one" "two" "three" "four")))

;; prompt the use to pick from the index
(grizzl-completing-read "Number: " search-index)

The error detail is:
Error in post-command-hook (#[nil \303\304 � #�\305�
"\207 [index grizzl-current-result prompt grizzl-search minibuffer-contents grizzl-display-result] 4]): (void-function delete-all-overlays)

I am running emacs 24.2.1, installed grizzl with built in package management. installed grizzl version: 20130810.1902

Please help to have a look if you can repro or give me some tips on debugging it on my machine.

Really want to find a similar mode like vim CtrlP mode. Grizzl is just what I want. Currently my projectile find file doesn't work, same error as above.

Thanks

Is there an easy way to maintain 24.3 compatibility? And maybe even 24.2?

I would really like to use grizzl and fiplr but using lexical-let* means that I can't use this in 24.3 AFAIK.

Also, with #11 I cannot get grizzl to run on 24.2.

While I love to use fancy new language features and experimental code, it would be nice at a minimum to maintain support with the latest stable release of Emacs.

I really like grizzl and am working on a git-ubiquitous-mode but cannot really test since I cannot get grizzl working on my everyday Emacs which is 24.3 stable.

Grizzl Display Error

Hello,

I am using Fiplr (which I know uses Girzzl). I've posted this on Fiplr's issues page, but I did not get any responses. I am hoping that this is either a grizzl issue, or someone here could have faced it here.

I am using Emacs for OS X 24.4.1 on Yosemite. When the screen is not full screen, Fiplr doesn't show any results until I write enough to get a 1 or 2 matches. When I delete the searching text, it goes back to being empty.

When I use it on a 13-inch non-Retina MacBook Air display, it also does not work even on full-screen. This leads me to suspect that it depends on the screen size. Is there a reason for that? Why does fuzzy matching only work at some minimum screen size?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.