Skip to content

Commit d18601c

Browse files
Search history (#99)
1 parent 60dd3e9 commit d18601c

File tree

13 files changed

+453
-112
lines changed

13 files changed

+453
-112
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added search history to the search bar. ([#99](https://github.com/sourcebot-dev/sourcebot/pull/99))
13+
1014
## [2.5.3] - 2024-11-28
1115

1216
### Added

packages/web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"@radix-ui/react-separator": "^1.1.0",
3636
"@radix-ui/react-slot": "^1.1.0",
3737
"@radix-ui/react-toast": "^1.2.2",
38+
"@radix-ui/react-toggle": "^1.1.0",
39+
"@radix-ui/react-tooltip": "^1.1.4",
3840
"@replit/codemirror-lang-csharp": "^6.2.0",
3941
"@replit/codemirror-vim": "^6.2.1",
4042
"@tanstack/react-query": "^5.53.3",

packages/web/src/app/components/searchBar/searchBar.tsx

Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ import { createTheme } from '@uiw/codemirror-themes';
3232
import CodeMirror, { Annotation, EditorView, KeyBinding, keymap, ReactCodeMirrorRef } from "@uiw/react-codemirror";
3333
import { cva } from "class-variance-authority";
3434
import { useRouter } from "next/navigation";
35-
import { useCallback, useMemo, useRef, useState } from "react";
35+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3636
import { useHotkeys } from 'react-hotkeys-hook';
37-
import { SearchSuggestionsBox, SuggestionMode } from "./searchSuggestionsBox";
37+
import { SearchSuggestionsBox } from "./searchSuggestionsBox";
3838
import { useSuggestionsData } from "./useSuggestionsData";
3939
import { zoekt } from "./zoektLanguageExtension";
40+
import { CounterClockwiseClockIcon } from "@radix-ui/react-icons";
41+
import { useSuggestionModeAndQuery } from "./useSuggestionModeAndQuery";
42+
import { Separator } from "@/components/ui/separator";
43+
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
44+
import { Toggle } from "@/components/ui/toggle";
4045

4146
interface SearchBarProps {
4247
className?: string;
@@ -66,7 +71,7 @@ const searchBarKeymap: readonly KeyBinding[] = ([
6671
] as KeyBinding[]).concat(historyKeymap);
6772

6873
const searchBarContainerVariants = cva(
69-
"search-bar-container flex items-center p-0.5 border rounded-md relative",
74+
"search-bar-container flex items-center py-0.5 px-1 border rounded-md relative",
7075
{
7176
variants: {
7277
size: {
@@ -91,13 +96,12 @@ export const SearchBar = ({
9196
const suggestionBoxRef = useRef<HTMLDivElement>(null);
9297
const editorRef = useRef<ReactCodeMirrorRef>(null);
9398
const [cursorPosition, setCursorPosition] = useState(0);
94-
const [isSuggestionsBoxEnabled, setIsSuggestionsBoxEnabled ] = useState(false);
99+
const [isSuggestionsEnabled, setIsSuggestionsEnabled] = useState(false);
95100
const [isSuggestionsBoxFocused, setIsSuggestionsBoxFocused] = useState(false);
96-
101+
const [isHistorySearchEnabled, setIsHistorySearchEnabled] = useState(false);
102+
97103
const focusEditor = useCallback(() => editorRef.current?.view?.focus(), []);
98104
const focusSuggestionsBox = useCallback(() => suggestionBoxRef.current?.focus(), []);
99-
const [suggestionMode, setSuggestionMode] = useState<SuggestionMode>("refine");
100-
const [suggestionQuery, setSuggestionQuery] = useState("");
101105

102106
const [_query, setQuery] = useState(defaultQuery ?? "");
103107
const query = useMemo(() => {
@@ -106,6 +110,22 @@ export const SearchBar = ({
106110
return _query.replaceAll(/\n/g, " ");
107111
}, [_query]);
108112

113+
// When the user navigates backwards/forwards while on the
114+
// search page (causing the `query` search param to change),
115+
// we want to update what query is displayed in the search bar.
116+
useEffect(() => {
117+
if (defaultQuery) {
118+
setQuery(defaultQuery);
119+
}
120+
}, [defaultQuery])
121+
122+
const { suggestionMode, suggestionQuery } = useSuggestionModeAndQuery({
123+
isSuggestionsEnabled,
124+
isHistorySearchEnabled,
125+
cursorPosition,
126+
query,
127+
});
128+
109129
const suggestionData = useSuggestionsData({
110130
suggestionMode,
111131
suggestionQuery,
@@ -152,7 +172,7 @@ export const SearchBar = ({
152172
useHotkeys('/', (event) => {
153173
event.preventDefault();
154174
focusEditor();
155-
setIsSuggestionsBoxEnabled(true);
175+
setIsSuggestionsEnabled(true);
156176
if (editorRef.current?.view) {
157177
cursorDocEnd({
158178
state: editorRef.current.view.state,
@@ -164,37 +184,40 @@ export const SearchBar = ({
164184
// Collapse the suggestions box if the user clicks outside of the search bar container.
165185
useClickListener('.search-bar-container', (isElementClicked) => {
166186
if (!isElementClicked) {
167-
setIsSuggestionsBoxEnabled(false);
187+
setIsSuggestionsEnabled(false);
168188
} else {
169-
setIsSuggestionsBoxEnabled(true);
189+
setIsSuggestionsEnabled(true);
170190
}
171191
});
172192

173-
const onSubmit = () => {
193+
const onSubmit = useCallback((query: string) => {
194+
setIsSuggestionsEnabled(false);
195+
setIsHistorySearchEnabled(false);
196+
174197
const url = createPathWithQueryParams('/search',
175198
[SearchQueryParams.query, query],
176-
)
199+
);
177200
router.push(url);
178-
}
201+
}, [router]);
179202

180203
return (
181204
<div
182205
className={cn(searchBarContainerVariants({ size, className }))}
183206
onKeyDown={(e) => {
184207
if (e.key === 'Enter') {
185208
e.preventDefault();
186-
setIsSuggestionsBoxEnabled(false);
187-
onSubmit();
209+
setIsSuggestionsEnabled(false);
210+
onSubmit(query);
188211
}
189212

190213
if (e.key === 'Escape') {
191214
e.preventDefault();
192-
setIsSuggestionsBoxEnabled(false);
215+
setIsSuggestionsEnabled(false);
193216
}
194217

195218
if (e.key === 'ArrowDown') {
196219
e.preventDefault();
197-
setIsSuggestionsBoxEnabled(true);
220+
setIsSuggestionsEnabled(true);
198221
focusSuggestionsBox();
199222
}
200223

@@ -203,16 +226,29 @@ export const SearchBar = ({
203226
}
204227
}}
205228
>
229+
<SearchHistoryButton
230+
isToggled={isHistorySearchEnabled}
231+
onClick={() => {
232+
setQuery("");
233+
setIsHistorySearchEnabled(!isHistorySearchEnabled);
234+
setIsSuggestionsEnabled(true);
235+
focusEditor();
236+
}}
237+
/>
238+
<Separator
239+
className="mx-1 h-6"
240+
orientation="vertical"
241+
/>
206242
<CodeMirror
207243
ref={editorRef}
208244
className="overflow-x-auto scrollbar-hide w-full"
209-
placeholder={"Search..."}
245+
placeholder={isHistorySearchEnabled ? "Filter history..." : "Search..."}
210246
value={query}
211247
onChange={(value) => {
212248
setQuery(value);
213249
// Whenever the user types, we want to re-enable
214250
// the suggestions box.
215-
setIsSuggestionsBoxEnabled(true);
251+
setIsSuggestionsEnabled(true);
216252
}}
217253
theme={theme}
218254
basicSetup={false}
@@ -223,7 +259,9 @@ export const SearchBar = ({
223259
<SearchSuggestionsBox
224260
ref={suggestionBoxRef}
225261
query={query}
226-
onCompletion={(newQuery: string, newCursorPosition: number) => {
262+
suggestionQuery={suggestionQuery}
263+
suggestionMode={suggestionMode}
264+
onCompletion={(newQuery: string, newCursorPosition: number, autoSubmit = false) => {
227265
setQuery(newQuery);
228266

229267
// Move the cursor to it's new position.
@@ -242,8 +280,12 @@ export const SearchBar = ({
242280

243281
// Re-focus the editor since suggestions cause focus to be lost (both click & keyboard)
244282
editorRef.current?.view?.focus();
283+
284+
if (autoSubmit) {
285+
onSubmit(newQuery);
286+
}
245287
}}
246-
isEnabled={isSuggestionsBoxEnabled}
288+
isEnabled={isSuggestionsEnabled}
247289
onReturnFocus={() => {
248290
focusEditor();
249291
}}
@@ -255,17 +297,40 @@ export const SearchBar = ({
255297
setIsSuggestionsBoxFocused(document.activeElement === suggestionBoxRef.current);
256298
}}
257299
cursorPosition={cursorPosition}
258-
onSuggestionModeChanged={(newSuggestionMode) => {
259-
if (suggestionMode !== newSuggestionMode) {
260-
console.debug(`Suggestion mode changed: ${suggestionMode} -> ${newSuggestionMode}`);
261-
}
262-
setSuggestionMode(newSuggestionMode);
263-
}}
264-
onSuggestionQueryChanged={(suggestionQuery) => {
265-
setSuggestionQuery(suggestionQuery);
266-
}}
267300
{...suggestionData}
268301
/>
269302
</div>
270303
)
304+
}
305+
306+
const SearchHistoryButton = ({
307+
isToggled,
308+
onClick,
309+
}: {
310+
isToggled: boolean,
311+
onClick: () => void
312+
}) => {
313+
return (
314+
<Tooltip>
315+
<TooltipTrigger
316+
asChild={true}
317+
>
318+
{/* @see : https://github.com/shadcn-ui/ui/issues/1988#issuecomment-1980597269 */}
319+
<div>
320+
<Toggle
321+
pressed={isToggled}
322+
className="h-6 w-6 min-w-6 px-0 p-1 cursor-pointer"
323+
onClick={onClick}
324+
>
325+
<CounterClockwiseClockIcon />
326+
</Toggle>
327+
</div>
328+
</TooltipTrigger>
329+
<TooltipContent
330+
side="bottom"
331+
>
332+
Search history
333+
</TooltipContent>
334+
</Tooltip>
335+
)
271336
}

0 commit comments

Comments
 (0)