From 5df9f57409a8e6c51d602b2f41c07a8b7571c72f Mon Sep 17 00:00:00 2001 From: Peter Gromov Date: Fri, 11 Sep 2009 11:31:44 +0400 Subject: [PATCH] introduce LookupArranger to support different sorting strategies in lookups --- .../SkipAbstractExpectedTypeWeigher.java | 8 +- .../ChangeSignatureDialog.java | 2 +- .../CompletionPreselectSkipper.java | 30 ++ .../completion/CompletionService.java | 5 - .../completion/CodeCompletionHandlerBase.java | 15 +- .../completion/CompletionLookupArranger.java | 153 ++++++++ .../CompletionPreferencePolicy.java | 100 ----- .../CompletionProgressIndicator.java | 3 +- .../completion/NegativeStatisticsWeigher.java | 2 +- .../intellij/codeInsight/lookup/Lookup.java | 3 + .../codeInsight/lookup/LookupArranger.java | 30 ++ .../codeInsight/lookup/LookupItem.java | 2 - .../codeInsight/lookup/LookupManager.java | 23 +- .../codeInsight/lookup/impl/LookupImpl.java | 341 ++++++++---------- .../impl/LookupItemWeightComparable.java | 4 +- .../lookup/impl/LookupManagerImpl.java | 44 +-- .../lookup/impl/TestLookupManager.java | 24 +- .../template/impl/ListTemplatesHandler.java | 2 +- .../template/impl/TemplateState.java | 13 +- .../internal/DumpLookupElementWeights.java | 4 +- .../refactoring/rename/RenameDialog.java | 2 +- .../ui/TextFieldWithAutoCompletion.java | 2 +- .../src/META-INF/LangExtensionPoints.xml | 1 + .../impl/CodeInsightTestFixtureImpl.java | 3 +- .../intellij/lang/ant/AntCompletionTest.java | 41 +-- .../CompleteWordFromDictionaryAction.java | 8 +- .../spellchecker/quickfixes/ChangeTo.java | 2 +- resources/src/META-INF/IdeaPlugin.xml | 4 +- .../xml/refactoring/XmlTagRenameDialog.java | 2 +- 29 files changed, 448 insertions(+), 425 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/codeInsight/completion/CompletionPreselectSkipper.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java delete mode 100644 platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreferencePolicy.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/SkipAbstractExpectedTypeWeigher.java b/java/java-impl/src/com/intellij/codeInsight/completion/SkipAbstractExpectedTypeWeigher.java index 9c7b313a47c0..7ddc29f94ff8 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/SkipAbstractExpectedTypeWeigher.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/SkipAbstractExpectedTypeWeigher.java @@ -11,12 +11,11 @@ import com.intellij.psi.*; import com.intellij.psi.statistics.StatisticsManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.TypeConversionUtil; -import org.jetbrains.annotations.NotNull; /** * @author peter */ -public class SkipAbstractExpectedTypeWeigher extends CompletionWeigher { +public class SkipAbstractExpectedTypeWeigher extends CompletionPreselectSkipper { enum Result { NON_DEFAULT, @@ -25,8 +24,9 @@ public class SkipAbstractExpectedTypeWeigher extends CompletionWeigher { ACCEPT } - public Comparable weigh(@NotNull final LookupElement item, final CompletionLocation location) { - return getSkippingStatus(item, location); + @Override + public boolean skipElement(LookupElement element, CompletionLocation location) { + return getSkippingStatus(element, location) != Result.ACCEPT; } public static Result getSkippingStatus(final LookupElement item, final CompletionLocation location) { diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialog.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialog.java index 43596847d8cd..e168c558a322 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialog.java @@ -374,7 +374,7 @@ public class ChangeSignatureDialog extends RefactoringDialog { LookupElement[] lookupItems = set.toArray(new LookupElement[set.size()]); editor.getCaretModel().moveToOffset(prefix.length()); editor.getSelectionModel().removeSelection(); - LookupManager.getInstance(myProject).showLookup(editor, lookupItems, prefix, null); + LookupManager.getInstance(myProject).showLookup(editor, lookupItems, prefix); } private JComponent createSignaturePanel() { diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionPreselectSkipper.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionPreselectSkipper.java new file mode 100644 index 000000000000..956532ff58ab --- /dev/null +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionPreselectSkipper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.codeInsight.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.extensions.ExtensionPointName; + +/** + * @author peter + */ +public abstract class CompletionPreselectSkipper { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.completion.skip"); + + public abstract boolean skipElement(LookupElement element, CompletionLocation location); + +} diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java index ea2a0611b49d..563b1c904b11 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java @@ -32,11 +32,6 @@ public abstract class CompletionService { * A "weigher" extension key (see {@link Weigher}) to sort completion items. */ public static final Key WEIGHER_KEY = Key.create("completion"); - /** - * A "weigher" extension key (see {@link Weigher}) to skip some of the top lookup items that shouldn't be selected, but should still be at the top - * (so {@link #WEIGHER_KEY} is not applicable). - */ - public static final Key PRESELECT_KEY = Key.create("preferredCompletionItem"); public static CompletionService getCompletionService() { return ServiceManager.getService(CompletionService.class); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index 60d3870aaf9a..0d308a1523b4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -13,8 +13,8 @@ import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.injected.editor.EditorWindow; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.Result; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; @@ -28,8 +28,8 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; @@ -50,6 +50,9 @@ import com.intellij.util.concurrency.Semaphore; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; +import java.util.List; +import java.util.Arrays; + public class CodeCompletionHandlerBase implements CodeInsightActionHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase"); private final CompletionType myCompletionType; @@ -277,7 +280,7 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler { insertLookupString(context, offset2, uniqueText); context.editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - lookupItemSelected(context, item, Lookup.AUTO_INSERT_SELECT_CHAR, items); + lookupItemSelected(context, item, Lookup.AUTO_INSERT_SELECT_CHAR, Arrays.asList(items)); } }.execute(); } @@ -289,7 +292,7 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler { editor.getSelectionModel().removeSelection(); } - protected static void selectLookupItem(final LookupElement item, final char completionChar, final CompletionContext context, final LookupElement[] items) { + protected static void selectLookupItem(final LookupElement item, final char completionChar, final CompletionContext context, final List items) { final int caretOffset = context.editor.getCaretModel().getOffset(); context.setSelectionEndOffset(caretOffset); @@ -413,10 +416,10 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler { } private static void lookupItemSelected(final CompletionContext context, @NotNull final LookupElement item, final char completionChar, - final LookupElement[] items) { + final List items) { final Editor editor = context.editor; final PsiFile file = context.file; - final InsertionContext context1 = new InsertionContext(context.getOffsetMap(), completionChar, items, file, editor); + final InsertionContext context1 = new InsertionContext(context.getOffsetMap(), completionChar, items.toArray(new LookupElement[items.size()]), file, editor); ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { final int idEndOffset = context.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java new file mode 100644 index 000000000000..ad336c7e4ace --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java @@ -0,0 +1,153 @@ +package com.intellij.codeInsight.completion; + +import com.intellij.codeInsight.lookup.Lookup; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementDecorator; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.codeInsight.lookup.LookupArranger; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.codeInsight.lookup.impl.LookupItemWeightComparable; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.WeighingService; +import com.intellij.psi.statistics.StatisticsInfo; +import com.intellij.psi.statistics.StatisticsManager; +import com.intellij.psi.util.proximity.PsiProximityComparator; +import org.jetbrains.annotations.NonNls; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class CompletionLookupArranger extends LookupArranger { + public static final Key RELEVANCE_KEY = Key.create("RELEVANCE_KEY"); + @NonNls public static final String SELECTED = "selected"; + @NonNls public static final String IGNORED = "ignored"; + private final CompletionLocation myLocation; + public static final Key WEIGHT = Key.create("WEIGHT"); + + public CompletionLookupArranger(final CompletionParameters parameters) { + myLocation = new CompletionLocation(parameters); + } + + @Override + public void sortItems(List items) { + final PsiProximityComparator proximityComparator = new PsiProximityComparator(myLocation.getCompletionParameters().getPosition()); + Collections.sort(items, new Comparator() { + public int compare(LookupElement o1, LookupElement o2) { + LookupElement c1 = getCoreElement(o1); + LookupElement c2 = getCoreElement(o2); + + if (c1 instanceof LookupItem && c2 instanceof LookupItem) { + double priority1 = ((LookupItem)c1).getPriority(); + double priority2 = ((LookupItem)c2).getPriority(); + if (priority1 > priority2) return -1; + if (priority2 > priority1) return 1; + } + + int grouping1 = c1.getGrouping(); + int grouping2 = c2.getGrouping(); + if (grouping1 > grouping2) return -1; + if (grouping2 > grouping1) return 1; + + int stringCompare = o1.getLookupString().compareToIgnoreCase(o2.getLookupString()); + if (stringCompare != 0) return stringCompare; + + return proximityComparator.compare(o1.getObject(), o2.getObject()); + } + }); + } + + private static LookupElement getCoreElement(LookupElement element) { + while (element instanceof LookupElementDecorator) { + element = ((LookupElementDecorator) element).getDelegate(); + } + return element; + } + + + public void itemSelected(LookupElement item, final Lookup lookup) { + final StatisticsManager manager = StatisticsManager.getInstance(); + manager.incUseCount(CompletionService.STATISTICS_KEY, item, myLocation); + final List items = lookup.getItems(); + final LookupImpl lookupImpl = (LookupImpl)lookup; + final int count = Math.min(lookupImpl.getPreferredItemsCount(), lookupImpl.getList().getSelectedIndex()); + for (int i = 0; i < count; i++) { + final LookupElement element = items.get(i); + StatisticsInfo info = StatisticsManager.serialize(CompletionService.STATISTICS_KEY, element, myLocation); + if (info != null && info != StatisticsInfo.EMPTY && manager.getUseCount(info) == 0) { + manager.incUseCount(new StatisticsInfo(composeContextWithValue(info), item == element ? SELECTED : IGNORED)); + } + } + + } + + public int suggestPreselectedItem(List sorted) { + final CompletionPreselectSkipper[] skippers = CompletionPreselectSkipper.EP_NAME.getExtensions(); + + nextItem: for (int i = 0; i < sorted.size(); i++){ + LookupElement item = sorted.get(i); + final Object obj = item.getObject(); + if (obj instanceof PsiElement && !((PsiElement)obj).isValid()) continue; + + for (final CompletionPreselectSkipper skipper : skippers) { + if (skipper.skipElement(item, myLocation)) { + continue nextItem; + } + } + + return i; + } + return sorted.size() - 1; + } + + public static String composeContextWithValue(final StatisticsInfo info) { + return info.getContext() + "###" + info.getValue(); + } + + public Comparable[] getWeight(final LookupElement item) { + if (item.getUserData(WEIGHT) != null) return item.getUserData(WEIGHT); + + final Comparable[] result = new Comparable[]{WeighingService.weigh(CompletionService.WEIGHER_KEY, item, myLocation)}; + + item.putUserData(WEIGHT, result); + + return result; + } + + + public static int doCompare(final double priority1, final double priority2, final Comparable[] weight1, final Comparable[] weight2) { + if (priority1 != priority2) { + final double v = priority1 - priority2; + if (v > 0) return -1; + if (v < 0) return 1; + } + + for (int i = 0; i < weight1.length; i++) { + final Comparable w1 = weight1[i]; + final Comparable w2 = weight2[i]; + if (w1 != null || w2 != null) { + if (w1 == null) return 1; + if (w2 == null) return -1; + //noinspection unchecked + final int res = w1.compareTo(w2); + if (res != 0) return -res; + } + } + + return 0; + } + + @Override + public LookupItemWeightComparable getRelevance(LookupElement item) { + if (item.getUserData(RELEVANCE_KEY) != null) return item.getUserData(RELEVANCE_KEY); + + final double priority = item instanceof LookupItem ? ((LookupItem)item).getPriority() : 0; + final LookupItemWeightComparable result = new LookupItemWeightComparable(priority, getWeight(item)); + + item.putUserData(RELEVANCE_KEY, result); + + return result; + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreferencePolicy.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreferencePolicy.java deleted file mode 100644 index 0b8cedf12100..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreferencePolicy.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.intellij.codeInsight.completion; - -import com.intellij.codeInsight.lookup.Lookup; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; -import com.intellij.codeInsight.lookup.LookupItemPreferencePolicy; -import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.openapi.util.Key; -import com.intellij.psi.WeighingComparable; -import com.intellij.psi.WeighingService; -import com.intellij.psi.statistics.StatisticsInfo; -import com.intellij.psi.statistics.StatisticsManager; -import org.jetbrains.annotations.NonNls; - -import javax.swing.*; - -public class CompletionPreferencePolicy implements LookupItemPreferencePolicy{ - public static final Key> PRESELECT_WEIGHT = Key.create("PRESELECT_WEIGHT"); - @NonNls public static final String SELECTED = "selected"; - @NonNls public static final String IGNORED = "ignored"; - private final CompletionLocation myLocation; - - public CompletionPreferencePolicy(final CompletionParameters parameters) { - myLocation = new CompletionLocation(parameters); - } - - public void itemSelected(LookupElement item, final Lookup lookup) { - final StatisticsManager manager = StatisticsManager.getInstance(); - manager.incUseCount(CompletionService.STATISTICS_KEY, item, myLocation); - final LookupImpl lookupImpl = (LookupImpl)lookup; - final ListModel model = lookupImpl.getList().getModel(); - final int count = Math.min(lookupImpl.getPreferredItemsCount(), lookupImpl.getList().getSelectedIndex()); - for (int i = 0; i < count; i++) { - final LookupElement element = (LookupElement)model.getElementAt(i); - StatisticsInfo info = StatisticsManager.serialize(CompletionService.STATISTICS_KEY, element, myLocation); - if (info != null && info != StatisticsInfo.EMPTY && manager.getUseCount(info) == 0) { - manager.incUseCount(new StatisticsInfo(composeContextWithValue(info), item == element ? SELECTED : IGNORED)); - } - } - - } - - public static String composeContextWithValue(final StatisticsInfo info) { - return info.getContext() + "###" + info.getValue(); - } - - public Comparable[] getWeight(final LookupElement item) { - if (item.getUserData(LookupItem.WEIGHT) != null) return item.getUserData(LookupItem.WEIGHT); - - final Comparable[] result = new Comparable[]{WeighingService.weigh(CompletionService.WEIGHER_KEY, item, myLocation)}; - - item.putUserData(LookupItem.WEIGHT, result); - - return result; - } - - - public int compare(final LookupElement item1, final LookupElement item2) { - if (item1 == item2) return 0; - - if (item1 instanceof LookupItem && item2 instanceof LookupItem) { - double priority1 = ((LookupItem)item1).getPriority(); - double priority2 = ((LookupItem)item2).getPriority(); - if (priority1 > priority2) return -1; - if (priority1 < priority2) return 1; - } - - return preselectWeigh(item2).compareTo(preselectWeigh(item1)); - } - - private WeighingComparable preselectWeigh(final LookupElement item) { - WeighingComparable data = item.getUserData(PRESELECT_WEIGHT); - if (data == null) { - item.putUserData(PRESELECT_WEIGHT, data = WeighingService.weigh(CompletionService.PRESELECT_KEY, item, myLocation)); - } - return data; - } - - public static int doCompare(final double priority1, final double priority2, final Comparable[] weight1, final Comparable[] weight2) { - if (priority1 != priority2) { - final double v = priority1 - priority2; - if (v > 0) return -1; - if (v < 0) return 1; - } - - for (int i = 0; i < weight1.length; i++) { - final Comparable w1 = weight1[i]; - final Comparable w2 = weight2[i]; - if (w1 != null || w2 != null) { - if (w1 == null) return 1; - if (w2 == null) return -1; - final int res = w1.compareTo(w2); - if (res != 0) return -res; - } - } - - return 0; - } - -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 7847c1690d0f..d439494b0066 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -74,8 +74,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement myContextOriginal = contextOriginal; myFreezeSemaphore = freezeSemaphore; - myLookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, new LookupItem[0], "", new CompletionPreferencePolicy( - parameters)); + myLookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, LookupElement.EMPTY_ARRAY, "", new CompletionLookupArranger(parameters)); myLookup.addLookupListener(new LookupAdapter() { public void itemSelected(LookupEvent event) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/NegativeStatisticsWeigher.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/NegativeStatisticsWeigher.java index d54280e84ea9..075967f7be67 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/NegativeStatisticsWeigher.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/NegativeStatisticsWeigher.java @@ -19,7 +19,7 @@ public class NegativeStatisticsWeigher extends CompletionWeigher { final StatisticsInfo info = StatisticsManager.serialize(CompletionService.STATISTICS_KEY, item, location); if (info == null || info == StatisticsInfo.EMPTY) return 0; - final StatisticsInfo ignoreInfo = new StatisticsInfo(CompletionPreferencePolicy.composeContextWithValue(info), CompletionPreferencePolicy.IGNORED); + final StatisticsInfo ignoreInfo = new StatisticsInfo(CompletionLookupArranger.composeContextWithValue(info), CompletionLookupArranger.IGNORED); final int count = manager.getUseCount(ignoreInfo); if (count >= StatisticsManager.OBLIVION_THRESHOLD - 1) { return -1; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/Lookup.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/Lookup.java index 0777f99ef7cc..a993aea447d4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/Lookup.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/Lookup.java @@ -7,6 +7,7 @@ import com.intellij.psi.PsiFile; import org.jetbrains.annotations.Nullable; import java.awt.*; +import java.util.*; public interface Lookup extends UserDataHolder{ char NORMAL_SELECT_CHAR = '\n'; @@ -40,4 +41,6 @@ public interface Lookup extends UserDataHolder{ PsiFile getPsiFile(); boolean isCompletion(); + + java.util.List getItems(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java new file mode 100644 index 000000000000..5f4520305afa --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java @@ -0,0 +1,30 @@ +package com.intellij.codeInsight.lookup; + +import java.util.List; + +/** + * @author peter + */ +public abstract class LookupArranger { + public static final LookupArranger DEFAULT = new LookupArranger() { + @Override + public Comparable getRelevance(LookupElement element) { + return 0; + } + + @Override + public void sortItems(List items) { + } + }; + + public abstract Comparable getRelevance(LookupElement element); + + public void itemSelected(LookupElement item, final Lookup lookup) { + } + + public int suggestPreselectedItem(List sorted) { + return 0; + } + + public abstract void sortItems(List items); +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupItem.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupItem.java index 74896e41a441..0bbecf0b997e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupItem.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupItem.java @@ -44,8 +44,6 @@ public class LookupItem extends MutableLookupElement implements Comparable public static final Object INDICATE_ANONYMOUS = Key.create("INDICATE ANONYMOUS"); public static final Key DEPRECATED_ATTR = Key.create("DEPRECATED"); - public static final Key WEIGHT = Key.create("WEIGHT"); - public static final Object CASE_INSENSITIVE = Key.create("CASE_INSENSITIVE"); public static final Key TAIL_TYPE_ATTR = Key.create("myTailType"); // one of constants defined in SimpleTailType interface diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupManager.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupManager.java index 812329ea02fb..d76de52b0b04 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupManager.java @@ -3,6 +3,7 @@ package com.intellij.codeInsight.lookup; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.beans.PropertyChangeListener; @@ -20,13 +21,16 @@ public abstract class LookupManager { return getInstance(project).getActiveLookup(); } - public abstract Lookup showLookup(Editor editor, LookupElement[] items, LookupItemPreferencePolicy itemPreferencePolicy); - @Deprecated - public abstract Lookup showLookup(Editor editor, LookupElement[] items, LookupItemPreferencePolicy itemPreferencePolicy, @Nullable String bottomText); - public abstract Lookup showLookup(Editor editor, LookupElement[] items, String prefix, LookupItemPreferencePolicy itemPreferencePolicy); - @Deprecated - public abstract Lookup showLookup(Editor editor, LookupElement[] items, String prefix, LookupItemPreferencePolicy itemPreferencePolicy, - @Nullable String bottomText); + public Lookup showLookup(Editor editor, @NotNull LookupElement... items) { + return showLookup(editor, items, "", LookupArranger.DEFAULT); + } + + public Lookup showLookup(Editor editor, @NotNull LookupElement[] items, String prefix) { + return showLookup(editor, items, prefix, LookupArranger.DEFAULT); + } + + public abstract Lookup showLookup(Editor editor, @NotNull LookupElement[] items, String prefix, @NotNull LookupArranger arranger); + public abstract void hideActiveLookup(); public abstract Lookup getActiveLookup(); @@ -37,9 +41,6 @@ public abstract class LookupManager { public abstract boolean isDisposed(); - public abstract Lookup createLookup(Editor editor, LookupElement[] items, final String prefix, LookupItemPreferencePolicy itemPreferencePolicy); + public abstract Lookup createLookup(Editor editor, @NotNull LookupElement[] items, final String prefix, LookupArranger arranger); - @Deprecated - public abstract Lookup createLookup(Editor editor, LookupElement[] items, final String prefix, LookupItemPreferencePolicy itemPreferencePolicy, - @Nullable String bottomText); } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index ca20e8ae92f4..3f2b21b8d5da 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -1,6 +1,6 @@ package com.intellij.codeInsight.lookup.impl; -import com.intellij.codeInsight.completion.CompletionPreferencePolicy; +import com.intellij.codeInsight.completion.CompletionLookupArranger; import com.intellij.codeInsight.completion.PrefixMatcher; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.hint.HintManagerImpl; @@ -22,7 +22,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.proximity.PsiProximityComparator; import com.intellij.ui.LightweightHint; import com.intellij.ui.ListScrollingUtil; import com.intellij.ui.plaf.beg.BegPopupMenuBorder; @@ -33,6 +32,7 @@ import com.intellij.util.containers.ConcurrentHashMap; import com.intellij.util.containers.SortedList; import com.intellij.util.ui.AsyncProcessIcon; import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -53,8 +53,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private final Project myProject; private final Editor myEditor; - private final SortedList myItems; - private final SortedMap> myItemsMap; + private final Map> myItemActions = new ConcurrentHashMap>(); private int myMinPrefixLength; private int myPreferredItemsCount; @@ -62,7 +61,9 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private RangeMarker myInitialSelection; private long myShownStamp = -1; private String myInitialPrefix; - @Nullable private final LookupItemPreferencePolicy myItemPreferencePolicy; + private final LookupArranger myArranger; + private final ArrayList myItems; + @Nullable private List mySortedItems; private RangeMarker myLookupStartMarker; private int myOldLookupStartOffset; @@ -81,63 +82,28 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private LookupElement myPreselectedItem = EMPTY_LOOKUP_ITEM; private boolean myDirty; private String myAdditionalPrefix = ""; - private final PsiElement myElement; private final AsyncProcessIcon myProcessIcon; - private final Comparator myComparator; private volatile boolean myCalculating; private final JLabel myAdComponent; private volatile String myAdText; private volatile int myLookupWidth = 50; private static final int LOOKUP_HEIGHT = Integer.getInteger("idea.lookup.height", 11).intValue(); - public LookupImpl(Project project, Editor editor, LookupElement[] items, @Nullable LookupItemPreferencePolicy itemPreferencePolicy){ + public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){ super(new JPanel(new BorderLayout())); myProject = project; myEditor = editor; - myItemPreferencePolicy = itemPreferencePolicy; + myArranger = arranger; + myItems = new ArrayList(); + setInitialOffset(myEditor.getCaretModel().getOffset(), myEditor.getSelectionModel().getSelectionStart(), myEditor.getSelectionModel().getSelectionEnd()); - final Document document = myEditor.getDocument(); - final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document); - myElement = psiFile == null ? null : psiFile.findElementAt(myEditor.getCaretModel().getOffset()); - - final PsiProximityComparator proximityComparator = new PsiProximityComparator(myElement == null ? psiFile : myElement); - myComparator = new Comparator() { - public int compare(LookupElement o1, LookupElement o2) { - LookupElement c1 = getCoreElement(o1); - LookupElement c2 = getCoreElement(o2); - - if (c1 instanceof LookupItem && c2 instanceof LookupItem) { - double priority1 = ((LookupItem)c1).getPriority(); - double priority2 = ((LookupItem)c2).getPriority(); - if (priority1 > priority2) return -1; - if (priority2 > priority1) return 1; - } - - int grouping1 = c1.getGrouping(); - int grouping2 = c2.getGrouping(); - if (grouping1 > grouping2) return -1; - if (grouping2 > grouping1) return 1; - - int stringCompare = o1.getLookupString().compareToIgnoreCase(o2.getLookupString()); - if (stringCompare != 0) return stringCompare; - - return proximityComparator.compare(o1.getObject(), o2.getObject()); - } - }; - myItems = new SortedList(myComparator); - myItemsMap = new TreeMap>(); - myProcessIcon = new AsyncProcessIcon("Completion progress"); myProcessIcon.setVisible(false); myList = new JList(new DefaultListModel()); myCellRenderer = new LookupCellRenderer(this); myList.setCellRenderer(myCellRenderer); - for (final LookupElement item : items) { - addItem(item); - } - myList.setFocusable(false); myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -156,15 +122,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { getComponent().add(bottomPanel, BorderLayout.SOUTH); getComponent().setBorder(new BegPopupMenuBorder()); - updateList(); - selectMostPreferableItem(); - } - - private LookupElement getCoreElement(LookupElement element) { - while (element instanceof LookupElementDecorator) { - element = ((LookupElementDecorator) element).getDelegate(); - } - return element; + updateListBounds(); } public AsyncProcessIcon getProcessIcon() { @@ -190,11 +148,14 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { @TestOnly public void resort() { - final ArrayList items = new ArrayList(myItems); myDirty = false; myPreselectedItem = EMPTY_LOOKUP_ITEM; - myItemsMap.clear(); - myItems.clear(); + final ArrayList items; + synchronized (myItems) { + items = new ArrayList(myItems); + myItems.clear(); + mySortedItems = null; + } for (final LookupElement item : items) { addItem(item); } @@ -202,27 +163,19 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } public void addItem(LookupElement item) { - final double priority = item instanceof LookupItem ? ((LookupItem)item).getPriority() : 0; - final Comparable[] weight = getWeight(myItemPreferencePolicy, myElement, item); - final LookupItemWeightComparable comparable = new LookupItemWeightComparable(priority, weight); - final CollectConsumer consumer = new CollectConsumer(); for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) { provider.fillActions(item, this, consumer); } myItemActions.put(item, consumer.getResult()); - synchronized (myItems) { - myItems.add(item); - - SortedList list = myItemsMap.get(comparable); - if (list == null) { - myItemsMap.put(comparable, list = new SortedList(myComparator)); - } - list.add(item); - } int maxWidth = myCellRenderer.updateMaximumWidth(item); myLookupWidth = Math.max(maxWidth, myLookupWidth); + + synchronized (myItems) { + myItems.add(item); + mySortedItems = null; + } } public Collection getActionsFor(LookupElement element) { @@ -230,23 +183,6 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { return collection == null ? Collections.emptyList() : collection; } - @Nullable - public LookupItemPreferencePolicy getItemPreferencePolicy() { - return myItemPreferencePolicy; - } - - private static Comparable[] getWeight(final LookupItemPreferencePolicy itemPreferencePolicy, final PsiElement context, - final LookupElement item) { - if (itemPreferencePolicy instanceof CompletionPreferencePolicy) { - return ((CompletionPreferencePolicy)itemPreferencePolicy).getWeight(item); - } - Comparable i = null; - if (item.getObject() instanceof PsiElement) { - i = PsiProximityComparator.getProximity((PsiElement)item.getObject(), context); - } - return new Comparable[]{i}; - } - public int getMinPrefixLength() { return myMinPrefixLength; } @@ -255,12 +191,32 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { return myList; } - public LookupElement[] getItems(){ + @NotNull + private List getSortedItems() { synchronized (myItems) { - return myItems.toArray(new LookupElement[myItems.size()]); + List sortedItems = mySortedItems; + if (sortedItems == null) { + myArranger.sortItems(sortedItems = new ArrayList(myItems)); + mySortedItems = sortedItems; + } + return sortedItems; } } + public List getItems() { + final ArrayList result = new ArrayList(); + final Object[] objects; + synchronized (myList) { + objects = ((DefaultListModel)myList.getModel()).toArray(); + } + for (final Object object : objects) { + if (!(object instanceof EmptyLookupItem)) { + result.add((LookupElement) object); + } + } + return result; + } + public void setAdvertisementText(@Nullable String text) { myAdText = text; } @@ -282,69 +238,80 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } private void updateList() { - synchronized (myItems) { - int minPrefixLength = myItems.isEmpty() ? 0 : Integer.MAX_VALUE; - for (final LookupElement item : myItems) { - minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength); - } - if (myMinPrefixLength != minPrefixLength) { - myLookupStartMarker = null; - myOldLookupStartOffset = -1; - } - myMinPrefixLength = minPrefixLength; + final List items = getSortedItems(); + SortedMap> itemsMap = new TreeMap>(); + int minPrefixLength = items.isEmpty() ? 0 : Integer.MAX_VALUE; + for (final LookupElement item : items) { + minPrefixLength = Math.min(item.getPrefixMatcher().getPrefix().length(), minPrefixLength); - Object oldSelected = !myDirty ? null : myList.getSelectedValue(); + final Comparable relevance = myArranger.getRelevance(item); + List list = itemsMap.get(relevance); + if (list == null) { + itemsMap.put(relevance, list = new ArrayList()); + } + list.add(item); + } + + if (myMinPrefixLength != minPrefixLength) { + myLookupStartMarker = null; + myOldLookupStartOffset = -1; + } + myMinPrefixLength = minPrefixLength; + + Object oldSelected = !myDirty ? null : myList.getSelectedValue(); + boolean hasExactPrefixes; + final boolean hasPreselectedItem; + synchronized (myList) { DefaultListModel model = (DefaultListModel)myList.getModel(); model.clear(); Set firstItems = new THashSet(); - addExactPrefixItems(model, firstItems); - - boolean hasExactPrefixes = !firstItems.isEmpty(); - - addMostRelevantItems(model, firstItems); - - final boolean hasPreselectedItem = addPreselectedItem(model, firstItems); - + hasExactPrefixes = addExactPrefixItems(model, firstItems, items); + addMostRelevantItems(model, firstItems, itemsMap.values()); + hasPreselectedItem = addPreselectedItem(model, firstItems); myPreferredItemsCount = firstItems.size(); - addRemainingItemsLexicographically(model, firstItems); + addRemainingItemsLexicographically(model, firstItems, items); + } - boolean isEmpty = model.getSize() == 0; - if (isEmpty) { - addEmptyItem(model); - } else { - myList.setFixedCellWidth(myLookupWidth); + updateListBounds(); + + myAdComponent.setText(myAdText); + + if (myList.getModel().getSize() > 0) { + if (oldSelected != null) { + if (hasExactPrefixes || !ListScrollingUtil.selectItem(myList, oldSelected)) { + selectMostPreferableItem(); + } } - myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, myList.getModel().getElementAt(0), 0, false, false).getPreferredSize().height); - - myList.setVisibleRowCount(Math.min(myList.getModel().getSize(), LOOKUP_HEIGHT)); - - myAdComponent.setText(myAdText); - - if (!isEmpty) { - if (oldSelected != null) { - if (hasExactPrefixes || !ListScrollingUtil.selectItem(myList, oldSelected)) { - selectMostPreferableItem(); - } + else { + if (myPreselectedItem == EMPTY_LOOKUP_ITEM) { + selectMostPreferableItem(); + myPreselectedItem = getCurrentItem(); + } + else if (hasPreselectedItem && !hasExactPrefixes) { + ListScrollingUtil.selectItem(myList, myPreselectedItem); } else { - if (myPreselectedItem == EMPTY_LOOKUP_ITEM) { - selectMostPreferableItem(); - myPreselectedItem = getCurrentItem(); - } - else if (hasPreselectedItem && !hasExactPrefixes) { - ListScrollingUtil.selectItem(myList, myPreselectedItem); - } - else { - selectMostPreferableItem(); - } + selectMostPreferableItem(); } } } } + private void updateListBounds() { + final ListModel model = myList.getModel(); + if (model.getSize() == 0) { + addEmptyItem((DefaultListModel)model); + } else { + myList.setFixedCellWidth(myLookupWidth); + } + myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false).getPreferredSize().height); + + myList.setVisibleRowCount(Math.min(model.getSize(), LOOKUP_HEIGHT)); + } + private void addEmptyItem(DefaultListModel model) { LookupItem item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions")); item.setPrefixMatcher(new CamelHumpMatcher("")); @@ -356,7 +323,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { model.addElement(item); } - private void addRemainingItemsLexicographically(DefaultListModel model, Set firstItems) { + private void addRemainingItemsLexicographically(DefaultListModel model, Set firstItems, List myItems) { for (LookupElement item : myItems) { if (!firstItems.contains(item) && prefixMatches(item)) { model.addElement(item); @@ -373,10 +340,10 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { return hasPreselectedItem; } - private void addMostRelevantItems(DefaultListModel model, Set firstItems) { - for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) { + private void addMostRelevantItems(DefaultListModel model, Set firstItems, final Collection> sortedItems) { + for (final List elements : sortedItems) { final List suitable = new SmartList(); - for (final LookupElement item : myItemsMap.get(comparable)) { + for (final LookupElement item : elements) { if (!firstItems.contains(item) && prefixMatches(item)) { suitable.add(item); } @@ -390,15 +357,29 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } } - private void addExactPrefixItems(DefaultListModel model, Set firstItems) { - for (final LookupItemWeightComparable comparable : myItemsMap.keySet()) { - for (final LookupElement item : myItemsMap.get(comparable)) { - if (isExactPrefixItem(item)) { - model.addElement(item); - firstItems.add(item); - } + private boolean addExactPrefixItems(DefaultListModel model, Set firstItems, final List elements) { + List sorted = new SortedList(new Comparator() { + public int compare(LookupElement o1, LookupElement o2) { + //noinspection unchecked + return myArranger.getRelevance(o1).compareTo(myArranger.getRelevance(o2)); + } + }); + for (final LookupElement item : elements) { + if (isExactPrefixItem(item)) { + sorted.add(item); + } } + for (final LookupElement item : sorted) { + model.addElement(item); + firstItems.add(item); + } + + return !firstItems.isEmpty(); + } + + private boolean isExactPrefixItem(LookupElement item) { + return item.getAllLookupStrings().contains(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix); } private boolean prefixMatches(final LookupElement item) { @@ -424,9 +405,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { JComponent internalComponent = myEditor.getContentComponent(); final JRootPane rootPane = editorComponent.getRootPane(); if (rootPane == null) { - synchronized (myItems) { - LOG.assertTrue(false, myItems); - } + LOG.assertTrue(false, myArranger); } JLayeredPane layeredPane = rootPane.getLayeredPane(); Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane); @@ -621,14 +600,15 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { return Math.max(offset - myMinPrefixLength, 0); } - private void selectMostPreferableItem(){ - final int index = doSelectMostPreferableItem(((DefaultListModel)myList.getModel()).toArray()); + private void selectMostPreferableItem() { + final List sortedItems = getItems(); + final int index = doSelectMostPreferableItem(sortedItems); myList.setSelectedIndex(index); if (index >= 0 && index < myList.getModel().getSize()){ ListScrollingUtil.selectItem(myList, index); } - else if (!myItems.isEmpty()) { + else if (!sortedItems.isEmpty()) { ListScrollingUtil.selectItem(myList, 0); } } @@ -666,8 +646,8 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { public void fireItemSelected(final LookupElement item, char completionChar){ PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - if (item != null && myItemPreferencePolicy != null){ - myItemPreferencePolicy.itemSelected(item, this); + if (item != null) { + myArranger.itemSelected(item, this); } if (!myListeners.isEmpty()){ @@ -787,14 +767,16 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { final String newPrefix = presentPrefix + afterCaret; synchronized (myItems) { - for (Iterator it = myItems.iterator(); it.hasNext();) { - LookupElement item = it.next(); + for (Iterator iterator = myItems.iterator(); iterator.hasNext();) { + LookupElement item = iterator.next(); if (!item.setPrefixMatcher(item.getPrefixMatcher().cloneWithPrefix(newPrefix))) { - it.remove(); + iterator.remove(); + mySortedItems = null; } } - myAdditionalPrefix = ""; } + myAdditionalPrefix = ""; + myAdditionalPrefix = ""; updateList(); offset += afterCaret.length(); @@ -813,7 +795,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } public boolean isCompletion() { - return myItemPreferencePolicy instanceof CompletionPreferencePolicy; + return myArranger instanceof CompletionLookupArranger; } public PsiElement getPsiElement() { @@ -877,36 +859,25 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } } - private int doSelectMostPreferableItem(Object[] items) { - if (myItemPreferencePolicy == null){ + private int doSelectMostPreferableItem(List items) { + if (items.isEmpty()) { return -1; } - int prefItemIndex = -1; + if (items.size() == 1) { + return 0; + } - for(int i = 0; i < items.length; i++){ - LookupElement item = (LookupElement)items[i]; - final Object obj = item.getObject(); - if (obj instanceof PsiElement && !((PsiElement)obj).isValid()) continue; - - if (prefItemIndex == -1) { - prefItemIndex = i; - } - else { - int d = myItemPreferencePolicy.compare(item, (LookupElement)items[prefItemIndex]); - if (d < 0) { - prefItemIndex = i; - } - } + for (int i = 0; i < items.size(); i++) { + LookupElement item = items.get(i); if (isExactPrefixItem(item)) { - break; + return i; } } - return prefItemIndex; - } - private boolean isExactPrefixItem(LookupElement item) { - return item.getAllLookupStrings().contains(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix); + final int index = myArranger.suggestPreselectedItem(items); + assert index >= 0 && index < items.size(); + return index; } public void refreshUi() { @@ -925,12 +896,8 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } } - public LookupElement[] getSortedItems() { - synchronized (myItems) { - final LookupElement[] result = new LookupElement[myList.getModel().getSize()]; - ((DefaultListModel)myList.getModel()).copyInto(result); - return result; - } + @TestOnly + public LookupArranger getLookupModel() { + return myArranger; } - } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupItemWeightComparable.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupItemWeightComparable.java index c55269c69090..372f8cba6ae5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupItemWeightComparable.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupItemWeightComparable.java @@ -3,7 +3,7 @@ */ package com.intellij.codeInsight.lookup.impl; -import com.intellij.codeInsight.completion.CompletionPreferencePolicy; +import com.intellij.codeInsight.completion.CompletionLookupArranger; import java.util.Arrays; @@ -20,7 +20,7 @@ public class LookupItemWeightComparable implements Comparable 0) { + for (final LookupElement item : items) { + item.setPrefixMatcher(new CamelHumpMatcher(prefix)); + myActiveLookup.addItem(item); + } + myActiveLookup.refreshUi(); + } + myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, null, myActiveLookup); return myActiveLookup; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/TestLookupManager.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/TestLookupManager.java index ec9cc9f35251..6dfe9e3f236d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/TestLookupManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/TestLookupManager.java @@ -1,10 +1,6 @@ package com.intellij.codeInsight.lookup.impl; -import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; -import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItemPreferencePolicy; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.util.messages.MessageBus; @@ -16,28 +12,13 @@ import com.intellij.util.messages.MessageBus; * To change this template use Options | File Templates. */ public class TestLookupManager extends LookupManagerImpl{ - private final Project myProject; public TestLookupManager(Project project, MessageBus bus){ super(project, bus); - myProject = project; - } - - public Lookup showLookup(final Editor editor, LookupElement[] items, LookupItemPreferencePolicy itemPreferencePolicy) { - hideActiveLookup(); - - for (final LookupElement item : items) { - item.setPrefixMatcher(new CamelHumpMatcher("")); - } - myActiveLookup = new LookupImpl(myProject, editor, items, itemPreferencePolicy); - myActiveLookupEditor = editor; - myActiveLookup.show(); - return myActiveLookup; } public void forceSelection(char completion, int index){ if(myActiveLookup == null) throw new RuntimeException("There are no items in this lookup"); - final LookupElement[] items = myActiveLookup.getItems(); - final LookupElement lookupItem = items[index]; + final LookupElement lookupItem = myActiveLookup.getItems().get(index); myActiveLookup.setCurrentItem(lookupItem); myActiveLookup.finishLookup(completion); } @@ -54,7 +35,4 @@ public class TestLookupManager extends LookupManagerImpl{ } } - public LookupElement[] getItems(){ - return myActiveLookup != null ? myActiveLookup.getItems() : null; - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java index 6f2afdae58e1..3c59607c0168 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java @@ -44,7 +44,7 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{ return; } - final LookupImpl lookup = (LookupImpl) LookupManager.getInstance(project).createLookup(editor, items, prefix, null); + final LookupImpl lookup = (LookupImpl) LookupManager.getInstance(project).createLookup(editor, items, prefix, LookupArranger.DEFAULT); lookup.addLookupListener( new LookupAdapter() { public void itemSelected(LookupEvent event) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index a33e512f9b7e..6f8c8402e8ff 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -1,7 +1,9 @@ package com.intellij.codeInsight.template.impl; import com.intellij.codeInsight.AutoPopupController; -import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.completion.CompletionInitializationContext; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.codeInsight.completion.OffsetMap; import com.intellij.codeInsight.lookup.*; import com.intellij.codeInsight.template.*; import com.intellij.lang.LanguageLiteralEscapers; @@ -23,8 +25,8 @@ import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -400,9 +402,6 @@ public class TemplateState implements Disposable { EditorModificationUtil.insertStringAtCaret(myEditor, s); itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems); } else { - if (lookupItems[0] instanceof LookupItem) { - ((LookupItem)lookupItems[0]).setPriority(Integer.MAX_VALUE); - } runLookup(currentSegmentNumber, lookupItems, psiFile); } } @@ -422,9 +421,7 @@ public class TemplateState implements Disposable { final LookupManager lookupManager = LookupManager.getInstance(myProject); if (lookupManager.isDisposed()) return; - final CompletionParameters parameters = - new CompletionParameters(psiFile, psiFile, CompletionType.BASIC, myEditor.getCaretModel().getOffset(), 1); - final Lookup lookup = lookupManager.showLookup(myEditor, lookupItems, new CompletionPreferencePolicy(parameters)); + final Lookup lookup = lookupManager.showLookup(myEditor, lookupItems); toProcessTab = false; lookup.addLookupListener(new LookupAdapter() { public void lookupCanceled(LookupEvent event) { diff --git a/platform/lang-impl/src/com/intellij/internal/DumpLookupElementWeights.java b/platform/lang-impl/src/com/intellij/internal/DumpLookupElementWeights.java index cd570da11202..67d705227849 100644 --- a/platform/lang-impl/src/com/intellij/internal/DumpLookupElementWeights.java +++ b/platform/lang-impl/src/com/intellij/internal/DumpLookupElementWeights.java @@ -1,9 +1,9 @@ package com.intellij.internal; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupItem; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.codeInsight.completion.CompletionLookupArranger; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAware; @@ -33,7 +33,7 @@ public class DumpLookupElementWeights extends AnAction implements DumbAware { final int count = lookup.getPreferredItemsCount(); for (int i = 0; i < model.getSize(); i++) { final LookupElement item = (LookupElement)model.getElementAt(i); - System.out.println(item.getLookupString() + Arrays.toString(item.getUserData(LookupItem.WEIGHT))); + System.out.println(item.getLookupString() + Arrays.toString(item.getUserData(CompletionLookupArranger.WEIGHT))); if (i == count - 1) { System.out.println("------------"); } diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameDialog.java b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameDialog.java index e1aaa8e0d792..cc5907bca1dc 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameDialog.java @@ -143,7 +143,7 @@ public class RenameDialog extends RefactoringDialog { final LookupElement[] lookupItems = items.toArray(new LookupElement[items.size()]); editor.getCaretModel().moveToOffset(prefix.length()); editor.getSelectionModel().removeSelection(); - LookupManager.getInstance(myProject).showLookup(editor, lookupItems, prefix, null); + LookupManager.getInstance(myProject).showLookup(editor, lookupItems, prefix); } } diff --git a/platform/lang-impl/src/com/intellij/ui/TextFieldWithAutoCompletion.java b/platform/lang-impl/src/com/intellij/ui/TextFieldWithAutoCompletion.java index 046c4b004485..2f2e14daa734 100644 --- a/platform/lang-impl/src/com/intellij/ui/TextFieldWithAutoCompletion.java +++ b/platform/lang-impl/src/com/intellij/ui/TextFieldWithAutoCompletion.java @@ -78,7 +78,7 @@ public class TextFieldWithAutoCompletion extends EditorTextField { assert editor != null; editor.getSelectionModel().removeSelection(); - LookupManager.getInstance(getProject()).showLookup(editor, calcLookupItems(getPrefix()), getPrefix(), null); + LookupManager.getInstance(getProject()).showLookup(editor, calcLookupItems(getPrefix()), getPrefix()); } } diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 330d495d514f..53e0ed20ef37 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -245,6 +245,7 @@ + diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 43db69de562a..114371c43cbe 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -712,7 +712,8 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig return myEmptyLookup ? LookupElement.EMPTY_ARRAY : null; } else { - return lookup.getSortedItems(); + final List list = lookup.getItems(); + return list.toArray(new LookupElement[list.size()]); } } diff --git a/plugins/ant/tests/src/com/intellij/lang/ant/AntCompletionTest.java b/plugins/ant/tests/src/com/intellij/lang/ant/AntCompletionTest.java index bde580be3cb0..23b0fb1f5f8b 100644 --- a/plugins/ant/tests/src/com/intellij/lang/ant/AntCompletionTest.java +++ b/plugins/ant/tests/src/com/intellij/lang/ant/AntCompletionTest.java @@ -8,6 +8,9 @@ import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.lookup.impl.TestLookupManager; import com.intellij.openapi.application.PluginPathManager; import com.intellij.testFramework.LightCodeInsightTestCase; +import org.jetbrains.annotations.NotNull; + +import java.util.List; public class AntCompletionTest extends LightCodeInsightTestCase { @@ -83,9 +86,6 @@ public class AntCompletionTest extends LightCodeInsightTestCase { public void testInsertion1() throws Exception { configureByFile("test8.xml"); performNormalCompletion(); - if (getItems() != null) { - select(); - } checkResultByFile("/test8-out.xml"); } @@ -93,18 +93,12 @@ public class AntCompletionTest extends LightCodeInsightTestCase { public void testInsertion2() throws Exception { configureByFile("test9.xml"); performNormalCompletion(); - if (getItems() != null) { - select(); - } checkResultByFile("/test8-out.xml"); } public void testTargetCompletion() throws Exception { configureByFile("targetCompletion.xml"); performNormalCompletion(); - if (getItems() != null) { - select(); - } checkResultByFile("/targetCompletion-out.xml"); } @@ -115,18 +109,13 @@ public class AntCompletionTest extends LightCodeInsightTestCase { AntSupport.markFileAsAntFile(myVFile, myFile.getViewProvider(), true); performNormalCompletion(); - if (getItems() != null) { - select(); - } + select(); checkResultByFile("/targetCompletion2-out.xml"); } public void testEntityCompletion() throws Exception { configureByFile("EntityCompletion.xml"); performNormalCompletion(); - if (getItems() != null) { - select(); - } checkResultByFile("EntityCompletion-out.xml"); } @@ -174,32 +163,32 @@ public class AntCompletionTest extends LightCodeInsightTestCase { checkResultByFile(testName + "-out.xml"); } - private void select() { + private static void select() { select(Lookup.NORMAL_SELECT_CHAR, getSelected()); } - private void performNormalCompletion() { + private static void performNormalCompletion() { new CodeCompletionHandlerBase(CompletionType.BASIC).invoke(getProject(), getEditor(), AntSupport.getAntFile(myFile)); } - private void select(char completionChar, LookupElement item) { + private static void select(char completionChar, LookupElement item) { ((TestLookupManager)LookupManager.getInstance(getProject())).forceSelection(completionChar, item); } - private LookupElement getSelected() { - return LookupManager.getInstance(getProject()).getActiveLookup().getCurrentItem(); + private static LookupElement getSelected() { + final Lookup lookup = LookupManager.getInstance(getProject()).getActiveLookup(); + return lookup.getCurrentItem(); } - private LookupElement[] getItems() { - return ((TestLookupManager)LookupManager.getInstance(getProject())).getItems(); + @NotNull + private static LookupElement[] getItems() { + final List list = LookupManager.getInstance(getProject()).getActiveLookup().getItems(); + return list.toArray(new LookupElement[list.size()]); } protected void tearDown() throws Exception { - if (getItems() != null) LookupManager.getInstance(getProject()).hideActiveLookup(); + LookupManager.getInstance(getProject()).hideActiveLookup(); super.tearDown(); } - //public static void main(String[] args) { - // new TestRunner().doRun(new TestSuite(AntCompletionTest.class)); - //} } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/actions/CompleteWordFromDictionaryAction.java b/plugins/spellchecker/src/com/intellij/spellchecker/actions/CompleteWordFromDictionaryAction.java index 411c27a1bef0..33f6aa5bb6b5 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/actions/CompleteWordFromDictionaryAction.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/actions/CompleteWordFromDictionaryAction.java @@ -62,12 +62,8 @@ public final class CompleteWordFromDictionaryAction extends AnAction { lookupItems.add(LookupElementBuilder.create(variant)); } - LookupElement[] items = new LookupElement[lookupItems.size()]; - items = lookupItems.toArray(items); - LookupManager lookupManager = LookupManager.getInstance(project); - lookupManager.showLookup(editor, items, prefix, null); - - /*AnnotationHolder.createInfoAnnotation(TextRange.from(0, prefix.length()), "test");*/ + LookupElement[] items = lookupItems.toArray(new LookupElement[lookupItems.size()]); + LookupManager.getInstance(project).showLookup(editor, items, prefix); } } } diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java index 4142df791d9c..223dd3521990 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java +++ b/plugins/spellchecker/src/com/intellij/spellchecker/quickfixes/ChangeTo.java @@ -73,7 +73,7 @@ public class ChangeTo implements SpellCheckerQuickFix { LookupElement[] items = new LookupElement[lookupItems.size()]; items = lookupItems.toArray(items); LookupManager lookupManager = LookupManager.getInstance(project); - lookupManager.showLookup(editor, items, null); + lookupManager.showLookup(editor, items); } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 94a8218eb3e5..c406da9e761b 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -746,8 +746,8 @@ - + + diff --git a/xml/impl/src/com/intellij/xml/refactoring/XmlTagRenameDialog.java b/xml/impl/src/com/intellij/xml/refactoring/XmlTagRenameDialog.java index 285e7a099c6a..4392ac71259a 100644 --- a/xml/impl/src/com/intellij/xml/refactoring/XmlTagRenameDialog.java +++ b/xml/impl/src/com/intellij/xml/refactoring/XmlTagRenameDialog.java @@ -116,7 +116,7 @@ public class XmlTagRenameDialog extends RefactoringDialog { LookupElement[] lookupItems = set.toArray(new LookupElement[set.size()]); editor.getCaretModel().moveToOffset(prefix.length()); editor.getSelectionModel().removeSelection(); - LookupManager.getInstance(getProject()).showLookup(editor, lookupItems, prefix, null); + LookupManager.getInstance(getProject()).showLookup(editor, lookupItems, prefix); } }