From 7bb4e22125b907c5c68a59f6c166fcdfd2447079 Mon Sep 17 00:00:00 2001 From: Kodai Nakamura Date: Thu, 5 Jun 2025 21:56:02 +0900 Subject: [PATCH] perf --- src/replace-links/replace-links.ts | 36 ++++++++++++++++++------------ src/trie.ts | 21 ++++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/replace-links/replace-links.ts b/src/replace-links/replace-links.ts index 7ac748f..f7fa5b4 100644 --- a/src/replace-links/replace-links.ts +++ b/src/replace-links/replace-links.ts @@ -136,15 +136,16 @@ const normalizeCanonicalPath = (linkPath: string, baseDir?: string): string => { const extractLinkParts = ( canonicalPath: string, ): { linkPath: string; alias: string; hasAlias: boolean } => { - const hasAlias = canonicalPath.includes("|"); - let linkPath = canonicalPath; - let alias = ""; - + const pipeIndex = canonicalPath.indexOf("|"); + const hasAlias = pipeIndex !== -1; + if (hasAlias) { - [linkPath, alias] = canonicalPath.split("|"); + const linkPath = canonicalPath.slice(0, pipeIndex); + const alias = canonicalPath.slice(pipeIndex + 1); + return { linkPath, alias, hasAlias }; } - return { linkPath, alias, hasAlias }; + return { linkPath: canonicalPath, alias: "", hasAlias }; }; // Link Content Creation @@ -281,11 +282,15 @@ const processFallbackSearch = ( // Iterate through potential multi-word sequences starting from startIndex const maxSearchLength = Math.min(text.length - startIndex, 100); // Limit search length for performance + let potentialMatch = ''; + let searchWord = ''; + for (let length = 1; length <= maxSearchLength; length++) { const endIndex = startIndex + length; - const potentialMatch = text.substring(startIndex, endIndex); - const searchWord = settings.ignoreCase - ? potentialMatch.toLowerCase() + const currentChar = text[startIndex + length - 1]; + potentialMatch += currentChar; + searchWord = settings.ignoreCase + ? searchWord + currentChar.toLowerCase() : potentialMatch; // Check if this potential match exists in fallback index @@ -495,16 +500,19 @@ const processStandardText = ( let node = trie; let lastCandidate: { candidate: string; length: number } | null = null; let j = i; + let candidateBuilder = ''; while (j < text.length) { - const ch = settings.ignoreCase ? text[j].toLowerCase() : text[j]; - const child = node.children.get(ch); + const ch = text[j]; + const chLower = settings.ignoreCase ? ch.toLowerCase() : ch; + candidateBuilder += ch; + + const child = node.children.get(chLower); if (!child) break; node = child; if (node.candidate) { - const candidate = text.substring(i, j + 1); - const candidateIsCjk = isCjkCandidate(candidate); + const candidateIsCjk = isCjkCandidate(candidateBuilder); if (candidateIsCjk || isWordBoundary(text[j + 1])) { lastCandidate = { @@ -517,7 +525,7 @@ const processStandardText = ( } if (lastCandidate) { - const candidate = text.substring(i, i + lastCandidate.length); + const candidate = candidateBuilder.slice(0, lastCandidate.length); // Skip if it's a date format if ( diff --git a/src/trie.ts b/src/trie.ts index a651f66..5b3363f 100644 --- a/src/trie.ts +++ b/src/trie.ts @@ -82,6 +82,9 @@ export const buildCandidateTrie = ( // Effective namespace computed relative to baseDir. namespace: string; }; + const basePrefix = baseDir ? `${baseDir}/` : null; + const basePrefixLength = basePrefix?.length || 0; + const candidates: Candidate[] = allFiles.map((f) => { const candidate: Candidate = { full: f.path, @@ -89,11 +92,8 @@ export const buildCandidateTrie = ( restrictNamespace: f.restrictNamespace, namespace: getEffectiveNamespace(f.path, baseDir), }; - if (baseDir) { - const prefix = `${baseDir}/`; - if (f.path.startsWith(prefix)) { - candidate.short = f.path.slice(prefix.length); - } + if (basePrefix && f.path.startsWith(basePrefix)) { + candidate.short = f.path.slice(basePrefixLength); } return candidate; }); @@ -152,11 +152,8 @@ export const buildCandidateTrie = ( if (file.aliases) { // Determine shorthand candidate for the file if available. let short: string | null = null; - if (baseDir) { - const prefix = `${baseDir}/`; - if (file.path.startsWith(prefix)) { - short = file.path.slice(prefix.length); - } + if (basePrefix && file.path.startsWith(basePrefix)) { + short = file.path.slice(basePrefixLength); } for (const alias of file.aliases) { // If alias equals the shorthand, use alias as canonical; otherwise use "full|alias". @@ -178,8 +175,8 @@ export const buildCandidateTrie = ( // When ignoreCase is enabled, we need to add both the original and lowercase versions const trieWords = ignoreCase ? words.flatMap(word => { - const segments = word.split('/'); - const lastSegment = segments[segments.length - 1]; + const lastSlashIndex = word.lastIndexOf('/'); + const lastSegment = lastSlashIndex === -1 ? word : word.slice(lastSlashIndex + 1); return [word, lastSegment.toLowerCase()]; }) : words;