Sorting completions in provider
Hi
I'd like to add completions based on IJ's variable completions. For example, I'd like to take the top 3 variables that IJ suggests and add new completions based on those. In order to do that, I need to get the top 3 variables that IJ suggests.
I'm calling runRemainingContributors from my completions contributor and collect all VariableLookupItem elements. My problem is, this list is still not ordered the way the user will see it, so I thought I'd call the LookupArranger, sort it and then get the top 3. Something like this:
final LookupArranger lookupArranger = ((LookupImpl) activeLookup).getArranger();
final CompletionLookupArranger dummyArranger = (CompletionLookupArranger)lookupArranger.createEmptyCopy();
completionResults.stream()
.filter(completionResult -> completionResult.getLookupElement().as(VariableLookupItem.class) != null)
.forEach(dummyArranger::addElement);
final List<LookupElement> sortedVariableLookupElements = dummyArranger.arrangeItems().getFirst();
This all seem to work fine in IJ 2019.3 but in IJ ~2020 completions are running asynchronously and there's the MLSorter which requires to run on the UI thread (why should a sorter care about its running thread?).
Any way to get around this? Maybe I should take a completely different approach?
Thanks
Shai
Please sign in to leave a comment.
Hi,
First, note that it's impossible to get the sorted results from a completion contributor, because 1) other contributors can add more items and 2) sorting is done on the UI thread where it depends on the typed prefix and some random factors due to concurrency. I'd suggest for your contributor to not depend on the sorting and generate additional suggestions for everything.
If you want very-very much to get the sorted results, you can devise some hacks. For example, intercept lookup's appearance (LookupListener) and add your items there manually, then maybe update them on prefix changes. But this can cause all kinds of troubles, as completion framework might not be ready for such interference. Besides, doing anything potentially expensive (e.g. resolve) on the UI thread can lead to UI freezes.
Hi Peter
Thanks for the answer, and for the tip about resolving on UI thread. That is something we should definitely change.
Regarding the first problem you mentioned, about other contributors, I'm only interested in IntelliJ's variables suggestions so I register my contributor before the JavaMemberName contributor and then call all the rest (via runRemainingContributors). Shouldn't that make sure I get all IJ's variable completions?
Regarding the second problem, I was hoping I could run the same sorting code that the Lookup runs. I have the prefix and I guess I'll live with differences due to concurrency (are you referring to frozen items?). Is that possible? Like I said in my original post, it seems to work so long as the MLSorter is not involved.
Thanks
Shai
Plugging in before JavaMemberName will indeed allow you to intercept all predefined variable name suggestions, but other contributors can still add their own suggestions to the same place. But I'm not aware of any such plugins, so this might be not important.
By concurrency I referred to frozen items, yes.
MLSorter working on UI thread is an implementation detail difficult to get rid of in 2020.2
"The sorting code that lookup runs" depends on the prefix and is run on any prefix change, you can't get the same from the contributors. If you run it once for some prefix, your suggestions can end up being somewhere at the bottom after any user typing. If you're OK with that, then your previous approach would work, although it's quite hacky and might break in future as well. I can provide a possibility for you to override the final sorter.
In my contributor I call CompletionResult.restartCompletionOnAnyPrefixChange so I hope this deals with the prefix change issues you describe.
I realise this code is hackish. I just didn't find any better way to achieve what I need. Generally speaking, I find that affecting completions order or sharing information between contributors/providers tends to stretch the limits of the open API.
What do you mean by "overriding the final sorter"?
1. Do you mean I'll be able to run the sorter internally in my provider as if MLSorting is always off and without affecting the actual sorting of the lookup?
2. Or do you mean I'll be able to run some custom sorter after or instead of, the ML sorter and decide which elements will eventually appear and in what order?
restartCompletionOnAnyPrefixChange indeed can help with the prefix.
Affecting completion sorting per se seems to be covered by the API in multiple ways, but the case when you add new items based on the final sorting indeed wasn't a goal when these APIs were designed.
I meant the option 1, where in your contributor you can subclass CompletionLookupArranger and set the final sorter to empty.
That would indeed help my cause so would be nice if you can provide that (especially since this is probably not a very common use case).
Thanks very much for your support. The responsiveness in this forum is truly amazing.
Shai
Done