keep lookup element matchers in LookupArranger to distinguish different prefixes for the same items in subsequent sessions (as Rider produces)

This commit is contained in:
peter
2016-05-02 20:54:04 +02:00
parent 4e4f13ae82
commit be3e871a3b
3 changed files with 95 additions and 85 deletions
@@ -156,7 +156,7 @@ public class CompletionLookupArranger extends LookupArranger {
}
@Override
public void addElement(Lookup lookup, LookupElement element, LookupElementPresentation presentation) {
public void addElement(LookupElement element, LookupElementPresentation presentation) {
StatisticsWeigher.clearBaseStatisticsInfo(element);
final String invariant = presentation.getItemText() + "\0###" + getTailTextOrSpace(presentation) + "###" + presentation.getTypeText();
@@ -166,14 +166,14 @@ public class CompletionLookupArranger extends LookupArranger {
CompletionSorterImpl sorter = obtainSorter(element);
Classifier<LookupElement> classifier = myClassifiers.get(sorter);
if (classifier == null) {
myClassifiers.put(sorter, classifier = sorter.buildClassifier(new AlphaClassifier((LookupImpl)lookup)));
myClassifiers.put(sorter, classifier = sorter.buildClassifier(new AlphaClassifier()));
}
ProcessingContext context = createContext(true);
classifier.addElement(element, context);
super.addElement(lookup, element, presentation);
super.addElement(element, presentation);
trimToLimit(lookup, context);
trimToLimit(context);
}
@Override
@@ -181,7 +181,7 @@ public class CompletionLookupArranger extends LookupArranger {
myProcess.itemSelected(lookupItem, completionChar);
}
private void trimToLimit(Lookup lookup, ProcessingContext context) {
private void trimToLimit(ProcessingContext context) {
if (myItems.size() < myLimit) return;
List<LookupElement> items = getMatchingItems();
@@ -197,7 +197,7 @@ public class CompletionLookupArranger extends LookupArranger {
if (!iterator.hasNext()) return;
List<LookupElement> removed = retainItems(retainedSet, lookup);
List<LookupElement> removed = retainItems(retainedSet);
for (LookupElement element : removed) {
removeItem(element, context);
}
@@ -223,11 +223,11 @@ public class CompletionLookupArranger extends LookupArranger {
return tailText == null || tailText.isEmpty() ? " " : tailText;
}
private List<LookupElement> sortByPresentation(Iterable<LookupElement> source, LookupImpl lookup) {
private List<LookupElement> sortByPresentation(Iterable<LookupElement> source) {
ArrayList<LookupElement> startMatches = ContainerUtil.newArrayList();
ArrayList<LookupElement> middleMatches = ContainerUtil.newArrayList();
for (LookupElement element : source) {
(CompletionServiceImpl.isStartMatch(element, lookup) ? startMatches : middleMatches).add(element);
(itemMatcher(element).isStartMatch(element) ? startMatches : middleMatches).add(element);
}
ContainerUtil.sort(startMatches, BY_PRESENTATION_COMPARATOR);
ContainerUtil.sort(middleMatches, BY_PRESENTATION_COMPARATOR);
@@ -247,7 +247,7 @@ public class CompletionLookupArranger extends LookupArranger {
LookupElement relevantSelection = findMostRelevantItem(itemsBySorter);
LookupImpl lookupImpl = (LookupImpl)lookup;
List<LookupElement> listModel = isAlphaSorted() ?
sortByPresentation(items, lookupImpl) :
sortByPresentation(items) :
fillModelByRelevance(lookupImpl, ContainerUtil.newIdentityTroveSet(items), itemsBySorter, relevantSelection);
int toSelect = getItemToSelect(lookupImpl, listModel, onExplicitAction, relevantSelection);
@@ -371,7 +371,7 @@ public class CompletionLookupArranger extends LookupArranger {
private ProcessingContext createContext(boolean pureRelevance) {
ProcessingContext context = new ProcessingContext();
context.put(PREFIX_CHANGES, myPrefixChanges);
context.put(WEIGHING_CONTEXT, myProcess.getLookup());
context.put(WEIGHING_CONTEXT, this);
if (pureRelevance) {
context.put(PURE_RELEVANCE, Boolean.TRUE);
}
@@ -417,7 +417,7 @@ public class CompletionLookupArranger extends LookupArranger {
for (int i = 0; i < items.size(); i++) {
LookupElement item = items.get(i);
boolean isSuddenLiveTemplate = isSuddenLiveTemplate(item);
if (isPrefixItem(lookup, item, true) && !isSuddenLiveTemplate || item.getLookupString().equals(selectedText)) {
if (isPrefixItem(item, true) && !isSuddenLiveTemplate || item.getLookupString().equals(selectedText)) {
if (item instanceof LiveTemplateLookupElement) {
// prefer most recent live template lookup item
exactMatchIndex = i;
@@ -428,7 +428,7 @@ public class CompletionLookupArranger extends LookupArranger {
exactMatchIndex = i;
}
}
else if (i == 0 && isSuddenLiveTemplate && items.size() > 1 && !CompletionServiceImpl.isStartMatch(items.get(1), lookup)) {
else if (i == 0 && isSuddenLiveTemplate && items.size() > 1 && !CompletionServiceImpl.isStartMatch(items.get(1), this)) {
return 0;
}
}
@@ -593,11 +593,9 @@ public class CompletionLookupArranger extends LookupArranger {
}
private class AlphaClassifier extends Classifier<LookupElement> {
private final LookupImpl myLookup;
private AlphaClassifier(LookupImpl lookup) {
private AlphaClassifier() {
super(null, "alpha");
myLookup = lookup;
}
@NotNull
@@ -609,7 +607,7 @@ public class CompletionLookupArranger extends LookupArranger {
@NotNull
@Override
public Iterable<LookupElement> classify(@NotNull Iterable<LookupElement> source, @NotNull ProcessingContext context) {
return sortByPresentation(source, myLookup);
return sortByPresentation(source);
}
}
@@ -22,6 +22,8 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.EqualityPolicy;
import com.intellij.util.containers.hash.LinkedHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,37 +32,54 @@ import java.util.*;
/**
* @author peter
*/
public abstract class LookupArranger {
protected final List<LookupElement> myItems = new ArrayList<LookupElement>();
private final List<LookupElement> myMatchingItems = new ArrayList<LookupElement>();
private final List<LookupElement> myExactPrefixItems = new ArrayList<LookupElement>();
private final List<LookupElement> myInexactPrefixItems = new ArrayList<LookupElement>();
public abstract class LookupArranger implements WeighingContext {
protected final List<LookupElement> myItems = new ArrayList<>();
private final List<LookupElement> myMatchingItems = new ArrayList<>();
private final List<LookupElement> myExactPrefixItems = new ArrayList<>();
private final List<LookupElement> myInexactPrefixItems = new ArrayList<>();
private final Map<LookupElement, PrefixMatcher> myMatchers = ContainerUtil.createConcurrentWeakMap(ContainerUtil.identityStrategy());
private String myAdditionalPrefix = "";
public void addElement(Lookup lookup, LookupElement item, LookupElementPresentation presentation) {
public void addElement(LookupElement item, LookupElementPresentation presentation) {
myItems.add(item);
updateCache(lookup, item);
updateCache(item);
}
private void updateCache(Lookup lookup, LookupElement item) {
if (!prefixMatches((LookupImpl)lookup, item)) {
private void updateCache(LookupElement item) {
if (!prefixMatches(item)) {
return;
}
myMatchingItems.add(item);
if (isPrefixItem(lookup, item, true)) {
if (isPrefixItem(item, true)) {
myExactPrefixItems.add(item);
} else if (isPrefixItem(lookup, item, false)) {
} else if (isPrefixItem(item, false)) {
myInexactPrefixItems.add(item);
}
}
private boolean prefixMatches(LookupImpl lookup, LookupElement item) {
PrefixMatcher matcher = lookup.itemMatcherNullable(item);
if (matcher == null) {
return false;
}
public void registerMatcher(@NotNull LookupElement item, @NotNull PrefixMatcher matcher) {
myMatchers.put(item, matcher);
}
@NotNull
public String itemPattern(@NotNull LookupElement element) {
String prefix = itemMatcher(element).getPrefix();
String additionalPrefix = myAdditionalPrefix;
return additionalPrefix.isEmpty() ? prefix : prefix + additionalPrefix;
}
@NotNull
public PrefixMatcher itemMatcher(@NotNull LookupElement item) {
PrefixMatcher matcher = myMatchers.get(item);
if (matcher == null) {
throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + myItems);
}
return matcher;
}
private boolean prefixMatches(LookupElement item) {
PrefixMatcher matcher = itemMatcher(item);
if (!myAdditionalPrefix.isEmpty()) {
matcher = matcher.cloneWithPrefix(matcher.getPrefix() + myAdditionalPrefix);
}
@@ -70,22 +89,41 @@ public abstract class LookupArranger {
public void itemSelected(@Nullable LookupElement lookupItem, char completionChar) {
}
public void prefixChanged(Lookup lookup) {
myAdditionalPrefix = ((LookupImpl)lookup).getAdditionalPrefix();
rebuildItemCache(lookup);
public final void prefixReplaced(Lookup lookup, String newPrefix) {
//noinspection unchecked
Map<LookupElement, PrefixMatcher> newMatchers = new LinkedHashMap(EqualityPolicy.IDENTITY);
for (LookupElement item : myItems) {
if (item.isValid()) {
PrefixMatcher matcher = itemMatcher(item).cloneWithPrefix(newPrefix);
if (matcher.prefixMatches(item)) {
newMatchers.put(item, matcher);
}
}
}
myMatchers.clear();
myMatchers.putAll(newMatchers);
myItems.clear();
myItems.addAll(newMatchers.keySet());
prefixChanged(lookup);
}
private void rebuildItemCache(Lookup lookup) {
public void prefixChanged(Lookup lookup) {
myAdditionalPrefix = ((LookupImpl)lookup).getAdditionalPrefix();
rebuildItemCache();
}
private void rebuildItemCache() {
myMatchingItems.clear();
myExactPrefixItems.clear();
myInexactPrefixItems.clear();
for (LookupElement item : myItems) {
updateCache(lookup, item);
updateCache(item);
}
}
protected List<LookupElement> retainItems(final Set<LookupElement> retained, Lookup lookup) {
protected List<LookupElement> retainItems(final Set<LookupElement> retained) {
List<LookupElement> filtered = ContainerUtil.newArrayList();
List<LookupElement> removed = ContainerUtil.newArrayList();
for (LookupElement item : myItems) {
@@ -94,7 +132,7 @@ public abstract class LookupArranger {
myItems.clear();
myItems.addAll(filtered);
rebuildItemCache(lookup);
rebuildItemCache();
return removed;
}
@@ -106,8 +144,8 @@ public abstract class LookupArranger {
return Collections.unmodifiableList(exactly ? myExactPrefixItems : myInexactPrefixItems);
}
protected static boolean isPrefixItem(Lookup lookup, LookupElement item, final boolean exactly) {
final String pattern = lookup.itemPattern(item);
protected boolean isPrefixItem(LookupElement item, final boolean exactly) {
final String pattern = itemPattern(item);
if (Comparing.strEqual(pattern, item.getLookupString(), item.isCaseSensitive())) {
return true;
}
@@ -147,7 +185,7 @@ public abstract class LookupArranger {
List<LookupElement> items = getMatchingItems();
for (LookupElement item : items) {
if (CompletionServiceImpl.isStartMatch(item, (LookupImpl)lookup)) {
if (CompletionServiceImpl.isStartMatch(item, this)) {
result.add(item);
}
}
@@ -70,11 +70,10 @@ import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LookupImpl extends LightweightHint implements LookupEx, Disposable, WeighingContext {
public class LookupImpl extends LightweightHint implements LookupEx, Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");
private final LookupOffsets myOffsets;
@@ -121,8 +120,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
private boolean myChangeGuard;
private volatile LookupArranger myArranger;
private LookupArranger myPresentableArranger;
private final Map<LookupElement, PrefixMatcher> myMatchers =
ContainerUtil.createConcurrentWeakMap(ContainerUtil.identityStrategy());
private final Map<LookupElement, Font> myCustomFonts = ContainerUtil.createConcurrentWeakMap(10, 0.75f, Runtime.getRuntime().availableProcessors(),
ContainerUtil.identityStrategy());
private boolean myStartCompletionWhenNothingMatches;
@@ -238,10 +235,10 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
return false;
}
myMatchers.put(item, matcher);
updateLookupWidth(item, presentation);
withLock(() -> {
myArranger.addElement(this, item, presentation);
myArranger.registerMatcher(item, matcher);
myArranger.addElement(item, presentation);
return null;
});
return true;
@@ -423,7 +420,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
private void addEmptyItem(CollectionListModel<LookupElement> model) {
LookupElement item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions"), false);
myMatchers.put(item, new CamelHumpMatcher(""));
model.add(item);
updateLookupWidth(item);
@@ -439,23 +435,17 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
@NotNull
@Override
public String itemPattern(@NotNull LookupElement element) {
String prefix = itemMatcher(element).getPrefix();
String additionalPrefix = getAdditionalPrefix();
return additionalPrefix.isEmpty() ? prefix : prefix + additionalPrefix;
if (element instanceof EmptyLookupItem) return "";
return myPresentableArranger.itemPattern(element);
}
@Override
@NotNull
public PrefixMatcher itemMatcher(@NotNull LookupElement item) {
PrefixMatcher matcher = itemMatcherNullable(item);
if (matcher == null) {
throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + getItems());
if (item instanceof EmptyLookupItem) {
return new CamelHumpMatcher("");
}
return matcher;
}
public PrefixMatcher itemMatcherNullable(LookupElement item) {
return myMatchers.get(item);
return myPresentableArranger.itemMatcher(item);
}
public void finishLookup(final char completionChar) {
@@ -912,35 +902,18 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
}
public void replacePrefix(final String presentPrefix, final String newPrefix) {
if (!performGuardedChange(new Runnable() {
@Override
public void run() {
EditorModificationUtil.deleteSelectedText(myEditor);
int offset = myEditor.getCaretModel().getOffset();
final int start = offset - presentPrefix.length();
myEditor.getDocument().replaceString(start, offset, newPrefix);
Map<LookupElement, PrefixMatcher> newMatchers = new HashMap<LookupElement, PrefixMatcher>();
for (LookupElement item : getItems()) {
if (item.isValid()) {
PrefixMatcher matcher = itemMatcher(item).cloneWithPrefix(newPrefix);
if (matcher.prefixMatches(item)) {
newMatchers.put(item, matcher);
}
}
}
myMatchers.clear();
myMatchers.putAll(newMatchers);
myOffsets.clearAdditionalPrefix();
myEditor.getCaretModel().moveToOffset(start + newPrefix.length());
}
if (!performGuardedChange(() -> {
EditorModificationUtil.deleteSelectedText(myEditor);
int offset = myEditor.getCaretModel().getOffset();
final int start = offset - presentPrefix.length();
myEditor.getDocument().replaceString(start, offset, newPrefix);
myOffsets.clearAdditionalPrefix();
myEditor.getCaretModel().moveToOffset(start + newPrefix.length());
})) {
return;
}
withLock(() -> {
myPresentableArranger.prefixChanged(this);
myPresentableArranger.prefixReplaced(this, newPrefix);
return null;
});
refreshUi(true, true);
@@ -1162,6 +1135,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
}
}
@SuppressWarnings("unused")
public void setPrefixChangeListener(PrefixChangeListener listener) {
myPrefixChangeListener = listener;
}