private PatternMatch?TryMatch(string candidate, bool fuzzyMatch) { if (fuzzyMatch && !_allowFuzzyMatching) { return(null); } var containerParts = candidate.Split(_containerSplitCharacters, StringSplitOptions.RemoveEmptyEntries); var patternSegmentCount = _patternSegments.Length; var containerPartCount = containerParts.Length; if (patternSegmentCount > containerPartCount) { // There weren't enough container parts to match against the pattern parts. // So this definitely doesn't match. return(null); } // So far so good. Now break up the container for the candidate and check if all // the dotted parts match up correctly. PatternMatch?match = null, result = null; for (int i = patternSegmentCount - 1, j = containerPartCount - 1; i >= 0; i--, j--) { var segment = _patternSegments[i]; var containerName = containerParts[j]; // Add up the lengths of all the container parts before this one, as well is the split characters that were removed. int containerOffset = j; for (int k = 0; k < j; k++) { containerOffset += containerParts[k].Length; } result = MatchPatternSegment(containerName, segment, fuzzyMatch, containerOffset); if (!result.HasValue) { // This container didn't match the pattern piece. So there's no match at all. return(null); } match = match?.Merge(result.Value, PatternMatchMergeStrategy.Container) ?? result; } return(match); }
/// <summary> /// Internal helper for MatchPatternInternal /// </summary> /// <remarks> /// PERF: Designed to minimize allocations in common cases. /// If there's no match, then null is returned. /// If there's a single match, or the caller only wants the first match, then it is returned (as a Nullable) /// If there are multiple matches they are merged. /// </remarks> /// <param name="candidate">The word being tested.</param> /// <param name="segment">The segment of the pattern to check against the candidate.</param> /// <param name="fuzzyMatch">If a fuzzy match should be performed</param> /// <param name="spanOffset">Index of segment[0] in candidate, used to merge matches together.</param> /// <returns>Returns a match if found. Otherwise it is null.</returns> private PatternMatch?MatchPatternSegment( string candidate, PatternSegment segment, bool fuzzyMatch, int segmentOffset) { if (fuzzyMatch && !_allowFuzzyMatching) { return(null); } // First check if the segment matches as is. This is also useful if the segment contains // characters we would normally strip when splitting into parts that we also may want to // match in the candidate. For example if the segment is "@int" and the candidate is // "@int", then that will show up as an exact match here. // // Note: if the segment contains a space or an asterisk then we must assume that it's a // multi-word segment. PatternMatch?match = null; if (!ContainsSpaceOrAsterisk(segment.TotalTextChunk.Text)) { match = MatchPatternChunk( candidate, segment.TotalTextChunk, punctuationStripped: false, fuzzyMatch: fuzzyMatch, chunkOffset: segmentOffset); if (match != null) { return(match); } } // The logic for pattern matching is now as follows: // // 1) Break the segment passed in into words. Breaking is rather simple and a // good way to think about it that if gives you all the individual alphanumeric words // of the pattern. // // 2) For each word try to match the word against the candidate value. // // 3) Matching is as follows: // // a) Check if the word matches the candidate entirely, in an case insensitive or // sensitive manner. If it does, return that there was an exact match. // // b) Check if the word is a prefix of the candidate, in a case insensitive or // sensitive manner. If it does, return that there was a prefix match. // // c) If the word 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'). // // d) If the word 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. // // e) If the word was entirely lowercase, then attempt a special lower cased camel cased // match. i.e. cofipro would match CodeFixProvider. // // f) If the word was not entirely lowercase, then attempt a normal camel cased match. // i.e. CoFiPro would match CodeFixProvider, but CofiPro would not. // // g) The word is all lower case. Is it a case insensitive substring of the candidate starting // on a part boundary of the candidate? // // 4) Merge matches back together using the Simple strategy. // // Only if all words have some sort of match is the pattern considered matched. int chunkOffset = segmentOffset; var subWordTextChunks = segment.SubWordTextChunks; foreach (var subWordTextChunk in subWordTextChunks) { // Try to match the candidate with this word var result = MatchPatternChunk( candidate, subWordTextChunk, punctuationStripped: true, fuzzyMatch: fuzzyMatch, chunkOffset: chunkOffset); if (result == null) { return(null); } match = match?.Merge(result.Value, PatternMatchMergeStrategy.Simple) ?? result.Value; } return(match); }