in src/Editor/Text/Impl/PatternMatching/PatternMatcher.cs [178:304]
private PatternMatch? NonFuzzyMatchPatternChunk(
string candidate,
TextChunk patternChunk,
bool punctuationStripped,
int chunkOffset)
{
int caseInsensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text, CompareOptions.IgnoreCase);
if (caseInsensitiveIndex == 0)
{
if (patternChunk.Text.Length == candidate.Length)
{
// a) Check if the part matches the candidate entirely, in an case insensitive or
// sensitive manner. If it does, return that there was an exact match.
return new PatternMatch(
PatternMatchKind.Exact, punctuationStripped, isCaseSensitive: string.Equals(candidate, patternChunk.Text, StringComparison.Ordinal),
matchedSpans: GetMatchedSpans(chunkOffset, candidate.Length));
}
else
{
// b) Check if the part is a prefix of the candidate, in a case insensitive or sensitive
// manner. If it does, return that there was a prefix match.
return new PatternMatch(
PatternMatchKind.Prefix, punctuationStripped, isCaseSensitive: _compareInfo.IsPrefix(candidate, patternChunk.Text),
matchedSpans: GetMatchedSpans(chunkOffset, patternChunk.Text.Length));
}
}
// b++) If the part is a case insensitive substring match, but not a prefix, and the caller
// requested simple substring matches, return that there was a substring match.
// This covers the case of non camel case naming conventions, for example matching
// 'afxsettingsstore.h' when user types 'store.h'
else if (caseInsensitiveIndex > 0 && _allowSimpleSubstringMatching)
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped,
isCaseSensitive: PartStartsWith(
candidate, new TextSpan(caseInsensitiveIndex, patternChunk.Text.Length),
patternChunk.Text, CompareOptions.None),
matchedSpans: GetMatchedSpans(chunkOffset + caseInsensitiveIndex, patternChunk.Text.Length));
}
var isLowercase = !ContainsUpperCaseLetter(patternChunk.Text);
if (isLowercase)
{
if (caseInsensitiveIndex > 0)
{
// c) If the part is entirely lowercase, then check if it is contained anywhere in the
// candidate in a case insensitive manner. If so, return that there was a substring
// match.
//
// Note: We only have a substring match if the lowercase part is prefix match of some
// word part. That way we don't match something like 'Class' when the user types 'a'.
// But we would match 'FooAttribute' (since 'Attribute' starts with 'a').
//
// Also, if we matched at location right after punctuation, then this is a good
// substring match. i.e. if the user is testing mybutton against _myButton
// then this should hit. As we really are finding the match at the beginning of
// a word.
if (char.IsPunctuation(candidate[caseInsensitiveIndex - 1]) ||
char.IsPunctuation(patternChunk.Text[0]))
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped,
isCaseSensitive: PartStartsWith(
candidate, new TextSpan(caseInsensitiveIndex, patternChunk.Text.Length),
patternChunk.Text, CompareOptions.None),
matchedSpans: GetMatchedSpans(chunkOffset + caseInsensitiveIndex, patternChunk.Text.Length));
}
var wordSpans = GetWordSpans(candidate);
for (int i = 0, n = wordSpans.GetCount(); i < n; i++)
{
var span = wordSpans[i];
if (PartStartsWith(candidate, span, patternChunk.Text, CompareOptions.IgnoreCase))
{
return new PatternMatch(PatternMatchKind.Substring, punctuationStripped,
isCaseSensitive: PartStartsWith(candidate, span, patternChunk.Text, CompareOptions.None),
matchedSpans: GetMatchedSpans(chunkOffset + span.Start, patternChunk.Text.Length));
}
}
}
}
else
{
// d) If the part was not entirely lowercase, then check if it is contained in the
// candidate in a case *sensitive* manner. If so, return that there was a substring
// match.
var caseSensitiveIndex = _compareInfo.IndexOf(candidate, patternChunk.Text);
if (caseSensitiveIndex > 0)
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: true,
matchedSpans: GetMatchedSpans(chunkOffset + caseSensitiveIndex, patternChunk.Text.Length));
}
}
var match = TryCamelCaseMatch(
candidate, patternChunk, punctuationStripped, isLowercase, chunkOffset);
if (match.HasValue)
{
return match.Value;
}
if (isLowercase)
{
// g) The word is all lower case. Is it a case insensitive substring of the candidate
// starting on a part boundary of the candidate?
// We could check every character boundary start of the candidate for the pattern.
// However, that's an m * n operation in the worst case. Instead, find the first
// instance of the pattern substring, and see if it starts on a capital letter.
// It seems unlikely that the user will try to filter the list based on a substring
// that starts on a capital letter and also with a lowercase one. (Pattern: fogbar,
// Candidate: quuxfogbarFogBar).
if (patternChunk.Text.Length < candidate.Length)
{
if (caseInsensitiveIndex != -1 && char.IsUpper(candidate[caseInsensitiveIndex]))
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: false,
matchedSpans: GetMatchedSpans(chunkOffset + caseInsensitiveIndex, patternChunk.Text.Length));
}
}
}
return null;
}