get rid of NegatingComparable indirection

This commit is contained in:
peter
2012-07-11 11:54:35 +02:00
parent c621d02691
commit c2ecf09643
4 changed files with 422 additions and 398 deletions
@@ -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);
}
@@ -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<CompletionResult> 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<String, MinusculeMatcher> 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<String, MinusculeMatcher> 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<CompletionResult> 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<String> 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<? extends CompletionPhase>... possibilities) {
if (!isPhase(possibilities)) {
LOG.error(ourPhase + "; set at " + ourPhaseTrace);
return false;
}
return true;
}
public static boolean isPhase(Class<? extends CompletionPhase>... possibilities) {
CompletionPhase phase = getCompletionPhase();
for (Class<? extends CompletionPhase> 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<LookupElement>("liftShorter") {
@Override
public Classifier<LookupElement> createClassifier(final Classifier<LookupElement> next) {
return new LiftShorterItemsClassifier(next, new LiftShorterItemsClassifier.LiftingCondition());
}
});
}
public CompletionSorterImpl emptySorter() {
return new CompletionSorterImpl(new ArrayList<ClassifierFactory<LookupElement>>());
}
private static class PreferStartMatching extends ClassifierFactory<LookupElement> {
private final CompletionLocation myLocation;
public PreferStartMatching(CompletionLocation location) {
super("startMatching");
myLocation = location;
}
@Override
public Classifier<LookupElement> createClassifier(Classifier<LookupElement> next) {
return new ComparingClassifier<LookupElement>(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<LookupElement> {
private final String myId;
private final CompletionLocation myLocation;
public PrefixMatchingClassifier(String id, CompletionLocation location) {
super(id);
myId = id;
myLocation = location;
}
@Override
public Classifier<LookupElement> createClassifier(Classifier<LookupElement> next) {
return new ComparingClassifier<LookupElement>(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<CompletionResult> 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<String, MinusculeMatcher> 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<String, MinusculeMatcher> 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<CompletionResult> 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<String> 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<? extends CompletionPhase>... possibilities) {
if (!isPhase(possibilities)) {
LOG.error(ourPhase + "; set at " + ourPhaseTrace);
return false;
}
return true;
}
public static boolean isPhase(Class<? extends CompletionPhase>... possibilities) {
CompletionPhase phase = getCompletionPhase();
for (Class<? extends CompletionPhase> 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<LookupElement>("liftShorter") {
@Override
public Classifier<LookupElement> createClassifier(final Classifier<LookupElement> next) {
return new LiftShorterItemsClassifier(next, new LiftShorterItemsClassifier.LiftingCondition());
}
});
}
public CompletionSorterImpl emptySorter() {
return new CompletionSorterImpl(new ArrayList<ClassifierFactory<LookupElement>>());
}
private static class PreferStartMatching extends ClassifierFactory<LookupElement> {
private final CompletionLocation myLocation;
public PreferStartMatching(CompletionLocation location) {
super("startMatching");
myLocation = location;
}
@Override
public Classifier<LookupElement> createClassifier(Classifier<LookupElement> next) {
return new ComparingClassifier<LookupElement>(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<LookupElement> {
private final String myId;
private final CompletionLocation myLocation;
public PrefixMatchingClassifier(String id, CompletionLocation location) {
super(id);
myId = id;
myLocation = location;
}
@Override
public Classifier<LookupElement> createClassifier(Classifier<LookupElement> next) {
return new ComparingClassifier<LookupElement>(next, myId) {
@NotNull
@Override
public Comparable getWeight(LookupElement element) {
return -getPrefixMatchingDegree(element, myLocation);
}
};
}
}
}
@@ -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<LookupElement> {
private final Map<LookupElement, Comparable> myWeights = new IdentityHashMap<LookupElement, Comparable>();
private final LookupElementWeigher myWeigher;
private Comparable myFirstWeight;
private Ref<Comparable> myFirstWeight;
private boolean myPrimitive = true;
public CachingComparingClassifier(Classifier<LookupElement> 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<LookupElemen
}
if (myPrimitive) {
if (myFirstWeight == null) {
myFirstWeight = weight;
} else if (!myFirstWeight.equals(weight)) {
myFirstWeight = Ref.create(weight);
} else if (!Comparing.equal(myFirstWeight.get(), weight)) {
myPrimitive = false;
}
}
@@ -18,8 +18,7 @@ package com.intellij.codeInsight.lookup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -29,50 +28,69 @@ import java.util.*;
public abstract class ComparingClassifier<T> extends Classifier<T> {
protected final Classifier<T> myNext;
protected final String myName;
private final boolean myNegated;
public ComparingClassifier(Classifier<T> next, String name) {
myNext = next;
myName = name;
this(next, name, false);
}
@NotNull
protected ComparingClassifier(Classifier<T> 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<Comparable, List<T>> groupByWeights(Iterable<T> source) {
TreeMap<Comparable, List<T>> map = new TreeMap<Comparable, List<T>>();
for (T t : source) {
final Comparable weight = getWeight(t);
List<T> list = map.get(weight);
if (list == null) {
map.put(weight, list = new SmartList<T>());
}
list.add(t);
}
return map;
}
@Override
public Iterable<T> classify(Iterable<T> source, ProcessingContext context) {
return ContainerUtil.flatten(groupByWeights(myNext.classify(source, context)).values());
List<T> nulls = null;
TreeMap<Comparable, List<T>> map = new TreeMap<Comparable, List<T>>();
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<T>();
nulls.add(t);
} else {
List<T> list = map.get(weight);
if (list == null) {
map.put(weight, list = new SmartList<T>());
}
list.add(t);
}
}
ArrayList<T> result = new ArrayList<T>(count);
Collection<List<T>> values = myNegated ? map.descendingMap().values() : map.values();
for (List<T> value : values) {
result.addAll(value);
}
if (nulls != null) {
result.addAll(nulls);
}
return result;
}
@Override
public void describeItems(LinkedHashMap<T, StringBuilder> map, ProcessingContext context) {
final Map<Comparable, List<T>> treeMap = groupByWeights(new ArrayList<T>(map.keySet()));
if (treeMap.size() > 1 || ApplicationManager.getApplication().isUnitTestMode()) {
for (Map.Entry<Comparable, List<T>> 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<T, String> weights = new IdentityHashMap<T, String>();
for (T t : map.keySet()) {
weights.put(t, String.valueOf(getWeight(t)));
}
if (new HashSet<String>(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);