From be3e871a3b99285c3e145da1ecea762be1b3e8ba Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 2 May 2016 20:52:36 +0200 Subject: [PATCH] keep lookup element matchers in LookupArranger to distinguish different prefixes for the same items in subsequent sessions (as Rider produces) --- .../completion/CompletionLookupArranger.java | 30 +++---- .../codeInsight/lookup/LookupArranger.java | 90 +++++++++++++------ .../codeInsight/lookup/impl/LookupImpl.java | 60 ++++--------- 3 files changed, 95 insertions(+), 85 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java index bd827d896e67..90ac7feff7ec 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionLookupArranger.java @@ -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 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 items = getMatchingItems(); @@ -197,7 +197,7 @@ public class CompletionLookupArranger extends LookupArranger { if (!iterator.hasNext()) return; - List removed = retainItems(retainedSet, lookup); + List 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 sortByPresentation(Iterable source, LookupImpl lookup) { + private List sortByPresentation(Iterable source) { ArrayList startMatches = ContainerUtil.newArrayList(); ArrayList 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 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 { - 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 classify(@NotNull Iterable source, @NotNull ProcessingContext context) { - return sortByPresentation(source, myLookup); + return sortByPresentation(source); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java index 26099019e9a2..a07a63e306a7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java @@ -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 myItems = new ArrayList(); - private final List myMatchingItems = new ArrayList(); - private final List myExactPrefixItems = new ArrayList(); - private final List myInexactPrefixItems = new ArrayList(); +public abstract class LookupArranger implements WeighingContext { + protected final List myItems = new ArrayList<>(); + private final List myMatchingItems = new ArrayList<>(); + private final List myExactPrefixItems = new ArrayList<>(); + private final List myInexactPrefixItems = new ArrayList<>(); + private final Map 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 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 retainItems(final Set retained, Lookup lookup) { + protected List retainItems(final Set retained) { List filtered = ContainerUtil.newArrayList(); List 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 items = getMatchingItems(); for (LookupElement item : items) { - if (CompletionServiceImpl.isStartMatch(item, (LookupImpl)lookup)) { + if (CompletionServiceImpl.isStartMatch(item, this)) { result.add(item); } } 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 10f38992c897..f312eae9003c 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 @@ -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 myMatchers = - ContainerUtil.createConcurrentWeakMap(ContainerUtil.identityStrategy()); private final Map 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 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 newMatchers = new HashMap(); - 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; }