no more prefixMatcher in lookupElement

This commit is contained in:
peter
2011-04-18 14:52:42 +02:00
parent 7905c50c67
commit 5e94091dc7
12 changed files with 115 additions and 174 deletions
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
@@ -61,7 +62,9 @@ public interface Lookup {
boolean isFocused();
@NotNull
String itemPattern(LookupElement element);
@NotNull
PrefixMatcher itemMatcher(LookupElement item);
}
@@ -16,7 +16,6 @@
package com.intellij.codeInsight.lookup;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.openapi.util.ClassConditionKey;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.psi.PsiElement;
@@ -35,7 +34,6 @@ import java.util.Set;
*/
public abstract class LookupElement extends UserDataHolderBase {
public static final LookupElement[] EMPTY_ARRAY = new LookupElement[0];
private PrefixMatcher myPrefixMatcher = PrefixMatcher.FALSE_MATCHER;
@NotNull
public abstract String getLookupString();
@@ -44,16 +42,6 @@ public abstract class LookupElement extends UserDataHolderBase {
return Collections.singleton(getLookupString());
}
public boolean setPrefixMatcher(@NotNull final PrefixMatcher matcher) {
myPrefixMatcher = matcher;
return myPrefixMatcher.prefixMatches(this);
}
@NotNull
public final PrefixMatcher getPrefixMatcher() {
return myPrefixMatcher;
}
@NotNull
public Object getObject() {
return this;
@@ -17,7 +17,6 @@ package com.intellij.codeInsight.lookup;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.openapi.util.ClassConditionKey;
import org.jetbrains.annotations.NotNull;
@@ -32,23 +31,12 @@ public abstract class LookupElementDecorator<T extends LookupElement> extends Lo
protected LookupElementDecorator(T delegate) {
myDelegate = delegate;
myDelegate.copyUserDataTo(this);
final PrefixMatcher matcher = delegate.getPrefixMatcher();
if (matcher != PrefixMatcher.FALSE_MATCHER) {
assert matcher.prefixMatches(this);
setPrefixMatcher(matcher);
}
}
public T getDelegate() {
return myDelegate;
}
@Override
public boolean setPrefixMatcher(@NotNull PrefixMatcher matcher) {
myDelegate.setPrefixMatcher(matcher);
return super.setPrefixMatcher(matcher);
}
@NotNull
public String getLookupString() {
return myDelegate.getLookupString();
@@ -29,11 +29,9 @@ import com.intellij.injected.editor.EditorWindow;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
@@ -49,7 +47,6 @@ import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
@@ -67,7 +64,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -291,12 +287,16 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler {
indicator.duringCompletion(initContext);
ProgressManager.checkCanceled();
final List<LookupElement> items = new ArrayList<LookupElement>();
Consumer<LookupElement> consumer = new Consumer<LookupElement>() {
public void consume(final LookupElement lookupElement) {
indicator.addItem((MatchedLookupElement)lookupElement);
MatchedLookupElement matched = (MatchedLookupElement)lookupElement;
indicator.addItem(matched);
items.add(matched.getDelegate());
}
};
data.set(CompletionService.getCompletionService().performCompletion(parameters, consumer));
CompletionService.getCompletionService().performCompletion(parameters, consumer);
data.set(items.toArray(new LookupElement[items.size()]));
}
});
}
@@ -423,12 +423,10 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler {
}
}
else if (decision instanceof AutoCompletionDecision.InsertItem) {
final LookupElement item = ((AutoCompletionDecision.InsertItem)decision).getElement();
indicator.closeAndFinish(true);
final Runnable restorePrefix = rememberDocumentState(indicator.getEditor());
indicator.getOffsetMap()
.addOffset(CompletionInitializationContext.START_OFFSET, (offset1 - indicator.getLookup().itemPattern(item).length()));
handleSingleItem(offset2, indicator, items, item.getLookupString(), item);
final LookupElement item = ((AutoCompletionDecision.InsertItem)decision).getElement();
indicator.getLookup().finishLookup(Lookup.AUTO_INSERT_SELECT_CHAR, item);
// the insert handler may have started a live template with completion
if (CompletionService.getCompletionService().getCurrentCompletion() == null &&
@@ -438,37 +436,6 @@ public class CodeCompletionHandlerBase implements CodeInsightActionHandler {
}
}
protected static void handleSingleItem(final int offset2, final CompletionProgressIndicator context, final LookupElement[] items, final String _uniqueText, final LookupElement item) {
new WriteCommandAction(context.getProject()) {
protected void run(Result result) throws Throwable {
String uniqueText = _uniqueText;
if (item.getObject() instanceof DeferredUserLookupValue && item.as(LookupItem.CLASS_CONDITION_KEY) != null) {
if (!((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.CLASS_CONDITION_KEY), context.getProject())) {
return;
}
uniqueText = item.getLookupString(); // text may be not ready yet
}
if (!StringUtil.startsWithIgnoreCase(uniqueText, context.getLookup().itemPattern(item))) {
FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS);
}
insertLookupString(offset2, uniqueText, context.getEditor(), context.getOffsetMap());
context.getEditor().getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
lookupItemSelected(context, item, Lookup.AUTO_INSERT_SELECT_CHAR, Arrays.asList(items));
}
}.execute();
}
private static void insertLookupString(final int currentOffset, final String newText, final Editor editor, final OffsetMap offsetMap) {
editor.getDocument().replaceString(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET), currentOffset, newText);
editor.getCaretModel().moveToOffset(offsetMap.getOffset(CompletionInitializationContext.START_OFFSET) + newText.length());
editor.getSelectionModel().removeSelection();
}
protected static void selectLookupItem(final LookupElement item, final char completionChar, final CompletionProgressIndicator context, final List<LookupElement> items) {
final int caretOffset = context.getEditor().getCaretModel().getOffset();
@@ -370,9 +370,8 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
LOG.assertTrue(myParameters.getPosition().isValid());
LookupElement delegate = item.getDelegate();
myItemSorters.put(delegate, item.getSorter());
myLookup.addItem(delegate);
myItemSorters.put(item.getDelegate(), item.getSorter());
myLookup.addItem(item.getDelegate(), item.getPrefixMatcher());
myCount++;
if (unitTestMode) return;
@@ -46,7 +46,7 @@ public class RelaxedMatchingContributor extends CompletionContributor {
if (!elements.isEmpty() && parameters.getInvocationCount() == 0) {
Set<String> prefixes = new HashSet<String>();
for (MatchedLookupElement element : elements) {
prefixes.add(element.getMatcher().getPrefix());
prefixes.add(element.getPrefixMatcher().getPrefix());
}
for (String prefix : prefixes) {
result.withPrefixMatcher(prefix)
@@ -54,7 +54,7 @@ public class RelaxedMatchingContributor extends CompletionContributor {
@Override
public boolean accepts(@NotNull String s, ProcessingContext context) {
for (MatchedLookupElement element : elements) {
if (element.getMatcher().cloneWithPrefix(s).prefixMatches(element)) {
if (element.getPrefixMatcher().cloneWithPrefix(s).prefixMatches(element)) {
return false;
}
}
@@ -142,13 +142,12 @@ public class CompletionServiceImpl extends CompletionService{
MatchedLookupElement matched = element.as(MatchedLookupElement.CLASS_CONDITION_KEY);
if (matched != null) {
getConsumer().consume(new MatchedLookupElement(element, matched.getMatcher(), matched.getSorter()));
getConsumer().consume(new MatchedLookupElement(element, matched.getPrefixMatcher(), matched.getSorter()));
return;
}
PrefixMatcher matcher = getPrefixMatcher();
if (matcher.prefixMatches(element)) {
element.setPrefixMatcher(matcher);
getConsumer().consume(new MatchedLookupElement(element, matcher, mySorter));
}
}
@@ -15,15 +15,10 @@
*/
package com.intellij.codeInsight.completion.impl;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.ClassConditionKey;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import org.jetbrains.annotations.NotNull;
/**
* @author peter
@@ -39,7 +34,7 @@ public class MatchedLookupElement extends LookupElementDecorator<LookupElement>
mySorter = sorter;
}
public PrefixMatcher getMatcher() {
public PrefixMatcher getPrefixMatcher() {
return myMatcher;
}
@@ -47,50 +42,4 @@ public class MatchedLookupElement extends LookupElementDecorator<LookupElement>
return mySorter;
}
@Override
public void handleInsert(InsertionContext context) {
if (!isCaseSensitive()) {
if (!myMatcher.prefixMatches(this)) {
return;
}
final String prefix = myMatcher.getPrefix();
final String oldLookupString = getLookupString();
if (StringUtil.startsWithIgnoreCase(oldLookupString, prefix)) {
final String newLookupString = handleCaseInsensitiveVariant(prefix, oldLookupString);
if (!newLookupString.equals(oldLookupString)) {
final Document document = context.getEditor().getDocument();
int startOffset = context.getStartOffset();
int tailOffset = context.getTailOffset();
assert startOffset >= 0 : "stale startOffset";
assert tailOffset >= 0 : "stale tailOffset";
document.replaceString(startOffset, tailOffset, newLookupString);
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
}
}
}
super.handleInsert(context);
}
private static String handleCaseInsensitiveVariant(final String prefix, @NotNull final String lookupString) {
final int length = prefix.length();
if (length == 0) return lookupString;
boolean isAllLower = true;
boolean isAllUpper = true;
boolean sameCase = true;
for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) {
final char c = prefix.charAt(i);
isAllLower = isAllLower && Character.isLowerCase(c);
isAllUpper = isAllUpper && Character.isUpperCase(c);
sameCase = sameCase && Character.isLowerCase(c) == Character.isLowerCase(lookupString.charAt(i));
}
if (sameCase) return lookupString;
if (isAllLower) return lookupString.toLowerCase();
if (isAllUpper) return lookupString.toUpperCase();
return lookupString;
}
}
@@ -17,6 +17,7 @@
package com.intellij.codeInsight.lookup.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.completion.CodeCompletionFeatures;
import com.intellij.codeInsight.completion.CompletionLookupArranger;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
@@ -40,6 +41,7 @@ import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Pair;
@@ -76,6 +78,7 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class LookupImpl extends LightweightHint implements LookupEx, Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");
@@ -120,6 +123,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
private boolean myReused;
private boolean myChangeGuard;
private LookupModel myModel = new LookupModel();
private final Map<LookupElement, PrefixMatcher> myMatchers = new ConcurrentHashMap<LookupElement, PrefixMatcher>();
private LookupHint myElementHint = null;
private Alarm myHintAlarm = new Alarm();
private JLabel mySortingLabel;
@@ -253,14 +257,15 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
final List<LookupElement> items = myModel.getItems();
myModel.clearItems();
for (final LookupElement item : items) {
addItem(item);
addItem(item, itemMatcher(item));
}
checkReused();
updateList();
ensureSelectionVisible();
}
public void addItem(LookupElement item) {
public void addItem(LookupElement item, PrefixMatcher matcher) {
myMatchers.put(item, matcher);
myModel.addItem(item);
updateLookupWidth(item);
@@ -495,7 +500,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
private void addEmptyItem(DefaultListModel model) {
LookupItem<String> item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions"));
item.setPrefixMatcher(new CamelHumpMatcher(""));
myMatchers.put(item, new CamelHumpMatcher(""));
if (!myCalculating) {
myList.setFixedCellWidth(Math.max(myCellRenderer.updateMaximumWidth(renderItemApproximately(item)), myLookupTextWidth));
}
@@ -560,6 +565,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
return ContainerUtil.flatten(classifier.classify(better));
}
@NotNull
@Override
public String itemPattern(LookupElement element) {
return itemMatcher(element).getPrefix() + myAdditionalPrefix;
@@ -587,8 +593,13 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
}
@Override
@NotNull
public PrefixMatcher itemMatcher(LookupElement item) {
return item.getPrefixMatcher();
PrefixMatcher matcher = myMatchers.get(item);
if (matcher == null) {
throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + getItems());
}
return matcher;
}
/**
@@ -645,7 +656,10 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
}
public void finishLookup(final char completionChar) {
final LookupElement item = (LookupElement)myList.getSelectedValue();
finishLookup(completionChar, (LookupElement)myList.getSelectedValue());
}
public void finishLookup(char completionChar, @Nullable final LookupElement item) {
doHide(false, true);
if (item == null ||
item instanceof EmptyLookupItem ||
@@ -662,30 +676,63 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
return;
}
final String prefix = itemPattern(item);
boolean plainMatch = ContainerUtil.or(item.getAllLookupStrings(), new Condition<String>() {
@Override
public boolean value(String s) {
return StringUtil.startsWithIgnoreCase(s, prefix);
}
});
if (!plainMatch) {
FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS);
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
EditorModificationUtil.deleteSelectedText(myEditor);
final int caretOffset = myEditor.getCaretModel().getOffset();
final String prefix = itemPattern(item);
int lookupStart = caretOffset - prefix.length();
final String lookupString = item.getLookupString();
if (!lookupString.startsWith(prefix)) { //todo another lookup string may match the prefix
FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.camelHumps");
}
myEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString);
int offset = lookupStart + lookupString.length();
myEditor.getCaretModel().moveToOffset(offset);
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getSelectionModel().removeSelection();
insertLookupString(item, prefix);
}
});
fireItemSelected(item, completionChar);
}
private void insertLookupString(LookupElement item, final String prefix) {
PrefixMatcher matcher = itemMatcher(item);
String lookupString = item.getLookupString();
if (!item.isCaseSensitive() && matcher.prefixMatches(item) && StringUtil.startsWithIgnoreCase(lookupString, prefix)) {
lookupString = handleCaseInsensitiveVariant(prefix, lookupString);
}
EditorModificationUtil.deleteSelectedText(myEditor);
final int caretOffset = myEditor.getCaretModel().getOffset();
int lookupStart = caretOffset - prefix.length();
myEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString);
int offset = lookupStart + lookupString.length();
myEditor.getCaretModel().moveToOffset(offset);
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getSelectionModel().removeSelection();
}
private static String handleCaseInsensitiveVariant(final String prefix, @NotNull final String lookupString) {
final int length = prefix.length();
if (length == 0) return lookupString;
boolean isAllLower = true;
boolean isAllUpper = true;
boolean sameCase = true;
for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) {
final char c = prefix.charAt(i);
isAllLower = isAllLower && Character.isLowerCase(c);
isAllUpper = isAllUpper && Character.isUpperCase(c);
sameCase = sameCase && Character.isLowerCase(c) == Character.isLowerCase(lookupString.charAt(i));
}
if (sameCase) return lookupString;
if (isAllLower) return lookupString.toLowerCase();
if (isAllUpper) return lookupString.toUpperCase();
return lookupString;
}
public int getLookupStart() {
LOG.assertTrue(myLookupStartMarker.isValid());
return myLookupStartMarker.getStartOffset();
@@ -900,7 +947,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
return itmBounds;
}
public void fireItemSelected(final LookupElement item, char completionChar){
public void fireItemSelected(@Nullable final LookupElement item, char completionChar){
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
if (item != null) {
@@ -1039,7 +1086,11 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable
myEditor.getDocument().insertString(offset, afterCaret);
final String newPrefix = presentPrefix + afterCaret;
myModel.retainMatchingItems(newPrefix, this);
Map<LookupElement, PrefixMatcher> newItems = myModel.retainMatchingItems(newPrefix, this);
myMatchers.clear();
myMatchers.putAll(newItems);
myAdditionalPrefix = "";
offset += afterCaret.length();
@@ -200,12 +200,10 @@ public class LookupManagerImpl extends LookupManager {
}
});
CamelHumpMatcher matcher = new CamelHumpMatcher(prefix == null ? "" : prefix);
if (items.length > 0) {
for (final LookupElement item : items) {
if (prefix != null) {
item.setPrefixMatcher(new CamelHumpMatcher(prefix));
}
myActiveLookup.addItem(item);
myActiveLookup.addItem(item, matcher);
}
myActiveLookup.refreshUi();
} else {
@@ -15,14 +15,14 @@
*/
package com.intellij.codeInsight.lookup.impl;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.SortedList;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -111,23 +111,27 @@ public class LookupModel {
}
}
void retainMatchingItems(final String newPrefix, final LookupImpl lookup) {
@Nullable
Map<LookupElement, PrefixMatcher> retainMatchingItems(final String newPrefix, final LookupImpl lookup) {
synchronized (lock) {
final List<LookupElement> newItems = ContainerUtil.findAll(myItems, new Condition<LookupElement>() {
@Override
public boolean value(LookupElement item) {
return item.isValid() && item.setPrefixMatcher(lookup.itemMatcher(item).cloneWithPrefix(newPrefix));
LinkedHashMap<LookupElement, PrefixMatcher> map = new LinkedHashMap<LookupElement, PrefixMatcher>();
for (LookupElement item : myItems) {
if (item.isValid()) {
PrefixMatcher matcher = lookup.itemMatcher(item).cloneWithPrefix(newPrefix);
if (matcher.prefixMatches(item)) {
map.put(item, matcher);
}
}
});
if (newItems.size() == myItems.size()) {
return;
}
clearItems();
for (LookupElement newItem : newItems) {
addItem(newItem);
if (map.size() != myItems.size()) {
clearItems();
for (LookupElement newItem : map.keySet()) {
addItem(newItem);
}
}
return map;
}
}
@@ -89,17 +89,12 @@ public class ListTemplatesHandler implements CodeInsightActionHandler {
}
public static void showTemplatesLookup(final Project project, final Editor editor, Map<TemplateImpl, String> template2Argument) {
ArrayList<LookupItem> array = new ArrayList<LookupItem>();
final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, LookupElement.EMPTY_ARRAY, null, LookupArranger.DEFAULT);
for (TemplateImpl template : template2Argument.keySet()) {
String argument = template2Argument.get(template);
String prefix = computePrefix(template, argument);
LookupItem item = new LookupItem(template, prefix);
item.setPrefixMatcher(new CamelHumpMatcher(prefix));
array.add(item);
String prefix = computePrefix(template, template2Argument.get(template));
lookup.addItem(new LookupItem(template, prefix), new CamelHumpMatcher(prefix));
}
LookupElement[] items = array.toArray(new LookupElement[array.size()]);
final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, items, null, LookupArranger.DEFAULT);
lookup.addLookupListener(new MyLookupAdapter(project, editor, template2Argument));
lookup.show();
}