Skip to content
Closed
Show file tree
Hide file tree
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
215 changes: 215 additions & 0 deletions scripts/test-launcher-file-result-command-map.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#!/usr/bin/env node

import test from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { build } from 'esbuild';

const root = path.resolve('.');
const {
buildLauncherFileCandidates,
buildLauncherFileResultCommandByPath,
} = await importBundledTs(path.join(root, 'src/renderer/src/utils/launcher-file-candidates.ts'));

async function importBundledTs(absPath) {
const result = await build({
absWorkingDir: root,
entryPoints: [absPath],
bundle: true,
write: false,
format: 'esm',
platform: 'node',
target: 'node20',
logLevel: 'silent',
loader: {
'.ts': 'ts',
'.tsx': 'tsx',
'.js': 'js',
'.jsx': 'jsx',
'.json': 'json',
},
});
const code = result.outputFiles[0].text;
const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString('base64')}`;
return import(dataUrl);
}

function makeResult(index, overrides = {}) {
const parentPath = `/Users/test/Documents/project-${String(index % 30).padStart(2, '0')}`;
const name = `alpha-file-${String(index).padStart(4, '0')}.txt`;
const filePath = `${parentPath}/${name}`;
return {
path: filePath,
name,
parentPath,
displayPath: `~/Documents/project-${String(index % 30).padStart(2, '0')}`,
isDirectory: false,
mtimeMs: Date.now() - (index % 24) * 60 * 60 * 1000,
birthtimeMs: Date.now() - (index % 24) * 60 * 60 * 1000,
topLevelRoot: 'Documents',
homeRelativeDepth: 3,
depth: 3,
noisyPathSegmentCount: 0,
...overrides,
};
}

function makeCommand(result, index, overrides = {}) {
return {
id: `system-file-result:${index}`,
title: result.name,
subtitle: result.displayPath,
keywords: [result.name, result.parentPath, result.displayPath],
category: 'system',
path: result.path,
...overrides,
};
}

function candidateSignature(candidates) {
return candidates.map((candidate) => [
candidate.stableKey,
candidate.label,
candidate.pathOrUrl,
candidate.matchKind,
candidate.matchScore,
candidate.subtype,
candidate.command.id,
candidate.command.title,
candidate.command.path,
candidate.finalScore,
]);
}

function checksumCandidates(candidates) {
let checksum = candidates.length;
for (const candidate of candidates) {
checksum += candidate.command.id.length;
checksum += candidate.stableKey.length;
checksum += Math.round(candidate.finalScore);
}
return checksum;
}

function stats(values) {
const sorted = [...values].sort((a, b) => a - b);
return {
min: Number(sorted[0].toFixed(3)),
median: Number(sorted[Math.floor(sorted.length / 2)].toFixed(3)),
max: Number(sorted[sorted.length - 1].toFixed(3)),
};
}

function measure(iterations, fn) {
const times = [];
let checksum = 0;
for (let index = 0; index < iterations; index += 1) {
const started = performance.now();
checksum += checksumCandidates(fn());
times.push(performance.now() - started);
}
return { ...stats(times), checksum };
}

test('launcher file command map preserves first path match and result order', () => {
const duplicatePath = '/Users/test/Documents/alpha-duplicate.txt';
const duplicateResult = makeResult(1, {
path: duplicatePath,
name: 'alpha-duplicate.txt',
parentPath: '/Users/test/Documents',
displayPath: '~/Documents',
});
const missingCommandResult = makeResult(2, {
path: '/Users/test/Documents/alpha-missing.txt',
name: 'alpha-missing.txt',
parentPath: '/Users/test/Documents',
displayPath: '~/Documents',
});
const tailResult = makeResult(3, {
path: '/Users/test/Documents/alpha-tail.txt',
name: 'alpha-tail.txt',
parentPath: '/Users/test/Documents',
displayPath: '~/Documents',
});

const firstDuplicateCommand = makeCommand(duplicateResult, 1, { title: 'First duplicate command' });
const secondDuplicateCommand = makeCommand(duplicateResult, 2, { title: 'Second duplicate command' });
const tailCommand = makeCommand(tailResult, 3, { title: 'Tail command' });
const commandByPath = buildLauncherFileResultCommandByPath([
firstDuplicateCommand,
secondDuplicateCommand,
tailCommand,
]);

assert.equal(commandByPath.get(duplicatePath), firstDuplicateCommand);

const candidates = buildLauncherFileCandidates({
launcherFileResults: [duplicateResult, missingCommandResult, tailResult],
fileResultCommandByPath: commandByPath,
searchQuery: 'alpha',
rootSearchRanking: {},
});

assert.deepEqual(
candidates.map((candidate) => candidate.command.title),
['First duplicate command', 'Tail command']
);
assert.deepEqual(
candidates.map((candidate) => candidate.pathOrUrl),
[duplicatePath, tailResult.path]
);
});

test('launcher file candidate assembly handles 3000 results with path-map lookup', () => {
const count = 3000;
const iterations = 25;
const launcherFileResults = Array.from({ length: count }, (_, index) => makeResult(index));
const fileResultCommands = launcherFileResults.map((result, index) => makeCommand(result, index));
const fileResultCommandByPath = buildLauncherFileResultCommandByPath(fileResultCommands);
const findBackedLookup = {
get(filePath) {
return fileResultCommands.find((command) => command.path === filePath);
},
};

const input = {
launcherFileResults,
searchQuery: 'alpha',
rootSearchRanking: {},
};
const mapCandidates = buildLauncherFileCandidates({
...input,
fileResultCommandByPath,
});
const findCandidates = buildLauncherFileCandidates({
...input,
fileResultCommandByPath: findBackedLookup,
});

assert.equal(mapCandidates.length, count);
assert.deepEqual(candidateSignature(mapCandidates), candidateSignature(findCandidates));

const mapStats = measure(iterations, () => buildLauncherFileCandidates({
...input,
fileResultCommandByPath,
}));
const findStats = measure(iterations, () => buildLauncherFileCandidates({
...input,
fileResultCommandByPath: findBackedLookup,
}));

assert.equal(mapStats.checksum, findStats.checksum);
assert.ok(
mapStats.median < findStats.median,
`expected path-map assembly median ${mapStats.median}ms to be below find-backed median ${findStats.median}ms`
);

console.log('Launcher file candidate assembly benchmark', JSON.stringify({
count,
iterations,
mapStats,
findStats,
speedup: Number((findStats.median / mapStats.median).toFixed(2)),
}));
});
103 changes: 16 additions & 87 deletions src/renderer/src/hooks/useLauncherCommandModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import {
rankRootSearchCandidates,
scoreRootSearchCandidate,
scoreRootSearchFields,
type MatchKind,
type RootSearchCandidate,
type RootSearchRankingState,
type RootSearchSubtype,
Expand All @@ -52,6 +51,11 @@ import {
assembleRootSearchSections,
isRootResultPromotionCandidate,
} from '../utils/root-search-sections';
import {
buildLauncherFileCandidates,
buildLauncherFileResultCommandByPath,
coerceRootSearchMatchKind as coerceMatchKind,
} from '../utils/launcher-file-candidates';
import type { LauncherCommandSection } from '../components/LauncherCommandList';

export type GroupedLauncherCommands = {
Expand Down Expand Up @@ -138,53 +142,6 @@ function inferCommandSubtype(command: CommandInfo): RootSearchSubtype {
return 'system-command';
}

function coerceMatchKind(value: string | undefined, fallback: MatchKind): MatchKind {
switch (value) {
case 'exact':
case 'alias-exact':
case 'nickname-exact':
case 'prefix':
case 'token-prefix':
case 'compact-prefix':
case 'word-boundary-fuzzy':
case 'contains':
case 'subsequence':
case 'description':
case 'path':
case 'url':
return value;
default:
return fallback;
}
}

function getFileFreshnessBoost(result: IndexedFileSearchResult): number {
const touched = Math.max(Number(result.mtimeMs || 0), Number(result.birthtimeMs || 0));
if (!touched) return 0;
const ageHours = Math.max(0, (Date.now() - touched) / (60 * 60 * 1000));
const ageDays = ageHours / 24;
if (ageHours <= 24) return 120;
if (ageDays <= 7) return 90;
if (ageDays <= 30) return 45;
if (ageDays <= 90) return 15;
return 0;
}

function getFileLocationBoost(result: IndexedFileSearchResult): number {
const topLevelRoot = String(result.topLevelRoot || '').trim();
const depth = Number(result.homeRelativeDepth || result.depth || 0);
const protectedRoot = topLevelRoot === 'Desktop' || topLevelRoot === 'Documents' || topLevelRoot === 'Downloads';
if (!protectedRoot) return 20;
return 120 - Math.min(90, Math.max(0, depth - 2) * 18);
}

function getFileDepthPenalty(result: IndexedFileSearchResult): number {
const depth = Math.max(0, Number(result.homeRelativeDepth || result.depth || 0));
if (depth <= 2) return 0;
if (depth <= 4) return (depth - 2) * 25;
return Math.min(260, 50 + (depth - 4) * 35);
}

type BrowserLauncherProfile = {
id?: string;
browserId?: BrowserSearchSource | string;
Expand Down Expand Up @@ -391,6 +348,10 @@ export function useLauncherCommandModel({
}),
[launcherFileResults, launcherFileIcons, homeDir]
);
const fileResultCommandByPath = useMemo(
() => buildLauncherFileResultCommandByPath(fileResultCommands),
[fileResultCommands]
);

const pinnedFileCommands = useMemo<CommandInfo[]>(
() =>
Expand Down Expand Up @@ -565,45 +526,13 @@ export function useLauncherCommandModel({

const fileCandidates = useMemo<RootSearchCandidate[]>(() => {
if (!hasSearchQuery || aiMode || rootBangState.mode !== 'none') return [];
return launcherFileResults
.map((result) => {
const command = fileResultCommands.find((item) => item.path === result.path);
if (!command) return null;
const scored = scoreRootSearchFields(searchQuery, [
{ value: result.name, kind: 'label', weight: 1 },
{ value: result.parentPath, kind: 'path', weight: 0.72 },
{ value: result.displayPath, kind: 'path', weight: 0.72 },
{ value: result.path, kind: 'path', weight: 0.68 },
]);
if (!scored.matched) return null;
const subtype: RootSearchSubtype = result.isDirectory ? 'folder' : 'file';
const matchKind = coerceMatchKind(result.matchKind, scored.matchKind);
const weakFolderMatch = subtype === 'folder' && (matchKind === 'contains' || matchKind === 'subsequence' || matchKind === 'path');
const stableKey = `file:${normalizeRootSearchStableValue(result.path)}`;
return scoreRootSearchCandidate({
command: {
...command,
rootSearchStableKey: stableKey,
rootSearchSource: 'file',
rootSearchSubtype: subtype,
},
source: 'file',
subtype,
stableKey,
label: result.name,
description: result.displayPath,
pathOrUrl: result.path,
matchKind,
matchScore: scored.matchScore,
sourceQualityBoost: subtype === 'file' ? 8 : weakFolderMatch ? -10 : 0,
freshnessBoost: getFileFreshnessBoost(result),
pathLocationBoost: getFileLocationBoost(result),
noisePenalty: Math.max(0, Number(result.noisyPathSegmentCount || 0)) * 70,
depthPenalty: getFileDepthPenalty(result),
}, searchQuery, rootSearchRanking);
})
.filter((candidate): candidate is RootSearchCandidate => Boolean(candidate));
}, [hasSearchQuery, aiMode, rootBangState, launcherFileResults, fileResultCommands, searchQuery, rootSearchRanking]);
return buildLauncherFileCandidates({
launcherFileResults,
fileResultCommandByPath,
searchQuery,
rootSearchRanking,
});
}, [hasSearchQuery, aiMode, rootBangState, launcherFileResults, fileResultCommandByPath, searchQuery, rootSearchRanking]);

const browserCandidates = useMemo<RootSearchCandidate[]>(() => {
if (!browserSearch.enabled || !browserSearch.alphaChromiumRootSearchEnabled || !hasSearchQuery || aiMode || rootBangState.mode !== 'none') return [];
Expand Down
Loading
Loading