IDEA-124379 Avoid completion lookup glitching

* advertise all unused features to keep advertiser width constant
* show a random advertisement also after the lookup is reused
* group advertisement changes together to avoid lookup shrinking and growing back
* remove hacky LookupImpl.myAdText kept for historical reasons
This commit is contained in:
peter
2014-05-13 12:43:12 +02:00
parent 7732843517
commit dd4697d174
5 changed files with 79 additions and 99 deletions
@@ -68,14 +68,18 @@ import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -117,10 +121,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
}
};
private volatile int myCount;
private boolean myLookupUpdated;
private final ConcurrentHashMap<LookupElement, CompletionSorterImpl> myItemSorters =
new ConcurrentHashMap<LookupElement, CompletionSorterImpl>(
ContainerUtil.<LookupElement>identityStrategy());
private final PropertyChangeListener myLookupManagerListener;
private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<Runnable>();
private final int myStartCaret;
public CompletionProgressIndicator(final Editor editor,
@@ -137,6 +143,13 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
myLookup = (LookupImpl)parameters.getLookup();
myStartCaret = myEditor.getCaretModel().getOffset();
myAdvertiserChanges.offer(new Runnable() {
@Override
public void run() {
myLookup.getAdvertiser().clearAdvertisements();
}
});
myLookup.setArranger(new CompletionLookupArranger(parameters, this));
myLookup.addLookupListener(myLookupListener);
@@ -177,9 +190,9 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
if (!CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS) {
myLookup.setFocusDegree(LookupImpl.FocusDegree.SEMI_FOCUSED);
if (FeatureUsageTracker.getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_CONTROL_DOT, getProject())) {
myLookup.addAdvertisement("Press " +
CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_DOT) +
" to choose the selected (or first) suggestion and insert a dot afterwards", null);
addAdvertisement("Press " +
CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_DOT) +
" to choose the selected (or first) suggestion and insert a dot afterwards", null);
}
} else {
myLookup.setFocusDegree(LookupImpl.FocusDegree.FOCUSED);
@@ -188,12 +201,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
if (!myEditor.isOneLineMode() &&
FeatureUsageTracker.getInstance()
.isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_CONTROL_ARROWS, getProject())) {
myLookup.addAdvertisement(CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_DOWN) + " and " +
CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_UP) +
" will move caret down and up in the editor", null);
addAdvertisement(CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_DOWN) + " and " +
CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_UP) +
" will move caret down and up in the editor", null);
}
} else if (DumbService.isDumb(getProject())) {
myLookup.addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground());
addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground());
}
ProgressManager.checkCanceled();
@@ -245,33 +258,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
if (myLookup.isAvailableToUser()) {
return;
}
final List<CompletionContributor> list = CompletionContributor.forParameters(myParameters);
for (final CompletionContributor contributor : list) {
if (myLookup.getAdvertisementText() != null) return;
for (final CompletionContributor contributor : CompletionContributor.forParameters(myParameters)) {
if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
@SuppressWarnings("deprecation") String s = contributor.advertise(myParameters);
if (myLookup.getAdvertisementText() != null) return;
if (s != null) {
myLookup.setAdvertisementText(s);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (isAutopopupCompletion() && !myLookup.isAvailableToUser()) {
return;
}
if (!CompletionServiceImpl.isPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class)) {
return;
}
if (CompletionServiceImpl.getCompletionPhase().indicator != CompletionProgressIndicator.this) {
return;
}
updateLookup();
}
}, myQueue.getModalityState());
return;
addAdvertisement(s, null);
}
}
}
@@ -320,6 +312,19 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
ApplicationManager.getApplication().assertIsDispatchThread();
if (isOutdated() || !shouldShowLookup()) return false;
while (true) {
Runnable action = myAdvertiserChanges.poll();
if (action == null) break;
action.run();
}
if (!myLookupUpdated) {
if (myLookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
DefaultCompletionContributor.addDefaultAdvertisements(myParameters, myLookup);
}
myLookup.getAdvertiser().showRandomText();
}
boolean justShown = false;
if (!myLookup.isShown()) {
if (hideAutopopupIfMeaningless()) {
@@ -330,18 +335,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
PerformanceWatcher.getInstance().dumpThreads(true);
}
if (StringUtil.isEmpty(myLookup.getAdvertisementText()) && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
final String text = DefaultCompletionContributor.getDefaultAdvertisementText(myParameters);
if (text != null) {
myLookup.setAdvertisementText(text);
}
}
if (!myLookup.showLookup()) {
return false;
}
justShown = true;
}
myLookupUpdated = true;
myLookup.refreshUi(true, justShown);
hideAutopopupIfMeaningless();
if (justShown) {
@@ -351,8 +350,13 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
}
private boolean shouldShowLookup() {
if (isAutopopupCompletion() && myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
return false;
if (isAutopopupCompletion()) {
if (myCount == 0) {
return false;
}
if (myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
return false;
}
}
return true;
}
@@ -532,7 +536,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
final Boolean aBoolean = new WriteCommandAction<Boolean>(getProject()) {
@Override
protected void run(Result<Boolean> result) throws Throwable {
protected void run(@NotNull Result<Boolean> result) throws Throwable {
if (!explicit) {
setMergeCommand();
}
@@ -550,7 +554,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
public void restorePrefix(@NotNull final Runnable customRestore) {
new WriteCommandAction(getProject()) {
@Override
protected void run(Result result) throws Throwable {
protected void run(@NotNull Result result) throws Throwable {
setMergeCommand();
customRestore.run();
@@ -718,6 +722,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
final Language language = PsiUtilCore.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset());
for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) {
//noinspection deprecation
final ThreeState result = confidence.shouldFocusLookup(parameters);
if (result != ThreeState.UNSURE) {
LOG.debug(confidence + " has returned shouldFocusLookup=" + result);
@@ -771,6 +776,17 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
return result;
}
public void addAdvertisement(@NotNull final String text, @Nullable final Color bgColor) {
myAdvertiserChanges.offer(new Runnable() {
@Override
public void run() {
myLookup.addAdvertisement(text, bgColor);
}
});
myQueue.queue(myUpdate);
}
private static class ModifierTracker extends KeyAdapter {
private final JComponent myContentComponent;
@@ -18,51 +18,41 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.documentation.actions.ShowQuickDocInfoAction;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
/**
* @author peter
*/
public class DefaultCompletionContributor extends CompletionContributor {
@Nullable
public static String getDefaultAdvertisementText(@NotNull final CompletionParameters parameters) {
final Random random = new Random();
if (random.nextInt(5) < 2 && CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_DOT_ETC)) {
return LangBundle.message("completion.dot.etc.ad");
static void addDefaultAdvertisements(@NotNull final CompletionParameters parameters, LookupImpl lookup) {
if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_DOT_ETC)) {
lookup.addAdvertisement(LangBundle.message("completion.dot.etc.ad"), null);
}
if (random.nextInt(5) < 2 && CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_SMART_ENTER)) {
if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_SMART_ENTER)) {
final String shortcut = getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_COMPLETE_STATEMENT);
if (shortcut != null) {
return LangBundle.message("completion.smart.enter.ad", shortcut);
lookup.addAdvertisement(LangBundle.message("completion.smart.enter.ad", shortcut), null);
}
}
if (random.nextInt(5) < 2 &&
CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_DOT_ETC)) {
return LangBundle.message("completion.dot.etc.ad");
}
if (random.nextInt(5) < 2 &&
CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_SMART_ENTER)) {
if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_SMART_ENTER)) {
final String shortcut = getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_COMPLETE_STATEMENT);
if (shortcut != null) {
return LangBundle.message("completion.smart.enter.ad", shortcut);
lookup.addAdvertisement(LangBundle.message("completion.smart.enter.ad", shortcut), null);
}
}
if (random.nextInt(5) < 2 &&
(CompletionUtil.shouldShowFeature(parameters, ShowQuickDocInfoAction.CODEASSISTS_QUICKJAVADOC_FEATURE) ||
if ((CompletionUtil.shouldShowFeature(parameters, ShowQuickDocInfoAction.CODEASSISTS_QUICKJAVADOC_FEATURE) ||
CompletionUtil.shouldShowFeature(parameters, ShowQuickDocInfoAction.CODEASSISTS_QUICKJAVADOC_LOOKUP_FEATURE))) {
final String shortcut = getActionShortcut(IdeActions.ACTION_QUICK_JAVADOC);
if (shortcut != null) {
return LangBundle.message("completion.quick.javadoc.ad", shortcut);
lookup.addAdvertisement(LangBundle.message("completion.quick.javadoc.ad", shortcut), null);
}
}
@@ -70,11 +60,9 @@ public class DefaultCompletionContributor extends CompletionContributor {
CompletionUtil.shouldShowFeature(parameters, ShowImplementationsAction.CODEASSISTS_QUICKDEFINITION_LOOKUP_FEATURE)) {
final String shortcut = getActionShortcut(IdeActions.ACTION_QUICK_IMPLEMENTATIONS);
if (shortcut != null) {
return LangBundle.message("completion.quick.implementations.ad", shortcut);
lookup.addAdvertisement(LangBundle.message("completion.quick.implementations.ad", shortcut), null);
}
}
return null;
}
@Override
@@ -31,6 +31,7 @@ import com.intellij.psi.Weigher;
import com.intellij.psi.WeighingService;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -68,14 +69,15 @@ public class CompletionServiceImpl extends CompletionService{
@Override
public String getAdvertisementText() {
final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion();
return completion == null ? null : completion.getLookup().getAdvertisementText();
return completion == null ? null : ContainerUtil.getFirstItem(completion.getLookup().getAdvertisements());
}
@Override
public void setAdvertisementText(@Nullable final String text) {
if (text == null) return;
final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion();
if (completion != null) {
completion.getLookup().setAdvertisementText(text);
completion.addAdvertisement(text, null);
}
}
@@ -112,7 +112,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
private FocusDegree myFocusDegree = FocusDegree.FOCUSED;
private volatile boolean myCalculating;
private final Advertiser myAdComponent;
private volatile String myAdText;
volatile int myLookupTextWidth = 50;
private boolean myChangeGuard;
private volatile LookupArranger myArranger;
@@ -294,18 +293,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
}
}
public void setAdvertisementText(@Nullable String text) {
myAdText = text;
if (StringUtil.isNotEmpty(text)) {
addAdvertisement(text, null);
}
}
public String getAdvertisementText() {
return myAdText;
}
public String getAdditionalPrefix() {
return myOffsets.getAdditionalPrefix();
}
@@ -712,6 +699,10 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
return true;
}
public Advertiser getAdvertiser() {
return myAdComponent;
}
public boolean mayBeNoticed() {
return myStampShown > 0 && System.currentTimeMillis() - myStampShown > 300;
}
@@ -1109,7 +1100,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
}
public void markReused() {
myAdComponent.clearAdvertisements();
synchronized (myList) {
myArranger = myArranger.createEmptyCopy();
}
@@ -1121,24 +1111,8 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
return;
}
Runnable runnable = new Runnable() {
@Override
public void run() {
if (!myDisposed) {
myAdComponent.addAdvertisement(text, bgColor);
if (myShown) {
requestResize();
refreshUi(false, false);
}
}
}
};
if (ApplicationManager.getApplication().isDispatchThread()) {
runnable.run();
}
else {
ApplicationManager.getApplication().invokeLater(runnable);
}
myAdComponent.addAdvertisement(text, bgColor);
requestResize();
}
public boolean isLookupDisposed() {
@@ -591,7 +591,7 @@ public class TemplateState implements Disposable {
lookup.setStartCompletionWhenNothingMatches(true);
}
lookup.setAdvertisementText(advertisingText);
lookup.addAdvertisement(advertisingText, null);
lookup.refreshUi(true, true);
ourLookupShown = true;
lookup.addLookupListener(new LookupAdapter() {