Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 8x 8x 8x 8x 32x 32x 32x 19x 19x 12x 7x 7x 7x 7x 1x 6x 6x 6x 6x 6x 6x 6x 6x 24x 24x 24x 8x 6x | import { MatchResult, MatchCandidate } from './base.matcher';
import { ExactMatcher } from './exact.matcher';
import { NormalizedMatcher } from './normalized.matcher';
import { ContextAwareMatcher } from './context-aware.matcher';
/**
* Hybrid matcher that combines multiple matching strategies.
* Tries exact match first, then falls back to more sophisticated methods.
*/
export class HybridMatcher {
private readonly exactMatcher = new ExactMatcher();
private readonly normalizedMatcher = new NormalizedMatcher();
private readonly contextAwareMatcher = new ContextAwareMatcher();
/**
* Attempt to find the best match using all available strategies.
*
* Strategy:
* 1. Try exact match (fastest, highest confidence)
* 2. Try normalized match (handles whitespace variations)
* 3. Try context-aware match (disambiguates using surrounding context)
*
* Returns the first successful match, or aggregates all candidates on failure.
*/
findBestMatch(searchBlock: string, content: string): MatchResult {
// Phase 1: Exact match (fastest)
const exactResult = this.exactMatcher.match(searchBlock, content);
if (exactResult.found && exactResult.unique) {
return exactResult;
}
// Phase 2: Normalized match (handles whitespace)
const normalizedResult = this.normalizedMatcher.match(searchBlock, content);
Iif (normalizedResult.found && normalizedResult.unique) {
return normalizedResult;
}
// Phase 3: Context-aware match (disambiguates)
const contextResult = this.contextAwareMatcher.match(searchBlock, content);
if (contextResult.found && contextResult.unique) {
return contextResult;
}
// Phase 4: Failure - aggregate all candidates
return this.aggregateFailureCandidates(
exactResult,
normalizedResult,
contextResult,
);
}
/**
* Aggregate candidates from all matchers on failure.
* Deduplicates similar candidates and sorts by confidence.
*/
private aggregateFailureCandidates(
exactResult: MatchResult,
normalizedResult: MatchResult,
contextResult: MatchResult,
): MatchResult {
const allCandidates = [
...(exactResult.candidates || []),
...(normalizedResult.candidates || []),
...(contextResult.candidates || []),
];
// Deduplicate candidates with same startLine
const deduplicated = this.deduplicateCandidates(allCandidates);
// Sort by confidence descending
deduplicated.sort((a, b) => b.confidence - a.confidence);
const bestConfidence =
deduplicated.length > 0 ? deduplicated[0].confidence : 0;
// Return found: true if we have any candidates, even if not unique
// This is important for showing options to the user
return {
found: deduplicated.length > 0,
unique: false,
confidence: bestConfidence,
candidates: deduplicated,
};
}
/**
* Remove duplicate candidates (same startLine and endLine).
* Keep the one with highest confidence.
*/
private deduplicateCandidates(
candidates: MatchCandidate[],
): MatchCandidate[] {
const map = new Map<string, MatchCandidate>();
for (const candidate of candidates) {
const key = `${candidate.startLine}-${candidate.endLine}`;
const existing = map.get(key);
if (!existing || candidate.confidence > existing.confidence) {
map.set(key, candidate);
}
}
return Array.from(map.values());
}
}
|