jojojames/fussy

Orderless is not, in fact, orderless

Opened this issue · 6 comments

Hi, thanks for this package! I have been using it for a while and have found the completion to be quite intuitive, except for a specific case:

The fzf-native scorer allows reordering of words, just like orderless does. So we can match the command in M-x "org-goto" by typing "goto org".

fussy disallows this by feeding the scorer the query "gotoorg" instead of "goto org". This, of course does not match, and the candidate is discarded:

fussy/fussy.el

Lines 616 to 618 in 4080b37

(if (fussy--orderless-p)
(replace-regexp-in-string "\\\s" "" string)
string)

I have "fixed" the issue on my end with a very ugly advice hack:

(defun my/allow-reordering-advice-replace-regexp (orig-fun regexp rep string &rest args) string)

(define-advice fussy-score (:around (orig-fun &rest args) allow-reordering)
  (prog2
      (advice-add 'replace-regexp-in-string :around 'my/allow-reordering-advice-replace-regexp)
      (apply orig-fun args)
    (advice-remove 'replace-regexp-in-string 'my/allow-reordering-advice-orderless-p)))

I understand that the word concatenation was a conscious choice and it is possible that other scorers do not support whitespace. In this case, maybe giving the user a choice per scorer would make sense?

I'm guessing you're using orderless to filter and fzf to score&sort.

(replace-regexp-in-string "\\s" "" string)

You could look into creating a list of scoring functions that would be able to accept a SPC and then conditionally skipping the replace-regexp by checking the value of fussy-score-fn.

PR accepted.

Thanks for your reply!

Another option I was thinking of, that would support all scorers would be to split all words by whitespace, score each word individually and then add/multiply the results.

I will implement such a custom scorer and use it for a while to see how well such an approach works. I‘ll get back to you then.

Hi again,

it's been a couple days and I've put a lot of thought into this problem. The big issue is that the scoring algorithms were not made with orderless in mind. Please keep in mind that I am fairly new to elisp so the code listings may not show good practices. They do work, however, and the concepts count.

Solution 1

Inputting each word into the scoring algorithm individually and then adding or multiplying results.

If the scoring algorithm receives the raw query (could be set as a text property), this can be achieved from the user-side with a custom scoring algorithm, e.g.,

(defun my/scoring-fn (str query &rest _args)
  (list (seq-reduce '*
		    (mapcar
		     (lambda (query-word)
		       (car (fussy-flx-rs-score str query-word)))
		     (split-string query))
		    1)))
(push 'my/scoring-fn fussy-score-fns-without-indices)

For each scoring algorithm the best reduction method would have to be found.

Solution 2

Concatenating the query words in the order first seen by orderless.

Currently, fussy discards org-goto with the query goto org as the scoring algorithm receives gotoorg. Instead of concatenating the query in the given order, we could concatenate in the order first seen, in this case orggoto, which will match.

This is fairly close to the current implementation, only allowing the reordering of the query words, but no interleaving.

It is implemented here: kim366@f2903e0

Solution 3

Inputting not the user's query, but instead what orderless matched, into the scoring algorithm.

Example: matching the query abc xyz with orderless-flex against ax_other_bycz will highlight [a][x]_other[b][y][c][z]. We can concatenate the highlights as axbycz and feed them to the scoring algorithm.

This approach gives maximum compatibility with orderless-flex as no candidates will be filtered out due to incompatible scoring. It also allows us to fully configure orderless, using e.g., initialism for the first word and other matching styles for other words as detailed in the orderless docs. It will require some long-term real-world use to see how intuitive the sorting is.

This also means, though, that overlapping matches will be discarded: with orderless-flex, the query org go will match org-info, as the g in go overlaps with the g in org and is discarded. This seems to be intended orderless behavior. Most scoring algorithms rank consecutive matches higher. With this query, org-goto will not match as highly as it should, as orderless-flex is greedy but scoring algorithms might not be (hotfuzz, for example).

It is implemented here: kim366@b457513

Conclusion

So solution 3 tries to maintain maximum compatiblity with orderless, being very "flex". Solution 2 tries to maintain maximum compatibility with the scoring algorithms, not allowing any interleaving.

I will use these for a while and see how I feel about them. What are your thoughts?

First question is are you trying to make this work with all backends or just the fzf one? We can go from there.

Did you have thoughts after using your implementations after a while?