[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/flx 6189f05c7e 004/182: add flx, ido implementation and he
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/flx 6189f05c7e 004/182: add flx, ido implementation and helm experiment |
Date: |
Tue, 13 Dec 2022 03:59:13 -0500 (EST) |
branch: elpa/flx
commit 6189f05c7e45688f0e83e28941bf6b6914b75b95
Author: Le Wang <le.wang@agworld.com.au>
Commit: Le Wang <le.wang@agworld.com.au>
add flx, ido implementation and helm experiment
---
flx-ido.el | 73 ++++++++++++++
flx-scratch-helm.el | 69 ++++++++++++++
flx.el | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 410 insertions(+)
diff --git a/flx-ido.el b/flx-ido.el
new file mode 100644
index 0000000000..0d66727099
--- /dev/null
+++ b/flx-ido.el
@@ -0,0 +1,73 @@
+;;;
+;;; credit to scott frazer's blog entry
here:http://scottfrazersblog.blogspot.com.au/2009/12/emacs-better-ido-flex-matching.html
+;;;
+
+(require 'ido)
+(require 'flx)
+
+;;; dynamically bound by ido
+(defvar hist)
+
+(defun flx-ido-match (query items)
+ "Better sorting for flx ido matching."
+ (if (zerop (length query))
+ items
+ (let ((cache (if (eq hist 'ido-file-history)
+ flx-file-cache
+ flx-strings-cache))
+ matches)
+ (mapc (lambda (item)
+ (let ((score (flx-score item query cache)))
+ (when score
+ (push (cons item (car score)) matches))))
+ items)
+ (mapcar 'car (if ido-rotate
+ matches
+ (sort matches (lambda (x y) (> (cdr x) (cdr y)))))))))
+
+(defvar flx-ido-use t
+ "*Use flx matching for ido.")
+
+(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-use
+ (setq ad-return-value (flx-ido-match ido-text (ad-get-arg 0)))
+ ad-do-it))
+
+
+(setq ido-enable-flex-matching t)
+
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;; testing
+
+;; (defvar ido-enable-replace-completing-read t
+;; "If t, use ido-completing-read instead of completing-read if possible.
+
+;; Set it to nil using let in around-advice for functions where the
+;; original completing-read is required. For example, if a function
+;; foo absolutely must use the original completing-read, define some
+;; advice like this:
+
+;; (defadvice foo (around original-completing-read-only activate)
+;; (let (ido-enable-replace-completing-read) ad-do-it))")
+
+;; ;; Replace completing-read wherever possible, unless directed otherwise
+;; (defadvice completing-read
+;; (around use-ido-when-possible activate)
+;; (if (or (not ido-enable-replace-completing-read) ; Manual override
disable ido
+;; (and (boundp 'ido-cur-list)
+;; ido-cur-list)) ; Avoid infinite loop from ido calling this
+;; ad-do-it
+;; (let ((allcomp (all-completions "" collection predicate)))
+;; (if allcomp
+;; (setq ad-return-value
+;; (ido-completing-read prompt
+;; allcomp
+;; nil require-match initial-input hist
def))
+;; ad-do-it))))
+
+;; (ido-everywhere t)
+
+(provide 'flx-ido)
diff --git a/flx-scratch-helm.el b/flx-scratch-helm.el
new file mode 100644
index 0000000000..859f466309
--- /dev/null
+++ b/flx-scratch-helm.el
@@ -0,0 +1,69 @@
+(require 'flx)
+(require 'flx-test-list)
+
+(defun helm-mp-flx-propertize (str score)
+ "Return propertized string according to score."
+ (let ((block-started (cadr score))
+ (last-char nil))
+ (loop for char in (cdr score)
+ do (progn
+ (when (and last-char
+ (not (= (1+ last-char) char)))
+ (put-text-property block-started (1+ last-char) 'face
'helm-match str)
+ (setq block-started char))
+ (setq last-char char)))
+ (put-text-property block-started (1+ last-char) 'face 'helm-match str)
+ (format "% [%s]" str (car score))))
+
+(defun flx-helm-candidate-transformer (candidates)
+ "We score candidate and add the score info for later use.
+
+The score info we add here is later removed with another filter."
+ (if (zerop (length helm-pattern))
+ candidates
+ (let* ((mp-3-patterns (helm-mp-3-get-patterns helm-pattern))
+ (flx-pattern (cdar mp-3-patterns))
+ (patterns (cons (cons 'identity
+ (mapconcat
+ #'identity
+ (split-string flx-pattern "" t)
+ ".*"))
+ (cdr mp-3-patterns)))
+ res)
+ (setq res (loop for candidate in candidates
+ for matched = (loop for (predicate . regexp) in patterns
+ always (funcall predicate
(string-match regexp (helm-candidate-get-display candidate))))
+ if matched
+ collect (let ((score (flx-score candidate flx-pattern
flx-file-cache)))
+ (unless (consp candidate)
+ (setq candidate (cons (copy-sequence
candidate) candidate)))
+ (setcdr candidate (cons (cdr candidate) score))
+ candidate)))
+ (sort res
+ (lambda (a b)
+ (> (caddr a) (caddr b))))
+ (loop for item in res
+ for index from 0
+ for score = (cddr item)
+ do (progn
+ ;; highlight first 20 matches
+ (when (and (< index 20) (> (car score) 0))
+ (setcar item (helm-mp-flx-propertize (car item) score)))
+ (setcdr item (cadr item))))
+ res)))
+
+(defun flx-helm-test-candidates ()
+ foo-list)
+
+(setq flx-helm-candidate-list-test
+ '((name . "flx candidate-list-test")
+ (candidates . flx-helm-test-candidates)
+ (candidate-transformer flx-helm-candidate-transformer)
+ (volatile)
+ (match-strict identity)
+ ))
+
+
+(defun flx-helm-demo ()
+ (interactive)
+ (helm :sources '(flx-helm-candidate-list-test)))
diff --git a/flx.el b/flx.el
new file mode 100644
index 0000000000..65d223dcd6
--- /dev/null
+++ b/flx.el
@@ -0,0 +1,268 @@
+;;; credit note: Daniel Skarda ido-speed-hack for bitmap idea
+;;; not necessary as we aren't using bitmap caching
+;;;
+;;;
+;;; credit to scott frazer's blog entry
here:http://scottfrazersblog.blogspot.com.au/2009/12/emacs-better-ido-flex-matching.html
+
+;;; Use defsubst instead of defun
+
+;;; Notes:
+;;;
+;;; * Using bitmaps to check for matches worked out to be SLOWER than just
+;;; scanning the string and using `flx-get-matches'.
+;;;
+;;; * Consing causes GC, which can often slowdown Emacs more than the benefits
+;;; of an optimization.
+;;;
+
+
+
+(eval-when-compile
+ (require 'cl))
+
+(defun flx-get-hash-for-string (str heatmap-func)
+ "Return hash-table for string where keys are characters value
+ is a sorted list of indexes for character occurrences."
+ (let* ((res (make-hash-table :test 'eq :size 32))
+ (str-len (length str))
+ char)
+ (loop for index from (1- str-len) downto 0
+ do (progn
+ (setq char (downcase (aref str index)))
+ (push index (gethash char res))))
+ (puthash 'heatmap (funcall heatmap-func str) res)
+ res))
+
+;;; Do we need more word separators than ST?
+(defsubst flx-is-word (char)
+ "returns t if char is word"
+ (and char
+ (not (memq char '(?\ ?- ?_ ?. ?/ ?\\)))))
+
+(defsubst flx-is-capital (char)
+ "returns t if char is word"
+ (and char
+ (and (<= char ?Z)
+ (<= ?A char))))
+
+(defsubst flx-is-boundary (last-char char)
+ (or (flx-is-capital char)
+ (null last-char)
+ (and (not (flx-is-word last-char))
+ (flx-is-word char))))
+
+(defsubst flx-inc-vec (vec &optional inc beg end)
+ "increment each element of vectory by INC(default=1)
+from BEG (inclusive) to end (not inclusive).
+"
+ (or inc
+ (setq inc 1))
+ (or beg
+ (setq beg 0))
+ (or end
+ (setq end (length vec)))
+ (while (< beg end)
+ (incf (aref vec beg) inc)
+ (incf beg))
+ vec)
+
+;; So we store one fixnum per character. Is this too memory inefficient?
+(defun flx-get-heatmap-str (str &optional group-separator)
+ "Generate heat map vector of string.
+
+See documentation for logic."
+ (let* ((str-len (length str))
+ (str-last-index (1- str-len))
+ ;; ++++ base
+ (scores (make-vector str-len -35))
+ (penalty-lead ?.)
+ (groups-alist (list (list -1 0))))
+ ;; ++++ final char bonus
+ (incf (aref scores str-last-index) 1)
+ ;; Establish baseline mapping
+ (loop for char across str
+ for index from 0
+ with last-char = nil
+ with group-word-count = 0
+ do (progn
+ (let ((effective-last-char
+ ;; before we find any words, all separaters are
+ ;; considered words of length 1. This is so "foo/__ab"
+ ;; gets penalized compared to "foo/ab".
+ (if (zerop group-word-count) nil last-char)))
+ (when (flx-is-boundary effective-last-char char)
+ (setcdr (cdar groups-alist) (cons index (cddar
groups-alist))))
+ (when (and (not (flx-is-word last-char))
+ (flx-is-word char))
+ (incf group-word-count)))
+ ;; ++++ -45 penalize extension
+ (when (eq last-char penalty-lead)
+ (incf (aref scores index) -45))
+ (when (eq group-separator char )
+ (setcar (cdar groups-alist) group-word-count)
+ (setq group-word-count 0)
+ (push (nconc (list index group-word-count)) groups-alist))
+ (if (= index str-last-index)
+ (setcar (cdar groups-alist) group-word-count)
+ (setq last-char char))))
+ (let* ((group-count (length groups-alist))
+ (separator-count (1- group-count)))
+ ;; ++++ slash group-count penalty
+ (unless (zerop separator-count)
+ (flx-inc-vec scores (* -2 group-count)))
+ ;; score each group further
+ (loop for group in groups-alist
+ for index from separator-count downto 0
+ with last-group-limit = nil
+ do (let ((group-start (car group))
+ (word-count (cadr group))
+ ;; this is the number of effective word groups
+ (words-length (length (cddr group)))
+ (basepath-p (not last-group-limit)))
+ (let (num)
+ (setq num
+ (if basepath-p
+ (+ 35
+ ;; ++++ basepath separator-count boosts
+ (if (> separator-count 1)
+ (1- separator-count)
+ 0)
+ ;; ++++ basepath word count penalty
+ (- word-count))
+ ;; ++++ non-basepath penalties
+ (if (= index 0)
+ -3
+ (+ -5 (1- index)))))
+ (flx-inc-vec scores num (1+ group-start) last-group-limit))
+ (loop for word in (cddr group)
+ for word-index from (1- words-length) downto 0
+ with last-word = (or last-group-limit
+ str-len)
+ do (progn
+ (incf (aref scores word)
+ ;; ++++ beg word bonus AND
+ 85)
+ (loop for index from word below last-word
+ for char-i from 0
+ do (incf (aref scores index)
+ (-
+ ;; ++++ word order penalty
+ (* -3 word-index)
+ ;; ++++ char order penalty
+ char-i)))
+ (setq last-word word)))
+ (setq last-group-limit (1+ group-start)))))
+ scores))
+
+(defun flx-get-heatmap-file (filename)
+ "Return heatmap vector for filename."
+ (flx-get-heatmap-str filename ?/))
+
+
+(defsubst flx-bigger-sublist (sorted-list val)
+ "return sublist bigger than VAL from sorted SORTED-LIST
+
+ if VAL is nil, return entire list."
+ (if val
+ (loop for sub on sorted-list
+ do (when (> (car sub) val)
+ (return sub)))
+ sorted-list))
+
+(defun flx-get-matches (hash query &optional greater-than q-index)
+ "Return list of all unique indexes into str where query can match.
+
+That is all character sequences of query that occur in str are returned.
+
+HASH accept as the cached analysis of str.
+sstr
+e.g. (\"aab\" \"ab\") returns
+ '((0 2) (1 2)
+"
+
+ (setq q-index (or q-index 0))
+ (let* ((q-char (aref query q-index))
+ (indexes (flx-bigger-sublist
+ (gethash q-char hash) greater-than)))
+ (if (< q-index (1- (length query)))
+ (apply ; `mapcan'
+ 'nconc
+ (mapcar
+ (lambda (index)
+ (let ((next-matches-for-rest (flx-get-matches hash query index
(1+ q-index))))
+ (when next-matches-for-rest
+ (mapcar (lambda (match)
+ (cons index match))
+ next-matches-for-rest))))
+ indexes))
+ (mapcar 'list indexes))))
+
+(defun flx-make-filename-cache ()
+ "Return cache hashtable appropraite for storeing filenames."
+ (flx-make-string-cache 'flx-get-heatmap-file))
+
+(defun flx-make-string-cache (&optional heat-func)
+ "Return cache hashtable appropraite for storeing strings."
+ (let ((hash (make-hash-table :test 'equal
+ :size 4096)))
+ (puthash 'heatmap-func (or heat-func 'flx-get-heatmap-str) hash)
+ hash))
+
+(defun flx-process-cache (str cache)
+ "Get calculated heatmap from cache, add it if necessary."
+ (let ((res (when cache
+ (gethash str cache))))
+ (or res
+ (progn
+ (setq res (flx-get-hash-for-string
+ str
+ (or (and cache (gethash 'heatmap-func cache))
+ 'flx-get-heatmap-str)))
+ (when cache
+ (puthash str res cache))
+ res))))
+
+
+(defun flx-score (str query &optional cache)
+ "return best score matching QUERY against STR"
+ (unless (or (zerop (length query))
+ (zerop (length str)))
+ (let* ((info-hash (flx-process-cache str cache))
+ (heatmap (gethash 'heatmap info-hash))
+ (matches (flx-get-matches info-hash query))
+ (best-score nil))
+ (mapc (lambda (match-vector)
+ (let ((score 0)
+ (contiguous-count 0)
+ last-match)
+ (loop for index in match-vector
+ do (progn
+ (if (and last-match
+ (= (1+ last-match) index))
+ (incf contiguous-count)
+ (setq contiguous-count 0))
+ (incf score (aref heatmap index))
+ (when (> contiguous-count 0)
+ (incf score (+ 45 (* 15 (min contiguous-count
4)))))
+ (setq last-match index)))
+ (if (or (null best-score)
+ (> score (car best-score)))
+ (setq best-score (cons score match-vector)))))
+ matches)
+ best-score)))
+
+
+(defvar flx-file-cache (flx-make-filename-cache)
+ "Cached heatmap info about strings.")
+
+(defvar flx-strings-cache (flx-make-string-cache)
+ "Cached heatmap info about filenames.")
+
+
+
+(provide 'flx)
+
+
+;;; macro expanded
+
+
- [nongnu] elpa/flx 3054a8038d 112/182: Fix typo in README.md, (continued)
- [nongnu] elpa/flx 3054a8038d 112/182: Fix typo in README.md, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx f0c3157268 114/182: Increase default flx-ido-threshold to 6000, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx f5fda2897f 125/182: enable lexical-binding, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 3cd3bf7687 129/182: add perf test-case, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 2b31df9ed0 140/182: Make dynamic programming test even more "difficult", ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx cd557fcb00 051/182: fix C-SPC, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 9e7749e9e6 067/182: Merge pull request #27 from bbatsov/fix-capital-check, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 7424f54816 079/182: flx-ido: don't require cl; flx requires it anyway, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 3bcaf03434 002/182: add readme, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx bdfd9d98aa 014/182: add travis status image, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 6189f05c7e 004/182: add flx, ido implementation and helm experiment,
ELPA Syncer <=
- [nongnu] elpa/flx 4c67675437 032/182: add GC tuning information to README, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 114cf369cb 020/182: add ido installation instructions, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 7452ab1c79 021/182: README update, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx f566207c82 015/182: travis config update, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 01eef11b96 033/182: small efficiency fix, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 44951ac311 042/182: simplify caching, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx cc3258bb10 038/182: cache key should be based on whole input, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 771f61f3fd 016/182: update test list, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx a792c2c5f1 053/182: change advice to before, fix comments, ELPA Syncer, 2022/12/13
- [nongnu] elpa/flx 61dcc4f563 089/182: Reset caches on file reload., ELPA Syncer, 2022/12/13