;;; flx-ido.el --- flx integration for ido ;; Copyright © 2013 Le Wang ;; Author: Le Wang ;; Maintainer: Le Wang ;; Description: flx integration for ido ;; Created: Sun Apr 21 20:38:36 2013 (+0800) ;; Version: 0.2 ;; URL: https://github.com/lewang/flx ;; Package-Requires: ((flx "0.1")) ;; This file is NOT part of GNU Emacs. ;;; License ;; 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, 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; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth ;; Floor, Boston, MA 02110-1301, USA. ;;; Commentary: ;; This package provides a more powerful alternative to `ido-mode''s ;; built-in flex matching. ;;; Acknowledgments ;; Scott Frazer's blog entry http://scottfrazersblog.blogspot.com.au/2009/12/emacs-better-ido-flex-matching.html ;; provided a lot of inspiration. ;; ;; ido-hacks was helpful for ido optimization and fontification ideas ;;; Installation: ;; Add the following code to your init file: ;; ;; (require 'flx-ido) ;; (ido-mode 1) ;; (ido-everywhere 1) ;; (flx-ido-mode 1) ;; ;; disable ido faces to see flx highlights. ;; (setq ido-use-faces nil) ;;; Code: (eval-when-compile (require 'cl)) (require 'ido) (require 'flx) (defcustom flx-ido-threshhold 6000 "flx will not kick in until collection is filtered below this size with \"flex\"." :group 'ido) (defcustom flx-ido-use-faces t "Use `flx-highlight-face' to indicate characters contributing to best score." :group 'ido) (unless (fboundp 'ido-delete-runs) (defun ido-delete-runs (list) "Delete consecutive runs of same item in list. Comparison done with `equal'. Runs may loop back on to the first item, in which case, the ending items are deleted." (let ((tail list) before-last-run) (while tail (if (consp (cdr tail)) (if (equal (car tail) (cadr tail)) (setcdr tail (cddr tail)) (setq before-last-run tail) (setq tail (cdr tail))) (setq tail (cdr tail)))) (when (and before-last-run (equal (car list) (cadr before-last-run))) (setcdr before-last-run nil))) list)) (defvar flx-ido-narrowed-matches-hash (make-hash-table :test 'equal)) (defun flx-ido-narrowed (query items) "Get the value from `flx-ido-narrowed-matches-hash' with the longest prefix match." (if (zerop (length query)) (list t (nreverse items)) (let ((query-key (flx-ido-key-for-query query)) best-match exact res) (loop for key being the hash-key of flx-ido-narrowed-matches-hash do (when (and (>= (length query-key) (length key)) (eq t (compare-strings query-key 0 (min (length query-key) (length key)) key 0 nil)) (or (null best-match) (> (length key) (length best-match)))) (setq best-match key) (when (= (length key) (length query-key)) (setq exact t) (return)))) (setq res (cond (exact (gethash best-match flx-ido-narrowed-matches-hash)) (best-match (flx-ido-undecorate (gethash best-match flx-ido-narrowed-matches-hash))) (t (flx-ido-undecorate items)))) (list exact res)))) (defun flx-ido-undecorate (strings) (flx-ido-decorate strings t)) (defun flx-ido-decorate (things &optional clear) (if flx-ido-use-faces (let ((decorate-count (min ido-max-prospects (length things)))) (nconc (loop for thing in things for i from 0 below decorate-count collect (if clear (flx-propertize thing nil) (flx-propertize (car thing) (cdr thing)))) (if clear (nthcdr decorate-count things) (mapcar 'car (nthcdr decorate-count things))))) (if clear things (mapcar 'car things)))) (defun flx-ido-match-internal (query items) (if (< (length items) flx-ido-threshhold) (let* ((matches (loop for item in items for string = (if (consp item) (car item) item) for score = (flx-score string query flx-file-cache) if score collect (cons item score) into matches finally return matches))) (flx-ido-decorate (ido-delete-runs (sort matches (lambda (x y) (> (cadr x) (cadr y))))))) (let ((regexp (mapconcat 'identity (split-string query "" t) ".*"))) (loop for item in items if (string-match regexp (if (consp item) (car item) item)) collect item into matches finally return matches)))) (defun flx-ido-key-for-query (query) (concat ido-current-directory query)) (defun flx-ido-cache (query items) (if (memq ido-cur-item '(file dir)) items (puthash (flx-ido-key-for-query query) items flx-ido-narrowed-matches-hash))) (defun flx-ido-match (query items) "Better sorting for flx ido matching." (destructuring-bind (exact res-items) (flx-ido-narrowed query items) (flx-ido-cache query (if exact res-items (flx-ido-match-internal query res-items))))) (defadvice ido-exit-minibuffer (around flx-ido-undecorate activate) "Remove flx properties after." (let* ((obj (car ido-matches)) (str (if (consp obj) (car obj) obj))) (when (and flx-ido-mode str) (remove-text-properties 0 (length str) '(face flx-highlight-face) str))) ad-do-it) (defadvice ido-read-internal (before flx-ido-reset-hash activate) "Clear flx narrowed hash beforehand." (when flx-ido-mode (clrhash flx-ido-narrowed-matches-hash))) (defadvice ido-restrict-to-matches (before flx-ido-reset-hash activate) "Clear flx narrowed hash." (when flx-ido-mode (clrhash flx-ido-narrowed-matches-hash))) (defadvice ido-set-matches-1 (around flx-ido-set-matches-1 activate) "Choose between the regular ido-set-matches-1 and my-ido-fuzzy-match" (if flx-ido-mode (setq ad-return-value (flx-ido-match ido-text (ad-get-arg 0))) ad-do-it)) ;;;###autoload (define-minor-mode flx-ido-mode "Toggle flx ido mode" :init-value nil :lighter "" :group 'ido :global t) (provide 'flx-ido) ;;; flx-ido.el ends here