diff --git a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementWeigher.java b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementWeigher.java index fff9c3bf551d..61df4cd82467 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementWeigher.java +++ b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementWeigher.java @@ -16,15 +16,26 @@ package com.intellij.codeInsight.lookup; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author peter */ public abstract class LookupElementWeigher { private final String myId; + private final boolean myNegated; + + protected LookupElementWeigher(String id, boolean negated) { + myId = id; + myNegated = negated; + } protected LookupElementWeigher(String id) { - myId = id; + this(id, false); + } + + public boolean isNegated() { + return myNegated; } @Override @@ -32,7 +43,7 @@ public abstract class LookupElementWeigher { return myId; } - @NotNull + @Nullable public abstract Comparable weigh(@NotNull LookupElement element); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java index b05af5f6371e..e35d45803640 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java @@ -1,356 +1,355 @@ -/* - * 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.impl; - -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.completion.*; -import com.intellij.codeInsight.lookup.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectManagerAdapter; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.Weigher; -import com.intellij.psi.WeighingService; -import com.intellij.psi.codeStyle.MinusculeMatcher; -import com.intellij.psi.codeStyle.NameUtil; -import com.intellij.psi.impl.DebugUtil; -import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; - -/** - * @author peter - */ -public class CompletionServiceImpl extends CompletionService{ - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.impl.CompletionServiceImpl"); - private static volatile CompletionPhase ourPhase = CompletionPhase.NoCompletion; - private static String ourPhaseTrace; - private static CodeInsightSettings ourSettings = CodeInsightSettings.getInstance(); - - public CompletionServiceImpl() { - ProjectManager.getInstance().addProjectManagerListener(new ProjectManagerAdapter() { - @Override - public void projectClosing(Project project) { - CompletionProgressIndicator indicator = getCurrentCompletion(); - if (indicator != null && indicator.getProject() == project) { - LookupManager.getInstance(indicator.getProject()).hideActiveLookup(); - setCompletionPhase(CompletionPhase.NoCompletion); - } - else if (indicator == null) { - setCompletionPhase(CompletionPhase.NoCompletion); - } - } - }); - } - - @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) - public static CompletionServiceImpl getCompletionService() { - return (CompletionServiceImpl)CompletionService.getCompletionService(); - } - - @Override - public String getAdvertisementText() { - final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); - return completion == null ? null : completion.getLookup().getAdvertisementText(); - } - - public void setAdvertisementText(@Nullable final String text) { - final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); - if (completion != null) { - completion.getLookup().setAdvertisementText(text); - } - } - - public CompletionResultSet createResultSet(final CompletionParameters parameters, final Consumer consumer, - @NotNull final CompletionContributor contributor) { - final PsiElement position = parameters.getPosition(); - final String prefix = CompletionData.findPrefixStatic(position, parameters.getOffset()); - final String textBeforePosition = parameters.getPosition().getContainingFile().getText().substring(0, parameters.getOffset()); - ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (!(indicator instanceof CompletionProgressIndicator)) { - throw new AssertionError("createResultSet may be invoked only from completion thread: " + indicator + "!=" + getCurrentCompletion() + "; phase set at " + ourPhaseTrace); - } - CompletionProgressIndicator process = (CompletionProgressIndicator)indicator; - CamelHumpMatcher matcher = new CamelHumpMatcher(prefix); - CompletionSorterImpl sorter = defaultSorter(parameters, matcher); - return new CompletionResultSetImpl(consumer, textBeforePosition, matcher, contributor,parameters, sorter, process, null); - } - - @Override - public CompletionProgressIndicator getCurrentCompletion() { - if (isPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.CommittingDocuments.class, - CompletionPhase.Synchronous.class)) { - return ourPhase.indicator; - } - return null; - } - - private static int getPrefixMatchingDegree(LookupElement item, CompletionLocation location) { - final MinusculeMatcher matcher = getMinusculeMatcher(location.getCompletionParameters().getLookup().itemPattern(item)); - - int max = Integer.MIN_VALUE; - for (String lookupString : item.getAllLookupStrings()) { - max = Math.max(max, matcher.matchingDegree(lookupString)); - } - return max; - } - - private static volatile Pair lastMatcher; - - private static MinusculeMatcher getMinusculeMatcher(String prefix) { - final int setting = ourSettings.COMPLETION_CASE_SENSITIVE; - final NameUtil.MatchingCaseSensitivity sensitivity = - setting == CodeInsightSettings.NONE ? NameUtil.MatchingCaseSensitivity.NONE : - setting == CodeInsightSettings.FIRST_LETTER ? NameUtil.MatchingCaseSensitivity.FIRST_LETTER : NameUtil.MatchingCaseSensitivity.ALL; - - Pair pair = lastMatcher; - if (pair != null && pair.first.equals(prefix)) { - return pair.second; - } - - MinusculeMatcher matcher = new MinusculeMatcher(CamelHumpMatcher.applyMiddleMatching(prefix), sensitivity); - lastMatcher = Pair.create(prefix, matcher); - return matcher; - } - - private static class CompletionResultSetImpl extends CompletionResultSet { - private final String myTextBeforePosition; - private final CompletionParameters myParameters; - private final CompletionSorterImpl mySorter; - private final CompletionProgressIndicator myProcess; - @Nullable private final CompletionResultSetImpl myOriginal; - - public CompletionResultSetImpl(final Consumer consumer, final String textBeforePosition, - final PrefixMatcher prefixMatcher, - CompletionContributor contributor, - CompletionParameters parameters, - @NotNull CompletionSorterImpl sorter, - @NotNull CompletionProgressIndicator process, - @Nullable CompletionResultSetImpl original) { - super(prefixMatcher, consumer, contributor); - myTextBeforePosition = textBeforePosition; - myParameters = parameters; - mySorter = sorter; - myProcess = process; - myOriginal = original; - } - - public void addElement(@NotNull final LookupElement element) { - CompletionResult matched = CompletionResult.wrap(element, getPrefixMatcher(), mySorter); - if (matched != null) { - passResult(matched); - } - } - - @NotNull - public CompletionResultSet withPrefixMatcher(@NotNull final PrefixMatcher matcher) { - if (!myTextBeforePosition.endsWith(matcher.getPrefix())) { - final int len = myTextBeforePosition.length(); - final String fragment = len > 100 ? myTextBeforePosition.substring(len - 100) : myTextBeforePosition; - PsiFile positionFile = myParameters.getPosition().getContainingFile(); - LOG.error("prefix should be some actual file string just before caret: " + matcher.getPrefix() + - "\n text=" + fragment + - "\ninjected=" + (InjectedLanguageUtil.getTopLevelFile(positionFile) != positionFile) + - "\nlang=" + positionFile.getLanguage()); - } - return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, matcher, myContributor, myParameters, mySorter, myProcess, this); - } - - @Override - public void stopHere() { - super.stopHere(); - if (myOriginal != null) { - myOriginal.stopHere(); - } - } - - @NotNull - public CompletionResultSet withPrefixMatcher(@NotNull final String prefix) { - return withPrefixMatcher(new CamelHumpMatcher(prefix)); - } - - @NotNull - @Override - public CompletionResultSet withRelevanceSorter(@NotNull CompletionSorter sorter) { - return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, getPrefixMatcher(), myContributor, myParameters, (CompletionSorterImpl)sorter, myProcess, this); - } - - @NotNull - @Override - public CompletionResultSet caseInsensitive() { - return withPrefixMatcher(new CamelHumpMatcher(getPrefixMatcher().getPrefix(), false)); - } - - @Override - public void restartCompletionOnPrefixChange(ElementPattern prefixCondition) { - final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); - if (indicator != null) { - indicator.addWatchedPrefix(myTextBeforePosition.length() - getPrefixMatcher().getPrefix().length(), prefixCondition); - } - } - - @Override - public void restartCompletionWhenNothingMatches() { - final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); - if (indicator != null) { - indicator.getLookup().setStartCompletionWhenNothingMatches(true); - } - } - } - - public static boolean assertPhase(Class... possibilities) { - if (!isPhase(possibilities)) { - LOG.error(ourPhase + "; set at " + ourPhaseTrace); - return false; - } - return true; - } - - public static boolean isPhase(Class... possibilities) { - CompletionPhase phase = getCompletionPhase(); - for (Class possibility : possibilities) { - if (possibility.isInstance(phase)) { - return true; - } - } - return false; - } - - public static void setCompletionPhase(@NotNull CompletionPhase phase) { - ApplicationManager.getApplication().assertIsDispatchThread(); - CompletionPhase oldPhase = getCompletionPhase(); - CompletionProgressIndicator oldIndicator = oldPhase.indicator; - if (oldIndicator != null && !(phase instanceof CompletionPhase.BgCalculation)) { - LOG.assertTrue(!oldIndicator.isRunning() || oldIndicator.isCanceled(), "don't change phase during running completion: oldPhase=" + oldPhase); - } - - Disposer.dispose(oldPhase); - ourPhase = phase; - ourPhaseTrace = DebugUtil.currentStackTrace(); - } - - public static CompletionPhase getCompletionPhase() { -// ApplicationManager.getApplication().assertIsDispatchThread(); - CompletionPhase phase = getPhaseRaw(); - ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.checkCanceled(); - } - return phase; - } - - public static CompletionPhase getPhaseRaw() { - return ourPhase; - } - - public CompletionSorterImpl defaultSorter(CompletionParameters parameters, final PrefixMatcher matcher) { - final CompletionLocation location = new CompletionLocation(parameters); - - CompletionSorterImpl sorter = emptySorter(); - sorter = sorter.withClassifier(new PreferStartMatching(location)); - - for (final Weigher weigher : WeighingService.getWeighers(CompletionService.RELEVANCE_KEY)) { - final String id = weigher.toString(); - if ("prefix".equals(id)) { - sorter = sorter.withClassifier(new PrefixMatchingClassifier(id, location)); - } - else { - sorter = sorter.weigh(new LookupElementWeigher(id) { - @NotNull - @Override - public Comparable weigh(@NotNull LookupElement element) { - return new NegatingComparable(weigher.weigh(element, location)); - } - }); - } - - } - - if (parameters.getCompletionType() == CompletionType.SMART) { - return sorter; - } - - return sorter.withClassifier("priority", true, new ClassifierFactory("liftShorter") { - @Override - public Classifier createClassifier(final Classifier next) { - return new LiftShorterItemsClassifier(next, new LiftShorterItemsClassifier.LiftingCondition()); - } - }); - } - - public CompletionSorterImpl emptySorter() { - return new CompletionSorterImpl(new ArrayList>()); - } - - private static class PreferStartMatching extends ClassifierFactory { - private final CompletionLocation myLocation; - - public PreferStartMatching(CompletionLocation location) { - super("startMatching"); - myLocation = location; - } - - @Override - public Classifier createClassifier(Classifier next) { - return new ComparingClassifier(next, "startMatching") { - @NotNull - @Override - public Comparable getWeight(LookupElement element) { - PrefixMatcher itemMatcher = myLocation.getCompletionParameters().getLookup().itemMatcher(element); - for (String ls : element.getAllLookupStrings()) { - if (itemMatcher.isStartMatch(ls)) { - return false; - } - } - return true; - } - }; - } - } - - private static class PrefixMatchingClassifier extends ClassifierFactory { - private final String myId; - private final CompletionLocation myLocation; - - public PrefixMatchingClassifier(String id, CompletionLocation location) { - super(id); - myId = id; - myLocation = location; - } - - @Override - public Classifier createClassifier(Classifier next) { - return new ComparingClassifier(next, myId) { - @NotNull - @Override - public Comparable getWeight(LookupElement element) { - return -getPrefixMatchingDegree(element, myLocation); - } - }; - } - } -} +/* + * 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.impl; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.Weigher; +import com.intellij.psi.WeighingService; +import com.intellij.psi.codeStyle.MinusculeMatcher; +import com.intellij.psi.codeStyle.NameUtil; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +/** + * @author peter + */ +public class CompletionServiceImpl extends CompletionService{ + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.impl.CompletionServiceImpl"); + private static volatile CompletionPhase ourPhase = CompletionPhase.NoCompletion; + private static String ourPhaseTrace; + private static CodeInsightSettings ourSettings = CodeInsightSettings.getInstance(); + + public CompletionServiceImpl() { + ProjectManager.getInstance().addProjectManagerListener(new ProjectManagerAdapter() { + @Override + public void projectClosing(Project project) { + CompletionProgressIndicator indicator = getCurrentCompletion(); + if (indicator != null && indicator.getProject() == project) { + LookupManager.getInstance(indicator.getProject()).hideActiveLookup(); + setCompletionPhase(CompletionPhase.NoCompletion); + } + else if (indicator == null) { + setCompletionPhase(CompletionPhase.NoCompletion); + } + } + }); + } + + @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) + public static CompletionServiceImpl getCompletionService() { + return (CompletionServiceImpl)CompletionService.getCompletionService(); + } + + @Override + public String getAdvertisementText() { + final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); + return completion == null ? null : completion.getLookup().getAdvertisementText(); + } + + public void setAdvertisementText(@Nullable final String text) { + final CompletionProgressIndicator completion = getCompletionService().getCurrentCompletion(); + if (completion != null) { + completion.getLookup().setAdvertisementText(text); + } + } + + public CompletionResultSet createResultSet(final CompletionParameters parameters, final Consumer consumer, + @NotNull final CompletionContributor contributor) { + final PsiElement position = parameters.getPosition(); + final String prefix = CompletionData.findPrefixStatic(position, parameters.getOffset()); + final String textBeforePosition = parameters.getPosition().getContainingFile().getText().substring(0, parameters.getOffset()); + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (!(indicator instanceof CompletionProgressIndicator)) { + throw new AssertionError("createResultSet may be invoked only from completion thread: " + indicator + "!=" + getCurrentCompletion() + "; phase set at " + ourPhaseTrace); + } + CompletionProgressIndicator process = (CompletionProgressIndicator)indicator; + CamelHumpMatcher matcher = new CamelHumpMatcher(prefix); + CompletionSorterImpl sorter = defaultSorter(parameters, matcher); + return new CompletionResultSetImpl(consumer, textBeforePosition, matcher, contributor,parameters, sorter, process, null); + } + + @Override + public CompletionProgressIndicator getCurrentCompletion() { + if (isPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.CommittingDocuments.class, + CompletionPhase.Synchronous.class)) { + return ourPhase.indicator; + } + return null; + } + + private static int getPrefixMatchingDegree(LookupElement item, CompletionLocation location) { + final MinusculeMatcher matcher = getMinusculeMatcher(location.getCompletionParameters().getLookup().itemPattern(item)); + + int max = Integer.MIN_VALUE; + for (String lookupString : item.getAllLookupStrings()) { + max = Math.max(max, matcher.matchingDegree(lookupString)); + } + return max; + } + + private static volatile Pair lastMatcher; + + private static MinusculeMatcher getMinusculeMatcher(String prefix) { + final int setting = ourSettings.COMPLETION_CASE_SENSITIVE; + final NameUtil.MatchingCaseSensitivity sensitivity = + setting == CodeInsightSettings.NONE ? NameUtil.MatchingCaseSensitivity.NONE : + setting == CodeInsightSettings.FIRST_LETTER ? NameUtil.MatchingCaseSensitivity.FIRST_LETTER : NameUtil.MatchingCaseSensitivity.ALL; + + Pair pair = lastMatcher; + if (pair != null && pair.first.equals(prefix)) { + return pair.second; + } + + MinusculeMatcher matcher = new MinusculeMatcher(CamelHumpMatcher.applyMiddleMatching(prefix), sensitivity); + lastMatcher = Pair.create(prefix, matcher); + return matcher; + } + + private static class CompletionResultSetImpl extends CompletionResultSet { + private final String myTextBeforePosition; + private final CompletionParameters myParameters; + private final CompletionSorterImpl mySorter; + private final CompletionProgressIndicator myProcess; + @Nullable private final CompletionResultSetImpl myOriginal; + + public CompletionResultSetImpl(final Consumer consumer, final String textBeforePosition, + final PrefixMatcher prefixMatcher, + CompletionContributor contributor, + CompletionParameters parameters, + @NotNull CompletionSorterImpl sorter, + @NotNull CompletionProgressIndicator process, + @Nullable CompletionResultSetImpl original) { + super(prefixMatcher, consumer, contributor); + myTextBeforePosition = textBeforePosition; + myParameters = parameters; + mySorter = sorter; + myProcess = process; + myOriginal = original; + } + + public void addElement(@NotNull final LookupElement element) { + CompletionResult matched = CompletionResult.wrap(element, getPrefixMatcher(), mySorter); + if (matched != null) { + passResult(matched); + } + } + + @NotNull + public CompletionResultSet withPrefixMatcher(@NotNull final PrefixMatcher matcher) { + if (!myTextBeforePosition.endsWith(matcher.getPrefix())) { + final int len = myTextBeforePosition.length(); + final String fragment = len > 100 ? myTextBeforePosition.substring(len - 100) : myTextBeforePosition; + PsiFile positionFile = myParameters.getPosition().getContainingFile(); + LOG.error("prefix should be some actual file string just before caret: " + matcher.getPrefix() + + "\n text=" + fragment + + "\ninjected=" + (InjectedLanguageUtil.getTopLevelFile(positionFile) != positionFile) + + "\nlang=" + positionFile.getLanguage()); + } + return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, matcher, myContributor, myParameters, mySorter, myProcess, this); + } + + @Override + public void stopHere() { + super.stopHere(); + if (myOriginal != null) { + myOriginal.stopHere(); + } + } + + @NotNull + public CompletionResultSet withPrefixMatcher(@NotNull final String prefix) { + return withPrefixMatcher(new CamelHumpMatcher(prefix)); + } + + @NotNull + @Override + public CompletionResultSet withRelevanceSorter(@NotNull CompletionSorter sorter) { + return new CompletionResultSetImpl(getConsumer(), myTextBeforePosition, getPrefixMatcher(), myContributor, myParameters, (CompletionSorterImpl)sorter, myProcess, this); + } + + @NotNull + @Override + public CompletionResultSet caseInsensitive() { + return withPrefixMatcher(new CamelHumpMatcher(getPrefixMatcher().getPrefix(), false)); + } + + @Override + public void restartCompletionOnPrefixChange(ElementPattern prefixCondition) { + final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); + if (indicator != null) { + indicator.addWatchedPrefix(myTextBeforePosition.length() - getPrefixMatcher().getPrefix().length(), prefixCondition); + } + } + + @Override + public void restartCompletionWhenNothingMatches() { + final CompletionProgressIndicator indicator = getCompletionService().getCurrentCompletion(); + if (indicator != null) { + indicator.getLookup().setStartCompletionWhenNothingMatches(true); + } + } + } + + public static boolean assertPhase(Class... possibilities) { + if (!isPhase(possibilities)) { + LOG.error(ourPhase + "; set at " + ourPhaseTrace); + return false; + } + return true; + } + + public static boolean isPhase(Class... possibilities) { + CompletionPhase phase = getCompletionPhase(); + for (Class possibility : possibilities) { + if (possibility.isInstance(phase)) { + return true; + } + } + return false; + } + + public static void setCompletionPhase(@NotNull CompletionPhase phase) { + ApplicationManager.getApplication().assertIsDispatchThread(); + CompletionPhase oldPhase = getCompletionPhase(); + CompletionProgressIndicator oldIndicator = oldPhase.indicator; + if (oldIndicator != null && !(phase instanceof CompletionPhase.BgCalculation)) { + LOG.assertTrue(!oldIndicator.isRunning() || oldIndicator.isCanceled(), "don't change phase during running completion: oldPhase=" + oldPhase); + } + + Disposer.dispose(oldPhase); + ourPhase = phase; + ourPhaseTrace = DebugUtil.currentStackTrace(); + } + + public static CompletionPhase getCompletionPhase() { +// ApplicationManager.getApplication().assertIsDispatchThread(); + CompletionPhase phase = getPhaseRaw(); + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + } + return phase; + } + + public static CompletionPhase getPhaseRaw() { + return ourPhase; + } + + public CompletionSorterImpl defaultSorter(CompletionParameters parameters, final PrefixMatcher matcher) { + final CompletionLocation location = new CompletionLocation(parameters); + + CompletionSorterImpl sorter = emptySorter(); + sorter = sorter.withClassifier(new PreferStartMatching(location)); + + for (final Weigher weigher : WeighingService.getWeighers(CompletionService.RELEVANCE_KEY)) { + final String id = weigher.toString(); + if ("prefix".equals(id)) { + sorter = sorter.withClassifier(new PrefixMatchingClassifier(id, location)); + } + else { + sorter = sorter.weigh(new LookupElementWeigher(id, true) { + @Override + public Comparable weigh(@NotNull LookupElement element) { + return weigher.weigh(element, location); + } + }); + } + + } + + if (parameters.getCompletionType() == CompletionType.SMART) { + return sorter; + } + + return sorter.withClassifier("priority", true, new ClassifierFactory("liftShorter") { + @Override + public Classifier createClassifier(final Classifier next) { + return new LiftShorterItemsClassifier(next, new LiftShorterItemsClassifier.LiftingCondition()); + } + }); + } + + public CompletionSorterImpl emptySorter() { + return new CompletionSorterImpl(new ArrayList>()); + } + + private static class PreferStartMatching extends ClassifierFactory { + private final CompletionLocation myLocation; + + public PreferStartMatching(CompletionLocation location) { + super("startMatching"); + myLocation = location; + } + + @Override + public Classifier createClassifier(Classifier next) { + return new ComparingClassifier(next, "startMatching") { + @NotNull + @Override + public Comparable getWeight(LookupElement element) { + PrefixMatcher itemMatcher = myLocation.getCompletionParameters().getLookup().itemMatcher(element); + for (String ls : element.getAllLookupStrings()) { + if (itemMatcher.isStartMatch(ls)) { + return false; + } + } + return true; + } + }; + } + } + + private static class PrefixMatchingClassifier extends ClassifierFactory { + private final String myId; + private final CompletionLocation myLocation; + + public PrefixMatchingClassifier(String id, CompletionLocation location) { + super(id); + myId = id; + myLocation = location; + } + + @Override + public Classifier createClassifier(Classifier next) { + return new ComparingClassifier(next, myId) { + @NotNull + @Override + public Comparable getWeight(LookupElement element) { + return -getPrefixMatchingDegree(element, myLocation); + } + }; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/CachingComparingClassifier.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/CachingComparingClassifier.java index 9876f8c506ee..683009d5da22 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/CachingComparingClassifier.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/CachingComparingClassifier.java @@ -15,9 +15,10 @@ */ package com.intellij.codeInsight.lookup; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Ref; import com.intellij.psi.ForceableComparable; import com.intellij.util.ProcessingContext; -import org.jetbrains.annotations.NotNull; import java.util.IdentityHashMap; import java.util.Map; @@ -28,22 +29,17 @@ import java.util.Map; public class CachingComparingClassifier extends ComparingClassifier { private final Map myWeights = new IdentityHashMap(); private final LookupElementWeigher myWeigher; - private Comparable myFirstWeight; + private Ref myFirstWeight; private boolean myPrimitive = true; public CachingComparingClassifier(Classifier next, LookupElementWeigher weigher) { - super(next, weigher.toString()); + super(next, weigher.toString(), weigher.isNegated()); myWeigher = weigher; } - @NotNull @Override public final Comparable getWeight(LookupElement t) { - final Comparable weight = myWeights.get(t); - if (weight == null) { - throw new AssertionError(myName + "; " + myWeights.containsKey(t) + "; element=" + t); - } - return weight; + return myWeights.get(t); } @Override @@ -63,8 +59,8 @@ public class CachingComparingClassifier extends ComparingClassifier extends Classifier { protected final Classifier myNext; protected final String myName; + private final boolean myNegated; public ComparingClassifier(Classifier next, String name) { - myNext = next; - myName = name; + this(next, name, false); } - @NotNull + protected ComparingClassifier(Classifier next, String name, boolean negated) { + myNext = next; + myName = name; + myNegated = negated; + } + + @Nullable public abstract Comparable getWeight(T t); public void addElement(T t) { myNext.addElement(t); } - private TreeMap> groupByWeights(Iterable source) { - TreeMap> map = new TreeMap>(); - for (T t : source) { - final Comparable weight = getWeight(t); - List list = map.get(weight); - if (list == null) { - map.put(weight, list = new SmartList()); - } - list.add(t); - } - return map; - } - @Override public Iterable classify(Iterable source, ProcessingContext context) { - return ContainerUtil.flatten(groupByWeights(myNext.classify(source, context)).values()); + List nulls = null; + TreeMap> map = new TreeMap>(); + int count = 0; + for (T t : myNext.classify(source, context)) { + count++; + final Comparable weight = getWeight(t); + if (weight == null) { + if (nulls == null) nulls = new SmartList(); + nulls.add(t); + } else { + List list = map.get(weight); + if (list == null) { + map.put(weight, list = new SmartList()); + } + list.add(t); + } + } + + ArrayList result = new ArrayList(count); + Collection> values = myNegated ? map.descendingMap().values() : map.values(); + for (List value : values) { + result.addAll(value); + } + if (nulls != null) { + result.addAll(nulls); + } + return result; } @Override public void describeItems(LinkedHashMap map, ProcessingContext context) { - final Map> treeMap = groupByWeights(new ArrayList(map.keySet())); - if (treeMap.size() > 1 || ApplicationManager.getApplication().isUnitTestMode()) { - for (Map.Entry> entry: treeMap.entrySet()){ - for (T t : entry.getValue()) { - final StringBuilder builder = map.get(t); - if (builder.length() > 0) { - builder.append(", "); - } - - builder.append(myName).append("=").append(entry.getKey()); + Map weights = new IdentityHashMap(); + for (T t : map.keySet()) { + weights.put(t, String.valueOf(getWeight(t))); + } + if (new HashSet(weights.values()).size() > 1 || ApplicationManager.getApplication().isUnitTestMode() || true) { + for (T t : map.keySet()) { + final StringBuilder builder = map.get(t); + if (builder.length() > 0) { + builder.append(", "); } + builder.append(myName).append("=").append(weights.get(t)); } } myNext.describeItems(map, context);