diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index 13150f9..82b6d2f 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -5,6 +5,7 @@ import { Virtuoso } from "react-virtuoso" import React, { memo, useMemo, useState, useEffect } from "react" import { Fzf } from "fzf" import { formatLargeNumber } from "../../utils/format" +import { highlightFzfMatch } from "../../utils/highlight" type HistoryViewProps = { onDone: () => void @@ -464,49 +465,4 @@ const ExportButton = ({ itemId }: { itemId: string }) => ( ) -const highlightFzfMatch = (text: string, positions: number[], highlightClassName: string = "history-item-highlight") => { - if (!positions.length) return text - - const parts: { text: string; highlight: boolean }[] = [] - let lastIndex = 0 - - // Sort positions to ensure we process them in order - positions.sort((a, b) => a - b) - - positions.forEach((pos) => { - // Add non-highlighted text before this position - if (pos > lastIndex) { - parts.push({ - text: text.substring(lastIndex, pos), - highlight: false - }) - } - - // Add highlighted character - parts.push({ - text: text[pos], - highlight: true - }) - - lastIndex = pos + 1 - }) - - // Add any remaining text - if (lastIndex < text.length) { - parts.push({ - text: text.substring(lastIndex), - highlight: false - }) - } - - // Build final string - return parts - .map(part => - part.highlight - ? `${part.text}` - : part.text - ) - .join('') -} - export default memo(HistoryView) diff --git a/webview-ui/src/components/settings/GlamaModelPicker.tsx b/webview-ui/src/components/settings/GlamaModelPicker.tsx index 5164ba5..de93b9c 100644 --- a/webview-ui/src/components/settings/GlamaModelPicker.tsx +++ b/webview-ui/src/components/settings/GlamaModelPicker.tsx @@ -7,6 +7,7 @@ import styled from "styled-components" import { glamaDefaultModelId } from "../../../../src/shared/api" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" +import { highlightFzfMatch } from "../../utils/highlight" import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions" const GlamaModelPicker: React.FC = () => { @@ -71,51 +72,6 @@ const GlamaModelPicker: React.FC = () => { })) }, [modelIds]) - const highlightFzfMatch = (text: string, positions: number[]) => { - if (!positions.length) return text - - const parts: { text: string; highlight: boolean }[] = [] - let lastIndex = 0 - - // Sort positions to ensure we process them in order - positions.sort((a, b) => a - b) - - positions.forEach((pos) => { - // Add non-highlighted text before this position - if (pos > lastIndex) { - parts.push({ - text: text.substring(lastIndex, pos), - highlight: false - }) - } - - // Add highlighted character - parts.push({ - text: text[pos], - highlight: true - }) - - lastIndex = pos + 1 - }) - - // Add any remaining text - if (lastIndex < text.length) { - parts.push({ - text: text.substring(lastIndex), - highlight: false - }) - } - - // Build final string - return parts - .map(part => - part.highlight - ? `${part.text}` - : part.text - ) - .join('') - } - const fzf = useMemo(() => { return new Fzf(searchableItems, { selector: item => item.html @@ -128,7 +84,7 @@ const GlamaModelPicker: React.FC = () => { const searchResults = fzf.find(searchTerm) return searchResults.map(result => ({ ...result.item, - html: highlightFzfMatch(result.item.html, Array.from(result.positions)) + html: highlightFzfMatch(result.item.html, Array.from(result.positions), "model-item-highlight") })) }, [searchableItems, searchTerm, fzf]) diff --git a/webview-ui/src/components/settings/OpenAiModelPicker.tsx b/webview-ui/src/components/settings/OpenAiModelPicker.tsx index 2e674dd..7e8a81f 100644 --- a/webview-ui/src/components/settings/OpenAiModelPicker.tsx +++ b/webview-ui/src/components/settings/OpenAiModelPicker.tsx @@ -5,6 +5,7 @@ import { useRemark } from "react-remark" import styled from "styled-components" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" +import { highlightFzfMatch } from "../../utils/highlight" const OpenAiModelPicker: React.FC = () => { const { apiConfiguration, setApiConfiguration, openAiModels, onUpdateApiConfig } = useExtensionState() @@ -70,51 +71,6 @@ const OpenAiModelPicker: React.FC = () => { })) }, [modelIds]) - const highlightFzfMatch = (text: string, positions: number[]) => { - if (!positions.length) return text - - const parts: { text: string; highlight: boolean }[] = [] - let lastIndex = 0 - - // Sort positions to ensure we process them in order - positions.sort((a, b) => a - b) - - positions.forEach((pos) => { - // Add non-highlighted text before this position - if (pos > lastIndex) { - parts.push({ - text: text.substring(lastIndex, pos), - highlight: false - }) - } - - // Add highlighted character - parts.push({ - text: text[pos], - highlight: true - }) - - lastIndex = pos + 1 - }) - - // Add any remaining text - if (lastIndex < text.length) { - parts.push({ - text: text.substring(lastIndex), - highlight: false - }) - } - - // Build final string - return parts - .map(part => - part.highlight - ? `${part.text}` - : part.text - ) - .join('') - } - const fzf = useMemo(() => { return new Fzf(searchableItems, { selector: item => item.html @@ -127,7 +83,7 @@ const OpenAiModelPicker: React.FC = () => { const searchResults = fzf.find(searchTerm) return searchResults.map(result => ({ ...result.item, - html: highlightFzfMatch(result.item.html, Array.from(result.positions)) + html: highlightFzfMatch(result.item.html, Array.from(result.positions), "model-item-highlight") })) }, [searchableItems, searchTerm, fzf]) diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx index 13e308d..f164fb3 100644 --- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx +++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx @@ -7,6 +7,7 @@ import styled from "styled-components" import { openRouterDefaultModelId } from "../../../../src/shared/api" import { useExtensionState } from "../../context/ExtensionStateContext" import { vscode } from "../../utils/vscode" +import { highlightFzfMatch } from "../../utils/highlight" import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions" const OpenRouterModelPicker: React.FC = () => { @@ -70,51 +71,6 @@ const OpenRouterModelPicker: React.FC = () => { })) }, [modelIds]) - const highlightFzfMatch = (text: string, positions: number[]) => { - if (!positions.length) return text - - const parts: { text: string; highlight: boolean }[] = [] - let lastIndex = 0 - - // Sort positions to ensure we process them in order - positions.sort((a, b) => a - b) - - positions.forEach((pos) => { - // Add non-highlighted text before this position - if (pos > lastIndex) { - parts.push({ - text: text.substring(lastIndex, pos), - highlight: false - }) - } - - // Add highlighted character - parts.push({ - text: text[pos], - highlight: true - }) - - lastIndex = pos + 1 - }) - - // Add any remaining text - if (lastIndex < text.length) { - parts.push({ - text: text.substring(lastIndex), - highlight: false - }) - } - - // Build final string - return parts - .map(part => - part.highlight - ? `${part.text}` - : part.text - ) - .join('') - } - const fzf = useMemo(() => { return new Fzf(searchableItems, { selector: item => item.html @@ -127,7 +83,7 @@ const OpenRouterModelPicker: React.FC = () => { const searchResults = fzf.find(searchTerm) return searchResults.map(result => ({ ...result.item, - html: highlightFzfMatch(result.item.html, Array.from(result.positions)) + html: highlightFzfMatch(result.item.html, Array.from(result.positions), "model-item-highlight") })) }, [searchableItems, searchTerm, fzf]) diff --git a/webview-ui/src/utils/highlight.ts b/webview-ui/src/utils/highlight.ts new file mode 100644 index 0000000..a6bbf76 --- /dev/null +++ b/webview-ui/src/utils/highlight.ts @@ -0,0 +1,44 @@ +export function highlightFzfMatch(text: string, positions: number[], highlightClassName: string = "history-item-highlight") { + if (!positions.length) return text + + const parts: { text: string; highlight: boolean }[] = [] + let lastIndex = 0 + + // Sort positions to ensure we process them in order + positions.sort((a, b) => a - b) + + positions.forEach((pos) => { + // Add non-highlighted text before this position + if (pos > lastIndex) { + parts.push({ + text: text.substring(lastIndex, pos), + highlight: false + }) + } + + // Add highlighted character + parts.push({ + text: text[pos], + highlight: true + }) + + lastIndex = pos + 1 + }) + + // Add any remaining text + if (lastIndex < text.length) { + parts.push({ + text: text.substring(lastIndex), + highlight: false + }) + } + + // Build final string + return parts + .map(part => + part.highlight + ? `${part.text}` + : part.text + ) + .join('') +} \ No newline at end of file