in vsintegration/src/FSharp.LanguageService.Base/PatternMatcher/PatternMatcher.cs [298:420]
private PatternMatch? MatchTextChunk(
string candidate,
bool includeMatchSpans,
TextChunk chunk,
bool punctuationStripped,
bool fuzzyMatch)
{
int caseInsensitiveIndex = _compareInfo.IndexOf(candidate, chunk.Text, CompareOptions.IgnoreCase);
if (caseInsensitiveIndex == 0)
{
if (chunk.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: candidate == chunk.Text,
matchedSpan: GetMatchedSpan(includeMatchSpans, 0, 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, chunk.Text),
matchedSpan: GetMatchedSpan(includeMatchSpans, 0, chunk.Text.Length));
}
}
var isLowercase = !ContainsUpperCaseLetter(chunk.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').
var wordSpans = GetWordSpans(candidate);
for (int i = 0; i < wordSpans.Count; i++)
{
var span = wordSpans[i];
if (PartStartsWith(candidate, span, chunk.Text, CompareOptions.IgnoreCase))
{
return new PatternMatch(PatternMatchKind.Substring, punctuationStripped,
isCaseSensitive: PartStartsWith(candidate, span, chunk.Text, CompareOptions.None),
matchedSpan: GetMatchedSpan(includeMatchSpans, span.Start, chunk.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, chunk.Text);
if (caseSensitiveIndex > 0)
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: true,
matchedSpan: GetMatchedSpan(includeMatchSpans, caseSensitiveIndex, chunk.Text.Length));
}
}
if (!isLowercase)
{
// e) If the part was not entirely lowercase, then attempt a camel cased match as well.
if (chunk.CharacterSpans.Count > 0)
{
var candidateParts = GetWordSpans(candidate);
List<TextSpan> matchedSpans;
var camelCaseWeight = TryCamelCaseMatch(candidate, includeMatchSpans, candidateParts, chunk, CompareOptions.None, out matchedSpans);
if (camelCaseWeight.HasValue)
{
return new PatternMatch(
PatternMatchKind.CamelCase, punctuationStripped, isCaseSensitive: true, camelCaseWeight: camelCaseWeight,
matchedSpans: GetMatchedSpans(includeMatchSpans, matchedSpans));
}
camelCaseWeight = TryCamelCaseMatch(candidate, includeMatchSpans, candidateParts, chunk, CompareOptions.IgnoreCase, out matchedSpans);
if (camelCaseWeight.HasValue)
{
return new PatternMatch(
PatternMatchKind.CamelCase, punctuationStripped, isCaseSensitive: false, camelCaseWeight: camelCaseWeight,
matchedSpans: GetMatchedSpans(includeMatchSpans, matchedSpans));
}
}
}
if (isLowercase)
{
// f) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries?
// 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 (chunk.Text.Length < candidate.Length)
{
if (caseInsensitiveIndex != -1 && char.IsUpper(candidate[caseInsensitiveIndex]))
{
return new PatternMatch(
PatternMatchKind.Substring, punctuationStripped, isCaseSensitive: false,
matchedSpan: GetMatchedSpan(includeMatchSpans, caseInsensitiveIndex, chunk.Text.Length));
}
}
}
if (fuzzyMatch)
{
if (chunk.SimilarityChecker.AreSimilar(candidate))
{
return new PatternMatch(
PatternMatchKind.Fuzzy, punctuationStripped, isCaseSensitive: false, matchedSpan: null);
}
}
return null;
}