mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add fuzzy search to history view
This commit is contained in:
10
webview-ui/package-lock.json
generated
10
webview-ui/package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fuse.js": "^7.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
@@ -9655,6 +9656,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
|
||||
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
|
||||
"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",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fuse.js": "^7.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useExtensionState } from "../context/ExtensionStateContext"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
import { memo, useMemo, useState } from "react"
|
||||
import Fuse, { FuseResult } from "fuse.js"
|
||||
|
||||
type HistoryViewProps = {
|
||||
onDone: () => void
|
||||
@@ -39,25 +40,23 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
return taskHistory.filter((item) => item.ts && item.task)
|
||||
}, [taskHistory])
|
||||
|
||||
const taskHistorySearchResults = useMemo(() => {
|
||||
return presentableTasks.filter((item) => item.task.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
}, [presentableTasks, searchQuery])
|
||||
const fuse = useMemo(() => {
|
||||
return new Fuse(presentableTasks, {
|
||||
keys: ["task"],
|
||||
threshold: 0.4,
|
||||
shouldSort: true,
|
||||
isCaseSensitive: false,
|
||||
ignoreLocation: true,
|
||||
includeMatches: true,
|
||||
minMatchCharLength: 1,
|
||||
})
|
||||
}, [presentableTasks])
|
||||
|
||||
const highlightText = (text: string, query: string) => {
|
||||
if (!query) return text
|
||||
const parts = text.split(new RegExp(`(${query})`, "gi"))
|
||||
return parts.map((part, index) =>
|
||||
part.toLowerCase() === query.toLowerCase() ? (
|
||||
<mark
|
||||
key={index}
|
||||
style={{ backgroundColor: "var(--vscode-editor-findMatchHighlightBackground)", color: "inherit" }}>
|
||||
{part}
|
||||
</mark>
|
||||
) : (
|
||||
part
|
||||
)
|
||||
)
|
||||
}
|
||||
const taskHistorySearchResults = useMemo(() => {
|
||||
if (!searchQuery) return presentableTasks
|
||||
const searchResults = fuse.search(searchQuery)
|
||||
return highlight(searchResults)
|
||||
}, [presentableTasks, searchQuery, fuse])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -75,6 +74,10 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.history-item-highlight {
|
||||
background-color: var(--vscode-editor-findMatchHighlightBackground);
|
||||
color: inherit;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div
|
||||
@@ -205,9 +208,9 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
{highlightText(item.task, searchQuery)}
|
||||
</div>
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: item.task }}
|
||||
/>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||
<div
|
||||
style={{
|
||||
@@ -364,4 +367,54 @@ const ExportButton = ({ itemId }: { itemId: string }) => (
|
||||
</VSCodeButton>
|
||||
)
|
||||
|
||||
// https://gist.github.com/evenfrost/1ba123656ded32fb7a0cd4651efd4db0
|
||||
const highlight = (fuseSearchResult: FuseResult<any>[], highlightClassName: string = "history-item-highlight") => {
|
||||
const set = (obj: Record<string, any>, path: string, value: any) => {
|
||||
const pathValue = path.split(".")
|
||||
let i: number
|
||||
|
||||
for (i = 0; i < pathValue.length - 1; i++) {
|
||||
obj = obj[pathValue[i]] as Record<string, any>
|
||||
}
|
||||
|
||||
obj[pathValue[i]] = value
|
||||
}
|
||||
|
||||
const generateHighlightedText = (inputText: string, regions: [number, number][] = []) => {
|
||||
let content = ""
|
||||
let nextUnhighlightedRegionStartingIndex = 0
|
||||
|
||||
regions.forEach((region) => {
|
||||
const lastRegionNextIndex = region[1] + 1
|
||||
|
||||
content += [
|
||||
inputText.substring(nextUnhighlightedRegionStartingIndex, region[0]),
|
||||
`<span class="${highlightClassName}">`,
|
||||
inputText.substring(region[0], lastRegionNextIndex),
|
||||
"</span>",
|
||||
].join("")
|
||||
|
||||
nextUnhighlightedRegionStartingIndex = lastRegionNextIndex
|
||||
})
|
||||
|
||||
content += inputText.substring(nextUnhighlightedRegionStartingIndex)
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
return fuseSearchResult
|
||||
.filter(({ matches }) => matches && matches.length)
|
||||
.map(({ item, matches }) => {
|
||||
const highlightedItem = { ...item }
|
||||
|
||||
matches?.forEach((match) => {
|
||||
if (match.key && typeof match.value === "string") {
|
||||
set(highlightedItem, match.key, generateHighlightedText(match.value, [...match.indices]))
|
||||
}
|
||||
})
|
||||
|
||||
return highlightedItem
|
||||
})
|
||||
}
|
||||
|
||||
export default memo(HistoryView)
|
||||
|
||||
Reference in New Issue
Block a user