IJPL-207762 perform batch update when adding elements to indicator

GitOrigin-RevId: b47d9b53bc9440bea5bba131ca17873342c81245
This commit is contained in:
Max Medvedev
2026-02-05 13:52:14 +00:00
committed by intellij-monorepo-bot
parent 1eae56e55c
commit 97863bb6e6
5 changed files with 57 additions and 20 deletions
@@ -24,6 +24,9 @@ import java.util.function.Predicate;
* If accessed from multiple threads, it needs to take care of proper synchronization itself.
*/
public abstract class LookupArranger implements WeighingContext {
//not static!
private final Key<Runnable> ADDING_CALLBACK = Key.create("on_adding_callback");
protected final List<LookupElement> myItems = new ArrayList<>();
private final List<LookupElement> myMatchingItems = new ArrayList<>();
private final List<LookupElement> myExactPrefixItems = new ArrayList<>();
@@ -36,6 +39,7 @@ public abstract class LookupArranger implements WeighingContext {
public void addElement(@NotNull LookupElement item, @NotNull LookupElementPresentation presentation) {
myItems.add(item);
updateCache(item);
runAddingCallback(item);
}
private void updateCache(@NotNull LookupElement item) {
@@ -240,6 +244,23 @@ public abstract class LookupArranger implements WeighingContext {
return myMatchingItems;
}
/**
* Invokes the specified callback when the specified item is added to this arranger.
* Is useful when using {@link com.intellij.codeInsight.completion.BaseCompletionLookupArranger} in batch mode, see {@link com.intellij.codeInsight.completion.BaseCompletionLookupArranger#batchUpdate}.
* In batch mode, items are added with a delay, so it's useful to invoke this method to get notified when the item is actually added.
*/
@ApiStatus.Internal
public void invokeWhenLookupElementAdded(@NotNull LookupElement item, @NotNull Runnable callback) {
item.putUserData(ADDING_CALLBACK, callback);
}
private void runAddingCallback(@NotNull LookupElement item) {
Runnable callback = item.getUserData(ADDING_CALLBACK);
if (callback == null) return;
item.putUserData(ADDING_CALLBACK, null);
callback.run();
}
/**
* @param items the items to give relevance weight for
* @param hideSingleValued whether criteria that gave same values for all items should be skipped
@@ -307,7 +307,7 @@ public class BaseCompletionLookupArranger extends LookupArranger implements Comp
private void printTestWarning() {
System.err.println("Your test might miss some lookup items, because only " + (myLimit / 2) + " most relevant items are guaranteed to be shown in the lookup. You can:");
System.err.println("1. Make the prefix used for completion longer, so that there are less suggestions.");
System.err.println("2. Increase 'ide.completion.variant.limit' (using RegistryValue#setValue with a test root disposable).");
System.err.println("2. Increase 'ide.completion.variant.limit' (using `Registry.get(\"ide.completion.variant.limit\").setValue(10000, getTestRootDisposable())`).");
System.err.println("3. Ignore this warning.");
}
@@ -175,32 +175,46 @@ private class AddItemJob(
) : Runnable {
override fun run() {
try {
val batch = mutableListOf<AddingEvent>()
while (true) {
indicator.checkCanceled()
when (val event = workingQueue.poll(30, TimeUnit.MILLISECONDS)) {
Stop -> {
tryReadOrCancel(indicator) {
indicator.addDelayedMiddleMatches()
}
return
}
is AddItem -> {
tryReadOrCancel(indicator) {
indicator.addItem(event.result)
}
}
is AddBatch -> {
tryReadOrCancel(indicator) {
indicator.withSingleUpdate {
for (result in event.results) {
indicator.addItem(result)
workingQueue.drainTo(batch)
if (batch.isEmpty()) {
// try awaiting the next event
val next = workingQueue.poll(30, TimeUnit.MILLISECONDS) ?: continue
batch.add(next)
}
var stop = false
tryReadOrCancel(indicator) {
indicator.withSingleUpdate {
for (event in batch) {
indicator.checkCanceled()
when (event) {
is AddItem -> {
indicator.addItem(event.result)
}
is AddBatch -> {
for (result in event.results) {
indicator.addItem(result)
}
}
Stop -> {
indicator.addDelayedMiddleMatches()
stop = true
break
}
}
}
}
null -> { /* keep waiting for the value */ }
}
if (stop) {
return
}
batch.clear()
}
}
catch (e: InterruptedException) {
@@ -414,9 +414,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
cellRenderer.itemAdded(item, presentation);
LookupArranger arranger = myArranger;
arranger.invokeWhenLookupElementAdded(item, () -> cellRenderer.itemAddedToArranger(item));
arranger.registerMatcher(item, matcher);
arranger.addElement(item, presentation);
cellRenderer.itemAddedToArranger(item);
return true;
}
@@ -4,6 +4,7 @@ package org.editorconfig.language.codeinsight
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.application.ex.PathManagerEx
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.registry.RegistryValue
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import org.editorconfig.EditorConfigRegistry
import org.editorconfig.language.assertIterableEquals
@@ -61,6 +62,7 @@ class EditorConfigCompletionTest : BasePlatformTestCase() {
fun doTest(vararg required: String) = with(myFixture) {
val name = getTestName(true)
configureByFile("$name/.editorconfig")
Registry.get("ide.completion.variant.limit").setValue(10000, testRootDisposable)
assertTrue(required.all(completeBasic().map(LookupElement::getLookupString)::contains))
}