Skip to content

Commit 42c1873

Browse files
committed
feat: implement settings search functionality with dropdown and highlighting
- Add SearchHighlight component for highlighting search terms - Add SettingsSearchDropdown component with search input and results - Add search utilities for filtering and highlighting settings - Update all settings components to support search highlighting - Add comprehensive test coverage for search functionality - Update localization files for search feature across all languages - Integrate search functionality into main SettingsView
1 parent 1e17b3b commit 42c1873

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3002
-900
lines changed

webview-ui/src/components/settings/About.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro
6262
</SectionHeader>
6363

6464
<Section>
65-
<div>
65+
<div data-setting-id="telemetrySetting">
6666
<VSCodeCheckbox
6767
checked={telemetrySetting === "enabled"}
6868
onChange={(e: any) => {

webview-ui/src/components/settings/AutoApproveSettings.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export const AutoApproveSettings = ({
161161
<span className="codicon codicon-eye" />
162162
<div>{t("settings:autoApprove.readOnly.label")}</div>
163163
</div>
164-
<div>
164+
<div data-setting-id="alwaysAllowReadOnlyOutsideWorkspace">
165165
<VSCodeCheckbox
166166
checked={alwaysAllowReadOnlyOutsideWorkspace}
167167
onChange={(e: any) =>
@@ -185,7 +185,7 @@ export const AutoApproveSettings = ({
185185
<span className="codicon codicon-edit" />
186186
<div>{t("settings:autoApprove.write.label")}</div>
187187
</div>
188-
<div>
188+
<div data-setting-id="alwaysAllowWriteOutsideWorkspace">
189189
<VSCodeCheckbox
190190
checked={alwaysAllowWriteOutsideWorkspace}
191191
onChange={(e: any) =>
@@ -200,7 +200,7 @@ export const AutoApproveSettings = ({
200200
{t("settings:autoApprove.write.outsideWorkspace.description")}
201201
</div>
202202
</div>
203-
<div>
203+
<div data-setting-id="alwaysAllowWriteProtected">
204204
<VSCodeCheckbox
205205
checked={alwaysAllowWriteProtected}
206206
onChange={(e: any) =>
@@ -222,7 +222,7 @@ export const AutoApproveSettings = ({
222222
<span className="codicon codicon-refresh" />
223223
<div>{t("settings:autoApprove.retry.label")}</div>
224224
</div>
225-
<div>
225+
<div data-setting-id="requestDelaySeconds">
226226
<div className="flex items-center gap-2">
227227
<Slider
228228
min={5}
@@ -247,7 +247,7 @@ export const AutoApproveSettings = ({
247247
<span className="codicon codicon-question" />
248248
<div>{t("settings:autoApprove.followupQuestions.label")}</div>
249249
</div>
250-
<div>
250+
<div data-setting-id="followupAutoApproveTimeoutMs">
251251
<div className="flex items-center gap-2">
252252
<Slider
253253
min={1000}
@@ -275,12 +275,12 @@ export const AutoApproveSettings = ({
275275
<div>{t("settings:autoApprove.execute.label")}</div>
276276
</div>
277277

278-
<div>
278+
<div data-setting-id="allowedCommands">
279279
<label className="block font-medium mb-1" data-testid="allowed-commands-heading">
280-
{t("settings:autoApprove.execute.allowedCommands")}
280+
{t("settings:autoApprove.execute.allowedCommands.label")}
281281
</label>
282282
<div className="text-vscode-descriptionForeground text-sm mt-1">
283-
{t("settings:autoApprove.execute.allowedCommandsDescription")}
283+
{t("settings:autoApprove.execute.allowedCommands.description")}
284284
</div>
285285
</div>
286286

@@ -323,12 +323,12 @@ export const AutoApproveSettings = ({
323323
</div>
324324

325325
{/* Denied Commands Section */}
326-
<div className="mt-6">
326+
<div className="mt-6" data-setting-id="deniedCommands">
327327
<label className="block font-medium mb-1" data-testid="denied-commands-heading">
328-
{t("settings:autoApprove.execute.deniedCommands")}
328+
{t("settings:autoApprove.execute.deniedCommands.label")}
329329
</label>
330330
<div className="text-vscode-descriptionForeground text-sm mt-1">
331-
{t("settings:autoApprove.execute.deniedCommandsDescription")}
331+
{t("settings:autoApprove.execute.deniedCommands.description")}
332332
</div>
333333
</div>
334334

webview-ui/src/components/settings/AutoApproveToggle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export const AutoApproveToggle = ({ onToggle, ...props }: AutoApproveToggleProps
124124
aria-label={t(labelKey)}
125125
aria-pressed={!!props[key]}
126126
data-testid={testId}
127+
data-setting-id={key}
127128
className={cn(" aspect-square h-[80px]", !props[key] && "opacity-50")}>
128129
<span className={cn("flex flex-col items-center gap-1")}>
129130
<span className={`codicon codicon-${icon}`} />

webview-ui/src/components/settings/BrowserSettings.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const BrowserSettings = ({
107107
</SectionHeader>
108108

109109
<Section>
110-
<div>
110+
<div data-setting-id="browserToolEnabled">
111111
<VSCodeCheckbox
112112
checked={browserToolEnabled}
113113
onChange={(e: any) => setCachedStateField("browserToolEnabled", e.target.checked)}>
@@ -126,7 +126,7 @@ export const BrowserSettings = ({
126126

127127
{browserToolEnabled && (
128128
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
129-
<div>
129+
<div data-setting-id="browserViewportSize">
130130
<label className="block font-medium mb-1">{t("settings:browser.viewport.label")}</label>
131131
<Select
132132
value={browserViewportSize}
@@ -149,7 +149,7 @@ export const BrowserSettings = ({
149149
</div>
150150
</div>
151151

152-
<div>
152+
<div data-setting-id="screenshotQuality">
153153
<label className="block font-medium mb-1">
154154
{t("settings:browser.screenshotQuality.label")}
155155
</label>
@@ -168,7 +168,7 @@ export const BrowserSettings = ({
168168
</div>
169169
</div>
170170

171-
<div>
171+
<div data-setting-id="remoteBrowserEnabled">
172172
<VSCodeCheckbox
173173
checked={remoteBrowserEnabled}
174174
onChange={(e: any) => {
@@ -189,7 +189,7 @@ export const BrowserSettings = ({
189189

190190
{remoteBrowserEnabled && (
191191
<>
192-
<div className="flex items-center gap-2">
192+
<div className="flex items-center gap-2" data-setting-id="remoteBrowserHost">
193193
<VSCodeTextField
194194
value={remoteBrowserHost ?? ""}
195195
onChange={(e: any) =>

webview-ui/src/components/settings/CheckpointSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const CheckpointSettings = ({ enableCheckpoints, setCachedStateField, ...
2626
</SectionHeader>
2727

2828
<Section>
29-
<div>
29+
<div data-setting-id="enableCheckpoints">
3030
<VSCodeCheckbox
3131
checked={enableCheckpoints}
3232
onChange={(e: any) => {

webview-ui/src/components/settings/ContextManagementSettings.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export const ContextManagementSettings = ({
9898
</SectionHeader>
9999

100100
<Section>
101-
<div>
101+
<div data-setting-id="maxOpenTabsContext">
102102
<span className="block font-medium mb-1">{t("settings:contextManagement.openTabs.label")}</span>
103103
<div className="flex items-center gap-2">
104104
<Slider
@@ -116,7 +116,7 @@ export const ContextManagementSettings = ({
116116
</div>
117117
</div>
118118

119-
<div>
119+
<div data-setting-id="maxWorkspaceFiles">
120120
<span className="block font-medium mb-1">
121121
{t("settings:contextManagement.workspaceFiles.label")}
122122
</span>
@@ -136,7 +136,7 @@ export const ContextManagementSettings = ({
136136
</div>
137137
</div>
138138

139-
<div>
139+
<div data-setting-id="maxConcurrentFileReads">
140140
<span className="block font-medium mb-1">
141141
{t("settings:contextManagement.maxConcurrentFileReads.label")}
142142
</span>
@@ -156,7 +156,7 @@ export const ContextManagementSettings = ({
156156
</div>
157157
</div>
158158

159-
<div>
159+
<div data-setting-id="showRooIgnoredFiles">
160160
<VSCodeCheckbox
161161
checked={showRooIgnoredFiles}
162162
onChange={(e: any) => setCachedStateField("showRooIgnoredFiles", e.target.checked)}
@@ -170,7 +170,7 @@ export const ContextManagementSettings = ({
170170
</div>
171171
</div>
172172

173-
<div>
173+
<div data-setting-id="maxReadFileLine">
174174
<div className="flex flex-col gap-2">
175175
<span className="font-medium">{t("settings:contextManagement.maxReadFile.label")}</span>
176176
<div className="flex items-center gap-4">
@@ -296,19 +296,21 @@ export const ContextManagementSettings = ({
296296
</div>
297297
</Section>
298298
<Section className="pt-2">
299-
<VSCodeCheckbox
300-
checked={autoCondenseContext}
301-
onChange={(e: any) => setCachedStateField("autoCondenseContext", e.target.checked)}
302-
data-testid="auto-condense-context-checkbox">
303-
<span className="font-medium">{t("settings:contextManagement.autoCondenseContext.name")}</span>
304-
</VSCodeCheckbox>
299+
<div data-setting-id="autoCondenseContext">
300+
<VSCodeCheckbox
301+
checked={autoCondenseContext}
302+
onChange={(e: any) => setCachedStateField("autoCondenseContext", e.target.checked)}
303+
data-testid="auto-condense-context-checkbox">
304+
<span className="font-medium">{t("settings:contextManagement.autoCondenseContext.name")}</span>
305+
</VSCodeCheckbox>
306+
</div>
305307
{autoCondenseContext && (
306308
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
307309
<div className="flex items-center gap-4 font-bold">
308310
<FoldVertical size={16} />
309311
<div>{t("settings:contextManagement.condensingThreshold.label")}</div>
310312
</div>
311-
<div>
313+
<div data-setting-id="condensingApiConfigId">
312314
<Select
313315
value={selectedThresholdProfile || "default"}
314316
onValueChange={(value) => {
@@ -353,7 +355,7 @@ export const ContextManagementSettings = ({
353355
</div>
354356

355357
{/* Threshold Slider */}
356-
<div>
358+
<div data-setting-id="autoCondenseContextPercent">
357359
<div className="flex items-center gap-2">
358360
<Slider
359361
min={10}

webview-ui/src/components/settings/LanguageSettings.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,25 @@ export const LanguageSettings = ({ language, setCachedStateField, className, ...
3131
</SectionHeader>
3232

3333
<Section>
34-
<Select value={language} onValueChange={(value) => setCachedStateField("language", value as Language)}>
35-
<SelectTrigger className="w-full">
36-
<SelectValue placeholder={t("settings:common.select")} />
37-
</SelectTrigger>
38-
<SelectContent>
39-
<SelectGroup>
40-
{Object.entries(LANGUAGES).map(([code, name]) => (
41-
<SelectItem key={code} value={code}>
42-
{name}
43-
<span className="text-muted-foreground">({code})</span>
44-
</SelectItem>
45-
))}
46-
</SelectGroup>
47-
</SelectContent>
48-
</Select>
34+
<div data-setting-id="language">
35+
<Select
36+
value={language}
37+
onValueChange={(value) => setCachedStateField("language", value as Language)}>
38+
<SelectTrigger className="w-full">
39+
<SelectValue placeholder={t("settings:common.select")} />
40+
</SelectTrigger>
41+
<SelectContent>
42+
<SelectGroup>
43+
{Object.entries(LANGUAGES).map(([code, name]) => (
44+
<SelectItem key={code} value={code}>
45+
{name}
46+
<span className="text-muted-foreground">({code})</span>
47+
</SelectItem>
48+
))}
49+
</SelectGroup>
50+
</SelectContent>
51+
</Select>
52+
</div>
4953
</Section>
5054
</div>
5155
)

webview-ui/src/components/settings/NotificationSettings.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const NotificationSettings = ({
3535
</SectionHeader>
3636

3737
<Section>
38-
<div>
38+
<div data-setting-id="ttsEnabled">
3939
<VSCodeCheckbox
4040
checked={ttsEnabled}
4141
onChange={(e: any) => setCachedStateField("ttsEnabled", e.target.checked)}
@@ -49,7 +49,7 @@ export const NotificationSettings = ({
4949

5050
{ttsEnabled && (
5151
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
52-
<div>
52+
<div data-setting-id="ttsSpeed">
5353
<label className="block font-medium mb-1">
5454
{t("settings:notifications.tts.speedLabel")}
5555
</label>
@@ -68,7 +68,7 @@ export const NotificationSettings = ({
6868
</div>
6969
)}
7070

71-
<div>
71+
<div data-setting-id="soundEnabled">
7272
<VSCodeCheckbox
7373
checked={soundEnabled}
7474
onChange={(e: any) => setCachedStateField("soundEnabled", e.target.checked)}
@@ -82,7 +82,7 @@ export const NotificationSettings = ({
8282

8383
{soundEnabled && (
8484
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
85-
<div>
85+
<div data-setting-id="soundVolume">
8686
<label className="block font-medium mb-1">
8787
{t("settings:notifications.sound.volumeLabel")}
8888
</label>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from "react"
2+
3+
export const highlightText = (text: string, query: string): React.ReactNode => {
4+
if (!query.trim()) return text
5+
6+
// Escape special regex characters to prevent regex injection
7+
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
8+
const parts = text.split(new RegExp(`(${escapedQuery})`, "gi"))
9+
return parts.map((part, index) =>
10+
part.toLowerCase() === query.toLowerCase() ? (
11+
<mark key={index} className="bg-vscode-editor-findMatchHighlightBackground text-inherit">
12+
{part}
13+
</mark>
14+
) : (
15+
part
16+
),
17+
)
18+
}
19+
20+
interface SearchHighlightProps {
21+
text: string
22+
searchQuery: string
23+
className?: string
24+
}
25+
26+
export const SearchHighlight: React.FC<SearchHighlightProps> = ({ text, searchQuery, className }) => {
27+
return <span className={className}>{highlightText(text, searchQuery)}</span>
28+
}
29+
30+
interface SettingHighlightWrapperProps {
31+
settingId: string
32+
searchQuery: string
33+
matches: Array<{ settingId: string }>
34+
children: React.ReactNode
35+
}
36+
37+
export const SettingHighlightWrapper: React.FC<SettingHighlightWrapperProps> = ({
38+
settingId,
39+
searchQuery,
40+
matches,
41+
children,
42+
}) => {
43+
const isHighlighted = matches.some((match) => match.settingId === settingId)
44+
45+
if (!searchQuery || !isHighlighted) {
46+
return <>{children}</>
47+
}
48+
49+
return (
50+
<div className="relative">
51+
<div className="absolute -left-2 top-0 bottom-0 w-1 bg-vscode-editor-findMatchHighlightBackground rounded" />
52+
{children}
53+
</div>
54+
)
55+
}

0 commit comments

Comments
 (0)