diff --git a/package-lock.json b/package-lock.json index b09bc95..c233870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "comlink": "^4.4.2", "dagre": "^0.8.5", "dexie": "^4.3.0", + "fuse.js": "^7.1.0", "monaco-editor": "^0.55.1", "react": "^19.2.4", "react-dom": "^19.2.4", @@ -4334,6 +4335,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fuse.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/package.json b/package.json index 865c23d..d58d2b4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "comlink": "^4.4.2", "dagre": "^0.8.5", "dexie": "^4.3.0", + "fuse.js": "^7.1.0", "monaco-editor": "^0.55.1", "react": "^19.2.4", "react-dom": "^19.2.4", diff --git a/src/logic/JarFile.ts b/src/logic/JarFile.ts index 81deba7..68a723c 100644 --- a/src/logic/JarFile.ts +++ b/src/logic/JarFile.ts @@ -1,7 +1,7 @@ -import { BehaviorSubject, combineLatest, distinct, distinctUntilChanged, map, Observable, switchMap, throttleTime } from 'rxjs'; +import { combineLatest, distinctUntilChanged, map, Observable } from 'rxjs'; import { minecraftJar } from './MinecraftApi'; -import { performSearch } from './Search'; import { searchQuery } from './State'; +import Fuse from 'fuse.js'; export const fileList = minecraftJar.pipe( distinctUntilChanged(), @@ -9,21 +9,39 @@ export const fileList = minecraftJar.pipe( ); // File list that only contains outer class files -export const classesList = fileList.pipe( +export const outerClassesList = fileList.pipe( map(files => files.filter(file => file.endsWith('.class') && !file.includes('$'))) ); +export const outerClassSearch = outerClassesList.pipe( + map(classes => { + const list = classes.map(className => { + let simpleClassName = className; + + const pos = className.lastIndexOf('/'); + if (pos !== -1) simpleClassName = className.substring(pos); + + return { 'class': simpleClassName, key: className }; + }); + + return new Fuse(list, { + minMatchCharLength: 3, + keys: ['class'] + }); + }) +); + const debouncedSearchQuery: Observable = searchQuery.pipe( - throttleTime(200), distinctUntilChanged() ); -export const searchResults: Observable = combineLatest([classesList, debouncedSearchQuery]).pipe( - switchMap(([classes, query]) => { - return [performSearch(query, classes)]; +export const searchResults: Observable = combineLatest([outerClassSearch, debouncedSearchQuery]).pipe( + map(([search, query]) => { + const results = search.search(query); + return results.map(r => r.item.key); }) ); export const isSearching = searchQuery.pipe( map((query) => query.length > 0) -); \ No newline at end of file +); diff --git a/src/ui/Code.tsx b/src/ui/Code.tsx index ea78c46..4265367 100644 --- a/src/ui/Code.tsx +++ b/src/ui/Code.tsx @@ -4,7 +4,7 @@ import { currentResult, isDecompiling } from '../logic/Decompiler'; import { useEffect, useRef, useState } from 'react'; import { editor, Range } from "monaco-editor"; import { isThin } from '../logic/Browser'; -import { classesList } from '../logic/JarFile'; +import { outerClassesList } from '../logic/JarFile'; import { getOpenTab } from '../logic/Tabs'; import { message, Spin } from 'antd'; import { LoadingOutlined } from '@ant-design/icons'; @@ -40,7 +40,7 @@ const Code = () => { const monaco = useMonaco(); const decompileResult = useObservable(currentResult); - const classList = useObservable(classesList); + const classList = useObservable(outerClassesList); const editorRef = useRef(null); const hideMinimap = useObservable(isThin); const decompiling = useObservable(isDecompiling); diff --git a/src/ui/EmptyState.tsx b/src/ui/EmptyState.tsx index 457dd2a..3cae811 100644 --- a/src/ui/EmptyState.tsx +++ b/src/ui/EmptyState.tsx @@ -1,7 +1,7 @@ import React from "react"; import { GithubOutlined, SearchOutlined, LinkOutlined, BranchesOutlined, CopyOutlined, CodeOutlined, FileSearchOutlined, AimOutlined } from '@ant-design/icons'; import { Card, Typography, Space, Tooltip, theme } from 'antd'; -import { classesList } from "../logic/JarFile"; +import { outerClassesList } from "../logic/JarFile"; import { openTab } from "../logic/Tabs"; import { useObservable } from "../utils/UseObservable"; @@ -10,7 +10,7 @@ const { useToken } = theme; export const EmptyState = () => { const { token } = useToken(); - const outerClasses = useObservable(classesList); + const outerClasses = useObservable(outerClassesList); const openRandomClass = () => { if (outerClasses && outerClasses.length > 0) { diff --git a/src/ui/FileList.tsx b/src/ui/FileList.tsx index ae61087..6445b28 100644 --- a/src/ui/FileList.tsx +++ b/src/ui/FileList.tsx @@ -3,7 +3,7 @@ import { Tree, Dropdown, message } from 'antd'; import type { TreeDataNode, TreeProps, MenuProps } from 'antd'; import { CaretDownFilled } from '@ant-design/icons'; import { combineLatest, from, map, Observable, shareReplay, switchMap, startWith } from 'rxjs'; -import { classesList } from '../logic/JarFile'; +import { outerClassesList } from '../logic/JarFile'; import { useObservable } from '../utils/UseObservable'; import { useCallback, useEffect, useMemo, useState } from 'react'; import type { Key } from 'antd/es/table/interface'; @@ -30,7 +30,7 @@ const classData: Observable | null> = jarIndex.pipe( ); const fileTree: Observable = combineLatest([ - classesList, + outerClassesList, classData, compactPackages.observable ]).pipe( @@ -231,7 +231,7 @@ const FileList = () => { const jar = useObservable(minecraftJar); const selectedKeys = useObservable(selectedFileKeys); - const classes = useObservable(classesList); + const classes = useObservable(outerClassesList); const onSelect: TreeProps['onSelect'] = useCallback((selectedKeys: Key[]) => { if (selectedKeys.length === 0) return; if (!classes || !classes.includes(selectedKeys[0] as string)) return;