Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 12 additions & 24 deletions src/services/patternMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,33 +110,22 @@ namespace ts {
const invalidPattern = dotSeparatedSegments.length === 0 || forEach(dotSeparatedSegments, segmentIsInvalid);

return {
getMatches,
getMatchesForLastSegmentOfPattern,
getMatches: (containers, candidate) => skipMatch(candidate) ? undefined : getMatches(containers, candidate, dotSeparatedSegments, stringToWordSpans),
getMatchesForLastSegmentOfPattern: candidate => skipMatch(candidate) ? undefined : matchSegment(candidate, lastOrUndefined(dotSeparatedSegments), stringToWordSpans),
patternContainsDots: dotSeparatedSegments.length > 1
};

// Quick checks so we can bail out when asked to match a candidate.
function skipMatch(candidate: string) {
return invalidPattern || !candidate;
}

function getMatchesForLastSegmentOfPattern(candidate: string): PatternMatch[] {
if (skipMatch(candidate)) {
return undefined;
}

return matchSegment(candidate, lastOrUndefined(dotSeparatedSegments));
}

function getMatches(candidateContainers: string[], candidate: string): PatternMatch[] | undefined {
if (skipMatch(candidate)) {
return undefined;
}

function getMatches(candidateContainers: ReadonlyArray<string>, candidate: string, dotSeparatedSegments: ReadonlyArray<Segment>, stringToWordSpans: Map<TextSpan[]>): PatternMatch[] | undefined {
// First, check that the last part of the dot separated pattern matches the name of the
// candidate. If not, then there's no point in proceeding and doing the more
// expensive work.
const candidateMatch = matchSegment(candidate, lastOrUndefined(dotSeparatedSegments));
const candidateMatch = matchSegment(candidate, lastOrUndefined(dotSeparatedSegments), stringToWordSpans);
if (!candidateMatch) {
return undefined;
}
Expand All @@ -162,7 +151,7 @@ namespace ts {
const segment = dotSeparatedSegments[i];
const containerName = candidateContainers[j];

const containerMatch = matchSegment(containerName, segment);
const containerMatch = matchSegment(containerName, segment, stringToWordSpans);
if (!containerMatch) {
// This container didn't match the pattern piece. So there's no match at all.
return undefined;
Expand All @@ -176,15 +165,15 @@ namespace ts {
return totalMatch;
}

function getWordSpans(word: string): TextSpan[] {
function getWordSpans(word: string, stringToWordSpans: Map<TextSpan[]>): TextSpan[] {
let spans = stringToWordSpans.get(word);
if (!spans) {
stringToWordSpans.set(word, spans = breakIntoWordSpans(word));
}
return spans;
}

function matchTextChunk(candidate: string, chunk: TextChunk): PatternMatch {
function matchTextChunk(candidate: string, chunk: TextChunk, stringToWordSpans: Map<TextSpan[]>): PatternMatch {
const index = indexOfIgnoringCase(candidate, chunk.textLowerCase);
if (index === 0) {
if (chunk.text.length === candidate.length) {
Expand All @@ -209,7 +198,7 @@ namespace ts {
// 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').
const wordSpans = getWordSpans(candidate);
const wordSpans = getWordSpans(candidate, stringToWordSpans);
for (const span of wordSpans) {
if (partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ true)) {
return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ false));
Expand All @@ -229,7 +218,7 @@ namespace ts {
if (!isLowercase) {
// e) If the part was not entirely lowercase, then attempt a camel cased match as well.
if (chunk.characterSpans.length > 0) {
const candidateParts = getWordSpans(candidate);
const candidateParts = getWordSpans(candidate, stringToWordSpans);
const isCaseSensitive = tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ false) ? true
: tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ true) ? false : undefined;
if (isCaseSensitive !== undefined) {
Expand Down Expand Up @@ -267,7 +256,7 @@ namespace ts {
return false;
}

function matchSegment(candidate: string, segment: Segment): PatternMatch[] {
function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map<TextSpan[]>): PatternMatch[] {
// 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
Expand All @@ -276,7 +265,7 @@ namespace ts {
// Note: if the segment contains a space or an asterisk then we must assume that it's a
// multi-word segment.
if (!containsSpaceOrAsterisk(segment.totalTextChunk.text)) {
const match = matchTextChunk(candidate, segment.totalTextChunk);
const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans);
if (match) {
return [match];
}
Expand Down Expand Up @@ -324,7 +313,7 @@ namespace ts {

for (const subWordTextChunk of subWordTextChunks) {
// Try to match the candidate with this word
const result = matchTextChunk(candidate, subWordTextChunk);
const result = matchTextChunk(candidate, subWordTextChunk, stringToWordSpans);
if (!result) {
return undefined;
}
Expand Down Expand Up @@ -438,7 +427,6 @@ namespace ts {
currentCandidate++;
}
}
}

function createSegment(text: string): Segment {
return {
Expand Down