lewang/flx

On integrate flx with Helm

tuhdo opened this issue · 6 comments

Hi Le Wang,

This is my high level idea of how we could use flx for Helm. If it is not applicable, then I guess we can leave this an open issue that hopefully be solved in the future.

My idea is that, if we enter a bunch of patterns, some of which can be normal Helm pattern, some can be fuzzy pattern, then we divide the narrowing process into two phases:

  • One is for normal Helm filtering. That is, we tried each pattern one by one; if one pattern results in empty set of candidates, then store that pattern and try the next one (without including the "failed" pattern). We filter until no more pattern and result in a set of candidates.
  • One is for flx filtering. That is, we apply flx on the output from the above phases and narrow down further. The result of this phase is the final set to be returned to user.

The key point is that flx is only applied when a pattern can result in no candidate.

Thanks.

It doesn't have to be that complicated. "Fuzzy" searches through flx should not have spaces at all. So for a query "term1 term2 term3" we could say only term1 is treated as "fuzzy" the rest are regular helm searches.

The searching isn't difficult, the problem is I would also want to highlight the letters that are actually matched. This allows people to learn the matching algorithm by intuition and become more efficient. I couldn't figure out how to do that when I first tried.

If you're curious, have a look at helm-match-plugin.el for how current matching is implemented.

Hi Le Wang,

Today I notice that helm-buffers-list can fuzzy match and it provides no highlighting. Perhaps if you can make flx work with Helm but provides no highlighting, it can still be acceptable to Helm users. Is it possible for you to add it to Helm now? You should just do it and see how the users response. I am curious to see how I can use flx.

@lewang

Hi Le Wang,

I'm not sure if you looked into this but I will give you the result of my investigation and hope it helps. Basically, Helm highlighting relies on these variables and functions:

Variables: helm-mp-highlight-delay
Variables: helm-mp-highlight-threshold

Functions: helm-mp-highlight-match ()
Functions: helm-mp-highlight-region (start,end,regexp,face)
Functions: helm-mp-highlight-match-internal (end)

First, let's start with helm-mp-highlight-match:

(defun helm-mp-highlight-match ()
  "Highlight matches after `helm-mp-highlight-delay' seconds."
  (unless (or (assoc 'nohighlight (helm-get-current-source))
              (not helm-mp-highlight-delay)
              (helm-empty-buffer-p)
              (string= helm-pattern ""))
    (helm-mp-highlight-match-internal (window-end (helm-window)))
    (run-with-idle-timer helm-mp-highlight-delay nil
                         'helm-mp-highlight-match-internal
                         (with-current-buffer helm-buffer (point-max)))))

The function checks that if the current Helm buffer is eligible for highlighting:

  • not explicity specified 'nohighlight.
  • helm-mp-highlight-delay is not nil
  • Current Helm buffer is not empty.
  • Helm pattern is not an empty string.

Then it calls helm-mp-highlight-match-internal:

(defun helm-mp-highlight-match-internal (end)
  (when helm-alive-p
    (set-buffer helm-buffer)
    (let ((requote (cl-loop for (pred . re) in
                         (helm-mp-3-get-patterns helm-pattern)
                         when (and (eq pred 'identity)
                                   (>= (length re)
                                       helm-mp-highlight-threshold))
                         collect re into re-list
                         finally return
                         (if (and re-list (>= (length re-list) 1))
                             (mapconcat 'identity re-list "\\|")
                           (regexp-quote helm-pattern)))))
      (when (>= (length requote) helm-mp-highlight-threshold)
        (helm-mp-highlight-region
         (point-min) end requote 'helm-match)))))

If the helm buffer is alive, it collects a list of regexps from Helm pattern and concat into a big regexp using mapconcat. Finally, it passes into the helm-mp-highlight-region which is the function that does actual highlighting. The function accepts starting point for highlighting with start parameter, end point for stopping highlighting with end parameter, a regexp and highlight face:

(defun helm-mp-highlight-region (start end regexp face)
  (save-excursion
    (goto-char start)
    (let ((case-fold-search (helm-set-case-fold-search regexp)) me)
      (condition-case _err
          (while (and (setq me (re-search-forward regexp nil t))
                      (< (point) end)
                      (< 0 (- (match-end 0) (match-beginning 0))))
            (unless (helm-pos-header-line-p)
              (put-text-property (match-beginning 0) me 'face face)))
        (invalid-regexp nil)))))

end argument in helm-mp-highlight-match-internal is passed by helm-mp-highlight-match and is (point-max). The other arguments is inside helm-mp-highlight-match-internal. In helm-mp-highlight-region, it keeps re-search-forward for the regexp passed into and highlight matched text with put-text-property.

That's all to it. So, to solve your highlighting problem, I suggest:

  • Create another function called helm-flx-mp-highlight-region that accepts the same parameters as helm-mp-highlight-region. Then use defalias to rebinding helm-flx-mp-highlight-region to helm-mp-highlight-region and helm-mp-highlight-region to something else.
(defalias `helm-mp-highlight-region `helm-flx-mp-highlight-region)
(defalias `helm-mp-original-highlight-region `helm-mp-highlight-region)
  • helm-flx-mp-highlight-region should highlight with put-text-property, similar to the original helm-mp-highlight-region function, but using flx returned indexes.
  • After done highlighting for flx, since the first pattern belongs to flx, calls the original helm-mp-original-highlight-region with the rest of the pattern for highlighting. Remember to remove the first pattern, as it is used by flx.

You should name this package helm-flx and do not include Ido.

Is anyone actively working on this? It seems like there is not too much left to do. Would it be worth my time to try to add highlighting and make a package out of this?

I will take any PRs with or without highlighting. @tuhdo has some good suggestions here that I haven't even looked through yet. I also won't have time to in the foreseeable future, so any help is appreciated.

I think helm integration is addressed by helm-flx. If there are shortcomings in my implementation, feel free to open an issue there and we can talk discuss.