diff --git a/plugins/local-search-neo/package.json b/plugins/local-search-neo/package.json index 25245ef7..5e4d7670 100644 --- a/plugins/local-search-neo/package.json +++ b/plugins/local-search-neo/package.json @@ -1,6 +1,6 @@ { "name": "local-search-neo", - "version": "1.0.0", + "version": "1.0.1", "description": "借助 Everything 来进行文件搜索", "type": "module", "scripts": { diff --git a/plugins/local-search-neo/public/plugin.json b/plugins/local-search-neo/public/plugin.json index a00af079..88ccf6c7 100644 --- a/plugins/local-search-neo/public/plugin.json +++ b/plugins/local-search-neo/public/plugin.json @@ -4,7 +4,7 @@ "author": "the_tree", "title": "本地搜索Neo", "description": "借助 Everything 来进行文件搜索", - "version": "1.0.0", + "version": "1.0.1", "main": "index.html", "preload": "preload/services.js", "logo": "logo.png", diff --git a/plugins/local-search-neo/src/App.vue b/plugins/local-search-neo/src/App.vue index 3595ec7e..484a1963 100644 --- a/plugins/local-search-neo/src/App.vue +++ b/plugins/local-search-neo/src/App.vue @@ -3,7 +3,11 @@ import { onMounted } from "vue"; import Finder from "./Finder/index.vue"; import { useFinderEnterAction } from "./Finder/composables/useFinderEnterAction"; import { getFileIconDataUrl, warmUpFileIconCache } from "./Finder/core/fileIconCache"; -import { DEFAULT_CATEGORIES, buildEverythingQuery } from "./Finder/core/finderLogic"; +import { + DEFAULT_CATEGORIES, + buildEverythingQuery, + mergeResultsByMatchPathPriority, +} from "./Finder/core/finderLogic"; import { useFinderCategories } from "./Finder/composables/useFinderCategories"; import { usePersistStorage } from "./Finder/composables/usePersistStorage"; import { useSubInput } from "./Finder/composables/useSubInput"; @@ -47,23 +51,39 @@ onMounted(() => { try { if (!window.services.everything.isAvailable()) return []; if (window.services.everything.getStartupStatus().state !== "ready") return []; - const result = window.services.everything.query( + const nameResult = window.services.everything.query( everythingQuery, MAIN_PUSH_RESULT_LIMIT, "modified-desc", - matchPathEnabled.value, + false, ); - const items: MainPushSearchResult[] = result.items.map((item) => ({ - title: item.path ?? getParentPath(item.fullPath), - text: item.name, - icon: window.ztools.getFileIcon(item.fullPath), - fullPath: item.fullPath, - })); + const matchPathResult = matchPathEnabled.value + ? window.services.everything.query( + everythingQuery, + MAIN_PUSH_RESULT_LIMIT, + "modified-desc", + true, + ) + : undefined; - if (result.total > MAIN_PUSH_RESULT_LIMIT) { + const resultItems = matchPathResult + ? mergeResultsByMatchPathPriority(nameResult.items, matchPathResult.items) + : nameResult.items; + const total = matchPathResult?.total ?? nameResult.total; + + const items: MainPushSearchResult[] = resultItems + .slice(0, MAIN_PUSH_RESULT_LIMIT) + .map((item) => ({ + title: item.path ?? (item.fullPath ? getParentPath(item.fullPath) : ""), + text: item.name, + icon: item.fullPath ? window.ztools.getFileIcon(item.fullPath) : undefined, + fullPath: item.fullPath, + })); + + if (total > MAIN_PUSH_RESULT_LIMIT) { items.pop(); items.push({ - text: `共有${result.total}个结果,查看更多...`, + text: `共有${total}个结果,查看更多...`, }); } diff --git a/plugins/local-search-neo/src/Finder/composables/useFinderSearch.ts b/plugins/local-search-neo/src/Finder/composables/useFinderSearch.ts index 34e8981c..560f51ef 100644 --- a/plugins/local-search-neo/src/Finder/composables/useFinderSearch.ts +++ b/plugins/local-search-neo/src/Finder/composables/useFinderSearch.ts @@ -3,6 +3,7 @@ import { getNextSelectedPath, getNextVisibleCount, getRestoredSelectedPath, + mergeResultsByMatchPathPriority, type FinderResult, type FinderSortMode, } from "../core/finderLogic"; @@ -76,14 +77,29 @@ export function useFinderSearch({ visibleCount.value = pageSize; try { - const result = window.services.everything.query( + const nameResult = window.services.everything.query( everythingQuery, maxResults, currentSortMode, - currentMatchPathEnabled, + false, ); - results.value = result.items; - everythingTotal.value = result.total; + + if (currentMatchPathEnabled) { + const matchPathResult = window.services.everything.query( + everythingQuery, + maxResults, + currentSortMode, + true, + ); + results.value = mergeResultsByMatchPathPriority( + nameResult.items, + matchPathResult.items, + ).slice(0, maxResults); + everythingTotal.value = matchPathResult.total; + } else { + results.value = nameResult.items; + everythingTotal.value = nameResult.total; + } updateResultStatus(); restoreSelection(options); } catch (error: unknown) { diff --git a/plugins/local-search-neo/src/Finder/composables/useSubInput.ts b/plugins/local-search-neo/src/Finder/composables/useSubInput.ts index 2dd350d8..c1f28f1e 100644 --- a/plugins/local-search-neo/src/Finder/composables/useSubInput.ts +++ b/plugins/local-search-neo/src/Finder/composables/useSubInput.ts @@ -22,7 +22,6 @@ export function useSubInput({ onInput, placeholder = "全盘搜索" }: UseSubInp if (onInput) { inputListeners.delete(onInput); } - disposeSubInput(); }); function bindSubInput() { @@ -46,6 +45,7 @@ export function useSubInput({ onInput, placeholder = "全盘搜索" }: UseSubInp subInputReady = true; } + /** 当非用户操作需要修改子输入框的值, 不触发重新搜索 */ function syncSubInputValue() { if (!subInputReady) return; programmaticInputValue = queryText.value; @@ -56,19 +56,10 @@ export function useSubInput({ onInput, placeholder = "全盘搜索" }: UseSubInp window.ztools.subInputFocus(); } - function disposeSubInput() { - if (!subInputReady) return; - - window.ztools.removeSubInput(); - subInputReady = false; - programmaticInputValue = undefined; - } - return { bindSubInput, syncSubInputValue, focusSubInput, - disposeSubInput, }; } diff --git a/plugins/local-search-neo/src/Finder/core/finderLogic.test.ts b/plugins/local-search-neo/src/Finder/core/finderLogic.test.ts index a953b5a4..63b0571f 100644 --- a/plugins/local-search-neo/src/Finder/core/finderLogic.test.ts +++ b/plugins/local-search-neo/src/Finder/core/finderLogic.test.ts @@ -17,6 +17,7 @@ import { isPdfPreviewCandidate, isTextPreviewCandidate, isVideoPreviewCandidate, + mergeResultsByMatchPathPriority, type FinderCategory, type FinderResult, } from "./finderLogic"; @@ -105,6 +106,30 @@ test("getRestoredSelectedPath keeps existing visible selection or picks sorted f assert.equal(getRestoredSelectedPath([], "D:\\missing.txt"), ""); }); +test("mergeResultsByMatchPathPriority keeps name matches first and removes duplicates", () => { + const nameResults: FinderResult[] = [ + { name: "name-a.txt", path: "C:\\demo", fullPath: "C:\\demo\\name-a.txt" }, + { name: "shared.txt", path: "C:\\demo", fullPath: "C:\\demo\\shared.txt" }, + { name: "name-b.txt", path: "D:\\demo", fullPath: "D:\\demo\\name-b.txt" }, + ]; + const matchPathResults: FinderResult[] = [ + { name: "shared.txt", path: "C:\\demo", fullPath: "c:\\demo\\shared.txt" }, + { name: "path-a.txt", path: "C:\\demo", fullPath: "C:\\demo\\path-a.txt" }, + { name: "path-b.txt", path: "D:\\demo", fullPath: "D:\\demo\\path-b.txt" }, + ]; + + assert.deepEqual( + mergeResultsByMatchPathPriority(nameResults, matchPathResults).map((item) => item.fullPath), + [ + "C:\\demo\\name-a.txt", + "C:\\demo\\shared.txt", + "D:\\demo\\name-b.txt", + "C:\\demo\\path-a.txt", + "D:\\demo\\path-b.txt", + ], + ); +}); + test("preview candidate helpers detect supported file types", () => { assert.equal(isTextPreviewCandidate({ name: "main.log", size: 1024 }), true); assert.equal(isTextPreviewCandidate({ name: "notes.md", size: 1024 }), true); diff --git a/plugins/local-search-neo/src/Finder/core/finderLogic.ts b/plugins/local-search-neo/src/Finder/core/finderLogic.ts index a8bed7f1..33c955bc 100644 --- a/plugins/local-search-neo/src/Finder/core/finderLogic.ts +++ b/plugins/local-search-neo/src/Finder/core/finderLogic.ts @@ -168,6 +168,23 @@ export function getRestoredSelectedPath(results: FinderResult[], currentPath: st return results[0]?.fullPath ?? ""; } +export function mergeResultsByMatchPathPriority< + T extends Pick, +>(nameResults: T[], matchPathResults: T[]): T[] { + const seen = new Set(); + const merged: T[] = []; + + for (const item of [...nameResults, ...matchPathResults]) { + const key = getResultDedupeKey(item); + if (seen.has(key)) continue; + + seen.add(key); + merged.push(item); + } + + return merged; +} + export function isImagePreviewCandidate( file: Pick, ): boolean { @@ -265,6 +282,10 @@ export function formatBytes(bytes?: number): string { return `${formatNumber(value)} ${units[unitIndex]}`; } +function getResultDedupeKey(item: Pick): string { + return (item.fullPath || `${item.path ?? ""}\\${item.name}`).toLowerCase(); +} + function normalizeCategoryRule(rule: string): string { const trimmed = rule.trim(); if (!trimmed) return ""; diff --git a/plugins/local-search-neo/src/Finder/index.vue b/plugins/local-search-neo/src/Finder/index.vue index ed172f76..25d7eb70 100644 --- a/plugins/local-search-neo/src/Finder/index.vue +++ b/plugins/local-search-neo/src/Finder/index.vue @@ -124,8 +124,6 @@ watch( onMounted(() => { bindSubInput(); - syncSubInputValue(); - startEverythingStatusPolling(); void ensureEverythingReady(); window.ztools.onPluginOut(closeTransientState); });