blob: afb392845031f71d36979ff04a1214ef28413fa2 [file] [log] [blame]
package com.intellij.codeInsight.completion.impl;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.PrefixMatcher;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.codeStyle.MinusculeMatcher;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.util.containers.FList;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
/**
* @author peter
*/
public class CamelHumpMatcher extends PrefixMatcher {
private final MinusculeMatcher myMatcher;
private final MinusculeMatcher myCaseInsensitiveMatcher;
private final boolean myCaseSensitive;
private static boolean ourForceStartMatching;
public CamelHumpMatcher(@NotNull final String prefix) {
this(prefix, true);
}
public CamelHumpMatcher(String prefix, boolean caseSensitive) {
super(prefix);
myCaseSensitive = caseSensitive;
myMatcher = createMatcher(myCaseSensitive);
myCaseInsensitiveMatcher = createMatcher(false);
}
@Override
public boolean isStartMatch(String name) {
return myMatcher.isStartMatch(name);
}
@Override
public boolean isStartMatch(LookupElement element) {
for (String s : element.getAllLookupStrings()) {
FList<TextRange> ranges = myCaseInsensitiveMatcher.matchingFragments(s);
if (ranges == null) continue;
if (ranges.isEmpty() || skipUnderscores(s) >= ranges.get(0).getStartOffset()) {
return true;
}
}
return false;
}
private static int skipUnderscores(@NotNull String name) {
return CharArrayUtil.shiftForward(name, 0, "_");
}
@Override
public boolean prefixMatches(@NotNull final String name) {
return myMatcher.matches(name);
}
@Override
public boolean prefixMatches(@NotNull final LookupElement element) {
return prefixMatchersInternal(element, !element.isCaseSensitive());
}
private boolean prefixMatchersInternal(final LookupElement element, final boolean itemCaseInsensitive) {
for (final String name : element.getAllLookupStrings()) {
if (itemCaseInsensitive && StringUtil.startsWithIgnoreCase(name, myPrefix) || prefixMatches(name)) {
return true;
}
if (itemCaseInsensitive && CodeInsightSettings.ALL != CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE) {
if (myCaseInsensitiveMatcher.matches(name)) {
return true;
}
}
}
return false;
}
@Override
@NotNull
public PrefixMatcher cloneWithPrefix(@NotNull final String prefix) {
return new CamelHumpMatcher(prefix, myCaseSensitive);
}
private MinusculeMatcher createMatcher(final boolean caseSensitive) {
String prefix = applyMiddleMatching(myPrefix);
if (!caseSensitive) {
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.NONE);
}
switch (CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE) {
case CodeInsightSettings.NONE:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.NONE);
case CodeInsightSettings.FIRST_LETTER:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.FIRST_LETTER);
default:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.ALL);
}
}
public static String applyMiddleMatching(String prefix) {
if (Registry.is("ide.completion.middle.matching") && !prefix.isEmpty() && !ourForceStartMatching) {
return "*" + StringUtil.replace(prefix, ".", ". ").trim();
}
return prefix;
}
@Override
public String toString() {
return myPrefix;
}
/**
* In an ideal world, all tests would use the same settings as production, i.e. middle matching.
* If you see a usage of this method which can be easily removed (i.e. it's easy to make a test pass without it
* by modifying test expectations slightly), please do it
*/
@TestOnly
@Deprecated
public static void forceStartMatching(Disposable parent) {
ourForceStartMatching = true;
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourForceStartMatching = false;
}
});
}
@Override
public int matchingDegree(String string) {
FList<TextRange> ranges = myCaseInsensitiveMatcher.matchingFragments(string);
if (ranges != null && !ranges.isEmpty()) {
int matchStart = ranges.get(0).getStartOffset();
int underscoreEnd = skipUnderscores(string);
if (matchStart > 0 && matchStart <= underscoreEnd) {
return myCaseInsensitiveMatcher.matchingDegree(string.substring(matchStart), true) - 1;
}
}
return myMatcher.matchingDegree(string, true);
}
}