jojojames / fussy Goto Github PK
View Code? Open in Web Editor NEWEmacs completion-style leveraging flx
License: GNU General Public License v3.0
Emacs completion-style leveraging flx
License: GNU General Public License v3.0
Heyo!
First off, I tried to write a benchmark for fuzzy Emacs completions styles at axelf4/emacs-completion-bench. It would be great if you could take a look and see whether there are any glaring mistakes in how the fussy performance is measured.
Second, I run into panics with two of the backends on the benchmark instances. With flx-rs:
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /home/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/flx-rs-0.1.4/src/search.rs:72:53
and with sublime_fuzzy:
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/dynmod.rs:15:46
Should be enough to uncomment those here and running the benchmark in order to reproduce.
Might be more performant to write a cache for the history compare functions.
Have something like this already but need some benchmark tests to show it's actually an improvement over the previous 'loop over all elements of the list to find a match' version.
(defun fussy-histlen< (c1 c2)
"Return t if C1 occurred more recently than C2.
Check C1 and C2 in `minibuffer-history-variable'."
(when (fussy--history-enabled-p)
(let* ((hist (fussy--history-hash-table))
(c1-pos (or (gethash c1 hist) 100000000))
(c2-pos (or (gethash c2 hist) 100000000)))
(if (= c1-pos c2-pos)
(fussy-strlen< c1 c2)
(< c1-pos c2-pos)))))
(defun fussy-histlen->strlen< (c1 c2)
"Return t if C1 occurs more recently than C2 or is shorter than C2."
(if (fussy--history-enabled-p)
(let* ((hist (fussy--history-hash-table))
(c1-pos (or (gethash c1 hist) 100000000))
(c2-pos (or (gethash c2 hist) 100000000)))
(if (= c1-pos c2-pos)
(fussy-strlen< c1 c2)
(< c1-pos c2-pos)))
(fussy-strlen< c1 c2)))
(defvar fussy--history-hash-table nil)
(defun fussy--history-hash-table ()
"Return hash table representing `minibuffer-history-variable'.
Key is history string and Value is the history position."
(if fussy--history-hash-table
fussy--history-hash-table
(let ((hist (fussy--history-enabled-p)))
(when hist
(setf fussy--history-hash-table
(make-hash-table :test 'equal
:size (length hist)))
(cl-loop for index from 0
for item in hist
do (puthash item index fussy--history-hash-table))
fussy--history-hash-table))))
(defun fussy--history-enabled-p ()
"Return whether or not `minibuffer-history-value' is available.
Return value is the history variable's value."
(and (not (eq minibuffer-history-variable t))
(symbol-value minibuffer-history-variable)))
I mentioned in another issue that orderless flex is too slow for me. It's especially slow in things like M-x that have a significant number of results.
I don't know how feasible it would be, but I wonder about maintaining the previous results and match string and if the previous match string is the prefix of the current match string, start with those results and filter them, rather than starting with the entire set.
For example, if I type to
, the filtered results would be cached, then when I added another g
, making the search string tog
, the previous results that were cached would be fed into the filter, rather than the initial set of all commands.
Backspacing would still be slow, though the caching mechanism could be elaborated to be a stack so the first several backspaces would be instant.
Installing package-build (20220625.1438)... Symbol's function definition is void: loaddefs-generate
We may be able to achieve better performance inlining some functions that are hot spots.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Inline-Functions.html
I'm not an expert on this and would need some good benchmarks to make sure it's worth it. Help wanted!
The repoducable code is here:
(let ((fussy-filter-fn #'fussy-filter-flex)
(fussy-score-fn #'fussy-fzf-native-score))
(fussy-score '("ポケモン.txt") "txt"))
;; => error: (args-out-of-range 13 16)
It seems that the position returned by fussy-fzf-native
is calculated as a byte string instead of a string, so fussy-propertize-common-part
results in args-out-of-range
.
(fussy-fzf-native-score "ポケモン.txt" "txt")
;; => (80 13 14 15)
(fussy-fzf-native-score "pckemon.txt" "txt")
;; => (80 8 9 10)
I think this is a fzf-native issue, but I would be happy to have a workaround because fzf-native does not seem to take care of non-ascii (e.g. here).
Debugger entered--Lisp error: (args-out-of-range 13 16)
add-face-text-property(13 16 completions-common-part nil #("ポケモン.txt" 0 1 (completion-score 80)))
(let ((block-started (car (cdr score))) (last-char nil) (str (if (consp obj) (car obj) obj))) (let ((--dolist-tail-- (cdr score))) (while --dolist-tail-- (let ((char (car --dolist-tail--))) (if (and last-char (not (= ... char))) (progn (add-face-text-property block-started (1+ last-char) 'completions-common-part nil str) (setq block-started char))) (setq last-char char) (setq --dolist-tail-- (cdr --dolist-tail--))))) (add-face-text-property block-started (1+ last-char) 'completions-common-part nil str) (if (and last-char (> (length str) (+ 2 last-char))) (progn (add-face-text-property (1+ last-char) (+ 2 last-char) 'completions-first-difference nil str))) (if (consp obj) (cons str (cdr obj)) str))
(if (<= (length score) 1) obj (let ((block-started (car (cdr score))) (last-char nil) (str (if (consp obj) (car obj) obj))) (let ((--dolist-tail-- (cdr score))) (while --dolist-tail-- (let ((char (car --dolist-tail--))) (if (and last-char (not ...)) (progn (add-face-text-property block-started ... ... nil str) (setq block-started char))) (setq last-char char) (setq --dolist-tail-- (cdr --dolist-tail--))))) (add-face-text-property block-started (1+ last-char) 'completions-common-part nil str) (if (and last-char (> (length str) (+ 2 last-char))) (progn (add-face-text-property (1+ last-char) (+ 2 last-char) 'completions-first-difference nil str))) (if (consp obj) (cons str (cdr obj)) str)))
fussy-propertize-common-part(#("ポケモン.txt" 0 1 (completion-score 80)) (80 13 14 15))
funcall(fussy-propertize-common-part #("ポケモン.txt" 0 1 (completion-score 80)) (80 13 14 15))
(setq x (funcall fussy-propertize-fn x score))
(progn (setq x (funcall fussy-propertize-fn x score)))
(if (fussy--should-propertize-p) (progn (setq x (funcall fussy-propertize-fn x score))))
(progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn (setq x (funcall fussy-propertize-fn x score)))) (setq result (cons x result)))
(if (and score (> (car score) 0)) (progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn (setq x (funcall fussy-propertize-fn x score)))) (setq result (cons x result))))
(let ((score (funcall fussy-score-fn x string cache))) (if (and score (> (car score) 0)) (progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn (setq x (funcall fussy-propertize-fn x score)))) (setq result (cons x result)))))
(if (> (length x) fussy-max-word-length-to-score) (setq result (cons x result)) (let ((score (funcall fussy-score-fn x string cache))) (if (and score (> (car score) 0)) (progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn (setq x (funcall fussy-propertize-fn x score)))) (setq result (cons x result))))))
(let ((x (car --dolist-tail--))) (setq x (copy-sequence x)) (if (> (length x) fussy-max-word-length-to-score) (setq result (cons x result)) (let ((score (funcall fussy-score-fn x string cache))) (if (and score (> (car score) 0)) (progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn (setq x ...))) (setq result (cons x result)))))) (setq --dolist-tail-- (cdr --dolist-tail--)))
(while --dolist-tail-- (let ((x (car --dolist-tail--))) (setq x (copy-sequence x)) (if (> (length x) fussy-max-word-length-to-score) (setq result (cons x result)) (let ((score (funcall fussy-score-fn x string cache))) (if (and score (> (car score) 0)) (progn (put-text-property 0 1 'completion-score (car score) x) (if (fussy--should-propertize-p) (progn ...)) (setq result (cons x result)))))) (setq --dolist-tail-- (cdr --dolist-tail--))))
(let ((--dolist-tail-- candidates)) (while --dolist-tail-- (let ((x (car --dolist-tail--))) (setq x (copy-sequence x)) (if (> (length x) fussy-max-word-length-to-score) (setq result (cons x result)) (let ((score (funcall fussy-score-fn x string cache))) (if (and score (> ... 0)) (progn (put-text-property 0 1 ... ... x) (if ... ...) (setq result ...))))) (setq --dolist-tail-- (cdr --dolist-tail--)))))
(let ((result 'nil)) (let ((--dolist-tail-- candidates)) (while --dolist-tail-- (let ((x (car --dolist-tail--))) (setq x (copy-sequence x)) (if (> (length x) fussy-max-word-length-to-score) (setq result (cons x result)) (let ((score ...)) (if (and score ...) (progn ... ... ...)))) (setq --dolist-tail-- (cdr --dolist-tail--))))) (reverse result))
fussy-score(("ポケモン.txt") "txt")
(let ((fussy-filter-fn #'fussy-filter-flex) (fussy-score-fn #'fussy-fzf-native-score)) (fussy-score '("ポケモン.txt") "txt"))
(progn (let ((fussy-filter-fn #'fussy-filter-flex) (fussy-score-fn #'fussy-fzf-native-score)) (fussy-score '("ポケモン.txt") "txt")))
elisp--eval-last-sexp(t)
eval-last-sexp(t)
eval-print-last-sexp(nil)
funcall-interactively(eval-print-last-sexp nil)
command-execute(eval-print-last-sexp)
This occurs with vertico, although unsure if that's related.
Minimal init.el that reproduces this:
;; Bootstrap straight.
(setq-default straight-repository-branch "develop")
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 6))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(setq-default straight-use-package-by-default t)
(use-package vertico
:hook after-init)
(use-package orderless
:defer nil)
(use-package fussy
:custom
(fussy-use-cache t) ;; comment this out to restore match highlighting
(fussy-filter-fn 'fussy-filter-orderless) ;; and `fussy-filter-orderless-flex'
(completion-styles '(fussy)))
with fussy-use-cache t
partial matches in M-x
and find-file
lose highlight faces, but without caching highlighting works correctly. This applies to fussy-filter-orderless
and fussy-filter-orderless-flex
Might be able to implement better ivy support taking inspiration from: https://github.com/Silex/ivy-fuz.el/blob/f171ac73422a4bae1503d63d804e691482ed35b2/ivy-fuz.el#L101
Would prefer ivy just support completion-styles too but this seems reasonable enough.
Started happening when I enter a space in my completion after 2148fd7
Error in post-command-hook (vertico--exhibit): (wrong-type-argument window-valid-p #<window 15>)
Haven't had time to debug further yet.
My config:
(use-package orderless
:straight t
:commands (orderless-filter))
(use-package fuz
:demand t
:config
(unless (require 'fuz-core nil t)
(fuz-build-and-load-dymod)))
(use-package fussy
:straight (fussy :type git :host github :repo "jojojames/fussy")
:demand t
:config
(setq fussy-filter-fn 'fussy-filter-orderless
fussy-score-fn 'fussy-fuz-score)
(push 'fussy completion-styles)
(setq
;; For example, project-find-file uses 'project-files which uses
;; substring completion by default. Set to nil to make sure it's using
;; flx.
completion-category-defaults nil
completion-category-overrides nil))
For whatever reason the link in the README.org file does not work when clicking on it on the main github page of this project.
M-x apr cus
does not retrieve customize-apropos
.
when using fussy-20221010.2026/ installed from melpa
I have
(use-package fussy
:ensure t
:custom
(fussy-compare-same-score-fn 'fussy-histlen->strlen<)
(fussy-max-limit-preferred-candidate-fn 'fussy-histlen->strlen<)
(fussy-filter-fn 'fussy-filter-orderless-flex)
(fussy-default-regex-fn 'fussy-pattern-flex-2)
:config
(push 'fussy completion-styles)
(setq
completion-category-defaults nil
completion-category-overrides nil))
M-x apr cus
works if instead I use orderless with
(use-package orderless
:ensure t
:custom
(orderless-matching-styles '(orderless-flex orderless-regexp orderless-literal))
(completion-styles '(orderless basic))
)
fussy-score-fn - flx-score
C-x b
Type 'm'
Expect to see 'Messages' but score is lower than 0 so is filtered out.
First, thanks for creating this package. It's really great to use so far.
Now, following the README, I setup the advice for fido mode to set the completion styles. However, seeing this issue on the Orderless github, I am uncertain whether I still need to add a hook to set the styles after setting up the advice, or if setting the advice is enough.
Any help is appreciated.
@jcs090218 This would be interesting to port to a native module, considering it's in C.
https://github.com/nvim-telescope/telescope-fzf-native.nvim/tree/main/src
For example, when I type ffap
and run hippe-expand
, it turns into find-file-at-point
?
Right now, we can enable fussy with company on some predicate. This provides pretty responsive typing as we're not doing fuzzy matching on the first few letters. We should try to support the same functionality for corfu.
Either a smarter completion-style that wraps this one or some advices on corfu itself, etc.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.