/// <summary> /// Updates the currently stored 'best result' if the current result is better. /// Returns 'true' if no further work is required and we can break early, or /// 'false' if we need to keep on going. /// /// If 'weight' is better than 'bestWeight' and matchSpanToAdd is not null, then /// matchSpanToAdd will be added to matchedSpansInReverse. /// </summary> private bool UpdateBestResultIfBetter( CamelCaseResult result, ref CamelCaseResult?bestResult, TextSpan?matchSpanToAdd) { if (matchSpanToAdd != null) { result = result.WithAddedMatchedSpan(matchSpanToAdd.Value); } if (!IsBetter(result, bestResult)) { // Even though we matched this current candidate hump we failed to match // the remainder of the pattern. Continue to the next candidate hump // to see if our pattern character will match it and potentially succeed. result.Free(); // We need to keep going. return(false); } // This was result was better than whatever previous best result we had was. // Free and overwrite the existing best results, and keep going. bestResult?.Free(); bestResult = result; // We found a path that allowed us to match everything contiguously // from the beginning. This is the best match possible. So we can // just break out now and return this result. return(GetKind(result) == PatternMatchKind.CamelCaseExact); }
private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, ArrayBuilder <TextSpan> candidateHumps) { var toEnd = result.MatchCount == candidateHumps.Count; if (result.FromStart) { if (result.Contiguous) { // We contiguously matched humps from the start of this candidate. If we // matched all the humps, then this was an exact match, otherwise it was a // contiguous prefix match return(toEnd ? PatternMatchKind.CamelCaseExact : PatternMatchKind.CamelCasePrefix); } else { return(PatternMatchKind.CamelCaseNonContiguousPrefix); } } else { // We didn't match from the start. Distinguish between a match whose humps are all // contiguous, and one that isn't. return(result.Contiguous ? PatternMatchKind.CamelCaseSubstring : PatternMatchKind.CamelCaseNonContiguousSubstring); } }
private bool IsBetter(CamelCaseResult result, CamelCaseResult?currentBestResult) { if (currentBestResult == null) { // We have no current best. So this result is the best. return(true); } return(GetKind(result) < GetKind(currentBestResult.Value)); }
private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result) { /* CamelCase PatternMatchKind truth table: * | FromStart | ToEnd | Contiguous || PatternMatchKind | * | True | True | True || CamelCaseExact | * | True | True | False || CamelCaseNonContiguousPrefix | * | True | False | True || CamelCasePrefix | * | True | False | False || CamelCaseNonContiguousPrefix | * | False | True | True || CamelCaseSubstring | * | False | True | False || CamelCaseNonContiguousSubstring | * | False | False | True || CamelCaseSubstring | * | False | False | False || CamelCaseNonContiguousSubstring | */ if (result.FromStart) { if (result.Contiguous) { // We contiguously matched humps from the start of this candidate. If we // matched all the humps, then this was an exact match, otherwise it was a // contiguous prefix match return(result.ToEnd ? PatternMatchKind.CamelCaseExact : PatternMatchKind.CamelCasePrefix); } else { return(PatternMatchKind.CamelCaseNonContiguousPrefix); } } else { // We didn't match from the start. Distinguish between a match whose humps are all // contiguous, and one that isn't. return(result.Contiguous ? PatternMatchKind.CamelCaseSubstring : PatternMatchKind.CamelCaseNonContiguousSubstring); } }
private PatternMatchKind GetKind(CamelCaseResult result) => PatternMatcher.GetCamelCaseKind(result, _candidateHumps);
private PatternMatchKind?TryUpperCaseCamelCaseMatch( string candidate, ArrayBuilder <TextSpan> candidateHumps, TextChunk patternChunk, CompareOptions compareOption, out ImmutableArray <TextSpan> matchedSpans) { var patternHumps = patternChunk.PatternHumps; // Note: we may have more pattern parts than candidate parts. This is because multiple // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U // and I will both match in UI. var currentCandidateHump = 0; var currentPatternHump = 0; int? firstMatch = null; bool?contiguous = null; var patternHumpCount = patternHumps.Count; var candidateHumpCount = candidateHumps.Count; using var _ = ArrayBuilder <TextSpan> .GetInstance(out var matchSpans); while (true) { // Let's consider our termination cases if (currentPatternHump == patternHumpCount) { Debug.Assert(firstMatch.HasValue); Debug.Assert(contiguous.HasValue); var matchCount = matchSpans.Count; matchedSpans = _includeMatchedSpans ? new NormalizedTextSpanCollection(matchSpans).ToImmutableArray() : ImmutableArray <TextSpan> .Empty; var camelCaseResult = new CamelCaseResult(firstMatch == 0, contiguous.Value, matchCount, null); return(GetCamelCaseKind(camelCaseResult, candidateHumps)); } else if (currentCandidateHump == candidateHumpCount) { // No match, since we still have more of the pattern to hit matchedSpans = ImmutableArray <TextSpan> .Empty; return(null); } var candidateHump = candidateHumps[currentCandidateHump]; var gotOneMatchThisCandidate = false; // Consider the case of matching SiUI against SimpleUIElement. The candidate parts // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to // still keep matching pattern parts against that candidate part. for (; currentPatternHump < patternHumpCount; currentPatternHump++) { var patternChunkCharacterSpan = patternHumps[currentPatternHump]; if (gotOneMatchThisCandidate) { // We've already gotten one pattern part match in this candidate. We will // only continue trying to consume pattern parts if the last part and this // part are both upper case. if (!char.IsUpper(patternChunk.Text[patternHumps[currentPatternHump - 1].Start]) || !char.IsUpper(patternChunk.Text[patternHumps[currentPatternHump].Start])) { break; } } if (!PartStartsWith(candidate, candidateHump, patternChunk.Text, patternChunkCharacterSpan, compareOption)) { break; } matchSpans.Add(new TextSpan(candidateHump.Start, patternChunkCharacterSpan.Length)); gotOneMatchThisCandidate = true; firstMatch ??= currentCandidateHump; // If we were contiguous, then keep that value. If we weren't, then keep that // value. If we don't know, then set the value to 'true' as an initial match is // obviously contiguous. contiguous ??= true; candidateHump = new TextSpan(candidateHump.Start + patternChunkCharacterSpan.Length, candidateHump.Length - patternChunkCharacterSpan.Length); } // Check if we matched anything at all. If we didn't, then we need to unset the // contiguous bit if we currently had it set. // If we haven't set the bit yet, then that means we haven't matched anything so // far, and we don't want to change that. if (!gotOneMatchThisCandidate && contiguous.HasValue) { contiguous = false; } // Move onto the next candidate. currentCandidateHump++; } }
private static PatternMatchKind GetKind(CamelCaseResult result) => PatternMatcher.GetCamelCaseKind(result);