Repository navigation
Fuzzy completion is incorrect when completeopt contains longest #15295
Description
Activity
When fuzzy is used, the leader input may be scattered in different positions of the string. This means that the substring may not exist. So when fuzzy exists, should the longest be ignored? I would like to hear other people's ideas.
I personally think it is fuzzy collection or fuzzy filtering ?? This determines their priority relative to
longestoption. My personal opinion is that as chrisbra described in the document before: Only fuzzy filtering should be done, then the priority is lower than thelongestoption. There are several reasons:- First of all, vim will not automatically pop up the completion menu, so when we press the completion key (like
<C-n>), our purpose is actually to start the completion from the leading character of the input, solongestshould play his role. At this time, many candidate lists will appear. Withfuzzyoption, we don't need to use<C-p>or<C-n>orupanddownkeys many times to select the target completion. We can quickly locate it through the fuzzy filtering function provided byfuzzyoption, and your implementation before patch 9.1.0598 is indeed the same. - Secondly, when we enter the leading key and press the completion key (such as
<C-n>), the order of the candidate list is not what we expected. Fuzzy filtering will not take effect until we press any letter.

The above figure also shows the different effects of fuzzy collection and fuzzy filtering. - Finally, to be as compatible as possible,
fuzzyoption should add functionality rather than mutually exclusive with existing functionality.
On the other hand, if fuzzy collection and fuzzy filtering are separate functions (#15294), it may be more clear to explain. Let's discuss further, the premise is still set the option
set completeopt+=longest,fuzzy.- If
fuzzyoption only has the function of fuzzy filtering, consider the following words:hello helio think test. When we presshthen<C-n>, Since there is no fuzzy collection, the candidate list will only havehello helioand notthink, so it is natural to work with thelongestrole first, and then perform fuzzy filtering on the next input. If we enterhothen<C-n>, the candidate list will be empty, so nothing will happen. - If the candidate collection function is also included (I really hope to add a new option, such as
fuzzycollect), I probably can think of two more functions that need to be implemented now:- When the completion key is pressed (like
<C-n>), fuzzy filtering needs to be performed while fuzzy collection is performed. If it is still the example in the above picture, the effect is similar to the following (Currently, As a demonstration, I achieve this by pressinghthen<C-n><C-p>, plusanother letterand then pressing the<BackSpace>key to enable fuzzy filtering to work):

Otherwise, let's putthisin the first word, then presshand<C-n>, the candidate list as follows (The default order of fuzzy collection is consistent with the order in which the words appear):

- If the
longestoption is included, the effect of thenoinsertoption should be implemented by default. At this time, only the candidate list is fuzzy filtered without insertion, because as you said, "the leader input may be scattered in different positions of the string", the longest match cannot be accurately defined at this time, so we need to achieve the effect ofnoinsertoption. - Of course, issue Fuzzy completion may be affected by
set complete-=i#15296 may still need to be solved.
- When the completion key is pressed (like
Finally, thank you very much for your support of
fuzzyoption. At least when there is no patch 9.1.0598, everything meets my needs perfectly. I don't know what other people think. I also want to hear more ideas from more people.- First of all, vim will not automatically pop up the completion menu, so when we press the completion key (like
quick respond to the order problem in @roccomao second point mentioned in the picture. There is no score sorting here. Because as long as the first matching result is found, it will be inserted. If the score is sorted, it will be a bit troublesome, so I gave up for the time being.
Since there is a lot of content, it may take me some time to understand and respond. I roughly understand the idea here. But it is difficult to satisfy everyone T.T Any suggestions are appreciated. Thanks for everyone :) I will still prioritize fixing existing bugs to avoid any hangs that cause vim to become unusable 😄
@habamax I certainly agree that fuzzy collection and fuzzy filtering can work together, but it might be better to split the options. First of all, the
fuzzyoption ofwildoptionsdoesn't seem to support fuzzy filtering. You can try enterec<Tab>and then pressg, it won't help us chooseechomsgor others. Secondly, it doesn't contain thelongestoption, so there is no conflict. So here I suggest splitting the functions forfuzzyoption, which will have more control space to adapt to various needs.@glepnir Sorry, because I don't know your internal implementation, I simply thought that since there are collection and filtering, I naturally thought that
hshould be used for collection first, and thenhshould be used for filtering again by default. Because it will also perform filtering when we enter other letters, even if we delete this letters at this time, and only the letterhis left, the candidate list is also a fuzzy filter forh.In short, thank you for your work, and I look forward to your subsequent implementation. FUZZY is a very great feature 👍
quick idea that came to my mind was
fuzzy:matchseqmentioned in the previous PR. Set matchseq to ensure the length of the string to match.fuzzy:3type cmpd sue cmp to get result and use d for fuzzy filter? not sure need more thinkingSome thoughts after trying to play with fuzzy matching a little. To be honest as mostly an end user (since I only casually observed the previous commits) I'm kind of confused using the current implementation (hence why I am making this post).
-
Longest / fuzzy-collect behavior: If the above example (
xyzandxzy, completing starting fromy), usinglongestshould end up having the completion beingxonly. Note that the whole point of "longest" is to insert the "longest common text of the matches" per the documentation. The "common text" part is really the main reason for this option existing to begin with (just think about how you use the option in day-to-day editing before fuzzy was implemented). We aren't trying to literally insert any longest fuzzy match. In this case,xis the longest initial substring and that should be the one that's completed, even if you entered "y". Also, this is also how it would work if you write a customcompletefuncthat returns['xyz', 'xzy']and then do<C-X><C-U>ony. Consistency is really important here. I think the current behavior of completingxzyis wrong as it's probably not what the user wants and also inconsistent with how other completion works.FWIW, I think intuitively and semantically,
longestshould always use non-fuzzy collect. The main reason, as I mentioned, is to get the longest common substring. Using it with fuzzy collect is a little odd, but if we do use fuzzy collect, then I think the behavior should be what I suggest here.So basically, step 1:
Step 2 (this was simulated by writing a completefunc and then
<C-X><C-U>as I mentioned above):
-
tags vs keyword completion: It's kind of confusing that right now completion of tags
i_Ctrl-X_Ctrl-]doesn't use fuzzy collect, but keyword completioni_Ctrl-Pdoes use it. That seems kind of inconsistent which to me is worse. Is there a reason it's different? Can we fix it? -
Fuzzy collect+filter vs fuzzy filter only: Stepping back I think the core issue right now is we keep discussing fuzzy collect and fuzzy filtering but the actual issue is that this "collect vs filter" concept is a somewhat new one that we are introducing to Vim. It's really more an implementation detail that we are now trying to expose to the user. Previously there was never any difference between the two, except when implementing custom completion functions (e.g.
completefunc) but even those are usually supposed to be written in a way that doesn't require the user thinking too much about this distinction.For one, Vim does not make it clear which part of the substring is the initial collected string and which one is the filtered one. Let's say if I try to complete a tag (from the Vim development tags file) by doing
ASS<C-X><C-]>EQwithcompleteopt=menu,noinsert,fuzzy(remember, tags are using non-fuzzy collect right now), this is what I see:
I think it's pretty hard for a user to visually tell that the "ASS" part is the initially collected string, and the "EQ" part is used for the additional fuzzy filtering. Furthermore, if you backspace enough to delete one of the "S" so you have "AS", and then you type "S" to get "ASS" again, now only "AS" is the collected string, the the third "S" is part of the filter. I think that's quite confusing tbh and would lead to people filing issues complaining that the behavior is non-intuitive.
I think it's ok to add an option to separate out fuzzy filtering only vs fuzzy collect, but I think it's important to think about the API / documentation / configuration that we are exposing to the user, instead of treating it as an afterthought. Most users don't know what features they are missing (unless you follow Vim dev intensely) but they definitely notice inconsistency and confusing features that behave oddly. In particularly I think it would be a good idea to find a better way to let the user know which part is the collected part (the "ASS" in my example) and which part is the filtered part (the "EQ"). I also feel like right now there are a fair bit of inconsistency and confusing bits because the semantics isn't clear and differs depending on what you do.
-
Yes, there are some unclear issues at present. This is what I have been thinking about recently. First, longest should be ignored when fuzzy exists. Second, what does fuzzy mean here? Fuzzy in completeopt only means fuzzy filtering based on the completion list that has been used. It is right to clearly distinguish between collection and filtering. For some built-in completions, fuzzy collection should be used. There are many kinds of built-in completion modes and they are based on different implementations. It is difficult to break these behaviors. For example, keyword completion is generated by searchit and compl_pattern. We cannot generate fuzzy matches based on regular expressions. My idea is to add new options to complete option for fuzzy collection, such as set complete+=fc with
.wb. However, there is no option to control file completion, tag completion, line completion, etc. If fuzzy collection is implemented on these modes, a new option may be needed. then use it likecompletefuzzycollect=k,f,w, keyword file whole-linecontrol the builtin completion mode which use fuzzy collect. The plan is to implement fuzzy collection one by one. Avoid a lot of wrong behaviors that cause multiple modes to not work and bring bad experience. When I have a new draft, I will submit a PR. I hope you can give me more suggestions there.thanks @ychin
First, longest should be ignored when fuzzy exists
Assuming we have fuzzy collect vs filter, then why should
longestbe ignored when only using fuzzy filter? The initial collection is not using fuzzy andlongestcould still be work just like before.Even if we have fuzzy collect, the more I think about it the more I think we could still support it, by just using the longest substring (from the collected results) as I mentioned. For example, in the top example here, we would just complete to
x. This seems reasonable to me and it's actually how user completion functions work now (e.g.completefunc). The user can always removelongestfrom the options if they don't like it.Just to give more examples to be clear, so with fuzzy collect, let's say we have this:
foobar foobez footrest abcIf I have
completeopt=fuzzy,fuzzy-collect,longest,menu(I invented afuzzy-collectoption here), and typefrthen do completion, I would imagine it should complete tofoosince there are two results ("foobar" and "footrest").You can see this demonstrated in today's Vim if you use a completion function (set with
completefuncoption) like so:function! FuzzyCompletion(findstart, base) if a:findstart return 0 endif return matchfuzzy(['foobar', 'foobez', 'footrest', 'abc'], a:base) endfunction
Which shows:
Note that the second screenshot is a little odd because it's highlighting "foo" in the popup which is actually kind of wrong as it should highlight the "f" and "r" in each string.
As I mentioned we should try to keep things as consistent and reasonable as possible. I think if the user specifies
longest, then it would be nice to have a reasonable implementation instead of just blatantly not working. Right now,nocompletedoes not work withlongestbut that is only because these two options fundamentally don't work together semantically. I'm not sure iflongestis in that camp with fuzzy collect.Either way I think we should try to make user completion functions work the same regarding
longest,fuzzy(since it currently supports it). We should have a single consistent model as much as possible.
Second, what does fuzzy mean here? Fuzzy in completeopt only means fuzzy filtering based on the completion list that has been used. It is right to clearly distinguish between collection and filtering.
My point is that this may be clear to someone who spends a lot of time thinking about / working on Vim, I don't think this is immediately intuitive to an end user, because this is not generally how completions work and they just see a popup. If there are real reasons why we want to have non-fuzzy collection, but fuzzy filtering, then that is a new mode of thinking that we are imposing on our users, and per the screenshot I posted in my above comment, it's not really clear to the user which substring is the "collected" part, and which part is the "filtered" part. If you backspace and type the same character again, now the behavior also changes because the last character is now used for filtering instead of collecting.
I'm just trying to make sure we think through this a little more to make sure the options we expose and the behaviors make sense. For example, ideally, the completion UI (including the popup menu) can do more to show the user which parts is the originally collected part and which is the new substring, but this could be a little too busy and impose more requirements on colorschemes.
Assuming we have fuzzy collect vs filter, then why should longest be ignored when only using fuzzy filter?
it's a little unclear. I agree with all of your distinction. What I'm talking about here is the current fuzzy behavior. :)
completeopt=fuzzy,fuzzy-collect,longest,menuThis doesn't make sense. I mentioned it in the comment above. I would prefer that we have a new option to control those built-in completion modes to enable fuzzy collection. We have
keyword filenames tags wholelineetc completion mode.
this way we can handle them one by one. If a pattern cannot be fuzzy-ed, we can skip it. In this way, any incorrect implementation will not cause too many bugs, at least we can temporarily restore it to its original state.completeopt=fuzzy,fuzzy-collect,longest,menu
This doesn't make senseSure, that was just an example to show that we are talking about using fuzzy collect. My core point was more that fuzzy collect can still support the
longestoption and make sense, by following my example that I listed afterwards (since you mentionedlongestwould just be disabled if fuzzy is used). In that section I wasn't particularly discussing how fuzzy collect itself should be configured and whatnot.So to summarize, my points in the first section was that::
- fuzzy filter only AND
longestshould work just fine, which I think you agreed with. - fuzzy collect AND
longestshould also work. I think you disagreed with that point and I was trying to make my case there. In particularly, it should collect up to the longest substring. How we configure fuzzy collect is another discussion.
- fuzzy filter only AND
I don't actually have a strong opinion here. It's just that supporting longest requires some extra work. Imagine that the leader is scattered at every position of the item. If supporting longest requires an additional fast algorithm to find out if there is a longest string that exists right? I'm worried about the efficiency here. do you have any better ideas?
You probably know the code better tbh regarding the performance and implementation time required. I just thought maybe we could reuse the way that
longestis already working with custom completion functions likecompletefuncbut I don't know if there are further edge cases to resolve (since the current way custom completion functions kind of feel like they work by accident whenlongestis turned on). But I will admit I haven't taken close look into the fuzzy implementations enough.But sure, implement the minimum to get things working first and then add features is better than having broken things that you have to fix later. So it might make sense to just disable
longestat least at first as you suggested if just to get to parity.5 remaining items
- added 9 commits that reference this issue
on Mar 7, 2025 - added 6 commits that reference this issue
on Mar 21, 2025

Steps to reproduce
xyz xzy(begin with the letterx)yorz, then press<C-n>, bothxyzandxzywill appear in the candidate listxyzis the only matchExpected behaviour
When
set completeopt+=longest,fuzzy, if we enteryorz, then press<C-n>, bothxyzandxzywill appear in the candidate list, or neither will appear. Of course, it is best if neither will appear in the candidate list.Version of Vim
9.1.0598
Environment
The bug was introduced by patch 9.1.0598
Logs and stack traces
No response